Browse Source

builder support lambda in callable typed properties (#1821)

* builder support lambda in callable typed properties

* notRequired is python 3.11+

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 8 months ago
parent
commit
4dd36d017b

+ 29 - 12
taipy/gui/builder/_api_generator.py

@@ -19,7 +19,7 @@ import typing as t
 from taipy.logger._taipy_logger import _TaipyLogger
 
 from ..utils.singleton import _Singleton
-from ..utils.viselements import VisElements, resolve_inherits
+from ..utils.viselements import VisElementProperties, VisElements, resolve_inherits
 from ._element import _Block, _Control
 
 if t.TYPE_CHECKING:
@@ -31,12 +31,16 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
         self.__module: t.Optional[types.ModuleType] = None
 
     @staticmethod
-    def find_default_property(property_list: t.List[t.Dict[str, t.Any]]) -> str:
+    def find_default_property(property_list: t.List[VisElementProperties]) -> str:
         for property in property_list:
-            if "default_property" in property and property["default_property"] is True:
+            if property.get("default_property", False) is True:
                 return property["name"]
         return ""
 
+    @staticmethod
+    def get_properties_dict(property_list: t.List[VisElementProperties]) -> t.Dict[str, t.Any]:
+        return {prop["name"]: prop.get("type", "str") for prop in property_list}
+
     def add_default(self):
         current_frame = inspect.currentframe()
         error_message = "Cannot generate elements API for the current module"
@@ -56,14 +60,24 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
                 setattr(
                     module,
                     blockElement[0],
-                    _ElementApiGenerator.create_block_api(blockElement[0], blockElement[0], default_property),
+                    _ElementApiGenerator.create_block_api(
+                        blockElement[0],
+                        blockElement[0],
+                        default_property,
+                        _ElementApiGenerator.get_properties_dict(blockElement[1]["properties"]),
+                    ),
                 )
             for controlElement in viselements["controls"]:
                 default_property = _ElementApiGenerator.find_default_property(controlElement[1]["properties"])
                 setattr(
                     module,
                     controlElement[0],
-                    _ElementApiGenerator.create_control_api(controlElement[0], controlElement[0], default_property),
+                    _ElementApiGenerator.create_control_api(
+                        controlElement[0],
+                        controlElement[0],
+                        default_property,
+                        _ElementApiGenerator.get_properties_dict(controlElement[1]["properties"]),
+                    ),
                 )
 
     def add_library(self, library: "ElementLibrary"):
@@ -82,7 +96,10 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
                 library_module,
                 element_name,
                 _ElementApiGenerator().create_control_api(
-                    element_name, f"{library_name}.{element_name}", element.default_attribute
+                    element_name,
+                    f"{library_name}.{element_name}",
+                    element.default_attribute,
+                    {name: str(prop.property_type) for name, prop in element.attributes.items()},
                 ),
             )
             # Allow element to be accessed from the root module
@@ -98,29 +115,29 @@ class _ElementApiGenerator(object, metaclass=_Singleton):
         classname: str,
         element_name: str,
         default_property: str,
+        properties: t.Dict[str, str],
     ):
-        return _ElementApiGenerator.create_element_api(classname, element_name, default_property, _Block)
+        return _ElementApiGenerator.create_element_api(classname, element_name, default_property, properties, _Block)
 
     @staticmethod
     def create_control_api(
         classname: str,
         element_name: str,
         default_property: str,
+        properties: t.Dict[str, str],
     ):
-        return _ElementApiGenerator.create_element_api(classname, element_name, default_property, _Control)
+        return _ElementApiGenerator.create_element_api(classname, element_name, default_property, properties, _Control)
 
     @staticmethod
     def create_element_api(
         classname: str,
         element_name: str,
         default_property: str,
+        properties: t.Dict[str, str],
         ElementBaseClass: t.Union[t.Type[_Block], t.Type[_Control]],
     ):
         return type(
             classname,
             (ElementBaseClass,),
-            {
-                "_ELEMENT_NAME": element_name,
-                "_DEFAULT_PROPERTY": default_property,
-            },
+            {"_ELEMENT_NAME": element_name, "_DEFAULT_PROPERTY": default_property, "_TYPES": properties},
         )

