Creating Records

The Creating a Record section provides an example of creating a single Record for a Model without any foreign key relationships. Additionally, some fields are an enum type, which limits the acceptable values to a set of choices.

This section demonstrates workflows for:

  1. Creating Records with enum fields and foreign key relationships.

  2. Creating multiple Records with a single method call.

Finally, some common errors are shown, and the Exceptions that are then raised by these errors.

Obtaining Choices

For fields that are enum type, Endpoint objects have a choices() method to provide a mapping of enum fields to their list of acceptable choices. The examples used in the document revolve around creating a new Device Record. Creating a Device requires specifying the status field, which is an example of an enum. Below demonstrates how to view the list of available choices for Device Status.

import os
from pynautobot import api

url = os.environ["NAUTOBOT_URL"]
token = os.environ["NAUTOBOT_TOKEN"]
nautobot = api(url=url, token=token)

# Get the choices for enum fields for the devices endpoint
nautobot.dcim.devices.choices()
{
    'face': [
        {
            'value': 'front',
            'display_name': 'Front'
        },
        {
            'value', 'rear',
            'display_name': 'Rear'
        }
    ],
    'status': [
      {
          'value': 'active',
          'display_name': 'Active'
      },
      {
          'value': 'maintenance',
          'display_name': 'Maintenance'
      },
      {
          'value': 'staged',
          'display_name': 'Staged'
      }
    ]
}

# Accessing entries from choices for the status field
device_status_choices = nautobot.dcim.devices.choices()['status']
device_status_choices[0]
{'value': 'active', 'display_name': 'Active'}

Tip

The list of available status choices is configurable, so the output will vary between implementations.

Warning

In order to avoid repeated calls to Nautobot, choices are cached on the Endpoint object. It is advisable to either create new Endpoint objects or delete the _choices attribute on Endpoints periodically.

Creating Objects with Foreign Key Relationships

Creating a Device in Nautobot requires the following fields to specify a foreign key relationship:

  • Device Type

  • Device Role

  • Site

This can be accomplished by providing the Primary Key (PK), which is an UUID string or a dictionary with key/value pairs that make the object unique.

The first example provides a workflow for obtaining the IDs of the foreign key relationships by using the get() method from the Endpoint object, and then referencing the id of those objects to create a new Device.

nautobot = api(url=url, token=token)

# Get objects for device_type, device_role, and site to get their ID
device_type = nautobot.dcim.device_types.get(slug="c9300-48")
device_role = nautobot.dcim.device_roles.get(slug="access")
site = nautobot.dcim.sites.get(slug="hq")

# Create new device using foreign key IDs
devices = nautobot.dcim.devices
hq_access_1 = devices.create(
    name="hq-access-01",
    device_type=device_type.id,
    device_role=device_role.id,
    site=site.id,
    status="active",
)
type(hq_access_1)
"<class 'pynautobot.models.dcim.Devices'>"
hq_access_1.created
'2021-01-01'

The above works, but it requires three get() calls. The next example demonstrates a simpler interface for creating a device by passing dictionary objects instead of using the Primary Key. The dictionaries passed for these fields use key/value pairs to lookup the Record with matching field/value pairs in the related Model.

The Device Type, Device Role, and Site Models all have a slug field that can be used to lookup a specific Record.

nautobot = api(url=url, token=token)
device_name = "hq-access-02"

# Create new device using fields to uniquely identify foreign key relationships
devices = nautobot.dcim.devices
hq_access_2 = devices.create(
    name=device_name,
    device_type={"slug": "c9300-48"},
    device_role={"slug": "access"},
    site={"slug": "hq"},
    status="active",
)

# Show that device was created in Nautobot
hq_access_2.created
'2021-01-01'

Creating Multiple Objects

It is also possible to create multiple Records of the same Model in a single create() call. This is done by passing a list of dictionaries instead of keyword arguments.

nautobot = api(url=url, token=token)

# Create multiple new devices with a single method call
devices = nautobot.dcim.devices
hq_access_multiple = devices.create([
    {
        "name": "hq-access-03",
        "device_type": {"slug": "c9300-48"},
        "device_role": {"slug": "access"},
        "site": {"slug": "hq"},
        "status": "active",
    },
    {
        "name": "hq-access-04",
        "device_type": {"slug": "c9300-48"},
        "device_role": {"slug": "access"},
        "site": {"slug": "hq"},
        "status": "active",
    },
])

