1
0
Эх сурвалжийг харах

Merge branch 'develop' into docs/#1819-docs-updating-docs-on-element-definition

Nam Nguyen 7 сар өмнө
parent
commit
23b5aee94d

+ 1 - 1
.gitignore

@@ -90,7 +90,7 @@ Data
 
 # demo files
 demo-*
-demo_*/
+demo_*
 .airflow
 *.dags
 data_sources

+ 2 - 2
CODE_OF_CONDUCT.md

@@ -77,7 +77,7 @@ the consequences for any action they deem in violation of this Code of Conduct:
 unprofessional or unwelcome in the community.
 
 **Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
+clarity about the nature of the violation and an explanation of why the
 behavior was inappropriate. A public apology may be requested.
 
 ### 2. Warning
@@ -109,7 +109,7 @@ Violating these terms may lead to a permanent ban.
 standards, including sustained inappropriate behavior,  harassment of an
 individual, or aggression toward or disparagement of classes of individuals.
 
-**Consequence**: A permanent ban from any sort of public interaction within
+**Consequence**: A permanent ban on any sort of public interaction within
 the community.
 
 ## Attribution

+ 3 - 3
CONTRIBUTING.md

@@ -26,7 +26,7 @@ Reporting bugs is through [GitHub issues](https://github.com/Avaiga/taipy/issues
 Please report relevant information and preferably code that exhibits the problem. We provide templates to help you
 describe the issue.
 
-The Taipy team will analyse and try to reproduce the bug to provide feedback. If confirmed, we will add a priority
+The Taipy team will analyze and try to reproduce the bug to provide feedback. If confirmed, we will add a priority
 to the issue and add it in our backlog. Feel free to propose a pull request to fix it.
 
 ## Issue reporting, feedback, proposal, design or any other comment
@@ -44,12 +44,12 @@ Do not hesitate to create an issue or pull request directly on the
 ## Implement Features
 
 The Taipy team manages its backlog in private. Each issue that is or is going to be engaged by the
-Taipy team is attached to the "🔒 Staff only" label or has already assigned to a Taipy team member.
+Taipy team is attached to the "🔒 Staff only" label or has already been assigned to a Taipy team member.
 Please, do not work on it, the Taipy team is on it.
 
 All other issues are sorted by labels and are available for a contribution. If you are new to the
 project, you can start with the "good first issue" or "🆘 Help wanted" label. You can also start with
-issue with higher priority like "Critical" or "High". The higher the priority, the more value it
+issues with higher priority, like "Critical" or "High". The higher the priority, the more value it
 will bring to Taipy.
 
 If you want to work on an issue, please add a comment and wait to be assigned to the issue to inform

+ 5 - 5
README.md

@@ -70,13 +70,13 @@ Taipy is a Two-in-One Tool for UI Generation and Scenario/Data Management
 
 ## ⚙️ Quickstart
 
-To install Taipy stable release run:
+To install the Taipy stable release run:
 
 ```bash
 pip install taipy
 ```
 
-To install Taipy on a Conda Environment or from source, please refer to the [Installation Guide](https://docs.taipy.io/en/latest/installation/).<br />
+To install Taipy on a Conda Environment or from a source, please refer to the [Installation Guide](https://docs.taipy.io/en/latest/installation/).<br />
 To get started with Taipy, please refer to the [Getting Started Guide](https://docs.taipy.io/en/latest/getting_started/).
 
 &nbsp;
@@ -127,7 +127,7 @@ Check out the movie genre demo scenario creation with this [Demo](https://docs.t
 
 This simple Taipy application demonstrates how to create a basic film recommendation system using Taipy.<br />
 The application filters a dataset of films based on the user's selected genre and displays the top seven films in that genre by popularity.
-Here is the full code for both the frontend and backend of the application.
+Here is the full code for both the front-end and back-end of the application.
 
 ```python
 import taipy as tp
@@ -169,10 +169,10 @@ if __name__ == "__main__":
     # Taipy User Interface
     # Let's add a GUI to our Scenario Management for a full application
 
-    # Get list of genres
+    # Get the list of genres
     genres = [
         "Action", "Adventure", "Animation", "Children", "Comedy", "Fantasy", "IMAX"
-        "Romance","Sci-FI", "Western", "Crime", "Mystery", "Drama", "Horror", "Thriller", "Film-Noir","War", "Musical", "Documentary"
+        "Romance", "Sci-FI", "Western", "Crime", "Mystery", "Drama", "Horror", "Thriller", "Film-Noir", "War", "Musical", "Documentary"
     ]
 
     # Initialization of variables

+ 1 - 0
contributors.txt

@@ -16,3 +16,4 @@ Forchapeatl
 yarikoptic
 Luke-0162
 Satoshi-Sh
+kushal34712

+ 9 - 6
taipy/gui/_renderers/builder.py

@@ -428,9 +428,9 @@ class _Builder:
                 var_type = self.__gui._get_unique_type_adapter(type(elt).__name__)
             if adapter is None:
                 adapter = self.__gui._get_adapter_for_type(var_type)
-            elif var_type == str.__name__ and callable(adapter):
+            elif var_type == str.__name__ and isroutine(adapter):
                 var_type += (
-                    f"__lambda_{id(adapter)}"
+                    _get_lambda_id(t.cast(LambdaType, adapter))
                     if adapter.__name__ == "<lambda>"
                     else _get_expr_var_name(adapter.__name__)
                 )
@@ -438,19 +438,19 @@ class _Builder:
                 if adapter is None:
                     adapter = self.__gui._get_adapter_for_type(lov_name)
                 else:
-                    self.__gui._add_type_for_var(lov_name, var_type)
+                    self.__gui._add_type_for_var(lov_name, t.cast(str, var_type))
             if value_name := self.__hashes.get("value"):
                 if adapter is None:
                     adapter = self.__gui._get_adapter_for_type(value_name)
                 else:
-                    self.__gui._add_type_for_var(value_name, var_type)
+                    self.__gui._add_type_for_var(value_name, t.cast(str, var_type))
             if adapter is not None:
                 self.__gui._add_adapter_for_type(var_type, adapter)  # type: ignore
 
             if default_lov is not None and lov:
                 for elt in lov:
                     ret = self.__gui._run_adapter(
-                        t.cast(t.Callable, adapter), elt, adapter.__name__ if callable(adapter) else "adapter"
+                        t.cast(t.Callable, adapter), elt, adapter.__name__ if isroutine(adapter) else "adapter"
                     )  # type: ignore
                     if ret is not None:
                         default_lov.append(ret)
@@ -460,7 +460,10 @@ class _Builder:
             val_list = value if isinstance(value, list) else [value]
             for val in val_list:
                 ret = self.__gui._run_adapter(
-                    t.cast(t.Callable, adapter), val, adapter.__name__ if callable(adapter) else "adapter", id_only=True
+                    t.cast(t.Callable, adapter),
+                    val,
+                    adapter.__name__ if isroutine(adapter) else "adapter",
+                    id_only=True,
                 )  # type: ignore
                 if ret is not None:
                     ret_list.append(ret)

+ 11 - 6
taipy/gui/builder/_element.py

@@ -16,13 +16,12 @@ import copy
 import inspect
 import re
 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
+from ..utils import _get_lambda_id, _getscopeattr
 from ._context_manager import _BuilderContextManager
 from ._factory import _BuilderFactory
 from ._utils import _LambdaByName, _python_builtins, _TransformVarToValue
@@ -37,10 +36,10 @@ class _Element(ABC):
     _ELEMENT_NAME = ""
     _DEFAULT_PROPERTY = ""
     __RE_INDEXED_PROPERTY = re.compile(r"^(.*?)__([\w\d]+)$")
-    _NEW_LAMBDA_NAME = "new_lambda"
     _TAIPY_EMBEDDED_PREFIX = "_tp_embedded_"
     _EMBEDDED_PROPERTIES = ["decimator"]
     _TYPES: t.Dict[str, str] = {}
+    __LAMBDA_VALUE_IDX = 0
 
     def __new__(cls, *args, **kwargs):
         obj = super(_Element, cls).__new__(cls)
@@ -67,6 +66,12 @@ class _Element(ABC):
         self._properties.update(kwargs)
         self.parse_properties()
 
+    @staticmethod
+    def __get_lambda_index():
+        _Element.__LAMBDA_VALUE_IDX += 1
+        _Element.__LAMBDA_VALUE_IDX %= 0xFFFFFFF0
+        return _Element.__LAMBDA_VALUE_IDX
+
     def _evaluate_lambdas(self, gui: Gui):
         for k, lmbd in self._lambdas.items():
             expr = gui._evaluate_expr(lmbd, lambda_expr=True)
@@ -100,8 +105,8 @@ class _Element(ABC):
             if key.startswith("on_") or self._is_callable(key):
                 return value if value.__name__.startswith("<") else value.__name__
             # Parse lambda function_is_callable
-            if (lambda_name := self.__parse_lambda_property(key, value)) is not None:
-                return lambda_name
+            if (lambda_call := self.__parse_lambda_property(key, value)) is not None:
+                return lambda_call
         # Embed value in the caller frame
         if not isinstance(value, str) and key in self._EMBEDDED_PROPERTIES:
             return self.__embed_object(value, is_expression=False)
@@ -131,7 +136,7 @@ class _Element(ABC):
             tree = _TransformVarToValue(self.__calling_frame, args + targets + _python_builtins).visit(lambda_fn)
             ast.fix_missing_locations(tree)
             lambda_text = ast.unparse(tree)
-            lambda_name = f"__lambda_{uuid.uuid4().hex}"
+            lambda_name = _get_lambda_id(value, index=(_Element.__get_lambda_index()))
             self._lambdas[lambda_name] = lambda_text
             return f'{{{lambda_name}({", ".join(args)})}}'
         except Exception as e:

+ 14 - 4
taipy/gui/builder/_utils.py

@@ -57,18 +57,28 @@ class _LambdaByName(ast.NodeVisitor):
 
     def visit_Call(self, node):
         if getattr(node.func, "attr", None) == self.element_name:
-            if self.lambdas.get(_LambdaByName._DEFAULT_NAME, None) is None:
-                self.lambdas[_LambdaByName._DEFAULT_NAME] = next(
+            if self.lambdas.get(_LambdaByName._DEFAULT_NAME, None) is None and (
+                a_lambda := next(
                     (
                         arg
                         for arg in node.args
-                        if isinstance(arg, ast.Lambda) and self.lineno >= arg.lineno and self.lineno <= arg.end_lineno
+                        if isinstance(arg, ast.Lambda)
+                        and arg.lineno is not None
+                        and arg.end_lineno is not None
+                        and self.lineno >= arg.lineno
+                        and self.lineno <= arg.end_lineno
                     ),
                     None,
                 )
+            ):
+                self.lambdas[_LambdaByName._DEFAULT_NAME] = a_lambda
+
             for kwd in node.keywords:
                 if (
-                    isinstance(kwd.value, ast.Lambda)
+                    kwd.arg is not None
+                    and isinstance(kwd.value, ast.Lambda)
+                    and kwd.value.lineno is not None
+                    and kwd.value.end_lineno is not None
                     and self.lineno >= kwd.value.lineno
                     and self.lineno <= kwd.value.end_lineno
                 ):

+ 12 - 11
taipy/gui/gui.py

@@ -13,7 +13,6 @@ from __future__ import annotations
 
 import contextlib
 import importlib
-import inspect
 import json
 import math
 import os
@@ -25,6 +24,7 @@ import typing as t
 import warnings
 from importlib import metadata, util
 from importlib.util import find_spec
+from inspect import currentframe, getabsfile, ismethod, ismodule, isroutine
 from pathlib import Path
 from threading import Timer
 from types import FrameType, FunctionType, LambdaType, ModuleType, SimpleNamespace
@@ -81,6 +81,7 @@ from .utils import (
     _get_client_var_name,
     _get_css_var_value,
     _get_expr_var_name,
+    _get_lambda_id,
     _get_module_name_from_frame,
     _get_non_existent_file_path,
     _get_page_from_module,
@@ -311,7 +312,7 @@ class Gui:
                 own Flask application instance and use it to serve the pages.
         """
         # store suspected local containing frame
-        self.__frame = t.cast(FrameType, t.cast(FrameType, inspect.currentframe()).f_back)
+        self.__frame = t.cast(FrameType, t.cast(FrameType, currentframe()).f_back)
         self.__default_module_name = _get_module_name_from_frame(self.__frame)
         self._set_css_file(css_file)
 
@@ -824,7 +825,7 @@ class Gui:
         if callable(on_change_fn):
             try:
                 arg_count = on_change_fn.__code__.co_argcount
-                if arg_count > 0 and inspect.ismethod(on_change_fn):
+                if arg_count > 0 and ismethod(on_change_fn):
                     arg_count -= 1
                 args: t.List[t.Any] = [None for _ in range(arg_count)]
                 if arg_count > 0:
@@ -1509,7 +1510,7 @@ class Gui:
         if callable(action_function):
             try:
                 argcount = action_function.__code__.co_argcount
-                if argcount > 0 and inspect.ismethod(action_function):
+                if argcount > 0 and ismethod(action_function):
                     argcount -= 1
                 args = t.cast(list, [None for _ in range(argcount)])
                 if argcount > 0:
@@ -1532,7 +1533,7 @@ class Gui:
         cp_args = [] if args is None else args.copy()
         cp_args.insert(0, self.__get_state())
         argcount = user_function.__code__.co_argcount
-        if argcount > 0 and inspect.ismethod(user_function):
+        if argcount > 0 and ismethod(user_function):
             argcount -= 1
         if argcount > len(cp_args):
             cp_args += (argcount - len(cp_args)) * [None]
@@ -2073,7 +2074,7 @@ class Gui:
                 self.add_page(name=k, page=v)
         elif isinstance(folder_name := pages, str):
             if not hasattr(self, "_root_dir"):
-                self._root_dir = os.path.dirname(inspect.getabsfile(self.__frame))
+                self._root_dir = os.path.dirname(getabsfile(self.__frame))
             folder_path = folder_name if os.path.isabs(folder_name) else os.path.join(self._root_dir, folder_name)
             folder_name = os.path.basename(folder_path)
             if not os.path.isdir(folder_path):  # pragma: no cover
@@ -2224,9 +2225,9 @@ class Gui:
     def _download(
         self, content: t.Any, name: t.Optional[str] = "", on_action: t.Optional[t.Union[str, t.Callable]] = ""
     ):
-        if callable(on_action) and on_action.__name__:
+        if isroutine(on_action) and on_action.__name__:
             on_action_name = (
-                _get_expr_var_name(str(on_action.__code__))
+                _get_lambda_id(t.cast(LambdaType, on_action))
                 if on_action.__name__ == "<lambda>"
                 else _get_expr_var_name(on_action.__name__)
             )
@@ -2377,7 +2378,7 @@ class Gui:
             return
         try:
             arg_count = on_page_load_fn.__code__.co_argcount
-            if arg_count > 0 and inspect.ismethod(on_page_load_fn):
+            if arg_count > 0 and ismethod(on_page_load_fn):
                 arg_count -= 1
             args: t.List[t.Any] = [None for _ in range(arg_count)]
             if arg_count > 0:
@@ -2752,7 +2753,7 @@ class Gui:
 
         app_config = self._config.config
 
-        run_root_dir = os.path.dirname(inspect.getabsfile(self.__frame))
+        run_root_dir = os.path.dirname(getabsfile(self.__frame))
 
         # Register _root_dir for abs path
         if not hasattr(self, "_root_dir"):
@@ -2797,7 +2798,7 @@ class Gui:
         glob_ctx: t.Dict[str, t.Any] = {t.__name__: t for t in _TaipyBase.__subclasses__()}
         glob_ctx[Gui.__SELF_VAR] = self
         glob_ctx["state"] = self.__state
-        glob_ctx.update({k: v for k, v in locals_bind.items() if inspect.ismodule(v) or callable(v)})
+        glob_ctx.update({k: v for k, v in locals_bind.items() if ismodule(v) or callable(v)})
 
         # Call on_init on each library
         for name, libs in self.__extensions.items():

+ 1 - 1
taipy/gui/utils/_evaluator.py

@@ -263,7 +263,7 @@ class _Evaluator:
             _warn(f"Cannot evaluate expression '{not_encoded_expr if is_edge_case else expr_string}'", e)
             expr_evaluated = None
         if lambda_expr and callable(expr_evaluated):
-            expr_hash = _get_lambda_id(expr_evaluated)
+            expr_hash = _get_lambda_id(expr_evaluated, module=module_name)
         # save the expression if it needs to be re-evaluated
         return self.__save_expression(gui, expr, expr_hash, expr_evaluated, var_map, lambda_expr)
 

+ 7 - 2
taipy/gui/utils/_lambda.py

@@ -9,8 +9,13 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
+import typing as t
 from types import LambdaType
 
+from ._variable_directory import _variable_encode
 
-def _get_lambda_id(lambda_fn: LambdaType):
-    return f"__lambda_{id(lambda_fn)}"
+
+def _get_lambda_id(lambda_fn: LambdaType, module: t.Optional[str] = None, index: t.Optional[int] = None):
+    return _variable_encode(
+        f"__lambda_{id(lambda_fn)}{f'_{index}' if index is not None else ''}", lambda_fn.__module__ or module
+    )