NetAuto Academy

Advanced Python Automation for Network Engineers

Building a Command-Line Tool with argparse

Advanced13 min read

What you'll learn

  • Why hardcoded constants at the top of a script don't scale to a real tool
  • How argparse subcommands structure a multi-action CLI
  • Config precedence: CLI argument > environment variable > default
  • That a token is still never accepted as a CLI argument
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

From constants to arguments

Every capstone script had a block like BASE_URL = "..." at the top, hand-edited to change behavior. A real tool takes that from the command line instead.

network_automation/cli.py — the parser
import argparse
import os

def build_parser():
  parser = argparse.ArgumentParser(prog="network-tool")
  parser.add_argument(
      "--base-url",
      default=os.environ.get("AUTOMATION_BASE_URL", "http://127.0.0.1:8767"),
  )
  subparsers = parser.add_subparsers(dest="command", required=True)

  check_parser = subparsers.add_parser("check", help="Check every device's compliance")
  check_parser.add_argument("--inventory", default="devices.csv")
  check_parser.add_argument("--workers", type=int, default=5)
  check_parser.add_argument("--report", default="report.json")

  report_parser = subparsers.add_parser("report", help="Summarize an existing report file")
  report_parser.add_argument("--report", default="report.json")

  return parser

Two subcommands -- check (run the automation engine) and report (read back an existing report) -- not five. add_subparsers() is what makes 'network-tool check' and 'network-tool report' both valid, each with their own arguments.

Quick checkConcept check
What does add_subparsers() enable that a single flat set of arguments couldn't?

Config precedence: CLI argument > environment variable > default

verify_precedence.py
# No arguments, no env var set:
args = parser.parse_args(["check"])
print("default base-url:", args.base_url)

# A CLI argument, explicitly passed:
args2 = parser.parse_args(["--base-url", "http://example.com", "check"])
print("CLI-arg base-url:", args2.base_url)

default=os.environ.get("AUTOMATION_BASE_URL", "http://127.0.0.1:8767") is what implements the precedence: argparse only falls back to this default when --base-url isn't passed -- and the default itself already checked the environment variable first.

output — real, verified
default base-url: http://127.0.0.1:8767
CLI-arg base-url: http://example.com

# separately, with AUTOMATION_BASE_URL=http://from-env.example set:
env-var base-url: http://from-env.example

Real, verified output across three separate runs — an explicit CLI argument always wins; without one, the environment variable is used; without either, the hardcoded fallback applies.

Quick checkPredict output
AUTOMATION_BASE_URL=http://env-value is set, AND --base-url http://cli-value is passed on the command line. Which one does args.base_url end up as?

Running it: real end-to-end output

terminal
$ export AUTOMATION_TOKEN=demo-token
$ python -m network_automation.cli check --inventory devices.csv --workers 5 --report report.json

AUTOMATION_TOKEN is read from the environment inside run_check() -- never a --token argument. A token on the command line would sit in shell history and process listings; an environment variable at least avoids that.

output — real, verified
Checked 5 devices
already-compliant: 1
validation-failed: 2
auth-failed: 1
unreachable: 1
Report written to report.json

$ python -m network_automation.cli report --report report.json
SW1: already-compliant -- no change needed
SW2: validation-failed -- missing VLANs: [10]
SW5: auth-failed -- SW5 rejected the credentials
SW3: validation-failed -- missing VLANs: [30]
SW4: unreachable -- SW4 timed out

Real, verified output from both subcommands, run for real against this module's mock backend.

Quick checkIdentify the correct code
Which is the correct way to supply the automation token to network-tool check?

Verified: it refuses to run without a token

Running network-tool check with no AUTOMATION_TOKEN set does not fall back to an empty or default credential — it stops immediately, before any connection is attempted.

terminal — no AUTOMATION_TOKEN set
$ python -m network_automation.cli check --inventory devices.csv
AUTOMATION_TOKEN environment variable is required -- never pass a token on the command line

Real, verified output — exit code 1, a clear message, and no attempt to connect without credentials.

Quick checkScenario
A learner wants to point network-tool at a different mock backend port without editing cli.py. What's the correct way, given this lesson's design?

Key takeaways

  • argparse subcommands (network-tool check, network-tool report) replace hardcoded constants edited by hand with a real, invokable CLI.
  • Config precedence — CLI argument > environment variable > default — verified directly across three separate real runs.
  • AUTOMATION_TOKEN is read only from the environment, never accepted as a CLI argument — verified to refuse to run without it.
  • Both subcommands were actually run against this module's mock backend, producing the exact output shown.