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

Set up drives list provider plugin #21

Merged
merged 10 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading