Skip to content

Commit c6dd0df

Browse files
committed
Add watchfiles attribute to contents manager
1 parent 3874a05 commit c6dd0df

File tree

6 files changed

+47
-20
lines changed

6 files changed

+47
-20
lines changed

jupyter_server/services/contents/fileio.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from tornado.web import HTTPError
1616
from traitlets import Bool
1717
from traitlets.config import Configurable
18-
from watchfiles import * # noqa
1918

2019
from jupyter_server.utils import to_api_path, to_os_path
2120

jupyter_server/services/contents/filemanager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
class FileContentsManager(FileManagerMixin, ContentsManager):
3737

38+
import watchfiles
39+
3840
root_dir = Unicode(config=True)
3941

4042
@default("root_dir")
@@ -546,6 +548,9 @@ def get_kernel_path(self, path, model=None):
546548

547549

548550
class AsyncFileContentsManager(FileContentsManager, AsyncFileManagerMixin, AsyncContentsManager):
551+
552+
import watchfiles
553+
549554
@default("checkpoints_class")
550555
def _checkpoints_class_default(self):
551556
return AsyncFileCheckpoints

jupyter_server/services/contents/manager.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
copy_pat = re.compile(r"\-Copy\d*\.")
3535

3636

37+
class NoWatchfilesAPI:
38+
pass
39+
40+
41+
NOWATCHFILESAPI = NoWatchfilesAPI()
42+
43+
3744
class ContentsManager(LoggingConfigurable):
3845
"""Base class for serving files and directories.
3946
@@ -409,6 +416,20 @@ def rename_file(self, old_path, new_path):
409416
# ContentsManager API part 2: methods that have useable default
410417
# implementations, but can be overridden in subclasses.
411418

419+
@property
420+
def watchfiles(self):
421+
"""File system change notifyer
422+
423+
Override this method in subclasses if the file system supports change notifications.
424+
425+
Returns
426+
-------
427+
api : class
428+
The supported API for file system change notifications. Loosely follows the API of the
429+
watchfiles Python package (can be a subset of it).
430+
"""
431+
return NOWATCHFILESAPI
432+
412433
def delete(self, path):
413434
"""Delete a file/directory and any associated checkpoints."""
414435
path = path.strip("/")

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ install_requires =
5353
terminado>=0.8.3
5454
tornado>=6.1.0
5555
traitlets>=5.1
56-
watchfiles>=0.12
56+
watchfiles>=0.13
5757
websocket-client
5858

5959
[options.extras_require]

tests/services/contents/test_fileio.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import asyncio
21
import os
32
import stat
43
import sys
54

65
import pytest
76

8-
from jupyter_server.services.contents.fileio import Change, atomic_writing, awatch
7+
from jupyter_server.services.contents.fileio import atomic_writing
98

109
umask = 0
1110

@@ -122,19 +121,3 @@ def test_atomic_writing_newlines(tmp_path):
122121
with open(path, newline="") as f:
123122
read = f.read()
124123
assert read == text
125-
126-
127-
async def test_watch_directory(tmp_path):
128-
file_path = tmp_path / "file.txt"
129-
130-
async def change_dir():
131-
# let the watcher start
132-
await asyncio.sleep(0.1)
133-
# add file to directory
134-
file_path.write_text("foo")
135-
136-
asyncio.create_task(change_dir())
137-
stop_event = asyncio.Event()
138-
async for change in awatch(tmp_path, stop_event=stop_event):
139-
assert change == {(Change.added, str(file_path))}
140-
stop_event.set()

tests/services/contents/test_largefilemanager.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import asyncio
2+
13
import pytest
24
import tornado
35

@@ -110,3 +112,20 @@ async def test_save_in_subdirectory(jp_large_contents_manager, tmp_path):
110112
assert "path" in model
111113
assert model["name"] == "Untitled.ipynb"
112114
assert model["path"] == "foo/Untitled.ipynb"
115+
116+
117+
async def test_watch_directory(tmp_path):
118+
cm = AsyncLargeFileManager(root_dir=str(tmp_path))
119+
file_path = tmp_path / "file.txt"
120+
121+
async def change_dir():
122+
# let the watcher start
123+
await asyncio.sleep(0.1)
124+
# add file to directory
125+
file_path.write_text("foo")
126+
127+
asyncio.create_task(change_dir())
128+
stop_event = asyncio.Event()
129+
async for change in cm.watchfiles.awatch(tmp_path, stop_event=stop_event):
130+
assert (cm.watchfiles.Change.added, str(file_path)) in change
131+
stop_event.set()

0 commit comments

Comments
 (0)