# show that both devices were created in Nautobot
hq_access_multiple
[hq-access-03, hq-access-04]

# We can access these Record objects as well
hq_access_03 = hq_access_multiple[0]
hq_access_03.created
'2021-01-01'

# Use get calls to get the newly created devices
hq_access_03 = nautobot.dcim.devices.get(name="hq-access-03")
hq_access_03.created
'2021-01-01'
hq_access_04 = nautobot.dcim.devices.get(name="hq-access-04")
hq_access_04.created
'2021-01-01'

Common Errors

When creating new Records with pynautobot, there are three common types of errors:

Note

The messages in the Exceptions provide context to identify the exact issue that causes the failure.

Missing a Required Field

A RequestError is raised when a required field is not passed to the create() method. Creating a new Device requires passing the name, device_type, device_role, site, and status fields. The below example demonstrates passing only name and status when creating a Device; as expected, an Exception is raised indicating that device_type, device_role, and site are also required fields.

>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     status="active",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'device_type': ['This field is required.'],
  'device_role': ['This field is required.'],
  'site': ['This field is required.']
}

Unable to Resolve a Reference to a Foreign Key Relationship

Another reason that a RequestError could be raised is for passing in foreign key fields that cannot be resolved. There are two reasons that can cause a foreign key to not be found:

  1. The Record referenced by the foreign key does not exist in the related Model.

  2. The related Model has multiple Records matching the constraints specified in the field/value dictionary.

The first two examples below make a reference to a non-existent device_type: one uses the Primary Key, and the other uses a dictionary to lookup the Record in the related Device Type Model.

>>> # Attempt to create device with non-existent device type ID
>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     device_type='2302f2a1-2ed4-4ac9-a43a-285c95190071',
...     device_role={"slug": "access"},
...     site={"slug": "hq"},
...     status="active",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'device_type': [
    'Related object not found using the provided numeric ID: 2302f2a1-2ed4-4ac9-a43a-285c95190071'
  ]
}
>>> # Foreign Key by fields do not exist
>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     device_type={"slug": "non-existent-type"},
...     device_role={"slug": "access"},
...     site={"slug": "hq"},
...     status="active",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'device_type': [
    "Related object not found using the provided attributes: " \
    "{'slug': 'non-existent-type'}"
  ]
}

The final example uses a dictionary for device_type that matches multiple Device Types in the database.

>>> # Non-unique data passed in for Foreign Key field
>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     device_type={"model": "c9300-48"},
...     device_role={"slug": "access"},
...     site={"slug": "hq"},
...     status="active",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'device_type': [
    "Multiple objects match the provided attributes: " \
    "{'model': 'c9300-48'}"
  ]
}

The Data Sent Does Not Adhere to the Database Schema

The last type of common error is sending data that does not adhere to the schema for a field. The examples below show:

  1. Passing an invalid type.

  2. Passing a valid type that does not adhere to the defined constraints.

In the examples below, the position field of a Device is used to demonstrate these errors. The position field is a reference to the rack units it is mounted into in the related Rack Record. The rack referenced in the examples is a 42U rack, which means it supports rack units 1-42. This field uses an integer type, and has the following constraints:

  • The rack units assigned must exist in the Rack Record.

  • The rack units assigned must not be occupied by an existing device.

The first example passes a string instead of an integer.

>>> # Attempt to provide invalid type for position
>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     device_type={"model": "c9300-48"},
...     device_role={"slug": "access"},
...     site={"slug": "hq"},
...     status="active",
...     rack={"name": "hq-001"},
...     face=1,
...     position="high",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'position': ['A valid integer is required.']
}

The last example specifies a rack unit higher than what is supported by Rack Record.

>>> # Attempt to provide invalid rack unit for position
>>> hq_access_5 = devices.create(
...     name="hq-access-05",
...     device_type={"model": "c9300-48"},
...     device_role={"slug": "access"},
...     site={"slug": "hq"},
...     status="active",
...     rack={"name": "hq-001"},
...     face=1,
...     position="high",
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
  'position': [
    'U100 is already occupied or does not have sufficient space' \
    'to accommodate this device type: c9300-48 (1U)'
  ]
}