Skip to content

Commit 678878f

Browse files
authored
Merge pull request #492 from mwakaba2/upgrade-anyio-3
Upgrade anyio to v3
2 parents 5e77b73 + eff9b0a commit 678878f

File tree

6 files changed

+69
-50
lines changed

6 files changed

+69
-50
lines changed

jupyter_server/services/contents/filecheckpoints.py

Lines changed: 9 additions & 4 deletions
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

Lines changed: 13 additions & 8 deletions
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

Lines changed: 15 additions & 10 deletions
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

Lines changed: 7 additions & 2 deletions
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

jupyter_server/tests/services/contents/test_manager.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def jp_contents_manager(request, tmp_path):
2323

2424

2525
@pytest.fixture(params=[FileContentsManager, AsyncFileContentsManager])
26-
def file_contents_manager_class(request, tmp_path):
26+
def jp_file_contents_manager_class(request, tmp_path):
2727
return request.param
2828

2929
# -------------- Functions ----------------------------
@@ -100,46 +100,45 @@ async def check_populated_dir_files(jp_contents_manager, api_path):
100100
# ----------------- Tests ----------------------------------
101101

102102

103-
def test_root_dir(file_contents_manager_class, tmp_path):
104-
fm = file_contents_manager_class(root_dir=str(tmp_path))
103+
def test_root_dir(jp_file_contents_manager_class, tmp_path):
104+
fm = jp_file_contents_manager_class(root_dir=str(tmp_path))
105105
assert fm.root_dir == str(tmp_path)
106106

107107

108-
def test_missing_root_dir(file_contents_manager_class, tmp_path):
108+
def test_missing_root_dir(jp_file_contents_manager_class, tmp_path):
109109
root = tmp_path / 'notebook' / 'dir' / 'is' / 'missing'
110110
with pytest.raises(TraitError):
111-
file_contents_manager_class(root_dir=str(root))
111+
jp_file_contents_manager_class(root_dir=str(root))
112112

113113

114-
def test_invalid_root_dir(file_contents_manager_class, tmp_path):
114+
def test_invalid_root_dir(jp_file_contents_manager_class, tmp_path):
115115
temp_file = tmp_path / 'file.txt'
116116
temp_file.write_text('')
117117
with pytest.raises(TraitError):
118-
file_contents_manager_class(root_dir=str(temp_file))
118+
jp_file_contents_manager_class(root_dir=str(temp_file))
119119

120-
121-
def test_get_os_path(file_contents_manager_class, tmp_path):
122-
fm = file_contents_manager_class(root_dir=str(tmp_path))
120+
def test_get_os_path(jp_file_contents_manager_class, tmp_path):
121+
fm = jp_file_contents_manager_class(root_dir=str(tmp_path))
123122
path = fm._get_os_path('/path/to/notebook/test.ipynb')
124123
rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
125124
fs_path = os.path.join(fm.root_dir, *rel_path_list)
126125
assert path == fs_path
127126

128-
fm = file_contents_manager_class(root_dir=str(tmp_path))
127+
fm = jp_file_contents_manager_class(root_dir=str(tmp_path))
129128
path = fm._get_os_path('test.ipynb')
130129
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
131130
assert path == fs_path
132131

133-
fm = file_contents_manager_class(root_dir=str(tmp_path))
132+
fm = jp_file_contents_manager_class(root_dir=str(tmp_path))
134133
path = fm._get_os_path('////test.ipynb')
135134
fs_path = os.path.join(fm.root_dir, 'test.ipynb')
136135
assert path == fs_path
137136

138137

139-
def test_checkpoint_subdir(file_contents_manager_class, tmp_path):
138+
def test_checkpoint_subdir(jp_file_contents_manager_class, tmp_path):
140139
subd = 'sub ∂ir'
141140
cp_name = 'test-cp.ipynb'
142-
fm = file_contents_manager_class(root_dir=str(tmp_path))
141+
fm = jp_file_contents_manager_class(root_dir=str(tmp_path))
143142
tmp_path.joinpath(subd).mkdir()
144143
cpm = fm.checkpoints
145144
cp_dir = cpm.checkpoint_path('cp', 'test.ipynb')
@@ -148,10 +147,10 @@ def test_checkpoint_subdir(file_contents_manager_class, tmp_path):
148147
assert cp_dir == os.path.join(str(tmp_path), cpm.checkpoint_dir, cp_name)
149148

