What You’ll Learn

What You’ll Need

What You’ll Need: Linux and Mac Users

What You’ll Need: Windows Users

Additional Important Points

As network engineers, we are familiar with the CLI. The CLI output is made to be very human-readable.

For example, if you look through the output below, the CLI command output is organized in a way that makes it easy for humans to read. Notice how the output uses spacing, indentation, colons, and commas, and it even organizes some of the output into rows and columns. Using your human logic, you could easily determine the Border Gateway Protocol (BGP) neighbor of the router.

script

However, a machine would struggle to read the previous CLI output. The machine does not have our human logic. A machine would see the above output just as a string of text, and it would be very difficult for a machine to determine the BGP neighbor of the router.

In order for a machine to read and work with the data, we must have data that has structure and is organized in a very specific and standardized way. Below is an example of data that your machine could much more easily interpret.

script

If you analyze the data above carefully, you will notice that the data is organized and follows very specific rules. The data has brackets, curly braces, commas, quotes, and colons. A machine can use the previously mentioned symbols to interpret and work with the data. As long as we give our machine the appropriate instructions to access the data we are looking for, our machine could easily determine the BGP neighbor of our router.

Although the output above is also human-readable, it is not as human-readable as the CLI output. In fact, network engineers often struggle when working with data in Python when implementing automation solutions. By the end of this tutorial, you should become proficient in working with different types of Python data commonly seen in network automation solutions.

A Python parser is a tool that we can use to convert data from one format to another format. In our example, we will use a parser to convert a more human-readable CLI format into a more machine-readable format.

Let’s now go through an example. Create a new file called netmiko_test.py. Copy and paste the code below into your netmiko_test.py script. (Make sure not to name the script netmiko.py. If you name the script netmiko.py, you will get a circular import error. If you receive a circular import error, delete the netmiko.py script and re-create the script, this time using the name netmiko_test.py.)

from netmiko import ConnectHandler

host = 'sandbox-iosxe-latest-1.cisco.com'
user = 'admin'
password = 'C1sco12345'
type = 'cisco_xe'


r1 = ConnectHandler(host=host, username=user, password=password, device_type=type)



print(r1.send_command(''))

Next, edit line 13 in your script to send the show version command to the router.

script

Save and run your script.

PS Linux$ python netmiko_test.py
Cisco IOS XE Software, Version 17.09.02a
Cisco IOS Software [Cupertino], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.9.2a, RELEASE SOFTWARE (fc4)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2022 by Cisco Systems, Inc.
Compiled Wed 30-Nov-22 02:47 by mcpre


Cisco IOS-XE software, Copyright (c) 2005-2022 by cisco Systems, Inc.
All rights reserved.  Certain components of Cisco IOS-XE software are
licensed under the GNU General Public License ("GPL") Version 2.0.  The
software code licensed under GPL Version 2.0 is free software that comes
with ABSOLUTELY NO WARRANTY.  You can redistribute and/or modify such
GPL code under the terms of GPL Version 2.0.  For more details, see the
documentation or "License Notice" file accompanying the IOS-XE software,
or the applicable URL provided on the flyer accompanying the IOS-XE
software.


ROM: IOS-XE ROMMON
Cat8000V uptime is 1 day, 19 hours, 52 minutes
Uptime for this control processor is 1 day, 19 hours, 53 minutes
System returned to ROM by reload
System restarted at 17:00:41 UTC Tue Jul 25 2023
System image file is "bootflash:packages.conf"
Last reload reason: reload



This product contains cryptographic features and is subject to United
States and local country laws governing import, export, transfer and
use. Delivery of Cisco cryptographic products does not imply
third-party authority to import, export, distribute or use encryption.
Importers, exporters, distributors and users are responsible for
compliance with U.S. and local country laws. By using this product you
agree to comply with applicable laws and regulations. If you are unable
to comply with U.S. and local laws, return this product immediately.

