Bladeren bron

Support file reloading on notebook (#460) (#970)

* Support file reloading on notebook

* remove uncomitted file

* fix lint

* fix ruff again

* per Fabien and add dependency
Dinh Long Nguyen 1 jaar geleden
bovenliggende
commit
3aedfcbead

+ 1 - 0
Pipfile

@@ -34,6 +34,7 @@ toml = "==0.10"
 twisted = "==23.8.0"
 tzlocal = "==3.0"
 boto3 = "==1.29.1"
+watchdog = "==4.0.0"
 
 [dev-packages]
 freezegun = "*"

+ 18 - 2
taipy/gui/_renderers/__init__.py

@@ -18,7 +18,8 @@ from ..utils import _is_in_notebook, _varname_from_content
 from ._html import _TaipyHTMLParser
 
 if t.TYPE_CHECKING:
-    from ..builder._element import _Element
+    from watchdog.observers import BaseObserverSubclassCallable
+
     from ..gui import Gui
 
 
@@ -41,6 +42,7 @@ class _Renderer(Page, ABC):
         self._content = ""
         self._base_element: t.Optional[_Element] = None
         self._filepath = ""
+        self._observer: t.Optional["BaseObserverSubclassCallable"] = None
         if isinstance(content, str):
             self.__process_content(content)
         elif isinstance(content, _Element):
@@ -52,7 +54,11 @@ class _Renderer(Page, ABC):
 
     def __process_content(self, content: str) -> None:
         if path.exists(content) and path.isfile(content):
-            return self.__parse_file_content(content)
+            self.__parse_file_content(content)
+            # Watchdog observer: watch for file changes
+            if _is_in_notebook() and self._observer is None:
+                self.__observe_file_change(content)
+            return
         if self._frame is not None:
             frame_dir_path = path.dirname(path.abspath(self._frame.f_code.co_filename))
             content_path = path.join(frame_dir_path, content)
@@ -60,6 +66,16 @@ class _Renderer(Page, ABC):
                 return self.__parse_file_content(content_path)
         self._content = content
 
+    def __observe_file_change(self, file_path: str):
+        from watchdog.observers import Observer
+
+        from .utils import FileWatchdogHandler
+
+        self._observer = Observer()
+        file_path = path.abspath(file_path)
+        self._observer.schedule(FileWatchdogHandler(file_path, self), path.dirname(file_path), recursive=False)
+        self._observer.start()
+
     def __parse_file_content(self, content):
         with open(t.cast(str, content), "r") as f:
             self._content = f.read()

+ 25 - 0
taipy/gui/_renderers/utils.py

@@ -9,14 +9,22 @@
 # 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 datetime
 import typing as t
+from pathlib import Path
 
 import pandas as pd
+from watchdog.events import FileSystemEventHandler
+
+from taipy.logger._taipy_logger import _TaipyLogger
 
 from .._warnings import _warn
 from ..types import NumberTypes
 from ..utils import _RE_PD_TYPE, _get_date_col_str_name, _MapDict
 
+if t.TYPE_CHECKING:
+    from . import _Renderer
+
 
 def _add_to_dict_and_get(dico: t.Dict[str, t.Any], key: str, value: t.Any) -> t.Any:
     if key not in dico.keys():
@@ -117,3 +125,20 @@ def _get_columns_dict(  # noqa: C901
             elif number_format and ctype in NumberTypes:
                 _add_to_dict_and_get(col_dict[col], "format", number_format)
     return col_dict
+
+
+class FileWatchdogHandler(FileSystemEventHandler):
+    def __init__(self, file_path: str, renderer: "_Renderer") -> None:
+        self._file_path = file_path
+        self._renderer = renderer
+        self._last_modified = datetime.datetime.now()
+
+    def on_modified(self, event):
+        if datetime.datetime.now() - self._last_modified < datetime.timedelta(seconds=1):
+            return
+        self._last_modified = datetime.datetime.now()
+        if Path(event.src_path).resolve() == Path(self._file_path).resolve():
+            self._renderer.set_content(self._file_path)
+            _TaipyLogger._get_logger().info(
+                f"File '{self._file_path}' has been modified. Reload your page to see the changes."
+            )

+ 3 - 4
taipy/gui/page.py

@@ -17,9 +17,6 @@ from types import FrameType
 
 from .utils import _filter_locals, _get_module_name_from_frame
 
-if t.TYPE_CHECKING:
-    from ._renderers import _Element  # noqa: F401
-
 
 class Page:
     """Generic page generator.
@@ -88,7 +85,9 @@ class Page:
         return (
             self._class_locals
             if self._is_class_module()
-            else None if (frame := self._get_frame()) is None else _filter_locals(frame.f_locals)
+            else None
+            if (frame := self._get_frame()) is None
+            else _filter_locals(frame.f_locals)
         )
 
     def _is_class_module(self):

+ 1 - 0
tools/packages/pipfiles/Pipfile3.10.max

@@ -79,3 +79,4 @@ version = "==4.2.13"
 "marshmallow" = {version="==3.20.2"}
 "apispec" = {version="==6.4.0", extras=["yaml"]}
 "apispec-webframeworks" = {version="==1.0.0"}
+"watchdog" = {version="==4.0.0"}

+ 1 - 0
tools/packages/pipfiles/Pipfile3.11.max

@@ -79,3 +79,4 @@ version = "==4.2.13"
 "marshmallow" = {version="==3.20.2"}
 "apispec" = {version="==6.4.0", extras=["yaml"]}
 "apispec-webframeworks" = {version="==1.0.0"}
+"watchdog" = {version="==4.0.0"}

+ 1 - 0
tools/packages/pipfiles/Pipfile3.12.max

@@ -79,3 +79,4 @@ version = "==4.2.13"
 "marshmallow" = {version="==3.20.2"}
 "apispec" = {version="==6.4.0", extras=["yaml"]}
 "apispec-webframeworks" = {version="==1.0.0"}
+"watchdog" = {version="==4.0.0"}

+ 1 - 0
tools/packages/pipfiles/Pipfile3.8.max

@@ -79,3 +79,4 @@ version = "==4.2.13"
 "marshmallow" = {version="==3.20.2"}
 "apispec" = {version="==6.4.0", extras=["yaml"]}
 "apispec-webframeworks" = {version="==1.0.0"}
+"watchdog" = {version="==4.0.0"}

+ 1 - 0
tools/packages/pipfiles/Pipfile3.9.max

@@ -79,3 +79,4 @@ version = "==4.2.13"
 "marshmallow" = {version="==3.20.2"}
 "apispec" = {version="==6.4.0", extras=["yaml"]}
 "apispec-webframeworks" = {version="==1.0.0"}
+"watchdog" = {version="==4.0.0"}

+ 1 - 0
tools/packages/taipy-gui/setup.requirements.txt

@@ -14,3 +14,4 @@ simple-websocket>=0.10.1,<=1.0.0
 taipy-config
 twisted>=23.8.0,<=23.10.0
 tzlocal>=3.0,<=5.2
+watchdog>=4.0.0,<=4.0.0