Skip to content

Commit 5d9fba4

Browse files
authored
Merge pull request #813 from dmamelin/rework-error-handling
Rework error handling
2 parents b822831 + 251cec6 commit 5d9fba4

10 files changed

Lines changed: 643 additions & 447 deletions

File tree

custom_components/pyscript/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,9 @@ def import_recurse(ctx_name, visited, ctx2imports):
728728
mtime=src_info.mtime,
729729
)
730730
reload = src_info.global_ctx_name in ctx_delete
731-
await GlobalContextMgr.load_file(
732-
global_ctx, src_info.file_path, source=src_info.source, reload=reload
733-
)
731+
try:
732+
await GlobalContextMgr.load_file(
733+
global_ctx, src_info.file_path, source=src_info.source, reload=reload
734+
)
735+
except Exception:
736+
_LOGGER.error("Failed to load %s", src_info.file_path)

custom_components/pyscript/eval.py

Lines changed: 224 additions & 226 deletions
Large diffs are not rendered by default.

custom_components/pyscript/function.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,10 @@ async def run_coro(cls, coro, ast_ctx=None):
449449
if task in cls.task2cb:
450450
for callback, info in cls.task2cb[task]["cb"].items():
451451
ast_ctx, args, kwargs = info
452-
await ast_ctx.call_func(callback, None, *args, **kwargs)
453-
if ast_ctx.get_exception_obj():
454-
ast_ctx.get_logger().error(ast_ctx.get_exception_long())
452+
try:
453+
await ast_ctx.call_func(callback, None, *args, **kwargs)
454+
except Exception as e:
455+
ast_ctx.log_exception(e)
455456
break
456457
if task in cls.unique_task2name:
457458
for name in cls.unique_task2name[task]:

custom_components/pyscript/global_ctx.py

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ def get_trig_info(self, name: str, trig_args: dict[str, Any]) -> TrigInfo:
120120
"""Return a new trigger info instance with the given args."""
121121
return TrigInfo(name, trig_args, self)
122122

123-
async def module_import(
124-
self, module_name: str, import_level: int
125-
) -> tuple[ModuleType | None, AstEval | None]:
123+
async def module_import(self, module_name: str, import_level: int) -> ModuleType | None:
126124
"""Import a pyscript module from the pyscript/modules or apps folder."""
127125

128126
pyscript_dir = Function.hass.config.path(FOLDER)
@@ -176,14 +174,14 @@ def find_first_file(file_paths: list[list[str]]) -> list[str] | None:
176174
mod_ctx = self.manager.get(ctx_name)
177175
if mod_ctx and mod_ctx.module:
178176
self.imports.add(mod_ctx.get_name())
179-
return mod_ctx.module, None
177+
return mod_ctx.module
180178

181179
#
182180
# not loaded already, so try to find and import it
183181
#
184182
file_info = await Function.hass.async_add_executor_job(find_first_file, file_paths)
185183
if not file_info:
186-
return None, None
184+
return None
187185

188186
[ctx_name, file_path, rel_import_path] = file_info
189187

@@ -192,18 +190,20 @@ def find_first_file(file_paths: list[list[str]]) -> list[str] | None:
192190
ctx_name, global_sym_table=mod.__dict__, manager=self.manager, rel_import_path=rel_import_path
193191
)
194192
global_ctx.set_auto_start(self.auto_start)
195-
_, error_ctx = await self.manager.load_file(global_ctx, file_path)
196-
if error_ctx:
193+
try:
194+
await self.manager.load_file(global_ctx, file_path)
195+
except Exception:
197196
_LOGGER.error(
198197
"module_import: failed to load module %s, ctx = %s, path = %s",
199198
module_name,
200199
ctx_name,
201200
file_path,
202201
)
203-
return None, error_ctx
202+
global_ctx.stop()
203+
raise
204204
global_ctx.module = mod
205205
self.imports.add(ctx_name)
206-
return mod, None
206+
return mod
207207

208208

209209
class GlobalContextMgr:
@@ -301,8 +301,8 @@ def new_name(cls, root: str) -> str:
301301
@classmethod
302302
async def load_file(
303303
cls, global_ctx: GlobalContext, file_path: str, source: str | None = None, reload: bool = False
304-
) -> tuple[bool, AstEval | None]:
305-
"""Load, parse and run the given script file; returns error ast_ctx on error, or None if ok."""
304+
) -> None:
305+
"""Load, parse and run the given script file."""
306306