A summary of U.S. laws governing Cisco cryptographic products may be found at:
http://www.cisco.com/wwl/export/crypto/tool/stqrg.html

If you require further assistance please contact us by sending email to
export@cisco.com.

License Level:
License Type: Perpetual
Next reload license Level:

Addon License Level:
Addon License Type: Subscription
Next reload addon license Level:

The current throughput level is 20000 kbps


Smart Licensing Status: Smart Licensing Using Policy

cisco C8000V (VXE) processor (revision VXE) with 1980715K/3075K bytes of memory.
Processor board ID 9UWS2FADP45
Router operating mode: Autonomous
3 Gigabit Ethernet interfaces
32768K bytes of non-volatile configuration memory.
3965352K bytes of physical memory.
5234688K bytes of virtual hard disk at bootflash:.

Configuration register is 0x2102

Although the output should be fairly easy for you to read through and decipher, a machine just sees the output as a string of text. A machine would struggle to read and work with the previous output.

To unleash the power of automation, we need to convert the previous show version output to a more structured type of data. To do that, we can use the genie parser.

Change line 13 in your script to use the genie parser to convert the show version command output from a string to a more structured type of data.

script

Save and run your script.

Linux$ python netmiko_test.py
{'version': {'xe_version': '17.09.02a', 'version_short': '17.9', 'platform': 'Virtual XE', 'version': '17.9.2a', 'image_id': 'X86_64_LINUX_IOSD-UNIVERSALK9-M', 'label': 'RELEASE SOFTWARE (fc4)', 'os': 'IOS-XE', 'location': 'Cupertino', 'image_type': 'production image', 'copyright_years': '1986-2022', 'compiled_date': 'Wed 30-Nov-22 02:47', 'compiled_by': 'mcpre', 'rom': 'IOS-XE ROMMON', 'hostname': 'Cat8000V', 'uptime': '1 day, 21 hours, 26 minutes', 'uptime_this_cp': '1 day, 21 hours, 27 minutes', 'returned_to_rom_by': 'reload', 'system_restarted_at': '17:00:42 UTC Tue Jul 25 2023', 'system_image': 'bootflash:packages.conf', 'last_reload_reason': 'reload', 'license_type': 'Perpetual', 'chassis': 'C8000V', 'main_mem': '1980715', 'processor_type': 'VXE', 'rtr_type': 'C8000V', 'chassis_sn': '9UWS2FADP45', 'router_operating_mode': 'Autonomous', 'number_of_intfs': {'Gigabit Ethernet': '3'}, 'mem_size': {'non-volatile configuration': '32768', 'physical': '3965352'}, 'disks': {'bootflash:.': {'disk_size': '5234688', 'type_of_disk': 'virtual hard disk'}}, 'curr_config_register': '0x2102'}}

Congratulations! You just converted CLI output to a more machine-readable format. Assuming you give your script the correct instructions, this is something your script can easily read through and apply its programming logic to.

In Python, data structures enable you to store your data so that the data can be easily accessed by your machine. There are four nonprimitive data structures that are built in to Python: lists, sets, tuples, and dictionaries. In this tutorial, we will focus on working with lists and dictionaries because they are the most commonly used nonprimitive data structures in network automation.

Before we work with lists in a network automation script, we first need to understand what a list is and how we can use lists in Python.

One way we can structure our data in Python is to use lists. Lists are used to store multiple values. Each value is comma-separated and is referred to as an element. When creating a list you put elements inside of a list by using brackets.

The following example is a list. Inside of the list, there are four different elements that are comma-separated (Svetlana, Lucy, Uncle Bob, and Deepak). Because we are using a list to store data, we want to put an open and a closed bracket around our four elements. Notice that our list is being stored into the people variable.

people = ['Svetlana', 'Lucy', 'Uncle Bob', 'Deepak']

Go ahead write the following script. Save the file as a list.py.

script

