Skip to content

Commit 2e5edfd

Browse files
authored
Merge pull request #142 from cryptk/feat_optional_is_ready
Feat optional is ready
2 parents 7bde7e9 + 50400cb commit 2e5edfd

3 files changed

Lines changed: 43 additions & 23 deletions

File tree

pyomnilogic_local/decorators.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,74 @@
55
import functools
66
import logging
77
from collections.abc import Callable
8-
from typing import Any, cast
8+
from typing import Any, cast, overload
99

1010
from pyomnilogic_local.util import OmniEquipmentNotReadyError
1111

1212
_LOGGER = logging.getLogger(__name__)
1313

1414

15-
def control_method[F: Callable[..., Any]](func: F) -> F:
15+
@overload
16+
def control_method[FUNC: Callable[..., Any]](func: FUNC, *, check_ready: bool = ...) -> FUNC: ...
17+
@overload
18+
def control_method[FUNC: Callable[..., Any]](func: None = ..., *, check_ready: bool = ...) -> Callable[[FUNC], FUNC]: ...
19+
def control_method[FUNC: Callable[..., Any]](func: FUNC | None = None, *, check_ready: bool = True) -> FUNC | Callable[[FUNC], FUNC]:
1620
"""Check readiness and mark state as dirty.
1721
1822
This decorator ensures equipment is ready before executing control methods and
1923
automatically marks telemetry as dirty after execution. It replaces the common
2024
pattern of checking is_ready and using @dirties_state() separately.
2125
2226
The decorator:
23-
1. Checks if equipment is ready (via is_ready property)
27+
1. Optionally checks if equipment is ready (via is_ready property)
2428
2. Raises OmniEquipmentNotReadyError with descriptive message if not ready
2529
3. Executes the control method
2630
4. Marks telemetry as dirty
2731
32+
Args:
33+
func: The function being decorated. Supplied automatically when used without parentheses.
34+
check_ready: If False, skip the is_ready check and execute unconditionally.
35+
Defaults to True.
36+
2837
Raises:
2938
OmniEquipmentNotReadyError: If equipment is not ready to accept commands
3039
3140
Example:
3241
@control_method
3342
async def turn_on(self) -> None:
3443
await self._api.async_set_equipment(...)
44+
45+
@control_method(check_ready=False)
46+
async def turn_off(self) -> None:
47+
await self._api.async_set_equipment(...)
3548
"""
36-
# Import here to avoid circular dependency
3749

38-
@functools.wraps(func)
39-
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
40-
# Check if equipment is ready
41-
if not self.is_ready:
42-
# Generate descriptive error message from function name
43-
action = func.__name__.replace("_", " ")
44-
msg = f"Cannot {action}: equipment is not ready to accept commands"
45-
raise OmniEquipmentNotReadyError(msg)
50+
def decorator(f: FUNC) -> FUNC:
51+
@functools.wraps(f)
52+
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
53+
# Check if equipment is ready
54+
if check_ready and not self.is_ready:
55+
# Generate descriptive error message from function name
56+
action = f.__name__.replace("_", " ")
57+
msg = f"Cannot {action}: equipment is not ready to accept commands"
58+
raise OmniEquipmentNotReadyError(msg)
59+
60+
# Execute the original function
61+
result = await f(self, *args, **kwargs)
62+
63+
# Mark telemetry as dirty
64+
if hasattr(self, "_omni"):
65+
self._omni._telemetry_dirty = True
66+
else:
67+
_LOGGER.warning("%s does not have _omni reference, cannot mark state as dirty", self.__class__.__name__)
4668

47-
# Execute the original function
48-
result = await func(self, *args, **kwargs)
69+
return result
4970

50-
# Mark telemetry as dirty
51-
if hasattr(self, "_omni"):
52-
self._omni._telemetry_dirty = True
53-
else:
54-
_LOGGER.warning("%s does not have _omni reference, cannot mark state as dirty", self.__class__.__name__)
71+
return cast("FUNC", wrapper)
5572

56-
return result
73+
if func is not None:
74+
# Used as @control_method without parentheses
75+
return decorator(func)
5776

58-
return cast("F", wrapper)
77+
# Used as @control_method(...) with parentheses
78+
return decorator

pyomnilogic_local/filter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ async def turn_on(self) -> None:
236236
is_on=target_speed,
237237
)
238238

239-
@control_method
239+
@control_method(check_ready=False)
240240
async def turn_off(self) -> None:
241241
"""Turn the filter off.
242242

pyomnilogic_local/pump.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ async def turn_on(self) -> None:
216216
is_on=target_speed,
217217
)
218218

219-
@control_method
219+
@control_method(check_ready=False)
220220
async def turn_off(self) -> None:
221221
"""Turn the pump off.
222222

0 commit comments

Comments
 (0)