Quellcode durchsuchen

Merge branch 'develop' into feature/#746-creating-Reason-class

Toan Quach vor 1 Jahr
Ursprung
Commit
d62235f9ba

+ 2 - 2
taipy/core/data/_file_datanode_mixin.py

@@ -55,8 +55,8 @@ class _FileDataNodeMixin(object):
                 Edit(
                 Edit(
                     {
                     {
                         "timestamp": self._last_edit_date,
                         "timestamp": self._last_edit_date,
-                        "writer_identifier": "TAIPY",
-                        "comments": "Default data written.",
+                        "editor": "TAIPY",
+                        "comment": "Default data written.",
                     }
                     }
                 )
                 )
             )
             )

+ 36 - 5
taipy/core/data/data_node.py

@@ -65,7 +65,37 @@ class DataNode(_Entity, _Labeled):
     SQL Data Node, CSV Data Node, ...).
     SQL Data Node, CSV Data Node, ...).
 
 
     !!! note
     !!! note
-        It is recommended not to instantiate subclasses of `DataNode` directly.
+        It is not recommended to instantiate subclasses of `DataNode` directly. Instead,
+        you have two ways:
+
+        1. Create a Scenario using the `create_scenario()^` function. Related data nodes
+            will be created automatically. Please refer to the `Scenario^` class for more
+            information.
+        2. Configure a `DataNodeConfig^` with the various configuration methods form `Config^`
+            and use the `create_global_data_node()^` function as illustrated in the following
+            example.
+
+    !!! Example
+
+        ```python
+        import taipy as tp
+        from taipy import Config
+
+        # Configure a global data node
+        dataset_cfg = Config.configure_data_node("my_dataset", scope=tp.Scope.GLOBAL)
+
+        # Instantiate a global data node
+        dataset = tp.create_global_data_node(dataset_cfg)
+
+        # Retrieve the list of all data nodes
+        all_data_nodes = tp.get_data_nodes()
+
+        # Write the data
+        dataset.write("Hello, World!")
+
+        # Read the data
+        print(dataset.read())
+        ```
 
 
     Attributes:
     Attributes:
         config_id (str): Identifier of the data node configuration. It must be a valid Python
         config_id (str): Identifier of the data node configuration. It must be a valid Python
@@ -78,10 +108,11 @@ class DataNode(_Entity, _Labeled):
         parent_ids (Optional[Set[str]]): The set of identifiers of the parent tasks.
         parent_ids (Optional[Set[str]]): The set of identifiers of the parent tasks.
         last_edit_date (datetime): The date and time of the last modification.
         last_edit_date (datetime): The date and time of the last modification.
         edits (List[Edit^]): The list of Edits (an alias for dict) containing metadata about each
         edits (List[Edit^]): The list of Edits (an alias for dict) containing metadata about each
-            data edition including but not limited to timestamp, comments, job_id:
-            timestamp: The time instant of the writing
-            comments: Representation of a free text to explain or comment on a data change
-            job_id: Only populated when the data node is written by a task execution and corresponds to the job's id.
+            data edition including but not limited to:
+                <ul><li>timestamp: The time instant of the writing </li>
+                <li>comments: Representation of a free text to explain or comment on a data change</li>
+                <li>job_id: Only populated when the data node is written by a task execution and
+                    corresponds to the job's id.</li></ul>
             Additional metadata related to the edition made to the data node can also be provided in Edits.
             Additional metadata related to the edition made to the data node can also be provided in Edits.
         version (str): The string indicates the application version of the data node to
         version (str): The string indicates the application version of the data node to
             instantiate. If not provided, the current version is used.
             instantiate. If not provided, the current version is used.

+ 2 - 2
taipy/core/data/in_memory.py

@@ -101,8 +101,8 @@ class InMemoryDataNode(DataNode):
                 Edit(
                 Edit(
                     {
                     {
                         "timestamp": self._last_edit_date,
                         "timestamp": self._last_edit_date,
-                        "writer_identifier": "TAIPY",
-                        "comments": "Default data written.",
+                        "editor": "TAIPY",
+                        "comment": "Default data written.",
                     }
                     }
                 )
                 )
             )
             )

