Skip to content

Commit 88168ce

Browse files
authored
add ANY_VALUE sentinel (#189)
1 parent 51fb5cd commit 88168ce

File tree

9 files changed

+34
-22
lines changed

9 files changed

+34
-22
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
- Added `ANY_VALUE` sentinel for clearer semantics in predicates - use this to indicate "any value is acceptable"
12+
13+
### Changed
14+
- `NOT_PROVIDED` predicate is now used only to indicate that a parameter was not provided to a function
15+
1016
## [0.15.5] - 2025-11-14
1117

1218
### Changed

docs/pages/core-concepts/bus/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ filtering.
241241
### Dictionary filtering
242242

243243
```python
244-
from hassette.const import NOT_PROVIDED
244+
from hassette.const import ANY_VALUE
245245

246246
# Literal match
247247
self.bus.on_call_service(
@@ -255,7 +255,7 @@ self.bus.on_call_service(
255255
self.bus.on_call_service(
256256
domain="light",
257257
service="turn_on",
258-
where={"brightness": NOT_PROVIDED},
258+
where={"brightness": ANY_VALUE},
259259
handler=self.on_brightness_set,
260260
)
261261

src/hassette/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .app import App, AppConfig, AppSync, only_app
55
from .bus import Bus, accessors, conditions, predicates
66
from .config import HassetteConfig
7-
from .const import MISSING_VALUE, NOT_PROVIDED
7+
from .const import ANY_VALUE, MISSING_VALUE, NOT_PROVIDED
88
from .core import Hassette
99
from .events import StateChangeEvent
1010
from .models import entities, states
@@ -15,6 +15,7 @@
1515
logging.getLogger("hassette").addHandler(logging.NullHandler())
1616

1717
__all__ = [
18+
"ANY_VALUE",
1819
"MISSING_VALUE",
1920
"NOT_PROVIDED",
2021
"Api",

src/hassette/bus/accessors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Accessors are combined with predicates to easily and cleanly extract values from events. Instead of writing
3-
a lambda like ``lambda e: e.payload.data.old_state.state``, you can use the accessor ``get_state_value_old`` or
4-
use ``get_path("payload.data.old_state.state")`` for a more generic solution.
3+
a lambda like ``lambda e: e.payload.data.old_state_value``, you can use the accessor ``get_state_value_old`` or
4+
use ``get_path("payload.data.old_state_value")`` for a more generic solution.
55
66
You generally will not need to use these directly — the main bus helpers use them under the hood to provide
77
the relevant data to predicates. For example, ``on_state_change`` uses ``get_state_value_old`` and

src/hassette/bus/predicates.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def is_kitchen_light(entity_id: str) -> bool:
4848
from logging import getLogger
4949
from typing import Any, Generic, TypeVar
5050

51-
from hassette.const import MISSING_VALUE, NOT_PROVIDED
51+
from hassette.const import ANY_VALUE, MISSING_VALUE
5252
from hassette.events import CallServiceEvent
5353
from hassette.events.base import EventT
5454
from hassette.models.states import StateT
@@ -142,14 +142,14 @@ class ValueIs(Generic[EventT, V]):
142142
143143
Args:
144144
source: Callable that extracts the value to compare from the event.
145-
condition: A literal or callable tested against the extracted value. If NOT_PROVIDED, always True.
145+
condition: A literal or callable tested against the extracted value. If ANY_VALUE, always True.
146146
"""
147147

148148
source: Callable[[EventT], V]
149-
condition: ChangeType = NOT_PROVIDED
149+
condition: ChangeType = ANY_VALUE
150150

151151
def __call__(self, event: EventT) -> bool:
152-
if self.condition is NOT_PROVIDED:
152+
if self.condition is ANY_VALUE:
153153
return True
154154
value = self.source(event)
155155
return compare_value(value, self.condition)
@@ -366,9 +366,10 @@ def __post_init__(self) -> None:
366366

367367
for k, cond in self.spec.items():
368368
source = get_service_data_key(k)
369+
c: ChangeType
369370
# presence check
370-
if cond is NOT_PROVIDED:
371-
c: ChangeType = Present()
371+
if cond is ANY_VALUE:
372+
c = Present()
372373
# auto-glob wrapping
373374
elif self.auto_glob and isinstance(cond, str) and is_glob(cond):
374375
c = Glob(cond)

src/hassette/const/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from .colors import COLORS, Color
2-
from .misc import MISSING_VALUE, NOT_PROVIDED
2+
from .misc import ANY_VALUE, MISSING_VALUE, NOT_PROVIDED
33
from .sensor import DEVICE_CLASS, STATE_CLASS, UNIT_OF_MEASUREMENT
44

55
__all__ = [
6+
"ANY_VALUE",
67
"COLORS",
78
"DEVICE_CLASS",
89
"MISSING_VALUE",

src/hassette/const/misc.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ def __bool__(self) -> bool:
1313

1414
NOT_PROVIDED = FalseySentinel("NOT_PROVIDED")
1515
"""Sentinel value to indicate a value was not provided."""
16+
17+
ANY_VALUE = FalseySentinel("ANY_VALUE")
18+
"""Sentinel value to indicate any value is acceptable (used in predicates for presence checks)."""

tests/predicates/test_predicates.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from hassette import conditions as C
1010
from hassette import predicates as P
1111
from hassette.bus.utils import compare_value, ensure_tuple, normalize_where
12-
from hassette.const import MISSING_VALUE, NOT_PROVIDED
12+
from hassette.const import ANY_VALUE, MISSING_VALUE, NOT_PROVIDED
1313
from hassette.events import CallServiceEvent, Event
1414

1515

@@ -315,15 +315,15 @@ def brightness_gt_200(value: int) -> bool:
315315

316316

317317
def test_service_data_where_with_not_provided() -> None:
318-
"""Test ServiceDataWhere requiring key presence with NOT_PROVIDED."""
318+
"""Test ServiceDataWhere requiring key presence with ANY_VALUE."""
319319
event = _create_service_event(domain="light", service="turn_on", service_data={"entity_id": "light.kitchen"})
320320

321321
# Key exists
322-
predicate = P.ServiceDataWhere({"entity_id": NOT_PROVIDED})
322+
predicate = P.ServiceDataWhere({"entity_id": ANY_VALUE})
323323
assert predicate(event) is True
324324

325325
# Key missing
326-
predicate = P.ServiceDataWhere({"brightness": NOT_PROVIDED})
326+
predicate = P.ServiceDataWhere({"brightness": ANY_VALUE})
327327
assert predicate(event) is False
328328

329329

tests/predicates/test_service_data_where.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from types import SimpleNamespace
33

44
from hassette.bus.predicates import ServiceDataWhere
5-
from hassette.const import NOT_PROVIDED
5+
from hassette.const import ANY_VALUE
66
from hassette.events import Event
77

88

@@ -13,16 +13,16 @@ def _make_event(service_data: dict[str, typing.Any]) -> Event:
1313

1414

1515
def test_service_data_where_not_provided_requires_presence() -> None:
16-
"""Test that ServiceDataWhere with NOT_PROVIDED requires key presence."""
17-
predicate = ServiceDataWhere({"required": NOT_PROVIDED})
16+
"""Test that ServiceDataWhere with ANY_VALUE requires key presence."""
17+
predicate = ServiceDataWhere({"required": ANY_VALUE})
1818

1919
assert predicate(_make_event({"required": 0})) is True
2020
assert predicate(_make_event({})) is False
2121

2222

2323
def test_service_data_where_typing_any_requires_presence() -> None:
24-
"""Test that ServiceDataWhere with NOT_PROVIDED works with any value type."""
25-
predicate = ServiceDataWhere({"required": NOT_PROVIDED})
24+
"""Test that ServiceDataWhere with ANY_VALUE works with any value type."""
25+
predicate = ServiceDataWhere({"required": ANY_VALUE})
2626

2727
assert predicate(_make_event({"required": "value"})) is True
2828
assert predicate(_make_event({})) is False
@@ -74,7 +74,7 @@ def test_service_data_where_multiple_conditions() -> None:
7474
predicate = ServiceDataWhere(
7575
{
7676
"entity_id": "light.*",
77-
"brightness": NOT_PROVIDED, # Must be present
77+
"brightness": ANY_VALUE, # Must be present
7878
"transition": 2,
7979
}
8080
)

0 commit comments

Comments
 (0)