Coverage for src / bluetooth_sig / registry / utils.py: 86%
42 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Common utilities for registry modules."""
3from __future__ import annotations
5from pathlib import Path
6from typing import Any, cast
8import msgspec
10from bluetooth_sig.gatt.constants import UINT16_MAX
11from bluetooth_sig.types.uuid import BluetoothUUID
14def load_yaml_uuids(file_path: Path) -> list[dict[str, Any]]:
15 """Load UUID entries from a YAML file.
17 Args:
18 file_path: Path to the YAML file
20 Returns:
21 List of UUID entry dictionaries
22 """
23 if not file_path.exists():
24 return []
26 with file_path.open("r", encoding="utf-8") as file_handle:
27 data = msgspec.yaml.decode(file_handle.read())
29 if not isinstance(data, dict):
30 return []
32 data_dict = cast("dict[str, Any]", data)
33 uuid_entries = data_dict.get("uuids")
34 if not isinstance(uuid_entries, list):
35 return []
37 typed_entries: list[dict[str, Any]] = []
38 for entry in uuid_entries:
39 if isinstance(entry, dict):
40 typed_entries.append(cast("dict[str, Any]", entry))
42 return typed_entries
45def normalize_uuid_string(uuid: str | int) -> str:
46 """Normalize a UUID string or int to uppercase hex without 0x prefix.
48 Args:
49 uuid: UUID as string (with or without 0x) or int
51 Returns:
52 Normalized UUID string
53 """
54 return uuid.replace("0x", "").replace("0X", "") if isinstance(uuid, str) else hex(uuid)[2:].upper()
57def find_bluetooth_sig_path() -> Path | None:
58 """Find the Bluetooth SIG assigned_numbers directory.
60 Returns:
61 Path to the uuids directory, or None if not found
62 """
63 # Try development location first (git submodule)
64 project_root = Path(__file__).parent.parent.parent.parent
65 base_path = project_root / "bluetooth_sig" / "assigned_numbers" / "uuids"
67 if base_path.exists():
68 return base_path
70 # Try installed package location
71 pkg_root = Path(__file__).parent.parent
72 base_path = pkg_root / "bluetooth_sig" / "assigned_numbers" / "uuids"
74 return base_path if base_path.exists() else None
77def parse_bluetooth_uuid(uuid: str | int | BluetoothUUID) -> BluetoothUUID:
78 """Parse various UUID formats into a BluetoothUUID.
80 Args:
81 uuid: UUID as string (with or without 0x), int, or BluetoothUUID
83 Returns:
84 BluetoothUUID instance
86 Raises:
87 ValueError: If UUID format is invalid
88 """
89 if isinstance(uuid, BluetoothUUID):
90 return uuid
91 if isinstance(uuid, int):
92 uuid_str = hex(uuid)[2:].upper()
93 # Pad to 4 characters only for 16-bit UUIDs (0x0000 - 0xFFFF)
94 if 0 <= uuid <= UINT16_MAX:
95 uuid_str = uuid_str.zfill(4)
96 return BluetoothUUID(uuid_str)
97 uuid_str = str(uuid).replace("0x", "").replace("0X", "")
98 return BluetoothUUID(uuid_str)