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

Merge branch 'develop' into 2366-allow-custom-javascript-files-for-specific-page

Nam Nguyen 4 сар өмнө
parent
commit
7b6f170321

+ 5 - 4
taipy/_entrypoint.py

@@ -14,7 +14,7 @@ import sys
 from importlib.util import find_spec
 from importlib.util import find_spec
 
 
 from taipy.common._cli._base_cli._taipy_parser import _TaipyParser
 from taipy.common._cli._base_cli._taipy_parser import _TaipyParser
-from taipy.common._cli._create_cli import _CreateCLI
+from taipy.common._cli._create_cli_factory import _CreateCLIFactory
 from taipy.common._cli._help_cli import _HelpCLI
 from taipy.common._cli._help_cli import _HelpCLI
 from taipy.common._cli._run_cli import _RunCLI
 from taipy.common._cli._run_cli import _RunCLI
 from taipy.core._cli._core_cli_factory import _CoreCLIFactory
 from taipy.core._cli._core_cli_factory import _CoreCLIFactory
@@ -42,14 +42,15 @@ def _entrypoint():
         _enterprise_entrypoint_initialize()
         _enterprise_entrypoint_initialize()
 
 
     _core_cli = _CoreCLIFactory._build_cli()
     _core_cli = _CoreCLIFactory._build_cli()
+    _create_cli = _CreateCLIFactory._build_cli()
 
 
     _RunCLI.create_parser()
     _RunCLI.create_parser()
     _GuiCLI.create_run_parser()
     _GuiCLI.create_run_parser()
     _core_cli.create_run_parser()
     _core_cli.create_run_parser()
 
 
     _VersionCLIFactory._build_cli().create_parser()
     _VersionCLIFactory._build_cli().create_parser()
-    _CreateCLI.generate_template_map()
-    _CreateCLI.create_parser()
+    _create_cli.generate_template_map()
+    _create_cli.create_parser()
     _MigrateCLI.create_parser()
     _MigrateCLI.create_parser()
     _HelpCLI.create_parser()
     _HelpCLI.create_parser()
 
 
@@ -67,7 +68,7 @@ def _entrypoint():
     _HelpCLI.handle_command()
     _HelpCLI.handle_command()
     _VersionCLIFactory._build_cli().handle_command()
     _VersionCLIFactory._build_cli().handle_command()
     _MigrateCLI.handle_command()
     _MigrateCLI.handle_command()
-    _CreateCLI.handle_command()
+    _create_cli.handle_command()
 
 
     _TaipyParser._remove_argument("help")
     _TaipyParser._remove_argument("help")
     _TaipyParser._parser.print_help()
     _TaipyParser._parser.print_help()

+ 3 - 4
taipy/common/_cli/_create_cli.py

@@ -11,7 +11,7 @@
 
 
 import pathlib
 import pathlib
 import sys
 import sys
-from typing import Dict, Optional
+from typing import Dict
 
 
 from cookiecutter.exceptions import OutputDirExistsException
 from cookiecutter.exceptions import OutputDirExistsException
 from cookiecutter.main import cookiecutter
 from cookiecutter.main import cookiecutter
@@ -29,9 +29,8 @@ class _CreateCLI(_AbstractCLI):
     _ARGUMENTS = ["--application"]
     _ARGUMENTS = ["--application"]
 
 
     @classmethod
     @classmethod
