Coverage for src/bluetooth_sig/registry/members.py: 94%
48 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
1"""Member UUID registry for Bluetooth SIG member companies."""
3from __future__ import annotations
5import threading
7import msgspec
9from bluetooth_sig.types.uuid import BluetoothUUID
11from .utils import find_bluetooth_sig_path, load_yaml_uuids, normalize_uuid_string, parse_bluetooth_uuid
14class MemberInfo(msgspec.Struct, frozen=True, kw_only=True):
15 """Information about a Bluetooth SIG member company."""
17 uuid: BluetoothUUID
18 name: str
21class MembersRegistry:
22 """Registry for Bluetooth SIG member company UUIDs."""
24 def __init__(self) -> None:
25 """Initialize the members registry."""
26 self._lock = threading.RLock()
27 self._members: dict[str, MemberInfo] = {} # normalized_uuid -> MemberInfo
28 self._members_by_name: dict[str, MemberInfo] = {} # lower_name -> MemberInfo
30 try:
31 self._load_members()
32 except (FileNotFoundError, Exception): # pylint: disable=broad-exception-caught
33 # If YAML loading fails, continue with empty registry
34 pass
36 def _load_members(self) -> None:
37 """Load member UUIDs from YAML file."""
38 base_path = find_bluetooth_sig_path()
39 if not base_path:
40 return
42 # Load member UUIDs
43 member_yaml = base_path / "member_uuids.yaml"
44 if member_yaml.exists():
45 for uuid_info in load_yaml_uuids(member_yaml):
46 uuid = normalize_uuid_string(uuid_info["uuid"])
48 bt_uuid = BluetoothUUID(uuid)
49 info = MemberInfo(uuid=bt_uuid, name=uuid_info["name"])
50 # Store using short form as key for easy lookup
51 self._members[bt_uuid.short_form.upper()] = info
52 # Also store by name for reverse lookup
53 self._members_by_name[uuid_info["name"].lower()] = info
55 def get_member_name(self, uuid: str | int | BluetoothUUID) -> str | None:
56 """Get member company name by UUID.
58 Args:
59 uuid: 16-bit UUID as string (with or without 0x), int, or BluetoothUUID
61 Returns:
62 Member company name, or None if not found
63 """
64 with self._lock:
65 try:
66 bt_uuid = parse_bluetooth_uuid(uuid)
68 # Get the short form (16-bit) for lookup
69 short_key = bt_uuid.short_form.upper()
70 if short_key in self._members:
71 return self._members[short_key].name
73 return None
74 except ValueError:
75 return None
77 def is_member_uuid(self, uuid: str | int | BluetoothUUID) -> bool:
78 """Check if a UUID is a registered member company UUID.
80 Args:
81 uuid: UUID to check
83 Returns:
84 True if the UUID is a member UUID, False otherwise
85 """
86 return self.get_member_name(uuid) is not None
88 def get_all_members(self) -> list[MemberInfo]:
89 """Get all registered member companies.
91 Returns:
92 List of all MemberInfo objects
93 """
94 with self._lock:
95 return list(self._members.values())
97 def get_member_info_by_name(self, name: str) -> MemberInfo | None:
98 """Get member information by company name.
100 Args:
101 name: Company name (case-insensitive)
103 Returns:
104 MemberInfo object, or None if not found
105 """
106 with self._lock:
107 return self._members_by_name.get(name.lower())
110# Global instance
111members_registry = MembersRegistry()