style: ruff format and the lint rules of the driver standard

Mechanical: ruff format, and ruff check --fix with the rule set every NAPALM
driver uses (E, F, I, UP, B at line length 100, Python 3.10). What ruff
cannot fix comes in the next commit.
This commit is contained in:
Christian Manivong committed 2026-10-23 08:32:50 +02:00
1 parent 739283e76b
commit c2a788fa7a
6 files changed
+307 -278

No files matched your search

+72 -73
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0
"""CLI output parsers for HPE/Aruba ProCurve switches.
@@ -9,13 +8,12 @@ backends.
"""
import re
from typing import Dict, List, Optional
# ---------------------------------------------------------------------------
# MAC address helpers
# ---------------------------------------------------------------------------
def procurve_mac_to_std(mac: str) -> str:
"""Convert ProCurve MAC format ``a1b2c3-d4e5f6`` to ``a1:b2:c3:d4:e5:f6``."""
clean = mac.replace("-", "").replace(":", "").replace(".", "").lower()
@@ -38,7 +36,8 @@ def _kv(output: str, key: str) -> str:
# show system-information
# ---------------------------------------------------------------------------
def parse_system_info(output: str) -> Dict:
def parse_system_info(output: str) -> dict:
"""Parse ``show system-information`` output.
Returns a dict with keys: hostname, os_version, serial_number, model,
@@ -63,10 +62,8 @@ def parse_system_info(output: str) -> Dict:
# between them, e.g.:
# " Software revision : R.11.27 Base MAC Addr : a1b2c3-d4e5f6"
# We capture all k:v pairs on every line.
_KV_RE = re.compile(
r"([A-Za-z][A-Za-z0-9 ()/#-]{1,40}?)\s*:\s*(\S.*?)(?=\s{3,}[A-Za-z]|$)"
)
kv_map: Dict[str, str] = {}
_KV_RE = re.compile(r"([A-Za-z][A-Za-z0-9 ()/#-]{1,40}?)\s*:\s*(\S.*?)(?=\s{3,}[A-Za-z]|$)")
kv_map: dict[str, str] = {}
for raw_line in output.splitlines():
for m in _KV_RE.finditer(raw_line):
k = m.group(1).strip()
@@ -163,6 +160,7 @@ def _parse_procurve_uptime(uptime_str: str) -> float:
# show version
# ---------------------------------------------------------------------------
def parse_version(output: str) -> str:
"""Extract firmware version from ``show version`` or ``show system-information``.
@@ -234,24 +232,24 @@ def parse_model_from_version(output: str) -> tuple[str, str]:
# 1 100/1000T | Yes Down Auto Unknown off 0
_INTF_BRIEF_RE = re.compile(
r"^\s*([^\s-]+)" # Port
r"(?:-(Trk\d+))?\s+" # Trunk group of a member port ("3-Trk3")
r"(\S+)\s+\|" # Type |
r"^\s*([^\s-]+)" # Port
r"(?:-(Trk\d+))?\s+" # Trunk group of a member port ("3-Trk3")
r"(\S+)\s+\|" # Type |
r"\s+(?:(?:Yes|No)\s+)?" # Intrusion Alert, on firmware that has the column
r"(Yes|No)\s+" # Enabled
r"(Up|Down)\s+" # Link
r"(\S+)\s+(\S+)", # Mode and MDI, in either order depending on firmware
r"(Yes|No)\s+" # Enabled
r"(Up|Down)\s+" # Link
r"(\S+)\s+(\S+)", # Mode and MDI, in either order depending on firmware
re.IGNORECASE,
)
def parse_interfaces_brief(output: str) -> Dict[str, Dict]:
def parse_interfaces_brief(output: str) -> dict[str, dict]:
"""Parse ``show interfaces brief`` tabular output.
Returns dict keyed by port name with sub-keys:
is_up, is_enabled, speed, duplex, description.
"""
interfaces: Dict[str, Dict] = {}
interfaces: dict[str, dict] = {}
for line in output.splitlines():
m = _INTF_BRIEF_RE.match(line)
if not m:
@@ -260,9 +258,7 @@ def parse_interfaces_brief(output: str) -> Dict[str, Dict]:
speed = 0.0
duplex = ""
# Mode examples: "1000FDx", "100HDx", "Unknown"; MDI is "Auto", "MDI", "MDIX", "NA"
sm = next(
filter(None, (re.match(r"(\d+)(FDx|HDx)?", t, re.I) for t in mode_or_mdi)), None
)
sm = next(filter(None, (re.match(r"(\d+)(FDx|HDx)?", t, re.I) for t in mode_or_mdi)), None)
if sm:
speed = float(sm.group(1))
duplex = "full" if (sm.group(2) or "").lower() == "fdx" else "half"
@@ -286,7 +282,8 @@ def parse_interfaces_brief(output: str) -> Dict[str, Dict]:
# show interfaces (per-port detail for MAC / description)
# ---------------------------------------------------------------------------
def parse_interface_detail(output: str, port: str) -> Dict:
def parse_interface_detail(output: str, port: str) -> dict:
"""Parse ``show interfaces <port>`` for MAC address and description."""
mac = procurve_mac_to_std(_kv(output, "MAC address"))
name = _kv(output, "Name") or _kv(output, "Description")
@@ -297,7 +294,8 @@ def parse_interface_detail(output: str, port: str) -> Dict:
# show ip / show ip interface
# ---------------------------------------------------------------------------
def parse_ip_interfaces(output: str) -> Dict[str, Dict]:
def parse_ip_interfaces(output: str) -> dict[str, dict]:
"""Parse ``show ip`` or ``show ip interface brief`` output.
Returns NAPALM-compatible dict::
@@ -314,7 +312,7 @@ def parse_ip_interfaces(output: str) -> Dict[str, Dict]:
IP Configuration for VLAN 99:
...
"""
result: Dict[str, Dict] = {}
result: dict[str, dict] = {}
current_vlan = ""
current_ip = ""
@@ -343,6 +341,7 @@ def parse_ip_interfaces(output: str) -> Dict[str, Dict]:
mask = line.split(":", 1)[-1].strip()
try:
import netaddr
prefix_len = netaddr.IPNetwork(f"{current_ip}/{mask}").prefixlen
except Exception:
prefix_len = 24
@@ -362,23 +361,21 @@ def parse_ip_interfaces(output: str) -> Dict[str, Dict]:
_ARP_RE = re.compile(
r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # IP
r"\s+"
r"([0-9a-fA-F]{6}-[0-9a-fA-F]{6}|" # MAC ProCurve hex-dash format
r"[0-9a-fA-F:]{17})" # or standard colon format
r"\s+(\S+)" # Type
r"(?:\s+(\S+))?", # Port (optional)
r"([0-9a-fA-F]{6}-[0-9a-fA-F]{6}|" # MAC ProCurve hex-dash format
r"[0-9a-fA-F:]{17})" # or standard colon format
r"\s+(\S+)" # Type
r"(?:\s+(\S+))?", # Port (optional)
)
def parse_arp_table(output: str) -> List[Dict]:
def parse_arp_table(output: str) -> list[dict]:
"""Parse ``show arp`` output into NAPALM ARP table format."""
table = []
for line in output.splitlines():
m = _ARP_RE.search(line)
if not m:
continue
ip, mac, entry_type, port = (
m.group(1), m.group(2), m.group(3), m.group(4) or ""
)
ip, mac, entry_type, port = (m.group(1), m.group(2), m.group(3), m.group(4) or "")
table.append(
{
"interface": port,
@@ -396,13 +393,13 @@ def parse_arp_table(output: str) -> List[Dict]:
_MAC_RE = re.compile(
r"([0-9a-fA-F]{6}-[0-9a-fA-F]{6}|[0-9a-fA-F:]{17})" # MAC
r"\s+(\S+)" # Port
r"\s+(\d+)" # VLAN
r"\s+(\w+)", # Type
r"\s+(\S+)" # Port
r"\s+(\d+)" # VLAN
r"\s+(\w+)", # Type
)
def parse_mac_table(output: str) -> List[Dict]:
def parse_mac_table(output: str) -> list[dict]:
"""Parse ``show mac-address`` into NAPALM MAC table format."""
table = []
for line in output.splitlines():
@@ -428,7 +425,8 @@ def parse_mac_table(output: str) -> List[Dict]:
# show lldp info remote-device
# ---------------------------------------------------------------------------
def parse_lldp_neighbors(output: str) -> Dict[str, List[Dict]]:
def parse_lldp_neighbors(output: str) -> dict[str, list[dict]]:
"""Parse ``show lldp info remote-device`` into NAPALM neighbors dict.
Output format::
@@ -437,7 +435,7 @@ def parse_lldp_neighbors(output: str) -> Dict[str, List[Dict]]:
--------- + ------------------- ------------ ------------ ...
1 | a1 b2 c3 d4 e5 f6 Gi 0/1 otherswitch ...
"""
neighbors: Dict[str, List[Dict]] = {}
neighbors: dict[str, list[dict]] = {}
in_table = False
for line in output.splitlines():
@@ -472,21 +470,19 @@ def parse_lldp_neighbors(output: str) -> Dict[str, List[Dict]]:
if re.match(r"^[0-9a-fA-F]{12}$", chassis):
chassis = ":".join(chassis[i : i + 2] for i in range(0, 12, 2))
neighbors.setdefault(local_port, []).append(
{"hostname": sys_name, "port": port_id}
)
neighbors.setdefault(local_port, []).append({"hostname": sys_name, "port": port_id})
return neighbors
def parse_lldp_neighbors_detail(output: str) -> Dict[str, List[Dict]]:
def parse_lldp_neighbors_detail(output: str) -> dict[str, list[dict]]:
"""Parse ``show lldp info remote-device detail`` output.
Returns NAPALM lldp_neighbors_detail format.
"""
neighbors: Dict[str, List[Dict]] = {}
neighbors: dict[str, list[dict]] = {}
current_port = ""
entry: Dict = {}
entry: dict = {}
def _save():
if current_port and entry:
@@ -516,9 +512,11 @@ def parse_lldp_neighbors_detail(output: str) -> Dict[str, List[Dict]]:
low = line.lower()
if "chassis id" in low:
val = line.split(":", 1)[-1].strip()
entry["remote_chassis_id"] = procurve_mac_to_std(
re.sub(r"\s+", "", val)
) if re.search(r"[0-9a-fA-F ]{14,}", val) else val
entry["remote_chassis_id"] = (
procurve_mac_to_std(re.sub(r"\s+", "", val))
if re.search(r"[0-9a-fA-F ]{14,}", val)
else val
)
elif "port id" in low and "descr" not in low:
entry["remote_port"] = line.split(":", 1)[-1].strip()
elif "port description" in low:
@@ -528,9 +526,7 @@ def parse_lldp_neighbors_detail(output: str) -> Dict[str, List[Dict]]:
elif "system description" in low:
entry["remote_system_description"] = line.split(":", 1)[-1].strip()
elif "system capabilities" in low and "enabled" not in low:
entry["remote_system_capab"] = _parse_capabilities(
line.split(":", 1)[-1].strip()
)
entry["remote_system_capab"] = _parse_capabilities(line.split(":", 1)[-1].strip())
elif "enabled capabilities" in low or ("system capabilities" in low and "enabled" in low):
entry["remote_system_enable_capab"] = _parse_capabilities(
line.split(":", 1)[-1].strip()
@@ -540,7 +536,7 @@ def parse_lldp_neighbors_detail(output: str) -> Dict[str, List[Dict]]:
return neighbors
def _parse_capabilities(cap_str: str) -> List[str]:
def _parse_capabilities(cap_str: str) -> list[str]:
"""Convert ProCurve capability abbreviations to NAPALM strings."""
_MAP = {
"B": "bridge",
@@ -566,12 +562,13 @@ def _parse_capabilities(cap_str: str) -> List[str]:
# show ntp associations / show timesync
# ---------------------------------------------------------------------------
def parse_ntp_servers(output: str) -> Dict[str, Dict]:
def parse_ntp_servers(output: str) -> dict[str, dict]:
"""Parse ``show ntp associations`` or ``show ntp status`` output.
Returns dict of {server_ip: {}} as per NAPALM get_ntp_servers.
"""
servers: Dict[str, Dict] = {}
servers: dict[str, dict] = {}
for line in output.splitlines():
# " 10.0.0.1 sys.peer 2 ..." or "NTP server: 10.0.0.1"
m = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", line)
@@ -588,9 +585,10 @@ def parse_ntp_servers(output: str) -> Dict[str, Dict]:
# show environment
# ---------------------------------------------------------------------------
def parse_environment(output: str) -> Dict:
def parse_environment(output: str) -> dict:
"""Parse ``show environment`` output into NAPALM environment dict."""
env: Dict = {
env: dict = {
"fans": {},
"temperature": {},
"power": {},
@@ -629,9 +627,7 @@ def parse_environment(output: str) -> Dict:
}
# Fan status
for m in re.finditer(
r"Fan\s*(\d+)\s*[:\s]+(\S+)", output, re.I
):
for m in re.finditer(r"Fan\s*(\d+)\s*[:\s]+(\S+)", output, re.I):
fan_id = f"fan_{m.group(1)}"
status = m.group(2).lower()
env["fans"][fan_id] = {"status": status in ("ok", "good", "normal", "operating")}
@@ -643,12 +639,13 @@ def parse_environment(output: str) -> Dict:
# show local-users
# ---------------------------------------------------------------------------
def parse_users(output: str) -> Dict[str, Dict]:
def parse_users(output: str) -> dict[str, dict]:
"""Parse ``show local-users`` or ``show users`` output.
Returns NAPALM users dict.
"""
users: Dict[str, Dict] = {}
users: dict[str, dict] = {}
for line in output.splitlines():
# " manager Operator Enabled ..." or " manager Manager ..."
m = re.match(
@@ -678,9 +675,10 @@ def parse_users(output: str) -> Dict[str, Dict]:
# show snmp-server / show snmp
# ---------------------------------------------------------------------------
def parse_snmp_info(output: str) -> Dict:
def parse_snmp_info(output: str) -> dict:
"""Parse ``show snmp-server`` output into NAPALM snmp_information dict."""
communities: Dict[str, Dict] = {}
communities: dict[str, dict] = {}
contact = _kv(output, "System Contact") or _kv(output, "Contact")
location = _kv(output, "System Location") or _kv(output, "Location")
chassis_id = ""
@@ -708,7 +706,8 @@ def parse_snmp_info(output: str) -> Dict:
# VLAN helpers
# ---------------------------------------------------------------------------
def expand_procurve_ports(port_str: str) -> List[str]:
def expand_procurve_ports(port_str: str) -> list[str]:
"""Expand a ProCurve port-list string into a flat list of port names.
Handles:
@@ -717,7 +716,7 @@ def expand_procurve_ports(port_str: str) -> List[str]:
- Trunk ports: ``Trk1,Trk2`` → ``["Trk1","Trk2"]``
- Mixed: ``1-3,Trk1,A1`` → ``["1","2","3","Trk1","A1"]``
"""
ports: List[str] = []
ports: list[str] = []
for token in port_str.split(","):
token = token.strip()
if not token:
@@ -746,7 +745,7 @@ def expand_procurve_ports(port_str: str) -> List[str]:
return ports
def parse_vlans_from_running_config(config_text: str) -> Dict[int, Dict]:
def parse_vlans_from_running_config(config_text: str) -> dict[int, dict]:
"""Parse VLAN blocks from ProCurve ``show running-config`` output.
Extracts ``name``, ``tagged``, and ``untagged`` port lists per VLAN.
@@ -777,8 +776,8 @@ def parse_vlans_from_running_config(config_text: str) -> Dict[int, Dict]:
"untagged": []},
}
"""
vlans: Dict[int, Dict] = {}
current_id: Optional[int] = None
vlans: dict[int, dict] = {}
current_id: int | None = None
for line in config_text.splitlines():
stripped = line.strip()
@@ -818,7 +817,7 @@ def parse_vlans_from_running_config(config_text: str) -> Dict[int, Dict]:
# Populate "interfaces" as union of tagged + untagged (NAPALM standard key)
for vdata in vlans.values():
seen: List[str] = []
seen: list[str] = []
for p in vdata["untagged"] + vdata["tagged"]:
if p not in seen:
seen.append(p)
@@ -827,7 +826,7 @@ def parse_vlans_from_running_config(config_text: str) -> Dict[int, Dict]:
return vlans
def parse_vlans_brief(output: str) -> Dict[int, str]:
def parse_vlans_brief(output: str) -> dict[int, str]:
"""Parse ``show vlans`` brief table for VLAN IDs and names only.
Used as a discovery step when running-config is unavailable.
@@ -841,7 +840,7 @@ def parse_vlans_brief(output: str) -> Dict[int, str]:
Returns ``{vlan_id: name}`` dict.
"""
vlans: Dict[int, str] = {}
vlans: dict[int, str] = {}
in_table = False
for line in output.splitlines():
@@ -857,7 +856,7 @@ def parse_vlans_brief(output: str) -> Dict[int, str]:
return vlans
def parse_vlan_ports_detail(output: str) -> Dict[str, List[str]]:
def parse_vlan_ports_detail(output: str) -> dict[str, list[str]]:
"""Parse ``show vlans <id>`` per-VLAN port detail output.
Returns ``{"tagged": [...], "untagged": [...]}`` for ONE vlan.
@@ -870,8 +869,8 @@ def parse_vlan_ports_detail(output: str) -> Dict[str, List[str]]:
2 Tagged Learn Up
Trk1 Tagged Learn Up
"""
tagged: List[str] = []
untagged: List[str] = []
tagged: list[str] = []
untagged: list[str] = []
in_table = False
for line in output.splitlines():
@@ -892,7 +891,7 @@ def parse_vlan_ports_detail(output: str) -> Dict[str, List[str]]:
return {"tagged": tagged, "untagged": untagged}
def derive_port_pvids(vlans: Dict[int, Dict]) -> Dict[str, int]:
def derive_port_pvids(vlans: dict[int, dict]) -> dict[str, int]:
"""Derive PVID (native/access VLAN) for each port from the ``get_vlans`` result.
On ProCurve a port is untagged in exactly ONE VLAN — that is its PVID.
@@ -910,7 +909,7 @@ def derive_port_pvids(vlans: Dict[int, Dict]) -> Dict[str, int]:
})
# → {"1": 1, "2": 1, "3": 1, "5": 1}
"""
pvids: Dict[str, int] = {}
pvids: dict[str, int] = {}
# First pass: assign PVID from untagged membership
for vid, vdata in sorted(vlans.items()):