Coverage for src / bluetooth_sig / gatt / special_values_resolver.py: 40%

55 statements  

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

1"""Resolver for merged special-value rules with runtime overrides. 

2 

3This module provides SpecialValueResolver that centralises special-value 

4lookups with a priority: user overrides > class rules > spec rules. 

5 

6It reuses the data types defined in `bluetooth_sig.types.special_values`. 

7""" 

8 

9from __future__ import annotations 

10 

11from bluetooth_sig.types.special_values import SpecialValueResult, SpecialValueRule 

12 

13from ..types.units import SpecialValueType 

14 

15 

16class SpecialValueResolver: 

17 """Resolve raw integers to SpecialValueResult and provide reverse lookups. 

18 

19 Behavior: 

20 - The resolver stores three sources: 

21 - spec_rules (lowest priority) 

22 - class_rules 

23 - user_overrides (highest priority) 

24 - Users may add rules at runtime or disable a raw value (by setting a 

25 sentinel None in user_overrides). 

26 """ 

27 

28 def __init__( 

29 self, 

30 spec_rules: dict[int, SpecialValueRule] | None = None, 

31 class_rules: dict[int, SpecialValueRule] | None = None, 

32 ) -> None: 

33 """Initialize resolver with spec and class-level rules. 

34 

35 Args: 

36 spec_rules: Rules from Bluetooth SIG specifications (lowest priority) 

37 class_rules: Rules from class-level _special_values (medium priority) 

38 """ 

39 self._spec_rules: dict[int, SpecialValueRule] = spec_rules or {} 

40 self._class_rules: dict[int, SpecialValueRule] = class_rules or {} 

41 # _user_overrides maps raw_value -> SpecialValueRule | None (None = disabled) 

42 self._user_overrides: dict[int, SpecialValueRule | None] = {} 

43 

44 # ------------------------ runtime mutation API ------------------------- 

45 def set_user_override(self, raw_value: int, rule: SpecialValueRule) -> None: 

46 """Set or replace a user override for a raw value.""" 

47 self._user_overrides[raw_value] = rule 

48 

49 def add_special_value(self, rule: SpecialValueRule) -> None: 

50 """Convenience to add a user-defined special rule.""" 

51 self._user_overrides[rule.raw_value] = rule 

52 

53 def clear_user_override(self, raw_value: int) -> None: 

54 """Clear any user override (fall back to class/spec).""" 

55 self._user_overrides.pop(raw_value, None) 

56 

57 def disable_special_value(self, raw_value: int) -> None: 

58 """Disable special-value handling for a raw value (treat as normal data).""" 

59 self._user_overrides[raw_value] = None 

60 

61 # -------------------------- resolution API ---------------------------- 

62 def resolve(self, raw_value: int) -> SpecialValueResult | None: 

63 """Return SpecialValueResult if raw_value is special, otherwise None.""" 

64 # priority: user_overrides > class_rules > spec_rules 

65 if raw_value in self._user_overrides: 

66 rule = self._user_overrides[raw_value] 

67 if rule is None: # explicit disable 

68 return None 

69 return rule.to_result() 

70 

71 if raw_value in self._class_rules: 

72 return self._class_rules[raw_value].to_result() 

73 

74 if raw_value in self._spec_rules: 

75 return self._spec_rules[raw_value].to_result() 

76 

77 return None 

78 

79 # ----------------------- reverse lookup helpers ----------------------- 

80 def get_raw_for_type(self, value_type: SpecialValueType) -> int | None: 

81 """Return the first raw value matching the given SpecialValueType. 

82 

83 Search priority: user > class > spec. 

84 """ 

85 for rules in (self._user_overrides, self._class_rules, self._spec_rules): 

86 for raw, rule in rules.items(): 

87 if rule is not None and rule.value_type == value_type: 

88 return raw 

89 return None 

90 

91 def get_raw_for_meaning(self, meaning: str) -> int | None: 

92 """Return first raw value where meaning contains the given string (ci, partial).""" 

93 m = meaning.lower() 

94 for rules in (self._user_overrides, self._class_rules, self._spec_rules): 

95 for raw, rule in rules.items(): 

96 if rule is None: 

97 continue 

98 if m in rule.meaning.lower(): 

99 return raw 

100 return None 

101 

102 # --------------------------- introspection --------------------------- 

103 def list_all_rules(self) -> dict[int, tuple[SpecialValueRule, str]]: 

104 """Return merged rules with source tag ('user','class','spec'). 

105 

106 User overrides (including disables) take precedence and may remove 

107 a rule by setting it to None. 

108 """ 

109 result: dict[int, tuple[SpecialValueRule, str]] = {} 

110 # Add spec (lowest priority) 

111 for raw, rule in self._spec_rules.items(): 

112 result[raw] = (rule, "spec") 

113 # Override with class rules 

114 for raw, rule in self._class_rules.items(): 

115 result[raw] = (rule, "class") 

116 # Apply user overrides 

117 for raw, rule in self._user_overrides.items(): 

118 if rule is None: 

119 result.pop(raw, None) 

120 else: 

121 result[raw] = (rule, "user") 

122 return result 

123 

124 def is_special(self, raw_value: int) -> bool: 

125 """Quick boolean check whether raw_value resolves to a special rule.""" 

126 return self.resolve(raw_value) is not None