+ 34 - 0
taipy/core/scenario/scenario.py

@@ -55,6 +55,40 @@ class Scenario(_Entity, Submittable, _Labeled):
     solve the Business case. It also holds a set of additional data nodes (instances of `DataNode` class)
     solve the Business case. It also holds a set of additional data nodes (instances of `DataNode` class)
     for extra data related to the scenario.
     for extra data related to the scenario.
 
 
+    !!! note
+
+        It is not recommended to instantiate a `Scenario` directly. Instead, it should be
+        created with the `create_scenario()^` function.
+
+    !!! Example
+
+        ```python
+        import taipy as tp
+        from taipy import Config
+
+        def by_two(x: int):
+            return x * 2
+
+        # Configure scenarios
+        input_cfg = Config.configure_data_node("my_input")
+        result_cfg = Config.configure_data_node("my_result")
+        task_cfg = Config.configure_task("my_double", function=by_two, input=input_cfg, output=result_cfg)
+        scenario_cfg = Config.configure_scenario("my_scenario", task_configs=[task_cfg])
+
+        # Create a new scenario from the configuration
+        scenario = tp.create_scenario(scenario_cfg)
+
+        # Write the input data and submit the scenario
+        scenario.my_input.write(3)
+        scenario.submit()
+
+        # Read the result
+        print(scenario.my_result.read())  # Output: 6
+
+        # Retrieve all scenarios
+        all_scenarios = tp.get_scenarios()
+        ```
+
     Attributes:
     Attributes:
         config_id (str): The identifier of the `ScenarioConfig^`.
         config_id (str): The identifier of the `ScenarioConfig^`.
         tasks (Set[Task^]): The set of tasks.
         tasks (Set[Task^]): The set of tasks.

+ 40 - 0
taipy/core/task/task.py

@@ -33,6 +33,46 @@ class Task(_Entity, _Labeled):
     A `Task` brings together the user code as function, the inputs and the outputs as data nodes
     A `Task` brings together the user code as function, the inputs and the outputs as data nodes
     (instances of the `DataNode^` class).
     (instances of the `DataNode^` class).
 
 
+    !!! note
+        It is not recommended to instantiate a `Task` directly. Instead, it should be
+        created with the `create_scenario()^` function. When creating a `Scenario^`,
+        the related data nodes and tasks are created automatically. Please refer to
+        the `Scenario^` class for more information.
+
+    !!! Example
+
+        ```python
+        import taipy as tp
+        from taipy import Config
+
+        def by_two(x: int):
+            return x * 2
+
+        # Configure data nodes, tasks and scenarios
+        input_cfg = Config.configure_data_node("my_input", default_data=2)
+        result_cfg = Config.configure_data_node("my_result")
+        task_cfg = Config.configure_task("my_double", function=by_two, input=input_cfg, output=result_cfg)
+        scenario_cfg = Config.configure_scenario("my_scenario", task_configs=[task_cfg])
+
+        # Instantiate a task along with a scenario
+        sc = tp.create_scenario(scenario_cfg)
+
+        # Retrieve task and data nodes from scenario
+        task_input = sc.my_input
+        double_task = sc.my_double
+        task_result = sc.my_result
+
+        # Write the input data and submit the task
+        task_input.write(3)
+        double_task.submit()
+
+        # Read the result
+        print(task_result.read())  # Output: 6
+
+        # Retrieve the list of all tasks
+        all_tasks = tp.get_tasks()
+        ```
+
     Attributes:
     Attributes:
         config_id (str): The identifier of the `TaskConfig^`.
         config_id (str): The identifier of the `TaskConfig^`.
         properties (dict[str, Any]): A dictionary of additional properties.
         properties (dict[str, Any]): A dictionary of additional properties.

+ 2 - 1
taipy/gui/_renderers/builder.py

@@ -70,6 +70,7 @@ class _Builder:
         "style",
         "style",
         "tooltip",
         "tooltip",
         "lov",
         "lov",
