From 5e58fd16cfaf44d3ac5c85ef444b4bc39d7f68db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fred=20Lef=C3=A9v=C3=A8re-Laoide?= <90181748+FredLL-Avaiga@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:35:31 +0200 Subject: [PATCH] improve exception reporting in expressions (#2156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improve exception reporting in expressions resolves #2144 * linter * fix test * better fix test --------- Co-authored-by: Fred Lefévère-Laoide --- taipy/gui/_page.py | 12 +++++++++++- taipy/gui/_warnings.py | 17 +++++++++++++---- taipy/gui/utils/_evaluator.py | 16 +++++++++++++--- tests/gui/helpers.py | 2 +- tools/release/bump_version.py | 2 +- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/taipy/gui/_page.py b/taipy/gui/_page.py index d50f93f9a1..6505926c13 100644 --- a/taipy/gui/_page.py +++ b/taipy/gui/_page.py @@ -17,6 +17,8 @@ import typing as t import warnings +from ._warnings import TaipyGuiAlwaysWarning + if t.TYPE_CHECKING: from ._renderers import Page from .gui import Gui @@ -40,7 +42,15 @@ def render(self, gui: Gui, silent: t.Optional[bool] = False): warnings.resetwarnings() with gui._set_locals_context(self._renderer._get_module_name()): self._rendered_jsx = self._renderer.render(gui) - if not silent: + if silent: + s = "" + for wm in w: + if wm.category is TaipyGuiAlwaysWarning: + s += f" - {wm.message}\n" + if s: + logging.warning("\033[1;31m\n" + s) + + else: if ( self._rendered_jsx and isinstance(self._rendered_jsx, str) diff --git a/taipy/gui/_warnings.py b/taipy/gui/_warnings.py index 9faaf8740a..b68cd37631 100644 --- a/taipy/gui/_warnings.py +++ b/taipy/gui/_warnings.py @@ -30,15 +30,24 @@ def set_debug_mode(debug_mode: bool): ) -def _warn(message: str, e: t.Optional[BaseException] = None): +class TaipyGuiAlwaysWarning(TaipyGuiWarning): + pass + + +def _warn( + message: str, + e: t.Optional[BaseException] = None, + always_show: t.Optional[bool] = False, +): warnings.warn( ( - f"{message}:\n{''.join(traceback.format_exception(type(e), e, e.__traceback__))}" + f"{message}:\n{''.join(traceback.format_exception(e))}" if e and TaipyGuiWarning._tp_debug_mode - else f"{message}:\n{e}" + else f"{message}:\n" + + "".join(traceback.format_exception(None, e, e.__traceback__.tb_next if e.__traceback__ else None)) if e else message ), - TaipyGuiWarning, + TaipyGuiWarning if not always_show else TaipyGuiAlwaysWarning, stacklevel=2, ) diff --git a/taipy/gui/utils/_evaluator.py b/taipy/gui/utils/_evaluator.py index 85d9f590ba..b124324858 100644 --- a/taipy/gui/utils/_evaluator.py +++ b/taipy/gui/utils/_evaluator.py @@ -47,6 +47,7 @@ class _Evaluator: __EXPR_EDGE_CASE_F_STRING = re.compile(r"[\{]*[a-zA-Z_][a-zA-Z0-9_]*:.+") __IS_TAIPY_EXPR_RE = re.compile(r"TpExPr_(.*)") __IS_ARRAY_EXPR_RE = re.compile(r"[^[]*\[(\d+)][^]]*") + __CLEAN_LAMBDA_RE = re.compile(r"^__lambda_[\d_]+(TPMDL_\d+)?(.*)$") def __init__(self, default_bindings: t.Dict[str, t.Any], shared_variable: t.List[str]) -> None: # key = expression, value = hashed value of the expression @@ -260,7 +261,12 @@ def evaluate_expr( with gui._get_authorization(): expr_evaluated = eval(not_encoded_expr if is_edge_case else expr_string, ctx) except Exception as e: - _warn(f"Cannot evaluate expression '{not_encoded_expr if is_edge_case else expr_string}'", e) + exception_str = not_encoded_expr if is_edge_case else expr_string + _warn( + f"Cannot evaluate expression '{_Evaluator._clean_exception_expr(exception_str)}'", + e, + always_show=True, + ) expr_evaluated = None if lambda_expr and callable(expr_evaluated): expr_hash = _get_lambda_id(expr_evaluated, module=module_name) # type: ignore[reportArgumentType] @@ -291,7 +297,7 @@ def refresh_expr(self, gui: Gui, var_name: str, holder: t.Optional[_TaipyBase]): if holder is not None: holder.set(expr_evaluated) except Exception as e: - _warn(f"Exception raised evaluating {expr_string}", e) + _warn(f"Exception raised evaluating {_Evaluator._clean_exception_expr(expr_string)}", e) def re_evaluate_expr(self, gui: Gui, var_name: str) -> t.Set[str]: # noqa C901 """ @@ -366,7 +372,7 @@ def re_evaluate_expr(self, gui: Gui, var_name: str) -> t.Set[str]: # noqa C901 expr_evaluated = eval(expr_string, ctx) _setscopeattr(gui, hash_expr, expr_evaluated) except Exception as e: - _warn(f"Exception raised evaluating {expr_string}", e) + _warn(f"Exception raised evaluating {_Evaluator._clean_exception_expr(expr_string)}", e) # refresh holders if any for h in self.__expr_to_holders.get(expr, []): holder_hash = self.__get_holder_hash(h, self.get_hash_from_expr(expr)) @@ -378,3 +384,7 @@ def re_evaluate_expr(self, gui: Gui, var_name: str) -> t.Set[str]: # noqa C901 def _get_instance_in_context(self, name: str): return self.__global_ctx.get(name) + + @staticmethod + def _clean_exception_expr(expr: str): + return _Evaluator.__CLEAN_LAMBDA_RE.sub(r"\2", expr) diff --git a/tests/gui/helpers.py b/tests/gui/helpers.py index 634fb6cfd1..56eaa94d8b 100644 --- a/tests/gui/helpers.py +++ b/tests/gui/helpers.py @@ -165,4 +165,4 @@ def run_e2e_multi_client(gui: Gui): @staticmethod def get_taipy_warnings(warns: t.List[warnings.WarningMessage]) -> t.List[warnings.WarningMessage]: - return [w for w in warns if w.category is TaipyGuiWarning] + return [w for w in warns if issubclass(w.category, TaipyGuiWarning)] diff --git a/tools/release/bump_version.py b/tools/release/bump_version.py index 575e6cfbf6..a8af3eab43 100644 --- a/tools/release/bump_version.py +++ b/tools/release/bump_version.py @@ -93,5 +93,5 @@ def bump_ext_version(version: Version, _base_path: str) -> None: for _path in paths: _version = extract_version(_path) bump_ext_version(_version, _path) - print(f"NEW_VERSION={_version.dev_name}") + print(f"NEW_VERSION={_version.dev_name}") # noqa T201 # type: ignore[reportPossiblyUnboundVariable]