Programmatic Nautobot GraphQL Requests via Pynautobot

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!

Author