Now that you have created your list, we can use the print function coupled with the type class to confirm that Python sees the data stored in your people variable as a list. Add the code in line 3 into your script.

script

Save and run your script.

python list.py
<class 'list'>

We have confirmed that Python sees our data stored in the people variable as a list. Nice work!

One of the benefits of structuring our data in a list is that a machine can easily read through and work with lists.

To print out all the elements in your list, you can just print out the entire variable. Update line 3 in your list.py script to print out all the elements stored in the people variable.

script

Save and then run your script.

Linux$ python list.py
['Svetlana', 'Lucy', 'Uncle Bob', 'Deepak']

You can also print out a specific element’s value in a list. To print out a specific element’s value in a list you need to understand how elements are assigned index IDs. Lists use zero-based indexing, meaning that each element is assigned an index ID based on its position starting with an index ID of zero. In the following image, the first element gets an index ID of zero, and then the other elements get assigned index IDs in sequential order, depending on their position.

script

To print out a specific element’s value in a list you need to reference the element’s index ID. You specify an index ID by placing the index ID number inside of a bracket. Update line 3 in our list.py script to print out the value of the element that has been assigned an index ID of zero.

script

Save and then run the script.

Linux$ python list.py
Svetlana

You should have been able to print out the first element’s value in your list: Svetlana.

When working with elements in a list we will frequently insert the element’s value into a string and print it out. To do this, change line 3 to save the Svetlana value into the name variable.

script

In line 5, we will then print out the value of the name variable (which is Svetlana) by inserting it into a string.

script

Save and run your script.

Linux$ python list.py
My name is Svetlana

Can you figure out how to print out the other element values in your list? Can you also add those values to strings? Try changing zero in line 3 to a different index ID number and see what happens.

Let’s get some hands-on experience with lists in a network automation script.

Go back to the netmiko_test.py script and add the show ip interface GigabitEthernet1 command to line 13. Also, change the beginning of line 13; remove the print function and store the data into the response variable.

script

Copy and paste the code below into lines 15 and 17 of your netmiko_test.py script. (A note about the new code in lines 15 and 17: Do not be too concerned with what we are actually doing in lines 15 and 17 for now. We will cover what we are doing in these lines of code later in this tutorial. For now, we are just adding code to your script so that the script will return a list to you.)

route_cache = response['GigabitEthernet1']['ip_route_cache_flags']

print(route_cache)

script

Save and run your script.

Linux$ python netmiko_test.py
['CEF', 'Fast']

You should receive a list with two elements. Try changing line 17 in your script to only print out the first element in the list.

Are you able to figure it out?

If not, here’s the solution:

script

Linux$ python netmiko_test.py
CEF

Next, let’s insert the element’s value into a string. Before we can insert the element’s value into a string, we need to change line 17 to save the CEF value into the route_cache_cef variable.

script

In line 19, we will then print out the value of the route_cache_cef variable (which is CEF) by inserting it into a string.

script

Save and run your script.

Linux$ python netmiko_test.py
We are using CEF for route caching

We successfully interacted with elements in a list in a network automation script. Nice work!

For extra credit, can you get the second element in our list to print out instead of the first element?

We will be working with the netmiko_test.py script later in this tutorial. Once you are done with this section, please delete the code in lines 15, 17, and 19 before proceeding. Your script should look like this:

script

Another way we can structure our data in Python is to use dictionaries. Dictionaries store data in key and value (key-value) pairs. The keys and values are separated by colons. Keys are typically surrounded by single quotes. Each key-value pair is separated by a comma. Dictionaries store their key-value pairs inside of curly braces.

The example below is a dictionary. Inside of the dictionary there are three different key-value pairs. For example, 'name': 'Uncle Bob' would be one key-value pair, 'age': 42 would be a second key-value pair, and 'hair_color': 'brown' would be a third key-value pair. The first part of each key-value pair is the keys (name, age, and hair_color). The second part of each key-value pair is the values (Uncle Bob, 42, and brown). A colon is used to separate each key from its corresponding value. Because we are using a dictionary to store our data, we want to enclose our key-value pairs inside of curly braces. Finally, we then store our data into the person variable.

