Skip to content

Conditions

Conditions receive one or more values extracted from an event and return a boolean indicating whether the condition is met.

These are the building blocks for more complex predicates used in event listeners. Any callable that takes a single value (or two values for comparison conditions) and returns a boolean can be used as a condition, so you can also implement your own custom conditions or pass lambda functions.

With helpers like on_state_change or on_attribute_change you will generally pass conditions to changed_to or changed_from. These conditions will receive the relevant value extracted from the event. Some helpers also allow you to pass conditions to changed, which will receive both the old and new values for comparison.

Examples:

Regex matching

from hassette import C

await self.bus.on_state_change(
    "sensor.my_phone_location",
    handler=handler,
    changed_to=C.Regex(r"^1101 Main .*"),
    name="phone_location",
)

Value is in a collection

from hassette import C

await self.bus.on_state_change(
    "sensor.my_phone_activity",
    handler=handler,
    changed_to=C.IsIn(["walking", "running"]),
    name="phone_activity",
)

Using comparison conditions

from hassette import C

await self.bus.on_state_change(
    "zone.home",
    handler=handler,
    changed=C.Increased(),
    name="zone_home_count",
)

Glob dataclass

Callable matcher for string glob patterns.

Examples:

ValueIs(source=get_entity_id, condition=Glob("light.*"))

# Multiple patterns (wrap with AnyOf)
AnyOf((ValueIs(source=get_entity_id, condition=Glob("light.*")),
       ValueIs(source=get_entity_id, condition=Glob("switch.*"))))
Source code in src/hassette/event_handling/conditions.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@dataclass(frozen=True)
class Glob:
    """Callable matcher for string glob patterns.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=Glob("light.*"))

    # Multiple patterns (wrap with AnyOf)
    AnyOf((ValueIs(source=get_entity_id, condition=Glob("light.*")),
           ValueIs(source=get_entity_id, condition=Glob("switch.*"))))
    ```
    """

    pattern: str

    def __call__(self, value: Any) -> bool:
        return isinstance(value, str) and matches_globs(value, (self.pattern,))

    def summarize(self) -> str:
        return f"matches {self.pattern}"

    def __repr__(self) -> str:
        return f"Glob({self.pattern!r})"

StartsWith dataclass

Callable matcher for string startswith checks.

Examples:

ValueIs(source=get_entity_id, condition=StartsWith("light."))

# Multiple prefixes (wrap with AnyOf)
AnyOf((ValueIs(source=get_entity_id, condition=StartsWith("light.")),
       ValueIs(source=get_entity_id, condition=StartsWith("switch."))))
Source code in src/hassette/event_handling/conditions.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@dataclass(frozen=True)
class StartsWith:
    """Callable matcher for string startswith checks.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=StartsWith("light."))

    # Multiple prefixes (wrap with AnyOf)
    AnyOf((ValueIs(source=get_entity_id, condition=StartsWith("light.")),
           ValueIs(source=get_entity_id, condition=StartsWith("switch."))))
    ```
    """

    prefix: str

    def __call__(self, value: Any) -> bool:
        return isinstance(value, str) and value.startswith(self.prefix)

    def summarize(self) -> str:
        return f"starts with {self.prefix}"

    def __repr__(self) -> str:
        return f"StartsWith({self.prefix!r})"

EndsWith dataclass

Callable matcher for string endswith checks.

Examples:

ValueIs(source=get_entity_id, condition=EndsWith(".kitchen"))

# Multiple suffixes (wrap with AnyOf)
AnyOf((ValueIs(source=get_entity_id, condition=EndsWith(".kitchen")),
       ValueIs(source=get_entity_id, condition=EndsWith(".living_room"))))
Source code in src/hassette/event_handling/conditions.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
@dataclass(frozen=True)
class EndsWith:
    """Callable matcher for string endswith checks.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=EndsWith(".kitchen"))

    # Multiple suffixes (wrap with AnyOf)
    AnyOf((ValueIs(source=get_entity_id, condition=EndsWith(".kitchen")),
           ValueIs(source=get_entity_id, condition=EndsWith(".living_room"))))
    ```
    """

    suffix: str

    def __call__(self, value: Any) -> bool:
        return isinstance(value, str) and value.endswith(self.suffix)

    def summarize(self) -> str:
        return f"ends with {self.suffix}"

    def __repr__(self) -> str:
        return f"EndsWith({self.suffix!r})"

