Browse Source

Hot loading asset folder on dev (#643)

PeterYusuke 2 năm trước cách đây
mục cha
commit
6e74cb00a3
4 tập tin đã thay đổi với 156 bổ sung7 xóa
  1. 48 7
      poetry.lock
  2. 14 0
      pynecone/utils.py
  3. 93 0
      pynecone/watch.py
  4. 1 0
      pyproject.toml

+ 48 - 7
poetry.lock

@@ -798,14 +798,14 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pyright"
-version = "1.1.296"
+version = "1.1.297"
 description = "Command line wrapper for pyright"
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pyright-1.1.296-py3-none-any.whl", hash = "sha256:51cc5f05807b1fb53f9f0e14736b8f772b500a3ba4e0edeb99727e68e700d9ea"},
-    {file = "pyright-1.1.296.tar.gz", hash = "sha256:6c3cd394473e55a516ebe443d02b83e63456ef29f052dcf8e64e7875c1418fa6"},
+    {file = "pyright-1.1.297-py3-none-any.whl", hash = "sha256:3fd6528280eb649f8b64b7ece55299f01e340d29f4cf257da876957e3ee24062"},
+    {file = "pyright-1.1.297.tar.gz", hash = "sha256:89082de2fbd240fa75767b57824f4d8516f2fb9005047265a67b895547c6272f"},
 ]
 
 [package.dependencies]
@@ -1043,14 +1043,14 @@ files = [
 
 [[package]]
 name = "setuptools"
-version = "67.4.0"
+version = "67.5.1"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"},
-    {file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"},
+    {file = "setuptools-67.5.1-py3-none-any.whl", hash = "sha256:1c39d42bda4cb89f7fdcad52b6762e3c309ec8f8715b27c684176b7d71283242"},
+    {file = "setuptools-67.5.1.tar.gz", hash = "sha256:15136a251127da2d2e77ac7a1bc231eb504654f7e3346d93613a13f2e2787535"},
 ]
 
 [package.extras]
@@ -1335,6 +1335,47 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 [package.extras]
 standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
 
+[[package]]
+name = "watchdog"
+version = "2.3.1"
+description = "Filesystem events monitoring"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "watchdog-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1f1200d4ec53b88bf04ab636f9133cb703eb19768a39351cee649de21a33697"},
+    {file = "watchdog-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:564e7739abd4bd348aeafbf71cc006b6c0ccda3160c7053c4a53b67d14091d42"},
+    {file = "watchdog-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95ad708a9454050a46f741ba5e2f3468655ea22da1114e4c40b8cbdaca572565"},
+    {file = "watchdog-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a073c91a6ef0dda488087669586768195c3080c66866144880f03445ca23ef16"},
+    {file = "watchdog-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa8b028750b43e80eea9946d01925168eeadb488dfdef1d82be4b1e28067f375"},
+    {file = "watchdog-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964fd236cd443933268ae49b59706569c8b741073dbfd7ca705492bae9d39aab"},
+    {file = "watchdog-2.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:91fd146d723392b3e6eb1ac21f122fcce149a194a2ba0a82c5e4d0ee29cd954c"},
+    {file = "watchdog-2.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efe3252137392a471a2174d721e1037a0e6a5da7beb72a021e662b7000a9903f"},
+    {file = "watchdog-2.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85bf2263290591b7c5fa01140601b64c831be88084de41efbcba6ea289874f44"},
+    {file = "watchdog-2.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f2df370cd8e4e18499dd0bfdef476431bcc396108b97195d9448d90924e3131"},
+    {file = "watchdog-2.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ea5d86d1bcf4a9d24610aa2f6f25492f441960cf04aed2bd9a97db439b643a7b"},
+    {file = "watchdog-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f5d0f7eac86807275eba40b577c671b306f6f335ba63a5c5a348da151aba0fc"},
+    {file = "watchdog-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b848c71ef2b15d0ef02f69da8cc120d335cec0ed82a3fa7779e27a5a8527225"},
+    {file = "watchdog-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d9878be36d2b9271e3abaa6f4f051b363ff54dbbe7e7df1af3c920e4311ee43"},
+    {file = "watchdog-2.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cd61f98cb37143206818cb1786d2438626aa78d682a8f2ecee239055a9771d5"},
+    {file = "watchdog-2.3.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3d2dbcf1acd96e7a9c9aefed201c47c8e311075105d94ce5e899f118155709fd"},
+    {file = "watchdog-2.3.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03f342a9432fe08107defbe8e405a2cb922c5d00c4c6c168c68b633c64ce6190"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7a596f9415a378d0339681efc08d2249e48975daae391d58f2e22a3673b977cf"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:0e1dd6d449267cc7d6935d7fe27ee0426af6ee16578eed93bacb1be9ff824d2d"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_i686.whl", hash = "sha256:7a1876f660e32027a1a46f8a0fa5747ad4fcf86cb451860eae61a26e102c8c79"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:2caf77ae137935c1466f8cefd4a3aec7017b6969f425d086e6a528241cba7256"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:53f3e95081280898d9e4fc51c5c69017715929e4eea1ab45801d5e903dd518ad"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:9da7acb9af7e4a272089bd2af0171d23e0d6271385c51d4d9bde91fe918c53ed"},
+    {file = "watchdog-2.3.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8a4d484e846dcd75e96b96d80d80445302621be40e293bfdf34a631cab3b33dc"},
+    {file = "watchdog-2.3.1-py3-none-win32.whl", hash = "sha256:a74155398434937ac2780fd257c045954de5b11b5c52fc844e2199ce3eecf4cf"},
+    {file = "watchdog-2.3.1-py3-none-win_amd64.whl", hash = "sha256:5defe4f0918a2a1a4afbe4dbb967f743ac3a93d546ea4674567806375b024adb"},
+    {file = "watchdog-2.3.1-py3-none-win_ia64.whl", hash = "sha256:4109cccf214b7e3462e8403ab1e5b17b302ecce6c103eb2fc3afa534a7f27b96"},
+    {file = "watchdog-2.3.1.tar.gz", hash = "sha256:d9f9ed26ed22a9d331820a8432c3680707ea8b54121ddcc9dc7d9f2ceeb36906"},
+]
+
+[package.extras]
+watchmedo = ["PyYAML (>=3.10)"]
+
 [[package]]
 name = "websockets"
 version = "10.4"
