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

48 statements  

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

1"""Service classes registry for Bluetooth SIG service class definitions.""" 

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

13 """Information about a Bluetooth SIG service class.""" 

14 

15 uuid: BluetoothUUID 

16 name: str 

17 id: str 

18 

19 

20class ServiceClassesRegistry(BaseRegistry[ServiceClassInfo]): 

21 """Registry for Bluetooth SIG service class definitions.""" 

22 

23 def __init__(self) -> None: 

24 """Initialize the service classes registry.""" 

25 super().__init__() 

26 self._service_classes: dict[str, ServiceClassInfo] = {} 

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

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

29 self._load_service_classes() 

30 

31 def _load_service_classes(self) -> None: 

32 """Load service classes from the Bluetooth SIG YAML file.""" 

33 base_path = find_bluetooth_sig_path() 

34 if not base_path: 

35 return 

36 

37 # Load service class UUIDs 

38 service_classes_yaml = base_path / "service_classes.yaml" 

39 if service_classes_yaml.exists(): 

40 for item in load_yaml_uuids(service_classes_yaml): 

41 try: 

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

43 name = item["name"] 

44 service_class_id = item["id"] 

45 

46 info = ServiceClassInfo(uuid=uuid, name=name, id=service_class_id) 

47 

48 # Store by UUID string for fast lookup 

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

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

51 self._id_to_info[service_class_id] = info 

52 

53 except (KeyError, ValueError): 

54 # Skip malformed entries 

55 continue 

56 

57 def get_service_class_info(self, uuid: str | int | BluetoothUUID) -> ServiceClassInfo | None: 

58 """Get service class information by UUID. 

59 

60 Args: 

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

62 

63 Returns: 

64 ServiceClassInfo if found, None otherwise 

65 """ 

66 try: 

67 bt_uuid = parse_bluetooth_uuid(uuid) 

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

69 except ValueError: 

70 return None 

71 

72 def get_service_class_info_by_name(self, name: str) -> ServiceClassInfo | None: 

73 """Get service class information by name (case insensitive). 

74 

75 Args: 

76 name: The service class name to look up 

77 

78 Returns: 

79 ServiceClassInfo if found, None otherwise 

80 """ 

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

82 

83 def get_service_class_info_by_id(self, service_class_id: str) -> ServiceClassInfo | None: 

84 """Get service class information by service class ID. 

85 

86 Args: 

87 service_class_id: The service class ID to look up 

88 

89 Returns: 

90 ServiceClassInfo if found, None otherwise 

91 """ 

92 return self._id_to_info.get(service_class_id) 

93 

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

95 """Check if a UUID corresponds to a known service class. 

96 

97 Args: 

98 uuid: The UUID to check 

99 

100 Returns: 

101 True if the UUID is a known service class, False otherwise 

102 """ 

103 return self.get_service_class_info(uuid) is not None 

104 

105 def get_all_service_classes(self) -> list[ServiceClassInfo]: 

106 """Get all service classes in the registry. 

107 

108 Returns: 

109 List of all ServiceClassInfo objects 

110 """ 

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

112 

113 

114# Global instance for convenience 

115service_classes_registry = ServiceClassesRegistry.get_instance()