Contains dataclass

Callable matcher for string containment checks.

Examples:

ValueIs(source=get_entity_id, condition=Contains("kitchen"))

# Multiple substrings (wrap with AnyOf)
AnyOf((ValueIs(source=get_entity_id, condition=Contains("kitchen")),
       ValueIs(source=get_entity_id, condition=Contains("living_room"))))
Source code in src/hassette/event_handling/conditions.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@dataclass(frozen=True)
class Contains:
    """Callable matcher for string containment checks.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=Contains("kitchen"))

    # Multiple substrings (wrap with AnyOf)
    AnyOf((ValueIs(source=get_entity_id, condition=Contains("kitchen")),
           ValueIs(source=get_entity_id, condition=Contains("living_room"))))
    ```
    """

    substring: str

    def __call__(self, value: Any) -> bool:
        return isinstance(value, str) and self.substring in value

    def summarize(self) -> str:
        return f"contains {self.substring}"

    def __repr__(self) -> str:
        return f"Contains({self.substring!r})"

Regex dataclass

Callable matcher for regex pattern matching.

Examples:

ValueIs(source=get_entity_id, condition=Regex(r"light\..*kitchen"))

# Multiple patterns (wrap with AnyOf)
AnyOf((ValueIs(source=get_entity_id, condition=Regex(r"light\..*kitchen")),
       ValueIs(source=get_entity_id, condition=Regex(r"switch\..*living_room"))))
Source code in src/hassette/event_handling/conditions.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
@dataclass(frozen=True)
class Regex:
    """Callable matcher for regex pattern matching.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=Regex(r"light\\..*kitchen"))

    # Multiple patterns (wrap with AnyOf)
    AnyOf((ValueIs(source=get_entity_id, condition=Regex(r"light\\..*kitchen")),
           ValueIs(source=get_entity_id, condition=Regex(r"switch\\..*living_room"))))
    ```
    """

    pattern: str
    _compiled: re.Pattern = field(init=False, repr=False)

    def __post_init__(self) -> None:
        object.__setattr__(self, "_compiled", re.compile(self.pattern))

    def __call__(self, value: Any) -> bool:
        return isinstance(value, str) and self._compiled.match(value) is not None

    def summarize(self) -> str:
        return f"matches /{self.pattern}/"

    def __repr__(self) -> str:
        return f"Regex({self.pattern!r})"

Present dataclass

Condition that checks if a value extracted from an event is present (not MISSING_VALUE).

Source code in src/hassette/event_handling/conditions.py
226
227
228
229
230
231
232
233
234
@dataclass(frozen=True)
class Present:
    """Condition that checks if a value extracted from an event is present (not MISSING_VALUE)."""

    def __call__(self, value: Any) -> bool:
        return value is not MISSING_VALUE

    def summarize(self) -> str:
        return "present"

Missing dataclass

Condition that checks if a value extracted from an event is missing (MISSING_VALUE).

Source code in src/hassette/event_handling/conditions.py
237
238
239
240
241
242
243
244
245
@dataclass(frozen=True)
class Missing:
    """Condition that checks if a value extracted from an event is missing (MISSING_VALUE)."""

    def __call__(self, value: Any) -> bool:
        return value is MISSING_VALUE

    def summarize(self) -> str:
        return "missing"

IsIn dataclass

Condition that checks if a value is in a given collection.

Examples:

ValueIs(source=get_entity_id, condition=IsIn(collection=["light.kitchen", "light.living"]))
Source code in src/hassette/event_handling/conditions.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
@dataclass(frozen=True)
class IsIn:
    """Condition that checks if a value is in a given collection.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=IsIn(collection=["light.kitchen", "light.living"]))
    ```
    """

    collection: Sequence[Any]

    def __post_init__(self) -> None:
        _validate_collection(self.collection)
        object.__setattr__(self, "collection", self.collection)

    def __call__(self, value: Any) -> bool:
        return value in self.collection

    def summarize(self) -> str:
        return f"in {_format_collection(self.collection)}"