@@ -1433,4 +1474,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.7"
-content-hash = "5ca32932250a2a3f00c95b0bdd77d9702b82e951958ee5ca29180f11174ac8e4"
+content-hash = "0ed6ff121b610ef2f2993889abbea9b3caa231f92964ab2fdc70667a814ad630"

+ 14 - 0
pynecone/utils.py

@@ -43,6 +43,7 @@ from rich.prompt import Prompt
 
 from pynecone import constants
 from pynecone.base import Base
+from pynecone.watch import AssetFolderWatch
 
 if TYPE_CHECKING:
     from pynecone.app import App
@@ -590,6 +591,16 @@ def posix_export(backend: bool = True, frontend: bool = True):
         os.system(cmd)
 
 
+def start_watching_assets_folder(root):
+    """Start watching assets folder.
+
+    Args:
+        root: root path of the project.
+    """
+    asset_watch = AssetFolderWatch(root)
+    asset_watch.start()
+
+
 def setup_frontend(root: Path):
     """Set up the frontend.
 
@@ -622,6 +633,9 @@ def run_frontend(app: App, root: Path, port: str):
     # Set up the frontend.
     setup_frontend(root)
 
+    # start watching asset folder
+    start_watching_assets_folder(root)
+
     # Compile the frontend.
     app.compile(force_compile=True)
 

+ 93 - 0
pynecone/watch.py

@@ -0,0 +1,93 @@
+"""General utility functions."""
+
+import contextlib
+import os
+import shutil
+import time
+
+from watchdog.events import FileSystemEvent, FileSystemEventHandler
+from watchdog.observers import Observer
+
+from pynecone.constants import APP_ASSETS_DIR, WEB_ASSETS_DIR
+
+
+class AssetFolderWatch:
+    """Asset folder watch class."""
+
+    def __init__(self, root):
+        """Initialize the Watch Class.
+
+        Args:
+            root: root path of the public.
+        """
+        self.path = str(root / APP_ASSETS_DIR)
+        self.event_handler = AssetFolderHandler(root)
+
+    def start(self):
+        """Start watching asset folder."""
+        self.observer = Observer()
+        self.observer.schedule(self.event_handler, self.path, recursive=True)
+        self.observer.start()
+
+
+class AssetFolderHandler(FileSystemEventHandler):
+    """Asset folder event handler."""
+
+    def __init__(self, root):
+        """Initialize the AssetFolderHandler Class.
+
+        Args:
+            root: root path of the public.
+        """
+        super().__init__()
+        self.root = root
+
+    def on_modified(self, event: FileSystemEvent):
+        """Event handler when a file or folder was modified.
+        This is called every time after a file is created, modified and deleted.
+
+        Args:
+            event: Event information.
+        """
+        dest_path = self.get_dest_path(event.src_path)
+
+        # wait 1 sec for fully saved
+        time.sleep(1)
+
+        if os.path.isfile(event.src_path):
+            with contextlib.suppress(PermissionError):
+                shutil.copyfile(event.src_path, dest_path)
+        if os.path.isdir(event.src_path):
+            if os.path.exists(dest_path):
+                shutil.rmtree(dest_path)
+            with contextlib.suppress(PermissionError):
+                shutil.copytree(event.src_path, dest_path)
+
+    def on_deleted(self, event: FileSystemEvent):
+        """Event hander when a file or folder was deleted.
+
+        Args:
+            event: Event infomation.
+        """
+        dest_path = self.get_dest_path(event.src_path)
+
+        if os.path.isfile(dest_path):
+            # when event is about a file, pass
+            # this will be deleted at on_modified function
+            return
+
+        if os.path.exists(dest_path):
+            shutil.rmtree(dest_path)
+
+    def get_dest_path(self, src_path: str) -> str:
+        """Get public file path.
+
+        Args:
+            src_path: The asset file path.
+
+        Returns:
+            The public file path.
+        """
+        return src_path.replace(
+            str(self.root / APP_ASSETS_DIR), str(self.root / WEB_ASSETS_DIR)
+        )

+ 1 - 0
pyproject.toml

@@ -39,6 +39,7 @@ psutil = "^5.9.4"
 websockets = "^10.4"
 cloudpickle = "^2.2.1"
 python-multipart = "^0.0.5"
+watchdog = "^2.3.1"
 
 [tool.poetry.group.dev.dependencies]
 pytest = "^7.1.2"