"""Catalogues of reusable engineering items (pipe classes, materials, ...). A ``CATALOGUE_ITEM`` / ``CATALOGUE_ITEMS`` property stores item id(s); the catalogue registry resolves them to full records for display and calculation. """ from __future__ import annotations from typing import Any, Iterable, Iterator, Optional class CatalogueItem: """A single catalogue record: an id, a display name and arbitrary attributes.""" __slots__ = ("item_id", "name", "attributes") def __init__(self, item_id: str, name: str, attributes: Optional[dict] = None) -> None: self.item_id = item_id self.name = name self.attributes = dict(attributes or {}) def attr(self, key: str, default: Any = None) -> Any: return self.attributes.get(key, default) def to_dict(self) -> dict: return {"id": self.item_id, "name": self.name, "attributes": self.attributes} @classmethod def from_dict(cls, data: dict) -> "CatalogueItem": return cls(data["id"], data.get("name", data["id"]), data.get("attributes")) def __repr__(self) -> str: # pragma: no cover return f"CatalogueItem({self.item_id!r}, {self.name!r})" class Catalogue: """A named, ordered collection of :class:`CatalogueItem`.""" def __init__(self, name: str, items: Optional[Iterable[CatalogueItem]] = None) -> None: self.name = name self._items: dict[str, CatalogueItem] = {} for it in items or []: self.add(it) def add(self, item: CatalogueItem) -> CatalogueItem: self._items[item.item_id] = item return item def get(self, item_id: str) -> Optional[CatalogueItem]: return self._items.get(item_id) def __iter__(self) -> Iterator[CatalogueItem]: return iter(self._items.values()) def __len__(self) -> int: return len(self._items) def to_dict(self) -> dict: return {"name": self.name, "items": [i.to_dict() for i in self._items.values()]} @classmethod def from_dict(cls, data: dict) -> "Catalogue": return cls(data["name"], [CatalogueItem.from_dict(i) for i in data.get("items", [])]) class CatalogueRegistry: """Holds all catalogues available to a document.""" def __init__(self, catalogues: Optional[Iterable[Catalogue]] = None) -> None: self._catalogues: dict[str, Catalogue] = {} for c in catalogues or []: self.register(c) def register(self, catalogue: Catalogue) -> Catalogue: self._catalogues[catalogue.name] = catalogue return catalogue def get(self, name: str) -> Optional[Catalogue]: return self._catalogues.get(name) def resolve(self, catalogue: str, item_id: str) -> Optional[CatalogueItem]: cat = self._catalogues.get(catalogue) return cat.get(item_id) if cat else None def __iter__(self) -> Iterator[Catalogue]: return iter(self._catalogues.values()) def to_dict(self) -> dict: return {"catalogues": [c.to_dict() for c in self._catalogues.values()]} @classmethod def from_dict(cls, data: dict) -> "CatalogueRegistry": return cls([Catalogue.from_dict(c) for c in data.get("catalogues", [])]) def default_registry() -> CatalogueRegistry: """A small starter registry of pipe classes and materials.""" pipe_classes = Catalogue("pipe_classes", [ CatalogueItem("DN50", "DN50", {"diameter_mm": 50, "pn": 16}), CatalogueItem("DN100", "DN100", {"diameter_mm": 100, "pn": 16}), CatalogueItem("DN200", "DN200", {"diameter_mm": 200, "pn": 10}), CatalogueItem("DN300", "DN300", {"diameter_mm": 300, "pn": 10}), ]) materials = Catalogue("materials", [ CatalogueItem("steel", "Steel", {"roughness_mm": 0.045}), CatalogueItem("pvc", "PVC", {"roughness_mm": 0.0015}), CatalogueItem("copper", "Copper", {"roughness_mm": 0.0015}), CatalogueItem("cast_iron", "Cast Iron", {"roughness_mm": 0.26}), ]) return CatalogueRegistry([pipe_classes, materials])