NotIn dataclass

Condition that checks if a value is not in a given collection.

Examples:

ValueIs(source=get_entity_id, condition=NotIn(collection=["light.kitchen", "light.living"]))
Source code in src/hassette/event_handling/conditions.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
@dataclass(frozen=True)
class NotIn:
    """Condition that checks if a value is not in a given collection.

    Examples:

    ```python
    ValueIs(source=get_entity_id, condition=NotIn(collection=["light.kitchen", "light.living"]))
    ```
    """

    collection: Sequence[Any]

    def __post_init__(self) -> None:
        _validate_collection(self.collection)
        object.__setattr__(self, "collection", self.collection)

    def __call__(self, value: Any) -> bool:
        return value not in self.collection

    def summarize(self) -> str:
        return f"not in {_format_collection(self.collection)}"

Intersects dataclass

Condition that checks if a collection value intersects with a given collection.

Examples:

ValueIs(source=get_tags, condition=Intersects(collection=["kitchen", "living"]))
Source code in src/hassette/event_handling/conditions.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
@dataclass(frozen=True)
class Intersects:
    """Condition that checks if a collection value intersects with a given collection.

    Examples:

    ```python
    ValueIs(source=get_tags, condition=Intersects(collection=["kitchen", "living"]))
    ```
    """

    collection: Sequence[Any]

    def __post_init__(self) -> None:
        _validate_collection(self.collection)
        object.__setattr__(self, "collection", self.collection)

    def __call__(self, value: Any) -> bool:
        if not isinstance(value, Sequence):
            return False
        # not using actual set operations to allow unhashable items
        return any(item in self.collection for item in value)

    def summarize(self) -> str:
        return f"intersects {_format_collection(self.collection)}"

NotIntersects dataclass

Condition that checks if a collection value does not intersect with a given collection.

Examples:

ValueIs(source=get_tags, condition=NotIntersects(collection=["kitchen", "living"]))
Source code in src/hassette/event_handling/conditions.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
@dataclass(frozen=True)
class NotIntersects:
    """Condition that checks if a collection value does not intersect with a given collection.

    Examples:

    ```python
    ValueIs(source=get_tags, condition=NotIntersects(collection=["kitchen", "living"]))
    ```
    """

    collection: Sequence[Any]

    def __post_init__(self) -> None:
        _validate_collection(self.collection)
        object.__setattr__(self, "collection", self.collection)

    def __call__(self, value: Any) -> bool:
        if not isinstance(value, Sequence):
            return True
        # not using actual set operations to allow unhashable items
        return all(item not in self.collection for item in value)

    def summarize(self) -> str:
        return f"does not intersect {_format_collection(self.collection)}"

IsOrContains dataclass

Condition that checks if a value is equal to or contained in a given collection.

Examples:

# check if the entity_id is either "light.kitchen" or a list containing it
ValueIs(source=get_entity_id, condition=IsOrContains("light.kitchen"))
Source code in src/hassette/event_handling/conditions.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
@dataclass(frozen=True)
class IsOrContains:
    """Condition that checks if a value is equal to or contained in a given collection.

    Examples:

    ```python
    # check if the entity_id is either "light.kitchen" or a list containing it
    ValueIs(source=get_entity_id, condition=IsOrContains("light.kitchen"))
    ```
    """

    condition: str

    def __call__(self, value: Sequence[Any] | Any) -> bool:
        if isinstance(value, Sequence) and not isinstance(value, str):
            return any(item == self.condition for item in value)
        return value == self.condition

    def summarize(self) -> str:
        return f"is or contains {self.condition}"

IsNone dataclass

Condition that checks if a value is None.

Examples:

ValueIs(source=get_attribute, condition=IsNone())
Source code in src/hassette/event_handling/conditions.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
@dataclass(frozen=True)
class IsNone:
    """Condition that checks if a value is None.

    Examples:

    ```python
    ValueIs(source=get_attribute, condition=IsNone())
    ```
    """

    def __call__(self, value: Any) -> bool:
        return value is None

    def summarize(self) -> str:
        return "is none"

IsNotNone dataclass

Condition that checks if a value is not None.

Examples:

ValueIs(source=get_attribute, condition=IsNotNone())
Source code in src/hassette/event_handling/conditions.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
@dataclass(frozen=True)
class IsNotNone:
    """Condition that checks if a value is not None.

    Examples:

    ```python
    ValueIs(source=get_attribute, condition=IsNotNone())
    ```
    """

    def __call__(self, value: Any) -> bool:
        return value is not None

    def summarize(self) -> str:
        return "is not none"

Comparison dataclass

Condition that compares an extracted value to a specified value using a specified comparison operator.

Source code in src/hassette/event_handling/conditions.py
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
@dataclass(frozen=True, init=False)
class Comparison:
    """Condition that compares an extracted value to a specified value using a specified comparison operator."""

    compare_to: Any
    """Value to compare against"""

    op: OPS
    """Comparison operator to use"""

    op_fn: Callable[[Any, Any], bool]
    """Operator function"""

    def __init__(self, op: OPS, value: Any):
        object.__setattr__(self, "compare_to", value)
        object.__setattr__(self, "op", op)
        match op:
            case ">" | "gt":
                op_fn = operator.gt
            case "<" | "lt":
                op_fn = operator.lt
            case ">=" | "ge":
                op_fn = operator.ge
            case "<=" | "le":
                op_fn = operator.le
            case "==" | "eq":
                op_fn = operator.eq
            case "!=" | "ne":
                op_fn = operator.ne
            case _:
                raise ValueError(f"Invalid comparison operator {op}. Allowed operators are {OP_LIST}")
        object.__setattr__(self, "op_fn", op_fn)

    def __call__(self, value: Any) -> bool:
        try:
            if isinstance(self.compare_to, (int, float)) and isinstance(value, str):
                return self.op_fn(float(value), float(self.compare_to))
            return self.op_fn(value, self.compare_to)
        except (TypeError, ValueError):
            return False

    def summarize(self) -> str:
        return f"{_OP_DISPLAY.get(self.op, self.op)} {self.compare_to}"

compare_to: Any instance-attribute

Value to compare against

op: OPS instance-attribute

Comparison operator to use

op_fn: Callable[[Any, Any], bool] instance-attribute

Operator function

Increased dataclass

Comparison condition that checks if a numeric value has increased compared to the previous value.

Expected to be used with predicates that provide both old and new values, such as StateComparison and AttrComparison. Returns False on type conversion errors.

Examples:

self.on_state_change("zone.home", changed=Increased())
Source code in src/hassette/event_handling/conditions.py
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
@dataclass(frozen=True)
class Increased:
    """Comparison condition that checks if a numeric value has increased compared to the previous value.

    Expected to be used with predicates that provide both old and new values, such as StateComparison and
    AttrComparison. Returns False on type conversion errors.

    Examples:

    ```python
    self.on_state_change("zone.home", changed=Increased())
    ```
    """

    def __call__(self, old_value: Any, new_value: Any) -> bool:
        try:
            return float(new_value) > float(old_value)
        except (TypeError, ValueError):
            return False

    def summarize(self) -> str:
        return "increased"

Decreased dataclass

Comparison condition that checks if a numeric value has decreased compared to the previous value.

Expected to be used with predicates that provide both old and new values, such as StateComparison and AttrComparison. Returns False on type conversion errors.

Examples:

self.on_state_change("zone.home", changed=Decreased())
Source code in src/hassette/event_handling/conditions.py
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
@dataclass(frozen=True)
class Decreased:
    """Comparison condition that checks if a numeric value has decreased compared to the previous value.

    Expected to be used with predicates that provide both old and new values, such as StateComparison and
    AttrComparison. Returns False on type conversion errors.

    Examples:

    ```python
    self.on_state_change("zone.home", changed=Decreased())
    ```
    """

    def __call__(self, old_value: Any, new_value: Any) -> bool:
        try:
            return float(new_value) < float(old_value)
        except (TypeError, ValueError):
            return False

    def summarize(self) -> str:
        return "decreased"