Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionalities to manipulate content #25

Merged
merged 24 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4edd731
add handler for saving file operation
DenisaCG Nov 21, 2024
111c795
add backend functionality to save file
DenisaCG Nov 21, 2024
01d4b60
implement save file request and connect to frontend
DenisaCG Nov 21, 2024
4eb0d37
Merge branch 'main' into manipulateContents
DenisaCG Nov 25, 2024
547ec44
remove unused import
DenisaCG Nov 25, 2024
233ceac
iterate on saving functionality
DenisaCG Nov 25, 2024
bd1c598
update docstrings
DenisaCG Nov 25, 2024
d02f167
add logic for object creation
DenisaCG Nov 26, 2024
f13f307
add delete functionality in backend and connect to frontend content m…
DenisaCG Nov 26, 2024
9784010
fix sub directory listing
DenisaCG Nov 26, 2024
2774654
format path in backend when listing
DenisaCG Nov 26, 2024
ef20987
add backend functionality to check if object exists in drive and coun…
DenisaCG Nov 26, 2024
ff3426e
add rename functionaltity in backend and frontend content manager
DenisaCG Nov 26, 2024
ea27266
add logic to copy object in backend and frontend content manager
DenisaCG Nov 27, 2024
cea5cc9
update docstrings
DenisaCG Nov 27, 2024
103a73c
iterate on saving functionality to format body
DenisaCG Nov 27, 2024
cf31d43
check if operations are done outside of a drive
DenisaCG Nov 27, 2024
a4e5350
remove log of mounted error
DenisaCG Nov 27, 2024
56ef541
wrap deleting object in try and catch block
DenisaCG Nov 27, 2024
76516be
refactor code to use helping functions
DenisaCG Nov 27, 2024
d7562eb
iterate on check object functionality
DenisaCG Nov 27, 2024
dfafbf5
remove dev comments
DenisaCG Nov 27, 2024
ed8fa37
Update docstrings and variable names
DenisaCG Nov 28, 2024
75d5d87
update docstring of save backend function
DenisaCG Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions jupyter_drives/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ async def patch(self, drive: str = "", path: str = ""):
result = await self._manager.rename_file(drive, path, **body)
self.finish(result)

@tornado.web.authenticated
async def put(self, drive: str = "", path: str = ""):
body = self.get_json_body()
if 'content' in body:
result = await self._manager.save_file(drive, path, **body)
elif 'to_path' in body:
result = await self._manager.copy_file(drive, path, **body)
self.finish(result)

@tornado.web.authenticated
async def delete(self, drive: str = "", path: str = ""):
result = await self._manager.delete_file(drive, path)
self.finish(result)

@tornado.web.authenticated
async def head(self, drive: str = "", path: str = ""):
result = await self._manager.check_file(drive, path)
self.finish(result)

handlers = [
("drives", ListJupyterDrivesHandler)
]
Expand Down
184 changes: 180 additions & 4 deletions jupyter_drives/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import httpx
import traitlets
import base64
from io import BytesIO
from jupyter_server.utils import url_path_join

import obstore as obs
Expand Down Expand Up @@ -165,6 +166,9 @@ async def get_contents(self, drive_name, path):
"""
if path == '/':
path = ''
else:
path = path.strip('/')

try :
data = []
isDir = False
Expand Down Expand Up @@ -237,23 +241,195 @@ async def get_contents(self, drive_name, path):

return response

async def new_file(self, drive_name, path, **kwargs):
async def new_file(self, drive_name, path):
"""Create a new file or directory at the given path.

Args:
drive_name: name of drive where the new content is created
path: path where new content should be created
"""
print('New file function called.')
data = {}
try:
# eliminate leading and trailing backslashes
path = path.strip('/')

# TO DO: switch to mode "created", which is not implemented yet
await obs.put_async(self._content_managers[drive_name], path, b"", mode = "overwrite")
metadata = await obs.head_async(self._content_managers[drive_name], path)

data = {
"path": path,
"content": "",
"last_modified": metadata["last_modified"].isoformat(),
"size": metadata["size"]
}
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason=f"The following error occured when creating the object: {e}",
)

response = {
"data": data
}
return response

async def save_file(self, drive_name, path, content, options_format, content_format, content_type):
"""Save file with new content.

