Coverage for src/bluetooth_sig/registry/browse_groups.py: 75%

48 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 00:10 +0000

1"""Browse group identifiers registry for Bluetooth SIG browse group identifiers.""" 

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from bluetooth_sig.registry.base import BaseRegistry 

8from bluetooth_sig.registry.utils import find_bluetooth_sig_path, load_yaml_uuids, parse_bluetooth_uuid 

9from bluetooth_sig.types.uuid import BluetoothUUID 

10 

11 

12class BrowseGroupInfo(msgspec.Struct, frozen=True, kw_only=True): 

13 """Information about a Bluetooth SIG browse group identifier.""" 

14 

15 uuid: BluetoothUUID 

16 name: str 

17 id: str 

18 

19 

20class BrowseGroupsRegistry(BaseRegistry[BrowseGroupInfo]): 

21 """Registry for Bluetooth SIG browse group identifiers.""" 

22 

23 def __init__(self) -> None: 

24 """Initialize the browse groups registry.""" 

25 super().__init__() 

26 self._browse_groups: dict[str, BrowseGroupInfo] = {} 

27 self._name_to_info: dict[str, BrowseGroupInfo] = {} 

28 self._id_to_info: dict[str, BrowseGroupInfo] = {} 

29 self._load_browse_groups() 

30 

31 def _load_browse_groups(self) -> None: 

32 """Load browse groups from the Bluetooth SIG YAML file.""" 

33 base_path = find_bluetooth_sig_path() 

34 if not base_path: 

35 return 

36 

37 # Load browse group UUIDs 

38 browse_groups_yaml = base_path / "uuids" / "browse_group_identifiers.yaml" 

39 if browse_groups_yaml.exists(): 

40 for item in load_yaml_uuids(browse_groups_yaml): 

41 try: 

42 uuid = parse_bluetooth_uuid(item["uuid"]) 

43 name = item["name"] 

44 browse_group_id = item["id"] 

45 

46 info = BrowseGroupInfo(uuid=uuid, name=name, id=browse_group_id) 

47 

48 # Store by UUID string for fast lookup 

49 self._browse_groups[uuid.short_form.upper()] = info 

50 self._name_to_info[name.lower()] = info 

51 self._id_to_info[browse_group_id] = info 

52 

53 except (KeyError, ValueError): 

54 # Skip malformed entries 

55 continue 

56 

57 def get_browse_group_info(self, uuid: str | int | BluetoothUUID) -> BrowseGroupInfo | None: 

58 """Get browse group information by UUID. 

59 

60 Args: 

61 uuid: The UUID to look up (string, int, or BluetoothUUID) 

62 

63 Returns: 

64 BrowseGroupInfo if found, None otherwise 

65 """ 

66 try: 

67 bt_uuid = parse_bluetooth_uuid(uuid) 

68 return self._browse_groups.get(bt_uuid.short_form.upper()) 

69 except ValueError: 

70 return None 

71 

72 def get_browse_group_info_by_name(self, name: str) -> BrowseGroupInfo | None: 

73 """Get browse group information by name (case insensitive). 

74 

75 Args: 

76 name: The browse group name to look up 

77 

78 Returns: 

79 BrowseGroupInfo if found, None otherwise 

80 """ 

81 return self._name_to_info.get(name.lower()) 

82 

83 def get_browse_group_info_by_id(self, browse_group_id: str) -> BrowseGroupInfo | None: 

84 """Get browse group information by browse group ID. 

85 

86 Args: 

87 browse_group_id: The browse group ID to look up 

88 

89 Returns: 

90 BrowseGroupInfo if found, None otherwise 

91 """ 

92 return self._id_to_info.get(browse_group_id) 

93 

94 def is_browse_group_uuid(self, uuid: str | int | BluetoothUUID) -> bool: 

95 """Check if a UUID corresponds to a known browse group. 

96 

97 Args: 

98 uuid: The UUID to check 

99 

100 Returns: 

101 True if the UUID is a known browse group, False otherwise 

102 """ 

103 return self.get_browse_group_info(uuid) is not None 

104 

105 def get_all_browse_groups(self) -> list[BrowseGroupInfo]: 

106 """Get all browse groups in the registry. 

107 

108 Returns: 

109 List of all BrowseGroupInfo objects 

110 """ 

111 return list(self._browse_groups.values()) 

112 

113 

114# Global instance for convenience 

115browse_groups_registry = BrowseGroupsRegistry.get_instance()