-    def generate_template_map(cls, template_path: Optional[pathlib.Path] = None):
-        if not template_path:
-            template_path = pathlib.Path(taipy.__file__).parent.resolve() / "templates"
+    def generate_template_map(cls):
+        template_path = pathlib.Path(taipy.__file__).parent.resolve() / "templates"
 
 
         # Update the template map with the new templates but do not override the existing ones
         # Update the template map with the new templates but do not override the existing ones
         cls._template_map.update(
         cls._template_map.update(

+ 31 - 0
taipy/common/_cli/_create_cli_factory.py

@@ -0,0 +1,31 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# 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.
+
+from importlib import import_module
+from operator import attrgetter
+from typing import Type
+
+from taipy.common._cli._base_cli._abstract_cli import _AbstractCLI
+
+from ...core.common._check_dependencies import EnterpriseEditionUtils
+from ._create_cli import _CreateCLI
+
+
+class _CreateCLIFactory:
+    @staticmethod
+    def _build_cli() -> Type[_AbstractCLI]:
+        if EnterpriseEditionUtils._using_enterprise():
+            module = import_module(EnterpriseEditionUtils._TAIPY_ENTERPRISE_MODULE + ".templates._create_cli")
+            create_cli = attrgetter("_CreateCLI")(module)
+        else:
+            create_cli = _CreateCLI
+
+        return create_cli

+ 8 - 3
taipy/gui/_page.py

@@ -42,8 +42,13 @@ class _Page(object):
             raise RuntimeError(f"Can't render page {self._route}: no renderer found")
             raise RuntimeError(f"Can't render page {self._route}: no renderer found")
         with warnings.catch_warnings(record=True) as w:
         with warnings.catch_warnings(record=True) as w:
             warnings.resetwarnings()
             warnings.resetwarnings()
-            with gui._set_locals_context(self._renderer._get_module_name()):
-                self._rendered_jsx = self._renderer.render(gui)
+            module_name = self._renderer._get_module_name()
+            with gui._set_locals_context(module_name):
+                render_return = self._renderer.render(gui)
+                if isinstance(render_return, tuple):
+                    self._rendered_jsx, module_name = render_return
+                else:
+                    self._rendered_jsx = render_return
             if silent:
             if silent:
                 s = ""
                 s = ""
                 for wm in w:
                 for wm in w:
@@ -78,4 +83,4 @@ class _Page(object):
         if hasattr(self._renderer, "head"):
         if hasattr(self._renderer, "head"):
             self._head = list(self._renderer.head)  # type: ignore
             self._head = list(self._renderer.head)  # type: ignore
         # return renderer module_name from frame
         # return renderer module_name from frame
-        return self._renderer._get_module_name()
+        return module_name

+ 26 - 13
taipy/gui/gui.py

@@ -187,7 +187,7 @@ class Gui:
         env_filename: t.Optional[str] = None,
         env_filename: t.Optional[str] = None,
         libraries: t.Optional[t.List[ElementLibrary]] = None,
         libraries: t.Optional[t.List[ElementLibrary]] = None,
         flask: t.Optional[Flask] = None,
         flask: t.Optional[Flask] = None,
-        script_paths: t.Optional[t.Union[str, Path, t.List[t.Union[str, Path]]]] = None
+        script_paths: t.Optional[t.Union[str, Path, t.List[t.Union[str, Path]]]] = None,
     ):
     ):
         """Initialize a new Gui instance.
         """Initialize a new Gui instance.
 
 
@@ -434,8 +434,12 @@ class Gui:
                         continue
                         continue
                     script_path = Path(script_path)
                     script_path = Path(script_path)
 
 
-                if isinstance(script_path,
-                              Path) and script_path.exists() and script_path.is_file() and script_path.suffix == ".js":
+                if (
+                    isinstance(script_path, Path)
+                    and script_path.exists()
+                    and script_path.is_file()
+                    and script_path.suffix == ".js"
+                ):
                     self.__script_files.append(str(script_path))
                     self.__script_files.append(str(script_path))
                 else:
                 else:
                     _warn(f"Script path '{script_path}' does not exist, is not a file, or is not a JavaScript file.")
                     _warn(f"Script path '{script_path}' does not exist, is not a file, or is not a JavaScript file.")
@@ -506,6 +510,7 @@ class Gui:
                     from plotly.graph_objs import Figure as PlotlyFigure  # type: ignore[reportMissingImports]
                     from plotly.graph_objs import Figure as PlotlyFigure  # type: ignore[reportMissingImports]
 
 
                     if isinstance(content, PlotlyFigure):
                     if isinstance(content, PlotlyFigure):
+
                         def get_plotly_content(figure: PlotlyFigure):
                         def get_plotly_content(figure: PlotlyFigure):
                             return figure.to_html()
                             return figure.to_html()
 
 
@@ -517,6 +522,7 @@ class Gui:
                     from matplotlib.figure import Figure as MatplotlibFigure
                     from matplotlib.figure import Figure as MatplotlibFigure
 
 
                     if isinstance(content, MatplotlibFigure):
                     if isinstance(content, MatplotlibFigure):
+
                         def get_matplotlib_content(figure: MatplotlibFigure):
                         def get_matplotlib_content(figure: MatplotlibFigure):
                             import base64
                             import base64
                             from io import BytesIO
                             from io import BytesIO
