Skip to content

Commit e71f26c

Browse files
committed
Improve contents
1 parent cfecb28 commit e71f26c

File tree

2 files changed

+80
-52
lines changed

2 files changed

+80
-52
lines changed

jupyverse_api/jupyverse_api/contents/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,17 @@ async def rename_content(
189189
user: User,
190190
) -> Content:
191191
...
192+
193+
@abstractmethod
194+
async def is_dir(
195+
self,
196+
path: str,
197+
) -> bool:
198+
...
199+
200+
@abstractmethod
201+
async def is_file(
202+
self,
203+
path: str,
204+
) -> bool:
205+
...

plugins/contents/fps_contents/routes.py

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import base64
22
import json
33
import os
4+
import pathlib
45
import shutil
56
from datetime import datetime
67
from http import HTTPStatus
7-
from pathlib import Path
88
from typing import Dict, List, Optional, Union, cast
99

10-
from anyio import open_file
10+
from anyio import Path, open_file, to_thread
1111
from fastapi import HTTPException, Response
1212
from jupyverse_api.auth import User
1313
from jupyverse_api.contents import Contents
@@ -32,12 +32,12 @@ async def create_checkpoint(
3232
src_path = Path(path)
3333
dst_path = Path(".ipynb_checkpoints") / f"{src_path.stem}-checkpoint{src_path.suffix}"
3434
try:
35-
dst_path.parent.mkdir(exist_ok=True)
36-
shutil.copyfile(src_path, dst_path)
35+
await dst_path.parent.mkdir(exist_ok=True)
36+
await to_thread.run_sync(shutil.copyfile, src_path, dst_path)
3737
except Exception:
3838
# FIXME: return error code?
3939
return []
40-
mtime = get_file_modification_time(dst_path)
40+
mtime = await get_file_modification_time(dst_path)
4141
return Checkpoint(**{"id": "checkpoint", "last_modified": mtime})
4242

4343
async def create_content(
@@ -49,29 +49,32 @@ async def create_content(
4949
create_content = CreateContent(**(await request.json()))
5050
content_path = Path(create_content.path)
5151
if create_content.type == "notebook":
52-
available_path = get_available_path(content_path / "Untitled.ipynb")
52+
available_path = await get_available_path(content_path / "Untitled.ipynb")
5353
async with await open_file(available_path, "w") as f:
5454
await f.write(
5555
json.dumps({"cells": [], "metadata": {}, "nbformat": 4, "nbformat_minor": 5})
5656
)
5757
src_path = available_path
5858
dst_path = Path(".ipynb_checkpoints") / f"{src_path.stem}-checkpoint{src_path.suffix}"
5959
try:
60-
dst_path.parent.mkdir(exist_ok=True)
61-
shutil.copyfile(src_path, dst_path)
60+
await dst_path.parent.mkdir(exist_ok=True)
61+
await to_thread.run_sync(shutil.copyfile, src_path, dst_path)
6262
except Exception:
6363
# FIXME: return error code?
6464
pass
6565
elif create_content.type == "directory":
6666
name = "Untitled Folder"
67-
available_path = get_available_path(content_path / name, sep=" ")
68-
available_path.mkdir(parents=True, exist_ok=True)
67+
available_path = await get_available_path(content_path / name, sep=" ")
68+
await available_path.mkdir(parents=True, exist_ok=True)
6969
else:
7070
assert create_content.ext is not None
71-
available_path = get_available_path(content_path / ("untitled" + create_content.ext))
72-
open(available_path, "w").close()
71+
available_path = await get_available_path(
72+
content_path / ("untitled" + create_content.ext)
73+
)
74+
async with await open_file(available_path, "w") as f:
75+
pass
7376

74-
return await self.read_content(available_path, False)
77+
return await self.read_content(pathlib.Path(available_path), False)
7578

7679
async def get_root_content(
7780
self,
@@ -87,9 +90,9 @@ async def get_checkpoint(
8790
):
8891
src_path = Path(path)
8992
dst_path = Path(".ipynb_checkpoints") / f"{src_path.stem}-checkpoint{src_path.suffix}"
90-
if not dst_path.exists():
93+
if not await dst_path.exists():
9194
return []
92-
mtime = get_file_modification_time(dst_path)
95+
mtime = await get_file_modification_time(dst_path)
9396
return [Checkpoint(**{"id": "checkpoint", "last_modified": mtime})]
9497

9598
async def get_content(
@@ -120,11 +123,11 @@ async def delete_content(
120123
user: User,
121124
):
122125
p = Path(path)
123-
if p.exists():
124-
if p.is_dir():
125-
shutil.rmtree(p)
126+
if await p.exists():
127+
if await p.is_dir():
128+
await to_thread.run_sync(shutil.rmtree, p)
126129
else:
127-
p.unlink()
130+
await p.unlink()
128131
return Response(status_code=HTTPStatus.NO_CONTENT.value)
129132

130133
async def rename_content(
@@ -134,25 +137,24 @@ async def rename_content(
134137
user: User,
135138
):
136139
rename_content = RenameContent(**(await request.json()))
137-
Path(path).rename(rename_content.path)
140+
await Path(path).rename(rename_content.path)
138141
return await self.read_content(rename_content.path, False)
139142

140143
async def read_content(
141-
self, path: Union[str, Path], get_content: bool, file_format: Optional[str] = None
144+
self, path: Union[str, pathlib.Path], get_content: bool, file_format: Optional[str] = None
142145
) -> Content:
143-
if isinstance(path, str):
144-
path = Path(path)
146+
apath = Path(path)
145147
content: Optional[Union[str, Dict, List[Dict]]] = None
146148
if get_content:
147-
if path.is_dir():
149+
if await apath.is_dir():
148150
content = [
149151
(await self.read_content(subpath, get_content=False)).dict()
150-
for subpath in path.iterdir()
152+
async for subpath in apath.iterdir()
151153
if not subpath.name.startswith(".")
152154
]
153-
elif path.is_file() or path.is_symlink():
155+
elif await apath.is_file() or await apath.is_symlink():
154156
try:
155-
async with await open_file(path, mode="rb") as f:
157+
async with await open_file(apath, mode="rb") as f:
156158
content_bytes = await f.read()
157159
if file_format == "base64":
158160
content = base64.b64encode(content_bytes).decode("ascii")
@@ -163,14 +165,14 @@ async def read_content(
163165
except Exception:
164166
raise HTTPException(status_code=404, detail="Item not found")
165167
format: Optional[str] = None
166-
if path.is_dir():
168+
if await apath.is_dir():
167169
size = None
168170
type = "directory"
169171
format = "json"
170172
mimetype = None
171-
elif path.is_file() or path.is_symlink():
172-
size = get_file_size(path)
173-
if path.suffix == ".ipynb":
173+
elif await apath.is_file() or await apath.is_symlink():
174+
size = await get_file_size(apath)
175+
if apath.suffix == ".ipynb":
174176
type = "notebook"
175177
format = None
176178
mimetype = None
@@ -188,7 +190,7 @@ async def read_content(
188190
cell["metadata"].update({"trusted": False})
189191
if file_format != "json":
190192
content = json.dumps(nb)
191-
elif path.suffix == ".json":
193+
elif apath.suffix == ".json":
192194
type = "json"
193195
format = "text"
194196
mimetype = "application/json"
@@ -201,15 +203,15 @@ async def read_content(
201203

202204
return Content(
203205
**{
204-
"name": path.name,
205-
"path": path.as_posix(),
206-
"last_modified": get_file_modification_time(path),
207-
"created": get_file_creation_time(path),
206+
"name": apath.name,
207+
"path": apath.as_posix(),
208+
"last_modified": await get_file_modification_time(apath),
209+
"created": await get_file_creation_time(apath),
208210
"content": content,
209211
"format": format,
210212
"mimetype": mimetype,
211213
"size": size,
212-
"writable": is_file_writable(path),
214+
"writable": await is_file_writable(apath),
213215
"type": type,
214216
}
215217
)
@@ -242,8 +244,20 @@ async def write_content(self, content: Union[SaveContent, Dict]) -> None:
242244
def file_id_manager(self):
243245
return FileIdManager()
244246

247+
async def is_dir(
248+
self,
249+
path: str,
250+
) -> bool:
251+
return await Path(path).is_dir()
252+
253+
async def is_file(
254+
self,
255+
path: str,
256+
) -> bool:
257+
return await Path(path).is_file()
258+
245259

246-
def get_available_path(path: Path, sep: str = "") -> Path:
260+
async def get_available_path(path: Path, sep: str = "") -> Path:
247261
directory = path.parent
248262
name = Path(path.name)
249263
i = None
@@ -257,31 +271,31 @@ def get_available_path(path: Path, sep: str = "") -> Path:
257271
if i_str:
258272
i_str = sep + i_str
259273
available_path = directory / (name.stem + i_str + name.suffix)
260-
if not available_path.exists():
274+
if not await available_path.exists():
261275
return available_path
262276

263277

264-
def get_file_modification_time(path: Path):
265-
if path.exists():
266-
return datetime.utcfromtimestamp(path.stat().st_mtime).isoformat() + "Z"
278+
async def get_file_modification_time(path: Path):
279+
if await path.exists():
280+
return datetime.utcfromtimestamp((await path.stat()).st_mtime).isoformat() + "Z"
267281

268282

269-
def get_file_creation_time(path: Path):
270-
if path.exists():
271-
return datetime.utcfromtimestamp(path.stat().st_ctime).isoformat() + "Z"
283+
async def get_file_creation_time(path: Path):
284+
if await path.exists():
285+
return datetime.utcfromtimestamp((await path.stat()).st_ctime).isoformat() + "Z"
272286

273287

274-
def get_file_size(path: Path) -> Optional[int]:
275-
if path.exists():
276-
return path.stat().st_size
288+
async def get_file_size(path: Path) -> Optional[int]:
289+
if await path.exists():
290+
return (await path.stat()).st_size
277291
raise HTTPException(status_code=404, detail="Item not found")
278292

279293

280-
def is_file_writable(path: Path) -> bool:
281-
if path.exists():
282-
if path.is_dir():
294+
async def is_file_writable(path: Path) -> bool:
295+
if await path.exists():
296+
if await path.is_dir():
283297
# FIXME
284298
return True
285299
else:
286-
return os.access(path, os.W_OK)
300+
return await to_thread.run_sync(os.access, path, os.W_OK)
287301
return False

0 commit comments

Comments
 (0)