Skip to content

Commit f4ba1ce

Browse files
committed
Resolve conflict due to anyio updates
2 parents 827fc6a + a0135ca commit f4ba1ce

File tree

9 files changed

+135
-89
lines changed

9 files changed

+135
-89
lines changed

jupyter_server/extension/manager.py

+51-38
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import importlib
22

3-
from traitlets.config import LoggingConfigurable, Config
3+
from traitlets.config import LoggingConfigurable
44

55
from traitlets import (
66
HasTraits,
77
Dict,
88
Unicode,
99
Bool,
1010
Any,
11-
validate
11+
Instance,
12+
default,
13+
observe,
14+
validate,
1215
)
1316

17+
from .config import ExtensionConfigManager
1418
from .utils import (
1519
ExtensionMetadataError,
1620
ExtensionModuleNotFound,
@@ -129,6 +133,8 @@ def validate(self):
129133
self._get_loader()
130134
except Exception:
131135
return False
136+
else:
137+
return True
132138

133139
def link(self, serverapp):
134140
"""Link the extension to a Jupyter ServerApp object.
@@ -238,35 +244,44 @@ class ExtensionManager(LoggingConfigurable):
238244
linking, loading, and managing Jupyter Server extensions.
239245
240246
Usage:
241-
m = ExtensionManager(jpserver_extensions=extensions)
247+
m = ExtensionManager(config_manager=...)
242248
"""
243-
def __init__(self, config_manager=None, *args, **kwargs):
244-
super().__init__(*args, **kwargs)
245-
# The `enabled_extensions` attribute provides a dictionary
246-
# with extension (package) names mapped to their ExtensionPackage interface
247-
# (see above). This manager simplifies the interaction between the
248-
# ServerApp and the extensions being appended.
249-
self._extensions = {}
250-
# The `_linked_extensions` attribute tracks when each extension
251-
# has been successfully linked to a ServerApp. This helps prevent
252-
# extensions from being re-linked recursively unintentionally if another
253-
# extension attempts to link extensions again.
254-
self._linked_extensions = {}
255-
self._config_manager = config_manager
256-
if self._config_manager:
257-
self.from_config_manager(self._config_manager)
258249

259-
@property
260-
def config_manager(self):
261-
return self._config_manager
250+
config_manager = Instance(ExtensionConfigManager, allow_none=True)
251+
252+
@default("config_manager")
253+
def _load_default_config_manager(self):
254+
config_manager = ExtensionConfigManager()
255+
self._load_config_manager(config_manager)
256+
return config_manager
257+
258+
@observe("config_manager")
259+
def _config_manager_changed(self, change):
260+
if change.new:
261+
self._load_config_manager(change.new)
262+
263+
# The `extensions` attribute provides a dictionary
264+
# with extension (package) names mapped to their ExtensionPackage interface
265+
# (see above). This manager simplifies the interaction between the
266+
# ServerApp and the extensions being appended.
267+
extensions = Dict(
268+
help="""
269+
Dictionary with extension package names as keys
270+
and ExtensionPackage objects as values.
271+
"""
272+
)
262273

263-
@property
264-
def extensions(self):
265-
"""Dictionary with extension package names as keys
266-
and an ExtensionPackage objects as values.
274+
# The `_linked_extensions` attribute tracks when each extension
275+
# has been successfully linked to a ServerApp. This helps prevent
276+
# extensions from being re-linked recursively unintentionally if another
277+
# extension attempts to link extensions again.
278+
linked_extensions = Dict(
279+
help="""
280+
Dictionary with extension names as keys
281+
282+
values are True if the extension is linked, False if not.
267283
"""
268-
# Sort enabled extensions before
269-
return self._extensions
284+
)
270285

271286
@property
272287
def extension_points(self):
@@ -277,16 +292,14 @@ def extension_points(self):
277292
for name, point in value.extension_points.items()
278293
}
279294

280-
@property
281-
def linked_extensions(self):
282-
"""Dictionary with extension names as keys; values are
283-
True if the extension is linked, False if not."""
284-
return self._linked_extensions
285-
286295
def from_config_manager(self, config_manager):
287296
"""Add extensions found by an ExtensionConfigManager"""
288-
self._config_manager = config_manager
289-
jpserver_extensions = self._config_manager.get_jpserver_extensions()
297+
# load triggered via config_manager trait observer
298+
self.config_manager = config_manager
299+
300+
def _load_config_manager(self, config_manager):
301+
"""Actually load our config manager"""
302+
jpserver_extensions = config_manager.get_jpserver_extensions()
290303
self.from_jpserver_extensions(jpserver_extensions)
291304

292305
def from_jpserver_extensions(self, jpserver_extensions):
@@ -300,21 +313,21 @@ def add_extension(self, extension_name, enabled=False):
300313
"""
301314
try:
302315
extpkg = ExtensionPackage(name=extension_name, enabled=enabled)
303-
self._extensions[extension_name] = extpkg
316+
self.extensions[extension_name] = extpkg
304317
return True
305318
# Raise a warning if the extension cannot be loaded.
306319
except Exception as e:
307320
self.log.warning(e)
308321
return False
309322

310323
def link_extension(self, name, serverapp):
311-
linked = self._linked_extensions.get(name, False)
324+
linked = self.linked_extensions.get(name, False)
312325
extension = self.extensions[name]
313326
if not linked and extension.enabled:
314327
try:
315328
# Link extension and store links
316329
extension.link_all_points(serverapp)
317-
self._linked_extensions[name] = True
330+
self.linked_extensions[name] = True
318331
self.log.info("{name} | extension was successfully linked.".format(name=name))
319332
except Exception as e:
320333
self.log.warning(e)

jupyter_server/extension/serverextension.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ def list_server_extensions(self):
306306
GREEN_ENABLED if enabled else RED_DISABLED))
307307
try:
308308
self.log.info(" - Validating {}...".format(name))
309-
extension.validate()
309+
if not extension.validate():
310+
raise ValueError("validation failed")
310311
version = extension.version
311312
self.log.info(
312313
" {} {} {}".format(name, version, GREEN_OK)

jupyter_server/services/contents/filecheckpoints.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
)
1515
from .fileio import AsyncFileManagerMixin, FileManagerMixin
1616

