Files
napalm-redfish/napalm_redfish/parsers/common.py
T
Christian Manivong 09029ca0a4 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). Mapping and
Iterable come from collections.abc, imports are sorted. What ruff cannot
fix comes in the next commit.
2026-10-23 08:39:45 +02:00

138 lines
3.9 KiB
Python

"""Helpers shared by the parsers. All pure: dict in, plain values out."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
# DMI defaults that board vendors leave in place (Supermicro boards ship most of them).
PLACEHOLDERS = frozenset(
{
"to be filled by o.e.m.",
"default string",
"not specified",
"not applicable",
"n/a",
"none",
"0123456789",
"system serial number",
"system product name",
"system manufacturer",
}
)
def text(value: Any) -> str | None:
"""A stripped string, ``None`` for missing or blank values."""
if value is None:
return None
value = str(value).strip()
return value or None
def ident(value: Any) -> str | None:
"""Like ``text``, but DMI placeholder strings count as missing too."""
value = text(value)
if value is None or value.lower() in PLACEHOLDERS:
return None
return value
def number(value: Any) -> float | None:
"""A float, ``None`` if the value is missing or not a number."""
if isinstance(value, bool) or value is None:
return None
try:
return float(value)
except (TypeError, ValueError):
return None
def integer(value: Any) -> int | None:
if isinstance(value, bool) or value is None:
return None
try:
return int(value)
except (TypeError, ValueError):
return None
def boolean(value: Any) -> bool | None:
return value if isinstance(value, bool) else None
def odata_id(ref: Any) -> str | None:
"""The ``@odata.id`` of a reference object, or ``None``."""
if isinstance(ref, Mapping):
return text(ref.get("@odata.id"))
return None
def link(resource: Mapping[str, Any], *keys: str) -> str | None:
"""Follow ``keys`` into nested objects and return the ``@odata.id`` found there."""
value: Any = resource
for key in keys:
if not isinstance(value, Mapping):
return None
value = value.get(key)
return odata_id(value)
def links(resource: Mapping[str, Any]) -> dict[str, str]:
"""All top-level properties of ``resource`` that are single references."""
found = {}
for key, value in resource.items():
if key.startswith("@") or key == "Links":
continue
path = odata_id(value)
if path is not None:
found[key] = path
return found
def link_list(resource: Mapping[str, Any], *keys: str) -> list[str]:
"""The ``@odata.id`` of each reference in the array at ``keys``."""
value: Any = resource
for key in keys:
if not isinstance(value, Mapping):
return []
value = value.get(key)
if not isinstance(value, list):
return []
return [path for path in (odata_id(ref) for ref in value) if path is not None]
def status(resource: Mapping[str, Any]) -> tuple[str | None, str | None]:
"""``(Status.State, Status.Health)``."""
st = resource.get("Status")
if not isinstance(st, Mapping):
return None, None
return text(st.get("State")), text(st.get("Health"))
def present(resource: Mapping[str, Any]) -> bool:
return status(resource)[0] != "Absent"
def location_label(resource: Mapping[str, Any]) -> str | None:
"""``Location.PartLocation.ServiceLabel``, the printed label of a slot or bay."""
loc = resource.get("Location")
if not isinstance(loc, Mapping):
return None
part = loc.get("PartLocation")
if not isinstance(part, Mapping):
return None
return text(part.get("ServiceLabel"))
def unique_name(names: Mapping[str, Any], name: str, suffix: Any) -> str:
"""``name``, or ``name (suffix)`` if it is already taken."""
if name not in names:
return name
candidate = f"{name} ({suffix})"
n = 2
while candidate in names:
candidate = f"{name} ({suffix}-{n})"
n += 1
return candidate