person = {'name': 'Uncle Bob', 'age': 42, 'hair_color': 'brown'}

Next, write the following script. Save the file as dictionary.py.

script

Now that you have created your dictionary, we can use the print function coupled with the type class to confirm that Python sees your data stored in the person variable as a dictionary. Add the code in line 3 into your script and save your script.

script

Run your script.

Linux$ python dictionary.py
<class 'dict'>

We have confirmed that Python sees our data stored in the person variable as a dictionary.

One of the benefits of structuring our data in a dictionary is that Python can easily read through and work with dictionaries.

To print out all the key-value pairs in your dictionary, you can just print out the variable. Update line 3 in your dictionary.py script to print out the key-value pairs stored in the person variable.

script

Save and then run your script.

Linux$ python dictionary.py
{'name': 'Uncle Bob', 'age': 42, 'hair_color': 'brown'}

You can also print out a specific value inside of a dictionary. To print out a specific value, you need to specify the appropriate key. You specify the appropriate key by placing the key name inside of a bracket. Update line 3 in our dictionary.py script to only print out the value of the name key.

script

Save and then run your script.

Linux$ python dictionary.py
Uncle Bob

When working with values in a dictionary, you will frequently insert the value into a string and print it out. To do this, change line 3 to store the Uncle Bob value into the name variable.

script

In line 5, we will then print out the value of the name variable (which is Uncle Bob ) by inserting it into a string.

script

Save and run your script.

Linux$ python dictionary.py
My name is Uncle Bob

For extra credit, see if you can print out the value of the other keys in the dictionary.py script.

Nice work! You now understand how to interact with dictionaries.

Now that you are familiar with dictionaries, let’s use your newly learned skills on a network automation script.

Go back to your netmiko_test.py script and update line 13 to use the show clock command. Also, update line 15 so that we can print out the data stored in the response variable.

script

Save and run the script.

Linux$ python netmiko_test.py
{'time': '18:07:01.097', 'timezone': 'UTC', 'day_of_week': 'Thu', 'month': 'Jul', 'day': '27', 'year': '2023'}

Let’s print out the value of the year key. Add the code to your script displayed in line 15:

script

Save and run your script.

Linux$ python netmiko_test.py
2023

Next, insert the value into a string and print out the value. To do this, change line 15 to save the 2023 value into the year variable.

script

In line 17, we will then print out the value of the year variable (which is 2023) by inserting it into a string.

script

Save and run your script.

Linux$ python netmiko_test.py
The year is 2023

Nice work! You successfully interacted with a value in a dictionary in a network automation script.

Play around with the script and see if you can extract the values of the other keys. Insert those other values into a string.

When you are done with this section, please delete the code in lines 15 and 17 so that your script looks like the following:

script

In Python, you can nest your data to create more sophisticated data structures. You can think of nesting your data as embedding a data structure within another data structure. Examples of nested data structures that you might see when working with network automation scripts are:

A common nested data type that you will see in network automation is a dictionary within another dictionary. The image below contains a dictionary within another dictionary. We can see that the outer dictionary is signified by the first open and the last closed curly braces (note the red arrows in the image). Stored in the outer dictionary is an Uncle Bob key (underlined in blue), and the value of that key is the inner dictionary (green arrows). The inner dictionary contains two key-value pairs (green underline).

script

Let’s create a dictionary within a dictionary. Create a new file called dict_in_dict.py. Copy and paste the following data into the script.

people = {'Uncle Bob': {'age': 42, 'hair_color': 'brown'}}

