"""Kleiner Client für die phpIPAM REST API. Doku: https://phpipam.net/api/api_documentation/ """ from __future__ import annotations import httpx from .config import settings class PhpIpamClient: def __init__(self) -> None: self._base = f"{settings.phpipam_base_url.rstrip('/')}/api/{settings.phpipam_app_id}" self._token: str | None = settings.phpipam_app_token self._client = httpx.Client(verify=settings.phpipam_verify_ssl, timeout=30.0) def close(self) -> None: self._client.close() def __enter__(self) -> "PhpIpamClient": return self def __exit__(self, *exc) -> None: self.close() # ------------------------------------------------------------------ # Auth # ------------------------------------------------------------------ def _ensure_token(self) -> str: if self._token: return self._token if not (settings.phpipam_username and settings.phpipam_password): raise RuntimeError( "Weder PHPIPAM_APP_TOKEN noch PHPIPAM_USERNAME/PHPIPAM_PASSWORD gesetzt." ) resp = self._client.post( f"{self._base}/user/", auth=(settings.phpipam_username, settings.phpipam_password), ) resp.raise_for_status() data = resp.json() if not data.get("success"): raise RuntimeError(f"phpipam Login fehlgeschlagen: {data}") self._token = data["data"]["token"] return self._token def _headers(self) -> dict[str, str]: return {"token": self._ensure_token()} # ------------------------------------------------------------------ # API calls # ------------------------------------------------------------------ def _get(self, path: str, params: dict | None = None) -> dict: resp = self._client.get(f"{self._base}{path}", headers=self._headers(), params=params) if resp.status_code == 404: # phpipam liefert 404 z.B. wenn ein Subnetz keine Adressen hat return {"success": False, "data": []} resp.raise_for_status() return resp.json() def list_subnets(self) -> list[dict]: """Alle Subnetze (flach, inkl. verschachtelter unter allen Sections).""" data = self._get("/subnets/") return data.get("data") or [] def list_addresses(self, subnet_id: int | str) -> list[dict]: """Alle belegten Adressen eines Subnetzes.""" data = self._get(f"/subnets/{subnet_id}/addresses/") return data.get("data") or [] def get_section_name(self, section_id: int | str) -> str | None: data = self._get(f"/sections/{section_id}/") section = data.get("data") return section.get("name") if section else None