@@ -830,12 +836,12 @@ class Gui:
         if var_name.startswith(_TaipyBase._HOLDER_PREFIX):
         if var_name.startswith(_TaipyBase._HOLDER_PREFIX):
             for hp in _TaipyBase._get_holder_prefixes():
             for hp in _TaipyBase._get_holder_prefixes():
                 if var_name.startswith(hp):
                 if var_name.startswith(hp):
-                    var_name = var_name[len(hp):]
+                    var_name = var_name[len(hp) :]
                     break
                     break
         suffix_var_name = ""
         suffix_var_name = ""
         if "." in var_name:
         if "." in var_name:
             first_dot_index = var_name.index(".")
             first_dot_index = var_name.index(".")
-            suffix_var_name = var_name[first_dot_index + 1:]
+            suffix_var_name = var_name[first_dot_index + 1 :]
             var_name = var_name[:first_dot_index]
             var_name = var_name[:first_dot_index]
         var_name_decode, module_name = _variable_decode(self._get_expr_from_hash(var_name))
         var_name_decode, module_name = _variable_decode(self._get_expr_from_hash(var_name))
         current_context = self._get_locals_context()
         current_context = self._get_locals_context()
@@ -1232,7 +1238,8 @@ class Gui:
                             if ret_payload:
                             if ret_payload:
                                 break
                                 break
                         except Exception as e:  # pragma: no cover
                         except Exception as e:  # pragma: no cover
-                            _warn(f"Exception raised in '{lib_name}.get_data({lib_name}, payload, {user_var_name}, value)'", # noqa: E501
+                            _warn(
+                                f"Exception raised in '{lib_name}.get_data({lib_name}, payload, {user_var_name}, value)'",  # noqa: E501
                                 e,
                                 e,
                             )
                             )
             if not isinstance(ret_payload, dict):
             if not isinstance(ret_payload, dict):
@@ -1306,9 +1313,9 @@ class Gui:
             k: v
             k: v
             for k, v in vars(self._get_data_scope()).items()
             for k, v in vars(self._get_data_scope()).items()
             if not k.startswith("_")
             if not k.startswith("_")
