Skip to content

Commit

Permalink
Merge pull request #21 from DenisaCG/multipleDrives
Browse files Browse the repository at this point in the history
Set up drives list provider plugin
  • Loading branch information
DenisaCG authored Nov 11, 2024
2 parents 61c372d + b6e30dc commit 40e4ed8
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 105 deletions.
16 changes: 10 additions & 6 deletions jupyter_drives/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,36 @@ def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
@tornado.web.authenticated
async def get(self):
result = await self._manager.list_drives()
self.finish(json.dumps(result))
self.finish(result)

@tornado.web.authenticated
async def post(self):
body = self.get_json_body()
result = await self._manager.mount_drive(**body)
self.finish(json.dump(result.message))
self.finish(result["message"])

class ContentsJupyterDrivesHandler(JupyterDrivesAPIHandler):
"""
Deals with contents of a drive.
"""
def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
return super().initialize(logger, manager)

@tornado.web.authenticated
async def get(self, path: str = "", drive: str = ""):
result = await self._manager.get_contents(drive, path)
self.finish(json.dump(result))
self.finish(result)

@tornado.web.authenticated
async def post(self, path: str = "", drive: str = ""):
result = await self._manager.new_file(drive, path)
self.finish(json.dump(result))
self.finish(result)

@tornado.web.authenticated
async def patch(self, path: str = "", drive: str = ""):
body = self.get_json_body()
result = await self._manager.rename_file(drive, path, **body)
self.finish(json.dump(result))
self.finish(result)

handlers = [
("drives", ListJupyterDrivesHandler)
Expand Down Expand Up @@ -121,9 +124,10 @@ def setup_handlers(web_app: tornado.web.Application, config: traitlets.config.Co
+ [
(
url_path_join(
base_url, NAMESPACE, pattern, r"(?P<drive>\w+)", path_regex
base_url, NAMESPACE, pattern, r"(?P<drive>(?:[^/]+))"+ path_regex
),
handler,
{"logger": log, "manager": manager}
)
for pattern, handler in handlers_with_path
]
Expand Down
17 changes: 12 additions & 5 deletions jupyter_drives/managers/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ async def list_drives(self):
"code": 200
}
else:
response = {"code": 400}
response = {"code": 400, "message": "No AWS credentials specified. Please set them in your user jupyter_server_config file."}
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason="No AWS credentials specified. Please set them in your user jupyter_server_config file.",
)

return response

