Skip to content

Commit 20f6a49

Browse files
committed
support new error handling
1 parent 9bb2a41 commit 20f6a49

7 files changed

Lines changed: 29 additions & 55 deletions

File tree

custom_components/pyscript/decorator.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def get_decorator_by_expr(cls, ast_ctx: AstEval, dec_expr: ast.expr) -> De
9797
args = []
9898
kwargs = {}
9999

100-
decorator = know_decorator(args, kwargs, dec_expr.lineno, dec_expr.col_offset)
100+
decorator = know_decorator(args, kwargs)
101101
return decorator
102102

103103
return None
@@ -136,7 +136,7 @@ async def wait_until(cls, ast_ctx: AstEval, *_arg: Any, **kwargs: Any) -> Any:
136136
if key in kwargs:
137137
dec_kwargs[key] = kwargs[key]
138138
found_args.add(key)
139-
dec = dec_class(dec_args, dec_kwargs, ast_ctx.lineno, ast_ctx.col_offset)
139+
dec = dec_class(dec_args, dec_kwargs)
140140
dm.add(dec)
141141

142142
unknown_args = set(kwargs.keys()).difference(found_args)
@@ -181,9 +181,7 @@ def __init__(self, ast_ctx: AstEval, **kwargs: dict[str, Any]) -> None:
181181
self.timeout_decorator = None
182182
if timeout := kwargs.get("timeout"):
183183
to_dec = DecoratorRegistry._decorators.get(DecoratorRegistry.prefix + "time_trigger")
184-
self.timeout_decorator = to_dec(
185-
[f"once(now + {timeout}s)"], {}, ast_ctx.lineno, ast_ctx.col_offset
186-
)
184+
self.timeout_decorator = to_dec([f"once(now + {timeout}s)"], {})
187185
self.add(self.timeout_decorator)
188186

189187
async def dispatch(self, data: DispatchData) -> None:
@@ -196,11 +194,17 @@ async def dispatch(self, data: DispatchData) -> None:
196194
await self.stop()
197195
self._future.set_result(data)
198196

197+
async def handle_exception(self, exc: Exception) -> None:
198+
"""Propagate an evaluation exception to the waiting caller."""
199+
if self._future.done():
200+
_LOGGER.debug("task.wait_until future already completed: %s", self._future.exception())
201+
return
202+
await self.stop()
203+
self._future.set_exception(exc)
204+
199205
async def wait_until(self) -> dict[str, Any]:
200206
"""Wait for dispatch and normalize the return payload."""
201207
data = await self._future
202-
if data.exception is not None:
203-
raise data.exception
204208
if data.trigger == self.timeout_decorator:
205209
ret = {"trigger_type": "timeout"}
206210
else:
@@ -253,17 +257,10 @@ async def _call(self, data: DispatchData) -> None:
253257
for result_handler_dec in result_handlers:
254258
await result_handler_dec.handle_call_result(data, result)
255259

256-
if data.call_ast_ctx.get_exception_obj():
257-
data.call_ast_ctx.get_logger().error(data.call_ast_ctx.get_exception_long())
258-
259260
async def dispatch(self, data: DispatchData) -> None:
260261
"""Handle a trigger dispatch: run guards, create a context, and invoke the function."""
261262
_LOGGER.debug("Dispatching for %s: %s", self.name, data)
262263

263-
if data.exception:
264-
self.logger.error(data.exception_text)
265-
return
266-
267264
decorators = self.get_decorators(TriggerHandlerDecorator)
268265
for dec in decorators:
269266
if await dec.handle_dispatch(data) is False:

custom_components/pyscript/decorator_abc.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ class DispatchData:
4646
call_ast_ctx: AstEval | None = field(default=None, kw_only=True)
4747
hass_context: Context | None = field(default=None, kw_only=True)
4848

49-
# Normally shouldn't be used.
50-
exception: Exception | None = field(default=None, kw_only=True)
51-
exception_text: str | None = field(default=None, kw_only=True)
52-
5349

5450
class Decorator(ABC):
5551
"""Generic decorator abstraction."""
@@ -70,15 +66,10 @@ class Decorator(ABC):
7066
kwargs: dict[str, Any]
7167

7268
@final
73-
def __init__(
74-
self, raw_args: list[Any], raw_kwargs: dict[str, Any], lineno: int, col_offset: int
75-
) -> None:
69+
def __init__(self, raw_args: list[Any], raw_kwargs: dict[str, Any]) -> None:
7670
"""Initialize the decorator definition."""
77-
7871
self.raw_args = raw_args
7972
self.raw_kwargs = raw_kwargs
80-
self.lineno = lineno
81-
self.col_offset = col_offset
8273

8374
async def validate(self) -> None:
8475
"""Validate the arguments."""
@@ -106,12 +97,10 @@ async def validate(self) -> None:
10697
)
10798
raise type_error from err
10899

109-
@abstractmethod
110-
async def start(self):
100+
async def start(self): # noqa: B027
111101
"""Start the decorator."""
112102

113-
@abstractmethod
114-
async def stop(self):
103+
async def stop(self): # noqa: B027
115104
"""Stop the decorator."""
116105

117106
def __repr__(self):
@@ -136,9 +125,6 @@ def __init__(self, ast_ctx: AstEval, name: str) -> None:
136125
self.func_name = name.split(".")[-1]
137126
self.logger = ast_ctx.get_logger()
138127

139-
self.lineno = ast_ctx.lineno
140-
self.col_offset = ast_ctx.col_offset
141-
142128
self.status: DecoratorManagerStatus = DecoratorManagerStatus.INIT
143129
self.startup_time = None
144130
self._decorators: list[Decorator] = []
@@ -167,19 +153,14 @@ def get_decorators[DT](self, decorator_type: type[DT] | None = None) -> list[DT]
167153

