Преглед на файлове

support lambda with dependencies (#1499)

* support lambda with dependencies
resolves #1498

* spell check

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide преди 10 месеца
родител
ревизия
b92f16cddc
променени са 2 файла, в които са добавени 19 реда и са изтрити 19 реда
  1. 17 19
      taipy/gui/builder/_element.py
  2. 2 0
      taipy/gui/builder/_utils.py

+ 17 - 19
taipy/gui/builder/_element.py

@@ -18,11 +18,13 @@ import io
 import re
 import sys
 import typing as t
+import uuid
 from abc import ABC, abstractmethod
 from collections.abc import Iterable
 from types import FrameType, FunctionType
 
 from .._warnings import _warn
+from ..utils import _getscopeattr
 
 if sys.version_info < (3, 9):
     from ..utils.unparse import _Unparser
@@ -50,8 +52,8 @@ class _Element(ABC):
         return obj
 
     def __init__(self, *args, **kwargs) -> None:
-        self.__variables: t.Dict[str, FunctionType] = {}
         self._properties: t.Dict[str, t.Any] = {}
+        self._lambdas: t.Dict[str, str] = {}
         self.__calling_frame = t.cast(
             FrameType, t.cast(FrameType, t.cast(FrameType, inspect.currentframe()).f_back).f_back
         )
@@ -65,6 +67,11 @@ class _Element(ABC):
         self._properties.update(kwargs)
         self.parse_properties()
 
+    def _evaluate_lambdas(self, gui: Gui):
+        for k, lmbd in self._lambdas.items():
+            expr = gui._evaluate_expr(f"{{{lmbd}}}")
+            gui._bind_var_val(k, _getscopeattr(gui, expr))
+
     # Convert property value to string/function
     def parse_properties(self):
         self._properties = {
@@ -96,9 +103,7 @@ class _Element(ABC):
                 _LambdaByName(self._ELEMENT_NAME, lambda_by_name).visit(st)
                 lambda_fn = lambda_by_name.get(
                     key,
-                    lambda_by_name.get(_LambdaByName._DEFAULT_NAME, None)
-                    if key == self._DEFAULT_PROPERTY
-                    else None,
+                    lambda_by_name.get(_LambdaByName._DEFAULT_NAME, None) if key == self._DEFAULT_PROPERTY else None,
                 )
                 if lambda_fn is not None:
                     args = [arg.arg for arg in lambda_fn.args.args]
@@ -119,22 +124,15 @@ class _Element(ABC):
                         lambda_text = string_fd.read()
                     else:
                         lambda_text = ast.unparse(tree)
-                    new_code = compile(f"{_Element._NEW_LAMBDA_NAME} = {lambda_text}", "<ast>", "exec")
-                    namespace: t.Dict[str, FunctionType] = {}
-                    exec(new_code, namespace)
-                    var_name = f"__lambda_{id(namespace[_Element._NEW_LAMBDA_NAME])}"
-                    self.__variables[var_name] = namespace[_Element._NEW_LAMBDA_NAME]
-                    return f'{{{var_name}({", ".join(args)})}}'
+                    lambda_name = f"__lambda_{uuid.uuid4().hex}"
+                    self._lambdas[lambda_name] = lambda_text
+                    return f'{{{lambda_name}({", ".join(args)})}}'
             except Exception as e:
                 _warn("Error in lambda expression", e)
         if hasattr(value, "__name__"):
             return str(getattr(value, "__name__"))  # noqa: B009
         return str(value)
 
-    def _bind_variables(self, gui: "Gui"):
-        for var_name, var_value in self.__variables.items():
-            gui._bind_var_val(var_name, var_value)
-
     @abstractmethod
     def _render(self, gui: "Gui") -> str:
         pass
@@ -161,7 +159,7 @@ class _Block(_Element):
         _BuilderContextManager().pop()
 
     def _render(self, gui: "Gui") -> str:
-        self._bind_variables(gui)
+        self._evaluate_lambdas(gui)
         el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
         return f"{el[0]}{self._render_children(gui)}</{el[1]}>"
 
@@ -172,7 +170,7 @@ class _Block(_Element):
 class _DefaultBlock(_Block):
     _ELEMENT_NAME = "part"
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs):  # do not remove as it could break the search in frames
         super().__init__(*args, **kwargs)
 
 
@@ -225,7 +223,7 @@ class html(_Block):
         self._content = args[1] if len(args) > 1 else ""
 
     def _render(self, gui: "Gui") -> str:
-        self._bind_variables(gui)
+        self._evaluate_lambdas(gui)
         if self._ELEMENT_NAME:
             attrs = ""
             if self._properties:
@@ -238,11 +236,11 @@ class html(_Block):
 class _Control(_Element):
     """NOT DOCUMENTED"""
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs):  # do not remove as it could break the search in frames
         super().__init__(*args, **kwargs)
 
     def _render(self, gui: "Gui") -> str:
-        self._bind_variables(gui)
+        self._evaluate_lambdas(gui)
         el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
         return (
             f"<div>{el[0]}</{el[1]}></div>"

+ 2 - 0
taipy/gui/builder/_utils.py

@@ -39,6 +39,8 @@ class _TransformVarToValue(ast.NodeTransformer):
         if var_parts[0] in self.non_vars:
             return node
         value = _get_value_in_frame(self.frame, var_parts[0])
+        if callable(value):
+            return node
         if len(var_parts) > 1:
             value = attrgetter(var_parts[1])(value)
         return ast.Constant(value=value, kind=None)