Now that we have created a dictionary within another dictionary, how can we go about printing out Uncle Bob’s hair color? The data is nested, so we will need to give Python the exact instructions on how to retrieve the value of the hair_color key. Because the hair_color key is nested within the Uncle Bob key, we first need to tell Python to retrieve the value stored within the Uncle Bob key. Add the following code to your dict_in_dict.py script to print out the value of the Uncle Bob key.

script

Save and then run your script.

PS Linux$ python dict_in_dict.py
{'age': 42, 'hair_color': 'brown'}

How do you think we can update our script to only return the value of the hair_color key? See if you can figure it out on your own.

If you were not able to figure it out, append the hair_color key to the end of line 3 in your script.

script

Save and run your script.

Linux$ python dict_in_dict.py
brown

Let’s now insert the brown value into a string. First, let’s store the value into the hair_color variable.

script

Next, print out the hair_color variable and insert it into a string.

script

Save and run your script.

Linux$ python dict_in_dict.py
His hair color is brown

There are really no limitations in regard to how much you can nest your data in Python. When working in network automation, you will frequently see data that is heavily nested. For example, it is common when running network automation scripts to see dictionaries nested within dictionaries that are nested within dictionaries. Let’s look at one more example and nest our data a little deeper.

Delete all lines of code in your dict_in_dict.py script. Copy and paste the following text into line 1 in your dict_in_dict.py script so that we have three dictionaries that are nested.

people = {'Uncle Bob': {'age': 42, 'hair_color': 'brown', 'phone_number': {'landline': '555-543-2145', 'cell': '555-456-1234'}}}

script

Play around and see if you can figure out the next step on your own. See if you can print out Uncle Bob’s cell phone number instead of his name.

Here’s the answer:

script

Linux$ python dict_in_dict.py
His cell phone number is 555-456-1234

Go back to your netmiko_test.py script and update line 13 to send the router the show version command. Add line 15 to print out the response variable.

script

Save and run the script.

Linux$ python netmiko_test.py
{'version': {'xe_version': '17.09.02a', 'version_short': '17.9', 'platform': 'Virtual XE', 'version': '17.9.2a', 'image_id': 'X86_64_LINUX_IOSD-UNIVERSALK9-M', 'label': 'RELEASE SOFTWARE (fc4)', 'os': 'IOS-XE', 'location': 'Cupertino', 'image_type': 'production image', 'copyright_years': '1986-2022', 'compiled_date': 'Wed 30-Nov-22 02:47', 'compiled_by': 'mcpre', 'rom': 'IOS-XE ROMMON', 'hostname': 'Cat8000V', 'uptime': '3 days, 2 hours, 5 minutes', 'uptime_this_cp': '3 days, 2 hours, 7 minutes', 'returned_to_rom_by': 'reload', 'system_restarted_at': '17:00:43 UTC Tue Jul 25 2023', 'system_image': 'bootflash:packages.conf', 'last_reload_reason': 'reload', 'license_type': 'Perpetual', 'chassis': 'C8000V', 'main_mem': '1980715', 'processor_type': 'VXE', 'rtr_type': 'C8000V', 'chassis_sn': '9UWS2FADP45', 'router_operating_mode': 'Autonomous', 'number_of_intfs': {'Gigabit Ethernet': '3'}, 'mem_size': {'non-volatile configuration': '32768', 'physical': '3965352'}, 'disks': {'bootflash:.': {'disk_size': '5234688', 'type_of_disk': 'virtual hard disk'}}, 'curr_config_register': '0x2102'}}

After running the script, you should receive a lot of output. Imagine you needed to give Python instructions to retrieve the value of the disk_size key. In order for you to give Python the appropriate instructions, you need to first determine how the disk_size key is nested within other dictionaries. This would be a very difficult and tedious task because the output is difficult for a human to read through and parse. To be able to better read and work with the previous output, you should make the output more human-readable.