+        "on_edit",
     ]
     ]
 
 
     def __init__(
     def __init__(
@@ -480,7 +481,7 @@ class _Builder:
     def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
     def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
         rebuild = self.__attributes.get("rebuild", False)
         rebuild = self.__attributes.get("rebuild", False)
         rebuild_hash = self.__hashes.get("rebuild")
         rebuild_hash = self.__hashes.get("rebuild")
-        if rebuild_hash or rebuild:
+        if rebuild_hash or _is_boolean_true(rebuild):
             attributes, hashes = self.__filter_attributes_hashes(self.__filter_attribute_names(attribute_names))
             attributes, hashes = self.__filter_attributes_hashes(self.__filter_attribute_names(attribute_names))
             rebuild_name = f"bool({self.__gui._get_real_var_name(rebuild_hash)[0]})" if rebuild_hash else "None"
             rebuild_name = f"bool({self.__gui._get_real_var_name(rebuild_hash)[0]})" if rebuild_hash else "None"
             try:
             try:

+ 9 - 1
taipy/gui/data/data_scope.py

@@ -16,13 +16,17 @@ from types import SimpleNamespace
 
 
 from .._warnings import _warn
 from .._warnings import _warn
 
 
+if t.TYPE_CHECKING:
+    from ..gui import Gui
+
 
 
 class _DataScopes:
 class _DataScopes:
     _GLOBAL_ID = "global"
     _GLOBAL_ID = "global"
     _META_PRE_RENDER = "pre_render"
     _META_PRE_RENDER = "pre_render"
     _DEFAULT_METADATA = {_META_PRE_RENDER: False}
     _DEFAULT_METADATA = {_META_PRE_RENDER: False}
 
 
-    def __init__(self) -> None:
+    def __init__(self, gui: "Gui") -> None:
+        self.__gui = gui
         self.__scopes: t.Dict[str, SimpleNamespace] = {_DataScopes._GLOBAL_ID: SimpleNamespace()}
         self.__scopes: t.Dict[str, SimpleNamespace] = {_DataScopes._GLOBAL_ID: SimpleNamespace()}
         # { scope_name: { metadata: value } }
         # { scope_name: { metadata: value } }
         self.__scopes_metadata: t.Dict[str, t.Dict[str, t.Any]] = {
         self.__scopes_metadata: t.Dict[str, t.Dict[str, t.Any]] = {
@@ -63,6 +67,10 @@ class _DataScopes:
         if id not in self.__scopes:
         if id not in self.__scopes:
             self.__scopes[id] = SimpleNamespace()
             self.__scopes[id] = SimpleNamespace()
             self.__scopes_metadata[id] = _DataScopes._DEFAULT_METADATA.copy()
             self.__scopes_metadata[id] = _DataScopes._DEFAULT_METADATA.copy()
+            # Propagate shared variables to the new scope from the global scope
+            for var in self.__gui._get_shared_variables():
+                if hasattr(self.__scopes[_DataScopes._GLOBAL_ID], var):
+                    setattr(self.__scopes[id], var, getattr(self.__scopes[_DataScopes._GLOBAL_ID], var, None))
 
 
     def delete_scope(self, id: str) -> None:  # pragma: no cover
     def delete_scope(self, id: str) -> None:  # pragma: no cover
         if self.__single_client:
         if self.__single_client:

+ 14 - 7
taipy/gui/gui.py

@@ -2275,12 +2275,19 @@ class Gui:
 
 
     def __init_ngrok(self):
     def __init_ngrok(self):
         app_config = self._config.config
         app_config = self._config.config
-        if app_config["run_server"] and app_config["ngrok_token"]:  # pragma: no cover
+        if hasattr(self, "_ngrok"):
+            # Keep the ngrok instance if token has not changed
+            if app_config["ngrok_token"] == self._ngrok[1]:
+                _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
+                return
+            # Close the old tunnel so new tunnel can open for new token
+            ngrok.disconnect(self._ngrok[0].public_url)
+        if app_config["run_server"] and (token := app_config["ngrok_token"]):  # pragma: no cover
             if not util.find_spec("pyngrok"):
             if not util.find_spec("pyngrok"):
                 raise RuntimeError("Cannot use ngrok as pyngrok package is not installed.")
                 raise RuntimeError("Cannot use ngrok as pyngrok package is not installed.")
-            ngrok.set_auth_token(app_config["ngrok_token"])
-            http_tunnel = ngrok.connect(app_config["port"], "http")
-            _TaipyLogger._get_logger().info(f" * NGROK Public Url: {http_tunnel.public_url}")
+            ngrok.set_auth_token(token)
+            self._ngrok = (ngrok.connect(app_config["port"], "http"), token)
+            _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
 
 
     def __bind_default_function(self):
     def __bind_default_function(self):
         with self.get_flask_app().app_context():
         with self.get_flask_app().app_context():
@@ -2325,13 +2332,13 @@ class Gui:
         extension_bp = Blueprint("taipy_extensions", __name__)
         extension_bp = Blueprint("taipy_extensions", __name__)
         extension_bp.add_url_rule(f"/{Gui._EXTENSION_ROOT}/<path:path>", view_func=self.__serve_extension)
         extension_bp.add_url_rule(f"/{Gui._EXTENSION_ROOT}/<path:path>", view_func=self.__serve_extension)
         scripts = [
         scripts = [
-            s if bool(urlparse(s).netloc) else f"/{Gui._EXTENSION_ROOT}/{name}/{s}{lib.get_query(s)}"
+            s if bool(urlparse(s).netloc) else f"{Gui._EXTENSION_ROOT}/{name}/{s}{lib.get_query(s)}"
             for name, libs in Gui.__extensions.items()
             for name, libs in Gui.__extensions.items()
             for lib in libs
             for lib in libs
             for s in (lib.get_scripts() or [])
             for s in (lib.get_scripts() or [])
         ]
         ]
         styles = [
         styles = [
-            s if bool(urlparse(s).netloc) else f"/{Gui._EXTENSION_ROOT}/{name}/{s}{lib.get_query(s)}"
+            s if bool(urlparse(s).netloc) else f"{Gui._EXTENSION_ROOT}/{name}/{s}{lib.get_query(s)}"
             for name, libs in Gui.__extensions.items()
             for name, libs in Gui.__extensions.items()
             for lib in libs
             for lib in libs
             for s in (lib.get_styles() or [])
             for s in (lib.get_styles() or [])
@@ -2341,7 +2348,7 @@ class Gui:
         else:
         else:
             styles.append(Gui.__ROBOTO_FONT)
             styles.append(Gui.__ROBOTO_FONT)
         if self.__css_file:
         if self.__css_file:
-            styles.append(f"/{self.__css_file}")
+            styles.append(f"{self.__css_file}")
 
 
         self._flask_blueprint.append(extension_bp)
         self._flask_blueprint.append(extension_bp)
 
 

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

@@ -23,7 +23,7 @@ if t.TYPE_CHECKING:
 class _Bindings:
 class _Bindings:
     def __init__(self, gui: "Gui") -> None:
     def __init__(self, gui: "Gui") -> None:
         self.__gui = gui
         self.__gui = gui
-        self.__scopes = _DataScopes()
+        self.__scopes = _DataScopes(gui)
 
 
     def _bind(self, name: str, value: t.Any) -> None:
     def _bind(self, name: str, value: t.Any) -> None:
         if hasattr(self, name):
         if hasattr(self, name):
@@ -69,7 +69,7 @@ class _Bindings:
         return id, create
         return id, create
 
 
     def _new_scopes(self):
     def _new_scopes(self):
-        self.__scopes = _DataScopes()
+        self.__scopes = _DataScopes(self.__gui)
 
 
     def _get_data_scope(self):
     def _get_data_scope(self):
         return self.__scopes.get_scope(self.__gui._get_client_id())[0]
         return self.__scopes.get_scope(self.__gui._get_client_id())[0]