From 13e0ea1e4c0b71c0adc40565f48d36cce7f4f773 Mon Sep 17 00:00:00 2001 From: James Reeve Date: Sun, 19 Jun 2022 18:06:35 -0400 Subject: [PATCH 1/2] feat(serverextension): add files endpoint, rename contents endpoint --- jupyterlab_s3_browser/handlers.py | 44 +++++++++++++++++++++++++++++-- src/s3.ts | 17 ++++++------ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/jupyterlab_s3_browser/handlers.py b/jupyterlab_s3_browser/handlers.py index a611be7..a3ff3de 100644 --- a/jupyterlab_s3_browser/handlers.py +++ b/jupyterlab_s3_browser/handlers.py @@ -199,8 +199,47 @@ def convertS3FStoJupyterFormat(result): "type": result["type"], } +class FilesHandler(APIHandler): + """ + Handles requests for getting files (e.g. for downloading) + """ -class S3Handler(APIHandler): + @property + def config(self): + return self.settings["s3_config"] + + @tornado.web.authenticated + def get(self, path=""): + """ + Takes a path and returns lists of files/objects + and directories/prefixes based on the path. + """ + path = path.removeprefix("/") + + try: + if not self.s3fs: + self.s3fs = create_s3fs(self.config) + + self.s3fs.invalidate_cache() + + with self.s3fs.open(path, "rb") as f: + result = f.read() + + except S3ResourceNotFoundException as e: + result = json.dumps({ + "error": 404, + "message": "The requested resource could not be found.", + }) + except Exception as e: + logging.error("Exception encountered during GET {}: {}".format(path, e)) + result = json.dumps({"error": 500, "message": str(e)}) + + self.finish(result) + + s3fs = None + s3_resource = None + +class ContentsHandler(APIHandler): """ Handles requests for getting S3 objects """ @@ -388,6 +427,7 @@ def setup_handlers(web_app): base_url = web_app.settings["base_url"] handlers = [ (url_path_join(base_url, "jupyterlab_s3_browser", "auth(.*)"), AuthHandler), - (url_path_join(base_url, "jupyterlab_s3_browser", "files(.*)"), S3Handler), + (url_path_join(base_url, "jupyterlab_s3_browser", "contents(.*)"), ContentsHandler), + (url_path_join(base_url, "jupyterlab_s3_browser", "files(.*)"), FilesHandler), ] web_app.add_handlers(host_pattern, handlers) diff --git a/src/s3.ts b/src/s3.ts index d51eb9c..5d9143f 100644 --- a/src/s3.ts +++ b/src/s3.ts @@ -13,7 +13,7 @@ export async function copyFile( const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', newPath), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', newPath), { method: 'PUT', headers: { 'X-Custom-S3-Copy-Src': oldPath } }, settings ) @@ -29,7 +29,7 @@ export async function moveFile( const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', newPath), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', newPath), { method: 'PUT', headers: { 'X-Custom-S3-Move-Src': oldPath } }, settings ) @@ -42,7 +42,7 @@ export async function deleteFile(path: string): Promise { const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'DELETE' }, settings ) @@ -58,7 +58,7 @@ export async function writeFile( const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'PUT', body: JSON.stringify({ content }) }, settings ) @@ -70,7 +70,7 @@ export async function createDirectory(path: string): Promise { const settings = ServerConnection.makeSettings(); // can be stored as class var await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'PUT', headers: { 'X-Custom-S3-Is-Dir': 'true' } }, settings ) @@ -97,7 +97,7 @@ export async function get( const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'GET' }, settings ) @@ -124,7 +124,7 @@ export async function ls(path: string): Promise { const settings = ServerConnection.makeSettings(); // can be stored as class var const response = await ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'GET', headers: { 'X-Custom-S3-Is-Dir': 'true' } }, settings ) @@ -146,11 +146,10 @@ export async function ls(path: string): Promise { } export async function read(path: string): Promise { - // pass const settings = ServerConnection.makeSettings(); // can be stored as class var const response = ( await ServerConnection.makeRequest( - URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path), + URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/contents', path), { method: 'GET' }, settings ) From 18da81e218e7918ef00d8c2b6907c920a39f07db Mon Sep 17 00:00:00 2001 From: James Reeve Date: Sun, 19 Jun 2022 18:08:53 -0400 Subject: [PATCH 2/2] feat(downloading): add support for downloading files --- src/contents.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/contents.ts b/src/contents.ts index 788e598..2de891b 100755 --- a/src/contents.ts +++ b/src/contents.ts @@ -6,12 +6,12 @@ import { DocumentRegistry } from '@jupyterlab/docregistry'; import { Contents, ServerConnection } from '@jupyterlab/services'; +import { URLExt } from '@jupyterlab/coreutils'; + import * as base64js from 'base64-js'; import * as s3 from './s3'; -import { Dialog, showDialog } from '@jupyterlab/apputils'; - /** * A Contents.IDrive implementation for s3-api-compatible object storage. */ @@ -129,12 +129,8 @@ export class S3Drive implements Contents.IDrive { * path if necessary. */ async getDownloadUrl(path: string): Promise { - await showDialog({ - title: 'Sorry', - body: 'This feature is not yet implemented.', - buttons: [Dialog.cancelButton({ label: 'Cancel' })] - }); - throw Error('Not yet implemented'); + const settings = ServerConnection.makeSettings(); + return URLExt.join(settings.baseUrl, 'jupyterlab_s3_browser/files', path); } /**