Automating Network Devices with pyntc
Introduction
Network engineers today are expected to manage increasingly complex infrastructures with fewer resources and tighter timelines. Manual processes simply can’t keep up with the pace of change. That’s why network automation is no longer optional, it’s a core skill. In this blog, we’ll introduce pyntc, one of many open-source Python libraries developed and maintained by Network to Code to help you on your automation journey. With its simple, vendor-agnostic interface, pyntc makes it easier to connect to devices, execute commands, and manage configurations, helping you automate tasks consistently across a multi-vendor environment.
What Is pyntc?
pyntc is an open source Python library that provides a uniform interface for automating network devices focused primarily on establishing connections and executing commands that supports multiple connection methods, such as SSH and API calls. It abstracts away the complexity of different vendor-specific APIs and command-line instructions, allowing network engineers to write vendor-agnostic automation code.
Who Is pyntc For?
- Network Engineers looking to automate repetitive tasks
- DevOps Engineers working with network infrastructure
- Network Automation Engineers building automation solutions
- Anyone who needs to manage network devices programmatically
Key Features
- Has multi-vendor support
- Offers unified interfaces for establishing connections to devices and executing commands
- Is operation focused (can upgrade devices, back up configurations, copy files, etc.)
- Leverages libraries such as Netmiko and other vendor SDKs
Installation
pip install pyntcUsage Examples
This example focuses on connecting to an Arista cEOS device at 172.17.0.2. The username and password for this device are both set to ‘admin’. For setup details and additional context, please refer to the “Lab Setup” documentation, which can be found at Lab Setup from the “100 Days of Nautobot” project.
1. Basic Device Connection
In this first script, we will establish the basic connection to the device.
01_basic_connectivity.py
#!/usr/bin/env python3
"""
Basic example demonstrating how to connect to a network device and get its facts using pyntc.
This example shows the fundamental connection and information-gathering capabilities.
"""
from pyntc import ntc_device as NTC
from rich import print
def main():
# Create a device object for an Arista EOS device
eos_device = NTC(
host="172.17.0.2", # Arista EOS device IP
username="admin", # Arista EOS username
password="admin", # Arista EOS password
device_type="arista_eos_eapi" # Correct device type for Arista EOS
)
try:
# Open connection to the device
eos_device.open()
# Print device information using individual properties
print("\n[bold green]Device Information:[/bold green]")
print(f"Hostname: {eos_device.hostname}")
print(f"Model: {eos_device.model}")
print(f"OS Version: {eos_device.os_version}")
print(f"Serial Number: {eos_device.serial_number}")
# Get running configuration
running_config = eos_device.running_config
print("\n[bold green]Running Configuration:[/bold green]")
print(running_config)
except Exception as e:
print(f"[bold red]Error:[/bold red] {str(e)}")
finally:
# Always close the connection
eos_device.close()
if __name__ == "__main__":
main()Here is the output from the script above:
# python 01_basic_connection.py
2025-06-11 15:57:45,079 [INFO] pyntc: Logging initialized for host None.
2025-06-11 15:57:45,081 [INFO] pyntc: Logging initialized for host 172.17.0.2.
2025-06-11 15:57:45,104 [INFO] paramiko.transport: Connected (version 2.0, client OpenSSH_8.7)
2025-06-11 15:57:45,198 [INFO] paramiko.transport: Authentication (keyboard-interactive) successful!
Device Information:
Hostname: ceos-01
Model: cEOSLab
OS Version: 4.32.0F-36401836.4320F
Serial Number: 757C747B89ECB1EFAE782D1ADD7F05A8
Running Configuration:
! Command: show running-config
! device: ceos-01 (cEOSLab, EOS-4.32.0F-36401836.4320F (engineering build))
!
no aaa root
!
…
!
…
interface Loopback100
description Mimic OSPF Area 0.0.0.2 loopback
ip address 192.168.101.100/24
!
interface Management0
ip address 172.17.0.2/16
!
ip routing
!
router ospf 1
router-id 10.0.0.1
max-lsa 12000
!
end2. Configuration Management
The second script demonstrates how to make a simple change to a network device. In this case, make a backup of the confirmation, and then verify the backup.
02_configuration_management.py
#!/usr/bin/env python3
"""
Configuration management example demonstrating how to make changes to a network device using pyntc.
This example shows how to back up configurations, make changes, and verify them.
"""
import os
from datetime import datetime
from pyntc import ntc_device as NTC
from rich import print
def main():
# Create a device object for an Arista EOS device
eos_device = NTC(
host="172.17.0.2", # Arista EOS device IP
username="admin", # Arista EOS username
password="admin", # Arista EOS password
device_type="arista_eos_eapi" # Correct device type for Arista EOS
)
try:
# Open connection to the device
eos_device.open()
# Create backups directory if it doesn't exist
if not os.path.exists("backups"):
os.makedirs("backups")
# Back up current configuration
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_filename = f"backups/ceos-01_{timestamp}.txt"
eos_device.backup_running_config(backup_filename)
print(f"\nConfiguration backed up to: {backup_filename}")
# Make configuration changes
print("\nApplying configuration changes...")
config_commands = [
"interface Ethernet1",
"description Configured by pyntc",
"ip address 192.168.1.1/24",
"no shutdown"
]
eos_device.config(config_commands)
# Save configuration
eos_device.save()
print("Configuration saved successfully!")
# Verify configuration
print("\nVerifying configuration:")
show_command = "show running-config interfaces Ethernet1"
interface_config = eos_device.show(show_command)
print(interface_config)
except Exception as e:
print(f"[bold red]Error:[/bold red] {str(e)}")
finally:
# Always close the connection
eos_device.close()
if __name__ == "__main__":
main()Here is the output from the script above:
2025-06-11 15:58:32,117 [INFO] pyntc: Logging initialized for host None.
2025-06-11 15:58:32,121 [INFO] pyntc: Logging initialized for host 172.17.0.2.
2025-06-11 15:58:32,145 [INFO] paramiko.transport: Connected (version 2.0, client OpenSSH_8.7)
2025-06-11 15:58:32,236 [INFO] paramiko.transport: Authentication (keyboard-interactive) successful!
Configuration backed up to: backups/ceos-01_20250611_155832.txt
Applying configuration changes...
2025-06-11 15:58:32,781 [INFO] pyntc: Host 172.17.0.2: Device configured with commands ['interface Ethernet1', 'description Configured by pyntc', 'ip address 192.168.1.1/24', 'no shutdown'].
Configuration saved successfully!
Verifying configuration:
{
'output': 'interface Ethernet1\n description Configured by pyntc\n no
switchport\n ip address 192.168.1.1/24\n ip ospf network point-to-point\n ip ospf
area 0.0.0.0\n'
}3. Multi-Device Operations
In the third example, we extend the process one step further to multiple network devices using pyntc.
03_multi_device_operations.py
#!/usr/bin/env python3
"""
Multi-device operations example demonstrating how to work with multiple network devices using pyntc.
This example shows how to gather information from multiple devices concurrently.
"""
import asyncio
import json
import os
from datetime import datetime
from typing import Dict, List
from pyntc import ntc_device as NTC
from rich import print
# Device inventory
DEVICE_INVENTORY = [
{
"name": "ceos-01",
"host": "172.17.0.2",
"username": "admin",
"password": "admin",
"device_type": "arista_eos_eapi"
},
{
"name": "ceos-02",
"host": "172.17.0.2", # Using same device for demonstration
"username": "admin",
"password": "admin",
"device_type": "arista_eos_eapi"
}
]
#use of async function for cooperative parallel processing
async def get_device_facts(device_info: Dict) -> Dict:
"""Get facts from a single device."""
device = NTC(
host=device_info["host"],
username=device_info["username"],
password=device_info["password"],
device_type=device_info["device_type"]
)
try:
device.open()
facts = {
"hostname": device.hostname,
"model": device.model,
"os_version": device.os_version,
"serial_number": device.serial_number,
"interfaces": device.interfaces
}
return {device_info["name"]: facts}
except Exception as e:
return {device_info["name"]: {"error": str(e)}}
finally:
device.close()
async def main():
print("Gathering device facts...")
# Create tasks for all devices
tasks = [get_device_facts(device) for device in DEVICE_INVENTORY]
# Wait for all tasks to complete
results = await asyncio.gather(*tasks)
# Combine results
facts_results = {}
for result in results:
facts_results.update(result)
# Create output directory if it doesn't exist
if not os.path.exists("output"):
os.makedirs("output")
# Save results to file
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"output/device_facts_{timestamp}.json"
with open(output_file, "w") as f:
json.dump(facts_results, f, indent=2)
print(f"\nResults saved to: {output_file}")
# Print summary
print("\nDevice Facts Summary:")
for device_name, facts in facts_results.items():
print(f"\n[bold green]{device_name}:[/bold green]")
if "error" in facts:
print(f"[bold red]Error:[/bold red] {facts['error']}")
else:
print(f"Hostname: {facts['hostname']}")
print(f"Model: {facts['model']}")
print(f"OS Version: {facts['os_version']}")
print(f"Serial Number: {facts['serial_number']}")
print(f"Number of Interfaces: {len(facts['interfaces'])}")
if __name__ == "__main__":
asyncio.run(main())Here is the output from the devices (again, we use the same device for illustration purposes):
# python 03_multi_device_operations.py
2025-06-11 15:59:41,381 [INFO] pyntc: Logging initialized for host None.
Gathering device facts...
2025-06-11 15:59:41,435 [INFO] pyntc: Logging initialized for host 172.17.0.2.
2025-06-11 15:59:41,457 [INFO] paramiko.transport: Connected (version 2.0, client OpenSSH_8.7)
2025-06-11 15:59:41,551 [INFO] paramiko.transport: Authentication (keyboard-interactive) successful!
2025-06-11 15:59:42,091 [INFO] pyntc: Logging initialized for host 172.17.0.2.
2025-06-11 15:59:42,114 [INFO] paramiko.transport: Connected (version 2.0, client OpenSSH_8.7)
2025-06-11 15:59:42,206 [INFO] paramiko.transport: Authentication (keyboard-interactive) successful!
Results saved to: output/device_facts_20250611_155942.json
Device Facts Summary:
ceos-01:
Hostname: ceos-01
Model: cEOSLab
OS Version: 4.32.0F-36401836.4320F
Serial Number: 757C747B89ECB1EFAE782D1ADD7F05A8
Number of Interfaces: 1
ceos-02:
Hostname: ceos-01
Model: cEOSLab
OS Version: 4.32.0F-36401836.4320F
Serial Number: 757C747B89ECB1EFAE782D1ADD7F05A8
Number of Interfaces: 1Conclusion
Whether you’re automating a single rack or orchestrating changes across an enterprise backbone, pyntc gives you a consistent, vendor-agnostic way to get it done. By abstracting the complexity of device connections and commands, it frees engineers to focus on building scalable automation workflows instead of troubleshooting syntax differences.
As part of Network to Code’s open-source ecosystem, pyntc is more than just a tool, it’s a stepping stone toward a modern, automated network practice. Start small with backups or configuration changes, then expand to multi-device operations and beyond. The more you use it, the faster you’ll unlock efficiency, accuracy, and confidence in your automation journey.
Additional Resources
- pyntc GitHub Repository: https://github.com/networktocode/pyntc
- pyntc Readthedoc: https://pyntc.readthedocs.io/en/latest/
- Discover more NTC open source projects: https://networktocode.com/community/open-source/
– Eric Chou
