Python Typing
Type annotations, generics, TypeVar, and static type checking with mypy.
Basic Type Hints
Variable annotations — documentation for humans and tools, not enforced at runtime
hostname: str = "sw-core-01"
port: int = 443
enabled: bool = True
ratio: float = 0.95
Function signatures — parameter types and return type
def ping(host: str, count: int = 4) -> bool:
...
def get_config(device: str) -> str | None:
...
Optional and Union
Optional — value can be the type or None, two equivalent syntaxes
from typing import Optional
# These are equivalent (Python 3.10+)
def find_device(name: str) -> Optional[str]: # older style
...
def find_device(name: str) -> str | None: # modern style (preferred)
...
Union — value can be one of several types
# Python 3.10+ syntax
def parse_input(data: str | bytes | dict) -> dict:
if isinstance(data, str):
return json.loads(data)
elif isinstance(data, bytes):
return json.loads(data.decode())
return data
Collection Types
Built-in generics — Python 3.9+ allows lowercase, no import needed
# Python 3.9+ (preferred)
hostnames: list[str] = ["sw1", "sw2"]
ip_map: dict[str, str] = {"sw1": "10.50.1.10"}
vlan_set: set[int] = {10, 20, 30}
coordinates: tuple[float, float] = (33.98, -118.45)
# Variable-length tuple
log_entries: tuple[str, ...] = ("entry1", "entry2", "entry3")
Nested generics — collections inside collections
# Dict mapping hostnames to list of interface names
interface_map: dict[str, list[str]] = {
"sw1": ["Gi1/0/1", "Gi1/0/2"],
"sw2": ["Gi1/0/1"],
}
# List of tuples (hostname, ip, port)
endpoints: list[tuple[str, str, int]] = [
("sw1", "10.50.1.10", 22),
("sw2", "10.50.1.11", 22),
]
TypedDict
TypedDict — typed dictionary with known keys, useful for API responses and structured data
from typing import TypedDict
class DeviceInfo(TypedDict):
hostname: str
ip: str
model: str
port: int
def get_device(name: str) -> DeviceInfo:
return {"hostname": name, "ip": "10.50.1.10", "model": "C9300", "port": 22}
device = get_device("sw1")
device["hostname"] # type checker knows this is str
TypedDict with optional keys — total=False makes all keys optional, or use NotRequired per-key
from typing import TypedDict, NotRequired
class DeviceConfig(TypedDict):
hostname: str # required
ip: str # required
description: NotRequired[str] # optional (Python 3.11+)
tags: NotRequired[list[str]] # optional
Protocol (Structural Typing)
Protocol — define an interface by structure, not inheritance. Any matching class satisfies it
from typing import Protocol
class Pingable(Protocol):
hostname: str
def ping(self) -> bool: ...
# This class satisfies Pingable without inheriting from it
class Switch:
def __init__(self, hostname: str):
self.hostname = hostname
def ping(self) -> bool:
return os.system(f"ping -c 1 {self.hostname}") == 0
def check_device(device: Pingable) -> str:
return f"{device.hostname}: {'UP' if device.ping() else 'DOWN'}"
Literal
Literal — restrict values to specific constants, catches typos at type-check time
from typing import Literal
def set_port_mode(mode: Literal["access", "trunk", "dynamic"]) -> None:
...
set_port_mode("access") # OK
set_port_mode("acces") # type checker catches the typo
Literal with return types — narrow the return type to specific values
def get_status(host: str) -> Literal["up", "down", "unreachable"]:
...
Dataclasses
@dataclass — typed class with auto-generated init, repr, eq
from dataclasses import dataclass
@dataclass
class Endpoint:
hostname: str
ip: str
port: int = 443
tags: list[str] | None = None
# Type checker validates construction
e = Endpoint(hostname="sw1", ip="10.50.1.10")
e = Endpoint(hostname="sw1", ip=443) # type error: ip should be str
Frozen dataclass — immutable, hashable, use for config and constants
@dataclass(frozen=True)
class VLANConfig:
id: int
name: str
subnet: str
# Immutable -- can use as dict key or in sets
vlan10 = VLANConfig(10, "data", "10.50.10.0/24")
Dataclass with field() — mutable defaults, metadata, validators
from dataclasses import dataclass, field
@dataclass
class Switch:
hostname: str
interfaces: list[str] = field(default_factory=list)
_session: object = field(default=None, repr=False, compare=False)
def __post_init__(self):
self.hostname = self.hostname.lower()
Callable
Callable type — specify function signatures as types
from typing import Callable
# Function that takes a string and returns bool
Validator = Callable[[str], bool]
def apply_validators(value: str, validators: list[Validator]) -> bool:
return all(v(value) for v in validators)
# Usage
is_not_empty: Validator = lambda s: len(s) > 0
is_fqdn: Validator = lambda s: "." in s
apply_validators("sw1.domain.com", [is_not_empty, is_fqdn])
Type Aliases
Type alias — give complex types a readable name
# Simple alias
IPAddress = str
HostName = str
# Complex alias
DeviceMap = dict[str, list[tuple[IPAddress, int]]]
def get_topology() -> DeviceMap:
return {
"sw1": [("10.50.1.10", 22), ("10.50.1.10", 443)],
}
TypeAlias (Python 3.10+) — explicit annotation for clarity
from typing import TypeAlias
InterfaceTable: TypeAlias = dict[str, list[str]]
Runtime Type Checking
isinstance — validate types at runtime, use for input validation
def process(data: str | dict) -> dict:
if isinstance(data, str):
return json.loads(data)
if isinstance(data, dict):
return data
raise TypeError(f"Expected str or dict, got {type(data).__name__}")
Type guard (Python 3.10+) — tell the type checker about narrowing
from typing import TypeGuard
def is_string_list(val: list) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
def process(items: list) -> None:
if is_string_list(items):
# type checker now knows items is list[str]
print(", ".join(items))
Practical Typing: Network Automation
Typed device inventory — combining TypedDict, Literal, and generics
from typing import TypedDict, Literal
class InterfaceInfo(TypedDict):
name: str
status: Literal["up", "down", "admin-down"]
vlan: int | None
speed: str
class DeviceInventory(TypedDict):
hostname: str
model: str
interfaces: list[InterfaceInfo]
def parse_show_interfaces(output: str) -> list[InterfaceInfo]:
...
def build_inventory(devices: list[str]) -> list[DeviceInventory]:
...