-               and not callable(v)
-               and "TpExPr" not in k
-               and not isinstance(v, (ModuleType, FunctionType, LambdaType, type, Page))
+            and not callable(v)
+            and "TpExPr" not in k
+            and not isinstance(v, (ModuleType, FunctionType, LambdaType, type, Page))
         }
         }
         function_data = {
         function_data = {
             k: v
             k: v
@@ -1970,6 +1977,12 @@ class Gui:
                 self.__add_pages_in_folder(child_dir_name, child_dir_path)
                 self.__add_pages_in_folder(child_dir_name, child_dir_path)
 
 
     # Proxy methods for LocalsContext
     # Proxy methods for LocalsContext
+    def _get_locals_context_obj(self) -> _LocalsContext:
+        return self.__locals_context
+
+    def _get_variable_directory_obj(self) -> _VariableDirectory:
+        return self.__var_dir
+
     def _get_locals_bind(self) -> t.Dict[str, t.Any]:
     def _get_locals_bind(self) -> t.Dict[str, t.Any]:
         return self.__locals_context.get_locals()
         return self.__locals_context.get_locals()
 
 
@@ -2055,7 +2068,7 @@ class Gui:
             raise Exception("name is required for add_page() function.")
             raise Exception("name is required for add_page() function.")
         if not Gui.__RE_PAGE_NAME.match(name):  # pragma: no cover
         if not Gui.__RE_PAGE_NAME.match(name):  # pragma: no cover
             raise SyntaxError(
             raise SyntaxError(
-                f'Page name "{name}" is invalid. It must only contain letters, digits, dash (-), underscore (_), and forward slash (/) characters.' # noqa: E501
+                f'Page name "{name}" is invalid. It must only contain letters, digits, dash (-), underscore (_), and forward slash (/) characters.'  # noqa: E501
             )
             )
         if name.startswith("/"):  # pragma: no cover
         if name.startswith("/"):  # pragma: no cover
             raise SyntaxError(f'Page name "{name}" cannot start with forward slash (/) character.')
             raise SyntaxError(f'Page name "{name}" cannot start with forward slash (/) character.')
@@ -2254,7 +2267,7 @@ class Gui:
                 self._bind(encoded_var_name, bind_locals[var_name])
                 self._bind(encoded_var_name, bind_locals[var_name])
             else:
             else:
                 _warn(
                 _warn(
-                    f"Variable '{var_name}' is not available in either the '{self._get_locals_context()}' or '__main__' modules." # noqa: E501
+                    f"Variable '{var_name}' is not available in either the '{self._get_locals_context()}' or '__main__' modules."  # noqa: E501
                 )
                 )
         return encoded_var_name
         return encoded_var_name
 
 
@@ -2623,7 +2636,7 @@ class Gui:
                 _warn(f"Using webapp_path: '{_conf_webapp_path}'.")
                 _warn(f"Using webapp_path: '{_conf_webapp_path}'.")
             else:  # pragma: no cover
             else:  # pragma: no cover
                 _warn(
                 _warn(
-                    f"webapp_path: '{_conf_webapp_path}' is not a valid directory. Falling back to '{_webapp_path}'." # noqa: E501
+                    f"webapp_path: '{_conf_webapp_path}' is not a valid directory. Falling back to '{_webapp_path}'."  # noqa: E501
                 )
                 )
         return _webapp_path
         return _webapp_path
 
 
@@ -2938,7 +2951,7 @@ class Gui:
                             glob_ctx[lib_context[0]] = lib_context[1]
                             glob_ctx[lib_context[0]] = lib_context[1]
                     elif lib_context:
                     elif lib_context:
                         _warn(
                         _warn(
-                            f"Method {name}.on_init() should return a Tuple[str, Any] where the first element must be a valid Python identifier." # noqa: E501
+                            f"Method {name}.on_init() should return a Tuple[str, Any] where the first element must be a valid Python identifier."  # noqa: E501
                         )
                         )
                 except Exception as e:  # pragma: no cover
                 except Exception as e:  # pragma: no cover
                     if not self._call_on_exception(f"{name}.on_init", e):
                     if not self._call_on_exception(f"{name}.on_init", e):

+ 26 - 11
taipy/gui/utils/_variable_directory.py

@@ -25,6 +25,8 @@ class _VariableDirectory:
         self._var_dir: t.Dict[str, t.Dict] = {}
         self._var_dir: t.Dict[str, t.Dict] = {}
         self._var_head: t.Dict[str, t.List[t.Tuple[str, str]]] = {}
         self._var_head: t.Dict[str, t.List[t.Tuple[str, str]]] = {}
         self._imported_var_dir: t.Dict[str, t.List[t.Tuple[str, str, str]]] = {}
         self._imported_var_dir: t.Dict[str, t.List[t.Tuple[str, str, str]]] = {}
+        self._pre_processed_module: t.Set[str] = set()
+        self._processed_module: t.Set[str] = set()
 
 
     def set_default(self, frame: FrameType) -> None:
     def set_default(self, frame: FrameType) -> None:
         self._default_module = _get_module_name_from_frame(frame)
         self._default_module = _get_module_name_from_frame(frame)
@@ -39,7 +41,10 @@ class _VariableDirectory:
             self._imported_var_dir[t.cast(str, module_name)] = imported_var_list
             self._imported_var_dir[t.cast(str, module_name)] = imported_var_list
 
 
     def pre_process_module_import_all(self) -> None:
     def pre_process_module_import_all(self) -> None:
-        for imported_dir in self._imported_var_dir.values():
+        for base_module, imported_dir in self._imported_var_dir.items():
+            # Skip pre process for modules that have been processed
+            if base_module in self._pre_processed_module:
+                continue
             additional_var_list: t.List[t.Tuple[str, str, str]] = []
             additional_var_list: t.List[t.Tuple[str, str, str]] = []
             for name, asname, module in imported_dir:
             for name, asname, module in imported_dir:
                 if name != "*" or asname != "*":
                 if name != "*" or asname != "*":
@@ -51,21 +56,28 @@ class _VariableDirectory:
                         (v, v, module) for v in self._locals_context.get_locals().keys() if not v.startswith("_")
                         (v, v, module) for v in self._locals_context.get_locals().keys() if not v.startswith("_")
                     )
                     )
             imported_dir.extend(additional_var_list)
             imported_dir.extend(additional_var_list)
+            # Save the pre-processed module
+            self._pre_processed_module.add(base_module)
 
 
     def process_imported_var(self) -> None:
     def process_imported_var(self) -> None:
         self.pre_process_module_import_all()
         self.pre_process_module_import_all()
