Skip to content

Commit 40e4ed8

Browse files
authored
Merge pull request #21 from DenisaCG/multipleDrives
Set up drives list provider plugin
2 parents 61c372d + b6e30dc commit 40e4ed8

File tree

9 files changed

+201
-105
lines changed

9 files changed

+201
-105
lines changed

jupyter_drives/handlers.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,33 +53,36 @@ def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
5353
@tornado.web.authenticated
5454
async def get(self):
5555
result = await self._manager.list_drives()
56-
self.finish(json.dumps(result))
56+
self.finish(result)
5757

5858
@tornado.web.authenticated
5959
async def post(self):
6060
body = self.get_json_body()
6161
result = await self._manager.mount_drive(**body)
62-
self.finish(json.dump(result.message))
62+
self.finish(result["message"])
6363

6464
class ContentsJupyterDrivesHandler(JupyterDrivesAPIHandler):
6565
"""
6666
Deals with contents of a drive.
6767
"""
68+
def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
69+
return super().initialize(logger, manager)
70+
6871
@tornado.web.authenticated
6972
async def get(self, path: str = "", drive: str = ""):
7073
result = await self._manager.get_contents(drive, path)
71-
self.finish(json.dump(result))
74+
self.finish(result)
7275

7376
@tornado.web.authenticated
7477
async def post(self, path: str = "", drive: str = ""):
7578
result = await self._manager.new_file(drive, path)
76-
self.finish(json.dump(result))
79+
self.finish(result)
7780

7881
@tornado.web.authenticated
7982
async def patch(self, path: str = "", drive: str = ""):
8083
body = self.get_json_body()
8184
result = await self._manager.rename_file(drive, path, **body)
82-
self.finish(json.dump(result))
85+
self.finish(result)
8386

