from __future__ import annotations __version__ = "0.1.9" import asyncio import pathlib _OUI_DATA_FILE = pathlib.Path(__file__).parent.joinpath("oui.data") class OUIManager: """Manages the OUI data.""" def __init__(self) -> None: """Initialize the OUIManager.""" self._oui_to_vendor: dict[str, str] = {} self._load_future: asyncio.Future[None] | None = None def get_vendor(self, mac: str) -> str | None: """Get the vendor for a MAC address.""" if not self._oui_to_vendor: raise RuntimeError("OUI data not loaded, call async_load first") return self._oui_to_vendor.get(mac.replace(":", "")[:6].upper()) async def async_load(self) -> None: """Load the OUI data.""" if self._oui_to_vendor: return if self._load_future: await self._load_future return loop = asyncio.get_running_loop() self._load_future = loop.create_future() try: self._oui_to_vendor = await loop.run_in_executor(None, self._load_oui_data) except Exception as err: self._load_future.set_exception(err) raise else: self._load_future.set_result(None) finally: self._load_future = None def _load_oui_data(self) -> dict[str, str]: """Load the OUI data.""" with open(_OUI_DATA_FILE, encoding="utf-8", errors="replace") as f: oui_to_vendor: dict[str, str] = {} for line in f.read().splitlines(): oui, _, vendor = line.partition("=") oui_to_vendor[oui] = vendor return oui_to_vendor _OUI_MANAGER = OUIManager() def is_loaded() -> bool: """Return if the OUI data is loaded.""" return bool(_OUI_MANAGER._oui_to_vendor) def get_vendor(mac: str) -> str | None: """Get the vendor for a MAC address.""" return _OUI_MANAGER.get_vendor(mac) async def async_load() -> None: """Load the OUI data.""" await _OUI_MANAGER.async_load() __all__ = ["async_load", "get_vendor", "is_loaded"]