To make nested data in Python more human-readable, you can use pretty printing from the Rich library. Add line 2 to your script to import pretty from the rich library. Next, update what is now line 16 in your script to pretty-print the Python dictionary.

script

Save and run your script.

Linux$ python netmiko_test.py
{
'version': {
│   │   'xe_version': '17.09.02a',
│   │   'version_short': '17.9',
│   │   'platform': 'Virtual XE',
│   │   'version': '17.9.2a',
│   │   'image_id': 'X86_64_LINUX_IOSD-UNIVERSALK9-M',
│   │   'label': 'RELEASE SOFTWARE (fc4)',
│   │   'os': 'IOS-XE',
│   │   'location': 'Cupertino',
│   │   'image_type': 'production image',
│   │   'copyright_years': '1986-2022',
│   │   'compiled_date': 'Wed 30-Nov-22 02:47',
│   │   'compiled_by': 'mcpre',
│   │   'rom': 'IOS-XE ROMMON',
│   │   'hostname': 'Cat8000V',
│   │   'uptime': '3 days, 2 hours, 17 minutes',
│   │   'uptime_this_cp': '3 days, 2 hours, 19 minutes',
│   │   'returned_to_rom_by': 'reload',
│   │   'system_restarted_at': '17:00:43 UTC Tue Jul 25 2023',
│   │   'system_image': 'bootflash:packages.conf',
│   │   'last_reload_reason': 'reload',
│   │   'license_type': 'Perpetual',
│   │   'chassis': 'C8000V',
│   │   'main_mem': '1980715',
│   │   'processor_type': 'VXE',
│   │   'rtr_type': 'C8000V',
│   │   'chassis_sn': '9UWS2FADP45',
│   │   'router_operating_mode': 'Autonomous',
│   │   'number_of_intfs': {'Gigabit Ethernet': '3'},
│   │   'mem_size': {'non-volatile configuration': '32768', 'physical': '3965352'},
│   │   'disks': {'bootflash:.': {'disk_size': '5234688', 'type_of_disk': 'virtual hard disk'}},
│   │   'curr_config_register': '0x2102'
}

That is much easier to read! You should be able to easily determine which dictionary keys you need to specify to obtain the value of the disk_size key.

script

Change line 16 in the netmiko_test.py script to print out the value of the disk_size key and store the value into the disk variable. To accomplish this, you will need to copy the keys highlighted in the red boxes above. Next, paste each key at the end of line 16, and enclose each key in a bracket. Also, print out the value of the disk variable by inserting it into a string in line 18. Please note that the bootflash key in line 16 has a colon and a period at the very end of the key. Make sure to include the entire key: ['bootflash:.'].

script

Save and run the file.

Linux$ python netmiko_test.py
The size of the disk is 5234688
Let’s see if you can do this on your own now. Update your netmiko_test.py script to use the show interface gigabitethernet1 command. When initially running your script, your script should print out a nested dictionary. When viewing the output, you should see a prefix_length key nested within the data. Next, give your script the appropriate instructions to print out the value of the prefix_length key and insert the value of the prefix_length key into a string. My solution is in the next topic.

First, to make the data more human-readable, we need to pretty-print out the output.

script

Next, we obtain the required keys we need to code into our script to navigate to the prefix_length key.

script

In line 16, we then copy and paste those required keys next to the response variable to obtain the value of the prefix_length key. We then store that value into a variable. In line 18, we print out the value of the variable by inserting it into a string.

script

When we run the script, we get the following.

Linux$ python netmiko_test.py
The prefix is 24

Nice work! You now understand how to work with Python lists, dictionaries, and nested data structures in network automation scripts. I will be creating another tutorial called “Working with Conditionals in Network Automation Scripts,” which will build off of this tutorial as you use conditionals coupled with nested data structures. Search for “Working with Conditionals in Network Automation Scripts” at Cisco U. to see if the tutorial is currently available.

Also, if you are interested, I teach at Cisco Automation Bootcamp.

Learn More