|
5 | 5 | import functools |
6 | 6 | import logging |
7 | 7 | from collections.abc import Callable |
8 | | -from typing import Any, cast |
| 8 | +from typing import Any, cast, overload |
9 | 9 |
|
10 | 10 | from pyomnilogic_local.util import OmniEquipmentNotReadyError |
11 | 11 |
|
12 | 12 | _LOGGER = logging.getLogger(__name__) |
13 | 13 |
|
14 | 14 |
|
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]: |
16 | 20 | """Check readiness and mark state as dirty. |
17 | 21 |
|
18 | 22 | This decorator ensures equipment is ready before executing control methods and |
19 | 23 | automatically marks telemetry as dirty after execution. It replaces the common |
20 | 24 | pattern of checking is_ready and using @dirties_state() separately. |
21 | 25 |
|
22 | 26 | 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) |
24 | 28 | 2. Raises OmniEquipmentNotReadyError with descriptive message if not ready |
25 | 29 | 3. Executes the control method |
26 | 30 | 4. Marks telemetry as dirty |
27 | 31 |
|
| 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 | +
|
28 | 37 | Raises: |
29 | 38 | OmniEquipmentNotReadyError: If equipment is not ready to accept commands |
30 | 39 |
|
31 | 40 | Example: |
32 | 41 | @control_method |
33 | 42 | async def turn_on(self) -> None: |
34 | 43 | 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(...) |
35 | 48 | """ |
36 | | - # Import here to avoid circular dependency |
37 | 49 |
|
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__) |
46 | 68 |
|
47 | | - # Execute the original function |
48 | | - result = await func(self, *args, **kwargs) |
| 69 | + return result |
49 | 70 |
|
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) |
55 | 72 |
|
56 | | - return result |
| 73 | + if func is not None: |
| 74 | + # Used as @control_method without parentheses |
| 75 | + return decorator(func) |
57 | 76 |
|
58 | | - return cast("F", wrapper) |
| 77 | + # Used as @control_method(...) with parentheses |
| 78 | + return decorator |
0 commit comments