Browse Source

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

Nam Nguyen 7 tháng trước cách đây
mục cha
commit
23b5aee94d

+ 1 - 1
.gitignore

@@ -90,7 +90,7 @@ Data
 
 
 # demo files
 # demo files
 demo-*
 demo-*
-demo_*/
+demo_*
 .airflow
 .airflow
 *.dags
 *.dags
 data_sources
 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.
 unprofessional or unwelcome in the community.
 
 
 **Consequence**: A private, written warning from community leaders, providing
 **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.
 behavior was inappropriate. A public apology may be requested.
 
 
 ### 2. Warning
 ### 2. Warning
@@ -109,7 +109,7 @@ Violating these terms may lead to a permanent ban.
 standards, including sustained inappropriate behavior,  harassment of an
 standards, including sustained inappropriate behavior,  harassment of an
 individual, or aggression toward or disparagement of classes of individuals.
 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.
 the community.
 
 
 ## Attribution
 ## 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
 Please report relevant information and preferably code that exhibits the problem. We provide templates to help you
 describe the issue.
 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.
 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
 ## 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
 ## Implement Features
 
 
 The Taipy team manages its backlog in private. Each issue that is or is going to be engaged by the
 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.
 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
 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
 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.
 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
 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
 ## ⚙️ Quickstart
 
 
