NetAuto Academy

Python for Network Engineers

Build Your First Python Network Automation Script

Beginner18 min read

What you'll learn

  • How to read a complete automation script, section by section, using everything learned this module
  • How variables, dictionaries, lists, loops, conditions, functions, and exception handling fit together in one real script
  • How to trace mock input data through to the exact report.txt file it produces
  • That a script can be fully understood, tested, and trusted before it ever touches a real device
  • How to write a script's results to a file, tying together the file-output concept from Lesson 6
  • A small modification challenge to test your own understanding of the script
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

What are we trying to automate?

A recurring, real network-engineering chore: confirm every switch in a group has synced NTP, and produce a report someone can actually read afterward — not a wall of raw command output, and not a script that quietly assumes every device responded.

This lesson builds that as one complete script, combining every concept from this module — variables, a list of dictionaries, a function, a loop, a condition, exception handling, and now file output — into a single working automation task. Close in spirit to the very first example "What is Network Automation?" described, now fully readable as actual code.

No real devices, no Netmiko

This script doesn't connect to anything. The "devices" are mock data — Python dictionaries with the facts a real check would discover, already filled in — so you can run this on your own laptop, right now, with nothing installed but Python itself. Connecting to real switches is what the later Netmiko module is for; this lesson is about trusting that the logic is correct before it ever touches a real device.

The complete script

ntp_compliance_check.py
# Mock inventory — in a real script, this might come from a CSV or JSON file
devices = [
  {
      "hostname": "SW1",
      "ip": "10.10.10.1",
      "ntp_synced": True,
      "reachable": True,
  },
  {
      "hostname": "SW2",
      "ip": "10.10.10.2",
      "ntp_synced": False,
      "reachable": True,
  },
  {
      "hostname": "SW3",
      "ip": "10.10.10.3",
      "ntp_synced": True,
      "reachable": False,
  },
]


def check_ntp_compliance(device):
  """Simulate checking NTP sync on one device. No real connection is made."""
  if not device["reachable"]:
      raise ConnectionError(f"{device['hostname']} is unreachable")
  return device["ntp_synced"]


results = []

for device in devices:
  print(f"Checking {device['hostname']}...")

  try:
      compliant = check_ntp_compliance(device)
  except ConnectionError:
      print(f"{device['hostname']}: unreachable")
      results.append(f"{device['hostname']} ({device['ip']}): UNREACHABLE")
      print()
      continue

  if compliant:
      print(f"{device['hostname']}: NTP compliant")
      results.append(f"{device['hostname']} ({device['ip']}): NTP compliant")
  else:
      print(f"{device['hostname']}: NTP NOT compliant")
      results.append(f"{device['hostname']} ({device['ip']}): NTP NOT compliant")

  print()

print("Automation complete.")

with open("report.txt", "w") as report_file:
  report_file.write("Network Automation Report\n")
  report_file.write("=========================\n\n")
  for line in results:
      report_file.write(line + "\n")

print("Report written to report.txt")

Save this as ntp_compliance_check.py and run it with 'python ntp_compliance_check.py' — no libraries, no setup, no real switches needed. It will create a report.txt file in the same folder.

Nothing here is a shortcut you haven't already seen: no list comprehensions, no lambdas, no classes — just the same for, if/else, def, and try/except from the last five lessons, arranged to do one real job.

Walking through it, section by section

1. Inventory

section: inventory
devices = [
  {
      "hostname": "SW1",
      "ip": "10.10.10.1",
      "ntp_synced": True,
      "reachable": True,
  },
  # ...SW2, SW3
]

A list of dictionaries — exactly Lesson 3's structured inventory shape.

What it does: defines the mock devices the whole script works from — three switches, each with a hostname, IP, current NTP status, and whether it's reachable. Python concept: a list of dictionaries — the same shape "Devices, Inventory & Source of Truth" called structured inventory, now written as real data. Engineer's view: this is the part a real script would instead load from a CSV, JSON file, or source of truth — everything below this line doesn't care where the list came from, only that it has the fields it needs.

2. Function

section: function
def check_ntp_compliance(device):
  """Simulate checking NTP sync on one device. No real connection is made."""
  if not device["reachable"]:
      raise ConnectionError(f"{device['hostname']} is unreachable")
  return device["ntp_synced"]

One function, one job: decide (or fail to decide) whether a single device is NTP-compliant.

What it does: takes one device, checks whether it's reachable, and either raises an exception (unreachable) or returns its NTP status (reachable). Python concept: def, a parameter, return, and raise — a function packaging repeatable logic, exactly like Lesson 5, now including the possibility of failure. Engineer's view: this is the one place that would change if you swapped the mock check for a real one (a real SSH session and a real show ntp status) — everything else in the script stays the same either way.

3. Main loop

section: main loop
for device in devices:
  print(f"Checking {device['hostname']}...")
  # ...

Visits every device in the inventory, once each.

What it does: repeats everything inside it once per device in devices. Python concept: a for loop, exactly like Lesson 4. Engineer's view: this is "ONE workflow + MANY devices" doing real work — the same loop runs whether devices has 3 entries or 3,000.

4. Condition

section: condition
if compliant:
  print(f"{device['hostname']}: NTP compliant")
  results.append(f"{device['hostname']} ({device['ip']}): NTP compliant")
else:
  print(f"{device['hostname']}: NTP NOT compliant")
  results.append(f"{device['hostname']} ({device['ip']}): NTP NOT compliant")

Only reached for a device that was actually reachable — sorts it based on its NTP status.

