Update Your Ansible Nautobot Environment & Helm Chart

Blog Detail

With the release of Nautobot 2.1.9 and 1.6.16 came new requirements for pynautobot to include an authentication token that for some initial calls that were not previously required. So to make sure that pynautobot (and subsequently Nautobot Ansible) and Nautobot Helm Chart work with the most recent version of Nautobot, new versions have been released.

pynautobot & Nautobot Ansible

First to check what version of pynautobot you have, you can run pip list to get that environment. Here is an example of using grep to only look for pynautobot.

❯ pip list | grep pynautobot
pynautobot         2.0.2

Nautobot 1.6 Environments

If you are continuing on the LTM release train of 1.6, your pynautobot needs to be upgraded to 1.5.2 in order to continue using the Ansible modules (4.5.0). No update to the Ansible modules is required-only the underlying pynautobot version. Complete this with:

pip install pynautobot==1.5.2

Accidental Upgrade to 2.x of pynautobot?

If you accidentally upgrade to the latest version of pynautobot but intended to be on 1.x, just issue the same command as above and you will get the right version. Nothing further would needs to be done-no harm.

pip install pynautobot=-1.5.2

Nautobot 2.1 Environments

For those with the latest Nautobot application version of 2.1.9, please upgrade the pynautobot instance in your Ansible environment to the latest of 2.1.1

pip install --upgrade pynautobot

Nautobot Helm Chart

First to check what version of Nautobot Helm Chart you have configured, you can run helm show chart nautobot/nautobot to get the full information about the configured chart. There will be multiple versions you will see in the output, the chart version that matters is the last line in the output and is a root key in the yaml output.

❯ helm show chart nautobot/nautobot
annotations:

... Truncated for bevity ...

sources:
- https://github.com/nautobot/nautobot
- https://github.com/nautobot/helm-charts
version: 2.0.5

Warning – READ BEFORE PROCEEDING

The latest version of the helm chart has a default version for Nautobot that is set to 2.1.9, if you are NOT providing custom image or statically declaring the version you WILL be upgraded to 2.1.9. For more information on using a custom image please see the documentation here or for using the Network to Code maintained images with a specific version please ensure nautobot.image.tag is set to the tagged version you are expecting to use. Below are some examples for values.yaml provided to a helm release.

If you are on a 1.X.X version of the helm chart please review the upgrade guide here before proceeding.

Custom Image

nautobot:
  image:
    registry: "ghcr.io"
    repository: "my-namespace/nautobot"
    tag: "1.6.16-py3.11"
    pullPolicy: "Always"
    pullSecrets:
      - ghcr-pull-secret

Network to Code Image

nautobot:
  image:
    tag: "1.6.16-py3.11"

Update Helm Repo

Before you can use the new version of the helm chart you must update the helm repo.

❯ helm repo update nautobot
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "nautobot" chart repository
Update Complete. ⎈Happy Helming!⎈

Update Helm Release

Now you can proceed to update your helm release with the latest helm chart version.

❯ helm upgrade <name of helm release> -f values.yml --version 2.1.0
Release "nautobot" has been upgraded. Happy Helming!
NAME: nautobot
LAST DEPLOYED: Wed Mar 27 20:09:47 2024
NAMESPACE: default
STATUS: deployed
REVISION: 3
NOTES:
*********************************************************************
*** PLEASE BE PATIENT: Nautobot may take a few minutes to install ***
*********************************************************************

... Truncated for bevity ...

Conclusion

When issues do arise on playbooks that were previously working fine, it’s best to give your dependency software packages a quick update. Hope that this helps. Happy automating.

-Josh, Jeremy



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

GraphQL vs. REST API Case Study

Blog Detail

If you’ve been watching this space, you’ve seen me talking about Nautobot’s GraphQL capabilities and how GraphQL helps you:

  • GraphQL queries are much more efficient than RESTful queries
  • GraphQL makes your life easier by making data more accessible
  • The above results in dramatic improvement in your quality of life