-To install Taipy stable release run:
+To install the Taipy stable release run:
 
 
 ```bash
 ```bash
 pip install taipy
 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/).
 To get started with Taipy, please refer to the [Getting Started Guide](https://docs.taipy.io/en/latest/getting_started/).
 
 
 &nbsp;
 &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 />
 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.
 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
 ```python
 import taipy as tp
 import taipy as tp
@@ -169,10 +169,10 @@ if __name__ == "__main__":
     # Taipy User Interface
     # Taipy User Interface
     # Let's add a GUI to our Scenario Management for a full application
     # Let's add a GUI to our Scenario Management for a full application
 
 
-    # Get list of genres
+    # Get the list of genres
     genres = [
     genres = [
         "Action", "Adventure", "Animation", "Children", "Comedy", "Fantasy", "IMAX"
         "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
     # Initialization of variables

+ 1 - 0
contributors.txt

@@ -16,3 +16,4 @@ Forchapeatl
 yarikoptic
 yarikoptic
 Luke-0162
 Luke-0162
 Satoshi-Sh
 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__)
                 var_type = self.__gui._get_unique_type_adapter(type(elt).__name__)
             if adapter is None:
             if adapter is None:
                 adapter = self.__gui._get_adapter_for_type(var_type)
                 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 += (
                 var_type += (
-                    f"__lambda_{id(adapter)}"
+                    _get_lambda_id(t.cast(LambdaType, adapter))
                     if adapter.__name__ == "<lambda>"
                     if adapter.__name__ == "<lambda>"
                     else _get_expr_var_name(adapter.__name__)
                     else _get_expr_var_name(adapter.__name__)
                 )
                 )
@@ -438,19 +438,19 @@ class _Builder:
                 if adapter is None:
                 if adapter is None:
                     adapter = self.__gui._get_adapter_for_type(lov_name)
                     adapter = self.__gui._get_adapter_for_type(lov_name)
                 else:
                 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 value_name := self.__hashes.get("value"):
                 if adapter is None:
                 if adapter is None:
                     adapter = self.__gui._get_adapter_for_type(value_name)
                     adapter = self.__gui._get_adapter_for_type(value_name)
                 else:
                 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:
             if adapter is not None:
                 self.__gui._add_adapter_for_type(var_type, adapter)  # type: ignore
                 self.__gui._add_adapter_for_type(var_type, adapter)  # type: ignore
 
 
             if default_lov is not None and lov:
             if default_lov is not None and lov:
                 for elt in lov:
                 for elt in lov:
                     ret = self.__gui._run_adapter(
                     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
                     )  # type: ignore
                     if ret is not None:
                     if ret is not None:
                         default_lov.append(ret)
                         default_lov.append(ret)
@@ -460,7 +460,10 @@ class _Builder:
             val_list = value if isinstance(value, list) else [value]
             val_list = value if isinstance(value, list) else [value]
             for val in val_list:
             for val in val_list:
                 ret = self.__gui._run_adapter(
                 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
                 )  # type: ignore
                 if ret is not None:
                 if ret is not None:
                     ret_list.append(ret)
                     ret_list.append(ret)

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

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

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

@@ -57,18 +57,28 @@ class _LambdaByName(ast.NodeVisitor):
 
 
     def visit_Call(self, node):
     def visit_Call(self, node):
         if getattr(node.func, "attr", None) == self.element_name:
         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
                         arg
                         for arg in node.args
                         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,
                     None,
                 )
                 )
+            ):
+                self.lambdas[_LambdaByName._DEFAULT_NAME] = a_lambda
+
             for kwd in node.keywords:
             for kwd in node.keywords:
                 if (
                 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.lineno
                     and self.lineno <= kwd.value.end_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 contextlib
 import importlib
 import importlib
-import inspect
 import json
 import json
 import math
 import math
 import os
 import os
@@ -25,6 +24,7 @@ import typing as t
 import warnings
 import warnings
 from importlib import metadata, util
 from importlib import metadata, util
 from importlib.util import find_spec
 from importlib.util import find_spec
+from inspect import currentframe, getabsfile, ismethod, ismodule, isroutine
 from pathlib import Path
 from pathlib import Path
 from threading import Timer
 from threading import Timer
 from types import FrameType, FunctionType, LambdaType, ModuleType, SimpleNamespace
 from types import FrameType, FunctionType, LambdaType, ModuleType, SimpleNamespace
@@ -81,6 +81,7 @@ from .utils import (
     _get_client_var_name,
     _get_client_var_name,
     _get_css_var_value,
     _get_css_var_value,
     _get_expr_var_name,
     _get_expr_var_name,
+    _get_lambda_id,
     _get_module_name_from_frame,
     _get_module_name_from_frame,
     _get_non_existent_file_path,
     _get_non_existent_file_path,
     _get_page_from_module,
     _get_page_from_module,
@@ -311,7 +312,7 @@ class Gui:
                 own Flask application instance and use it to serve the pages.
                 own Flask application instance and use it to serve the pages.
         """
         """
         # store suspected local containing frame
         # 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.__default_module_name = _get_module_name_from_frame(self.__frame)
         self._set_css_file(css_file)
         self._set_css_file(css_file)
 
 
@@ -824,7 +825,7 @@ class Gui:
         if callable(on_change_fn):
         if callable(on_change_fn):
             try:
             try:
                 arg_count = on_change_fn.__code__.co_argcount
                 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
                     arg_count -= 1
                 args: t.List[t.Any] = [None for _ in range(arg_count)]
                 args: t.List[t.Any] = [None for _ in range(arg_count)]
                 if arg_count > 0:
                 if arg_count > 0:
@@ -1509,7 +1510,7 @@ class Gui:
         if callable(action_function):
         if callable(action_function):
             try:
             try:
                 argcount = action_function.__code__.co_argcount
                 argcount = action_function.__code__.co_argcount
-                if argcount > 0 and inspect.ismethod(action_function):
+                if argcount > 0 and ismethod(action_function):
                     argcount -= 1
                     argcount -= 1
                 args = t.cast(list, [None for _ in range(argcount)])
                 args = t.cast(list, [None for _ in range(argcount)])
                 if argcount > 0:
                 if argcount > 0:
@@ -1532,7 +1533,7 @@ class Gui:
         cp_args = [] if args is None else args.copy()
         cp_args = [] if args is None else args.copy()
         cp_args.insert(0, self.__get_state())
         cp_args.insert(0, self.__get_state())
         argcount = user_function.__code__.co_argcount
         argcount = user_function.__code__.co_argcount
-        if argcount > 0 and inspect.ismethod(user_function):
+        if argcount > 0 and ismethod(user_function):
             argcount -= 1
             argcount -= 1
         if argcount > len(cp_args):
         if argcount > len(cp_args):
             cp_args += (argcount - len(cp_args)) * [None]
             cp_args += (argcount - len(cp_args)) * [None]
@@ -2073,7 +2074,7 @@ class Gui:
                 self.add_page(name=k, page=v)
                 self.add_page(name=k, page=v)
         elif isinstance(folder_name := pages, str):
         elif isinstance(folder_name := pages, str):
             if not hasattr(self, "_root_dir"):
             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_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)
             folder_name = os.path.basename(folder_path)
             if not os.path.isdir(folder_path):  # pragma: no cover
             if not os.path.isdir(folder_path):  # pragma: no cover
@@ -2224,9 +2225,9 @@ class Gui:
     def _download(
     def _download(
         self, content: t.Any, name: t.Optional[str] = "", on_action: t.Optional[t.Union[str, t.Callable]] = ""
         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 = (
             on_action_name = (
-                _get_expr_var_name(str(on_action.__code__))
+                _get_lambda_id(t.cast(LambdaType, on_action))
                 if on_action.__name__ == "<lambda>"
                 if on_action.__name__ == "<lambda>"
                 else _get_expr_var_name(on_action.__name__)
                 else _get_expr_var_name(on_action.__name__)
             )
             )
@@ -2377,7 +2378,7 @@ class Gui:
             return
             return
         try:
         try:
             arg_count = on_page_load_fn.__code__.co_argcount
             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
                 arg_count -= 1
             args: t.List[t.Any] = [None for _ in range(arg_count)]
             args: t.List[t.Any] = [None for _ in range(arg_count)]
             if arg_count > 0:
             if arg_count > 0:
@@ -2752,7 +2753,7 @@ class Gui:
 
 
         app_config = self._config.config
         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
         # Register _root_dir for abs path
         if not hasattr(self, "_root_dir"):
         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: t.Dict[str, t.Any] = {t.__name__: t for t in _TaipyBase.__subclasses__()}
         glob_ctx[Gui.__SELF_VAR] = self
         glob_ctx[Gui.__SELF_VAR] = self
         glob_ctx["state"] = self.__state
         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
         # Call on_init on each library
         for name, libs in self.__extensions.items():
         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)
             _warn(f"Cannot evaluate expression '{not_encoded_expr if is_edge_case else expr_string}'", e)
             expr_evaluated = None
             expr_evaluated = None
         if lambda_expr and callable(expr_evaluated):
         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
         # save the expression if it needs to be re-evaluated
         return self.__save_expression(gui, expr, expr_hash, expr_evaluated, var_map, lambda_expr)
         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
 # 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.
 # specific language governing permissions and limitations under the License.
 
 
+import typing as t
 from types import LambdaType
 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
+    )