What it does: decides, for a device that was successfully checked, whether it's compliant or not, and records the right message either way. Python concept: if/else, exactly like Lesson 4. Engineer's view: this only runs for devices that survived the exception-handling step below — reachability is checked first, compliance second, and the two are kept separate on purpose.

5. Exception handling

section: exception handling
try:
  compliant = check_ntp_compliance(device)
except ConnectionError:
  print(f"{device['hostname']}: unreachable")
  results.append(f"{device['hostname']} ({device['ip']}): UNREACHABLE")
  print()
  continue

Catches an unreachable device without stopping the loop.

What it does: attempts the check; if the device turns out to be unreachable, records that outcome and moves straight on to the next device with continue. Python concept: try/except, exactly like Lesson 7 — narrowed to the one exception type (ConnectionError) this script actually expects, not a bare except:. Engineer's view: this is what keeps SW3 being down from ever affecting SW1 or SW2 — the failure is recorded, not hidden, and the run continues.

6. Result collection

section: result collection
results = []
# ...
results.append(f"{device['hostname']} ({device['ip']}): NTP compliant")

One list, filled in as the loop runs — one entry per device, no matter which branch it took.

What it does: results starts empty and gains exactly one entry per device, whether that device turned out compliant, non-compliant, or unreachable. Python concept: a list, built up with .append() inside a loop. Engineer's view: this is the running record the report gets built from — by the time the loop finishes, results has one line for every device in the inventory, with nothing skipped and nothing duplicated.

7. Report generation

section: report generation
with open("report.txt", "w") as report_file:
  report_file.write("Network Automation Report\n")
  report_file.write("=========================\n\n")
  for line in results:
      report_file.write(line + "\n")

Writes results to an actual file on disk — this is Lesson 6's file-output concept, now doing real work.

What it does: opens (or creates) report.txt, writes a short header, then writes every entry from results as its own line. Python concept: open(..., "w") with with, and .write() — exactly like Lesson 6, looping over a list to write it out line by line. Engineer's view: this is the difference between a script that's useful once, while you're watching the terminal, and one that leaves behind a record — the same reason "Network Automation Fundamentals" argued reporting has to be more than a message that flashes by on screen.

Expected terminal output

Running python ntp_compliance_check.py prints:

terminal
Checking SW1...
SW1: NTP compliant

Checking SW2...
SW2: NTP NOT compliant

Checking SW3...
SW3: unreachable

Automation complete.
Report written to report.txt

One 'Checking...' line and one result line per device, in inventory order — SW1 compliant, SW2 not compliant, SW3 unreachable — then a two-line summary once the loop finishes.

The report file it writes

The script also creates report.txt in the same folder — a record that outlives the terminal session, exactly like Lesson 6:

report.txt
Network Automation Report
=========================

SW1 (10.10.10.1): NTP compliant
SW2 (10.10.10.2): NTP NOT compliant
SW3 (10.10.10.3): UNREACHABLE

Every device gets exactly one line — hostname, IP, and outcome — whether it was compliant, not compliant, or unreachable. Nothing here is invented: this is character-for-character what report_file.write() produces.

Terminal wording vs. report wording

Notice SW3 reads "unreachable" (lowercase) on the terminal but "UNREACHABLE" (uppercase) in report.txt. That's deliberate, not a mistake — the terminal messages are for someone watching the script run right now; the report file is a saved record someone might read later, where an uppercase status is easier to scan for at a glance.

Quick checkIdentify the inventory
Which part of the script is the inventory?
Quick checkIdentify the loop
Which line in the script is the loop that visits every device in the inventory?
Quick checkIdentify the function
Which line defines a function in this script?
Quick checkIdentify the exception handling
Which part of the script is responsible for handling an unreachable device without crashing the whole run?
Quick checkIdentify the correct code
Which code correctly processes every device and records exactly one result per device, matching how this script does it?

A small challenge

Before moving on, try modifying the script yourself — you don't need to submit or share it anywhere, just run it and see what changes:

Add a fourth device to the devices list, reachable and NTP compliant — something like {"hostname": "SW4", "ip": "10.10.10.4", "ntp_synced": True, "reachable": True}.

What to change: just that one new dictionary, added to the devices list. Nothing else in the script needs to change.

Expected behavior: the terminal should print a fourth "Checking SW4..." / "SW4: NTP compliant" pair before "Automation complete," and report.txt should gain a fourth line, SW4 (10.10.10.4): NTP compliant, after SW3's.

If you predict that correctly before running it, that's the real sign this module has landed — not memorizing syntax, but being able to trace mock data through a script and know what comes out the other end, on both the screen and in the file it leaves behind.

What you now know how to read

Every future Python example in this curriculum — including real Netmiko code that actually opens SSH sessions — is built from exactly these same pieces: variables holding device facts, lists and dictionaries structuring an inventory, a loop visiting every device, a condition deciding what to do, a function packaging the logic, a try/except keeping one failure from taking down the whole run, and a file left behind as a record. You already know how to read all of it.

Key takeaways

  • Variables and dictionaries hold each device's own facts — hostname, IP, sync status, reachability — the same shape every lesson in this module has built toward.
  • A for loop applies the same check to every device in the inventory, one at a time, regardless of how many devices there are.
  • A function (check_ntp_compliance) packages the reachability-and-compliance logic once, called fresh for every device.
  • try/except keeps one unreachable device (SW3) from stopping the devices checked after it — the failure is recorded, not hidden, and the run continues.
  • The script doesn't just print a result — it writes report.txt, a saved record someone can read after the script has finished running.