Python for Infrastructure Automation›01 · Python essentials for ops people

Lesson 01 of 9 · Modules

Python essentials for ops people

Python for people who already live in a terminal: set up a virtual environment, then learn the core language (values, lists, dictionaries, loops, functions, f-strings and exceptions) through ops-flavoured examples.

Beginner
Key wordsvenvpiptypeslistsdictsloopsfunctionsf-stringsexceptions

Why Python after Bash

Bash is perfect for gluing commands together. When a script needs data structures, error handling, APIs, tests or more than a page of logic, Python is easier to write, read and maintain. Ansible, many Kubernetes operators, cloud CLIs and most infrastructure tooling are written in it.

Bash is like giving someone quick instructions by walkie-talkie: fast, but hard to do anything complicated. Python is like writing a proper plan in a notebook: lists, tables, "if this goes wrong, do that", and it's easy for the next person to read.

Set up a project

$ mkdir ops-tools && cd ops-tools
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ pip install requests pyyaml
(.venv) $ python --version
Python 3.12.3

Always work inside a venv. Installing packages into the system Python can break OS tools (many distributions now refuse pip install system-wide for this reason).

Values and types

host = "web-01"          # str
port = 8080              # int
load = 0.73              # float
healthy = True           # bool
nothing = None           # "no value"

print(type(port), port + 1)          # <class 'int'> 8081
print(f"{host}:{port} load={load:.0%}")   # web-01:8080 load=73%

Lists and dictionaries: the two structures you'll use most

hosts = ["web-01", "web-02", "db-01"]
hosts.append("cache-01")
print(hosts[0], len(hosts))            # web-01 4

server = {"name": "web-01", "ip": "10.0.5.20", "roles": ["web", "api"]}
print(server["ip"])                    # 10.0.5.20
print(server.get("zone", "unknown"))   # unknown (no KeyError)
server["zone"] = "eu-1a"

Real infrastructure data (JSON from APIs, YAML configs, kubectl output) is just nested lists and dictionaries.

Loops and conditions

servers = [
    {"name": "web-01", "disk_pct": 91},
    {"name": "web-02", "disk_pct": 40},
    {"name": "db-01", "disk_pct": 83},
]

for s in servers:
    if s["disk_pct"] >= 90:
        print(f"CRITICAL {s['name']} {s['disk_pct']}%")
    elif s["disk_pct"] >= 80:
        print(f"WARNING  {s['name']} {s['disk_pct']}%")

full = [s["name"] for s in servers if s["disk_pct"] >= 80]   # list comprehension
print(full)                                                    # ['web-01', 'db-01']

Functions

def classify(pct: int, warn: int = 80, crit: int = 90) -> str:
    """Return OK, WARNING or CRITICAL for a usage percentage."""
    if pct >= crit:
        return "CRITICAL"
    if pct >= warn:
        return "WARNING"
    return "OK"

print(classify(85))              # WARNING
print(classify(85, warn=90))     # OK

Type hints (pct: int, -> str) are optional, but they document intent and let editors catch mistakes.

Errors: exceptions

from pathlib import Path

def read_config(path: str) -> str:
    try:
        return Path(path).read_text()
    except FileNotFoundError:
        print(f"config {path} not found, using defaults")
        return ""
    except PermissionError:
        raise SystemExit(f"cannot read {path}: permission denied")

Catch specific exceptions you can handle; let unexpected ones surface with a full traceback. That's a feature, not a failure.

Try it: disk report in Python

  1. Create a venv and a file disk_report.py.
  2. Use shutil.disk_usage("/") to get total/used/free bytes and compute the used percentage.
  3. Put three mount points in a list (e.g. ["/", "/var", "/tmp"]), loop over them, and print OK/WARNING/CRITICAL with the classify function. Skip paths that don't exist (Path(p).exists()).
  4. Print the results as an aligned table with f-strings (f"{mount:<10} {pct:>5.1f}%").

Going deeper: habits from day one

  • Follow PEP 8 style; let ruff (linter and formatter) enforce it automatically.
  • Put code in functions and use if __name__ == "__main__": main() so files can be imported and tested (lesson 08).
  • Prefer pathlib.Path over string paths, and the logging module over print once a tool is more than a quick script (lesson 05).
  • Pin dependencies (requirements.txt or pyproject.toml) so a tool behaves the same next year.

Recap

  • Always use a venv; install with pip, record with pip freeze.
  • Core types: str, int, float, bool, None; structures: lists and dicts (nest them freely).
  • for, if/elif/else, comprehensions, functions with defaults and type hints, f-strings.
  • Handle specific exceptions; let the rest fail loudly.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.