-        default_imported_dir = self._imported_var_dir[t.cast(str, self._default_module)]
-        with self._locals_context.set_locals_context(self._default_module):
-            for name, asname, module in default_imported_dir:
-                if name == "*" and asname == "*":
-                    continue
-                imported_module_name = _get_module_name_from_imported_var(
-                    name, self._locals_context.get_locals().get(asname, None), module
-                )
-                temp_var_name = self.add_var(asname, self._default_module)
-                self.add_var(name, imported_module_name, temp_var_name)
+        default_module = t.cast(str, self._default_module)
+        if default_module not in self._processed_module:
+            default_imported_dir = self._imported_var_dir[default_module]
+            with self._locals_context.set_locals_context(self._default_module):
+                for name, asname, module in default_imported_dir:
+                    if name == "*" and asname == "*":
+                        continue
+                    imported_module_name = _get_module_name_from_imported_var(
+                        name, self._locals_context.get_locals().get(asname, None), module
+                    )
+                    temp_var_name = self.add_var(asname, self._default_module)
+                    self.add_var(name, imported_module_name, temp_var_name)
+            self._processed_module.add(default_module)
 
 
         for k, v in self._imported_var_dir.items():
         for k, v in self._imported_var_dir.items():
+            if k in self._processed_module:
+                continue
             with self._locals_context.set_locals_context(k):
             with self._locals_context.set_locals_context(k):
                 for name, asname, module in v:
                 for name, asname, module in v:
                     if name == "*" and asname == "*":
                     if name == "*" and asname == "*":
@@ -82,6 +94,7 @@ class _VariableDirectory:
                         self.add_var(asname, k, var_name)
                         self.add_var(asname, k, var_name)
                     else:
                     else:
                         self.add_var(name, imported_module_name, var_asname)
                         self.add_var(name, imported_module_name, var_asname)
+            self._processed_module.add(k)
 
 
     def add_var(self, name: str, module: t.Optional[str], var_name: t.Optional[str] = None) -> str:
     def add_var(self, name: str, module: t.Optional[str], var_name: t.Optional[str] = None) -> str:
         if module is None:
         if module is None:
@@ -119,9 +132,11 @@ _MODULE_NAME_MAP: t.List[str] = []
 _MODULE_ID = "_TPMDL_"
 _MODULE_ID = "_TPMDL_"
 _RE_TPMDL_DECODE = re.compile(r"(.*?)" + _MODULE_ID + r"(\d+)$")
 _RE_TPMDL_DECODE = re.compile(r"(.*?)" + _MODULE_ID + r"(\d+)$")
 
 
+
 def _is_moduled_variable(var_name: str):
 def _is_moduled_variable(var_name: str):
     return _MODULE_ID in var_name
     return _MODULE_ID in var_name
 
 
+
 def _variable_encode(var_name: str, module_name: t.Optional[str]):
 def _variable_encode(var_name: str, module_name: t.Optional[str]):
     if module_name is None:
     if module_name is None:
         return var_name
         return var_name

+ 7 - 11
taipy/templates/default/hooks/post_gen_project.py

@@ -77,33 +77,29 @@ def handle_single_page_app():
         main_file.write("\n")
         main_file.write("\n")
         main_file.write("    gui = Gui(page=page)\n")
         main_file.write("    gui = Gui(page=page)\n")
 
 
+    with open(os.path.join(os.getcwd(), "sections", "import.txt"), "a") as import_file:
+        import_file.write("import taipy.gui.builder as tgb\n")
+
     handle_run_service()
     handle_run_service()
 
 
     with open(os.path.join(os.getcwd(), "sections", "page_content.txt"), "a") as page_content_file:
     with open(os.path.join(os.getcwd(), "sections", "page_content.txt"), "a") as page_content_file:
         page_content_file.write(
         page_content_file.write(
-            '''
-page = """
-<center>
-<|navbar|lov={[("home", "Homepage")]}|>
-</center>
+            """
+with tgb.Page() as page:
+    tgb.navbar(lov="{[('home', 'Homepage')]}")
 
 
 """
 """
-'''
         )
         )
 
 
 
 
 def handle_multi_page_app(pages):
 def handle_multi_page_app(pages):
     for page_name in pages:
     for page_name in pages:
         os.mkdir(os.path.join(os.getcwd(), "pages", page_name))
         os.mkdir(os.path.join(os.getcwd(), "pages", page_name))