This post is a case study in those aspects. It will empirically demonstrate how GraphQL:

  • Minimizes your number of queries
  • Returns only the data you want
  • Makes it so you don’t have to manually filter data and build the desired data structure in a script
  • Creates faster automation
  • Reduces your workload

I will be running this case study using https://demo.nautobot.com/. Readers are encouraged to follow along, using the scripts below.

The Problem Statement

In this case study, the goal is to gather specific information for certain network elements from Nautobot. Specifically, we want a data structure with the following information:

  • We want information from all devices within the ams site
  • The data structure should organize information so that all the data is grouped on a per-device basis
  • We want this specific data for each device:
    • Device name
    • Device role
    • All the interface names
    • The list of IP address(es) for each interface, even if the interface has no configured IP address(es)

The GraphQL Solution

The GraphQL solution will leverage the pynautobot Python package, which provides a customized and efficient way to programmatically query Nautobot via GraphQL.

Also, from an earlier blog post in this GraphQL series, recall that you can craft GraphQL queries in Nautobot’s GraphiQL interface.

Here is the script we will use to accomplish our task using GraphQL:

import pynautobot

from pprint import pprint
from time import time

start_time = time()

# Nautobot URL and auth info
url = "https://demo.nautobot.com"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

# GraphQL query
query = """
query {
  devices(site:"ams") {
    name
    device_role {
      name
    }
    interfaces {
      name
        ip_addresses {
        address
      }
    }
  }
}
"""

print("Querying Nautobot via pynautobot.")
print()

print("url is: {}".format(url))
print()

print("query is:")
print(query)
print()

nb = pynautobot.api(url, token)

response = nb.graphql.query(query=query)
response_data = response.json

print("Here is the response data in json:")
pprint(response_data)
print()

end_time = time()

run_time = end_time - start_time

print("Run time = {}".format(run_time))

GraphQL Results

Here are the results when the script runs (snipped in places for brevity):

blogs/graphql_vs_restful % python3 -i graphql_query_ams_device_ints_pynautobot.py
Querying Nautobot via pynautobot.

url is: https://demo.nautobot.com

query is:

query {
  devices(site:"ams") {
    name
    device_role {
      name
    }
    interfaces {
      name
	  ip_addresses {
          address
      }
    }
  }
}


