Skip to content

Advanced Usage

Threading

pynetbox supports multithreaded page fetching for .filter() and .all() queries, which can significantly improve performance when iterating large result sets.

NetBox Configuration Required

For threading to be effective, MAX_PAGE_SIZE in your NetBox installation must be set to a finite value (not 0 or None). The default of 1000 is usually a good choice.

Enabling Threading

Enable threading globally by passing threading=True when constructing the API client:

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='your-token',
    threading=True,
)

# .all() and .filter() now fetch pages in parallel
devices = nb.dcim.devices.all()

How It Works

When threading is enabled, pynetbox issues an initial request to determine the total record count, then dispatches concurrent requests for the remaining pages. The records are streamed back through the same RecordSet interface as a single-threaded query.

Threading is opt-in per API client; thread safety beyond pynetbox's own page-fetching is the caller's responsibility.

Example

import pynetbox
import time

# Single-threaded
nb = pynetbox.api('http://localhost:8000', token='your-token')
start = time.time()
devices = list(nb.dcim.devices.all())
print(f"Without threading: {time.time() - start:.2f}s")

# Multithreaded
nb_threaded = pynetbox.api(
    'http://localhost:8000',
    token='your-token',
    threading=True,
)
start = time.time()
devices = list(nb_threaded.dcim.devices.all())
print(f"With threading: {time.time() - start:.2f}s")

Filter Validation

NetBox does not validate filter parameters passed to list endpoints. An unrecognized parameter is silently ignored, which means a typo in a .filter() or .get() call can quietly return the entire table.

pynetbox can optionally validate filter parameters against NetBox's OpenAPI specification before making the request, raising ParameterValidationError if any parameter is unrecognized.

Enabling Strict Filters Globally

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='your-token',
    strict_filters=True,
)

try:
    nb.dcim.devices.filter(non_existing_filter='value')
except pynetbox.ParameterValidationError as e:
    print(f"Invalid filter: {e}")

Per-Request Validation

Validation can also be toggled per request by passing strict_filters directly to .filter() or .get(). The per-request value overrides the global setting.

nb = pynetbox.api('http://localhost:8000', token='your-token')

# Enable for one request (when not globally enabled)
try:
    nb.dcim.devices.filter(
        non_existing_filter='aaaa',
        strict_filters=True,
    )
except pynetbox.ParameterValidationError as e:
    print(f"Invalid filter: {e}")

# Disable for one request (when globally enabled)
nb_strict = pynetbox.api(
    'http://localhost:8000',
    token='your-token',
    strict_filters=True,
)
# Skips validation; NetBox will accept the request but ignore the unknown filter
nb_strict.dcim.devices.filter(
    non_existing_filter='aaaa',
    strict_filters=False,
)

Custom field filters

Custom field filters (cf_<fieldname> and their lookup suffixes such as cf_<fieldname>__gt) are dynamic and not listed in the OpenAPI specification. They are skipped by the validator and never raise ParameterValidationError.

Benefits

  • Catch typos early: surface misspelled filter names before they cause silent full-table scans.
  • Better error messages: failures point to the exact invalid parameter.
  • Safer development: enable globally during development and disable in production hot paths if needed.

Example

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='your-token',
    strict_filters=True,
)

# Valid filter
devices = nb.dcim.devices.filter(site='datacenter1')

# Invalid filter
try:
    devices = nb.dcim.devices.filter(iste='datacenter1')   # typo
except pynetbox.ParameterValidationError as e:
    print(f"Error: {e}")

Custom Sessions

You can substitute pynetbox's default requests.Session with your own to customize HTTP behavior such as headers, SSL verification, timeouts, and retries.

Custom Headers

To set custom headers on every request:

import pynetbox
import requests

session = requests.Session()
session.headers = {'mycustomheader': 'test'}

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)
nb.http_session = session

Custom headers are merged with the headers pynetbox sets internally.

Disabling SSL Verification

To disable SSL certificate verification (for self-signed certificates in lab environments). See the requests docs for additional options:

import pynetbox
import requests

session = requests.Session()
session.verify = False

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)
nb.http_session = session

Timeouts

Setting a default timeout requires a custom HTTP adapter:

import pynetbox
import requests
from requests.adapters import HTTPAdapter


class TimeoutHTTPAdapter(HTTPAdapter):
    def __init__(self, *args, **kwargs):
        self.timeout = kwargs.pop('timeout', 5)
        super().__init__(*args, **kwargs)

    def send(self, request, **kwargs):
        kwargs['timeout'] = self.timeout
        return super().send(request, **kwargs)


adapter = TimeoutHTTPAdapter(timeout=10)
session = requests.Session()
session.mount('http://', adapter)
session.mount('https://', adapter)

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)
nb.http_session = session

File Uploads (Image Attachments)

pynetbox supports file uploads on endpoints that accept them, such as image attachments. When a file-like object (anything with a callable .read()) is passed to .create(), pynetbox automatically switches to multipart/form-data encoding instead of JSON.

Creating an Image Attachment

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)

with open('/path/to/image.png', 'rb') as f:
    attachment = nb.extras.image_attachments.create(
        object_type='dcim.device',
        object_id=1,
        image=f,
        name='rack-photo.png',
    )

Using io.BytesIO

In-memory file objects work the same way:

import io
import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)

image_data = b'...'   # raw image bytes
file_obj = io.BytesIO(image_data)
file_obj.name = 'generated-image.png'   # optional: filename hint

attachment = nb.extras.image_attachments.create(
    object_type='dcim.device',
    object_id=1,
    image=file_obj,
)

Custom Filename and Content-Type

For full control over the multipart part, pass a tuple in place of the file object. The accepted forms are (filename, file_object) and (filename, file_object, content_type):

with open('/path/to/image.png', 'rb') as f:
    attachment = nb.extras.image_attachments.create(
        object_type='dcim.device',
        object_id=1,
        image=('custom-name.png', f, 'image/png'),
    )

Multi-Format Responses

Some endpoints can return data in multiple formats. The rack elevation endpoint, for example, supports both JSON (a list of rack-unit objects) and SVG (a rendered diagram).

Rack Elevation as JSON

By default the elevation endpoint returns JSON:

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f',
)

rack = nb.dcim.racks.get(123)

# Returns a list of RU objects
for unit in rack.elevation.list():
    print(unit.id, unit.name)

Rack Elevation as SVG

Pass render='svg' to get a rendered SVG diagram as a string:

rack = nb.dcim.racks.get(123)

svg_diagram = rack.elevation.list(render='svg')
# '<svg xmlns="http://www.w3.org/2000/svg">...</svg>'

with open('rack-elevation.svg', 'w') as f:
    f.write(svg_diagram)