17-
from anyio import run_sync_in_worker_thread
17+
try:
18+
from anyio.to_thread import run_sync
19+
except ImportError:
20+
# fallback on anyio v2 for python version < 3.7
21+
from anyio import run_sync_in_worker_thread as run_sync
22+
1823
from jupyter_core.utils import ensure_dir_exists
1924
from traitlets import Unicode
2025

@@ -156,7 +161,7 @@ async def restore_checkpoint(self, contents_mgr, checkpoint_id, path):
156161

157162
async def checkpoint_model(self, checkpoint_id, os_path):
158163
"""construct the info dict for a given checkpoint"""
159-
stats = await run_sync_in_worker_thread(os.stat, os_path)
164+
stats = await run_sync(os.stat, os_path)
160165
last_modified = tz.utcfromtimestamp(stats.st_mtime)
161166
info = dict(
162167
id=checkpoint_id,
@@ -176,7 +181,7 @@ async def rename_checkpoint(self, checkpoint_id, old_path, new_path):
176181
new_cp_path,
177182
)
178183
with self.perm_to_403():
179-
await run_sync_in_worker_thread(shutil.move, old_cp_path, new_cp_path)
184+
await run_sync(shutil.move, old_cp_path, new_cp_path)
180185

181186
async def delete_checkpoint(self, checkpoint_id, path):
182187
"""delete a file's checkpoint"""
@@ -187,7 +192,7 @@ async def delete_checkpoint(self, checkpoint_id, path):
187192

188193
self.log.debug("unlinking %s", cp_path)
189194
with self.perm_to_403():
190-
await run_sync_in_worker_thread(os.unlink, cp_path)
195+
await run_sync(os.unlink, cp_path)
191196

