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

1"""Context lookup mixin for GATT characteristics. 

2 

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""" 

7 

8from __future__ import annotations 

9 

10import re 

11from functools import lru_cache 

12from typing import Any 

13 

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 

19 

20 

21class ContextLookupMixin: 

22 """Mixin providing context-based characteristic lookup helpers. 

23 

24 These methods allow a characteristic to resolve its dependencies and 

25 siblings from a shared :class:`CharacteristicContext` at parse/encode 

26 time. 

27 """ 

28 

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 

40 

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. 

47 

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. 

52 

53 Args: 

54 ctx: Context containing other characteristics. 

55 characteristic_name: Enum, string name, or characteristic class. 

56 

57 Returns: 

58 Parsed characteristic value if found, ``None`` otherwise. 

59 

60 """ 

61 if not ctx or not ctx.other_characteristics: 

62 return None 

63 

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 

81 

82 return ctx.other_characteristics.get(str(char_uuid))