Coverage for src / bluetooth_sig / gatt / services / custom.py: 86%

29 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

1"""Custom service implementation for user-defined services.""" 

2 

3from __future__ import annotations 

4 

5from ...types import ServiceInfo 

6from .base import BaseGattService 

7 

8 

9class CustomBaseGattService(BaseGattService): 

10 """Helper base class for custom service implementations. 

11 

12 This class provides a wrapper around custom services that are not 

13 defined in the Bluetooth SIG specification. 

14 """ 

15 

16 _is_custom = True 

17 _is_base_class = True # Exclude from registry validation tests 

18 _configured_info: ServiceInfo | None = None 

19 _allows_sig_override = False 

20 

21 def __init_subclass__(cls, allow_sig_override: bool = False, **kwargs: object) -> None: 

22 """Set up _info if provided as class attribute. 

23 

24 Args: 

25 allow_sig_override: Set to True when intentionally overriding SIG UUIDs 

26 **kwargs: Additional keyword arguments 

27 

28 """ 

29 super().__init_subclass__(**kwargs) 

30 cls._allows_sig_override = allow_sig_override 

31 

32 info = cls._info 

33 if info is not None: 

34 if not allow_sig_override and info.uuid.is_sig_service(): 

35 raise ValueError(f"{cls.__name__} uses SIG UUID {info.uuid} without override flag") 

36 cls._configured_info = info 

37 

38 def __init__(self, info: ServiceInfo | None = None) -> None: 

39 """Initialize a custom service. 

40 

41 Args: 

42 info: Optional override for class-configured _info 

43 

44 """ 

45 final_info = info or self.__class__._configured_info 

46 if not final_info: 

47 raise ValueError(f"{self.__class__.__name__} requires 'info' parameter or '_info' class attribute") 

48 if not final_info.uuid or str(final_info.uuid) == "0000": 

49 raise ValueError("Valid UUID is required for custom services") 

50 super().__init__(info=final_info) 

51 

52 def __post_init__(self) -> None: 

53 """Initialize custom service info management.""" 

54 if hasattr(self, "_provided_info") and self._provided_info: 

55 self._info = self._provided_info 

56 elif self.__class__._configured_info: # pylint: disable=protected-access 

57 self._info = self.__class__._configured_info # pylint: disable=protected-access 

58 else: 

59 raise ValueError(f"CustomBaseGattService {self.__class__.__name__} has no valid info source")