192197
async def list_checkpoints(self, path):
193198
"""list the checkpoints for a given file

jupyter_server/services/contents/fileio.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
import os
1313
import shutil
1414

15-
from anyio import run_sync_in_worker_thread
15+
try:
16+
from anyio.to_thread import run_sync
17+
except ImportError:
18+
# fallback on anyio v2 for python version < 3.7
19+
from anyio import run_sync_in_worker_thread as run_sync
20+
1621
from tornado.web import HTTPError
1722

1823
from jupyter_server.utils import (
@@ -36,7 +41,7 @@ def replace_file(src, dst):
3641
async def async_replace_file(src, dst):
3742
""" replace dst with src asynchronously
3843
"""
39-
await run_sync_in_worker_thread(os.replace, src, dst)
44+
await run_sync(os.replace, src, dst)
4045

4146
def copy2_safe(src, dst, log=None):
4247
"""copy src to dst
@@ -55,9 +60,9 @@ async def async_copy2_safe(src, dst, log=None):
5560
5661
like shutil.copy2, but log errors in copystat instead of raising
5762
"""
58-
await run_sync_in_worker_thread(shutil.copyfile, src, dst)
63+
await run_sync(shutil.copyfile, src, dst)
5964
try:
60-
await run_sync_in_worker_thread(shutil.copystat, src, dst)
65+
await run_sync(shutil.copystat, src, dst)
6166
except OSError:
6267
if log:
6368
log.debug("copystat on %s failed", dst, exc_info=True)
@@ -355,7 +360,7 @@ async def _read_notebook(self, os_path, as_version=4):
355360
"""Read a notebook from an os path."""
356361
with self.open(os_path, 'r', encoding='utf-8') as f:
357362
try:
358-
return await run_sync_in_worker_thread(partial(nbformat.read, as_version=as_version), f)
363+
return await run_sync(partial(nbformat.read, as_version=as_version), f)
359364
except Exception as e:
360365
e_orig = e
361366

@@ -379,7 +384,7 @@ async def _read_notebook(self, os_path, as_version=4):
379384
async def _save_notebook(self, os_path, nb):
380385
"""Save a notebook to an os_path."""
381386
with self.atomic_writing(os_path, encoding='utf-8') as f:
382-
await run_sync_in_worker_thread(partial(nbformat.write, version=nbformat.NO_CONVERT), nb, f)
387+
await run_sync(partial(nbformat.write, version=nbformat.NO_CONVERT), nb, f)
383388

384389
async def _read_file(self, os_path, format):
385390
"""Read a non-notebook file.
@@ -394,7 +399,7 @@ async def _read_file(self, os_path, format):
394399
raise HTTPError(400, "Cannot read non-file %s" % os_path)
395400

396401
with self.open(os_path, 'rb') as f:
397-
bcontent = await run_sync_in_worker_thread(f.read)
402+
bcontent = await run_sync(f.read)
398403

399404
if format is None or format == 'text':
400405
# Try to interpret as unicode if format is unknown or if unicode
@@ -429,4 +434,4 @@ async def _save_file(self, os_path, content, format):
429434
) from e
430435

431436
with self.atomic_writing(os_path, text=False) as f:
432-
await run_sync_in_worker_thread(f.write, bcontent)
437+
await run_sync(f.write, bcontent)

jupyter_server/services/contents/filemanager.py

+15-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
import mimetypes
1313
import nbformat
1414

15-
from anyio import run_sync_in_worker_thread
15+
try:
16+
from anyio.to_thread import run_sync
17+
except ImportError:
18+
# fallback on anyio v2 for python version < 3.7
19+
from anyio import run_sync_in_worker_thread as run_sync
20+
1621
from send2trash import send2trash
1722
from tornado import web
1823

