Python for Network Data — JSON, XML & Idempotent Pushes
25 min In ProgressLearning Objectives
- Parse JSON and XML device data with Python's standard library
- Implement the idempotent check-before-push pattern in Python
- Explain why a script that runs twice should change nothing on the second run
JSON — The Language of Modern Network APIs
Modern network APIs (Cisco DNA Center, Meraki, AWS) return JSON. It's the same format your browser uses for AJAX — lightweight, human-readable, and trivially parseable in Python with json.loads(). Rule of thumb: if the API is less than 10 years old, it speaks JSON. If it's a legacy system (NETCONF, SOAP, older Cisco), it speaks XML.
import json
# Simulated API response from a network device
raw_json = '''{
"hostname": "edge-router-1",
"interfaces": [
{"name": "GigabitEthernet0/0", "status": "up", "ip": "192.168.1.1"},
{"name": "GigabitEthernet0/1", "status": "down", "ip": "10.0.0.1"},
{"name": "Loopback0", "status": "up", "ip": "1.1.1.1"}
]
}'''
data = json.loads(raw_json)
print(f"Device: {data['hostname']}")
print('Interface status:')
for iface in data['interfaces']:
flag = ' UP' if iface['status'] == 'up' else 'DOWN'
print(f" [{flag}] {iface['name']:28} {iface['ip']}")
XML — Legacy Protocol Data (NETCONF / SOAP)
NETCONF — the network configuration protocol used by many enterprise devices — uses XML. Python's built-in xml.etree.ElementTree handles it without any pip install.
import xml.etree.ElementTree as ET
xml_data = '''<device>
<hostname>core-switch-1</hostname>
<interface name="Gi0/0">up</interface>
<interface name="Gi0/1">down</interface>
<interface name="Gi0/2">up</interface>
</device>'''
root = ET.fromstring(xml_data)
print(f"Device: {root.find('hostname').text}")
print('Interfaces:')
for iface in root.findall('interface'):
status = iface.text
flag = ' UP' if status == 'up' else 'DOWN'
print(f" [{flag}] {iface.attrib['name']}")
Idempotent Config Push — Look Before You Leap
The single most important pattern in network automation: check whether a change is needed before making it. A script that runs the same change twice without checking creates duplicate config lines — and on Cisco IOS some duplicate lines stack silently, causing subtle misbehavior. The check-before-push pattern (from the discussion notes) is idempotency in practice: run it once, run it ten times — the network ends up in the same state either way.
class simulated_device_connection:
"""Simulates a Netmiko ConnectHandler for safe sandbox practice."""
def __init__(self, hostname, running_config):
self.hostname = hostname
self._config = list(running_config)
self._changes = []
def send_command(self, cmd):
if 'include' in cmd:
keyword = cmd.split('include')[-1].strip()
return '\n'.join(line for line in self._config if keyword in line)
return '\n'.join(self._config)
def send_config_set(self, lines):
for line in lines:
if line not in self._config:
self._config.append(line)
self._changes.append(line)
return f'{self.hostname}: applied {len(lines)} line(s)'
def disconnect(self):
pass
def idempotent_push(conn, config_line):
current = conn.send_command(f'show run | include {config_line}')
if config_line in current:
return f'{conn.hostname}: already present — skipping'
conn.send_config_set([config_line])
return f'{conn.hostname}: ADDED {config_line}'
devices = [
simulated_device_connection('edge-router-1', ['ip ssh version 2', 'ntp server 10.0.0.1']),
simulated_device_connection('core-switch-1', ['ip ssh version 2']),
simulated_device_connection('access-switch-1', []),
]
print('First run:')
for dev in devices:
print(' ', idempotent_push(dev, 'ntp server 10.0.0.1'))
print('\nSecond run (nothing should change):')
for dev in devices:
print(' ', idempotent_push(dev, 'ntp server 10.0.0.1'))
📝 Exercise: Parse a JSON Inventory and Flag Down Interfaces
Parse the JSON string below — it contains three devices, each with a list of interfaces. Write a function audit_interfaces(json_str) that returns a list of (hostname, interface_name) tuples for every interface whose status is 'down'. Print the results.