Here is the response data in json:
{'data': {'devices': [{'device_role': {'name': 'edge'},
                       'interfaces': [{'ip_addresses': [{'address': '10.11.192.0/32'}],
                                       'name': 'Ethernet1/1'},
                                      {'ip_addresses': [{'address': '10.11.192.2/32'}],
                                       'name': 'Ethernet2/1'},
                                      {'ip_addresses': [{'address': '10.11.192.4/32'}],
                                       'name': 'Ethernet3/1'},
                                      {'ip_addresses': [{'address': '10.11.192.8/32'}],
                                       'name': 'Ethernet4/1'},
                                      < --- snip for brevity --- >
                                      {'ip_addresses': [],
                                       'name': 'Ethernet60/1'},
                                      {'ip_addresses': [{'address': '10.11.128.1/32'}],
                                       'name': 'Loopback0'},
                                      {'ip_addresses': [],
                                       'name': 'Management1'}],
                       'name': 'ams-edge-01'},
                      {'device_role': {'name': 'edge'},
                       'interfaces': [{'ip_addresses': [{'address': '10.11.192.1/32'}],
                                       'name': 'Ethernet1/1'},
                                      < --- snip for brevity --- >
                                      {'ip_addresses': [{'address': '10.11.128.2/32'}],
                                       'name': 'Loopback0'},
                                      {'ip_addresses': [],
                                       'name': 'Management1'}],
                       'name': 'ams-edge-02'},
                      {'device_role': {'name': 'leaf'},
                       'interfaces': [{'ip_addresses': [{'address': '10.11.192.5/32'}],
                                       'name': 'Ethernet1'},
                                      {'ip_addresses': [], 'name': 'Ethernet3'},
                                      < --- snip for brevity --- >
                                      {'ip_addresses': [{'address': '10.11.64.0/32'}],
                                       'name': 'vlan99'},
                                      {'ip_addresses': [{'address': '10.11.0.0/32'}],
                                       'name': 'vlan1000'}],
                       'name': 'ams-leaf-01'},
                      {'device_role': {'name': 'leaf'},
                       'interfaces': [{'ip_addresses': [{'address': '10.11.192.9/32'}],
                                       'name': 'Ethernet1'},
                                      {'ip_addresses': [{'address': '10.11.192.11/32'}],
                                       'name': 'Ethernet2'},
                                      {'ip_addresses': [], 'name': 'Ethernet3'},
                                      < --- snip for brevity --- >                                      
                                      {'ip_addresses': [], 'name': 'Management1'},
                                      {'ip_addresses': [{'address': '10.11.1.0/32'}],
                                       'name': 'vlan1000'}],
                        < --- snip for brevity --- >
                      {'device_role': {'name': 'leaf'},
                       'interfaces': [{'ip_addresses': [{'address': '10.11.192.33/32'}],
                                       'name': 'Ethernet1'},
                                      < --- snip for brevity --- >
                                      {'ip_addresses': [{'address': '10.11.7.0/32'}],
                                       'name': 'vlan1000'}],
                       'name': 'ams-leaf-08'}]}}

Run time = 1.8981318473815918
>>> 

Take specific note of the following GraphQL features demonstrated above:

  • The returned data comes back in a structure that matches that of the query
  • GraphQL returns only the requested data
  • The returned data is ready for programmatic parsing

Running the script six times produced an average of 2.23 seconds, returning data for ten devices in the ams site.

RESTful Solution

For the RESTful solution, we’re not concerned about matching the exact data structure returned by GraphQL. We’re only concerned with getting the same data into a structure that can be parsed for programmatic use.

The GraphQL results were grouped by device, and the RESTful solution will do that as well, but will have some small format changes.

Here is the format for the data structure that the RESTful solution will return:

{ 
  <device_1_name>: {
    'role': <device_1_role>,
    'interface_info': {
      <interface_1_name>: [list of ip addresses for interface_1],
      <interface_2_name>: [list of ip addresses for interface_2],
        . . . 
    }
  }
  . . . 
  <device_n_name>: {
    'role': <device_n_role>,
    'interface_info': {
      <interface_1_name>: [list of ip addresses for interface_1],
      <interface_2_name>: [list of ip addresses for interface_2],
        . . . 
    }
  }
}

The format above is slightly different than that of the GraphQL results, but is still programmatically parsable.

The RESTful script that returns the data is below. When examining it, take note of the following:

  • We had to artificially construct the data structure, which required a non-trivial amount of work
  • The RESTful script requires three distinct API calls, with some calls iterated multiple times
  • Each API call returns WAY more information than we are interested in
  • Since the call to get interface data for the ams site returns so much extraneous information, Nautobot applies the default limit of 50 results per call
    • The limit constraint reduces the load on the Nautobot server
    • With the default database in https://demo.nautobot.com, the call to get all the interface data iterates six times, returning up to 50 results per call
  • The call to get the IP address information must iterate once for each of the ten devices in the ams site
  • The RESTful script is over twice as long as the GraphQL script and is much more complex
  • The amount of time required to construct, test, and validate the RESTful script was well over an order magnitude longer than that required for the GraphQL script (your mileage may vary!)
"""
Use REST API calls to get the following info for each device in 'ams' site:
- device name
- device role
- interface info
  - interface name
  - ip address

"""

import json
import requests

from pprint import pprint
from time import time

start_time = time()

# Looking at the Nautobot API:
#  - /api/dcim/devices gives you name and role
#  - /api/dcim/interfaces gives you all the interface info
#  - /api/addresses gets IP address info

# Define general request components
payload = {}
headers = {
    "Content-Type": "application/json",
    "Authorization": "Token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}

##########################################

# Define devices url, query for 'ams' site devices
ams_dev_url = "https://demo.nautobot.com/api/dcim/devices?site=ams"

# Query Nautobot for the ams devices
ams_dev_resp = requests.get(ams_dev_url, headers=headers, data=payload)

# Turn the response text string into json
ams_devices_json = ams_dev_resp.json()

# Device info dict
device_info = {}

# Create a dict with device names as keys; the value for each key will be a dict.
for device in ams_devices_json["results"]:
    role = device['device_role']['display']
    dev_name = device["name"]
    device_info[dev_name] = {
        'role': role,
        'interface_info': {},
     }

print("device_info is:")
pprint(device_info)
print()
print()

##########################################

print("The GraphQL query returned all interfaces for a device, regardless of whether ")
print("an ip address was configured; we will match that here.")
print()

print("Gathering interface info for `ams` site.")

# Define url for device interfaces in 'ams' site
ams_interface_url = "https://demo.nautobot.com/api/dcim/interfaces?site=ams"

# Define a list to hold the interface info for `ams` site
ams_interface_info = []

# Account for ams_interface_url results limit; iterate url until 'next' url is None
while ams_interface_url is not None:
    ams_interface_resp = requests.get(ams_interface_url, headers=headers, data=payload)
    ams_interface_json = ams_interface_resp.json()
    ams_interface_url = ams_interface_json["next"]
    print("ams_interface_url is {}".format(ams_interface_url))
    ams_interface_info.extend(ams_interface_json["results"])
print()

print("Adding interface names to device_info for the appropriate device.")
# Filter out the interface names and add them in device_info
for interface_entry in ams_interface_info:
    dev_name = interface_entry["device"]["name"]
    interface_name = interface_entry["name"]
    device_info[dev_name]['interface_info'][interface_name] = []
print()

#####################################

print("Finally, gather the IP address info for each interface.")
print("This RESTful call returns only interfaces that have IP addresses configured.")
print()
ip_info_list = []

for device in device_info.keys():
    ip_url = "https://demo.nautobot.com/api/ipam/ip-addresses?device={}".format(device)

    # Account for ip_url results limit; iterate url until 'next' url is None
    while ip_url is not None:
        print("ip_url = {}".format(ip_url))
        ip_url_response = requests.get(ip_url, headers=headers, data=payload)
        ip_json = ip_url_response.json()
        ip_url = ip_json["next"]
        ip_info_list.extend(ip_json["results"])
print()

print("Add the IP address info to device_info.")
print()
for item in ip_info_list:
    device = item["assigned_object"]["device"]["name"]
    interface = item["assigned_object"]["name"]
    address = item["address"]
    device_info[device]['interface_info'][interface].append(address)

print("Here is the completed data structure:")
pprint(device_info)
print()
end_time = time()

run_time = end_time - start_time

print("Run time = {}".format(run_time))


Here are the results of the RESTful script:

blogs/graphql_vs_restful % python3 -i restful_api_query_ams_device_ints.py
device_info is:
{'ams-edge-01': {'interface_info': {}, 'role': 'edge'},
 'ams-edge-02': {'interface_info': {}, 'role': 'edge'},
 'ams-leaf-01': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-02': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-03': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-04': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-05': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-06': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-07': {'interface_info': {}, 'role': 'leaf'},
 'ams-leaf-08': {'interface_info': {}, 'role': 'leaf'}}


The GraphQL query returned all interfaces for a device, regardless of whether 
an ip address was configured; we will match that here.

Gathering interface info for `ams` site.
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=50&site=ams
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=100&site=ams
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=150&site=ams
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=200&site=ams
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=250&site=ams
ams_interface_url is https://demo.nautobot.com/api/dcim/interfaces/?limit=50&offset=300&site=ams
ams_interface_url is None

Adding interface names to device_info for the appropriate device.

Finally, gather the IP address info for each interface.
This RESTful call returns only interfaces that have IP addresses configured.

ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-edge-01
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-edge-02
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-01
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-02
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-03
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-04
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-05
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-06
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-07
ip_url = https://demo.nautobot.com/api/ipam/ip-addresses?device=ams-leaf-08

Add the IP address info to device_info.

Here is the completed data structure:
{'ams-edge-01': {'interface_info': {'Ethernet1/1': ['10.11.192.0/32'],
                                    'Ethernet10/1': ['10.11.192.32/32'],
                                    'Ethernet11/1': [],
                                    'Ethernet12/1': [],
                                     < --- snip for brevity --- >
                                    'Ethernet9/1': ['10.11.192.28/32'],
                                    'Loopback0': ['10.11.128.1/32'],
                                    'Management1': []},
                 'role': 'edge'},

 'ams-edge-02': {'interface_info': {'Ethernet1/1': ['10.11.192.1/32'],
                                    'Ethernet10/1': ['10.11.192.34/32'],
                                    < --- snip for brevity --- >
                                    'Loopback0': ['10.11.128.2/32'],
                                    'Management1': []},
                 'role': 'edge'},
 'ams-leaf-01': {'interface_info': {'Ethernet1': ['10.11.192.5/32'],
                                    < --- snip for brevity --- >
                                    'vlan99': ['10.11.64.0/32']},
                 'role': 'leaf'},
 < --- some devices snipped for brevity --- >
 'ams-leaf-07': {'interface_info': {'Ethernet1': ['10.11.192.29/32'],
                                    'Ethernet10': [],
                                    < --- snip for brevity --- >
                                    'vlan99': ['10.11.70.0/32']},
                 'role': 'leaf'},
 'ams-leaf-08': {'interface_info': {'Ethernet1': ['10.11.192.33/32'],
                                    < --- snip for brevity --- >
                                    'vlan99': ['10.11.71.0/32']},
                 'role': 'leaf'}}

Run time = 13.60936713218689
>>> 

Running the script six times produced an average run time of 14.9 seconds.

This script created a data structure that is not identical to the structure created by GraphQL, but is similar in nature and is still parsable.

Final Results

MethodAverage Run Time# of QueriesTime to Create Script
GraphQL2.2 seconds1~ 20 minutes
RESTful14.9 seconds17~ 200 minutes+

NOTE: These results are based on the baseline data in the Nautobot demo sandbox. If someone has modified the database, your actual results may vary a bit.

By any measure, GraphQL is the clear choice here! GraphQL allows a much simpler script that is much more efficient than REST.

Imagine your automation task being able to run an average of 12.2 seconds faster (14.9 – 2.2 seconds) by using GraphQL.

I also don’t want to undersell the amount of time and headache required to create the RESTful script, including parsing the REST data and crafting the data structure: it was not pleasant, and we should not talk about it again. Ever.

GraphQL Considerations for Server Load

Querying with GraphQL results in much less coding and post-processing for the user and is generally much more efficient than RESTful calls that achieve the same result.

However, the load on the Nautobot server must still be considered. Depending on the data you are after and your use case, it may make sense to:

  • Use multiple, targeted GraphQL queries instead of a single GraphQL query with a large scope
  • Use RESTful queries and offload the processing from the Nautobot server, doing the post-processing on your local host

Depending on how many sites and devices you have, the example query below may put undue load on the Nautobot server:

query {
  devices {
    name
    device_role {
      name
    }
    interfaces {
      name
	ip_addresses {
          address
      }
    }
  }
}

This script could cause a lot of undue load on the server because it is not targeted to a site or group of sites.

To ease the load, you could instead do the following:

1. Make a GraphQL query to return all the site names

 query {
     sites {
       name
   }
 }

2. Make additional GraphQL queries to programmatically iterate over each site name within the query parameter devices(site:"<site_name>"):

     query {
       devices(site:"ams") {
         name
         device_role {
           name
         }
         interfaces {
           name
         ip_addresses {
               address
           }
         }
       }
     }
     query {
       devices(site:"bkk") {
         name
         device_role {
           name
         }
         interfaces {
           name
         ip_addresses {
               address
           }
         }
       }
     }

    et cetera . . .

    Wrapping Up

    This case study validates the clear advantages GraphQL offers: simpler and faster automation, higher query efficiency, less time swimming in extraneous data, and thus less time coding. The great part is that Nautobot delivers GraphQL capabilities that you can leverage right now. Please do give it a try.

    If you have questions, you can check out these Network to Code resources for more info:


    Conclusion

    You can also hit us up on the #nautobot channel on NTC Slack.

    Thank you, and have a great day!

    -Tim



    ntc img
    ntc img

    Contact Us to Learn More

    Share details about yourself & someone from our team will reach out to you ASAP!

    Programmatic Nautobot GraphQL Requests via Pynautobot

    Blog Detail

    Thanks to its ability to efficiently allow the request of specific information that can span multiple resources, GraphQL is a powerful query tool. A single GraphQL API request can return information that would otherwise require literally dozens of individual REST queries and extensive data filtering, post-processing, and isolation.

    prior blog post in this series covered how to craft Nautobot GraphQL queries using the Nautobot GraphiQL interface. Recall that the GraphiQL interface is just for testing the GraphQL queries. This post will build on that knowledge, showing you how to leverage those queries to craft remote GraphQL requests for programmatic use via the open source pynautobot Python library. The pynautobot project page is here; the GitHub repository can be found here.

    The pynautobot Python library is an API wrapper that allows easy interactions with Nautobot. This specific article will focus the GraphQL capabilities within the library.

    The examples in this post all use the public Nautobot demo site https://demo.nautobot.com/. Readers are encouraged to follow along.

    Authentication

    Security tokens are typically required for programmatic access to Nautobot’s data. The following sections cover how to obtain a token and how to leverage it within Postman to craft GraphQL API calls.

    Tokens

    Nautobot security tokens are created in the Web UI. To view your token(s), navigate to the API Tokens page under your user profile. If there is no token present, create one or get the necessary permissions to do so.

    Getting Started with Pynautobot

    Installing Pynautobot

    pynautobot is a Python library. From your environment install it via pip3:

    % pip3 install pynautobot
    Collecting pynautobot
    Downloading pynautobot-1.0.1-py3-none-any.whl (29 kB)
    . . . 
    Successfully installed pynautobot-1.0.1
    % 
    

    Using Pynautbot

    This first example will walk through a pynautobot GraphQL query in a Python interpreter.

    Start a Python shell and import pynautobot:

    % python3
    Python 3.9.2 (v3.9.2:1a79785e3e, Feb 19 2021, 09:06:10) 
    [Clang 6.0 (clang-600.0.57)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 
    >>> 
    >>> import pynautobot
    >>> 
    

    Taking a quick look at Classes within pynautobotapi is the one we will want to use. The help tells us that the api Class can take url and token as parameters.

    >>> dir(pynautobot)
    ['AllocationError', 'ContentError', 'DistributionNotFound', 'RequestError',  << dunders snipped >>, 'api', 'core', 'get_distribution', 'models']
    >>> 
    >>> help(pynautobot.api)
    
    
    Help on class Api in module pynautobot.core.api:
    
    class Api(builtins.object)
     |  Api(url, token=None, threading=False)
     . . . 
    

    NOTE: the token from the https://demo.nautobot.com server is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

    Define a value token with the token value from your Nautobot implementation and a value url for your Nautobot server.

    >>> url = "https://demo.nautobot.com"
    >>> 
    >>> token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    >>> 
    
    

    This live example will use a query from a previous post. Define query in Python using the exact text from the query used in example 3 in the GraphiQL post:

    NOTE: the query text can be found in the last section of the blog, under Text Query Examples and Results

    >>> query = """
    ... query {
    ...  devices(site:"ams") {
    ...    name
    ...  }
    ... }
    ... """
    >>> 
    >>> query
    '\nquery {\n devices(site:"ams") {\n   name\n }\n}\n'
    >>> 
    >>> print(query)
    
    query {
     devices(site:"ams") {
       name
     }
    }
    >>> 
    

    Now construct the pynautobot.api Class with the parameters we have:

    >>> 
    >>> nb = pynautobot.api(url, token)
    >>> 
    

    Notice that the nb Object instance has a graphql attribute . . .

    >>> dir(nb)
    ['__class__', << dunders snipped >>, 'base_url', 'circuits', 'dcim', 'extras', 'graphql', 'headers', 'http_session', 'ipam', 'openapi', 'plugins', 'status', 'tenancy', 'threading', 'token', 'users', 'version', 'virtualization']
    >>> 
    

    . . . and graphql has a query method:

    >>> dir(nb.graphql)
    ['__class__', << dunders snipped >>, 'api', 'query', 'url']
    >>> 
    >>> help(nb.graphql.query)
    
    Help on method query in module pynautobot.core.graphql:
    
    query(query: str, variables: Optional[Dict[str, Any]] = None) -> pynautobot.core.graphql.GraphQLRecord method of pynautobot.core.graphql.GraphQLQuery instance
        Runs query against Nautobot Graphql endpoint.
        
        Args:
            query (str): Query string to send to the API
            variables (dict): Dictionary of variables to use with the query string, defaults to None
        
        Raises:
            GraphQLException:
                - When the query string is invalid.
            TypeError:
                - When `query` passed in is not of type string.
                - When `variables` passed in is not a dictionary.
            Exception:
                - When unknown error is passed in, please open an issue so this can be addressed.
        
        Examples:
            >>> try:
            ...   response.raise_for_status()
            ... except Exception as e:
            ...   variable = e
            ... 
            >>> variable
            >>> variable.response.json()
            {'errors': [{'message': 'Cannot query field "nae" on type "DeviceType". Did you mean "name" or "face"?', 'locations': [{'line': 4, 'column': 5}]}]}
        
            >>> variable.response.status_code
            400
        
        Returns:
            GraphQLRecord: Response of the API call
    
    
    

    Using the structure above, create the query and explore the results.

    >>> 
    >>> response = nb.graphql.query(query=query)
    >>> 
    >>> dir(response)
    ['__class__', << dunders snipped >>, 'json', 'status_code']
    >>> 
    >>> response.status_code
    200
    >>> 
    >>> response.json
    {'data': {'devices': [{'name': 'ams-edge-01'}, {'name': 'ams-edge-02'}, {'name': 'ams-leaf-01'}, {'name': 'ams-leaf-02'}, {'name': 'ams-leaf-03'}, {'name': 'ams-leaf-04'}, {'name': 'ams-leaf-05'}, {'name': 'ams-leaf-06'}, {'name': 'ams-leaf-07'}, {'name': 'ams-leaf-08'}]}}
    >>> 
    

    Here is response in a more readable format:

    >>> from pprint import pprint
    >>> pprint(response.json)
    {'data': {'devices': [{'name': 'ams-edge-01'},
                          {'name': 'ams-edge-02'},
                          {'name': 'ams-leaf-01'},
                          {'name': 'ams-leaf-02'},
                          {'name': 'ams-leaf-03'},
                          {'name': 'ams-leaf-04'},
                          {'name': 'ams-leaf-05'},
                          {'name': 'ams-leaf-06'},
                          {'name': 'ams-leaf-07'},
                          {'name': 'ams-leaf-08'}]}}
    >>> 
    

    The response object can be parsed, making the data accessible to your Python code:

    >>> 
    >>> devices = response.json['data']['devices']
    >>> 
    >>> devices[2]
    {'name': 'ams-leaf-01'}
    >>> 
    

    A Full Pynautobot Script

    This next example features a full script, using an example from the GraphQL Aliasing with Nautobot post in this series. You can also see the YouTube video that accompanies the post here.

    The script below features a query that uses GraphQL aliasing to request device names for multiple sites in a single query. The device names in each site will be returned grouped by the site name; each device name will be alaised with an inventory_hostname key (instead of name) for use in an Ansible environment.

    The query text for this script was pulled directly from the Aliasing Solves the Problem section of the referenced blog post and copied directly into the query variable.

    import json
    import pynautobot
    
    from pprint import pprint
    
    print("Querying Nautobot via pynautobot.")
    print()
    
    url = "https://demo.nautobot.com"
    print("url is: {}".format(url))
    print()
    
    query = """
    query {
      ams_devices:devices(site:"ams") {
        inventory_hostname:name
      }
      sin_devices:devices(site:"sin") {
        inventory_hostname:name
      }
      bkk_devices:devices(site:"bkk") {
        inventory_hostname:name
      }
    }
    """
    
    print("query is:")
    print(query)
    print()
    
    token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    
    nb = pynautobot.api(url, token)
    
    response = nb.graphql.query(query=query)
    response_data = response.json
    
    print("Here is the response data in json:")
    pprint(response_data)
    print()
    
    print("Here are the bkk devices:")
    pprint(response_data['data']['bkk_devices'])
    

    Here is the script’s output:

    % python3 -i graphql_ams_query_pynautobot.py
    Querying Nautobot via pynautobot.
    
    url is: https://demo.nautobot.com
    
    query is:
    
    query {
      ams_devices:devices(site:"ams") {
        inventory_hostname:name
      }
      sin_devices:devices(site:"sin") {
        inventory_hostname:name
      }
      bkk_devices:devices(site:"bkk") {
        inventory_hostname:name
      }
    }
    
    
    Here is the response data in json:
    {'data': {'ams_devices': [{'inventory_hostname': 'ams-edge-01'},
                              {'inventory_hostname': 'ams-edge-02'},
                              {'inventory_hostname': 'ams-leaf-01'},
                              {'inventory_hostname': 'ams-leaf-02'},
                              {'inventory_hostname': 'ams-leaf-03'},
                              {'inventory_hostname': 'ams-leaf-04'},
                              {'inventory_hostname': 'ams-leaf-05'},
                              {'inventory_hostname': 'ams-leaf-06'},
                              {'inventory_hostname': 'ams-leaf-07'},
                              {'inventory_hostname': 'ams-leaf-08'}],
              'bkk_devices': [{'inventory_hostname': 'bkk-edge-01'},
                              {'inventory_hostname': 'bkk-edge-02'},
                              {'inventory_hostname': 'bkk-leaf-01'},
                              {'inventory_hostname': 'bkk-leaf-02'},
                              {'inventory_hostname': 'bkk-leaf-03'},
                              {'inventory_hostname': 'bkk-leaf-04'},
                              {'inventory_hostname': 'bkk-leaf-05'},
                              {'inventory_hostname': 'bkk-leaf-06'},
                              {'inventory_hostname': 'bkk-leaf-07'},
                              {'inventory_hostname': 'bkk-leaf-08'}],
              'sin_devices': [{'inventory_hostname': 'sin-edge-01'},
                              {'inventory_hostname': 'sin-edge-02'},
                              {'inventory_hostname': 'sin-leaf-01'},
                              {'inventory_hostname': 'sin-leaf-02'},
                              {'inventory_hostname': 'sin-leaf-03'},
                              {'inventory_hostname': 'sin-leaf-04'},
                              {'inventory_hostname': 'sin-leaf-05'},
                              {'inventory_hostname': 'sin-leaf-06'},
                              {'inventory_hostname': 'sin-leaf-07'},
                              {'inventory_hostname': 'sin-leaf-08'}]}}
    
    Here are the bkk devices:
    [{'inventory_hostname': 'bkk-edge-01'},
     {'inventory_hostname': 'bkk-edge-02'},
     {'inventory_hostname': 'bkk-leaf-01'},
     {'inventory_hostname': 'bkk-leaf-02'},
     {'inventory_hostname': 'bkk-leaf-03'},
     {'inventory_hostname': 'bkk-leaf-04'},
     {'inventory_hostname': 'bkk-leaf-05'},
     {'inventory_hostname': 'bkk-leaf-06'},
     {'inventory_hostname': 'bkk-leaf-07'},
     {'inventory_hostname': 'bkk-leaf-08'}]
    >>> 

    Conclusion

    To fully leverage GraphQL’s efficiency, it must be used programmatically. The first post in this series demonstrated using Nautobot’s GraphiQL interface to craft GraphQL queries. This post builds on that by showing how to convert those GraphQL queries into remote requests in Python code for programmatic use.

    To find out more about pynautobot, including the additional capabilities not described in this post, start with the pynautobot Github repo.



    ntc img
    ntc img

    Contact Us to Learn More

    Share details about yourself & someone from our team will reach out to you ASAP!