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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""Custom service implementation for user-defined services."""
3from __future__ import annotations
5from ...types import ServiceInfo
6from .base import BaseGattService
9class CustomBaseGattService(BaseGattService):
10 """Helper base class for custom service implementations.
12 This class provides a wrapper around custom services that are not
13 defined in the Bluetooth SIG specification.
14 """
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
21 def __init_subclass__(cls, allow_sig_override: bool = False, **kwargs: object) -> None:
22 """Set up _info if provided as class attribute.
24 Args:
25 allow_sig_override: Set to True when intentionally overriding SIG UUIDs
26 **kwargs: Additional keyword arguments
28 """
29 super().__init_subclass__(**kwargs)
30 cls._allows_sig_override = allow_sig_override
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
38 def __init__(self, info: ServiceInfo | None = None) -> None:
39 """Initialize a custom service.
41 Args:
42 info: Optional override for class-configured _info
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)
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")