@@ -578,7 +583,7 @@ async def _dir_model(self, path, content=True):
578583
if content:
579584
model['content'] = contents = []
580585
os_dir = self._get_os_path(path)
581-
dir_contents = await run_sync_in_worker_thread(os.listdir, os_dir)
586+
dir_contents = await run_sync(os.listdir, os_dir)
582587
for name in dir_contents:
583588
try:
584589
os_path = os.path.join(os_dir, name)
@@ -588,7 +593,7 @@ async def _dir_model(self, path, content=True):
588593
continue
589594

590595
try:
591-
st = await run_sync_in_worker_thread(os.lstat, os_path)
596+
st = await run_sync(os.lstat, os_path)
592597
except OSError as e:
593598
# skip over broken symlinks in listing
594599
if e.errno == errno.ENOENT:
@@ -721,7 +726,7 @@ async def _save_directory(self, os_path, model, path=''):
721726
raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
722727
if not os.path.exists(os_path):
723728
with self.perm_to_403():
724-
await run_sync_in_worker_thread(os.mkdir, os_path)
729+
await run_sync(os.mkdir, os_path)
725730
elif not os.path.isdir(os_path):
726731
raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
727732
else:
@@ -791,16 +796,16 @@ async def _check_trash(os_path):
791796
# It's a bit more nuanced than this, but until we can better
792797
# distinguish errors from send2trash, assume that we can only trash
793798
# files on the same partition as the home directory.
794-
file_dev = (await run_sync_in_worker_thread(os.stat, os_path)).st_dev
795-
home_dev = (await run_sync_in_worker_thread(os.stat, os.path.expanduser('~'))).st_dev
799+
file_dev = (await run_sync(os.stat, os_path)).st_dev
800+
home_dev = (await run_sync(os.stat, os.path.expanduser('~'))).st_dev
796801
return file_dev == home_dev
797802

798803
async def is_non_empty_dir(os_path):
799804
if os.path.isdir(os_path):
800805
# A directory containing only leftover checkpoints is
801806
# considered empty.
802807
cp_dir = getattr(self.checkpoints, 'checkpoint_dir', None)
803-
dir_contents = set(await run_sync_in_worker_thread(os.listdir, os_path))
808+
dir_contents = set(await run_sync(os.listdir, os_path))
804809
if dir_contents - {cp_dir}:
805810
return True
806811

@@ -828,11 +833,11 @@ async def is_non_empty_dir(os_path):
828833
raise web.HTTPError(400, u'Directory %s not empty' % os_path)
829834
self.log.debug("Removing directory %s", os_path)
830835
with self.perm_to_403():
831-
await run_sync_in_worker_thread(shutil.rmtree, os_path)
836+
await run_sync(shutil.rmtree, os_path)
832837
else:
833838
self.log.debug("Unlinking file %s", os_path)
834839
with self.perm_to_403():
835-
await run_sync_in_worker_thread(rm, os_path)
840+
await run_sync(rm, os_path)
836841

837842
async def rename_file(self, old_path, new_path):
838843
"""Rename a file."""
@@ -851,7 +856,7 @@ async def rename_file(self, old_path, new_path):
851856
# Move the file
852857
try:
853858
with self.perm_to_403():
854-
await run_sync_in_worker_thread(shutil.move, old_os_path, new_os_path)
859+
await run_sync(shutil.move, old_os_path, new_os_path)
855860
except web.HTTPError:
856861
raise
857862
except Exception as e:

jupyter_server/services/contents/largefilemanager.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
from anyio import run_sync_in_worker_thread
1+
try:
2+
from anyio.to_thread import run_sync
3+
except ImportError:
4+
# fallback on anyio v2 for python version < 3.7
5+
from anyio import run_sync_in_worker_thread as run_sync
6+
27
from tornado import web
38
import base64
49
import os, io
@@ -135,6 +140,6 @@ async def _save_large_file(self, os_path, content, format):
135140
if os.path.islink(os_path):
136141
os_path = os.path.join(os.path.dirname(os_path), os.readlink(os_path))
137142
with io.open(os_path, 'ab') as f:
138-
await run_sync_in_worker_thread(f.write, bcontent)
143+
await run_sync(f.write, bcontent)
139144

140145

0 commit comments

Comments
 (0)