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 pyntc

Usage 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
!
end

2. 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: 1

Conclusion

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

– Eric Chou



Author