Skip to content

Commit 137f617

Browse files
committed
Merge branch 'main' into boto
2 parents 21b84a7 + c700461 commit 137f617

File tree

5 files changed

+822
-154
lines changed

5 files changed

+822
-154
lines changed

jupyter_drives/handlers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,25 @@ async def patch(self, drive: str = "", path: str = ""):
8484
result = await self._manager.rename_file(drive, path, **body)
8585
self.finish(result)
8686

87+
@tornado.web.authenticated
88+
async def put(self, drive: str = "", path: str = ""):
89+
body = self.get_json_body()
90+
if 'content' in body:
91+
result = await self._manager.save_file(drive, path, **body)
92+
elif 'to_path' in body:
93+
result = await self._manager.copy_file(drive, path, **body)
94+
self.finish(result)
95+
96+
@tornado.web.authenticated
97+
async def delete(self, drive: str = "", path: str = ""):
98+
result = await self._manager.delete_file(drive, path)
99+
self.finish(result)
100+
101+
@tornado.web.authenticated
102+
async def head(self, drive: str = "", path: str = ""):
103+
result = await self._manager.check_file(drive, path)
104+
self.finish(result)
105+
87106
handlers = [
88107
("drives", ListJupyterDrivesHandler)
89108
]

jupyter_drives/manager.py

Lines changed: 183 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import httpx
99
import traitlets
1010
import base64
11+
from io import BytesIO
1112
from jupyter_server.utils import url_path_join
1213

