Table of Contents
Genie Dq (Dictionary Query) is a Python pyATS library and part of genie.utils to easily query a python dictionary without having to traverse through the dictionary to get a value.
Genie Dq Fundamental
What is genie Dq and how can it be used? The easiest way to answer this question is with an example.
This is a portion of output of a sample “show Interfaces” command in the format of dictionary, parsed using the Cisco Genie parser.
We use cisco genie parser to convert the output of show commands to a dictionary structured output to retrieve any value through python dictionary without using regular expression.
The normal way of retrieving a value is to traverse dictionary structure to reach and query the desired key.
In this figure, if we assume that the output of the “show Interfaces” command parsed by Genie is stored in interfaces variable, we go through the keys “GigabitEthernet1”, “counter”, “rate”, and finally “load_interval” to achieve the value of Load interval in the GigabitEthernet1 interface.
But in the second method, we use the Dq “get_values” method to easily query the value of the “load_interval” key.
To use the Dq capabilities, assuming here that “interfaces” is the output of genie parser, we use “Dq(interfaces)” or “interfaces.q” to access Dq methods.
As an example, we use the “get_values()” method to pass the key and get the value of the key, regardless of the key’s position in the dictionary structure. If the key exists in more than one position in the dictionary structure, a list of values will be returned.
In this example, the value of the load interval is returned for all interfaces, but we only need the value of the load interval of the GigabitEthernet1 interface.
In the third example, we use another method “contains()” in addition to “get_values()”. With “contains” we limit the dictionary output to the path that contains the keyword “GigabitEthernet1”. Therefore, the load interval value is returned for GigabitEthernet1 only.
Genie Dq Methods
The methods “get_values()” and “contains()” are the most well-known methods of genie Dq methods, but they are not limited to these methods.
This table shows some of the other Dq methods that are also present in our demonstration that we will see in a few minutes.
get_values(): Return a list of the values of the provided key.
contains(): Filters down the dictionary and only keep the paths which contains the provided string.
not_contains(): Only keep paths which does not contains the provided string. It remove unwanted path and we will have a dictionary with only desired keys/paths.
contains_key_value(): Similar to contains() except that instead of only the expected value, the key and value pair is provided.
reconstruct(): Rebuilds a dictionary from Dq output. It helps us for easier further processing.
value_operator(): Filter down the path based on the value of a certain key with {==, !=, >=, <=, >, <}.
count(): It counts how many elements match the requirement.
key_regex(): If looking for keys with a regex pattern, you need to set “key_regex” to True.
value_regex(): It is used for applying regex pattern on the values. To use it, you need to set “value_regex” variable to True.
For a more complete list of Dq methods and examples, see this link.
Genie Dq Example Script
This is the script we used in the previous section, which parses the output of the “show Interfaces” command with the genie parser and stores the dictionary result in the “interfaces” variable.
from nornir import InitNornir from nornir_netmiko.tasks import netmiko_send_command from rich import print as rprint from genie.utils import Dq nr = InitNornir(config_file="config.yaml") def genie_Dq_example(task): result=task.run(task=netmiko_send_command, command_string="show interfaces", use_genie=True) interfaces = result.result rprint(interfaces) # rprint(interfaces["GigabitEthernet1"]["counters"]["out_pkts"]) # rprint("return the list of values of out_pkts key :", \ # Dq(interfaces).get_values("out_pkts")) # rprint("return the value of out_pkts key of index 0 in the list:", \ # Dq(interfaces).get_values("out_pkts")[0]) # rprint("return the value of out_pkts key of GigabitEthernet1 interface:", \ # Dq(interfaces).contains("GigabitEthernet1").get_values("out_pkts")) # rprint("return the value of out_pkts if it is greater than zero: ", \ # interfaces.q.value_operator("out_pkts", '>', 0).get_values("out_pkts")) # rprint("keep the dictionary path which contain the key_value pair of enabled:True ", \ # interfaces.q.contains_key_value('enabled', True)) # rprint("keep the dictionary path which contain the key_value pair of enabled:True and rebuild the dictionary", \ # interfaces.q.contains_key_value('enabled', True).reconstruct()) # rprint("keep the path which does not contains False string and te´hen return the value of out_pkts key : ", \ # Dq(interfaces).not_contains(False).get_values("out_pkts")) # rprint("keep the path with key of line_protocol which value is matched with regex u.* : ", \ # interfaces.q.contains_key_value('line_protocol', 'u.*', value_regex=True).reconstruct()) # rprint("keep the path with key matching with regex line.* and the value of up : ", \ # interfaces.q.contains_key_value('line.*', 'up', key_regex=True).reconstruct()) results=nr.run(task=genie_Dq_example)
If we run the script, we expect to see the output of interfaces and interface statistics in a dictionary format.
{ 'GigabitEthernet1': { 'port_channel': {'port_channel_member': False}, 'enabled': True, 'line_protocol': 'up', 'oper_status': 'up', 'type': 'CSR vNIC', 'mac_address': '000c.2971.8b31', 'phys_address': '000c.2971.8b31', 'ipv4': {'192.168.2.91/24': {'ip': '192.168.2.91', 'prefix_length': '24'}}, ... 'queues': { 'input_queue_size': 0, 'input_queue_max': 375, ... }, 'counters': { 'rate': {'load_interval': 300, 'in_rate': 12000, 'in_rate_pkts': 13, 'out_rate': 0, 'out_rate_pkts': 0}, 'last_clear': 'never', 'in_pkts': 274988, 'in_octets': 84135812, ... } }, 'GigabitEthernet2': { 'port_channel': {'port_channel_member': False}, 'enabled': False, ... 'counters': { 'rate': {'load_interval': 300, 'in_rate': 0, 'in_rate_pkts': 0, 'out_rate': 0, 'out_rate_pkts': 0}, 'last_clear': 'never', ... } }, 'GigabitEthernet3': { 'port_channel': {'port_channel_member': False}, 'enabled': False, ... 'counters': { 'rate': {'load_interval': 300, 'in_rate': 0, 'in_rate_pkts': 0, 'out_rate': 0, 'out_rate_pkts': 0}, 'last_clear': 'never', ... } } }
Suppose you want to extract the number of output packets of GigabitEthernet1 interface.
Using the method we learned in the previous section, we need to parse the dictionary to reach the desired key to get the value. In this example “interfaces[“GigabitEthernet1”][“counters”][“out_pkts”]“.
Let’s uncomment the corresponding line and run the script to see the result.
rprint(interfaces["GigabitEthernet1"]["counters"]["out_pkts"])
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py 1167
As expected, the number of outgoing packets from the GigabitEthernet1 interface is displayed.
However, the simpler method is to ask the Dq function to look for the “out_pkts” key and return the values. But, since the key applies to all interfaces, a list of values is returned.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("return the list of values of out_pkts key :", \ Dq(interfaces).get_values("out_pkts"))
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py [1263, 0, 0]
To limit the “out_pkts” output to only the GigabitEthernet1 interface, we can return the first index of the returned list or use the “contains()” method to restrict the path to include the string “GigabitEthernet1”.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("return the value of out_pkts key of index 0 in the list:", \ Dq(interfaces).get_values("out_pkts")[0]) rprint("return the value of out_pkts key of GigabitEthernet1 interface:", \ Dq(interfaces).contains("GigabitEthernet1").get_values("out_pkts"))
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py return the value of out_pkts key of index 0 in the list: 1653 return the value of out_pkts key of GigabitEthernet1 interface: [1653]
The other method to get the same value is to limit the output only to interfaces with a number of output packets greater than zero. This is done a new Dq method, “value_operator()” method.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("return the value of out_pkts if it is greater than zero: ", \ interfaces.q.value_operator("out_pkts", '>', 0).get_values("out_pkts"))
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py return the value of out_pkts if it is greater than zero: [2157]
In the next example of Dq methods, we restrict the directory path to just the path with key-value pair “enabled:True”. It is done using the “contains_key_value()” method.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("keep the dictionary path which contain the key_value pair of enabled:True ", \ interfaces.q.contains_key_value('enabled', True))
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py keep the dictionary path which contain the key_value pair of enabled:True Dq(paths=[DictItem(path=('GigabitEthernet1', 'enabled'), value=True)])
The result is small and readable, but if the output was not small, it wasn’t easy to read the output of the Dq function.
In such a situation we can use reconstruct() method which rebuilds a dictionary from Dq output.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("keep the dictionary path which contain the key_value pair of enabled:True and rebuild the dictionary", \ interfaces.q.contains_key_value('enabled', True).reconstruct())
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py keep the dictionary path which contain the key_value pair of enabled:True and rebuild the dictionary {'GigabitEthernet1': {'enabled': True}}
The output is pure dictionary output that can be easily further processed.
The other method to get the number of output packets of the GigabitEthernet1 interface is by using the “not_contains()” keyword.
We restrict the dictionary path to those paths that do not contain the “False” keyword. We do that because the interfaces that are not still enabled contains the “False” keyword. Then we return the value of the “out_pkts” key.
Let’s uncomment the corresponding line and run the script to see the result.
rprint("keep the path which does not contains False string and te´hen return the value of out_pkts key : ", \ Dq(interfaces).not_contains(False).get_values("out_pkts"))
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py keep the path which does not contains False string and te´hen return the value of out_pkts key : [2658]
In these last two examples, we use a regular expression in the key or value to restrict the path to those paths that match a specific regular expression.
To use regular expression, we have to enable regular expression feature using “key_regex=True”.
Here we use “contains_key_value()” to match a key_value pair by using a regular expression in the key or in the value. To convert the final path into a pure dictionary, we use the reconstruct() method.
Let’s uncomment the corresponding lines and run the script to see the result.
rprint("keep the path with key of line_protocol which value is matched with regex u.* : ", \ interfaces.q.contains_key_value('line_protocol', 'u.*', value_regex=True).reconstruct()) rprint("keep the path with key matching with regex line.* and the value of up : ", \ interfaces.q.contains_key_value('line.*', 'up', key_regex=True).reconstruct())
#majid@devnet:~/devnet/pyhton_nornir/2023/9.genie_parser$ python3 9.3.genie_Dq_example.py keep the path with key of line_protocol which value is matched with regex u.* : {'GigabitEthernet1': {'line_protocol': 'up'}} keep the path with key matching with regex line.* and the value of up : {'GigabitEthernet1': {'line_protocol': 'up'}}