Coverage for src / bluetooth_sig / gatt / characteristics / context_lookup.py: 94%
35 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Context lookup mixin for GATT characteristics.
3Provides methods to retrieve dependency and sibling characteristics from
4a :class:`CharacteristicContext`, extracted from :mod:`.base` to keep it
5focused on core parsing/encoding.
6"""
8from __future__ import annotations
10import re
11from functools import lru_cache
12from typing import Any
14from ...types import CharacteristicInfo
15from ...types.gatt_enums import CharacteristicName
16from ...types.uuid import BluetoothUUID
17from ..context import CharacteristicContext
18from ..uuid_registry import uuid_registry
21class ContextLookupMixin:
22 """Mixin providing context-based characteristic lookup helpers.
24 These methods allow a characteristic to resolve its dependencies and
25 siblings from a shared :class:`CharacteristicContext` at parse/encode
26 time.
27 """
29 @staticmethod
30 @lru_cache(maxsize=32)
31 def _get_characteristic_uuid_by_name(
32 characteristic_name: CharacteristicName | str,
33 ) -> BluetoothUUID | None:
34 """Get characteristic UUID by name using cached registry lookup."""
35 name_str = (
36 characteristic_name.value if isinstance(characteristic_name, CharacteristicName) else characteristic_name
37 )
38 char_info = uuid_registry.get_characteristic_info(name_str)
39 return char_info.uuid if char_info else None
41 def get_context_characteristic(
42 self,
43 ctx: CharacteristicContext | None,
44 characteristic_name: CharacteristicName | str | type,
45 ) -> Any: # noqa: ANN401 # Type determined by characteristic_name at runtime
46 """Find a characteristic in a context by name or class.
48 Note:
49 Returns ``Any`` because the characteristic type is determined at
50 runtime by *characteristic_name*. For type-safe access, use direct
51 characteristic class instantiation instead of this lookup method.
53 Args:
54 ctx: Context containing other characteristics.
55 characteristic_name: Enum, string name, or characteristic class.
57 Returns:
58 Parsed characteristic value if found, ``None`` otherwise.
60 """
61 if not ctx or not ctx.other_characteristics:
62 return None
64 if isinstance(characteristic_name, type):
65 configured_info: CharacteristicInfo | None = getattr(characteristic_name, "_configured_info", None)
66 if configured_info is not None:
67 char_uuid = configured_info.uuid
68 else:
69 class_name: str = characteristic_name.__name__
70 name_without_suffix: str = class_name.replace("Characteristic", "")
71 sig_name: str = re.sub(r"(?<!^)(?=[A-Z])", " ", name_without_suffix)
72 resolved_uuid = self._get_characteristic_uuid_by_name(sig_name)
73 if resolved_uuid is None:
74 return None
75 char_uuid = resolved_uuid
76 else:
77 resolved_uuid = self._get_characteristic_uuid_by_name(characteristic_name)
78 if resolved_uuid is None:
79 return None
80 char_uuid = resolved_uuid
82 return ctx.other_characteristics.get(str(char_uuid))