+ 8 - 4
taipy/gui/builder/_element.py

@@ -39,7 +39,8 @@ class _Element(ABC):
     __RE_INDEXED_PROPERTY = re.compile(r"^(.*?)__([\w\d]+)$")
     _NEW_LAMBDA_NAME = "new_lambda"
     _TAIPY_EMBEDDED_PREFIX = "_tp_embedded_"
-    _EMBEDED_PROPERTIES = ["decimator"]
+    _EMBEDDED_PROPERTIES = ["decimator"]
+    _TYPES: t.Dict[str, str] = {}
 
     def __new__(cls, *args, **kwargs):
         obj = super(_Element, cls).__new__(cls)
@@ -87,17 +88,20 @@ class _Element(ABC):
             return f"{match.group(1)}[{match.group(2)}]"
         return key
 
+    def _is_callable(self, name: str):
+        return "callable" in self._TYPES.get(name, "").lower()
+
     def _parse_property(self, key: str, value: t.Any) -> t.Any:
         if isinstance(value, (str, dict, Iterable)):
             return value
         if isinstance(value, FunctionType):
-            if key.startswith("on_"):
+            if key.startswith("on_") or self._is_callable(key):
                 return value if value.__name__.startswith("<") else value.__name__
-            # Parse lambda function
+            # Parse lambda function_is_callable
             if (lambda_name := self.__parse_lambda_property(key, value)) is not None:
                 return lambda_name
         # Embed value in the caller frame
-        if not isinstance(value, str) and key in self._EMBEDED_PROPERTIES:
+        if not isinstance(value, str) and key in self._EMBEDDED_PROPERTIES:
             return self.__embed_object(value, is_expression=False)
         if hasattr(value, "__name__"):
             return str(getattr(value, "__name__"))  # noqa: B009

+ 2 - 2
taipy/gui/utils/viselements.py

@@ -13,7 +13,7 @@ import typing as t
 from copy import deepcopy
 
 
-class VisElementProperties(t.TypedDict, total=False):
+class VisElementProperties(t.TypedDict):
     name: str
     type: str
     doc: str
@@ -21,7 +21,7 @@ class VisElementProperties(t.TypedDict, total=False):
     default_property: t.Any
 
 
-class VisElementDetail(t.TypedDict, total=False):
+class VisElementDetail(t.TypedDict):
     inherits: t.List[str]
     properties: t.List[VisElementProperties]
 

+ 4 - 4
taipy/gui/viselements.json

@@ -783,7 +783,7 @@
                     },
                     {
                         "name": "style",
-                        "type": "str",
+                        "type": "Union[str, Callable]",
                         "doc": "Allows the styling of table lines.<br/>See <a href=\"#dynamic-styling\">below</a> for details."
                     },
                     {
@@ -851,7 +851,7 @@
                     },
                     {
                         "name": "on_edit",
-                        "type": "Callable",
+                        "type": "Union[Callable, bool]",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a cell edition is validated.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n<li>col (str): the column name.</li>\n<li>value (Any): the new cell value cast to the type of the column.</li>\n<li>user_value (str): the new cell value, as it was provided by the user.</li>\n<li>tz (str): the timezone if the column type is date.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot edit cells.",
                         "signature": [
                             [
@@ -870,7 +870,7 @@
                     },
                     {
                         "name": "on_delete",
-                        "type": "Callable",
+                        "type": "Union[Callable, bool]",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when a row is deleted.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot delete rows.",
                         "signature": [
                             [
@@ -889,7 +889,7 @@
                     },
                     {
                         "name": "on_add",
-                        "type": "Callable",
+                        "type": "Union[Callable, bool]",
                         "doc": "TODO: Default implementation and False value. The name of a function that is triggered when the user requests a row to be added.<br/>All parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the name of the tabular data variable.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>This dictionary has the following keys:\n<ul>\n<li>index (int): the row index.</li>\n</ul>\n</li>\n</ul><br/>If this property is not set, the user cannot add rows.",
                         "signature": [
                             [