307307
mtime = None
308308
if source is None:
@@ -319,7 +319,7 @@ def read_file(path: str) -> tuple[str | None, float]:
319319
source, mtime = await Function.hass.async_add_executor_job(read_file, file_path)
320320

321321
if source is None:
322-
return False, None
322+
return
323323

324324
ctx_curr = cls.get(global_ctx.get_name())
325325
if ctx_curr:
@@ -332,24 +332,17 @@ def read_file(path: str) -> tuple[str | None, float]:
332332
#
333333
ast_ctx = AstEval(global_ctx.get_name(), global_ctx)
334334
Function.install_ast_funcs(ast_ctx)
335-
336-
if not ast_ctx.parse(source, filename=file_path):
337-
exc = ast_ctx.get_exception_long()
338-
ast_ctx.get_logger().error(exc)
339-
global_ctx.stop()
340-
return False, ast_ctx
341-
await ast_ctx.eval()
342-
exc = ast_ctx.get_exception_long()
343-
if exc is not None:
344-
ast_ctx.get_logger().error(exc)
345-
global_ctx.stop()
346-
return False, ast_ctx
347335
global_ctx.source = source
348336
global_ctx.file_path = file_path
349337
if mtime is not None:
350338
global_ctx.mtime = mtime
339+
try:
340+
ast_ctx.parse(source, filename=file_path)
341+
await ast_ctx.eval()
342+
except Exception as e:
343+
global_ctx.stop()
344+
ast_ctx.log_exception(e)
345+
raise
351346
cls.set(global_ctx.get_name(), global_ctx)
352347

353348
_LOGGER.info("%s %s", "Reloaded" if reload else "Loaded", file_path)
354-
355-
return True, None

custom_components/pyscript/jupyter_kernel.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import uuid
2121

2222
from .const import LOGGER_PATH
23+
from .eval import EvalExceptionFormatter
2324
from .function import Function
2425
from .global_ctx import GlobalContextMgr
2526
from .state import State
@@ -355,16 +356,14 @@ async def shell_handler(self, shell_socket, wire_msg):
355356
code = "print([])"
356357

357358
self.global_ctx.set_auto_start(False)
358-
self.ast_ctx.parse(code)
359-
exc = self.ast_ctx.get_exception_obj()
360-
if exc is None:
359+
try:
360+
self.ast_ctx.parse(code)
361361
result = await self.ast_ctx.eval()
362-
exc = self.ast_ctx.get_exception_obj()
363-
await Function.waiter_sync()
364-
self.global_ctx.set_auto_start(True)
365-
self.global_ctx.start()
366-
if exc:
367-
traceback_mesg = self.ast_ctx.get_exception_long().split("\n")
362+
await Function.waiter_sync()
363+
self.global_ctx.set_auto_start(True)
364+
self.global_ctx.start()
365+
except Exception as exc:
366+
traceback_mesg = EvalExceptionFormatter(exc).format()
368367

369368
metadata = {
370369
"dependencies_met": True,
@@ -503,17 +502,15 @@ async def shell_handler(self, shell_socket, wire_msg):
503502

504503
elif msg["header"]["msg_type"] == "is_complete_request":
505504
code = msg["content"]["code"]
506-
self.ast_ctx.parse(code)
507-
exc = self.ast_ctx.get_exception_obj()
508-
509505
# determine indent of last line
510506
indent = 0
511507
i = code.rfind("\n")
512508
if i >= 0:
513509
while i + 1 < len(code) and code[i + 1] == " ":
514510
i += 1
515511
indent += 1
516-
if exc is None:
512+
try:
513+
self.ast_ctx.parse(code)
517514
if indent == 0:
518515
content = {
519516
# One of 'complete', 'incomplete', 'invalid', 'unknown'
@@ -529,7 +526,7 @@ async def shell_handler(self, shell_socket, wire_msg):
529526
"status": "incomplete",
530527
"indent": " " * indent,
531528
}
532-
else:
529+
except Exception as exc:
533530
#
534531
# if the syntax error is right at the end, then we label it incomplete,
535532
# otherwise it's invalid

0 commit comments

Comments
 (0)