From c6dd0df0aebaee31056ea2cd20884bc001f4b3a1 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 15 Apr 2022 12:35:59 +0200 Subject: [PATCH] Add watchfiles attribute to contents manager --- jupyter_server/services/contents/fileio.py | 1 - .../services/contents/filemanager.py | 5 +++++ jupyter_server/services/contents/manager.py | 21 +++++++++++++++++++ setup.cfg | 2 +- tests/services/contents/test_fileio.py | 19 +---------------- .../contents/test_largefilemanager.py | 19 +++++++++++++++++ 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/jupyter_server/services/contents/fileio.py b/jupyter_server/services/contents/fileio.py index bba4d67a9d..d01bfd16dc 100644 --- a/jupyter_server/services/contents/fileio.py +++ b/jupyter_server/services/contents/fileio.py @@ -15,7 +15,6 @@ from tornado.web import HTTPError from traitlets import Bool from traitlets.config import Configurable -from watchfiles import * # noqa from jupyter_server.utils import to_api_path, to_os_path diff --git a/jupyter_server/services/contents/filemanager.py b/jupyter_server/services/contents/filemanager.py index 88aa0e3620..b18a19efd9 100644 --- a/jupyter_server/services/contents/filemanager.py +++ b/jupyter_server/services/contents/filemanager.py @@ -35,6 +35,8 @@ class FileContentsManager(FileManagerMixin, ContentsManager): + import watchfiles + root_dir = Unicode(config=True) @default("root_dir") @@ -546,6 +548,9 @@ def get_kernel_path(self, path, model=None): class AsyncFileContentsManager(FileContentsManager, AsyncFileManagerMixin, AsyncContentsManager): + + import watchfiles + @default("checkpoints_class") def _checkpoints_class_default(self): return AsyncFileCheckpoints diff --git a/jupyter_server/services/contents/manager.py b/jupyter_server/services/contents/manager.py index 7bd6450803..e4dd4b4b7f 100644 --- a/jupyter_server/services/contents/manager.py +++ b/jupyter_server/services/contents/manager.py @@ -34,6 +34,13 @@ copy_pat = re.compile(r"\-Copy\d*\.") +class NoWatchfilesAPI: + pass + + +NOWATCHFILESAPI = NoWatchfilesAPI() + + class ContentsManager(LoggingConfigurable): """Base class for serving files and directories. @@ -409,6 +416,20 @@ def rename_file(self, old_path, new_path): # ContentsManager API part 2: methods that have useable default # implementations, but can be overridden in subclasses. + @property + def watchfiles(self): + """File system change notifyer + + Override this method in subclasses if the file system supports change notifications. + + Returns + ------- + api : class + The supported API for file system change notifications. Loosely follows the API of the + watchfiles Python package (can be a subset of it). + """ + return NOWATCHFILESAPI + def delete(self, path): """Delete a file/directory and any associated checkpoints.""" path = path.strip("/") diff --git a/setup.cfg b/setup.cfg index 2efdd71469..db168f08bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,7 @@ install_requires = terminado>=0.8.3 tornado>=6.1.0 traitlets>=5.1 - watchfiles>=0.12 + watchfiles>=0.13 websocket-client [options.extras_require] diff --git a/tests/services/contents/test_fileio.py b/tests/services/contents/test_fileio.py index 5f5716c53d..017916ce07 100644 --- a/tests/services/contents/test_fileio.py +++ b/tests/services/contents/test_fileio.py @@ -1,11 +1,10 @@ -import asyncio import os import stat import sys import pytest -from jupyter_server.services.contents.fileio import Change, atomic_writing, awatch +from jupyter_server.services.contents.fileio import atomic_writing umask = 0 @@ -122,19 +121,3 @@ def test_atomic_writing_newlines(tmp_path): with open(path, newline="") as f: read = f.read() assert read == text - - -async def test_watch_directory(tmp_path): - file_path = tmp_path / "file.txt" - - async def change_dir(): - # let the watcher start - await asyncio.sleep(0.1) - # add file to directory - file_path.write_text("foo") - - asyncio.create_task(change_dir()) - stop_event = asyncio.Event() - async for change in awatch(tmp_path, stop_event=stop_event): - assert change == {(Change.added, str(file_path))} - stop_event.set() diff --git a/tests/services/contents/test_largefilemanager.py b/tests/services/contents/test_largefilemanager.py index 82c5e54e78..ba2e8122c8 100644 --- a/tests/services/contents/test_largefilemanager.py +++ b/tests/services/contents/test_largefilemanager.py @@ -1,3 +1,5 @@ +import asyncio + import pytest import tornado @@ -110,3 +112,20 @@ async def test_save_in_subdirectory(jp_large_contents_manager, tmp_path): assert "path" in model assert model["name"] == "Untitled.ipynb" assert model["path"] == "foo/Untitled.ipynb" + + +async def test_watch_directory(tmp_path): + cm = AsyncLargeFileManager(root_dir=str(tmp_path)) + file_path = tmp_path / "file.txt" + + async def change_dir(): + # let the watcher start + await asyncio.sleep(0.1) + # add file to directory + file_path.write_text("foo") + + asyncio.create_task(change_dir()) + stop_event = asyncio.Event() + async for change in cm.watchfiles.awatch(tmp_path, stop_event=stop_event): + assert (cm.watchfiles.Change.added, str(file_path)) in change + stop_event.set()