1314
import obstore as obs
@@ -183,6 +184,9 @@ async def get_contents(self, drive_name, path):
183184
"""
184185
if path == '/':
185186
path = ''
187+
else:
188+
path = path.strip('/')
189+
186190
try :
187191
data = []
188192
isDir = False
@@ -255,23 +259,198 @@ async def get_contents(self, drive_name, path):
255259

256260
return response
257261

258-
async def new_file(self, drive_name, path, **kwargs):
262+
async def new_file(self, drive_name, path):
259263
"""Create a new file or directory at the given path.
260264
261265
Args:
262266
drive_name: name of drive where the new content is created
263267
path: path where new content should be created
264268
"""
265-
print('New file function called.')
269+
data = {}
270+
try:
271+
# eliminate leading and trailing backslashes
272+
path = path.strip('/')
273+
274+
# TO DO: switch to mode "created", which is not implemented yet
275+
await obs.put_async(self._content_managers[drive_name], path, b"", mode = "overwrite")
276+
metadata = await obs.head_async(self._content_managers[drive_name], path)
277+
278+
data = {
279+
"path": path,
280+
"content": "",
281+
"last_modified": metadata["last_modified"].isoformat(),
282+
"size": metadata["size"]
283+
}
284+
except Exception as e:
285+
raise tornado.web.HTTPError(
286+
status_code= httpx.codes.BAD_REQUEST,
287+
reason=f"The following error occured when creating the object: {e}",
288+
)
289+
290+
response = {
291+
"data": data
292+
}
293+
return response
294+
295+
async def save_file(self, drive_name, path, content, options_format, content_format, content_type):
296+
"""Save file with new content.
297+
298+
Args:
299+
drive_name: name of drive where file exists
300+
path: path where new content should be saved
301+
content: content of object
302+
options_format: format of content (as sent through contents manager request)
303+
content_format: format of content (as defined by the registered file formats in JupyterLab)
304+
content_type: type of content (as defined by the registered file types in JupyterLab)
305+
"""
306+
data = {}
307+
try:
308+
# eliminate leading and trailing backslashes
309+
path = path.strip('/')
310+
311+
if options_format == 'json':
312+
formatted_content = json.dumps(content, indent=2)
313+
formatted_content = formatted_content.encode("utf-8")
314+
elif options_format == 'base64' and (content_format == 'base64' or content_type == 'PDF'):
315+
# transform base64 encoding to a UTF-8 byte array for saving or storing
316+
byte_characters = base64.b64decode(content)
317+
318+
byte_arrays = []
319+
for offset in range(0, len(byte_characters), 512):
320+
slice_ = byte_characters[offset:offset + 512]
321+
byte_array = bytearray(slice_)
322+
byte_arrays.append(byte_array)
323+
324+
# combine byte arrays and wrap in a BytesIO object
325+
formatted_content = BytesIO(b"".join(byte_arrays))
326+
formatted_content.seek(0) # reset cursor for any further reading
327+
elif options_format == 'text':
328+
formatted_content = content.encode("utf-8")
329+
else:
330+
formatted_content = content
331+
332+
await obs.put_async(self._content_managers[drive_name], path, formatted_content, mode = "overwrite")
333+
metadata = await obs.head_async(self._content_managers[drive_name], path)
334+
335+
data = {
336+
"path": path,
337+
"content": content,
338+
"last_modified": metadata["last_modified"].isoformat(),
339+
"size": metadata["size"]
340+
}
341+
except Exception as e:
342+
raise tornado.web.HTTPError(
343+
status_code= httpx.codes.BAD_REQUEST,
344+
reason=f"The following error occured when saving the file: {e}",
345+
)
346+
347+
response = {
348+
"data": data
349+
}
350+
return response
266351

267-
async def rename_file(self, drive_name, path, **kwargs):
352+
async def rename_file(self, drive_name, path, new_path):
268353
"""Rename a file.
269354
270355
Args:
271356
drive_name: name of drive where file is located
272357
path: path of file
358+
new_path: path of new file name
273359
"""
274-
print('Rename file function called.')
360+
data = {}
361+
try:
362+
# eliminate leading and trailing backslashes
363+
path = path.strip('/')
364+
365+
await obs.rename_async(self._content_managers[drive_name], path, new_path)
366+
metadata = await obs.head_async(self._content_managers[drive_name], new_path)
367+
368+
data = {
369+
"path": new_path,
370+
"last_modified": metadata["last_modified"].isoformat(),
371+
"size": metadata["size"]
372+
}
373+
except Exception as e:
374+
raise tornado.web.HTTPError(
375+
status_code= httpx.codes.BAD_REQUEST,
376+
reason=f"The following error occured when renaming the object: {e}",
377+
)
378+
379+
response = {
380+
"data": data
381+
}
382+
return response
383+
384+
async def delete_file(self, drive_name, path):
385+
"""Delete an object.
386+
387+
Args:
388+
drive_name: name of drive where object exists
389+
path: path where content is located
390+
"""
391+
try:
392+
# eliminate leading and trailing backslashes
393+
path = path.strip('/')
394+
await obs.delete_async(self._content_managers[drive_name], path)
395+
396+
except Exception as e:
397+
raise tornado.web.HTTPError(
398+
status_code= httpx.codes.BAD_REQUEST,
399+
reason=f"The following error occured when deleting the object: {e}",
400+
)
401+
402+
return
403+
404+
async def check_file(self, drive_name, path):
405+
"""Check if an object already exists within a drive.
406+
407+
Args:
408+
drive_name: name of drive where object exists
409+
path: path where content is located
410+
"""
411+
try:
412+
# eliminate leading and trailing backslashes
413+
path = path.strip('/')
414+
await obs.head_async(self._content_managers[drive_name], path)
415+
except Exception:
416+
raise tornado.web.HTTPError(
417+
status_code= httpx.codes.NOT_FOUND,
418+
reason="Object does not already exist within drive.",
419+
)
420+
421+
return
422+
423+
async def copy_file(self, drive_name, path, to_path):
424+
"""Save file with new content.
425+
426+
Args:
427+
drive_name: name of drive where file exists
428+
path: path where original content exists
429+
to_path: path where object should be copied
430+
"""
431+
data = {}
432+
try:
433+
# eliminate leading and trailing backslashes
434+
path = path.strip('/')
435+
436+
await obs.copy_async(self._content_managers[drive_name], path, to_path)
437+
metadata = await obs.head_async(self._content_managers[drive_name], to_path)
438+
439+
data = {
440+
"path": to_path,
441+
"last_modified": metadata["last_modified"].isoformat(),
442+
"size": metadata["size"]
443+
}
444+
except Exception as e:
445+
raise tornado.web.HTTPError(
446+
status_code= httpx.codes.BAD_REQUEST,
447+
reason=f"The following error occured when copying the: {e}",
448+
)
449+
450+
response = {
451+
"data": data
452+
}
453+
return response
275454

276455
def _get_drive_location(self, drive_name):
277456
"""Helping function for getting drive region.

0 commit comments

Comments
 (0)