8487
handlers = [
8588
("drives", ListJupyterDrivesHandler)
@@ -121,9 +124,10 @@ def setup_handlers(web_app: tornado.web.Application, config: traitlets.config.Co
121124
+ [
122125
(
123126
url_path_join(
124-
base_url, NAMESPACE, pattern, r"(?P<drive>\w+)", path_regex
127+
base_url, NAMESPACE, pattern, r"(?P<drive>(?:[^/]+))"+ path_regex
125128
),
126129
handler,
130+
{"logger": log, "manager": manager}
127131
)
128132
for pattern, handler in handlers_with_path
129133
]

jupyter_drives/managers/s3.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ async def list_drives(self):
6767
"code": 200
6868
}
6969
else:
70-
response = {"code": 400}
70+
response = {"code": 400, "message": "No AWS credentials specified. Please set them in your user jupyter_server_config file."}
7171
raise tornado.web.HTTPError(
7272
status_code= httpx.codes.BAD_REQUEST,
7373
reason="No AWS credentials specified. Please set them in your user jupyter_server_config file.",
7474
)
75-
75+
7676
return response
7777

7878
async def mount_drive(self, drive_name):
@@ -85,13 +85,20 @@ async def mount_drive(self, drive_name):
8585
S3ContentsManager
8686
'''
8787
try :
88+
s3_contents_manager = S3ContentsManager(
89+
access_key_id = self._config.access_key_id,
90+
secret_access_key = self._config.secret_access_key,
91+
endpoint_url = self._config.api_base_url,
92+
bucket = drive_name
93+
)
94+
8895
# checking if the drive wasn't mounted already
89-
if self.s3_content_managers[drive_name] is None:
96+
if drive_name not in self.s3_content_managers or self.s3_content_managers[drive_name] is None:
9097

9198
# dealing with long-term credentials (access key, secret key)
9299
if self._config.session_token is None:
93100
s3_contents_manager = S3ContentsManager(
94-
access_key = self._config.access_key_id,
101+
access_key_id = self._config.access_key_id,
95102
secret_access_key = self._config.secret_access_key,
96103
endpoint_url = self._config.api_base_url,
97104
bucket = drive_name
@@ -100,7 +107,7 @@ async def mount_drive(self, drive_name):
100107
# dealing with short-term credentials (access key, secret key, session token)
101108
else:
102109
s3_contents_manager = S3ContentsManager(
103-
access_key = self._config.access_key_id,
110+
access_key_id = self._config.access_key_id,
104111
secret_access_key = self._config.secret_access_key,
105112
session_token = self._config.session_token,
106113
endpoint_url = self._config.api_base_url,

src/contents.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
import { Signal, ISignal } from '@lumino/signaling';
55
import { Contents, ServerConnection } from '@jupyterlab/services';
6+
import { PathExt } from '@jupyterlab/coreutils';
7+
import { IDriveInfo } from './token';
68

7-
const data: Contents.IModel = {
9+
let data: Contents.IModel = {
810
name: '',
911
path: '',
1012
last_modified: '',
@@ -26,8 +28,24 @@ export class Drive implements Contents.IDrive {
2628
constructor(options: Drive.IOptions = {}) {
2729
this._serverSettings = ServerConnection.makeSettings();
2830
this._name = options.name ?? '';
31+
this._drivesList = options.drivesList ?? [];
2932
//this._apiEndpoint = options.apiEndpoint ?? SERVICE_DRIVE_URL;
3033
}
34+
35+
/**
36+
* The drives list getter.
37+
*/
38+
get drivesList(): IDriveInfo[] {
39+
return this._drivesList;
40+
}
41+
42+
/**
43+
* The drives list setter.
44+
* */
45+
set drivesList(list: IDriveInfo[]) {
46+
this._drivesList = list;
47+
}
48+
3149
/**
3250
* The Drive base URL
3351
*/
@@ -41,6 +59,7 @@ export class Drive implements Contents.IDrive {
4159
set baseUrl(url: string) {
4260
this._baseUrl = url;
4361
}
62+
4463
/**
4564
* The Drive name getter
4665
*/
@@ -170,6 +189,48 @@ export class Drive implements Contents.IDrive {
170189
} else {
171190
relativePath = localPath;
172191
}
192+
193+
data = {
194+
name: PathExt.basename(localPath),
195+
path: PathExt.basename(localPath),
196+
last_modified: '',
197+
created: '',
198+
content: [],
199+
format: 'json',
200+
mimetype: '',
201+
size: undefined,
202+
writable: true,
203+
type: 'directory'
204+
};
205+
} else {
206+
const drivesList: Contents.IModel[] = [];
207+
for (const drive of this._drivesList) {
208+
drivesList.push({
209+
name: drive.name,
210+
path: drive.name,
211+
last_modified: '',
212+
created: drive.creationDate,
213+
content: [],
214+
format: 'json',
215+
mimetype: '',
216+
size: undefined,
217+
writable: true,
218+
type: 'directory'
219+
});
220+
}
221+
222+
data = {
223+
name: this._name,
224+
path: this._name,
225+
last_modified: '',
226+
created: '',
227+
content: drivesList,
228+
format: 'json',
229+
mimetype: '',
230+
size: undefined,
231+
writable: true,
232+
type: 'directory'
233+
};
173234
}
174235
console.log('GET: ', relativePath);
175236

@@ -532,6 +593,7 @@ export class Drive implements Contents.IDrive {
532593
}*/
533594

534595
// private _apiEndpoint: string;
596+
private _drivesList: IDriveInfo[] = [];
535597
private _serverSettings: ServerConnection.ISettings;
536598
private _name: string = '';
537599
private _provider: string = '';
@@ -548,6 +610,11 @@ export namespace Drive {
548610
* The options used to initialize a `Drive`.
549611
*/
550612
export interface IOptions {
613+
/**
614+
* List of available drives.
615+
*/
616+
drivesList?: IDriveInfo[];
617+
551618
/**
552619
* The name for the `Drive`, which is used in file
553620
* paths to disambiguate it from other drives.

src/drives.ts

Lines changed: 0 additions & 81 deletions
This file was deleted.

src/handler.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,81 @@
11
import { URLExt } from '@jupyterlab/coreutils';
2-
32
import { ServerConnection } from '@jupyterlab/services';
3+
import { ReadonlyJSONObject } from '@lumino/coreutils';
4+
5+
import { DrivesResponseError } from './drivesError';
6+
7+
/**
8+
* Array of Jupyter Drives Auth Error Messages.
9+
*/
10+
export const AUTH_ERROR_MESSAGES = [
11+
'Invalid access key or secret access key',
12+
'could not read Access Key',
13+
'could not read Secret Access Key',
14+
'could not read Session Token',
15+
'Authentication error'
16+
];
417

518
/**
619
* Call the API extension
720
*
8-
* @param endPoint API REST end point for the extension
9-
* @param init Initial values for the request
21+
* @param endPoint API REST end point for the extension; default ''
22+
* @param method HTML method; default 'GET'
23+
* @param body JSON object to be passed as body or null; default null
24+
* @param namespace API namespace; default 'git'
1025
* @returns The response body interpreted as JSON
26+
*
27+
* @throws {ServerConnection.NetworkError} If the request cannot be made
1128
*/
1229
export async function requestAPI<T>(
1330
endPoint = '',
14-
init: RequestInit = {}
31+
method = 'GET',
32+
body: Partial<ReadonlyJSONObject> | null = null,
33+
namespace = 'jupyter-drives'
1534
): Promise<T> {
1635
// Make request to Jupyter API
1736
const settings = ServerConnection.makeSettings();
1837
const requestUrl = URLExt.join(
1938
settings.baseUrl,
20-
'jupyter-drives', // API Namespace
39+
namespace, // API Namespace
2140
endPoint
2241
);
2342

43+
const init: RequestInit = {
44+
method,
45+
body: body ? JSON.stringify(body) : undefined
46+
};
47+
2448
let response: Response;
2549
try {
2650
response = await ServerConnection.makeRequest(requestUrl, init, settings);
27-
} catch (error) {
28-
throw new ServerConnection.NetworkError(error as any);
51+
} catch (error: any) {
52+
throw new ServerConnection.NetworkError(error);
2953
}
3054

3155
let data: any = await response.text();
32-
56+
let isJSON = false;
3357
if (data.length > 0) {
3458
try {
3559
data = JSON.parse(data);
60+
isJSON = true;
3661
} catch (error) {
3762
console.log('Not a JSON response body.', response);
3863
}
3964
}
4065

4166
if (!response.ok) {
42-
throw new ServerConnection.ResponseError(response, data.message || data);
67+
if (isJSON) {
68+
const { message, traceback, ...json } = data;
69+
throw new DrivesResponseError(
70+
response,
71+
message ||
72+
`Invalid response: ${response.status} ${response.statusText}`,
73+
traceback || '',
74+
json
75+
);
76+
} else {
77+
throw new DrivesResponseError(response, data);
78+
}
4379
}
4480

4581
return data;

0 commit comments

Comments
 (0)