Skip to content

Commit c700461

Browse files
authored
Merge pull request #25 from DenisaCG/manipulateContents
Add functionalities to manipulate content
2 parents 760d10e + 75d5d87 commit c700461

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
@@ -165,6 +166,9 @@ async def get_contents(self, drive_name, path):
165166
"""
166167
if path == '/':
167168
path = ''
169+
else:
170+
path = path.strip('/')
171+
168172
try :
169173
data = []
170174
isDir = False
@@ -237,23 +241,198 @@ async def get_contents(self, drive_name, path):
237241

238242
return response
239243

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

249-
async def rename_file(self, drive_name, path, **kwargs):
334+
async def rename_file(self, drive_name, path, new_path):
250335
"""Rename a file.
251336
252337
Args:
253338
drive_name: name of drive where file is located
254339
path: path of file
340+
new_path: path of new file name
255341
"""
256-
print('Rename file function called.')
342+
data = {}
343+
try:
344+
# eliminate leading and trailing backslashes
345+
path = path.strip('/')
346+
347+
await obs.rename_async(self._content_managers[drive_name], path, new_path)
348+
metadata = await obs.head_async(self._content_managers[drive_name], new_path)
349+
350+
data = {
351+
"path": new_path,
352+
"last_modified": metadata["last_modified"].isoformat(),
353+
"size": metadata["size"]
354+
}
355+
except Exception as e:
356+
raise tornado.web.HTTPError(
357+
status_code= httpx.codes.BAD_REQUEST,
358+
reason=f"The following error occured when renaming the object: {e}",
359+
)
360+
361+
response = {
362+
"data": data
363+
}
364+
return response
365+
366+
async def delete_file(self, drive_name, path):
367+
"""Delete an object.
368+
369+
Args:
370+
drive_name: name of drive where object exists
371+
path: path where content is located
372+
"""
373+
try:
374+
# eliminate leading and trailing backslashes
375+
path = path.strip('/')
376+
await obs.delete_async(self._content_managers[drive_name], path)
377+
378+
except Exception as e:
379+
raise tornado.web.HTTPError(
380+
status_code= httpx.codes.BAD_REQUEST,
381+
reason=f"The following error occured when deleting the object: {e}",
382+
)
383+
384+
return
385+
386+
async def check_file(self, drive_name, path):
387+
"""Check if an object already exists within a drive.
388+
389+
Args:
390+
drive_name: name of drive where object exists
391+
path: path where content is located
392+
"""
393+
try:
394+
# eliminate leading and trailing backslashes
395+
path = path.strip('/')
396+
await obs.head_async(self._content_managers[drive_name], path)
397+
except Exception:
398+
raise tornado.web.HTTPError(
399+
status_code= httpx.codes.NOT_FOUND,
400+
reason="Object does not already exist within drive.",
401+
)
402+
403+
return
404+
405+
async def copy_file(self, drive_name, path, to_path):
406+
"""Save file with new content.
407+
408+
Args:
409+
drive_name: name of drive where file exists
410+
path: path where original content exists
411+
to_path: path where object should be copied
412+
"""
413+
data = {}
414+
try:
415+
# eliminate leading and trailing backslashes
416+
path = path.strip('/')
417+
418+
await obs.copy_async(self._content_managers[drive_name], path, to_path)
419+
metadata = await obs.head_async(self._content_managers[drive_name], to_path)
420+
421+
data = {
422+
"path": to_path,
423+
"last_modified": metadata["last_modified"].isoformat(),
424+
"size": metadata["size"]
425+
}
426+
except Exception as e:
427+
raise tornado.web.HTTPError(
428+
status_code= httpx.codes.BAD_REQUEST,
429+
reason=f"The following error occured when copying the: {e}",
430+
)
431+
432+
response = {
433+
"data": data
434+
}
435+
return response
257436

258437
async def _call_provider(
259438
self,

0 commit comments

Comments
 (0)