async def mount_drive(self, drive_name):
Expand All @@ -85,13 +85,20 @@ async def mount_drive(self, drive_name):
S3ContentsManager
'''
try :
s3_contents_manager = S3ContentsManager(
access_key_id = self._config.access_key_id,
secret_access_key = self._config.secret_access_key,
endpoint_url = self._config.api_base_url,
bucket = drive_name
)

# checking if the drive wasn't mounted already
if self.s3_content_managers[drive_name] is None:
if drive_name not in self.s3_content_managers or self.s3_content_managers[drive_name] is None:

# dealing with long-term credentials (access key, secret key)
if self._config.session_token is None:
s3_contents_manager = S3ContentsManager(
access_key = self._config.access_key_id,
access_key_id = self._config.access_key_id,
secret_access_key = self._config.secret_access_key,
endpoint_url = self._config.api_base_url,
bucket = drive_name
Expand All @@ -100,7 +107,7 @@ async def mount_drive(self, drive_name):
# dealing with short-term credentials (access key, secret key, session token)
else:
s3_contents_manager = S3ContentsManager(
access_key = self._config.access_key_id,
access_key_id = self._config.access_key_id,
secret_access_key = self._config.secret_access_key,
session_token = self._config.session_token,
endpoint_url = self._config.api_base_url,
Expand Down
69 changes: 68 additions & 1 deletion src/contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import { Signal, ISignal } from '@lumino/signaling';
import { Contents, ServerConnection } from '@jupyterlab/services';
import { PathExt } from '@jupyterlab/coreutils';
import { IDriveInfo } from './token';

const data: Contents.IModel = {
let data: Contents.IModel = {
name: '',
path: '',
last_modified: '',
Expand All @@ -26,8 +28,24 @@ export class Drive implements Contents.IDrive {
constructor(options: Drive.IOptions = {}) {
this._serverSettings = ServerConnection.makeSettings();
this._name = options.name ?? '';
this._drivesList = options.drivesList ?? [];
//this._apiEndpoint = options.apiEndpoint ?? SERVICE_DRIVE_URL;
}

/**
* The drives list getter.
*/
get drivesList(): IDriveInfo[] {
return this._drivesList;
}

/**
* The drives list setter.
* */
set drivesList(list: IDriveInfo[]) {
this._drivesList = list;
}

/**
* The Drive base URL
*/
Expand All @@ -41,6 +59,7 @@ export class Drive implements Contents.IDrive {
set baseUrl(url: string) {
this._baseUrl = url;
}

/**
* The Drive name getter
*/
Expand Down Expand Up @@ -170,6 +189,48 @@ export class Drive implements Contents.IDrive {
} else {
relativePath = localPath;
}

data = {
name: PathExt.basename(localPath),
path: PathExt.basename(localPath),
last_modified: '',
created: '',
content: [],
format: 'json',
mimetype: '',
size: undefined,
writable: true,
type: 'directory'
};
} else {
const drivesList: Contents.IModel[] = [];
for (const drive of this._drivesList) {
drivesList.push({
name: drive.name,
path: drive.name,
last_modified: '',
created: drive.creationDate,
content: [],
format: 'json',
mimetype: '',
size: undefined,
writable: true,
type: 'directory'
});
}

data = {
name: this._name,
path: this._name,
last_modified: '',
created: '',
content: drivesList,
format: 'json',
mimetype: '',
size: undefined,
writable: true,
type: 'directory'
};
}
console.log('GET: ', relativePath);

Expand Down Expand Up @@ -532,6 +593,7 @@ export class Drive implements Contents.IDrive {
}*/

// private _apiEndpoint: string;
private _drivesList: IDriveInfo[] = [];
private _serverSettings: ServerConnection.ISettings;
private _name: string = '';
private _provider: string = '';
Expand All @@ -548,6 +610,11 @@ export namespace Drive {
* The options used to initialize a `Drive`.
*/
export interface IOptions {
/**
* List of available drives.
*/
drivesList?: IDriveInfo[];

/**
* The name for the `Drive`, which is used in file
* paths to disambiguate it from other drives.
Expand Down
81 changes: 0 additions & 81 deletions src/drives.ts

This file was deleted.

54 changes: 45 additions & 9 deletions src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,81 @@
import { URLExt } from '@jupyterlab/coreutils';

import { ServerConnection } from '@jupyterlab/services';
import { ReadonlyJSONObject } from '@lumino/coreutils';

import { DrivesResponseError } from './drivesError';

/**
* Array of Jupyter Drives Auth Error Messages.
*/
export const AUTH_ERROR_MESSAGES = [
'Invalid access key or secret access key',
'could not read Access Key',
'could not read Secret Access Key',
'could not read Session Token',
'Authentication error'
];

/**
* Call the API extension
*
* @param endPoint API REST end point for the extension
* @param init Initial values for the request
* @param endPoint API REST end point for the extension; default ''
* @param method HTML method; default 'GET'
* @param body JSON object to be passed as body or null; default null
* @param namespace API namespace; default 'git'
* @returns The response body interpreted as JSON
*
* @throws {ServerConnection.NetworkError} If the request cannot be made
*/
export async function requestAPI<T>(
endPoint = '',
init: RequestInit = {}
method = 'GET',
body: Partial<ReadonlyJSONObject> | null = null,
namespace = 'jupyter-drives'
): Promise<T> {
// Make request to Jupyter API
const settings = ServerConnection.makeSettings();
const requestUrl = URLExt.join(
settings.baseUrl,
'jupyter-drives', // API Namespace
namespace, // API Namespace
endPoint
);

const init: RequestInit = {
method,
body: body ? JSON.stringify(body) : undefined
};

let response: Response;
try {
response = await ServerConnection.makeRequest(requestUrl, init, settings);
} catch (error) {
throw new ServerConnection.NetworkError(error as any);
} catch (error: any) {
throw new ServerConnection.NetworkError(error);
}

let data: any = await response.text();

let isJSON = false;
if (data.length > 0) {
try {
data = JSON.parse(data);
isJSON = true;
} catch (error) {
console.log('Not a JSON response body.', response);
}
}

if (!response.ok) {
throw new ServerConnection.ResponseError(response, data.message || data);
if (isJSON) {
const { message, traceback, ...json } = data;
throw new DrivesResponseError(
response,
message ||
`Invalid response: ${response.status} ${response.statusText}`,
traceback || '',
json
);
} else {
throw new DrivesResponseError(response, data);
}
}

return data;
Expand Down
Loading

0 comments on commit 40e4ed8

Please sign in to comment.