-        with open(os.path.join(os.getcwd(), "pages", "page_example", "page_example.md"), "r") as page_md_file:
-            page_md_content = page_md_file.read()
-        page_md_content = page_md_content.replace("Page example", page_name.replace("_", " ").title())
-        with open(os.path.join(os.getcwd(), "pages", page_name, page_name + ".md"), "w") as page_md_file:
-            page_md_file.write(page_md_content)
 
 
         with open(os.path.join(os.getcwd(), "pages", "page_example", "page_example.py"), "r") as page_content_file:
         with open(os.path.join(os.getcwd(), "pages", "page_example", "page_example.py"), "r") as page_content_file:
             page_py_content = page_content_file.read()
             page_py_content = page_content_file.read()
         page_py_content = page_py_content.replace("page_example", page_name)
         page_py_content = page_py_content.replace("page_example", page_name)
+        page_py_content = page_py_content.replace("Page example", page_name.replace("_", " ").title())
         with open(os.path.join(os.getcwd(), "pages", page_name, page_name + ".py"), "w") as page_content_file:
         with open(os.path.join(os.getcwd(), "pages", page_name, page_name + ".py"), "w") as page_content_file:
             page_content_file.write(page_py_content)
             page_content_file.write(page_py_content)
 
 

+ 0 - 1
taipy/templates/default/{{cookiecutter.__root_folder}}/pages/page_example/page_example.md

@@ -1 +0,0 @@
-# Page example

+ 5 - 4
taipy/templates/default/{{cookiecutter.__root_folder}}/pages/page_example/page_example.py

@@ -11,11 +11,12 @@
 
 
 """
 """
 A page of the application.
 A page of the application.
-Page content is imported from the page_example.md file.
+Page content is built using the Page builder API.
 
 
-Please refer to https://docs.taipy.io/en/latest/manuals/userman/gui/pages for more details.
+Please refer to https://docs.taipy.io/en/latest/userman/gui/pages/builder/ for more details.
 """
 """
 
 
-from taipy.gui import Markdown
+import taipy.gui.builder as tgb
 
 
-page_example = Markdown("pages/page_example/page_example.md")
+with tgb.Page() as page_example:
+    tgb.text("# Page example", mode="md")

+ 0 - 3
taipy/templates/default/{{cookiecutter.__root_folder}}/pages/root.md

@@ -1,3 +0,0 @@
-<center>
-<|navbar|>
-</center>

+ 5 - 4
taipy/templates/default/{{cookiecutter.__root_folder}}/pages/root.py

@@ -11,11 +11,12 @@
 
 
 """
 """
 The root page of the application.
 The root page of the application.
-Page content is imported from the root.md file.
+Page content is built using the Page builder API.
 
 
-Please refer to https://docs.taipy.io/en/latest/manuals/userman/gui/pages for more details.
+Please refer to https://docs.taipy.io/en/latest/userman/gui/pages/builder/ for more details.
 """
 """
 
 
-from taipy.gui import Markdown
+import taipy.gui.builder as tgb
 
 
-root_page = Markdown("pages/root.md")
+with tgb.Page() as root_page:
+    tgb.navbar()

+ 0 - 1
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/job_page/job_page.md

@@ -1 +0,0 @@
-<|job_selector|>

+ 3 - 2
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/job_page/job_page.py

@@ -9,6 +9,7 @@
 # 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.
 
 
-from taipy.gui import Markdown
+import taipy.gui.builder as tgb
 
 
-job_page = Markdown("pages/job_page/job_page.md")
+with tgb.Page() as job_page:
+    tgb.job_selector()

+ 0 - 21
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/root.md

@@ -1,21 +0,0 @@
-<|layout|columns=1 5|
-
-<|sidebar|
-
-<|{selected_scenario}|scenario_selector|>
-
-<|part|render={selected_scenario}|
-<|{selected_data_node}|data_node_selector|not display_cycles|>
-|>
-|>
-
-<|part|class_name=main|
-
-<|navbar|>
-
-<|part|class_name=main|
-<|content|>
-|>
-
-|>
-|>

+ 15 - 2
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/root.py

@@ -9,10 +9,23 @@
 # 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.
 
 
-from taipy.gui import Markdown
+import taipy.gui.builder as tgb
 
 
 selected_scenario = None
 selected_scenario = None
 selected_data_node = None
 selected_data_node = None
 content = ""
 content = ""
 
 
