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:
Creating Records with enum fields and foreign key relationships.
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.
>>> import os
>>> from pprint import pprint
>>> 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 cables endpoint
>>> pprint(nautobot.dcim.cables.choices())
{'length_unit': [{'display': 'Kilometers', 'value': 'km'},
{'display': 'Meters', 'value': 'm'},
{'display': 'Centimeters', 'value': 'cm'},
{'display': 'Miles', 'value': 'mi'},
{'display': 'Feet', 'value': 'ft'},
{'display': 'Inches', 'value': 'in'}],
'termination_a_type': [{'display': 'dcim | interface',
'value': 'dcim.interface'},
{'display': 'dcim | power feed',
'value': 'dcim.powerfeed'},
{'display': 'circuits | circuit termination',
'value': 'circuits.circuittermination'},
{'display': 'dcim | console port',
'value': 'dcim.consoleport'},
{'display': 'dcim | console server port',
'value': 'dcim.consoleserverport'},
{'display': 'dcim | front port',
'value': 'dcim.frontport'},
{'display': 'dcim | power outlet',
'value': 'dcim.poweroutlet'},
{'display': 'dcim | power port',
'value': 'dcim.powerport'},
{'display': 'dcim | rear port',
'value': 'dcim.rearport'}],
'termination_b_type': [{'display': 'dcim | interface',
'value': 'dcim.interface'},
{'display': 'dcim | power feed',
'value': 'dcim.powerfeed'},
{'display': 'circuits | circuit termination',
'value': 'circuits.circuittermination'},
{'display': 'dcim | console port',
'value': 'dcim.consoleport'},
{'display': 'dcim | console server port',
'value': 'dcim.consoleserverport'},
{'display': 'dcim | front port',
'value': 'dcim.frontport'},
{'display': 'dcim | power outlet',
'value': 'dcim.poweroutlet'},
{'display': 'dcim | power port',
'value': 'dcim.powerport'},
{'display': 'dcim | rear port',
'value': 'dcim.rearport'}],
'type': [{'display': 'CAT3', 'value': 'cat3'},
{'display': 'CAT5', 'value': 'cat5'},
{'display': 'CAT5e', 'value': 'cat5e'},
{'display': 'CAT6', 'value': 'cat6'},
{'display': 'CAT6a', 'value': 'cat6a'},
{'display': 'CAT7', 'value': 'cat7'},
{'display': 'CAT7a', 'value': 'cat7a'},
{'display': 'CAT8', 'value': 'cat8'},
{'display': 'Direct Attach Copper (Active)', 'value': 'dac-active'},
{'display': 'Direct Attach Copper (Passive)', 'value': 'dac-passive'},
{'display': 'MRJ21 Trunk', 'value': 'mrj21-trunk'},
{'display': 'Coaxial', 'value': 'coaxial'},
{'display': 'Multimode Fiber', 'value': 'mmf'},
{'display': 'Multimode Fiber (OM1)', 'value': 'mmf-om1'},
{'display': 'Multimode Fiber (OM2)', 'value': 'mmf-om2'},
{'display': 'Multimode Fiber (OM3)', 'value': 'mmf-om3'},
{'display': 'Multimode Fiber (OM4)', 'value': 'mmf-om4'},
{'display': 'Singlemode Fiber', 'value': 'smf'},
{'display': 'Singlemode Fiber (OS1)', 'value': 'smf-os1'},
{'display': 'Singlemode Fiber (OS2)', 'value': 'smf-os2'},
{'display': 'Active Optical Cabling (AOC)', 'value': 'aoc'},
{'display': 'Power', 'value': 'power'},
{'display': 'Other', 'value': 'other'}]}
>>>
>>> # Accessing entries from choices for the type field
>>> cable_types_choices = nautobot.dcim.cables.choices()['type']
>>> cable_types_choices[3]
{'value': 'cat6', 'display': 'CAT6'}
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:
Role
Device Type
Location
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, role, and location to get their ID
>>> device_type = nautobot.dcim.device_types.get(model="c9300-48")
>>> role = nautobot.extras.roles.get(name="access")
>>> location = nautobot.dcim.locations.get(name="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,
... role=role.id,
... location=location.id,
... status={"name": "Active"},
... )
>>> type(hq_access_1)
"<class 'pynautobot.models.dcim.Devices'>"
>>> hq_access_1.created
'2023-09-30T07:56:23.664150Z'
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 Model has model
field, and Role and Location Models all have a name
field that can be used to lookup a specific Record. name
is not unique for Location.
>>> 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={"model": "c9300-48"},
... role={"name": "access"},
... location="HQ",
... status="Active",
... )
>>>
>>> # Show that device was created in Nautobot
>>> hq_access_2.created
'2023-09-30T08:02:03.872486Z'
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": {"model": "c9300-48"},
... "role": {"name": "access"},
... "location": {"name": "HQ"},
... "status": "Active",
... },
... {
... "name": "hq-access-04",
... "device_type": {"model": "c9300-48"},
... "role": {"name": "access"},
... "location": {"name": "HQ"},
... "status": "Active",
... },
... ])
>>>
>>> # show that both devices were created in Nautobot
>>> hq_access_multiple
[<pynautobot.models.dcim.Devices ('hq-access-03') ...>, <pynautobot.models.dcim.Devices ('hq-access-04') at ...>]
>>>
>>> # We can access these Record objects as well
>>> hq_access_03 = hq_access_multiple[0]
>>> hq_access_03.created
'2023-09-30T08:14:24.756447Z'
>>> # Use get calls to get the newly created devices
>>> hq_access_03 = nautobot.dcim.devices.get(name="hq-access-03")
>>> hq_access_03.created
'2023-09-30T08:14:24.756447Z'
>>> hq_access_04 = nautobot.dcim.devices.get(name="hq-access-04")
>>> hq_access_04.created
'2023-09-30T08:14:24.790198Z'
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
, role
, location
, 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
, role
, and location
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.'],
'role': ['This field is required.'],
'location': ['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:
The Record referenced by the foreign key does not exist in the related Model.
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',
... role={"name": "access"},
... location={"name": "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: {'pk': UUID('2302f2a1-2ed4-4ac9-a43a-285c95190071')}"
]
}
>>> # Foreign Key by fields do not exist
>>> hq_access_5 = devices.create(
... name="hq-access-05",
... device_type={"model": "non-existent-type"},
... role={"name": "access"},
... location={"name": "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: " \
"{'model': '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={"manufacturer": { "name": "Cisco" } },
... role={"name": "access"},
... location={"name": "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: " \
" {'manufacturer__name': 'Cisco'}"
]
}
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:
Passing an invalid type.
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"},
... role={"name": "access"},
... location={"name": "HQ"},
... status="Active",
... rack={"name": "hq-001"},
... face="front",
... position=1,
... )
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"},
... role={"name": "access"},
... location={"name": "HQ"},
... status={"name": "Active"},
... rack={"name": "hq-001"},
... face="front",
... position=1,
... )
Traceback (most recent call last):
...
pynautobot.core.query.RequestError:
The request failed with code 400 Bad Request:
{
'non_field_errors': [
'The position and face is already occupied on this rack. The fields rack, position, face must make a unique set.'
]
}