Skip to content

Primary Keys

Each Nautobot instance primary key is deterministically generated using UUID5, based on the source content type and primary key.

Note

If the source primary key is already a UUID, it is passed through without change.

To generate the UUID, the source_pk_to_uuid() function is used. It takes two arguments: the source content type and the source primary key. Internally, it uses settings.SECRET_KEY to define the UUIDv5 namespace. Since the secret key is the same, it's possible to repeat the import process generating the same UUIDs.

It's possible to customize the primary key generation for particular source model.

This feature is used to deduplicate IP addresses and prefixes, as Nautobot has a strict database constraint for duplicates, but NetBox allows it. As a solution, Nautobot's IP address primary key is derived from the IP address value instead of the ID. Then duplicate source IP addresses are merged into a single Nautobot IP address.

An example how to derive Nautobot UUID from the field name instead of ID:

from nautobot_netbox_importer.generator.base import source_pk_to_uuid

def my_setup(adapter: SourceAdapter) -> None:
    """Customize the mapping from source to Nautobot"""

    # Function accepts the source data and returns the Nautobot primary key
    def get_pk_from_name(source: RecordData) -> Uid:
        """Generate Nautobot primary key from name."""
        # Get the name field value from the source data
        name = name_field.get_source_value(source)
        if not name:
            raise ValueError("Missing name for the record")

        # Generate the UUID5 using the source content type and name
        return source_pk_to_uuid(model_wrapper.content_type, name)

    # Configure the model with the custom primary key generation
    model_wrapper = configure_model(
        "my_app.my_model",
        get_pk_from_data=get_pk_from_name,  # Customize the primary key generation
        fields={
            "name": "",  # Specify the `name` field
        },
    )

    # Get the name field source reference
    name_field = model_wrapper.fields["name"]

Once the primary key is generated, it's cached, see more in Caching.

Nautobot Primary Key Generation Flowchart

Here's a flowchart showing how primary keys are generated in the Nautobot importer, starting with get_pk_from_data:

flowchart TD
    start("get_pk_from_data(data)")
    customFunc{"Custom\nget_pk_from_data_hook\nexists?"}
    useCustomFunc["Use custom function\nto generate PK"]
    checkIdentifiers{"Are identifiers\ndefined?"}
    usePkField["Use data[pk_field.name]\nas UID"]
    checkDataUidCache{"Is data UID\nin cache?"}
    returnCachedUid["Return cached UID"]
    getFromIdentifiers["Get PK from identifiers"]
    cacheResult["Cache result"]
    returnResult["Return result"]

    getPkFromIdentifiers["get_pk_from_identifiers(data)"]
    identifiersInCache{"Are identifiers\nin cache?"}
    returnIdentifiersCache["Return cached value"]
    tryNautobot["Try to get instance\nfrom Nautobot"]
    nautonotExists{"Instance\nexists?"}
    useNautobotUid["Use Nautobot UID"]
    fallbackUid["Fall back to get_pk_from_uid()"]

    getPkFromUid["get_pk_from_uid(uid)"]
    uidInCache{"Is UID in cache?"}
    returnUidCache["Return cached value"]
    checkFieldType{"What is\nPK field type?"}
    useUuid["Generate UUID from\nsource_pk_to_uuid()"]
    useAutoIncrement["Increment counter\nand use as PK"]
    unsupportedType["Raise error:\nUnsupported type"]
    cacheUid["Cache UID mapping"]

    start --> customFunc
    customFunc -->|Yes| useCustomFunc --> returnResult
    customFunc -->|No| checkIdentifiers

    checkIdentifiers -->|No| usePkField --> getPkFromUid
    checkIdentifiers -->|Yes| checkDataUidCache

    checkDataUidCache -->|Yes| returnCachedUid --> returnResult
    checkDataUidCache -->|No| getFromIdentifiers
    getFromIdentifiers --> getPkFromIdentifiers
    getPkFromIdentifiers --> identifiersInCache

    identifiersInCache -->|Yes| returnIdentifiersCache --> cacheResult
    identifiersInCache -->|No| tryNautobot --> nautonotExists

    nautonotExists -->|Yes| useNautobotUid --> cacheResult
    nautonotExists -->|No| fallbackUid --> getPkFromUid

    getPkFromUid --> uidInCache
    uidInCache -->|Yes| returnUidCache --> returnResult
    uidInCache -->|No| checkFieldType

    checkFieldType -->|UUID| useUuid --> cacheUid
    checkFieldType -->|Auto increment| useAutoIncrement --> cacheUid
    checkFieldType -->|Other| unsupportedType

    cacheUid --> returnResult
    cacheResult --> returnResult

This flowchart shows:

  1. How the process starts by checking for a custom function
  2. The branching logic based on whether identifiers are defined
  3. Multiple caching points to avoid redundant lookups
  4. How the system looks for existing Nautobot instances
  5. How different field types (UUID vs auto-increment) are handled differently
  6. The final caching and return of the result

The process ensures each record gets a consistent primary key that can be reliably referenced throughout the import process.