-root = Markdown("pages/root.md")
+
+with tgb.Page() as root:
+    with tgb.layout(columns="1, 5"):
+        with tgb.part(class_name="sidebar"):
+            tgb.scenario_selector("{selected_scenario}")
+
+            with tgb.part(render="{selected_scenario}"):
+                tgb.data_node_selector("{selected_data_node}", display_cycles=False)
+
+        with tgb.part(class_name="main"):
+            tgb.navbar()
+
+            with tgb.part(class_name="main"):
+                tgb.text("{content}")

+ 0 - 12
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/scenario_page/scenario_page.md

@@ -1,12 +0,0 @@
-<|layout|columns=1 1|
-
-<|part|render={selected_scenario}|
-
-<|{selected_scenario}|scenario|not expandable|expanded|on_submission_change=notify_on_submission|>
-
-<|{selected_scenario}|scenario_dag|>
-|>
-
-<|part|partial={data_node_partial}|render={selected_data_node}|>
-
-|>

+ 15 - 2
taipy/templates/sdm/{{cookiecutter.__root_folder}}/pages/scenario_page/scenario_page.py

@@ -9,7 +9,8 @@
 # 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.
 
 
-from taipy.gui import Markdown, notify
+import taipy.gui.builder as tgb
+from taipy.gui import notify
 
 
 from .data_node_management import manage_partial
 from .data_node_management import manage_partial
 
 
@@ -27,4 +28,16 @@ def manage_data_node_partial(state):
     manage_partial(state)
     manage_partial(state)
 
 
 
 
-scenario_page = Markdown("pages/scenario_page/scenario_page.md")
+with tgb.Page() as scenario_page:
+    with tgb.layout(columns="1, 1"):
+        with tgb.part(render="{selected_scenario}"):
+            tgb.scenario(
+                "{selected_scenario}",
+                expandable=False,
+                expanded=True,
+                on_submission_change=notify_on_submission,
+            )
+
+            tgb.scenario_dag("{selected_scenario}")
+
+        tgb.part(partial="{data_node_partial}", render="{selected_data_node}")

+ 29 - 0
tests/gui/gui_specific/state_asset/page2.py

@@ -0,0 +1,29 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# 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.
+
+from taipy.gui import Markdown
+
+b = 20
+
+
+def get_b(state):
+    return state.b
+
+
+def set_b(state, val):
+    state.b = val
+
+
+md_page2 = Markdown(
+    """
+<|{b}|>
+"""
+)

+ 36 - 0
tests/gui/gui_specific/test_variable_directory.py

@@ -0,0 +1,36 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# 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.
+
+
+from taipy.gui import Gui
+from taipy.gui.utils import _LocalsContext, _VariableDirectory
+
+from .state_asset.page1 import md_page1
+from .state_asset.page2 import md_page2
+
+
+def test_variable_directory_dyanmic_process(gui: Gui):
+    gui.run(run_server=False)
+    with gui.get_flask_app().app_context():
+        locals_context = _LocalsContext()
+        variable_directory = _VariableDirectory(locals_context)
+        page1_module = str(md_page1._get_module_name())
+        page2_module = str(md_page2._get_module_name())
+        locals_context.add(page1_module, md_page1._get_locals())
+        variable_directory.set_default(md_page1._get_frame())  # type: ignore
+        variable_directory.process_imported_var()
+        assert page1_module in variable_directory._pre_processed_module
+        assert page1_module in variable_directory._processed_module
+        locals_context.add(page2_module, md_page2._get_locals())
+        variable_directory.add_frame(md_page2._get_frame())
+        variable_directory.process_imported_var()
+        assert page2_module in variable_directory._pre_processed_module
+        assert page2_module in variable_directory._processed_module

+ 1 - 1
tests/templates/test_default_template.py

@@ -145,7 +145,7 @@ def test_multipage_gui_template(tmpdir):
 
 
     assert sorted(os.listdir(os.path.join(tmpdir, "foo_app"))) == sorted(["requirements.txt", "main.py", "pages"])
     assert sorted(os.listdir(os.path.join(tmpdir, "foo_app"))) == sorted(["requirements.txt", "main.py", "pages"])
     assert sorted(os.listdir(os.path.join(tmpdir, "foo_app", "pages"))) == sorted(
     assert sorted(os.listdir(os.path.join(tmpdir, "foo_app", "pages"))) == sorted(
-        ["name_1", "name_2", "name_3", "root.md", "root.py", "__init__.py"]
+        ["name_1", "name_2", "name_3", "root.py", "__init__.py"]
     )
     )
 
 
     taipy_path = os.getcwd()
     taipy_path = os.getcwd()