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

48 statements  

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

1"""Declarations registry for Bluetooth SIG GATT attribute declarations.""" 

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 DeclarationInfo(msgspec.Struct, frozen=True, kw_only=True): 

13 """Information about a Bluetooth SIG GATT attribute declaration.""" 

14 

15 uuid: BluetoothUUID 

16 name: str 

17 id: str 

18 

19 

20class DeclarationsRegistry(BaseRegistry[DeclarationInfo]): 

21 """Registry for Bluetooth SIG GATT attribute declarations.""" 

22 

23 def __init__(self) -> None: 

24 """Initialize the declarations registry.""" 

25 super().__init__() 

26 self._declarations: dict[str, DeclarationInfo] = {} 

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

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

29 self._load_declarations() 

30 

31 def _load_declarations(self) -> None: 

32 """Load declarations from the Bluetooth SIG YAML file.""" 

33 base_path = find_bluetooth_sig_path() 

34 if not base_path: 

35 return 

36 

37 # Load declaration UUIDs 

38 declarations_yaml = base_path / "uuids" / "declarations.yaml" 

39 if declarations_yaml.exists(): 

40 for item in load_yaml_uuids(declarations_yaml): 

41 try: 

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

43 name = item["name"] 

44 declaration_id = item["id"] 

45 

46 info = DeclarationInfo(uuid=uuid, name=name, id=declaration_id) 

47 

48 # Store by UUID string for fast lookup 

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

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

51 self._id_to_info[declaration_id] = info 

52 

53 except (KeyError, ValueError): 

54 # Skip malformed entries 

55 continue 

56 

57 def get_declaration_info(self, uuid: str | int | BluetoothUUID) -> DeclarationInfo | None: 

58 """Get declaration information by UUID. 

59 

60 Args: 

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

62 

63 Returns: 

64 DeclarationInfo if found, None otherwise 

65 """ 

66 try: 

67 bt_uuid = parse_bluetooth_uuid(uuid) 

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

69 except ValueError: 

70 return None 

71 

72 def get_declaration_info_by_name(self, name: str) -> DeclarationInfo | None: 

73 """Get declaration information by name (case insensitive). 

74 

75 Args: 

76 name: The declaration name to look up 

77 

78 Returns: 

79 DeclarationInfo if found, None otherwise 

80 """ 

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

82 

83 def get_declaration_info_by_id(self, declaration_id: str) -> DeclarationInfo | None: 

84 """Get declaration information by declaration ID. 

85 

86 Args: 

87 declaration_id: The declaration ID to look up 

88 

89 Returns: 

90 DeclarationInfo if found, None otherwise 

91 """ 

92 return self._id_to_info.get(declaration_id) 

93 

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

95 """Check if a UUID corresponds to a known declaration. 

96 

97 Args: 

98 uuid: The UUID to check 

99 

100 Returns: 

101 True if the UUID is a known declaration, False otherwise 

102 """ 

103 return self.get_declaration_info(uuid) is not None 

104 

105 def get_all_declarations(self) -> list[DeclarationInfo]: 

106 """Get all declarations in the registry. 

107 

108 Returns: 

109 List of all DeclarationInfo objects 

110 """ 

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

112 

113 

114# Global instance for convenience 

115declarations_registry = DeclarationsRegistry.get_instance()