Netmiko is one of the first tools a network automation engineer encounters. Built by Kirk Byers, it abstracts the messiness of SSH handling across dozens of vendor platforms into a clean, consistent API.
Installation
Basic Connection
1
2
3
4
5
6
7
8
9
10
11
12
| from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "192.168.1.1",
"username": "admin",
"password": "cisco123",
}
with ConnectHandler(**device) as conn:
output = conn.send_command("show ip interface brief")
print(output)
|
Always use the with statement — it ensures the connection is properly closed even if an exception occurs.
| Platform |
device_type |
| Cisco IOS / IOS-XE |
cisco_ios |
| Cisco IOS-XR |
cisco_xr |
| Cisco NX-OS |
cisco_nxos |
| Arista EOS |
arista_eos |
| Juniper JunOS |
juniper_junos |
Robust Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| from netmiko import ConnectHandler
from netmiko.exceptions import (
NetmikoTimeoutException,
NetmikoAuthenticationException,
)
import logging
log = logging.getLogger(__name__)
def connect(params: dict):
try:
conn = ConnectHandler(**params)
log.info(f"Connected to {params['host']}")
return conn
except NetmikoTimeoutException:
log.error(f"Timeout: {params['host']} unreachable")
except NetmikoAuthenticationException:
log.error(f"Auth failed: {params['host']}")
except Exception as e:
log.error(f"Unexpected: {e}")
return None
|
Multi-Device with ThreadPoolExecutor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| from concurrent.futures import ThreadPoolExecutor, as_completed
INVENTORY = [
{"device_type": "cisco_ios", "host": "10.0.0.1", "username": "admin", "password": "cisco"},
{"device_type": "arista_eos","host": "10.0.0.2", "username": "admin", "password": "arista"},
]
def run_cmd(device, cmd):
result = {"host": device["host"], "output": None, "error": None}
try:
with ConnectHandler(**device) as conn:
result["output"] = conn.send_command(cmd)
except Exception as e:
result["error"] = str(e)
return result
def run_all(inventory, cmd, workers=10):
results = []
with ThreadPoolExecutor(max_workers=workers) as ex:
futures = {ex.submit(run_cmd, d, cmd): d["host"] for d in inventory}
for f in as_completed(futures):
results.append(f.result())
return results
for r in run_all(INVENTORY, "show version"):
status = "OK" if r["output"] else f"ERR: {r['error']}"
print(f"{r['host']:15} {status}")
|
TextFSM Structured Output
1
2
3
4
5
6
7
8
| with ConnectHandler(**device) as conn:
interfaces = conn.send_command(
"show ip interface brief",
use_textfsm=True # returns list of dicts
)
for i in interfaces:
print(f"{i['intf']:25} {i['ipaddr']:18} [{i['status'].upper()}]")
|
Key Takeaways
- Always use
with ConnectHandler(...) for automatic cleanup
- Handle
NetmikoTimeoutException and NetmikoAuthenticationException explicitly
- Use
ThreadPoolExecutor for multi-device — never loop serially in production
use_textfsm=True gives you structured output for free
Next up: Nornir — a framework that adds proper inventory management and task runners on top of Netmiko’s connection model.