"""Read a node's configuration and latest telemetry. Demonstrates two things beyond the typed resources: - Looking up a node by its Barbara ID (``deviceName``) or by its internal ``_id``. - Reading data that has no dedicated resource method yet — the node's live telemetry — through the client's low-level ``request(...)`` escape hatch. Every resource method is built on the same call, so it is always available for an endpoint this SDK doesn't wrap yet. Usage: python node_info.py Credentials are read from environment variables (BBR_API_CLIENT_ID / BBR_API_CLIENT_SECRET / BBR_API_USERNAME / BBR_API_PASSWORD), optionally loaded from a .env file first — see load_dotenv() below and README.md. """ from __future__ import annotations import argparse import json import os import re from typing import Any, Optional from barbara import BarbaraApiError, BarbaraAuthError, BarbaraClient, BarbaraNotFoundError from barbara.models import Node from barbara.utils import unwrap INTERNAL_ID_RE = re.compile(r"^[0-9a-f]{24}$") def load_dotenv(path: str) -> None: """Minimal .env loader (no extra dependency). Variables already set in the environment always take precedence over the file. """ if not os.path.isfile(path): return with open(path, encoding="utf-8") as fh: for line in fh: line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'")) def resolve_node(client: BarbaraClient, identifier: str) -> Node: """Accept either an internal ``_id`` or a Barbara ID (``deviceName``).""" if INTERNAL_ID_RE.match(identifier): return client.nodes.get(identifier) return client.nodes.resolve(identifier) def get_static_info(node: Node) -> dict[str, Any]: """The node's stable configuration: identity, version, network, GPS... Everything here comes from the single ``GET /v1/devices/{id}`` call already made by ``client.nodes.get``/``resolve`` — no extra request. """ raw = node.raw return { "identity": { "deviceName": raw.get("deviceName"), "name": raw.get("name"), "tags": raw.get("tags"), "created": raw.get("created"), }, "version": raw.get("deviceVersion"), "config": raw.get("deviceConfig"), "gps": raw.get("gps"), "alarms": raw.get("alarms"), "status": raw.get("status"), } def get_telemetry(client: BarbaraClient, node_id: str) -> Optional[dict[str, Any]]: """Latest telemetry sample, via ``GET /v2/devices/{id}/telemetries/last``. Not wrapped by a resource method yet, so this calls the client directly instead of going through ``client.nodes``. """ try: data = unwrap(client.request("GET", f"/api/v2/devices/{node_id}/telemetries/last")) except BarbaraApiError: # The node may not have reported a v2 sample yet (e.g. an old agent). return None return dict(data) if data else None def main() -> None: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("node", help="Node name (deviceName) or internal _id") parser.add_argument( "--env-file", default=".env", help="Path to a .env file with Barbara API Credentials" ) args = parser.parse_args() load_dotenv(args.env_file) with BarbaraClient.from_env() as client: try: client.api_version() except BarbaraAuthError as exc: raise SystemExit(f"Login failed — check your Barbara API Credentials: {exc}") print("Login OK.") try: node = resolve_node(client, args.node) except BarbaraNotFoundError: raise SystemExit(f"No node found matching '{args.node}'") result = { "nodeId": node.id, "static": get_static_info(node), "telemetry": get_telemetry(client, node.id), } print(json.dumps(result, indent=2, default=str)) if __name__ == "__main__": main()