Python for Network Data — JSON, XML & Idempotent Pushes

25 min In Progress

Learning 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.

code.py
Output

            
← Back to Chapter