150149

151-
async def test_bad_symlink(file_contents_manager_class, tmp_path):
150+
async def test_bad_symlink(jp_file_contents_manager_class, tmp_path):
152151
td = str(tmp_path)
153152

154-
cm = file_contents_manager_class(root_dir=td)
153+
cm = jp_file_contents_manager_class(root_dir=td)
155154
path = 'test bad symlink'
156155
_make_dir(cm, path)
157156

@@ -173,10 +172,10 @@ async def test_bad_symlink(file_contents_manager_class, tmp_path):
173172
sys.platform.startswith('win'),
174173
reason="Windows doesn't detect symlink loops"
175174
)
176-
async def test_recursive_symlink(file_contents_manager_class, tmp_path):
175+
async def test_recursive_symlink(jp_file_contents_manager_class, tmp_path):
177176
td = str(tmp_path)
178177

179-
cm = file_contents_manager_class(root_dir=td)
178+
cm = jp_file_contents_manager_class(root_dir=td)
180179
path = 'test recursive symlink'
181180
_make_dir(cm, path)
182181

@@ -195,9 +194,9 @@ async def test_recursive_symlink(file_contents_manager_class, tmp_path):
195194
assert 'recursive' not in contents
196195

197196

198-
async def test_good_symlink(file_contents_manager_class, tmp_path):
197+
async def test_good_symlink(jp_file_contents_manager_class, tmp_path):
199198
td = str(tmp_path)
200-
cm = file_contents_manager_class(root_dir=td)
199+
cm = jp_file_contents_manager_class(root_dir=td)
201200
parent = 'test good symlink'
202201
name = 'good symlink'
203202
path = '{0}/{1}'.format(parent, name)
@@ -216,13 +215,13 @@ async def test_good_symlink(file_contents_manager_class, tmp_path):
216215
sys.platform.startswith('win'),
217216
reason="Can't test permissions on Windows"
218217
)
219-
async def test_403(file_contents_manager_class, tmp_path):
218+
async def test_403(jp_file_contents_manager_class, tmp_path):
220219
if hasattr(os, 'getuid'):
221220
if os.getuid() == 0:
222221
raise pytest.skip("Can't test permissions as root")
223222

224223
td = str(tmp_path)
225-
cm = file_contents_manager_class(root_dir=td)
224+
cm = jp_file_contents_manager_class(root_dir=td)
226225
model = await ensure_async(cm.new_untitled(type='file'))
227226
os_path = cm._get_os_path(model['path'])
228227

@@ -233,10 +232,9 @@ async def test_403(file_contents_manager_class, tmp_path):
233232
except HTTPError as e:
234233
assert e.status_code == 403
235234

236-
237-
async def test_escape_root(file_contents_manager_class, tmp_path):
235+
async def test_escape_root(jp_file_contents_manager_class, tmp_path):
238236
td = str(tmp_path)
239-
cm = file_contents_manager_class(root_dir=td)
237+
cm = jp_file_contents_manager_class(root_dir=td)
240238
# make foo, bar next to root
241239
with open(os.path.join(cm.root_dir, '..', 'foo'), 'w') as f:
242240
f.write('foo')

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ install_requires =
4242
terminado>=0.8.3
4343
prometheus_client
4444
pywin32>=1.0 ; sys_platform == 'win32'
45-
anyio>=2.0.2,<3
45+
anyio>=2.0.2,<3 ; python_version < '3.7'
46+
anyio>=3.0.1,<4 ; python_version >= '3.7'
4647

4748
[options.extras_require]
4849
test = coverage; pytest; pytest-cov; pytest-mock; requests; pytest-tornasync; pytest-console-scripts; ipykernel

0 commit comments

Comments
 (0)