Args:
drive_name: name of drive where file exists
path: path where new content should be saved
content: content of object
DenisaCG marked this conversation as resolved.
Show resolved Hide resolved
"""
data = {}
try:
# eliminate leading and trailing backslashes
path = path.strip('/')

if options_format == 'json':
formatted_content = json.dumps(content, indent=2)
formatted_content = formatted_content.encode("utf-8")
elif options_format == 'base64' and (content_format == 'base64' or content_type == 'PDF'):
# transform base64 encoding to a UTF-8 byte array for saving or storing
byte_characters = base64.b64decode(content)

byte_arrays = []
for offset in range(0, len(byte_characters), 512):
slice_ = byte_characters[offset:offset + 512]
byte_array = bytearray(slice_)
byte_arrays.append(byte_array)

# combine byte arrays and wrap in a BytesIO object
formatted_content = BytesIO(b"".join(byte_arrays))
formatted_content.seek(0) # reset cursor for any further reading
elif options_format == 'text':
formatted_content = content.encode("utf-8")
else:
formatted_content = content

await obs.put_async(self._content_managers[drive_name], path, formatted_content, mode = "overwrite")
metadata = await obs.head_async(self._content_managers[drive_name], path)

data = {
"path": path,
"content": content,
"last_modified": metadata["last_modified"].isoformat(),
"size": metadata["size"]
}
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason=f"The following error occured when saving the file: {e}",
)

response = {
"data": data
}
return response

async def rename_file(self, drive_name, path, **kwargs):
async def rename_file(self, drive_name, path, new_path):
"""Rename a file.

Args:
drive_name: name of drive where file is located
path: path of file
DenisaCG marked this conversation as resolved.
Show resolved Hide resolved
new_path: path of new file name
"""
print('Rename file function called.')
data = {}
try:
# eliminate leading and trailing backslashes
path = path.strip('/')

await obs.rename_async(self._content_managers[drive_name], path, new_path)
metadata = await obs.head_async(self._content_managers[drive_name], new_path)

data = {
"path": new_path,
"last_modified": metadata["last_modified"].isoformat(),
"size": metadata["size"]
}
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason=f"The following error occured when renaming the object: {e}",
)

response = {
"data": data
}
return response

async def delete_file(self, drive_name, path):
"""Delete an object.

Args:
drive_name: name of drive where object exists
path: path where content is located
"""
try:
# eliminate leading and trailing backslashes
path = path.strip('/')
await obs.delete_async(self._content_managers[drive_name], path)

except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason=f"The following error occured when deleting the object: {e}",
)

return

async def check_file(self, drive_name, path):
"""Check if an object already exists within a drive.

Args:
drive_name: name of drive where object exists
path: path where content is located
"""
try:
# eliminate leading and trailing backslashes
path = path.strip('/')
await obs.head_async(self._content_managers[drive_name], path)
except Exception:
raise tornado.web.HTTPError(
status_code= httpx.codes.NOT_FOUND,
reason="Object does not already exist within drive.",
)

return

async def copy_file(self, drive_name, path, to_path):
"""Save file with new content.

Args:
drive_name: name of drive where file exists
path: path where original content exists
to_path: path where object should be copied
"""
data = {}
try:
# eliminate leading and trailing backslashes
path = path.strip('/')

await obs.copy_async(self._content_managers[drive_name], path, to_path)
metadata = await obs.head_async(self._content_managers[drive_name], to_path)

data = {
"path": to_path,
"last_modified": metadata["last_modified"].isoformat(),
"size": metadata["size"]
}
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason=f"The following error occured when copying the: {e}",
)

response = {
"data": data
}
return response

async def _call_provider(
self,
Expand Down
Loading
Loading