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
« 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.
3This module provides SpecialValueResolver that centralises special-value
4lookups with a priority: user overrides > class rules > spec rules.
6It reuses the data types defined in `bluetooth_sig.types.special_values`.
7"""
9from __future__ import annotations
11from bluetooth_sig.types.special_values import SpecialValueResult, SpecialValueRule
13from ..types.units import SpecialValueType
16class SpecialValueResolver:
17 """Resolve raw integers to SpecialValueResult and provide reverse lookups.
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 """
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.
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] = {}
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
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
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)
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
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()
71 if raw_value in self._class_rules:
72 return self._class_rules[raw_value].to_result()
74 if raw_value in self._spec_rules:
75 return self._spec_rules[raw_value].to_result()
77 return None
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.
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
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
102 # --------------------------- introspection ---------------------------
103 def list_all_rules(self) -> dict[int, tuple[SpecialValueRule, str]]:
104 """Return merged rules with source tag ('user','class','spec').
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
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