168154
async def validate(self) -> None:
169155
"""Validate all decorators."""
170-
lineno, col_offset = self.ast_ctx.lineno, self.ast_ctx.col_offset
171156
try:
172157
for decorator in self._decorators:
173-
self.ast_ctx.lineno, self.ast_ctx.col_offset = decorator.lineno, decorator.col_offset
174158
_LOGGER.debug("Validating decorator: %s", decorator)
175-
self.lineno, self.col_offset = decorator.lineno, decorator.col_offset
176159
await decorator.validate()
177160
except Exception:
178161
self.update_status(DecoratorManagerStatus.INVALID)
179162
raise
180163

181-
self.ast_ctx.lineno, self.ast_ctx.col_offset = lineno, col_offset
182-
183164
if len(self._decorators) == 0:
184165
self.update_status(DecoratorManagerStatus.NO_DECORATORS)
185166
else:
@@ -224,6 +205,10 @@ async def stop(self):
224205

225206
self.update_status(DecoratorManagerStatus.STOPPED)
226207

208+
async def handle_exception(self, exc: Exception) -> None:
209+
"""Handle a decorator exception."""
210+
self.ast_ctx.log_exception(exc)
211+
227212
@abstractmethod
228213
async def dispatch(self, data: DispatchData) -> None:
229214
"""Dispatch a trigger call."""

custom_components/pyscript/decorators/base.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import voluptuous as vol
88

99
from ..decorator import FunctionDecoratorManager
10-
from ..decorator_abc import Decorator, DispatchData
10+
from ..decorator_abc import Decorator
1111
from ..eval import AstEval, Function
1212

1313
_LOGGER = logging.getLogger(__name__)
@@ -43,9 +43,6 @@ def create_expression(self, expression: str) -> None:
4343
)
4444
Function.install_ast_funcs(self._ast_expression)
4545
self._ast_expression.parse(expression, mode="eval")
46-
exc = self._ast_expression.get_exception_obj()
47-
if exc is not None:
48-
raise exc
4946

5047
def has_expression(self) -> bool:
5148
"""Return True if expression was created."""
@@ -55,9 +52,8 @@ async def check_expression_vars(self, state_vars: dict[str, Any]) -> bool:
5552
"""Evaluate expression and dispatch an exception event via manager on failure."""
5653
if not self.has_expression():
5754
raise AttributeError(f"{self} has no expression defined")
58-
ret = await self._ast_expression.eval(state_vars)
59-
if exception := self._ast_expression.get_exception_obj():
60-
exception_text = self._ast_expression.get_exception_long()
61-
await self.dm.dispatch(DispatchData({}, exception=exception, exception_text=exception_text))
55+
try:
56+
return await self._ast_expression.eval(state_vars)
57+
except Exception as exc:
58+
await self.dm.handle_exception(exc)
6259
return False
63-
return ret

custom_components/pyscript/decorators/service.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def validate(self) -> None:
7474
try:
7575
desc = desc[4:].lstrip(" \n\r")
7676
file_desc = io.StringIO(desc)
77-
self.description = yaml.safe_load(file_desc) or OrderedDict()
77+
self.description = yaml.load(file_desc, Loader=yaml.BaseLoader) or OrderedDict() # noqa: S506
7878
file_desc.close()
7979
except Exception as exc:
8080
self.dm.logger.error(
@@ -106,13 +106,9 @@ async def _service_callback(self, call: ServiceCall) -> None:
106106
async def do_service_call(func, ast_ctx, data):
107107
try:
108108
_LOGGER.debug("Service call start: %s", func.name)
109-
retval = await func.call(ast_ctx, **data)
110-
_LOGGER.debug("Service call done: %s", ast_ctx.get_exception_long())
111-
if ast_ctx.get_exception_obj():
112-
ast_ctx.get_logger().error(ast_ctx.get_exception_long())
113-
return retval
109+
return await func.call(ast_ctx, **data)
114110
except Exception as exc:
115-
_LOGGER.exception(exc)
111+
await self.dm.handle_exception(exc)
116112
return None
117113

118114
task = Function.create_task(do_service_call(self.dm.eval_func, ast_ctx, func_args))

custom_components/pyscript/decorators/state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,6 @@ async def start(self) -> None:
316316
async def stop(self):
317317
"""Stop the trigger."""
318318
await super().stop()
319-
if hasattr(self, "cycle_task"):
319+
if self.cycle_task is not None:
320320
self.cycle_task.cancel()
321321
State.notify_del(self.state_trig_ident, self.notify_q)

custom_components/pyscript/decorators/timing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ async def _cycle(self):
144144

145145
async def stop(self):
146146
"""Stop the trigger."""
147-
if hasattr(self, "_cycle_task"):
147+
if self._cycle_task is not None:
148148
self._cycle_task.cancel()
149149
if self.run_on_shutdown:
150150
await self.dispatch(DispatchData({"trigger_type": "time", "trigger_time": "shutdown"}))

custom_components/pyscript/global_ctx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ async def create_decorator_manager(
8888
else:
8989
self.dms_delay_start.add(dm)
9090
except Exception as exc:
91-
_LOGGER.error(ast_ctx.format_exc(exc, dm.lineno, dm.col_offset))
91+
ast_ctx.log_exception(exc)
9292

9393
def trigger_unregister(self, func: EvalFunc) -> None:
9494
"""Unregister a trigger function."""

0 commit comments

Comments
 (0)