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

Try to get the list of drives and their contents #15

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ec60b00
Replace mock_s3 by mock_aws in test_handlers.py.
HaudinFlorence Mar 18, 2024
8dbc658
Create a new Drive class and a DefaultAndDrivesFileBrowser SidePanel …
HaudinFlorence Nov 7, 2023
0e0d037
Merge the dialog and the filebrowser plugins into one single called A…
HaudinFlorence Nov 9, 2023
e53ca9e
Try to add a toolbar to the default browser.
HaudinFlorence Nov 13, 2023
3723b6a
Take comments into account concerning adding the toolbar of the browser.
HaudinFlorence Nov 13, 2023
e017f9d
Introduce changes to keep all the attributes required by the IDrive i…
HaudinFlorence Nov 15, 2023
ccc9188
Try to modify the filebrowser to handle several Dirlistings.
HaudinFlorence Nov 17, 2023
f7c0b82
Introduce the DriveDirlisting class
HaudinFlorence Nov 28, 2023
0eeeb47
Rename class DriveListing -> DriveBrowser, rename file browser.ts -> …
HaudinFlorence Nov 29, 2023
8f32f61
Rename class DriveListing -> DriveBrowser, rename file browser.ts -> …
HaudinFlorence Nov 29, 2023
7833d0d
Display each drive filebrowser in a separate side panel.
HaudinFlorence Dec 5, 2023
ef34b57
Try to implement the Untitled method in Drive class in contents.ts an…
HaudinFlorence Dec 7, 2023
3488775
Try to implement the delete method in Drive class in contents.ts.
HaudinFlorence Dec 11, 2023
ceba8dc
Try to implement the rename method in Drive class contents.ts
HaudinFlorence Dec 12, 2023
812c6ad
Try to implement a copy method in Drive class in contents.ts.
HaudinFlorence Dec 14, 2023
f3ea49f
Add a command removeDriveBrowser and the relative logics to remove a …
HaudinFlorence Dec 15, 2023
3c34167
Move the logics to add a drive filebrowser in a command named AddDriv…
HaudinFlorence Dec 15, 2023
d6119d2
Add logics to dispose a drive in the command RemoveDriveBrowser.
HaudinFlorence Dec 18, 2023
accaf6d
Add a disposed signal on Drive and use it for disposing the proper si…
HaudinFlorence Dec 19, 2023
2f224b2
Add a getDrivesList request and a createDrivesList in index.ts to get…
HaudinFlorence Dec 21, 2023
73409c8
Restore logics to open a dialog to select the drives to be added.
HaudinFlorence Dec 29, 2023
44c7f89
Fix bug with the toolbar and the error message related to setFilter a…
HaudinFlorence Jan 16, 2024
39ba3d0
Implement a method getDriveContent to get the content of the drives.
HaudinFlorence Jan 18, 2024
1dd9969
Add a method postDriveMounted to mount the drive and successfully mou…
HaudinFlorence Mar 5, 2024
b7100c8
Adapt the regex for the url for the handler with path to manage to ge…
HaudinFlorence Mar 11, 2024
3a91d96
Get credentials from a config file.
HaudinFlorence Mar 12, 2024
5c27866
Update the get method of the Drive class to be able to browser into s…
HaudinFlorence Mar 15, 2024
1fb7a20
Update test_handlers.py.
HaudinFlorence Mar 18, 2024
7a2f10f
Try to failing test.
HaudinFlorence Mar 18, 2024
1294b81
Deplace the activation message of AddDrives plugin before the call of…
HaudinFlorence Mar 20, 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
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ jobs:

jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "@jupyter/drives.*OK"
python -m jupyterlab.browser_check

- name: Package the extension
run: |
Expand Down Expand Up @@ -87,7 +86,6 @@ jobs:

jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "@jupyter/drives.*OK"
python -m jupyterlab.browser_check --no-browser-test

integration-tests:
name: Integration tests
Expand Down
6 changes: 3 additions & 3 deletions jupyter_drives/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class DrivesConfig(Configurable):
config = True,
help = "Region name.",
)

api_base_url = Unicode(
config=True,
help="Base URL of the provider service REST API.",
Expand All @@ -53,7 +53,7 @@ class DrivesConfig(Configurable):
allow_none = True,
help="Custom path of file where credentials are located. Extension automatically checks jupyter_notebook_config.py or directly in ~/.aws/credentials for AWS CLI users."
)

@default("api_base_url")
def set_default_api_base_url(self):
# for AWS S3 drives
Expand Down Expand Up @@ -87,7 +87,7 @@ def _load_credentials(self):

# if not, try to load credentials from AWS CLI
aws_credentials_path = "~/.aws/credentials" #add read me about credentials path in windows: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html
if os.path_exists(aws_credentials_path):
if os.path.exists(aws_credentials_path):
self.access_key_id, self.secret_access_key, self.session_token = self._extract_credentials_from_file(aws_credentials_path)
return

Expand Down
18 changes: 11 additions & 7 deletions jupyter_drives/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,25 @@ def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
async def get(self):
result = await self._manager.list_drives()
self.finish(json.dumps(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 = ""):
Expand All @@ -92,11 +96,11 @@ async def patch(self, path: str = "", drive: str = ""):
def setup_handlers(web_app: tornado.web.Application, config: traitlets.config.Config, log: Optional[logging.Logger] = None):
host_pattern = ".*$"
base_url = web_app.settings["base_url"]

log = log or logging.getLogger(__name__)

provider = DrivesConfig(config=config).provider
entry_point = MANAGERS.get(provider)

if entry_point is None:
log.error(f"JupyterDrives Manager: No manager defined for provider '{provider}'.")
raise NotImplementedError()
Expand All @@ -121,14 +125,14 @@ 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
]
)

log.debug(f"Jupyter-Drives Handlers: {drives_handlers}")


web_app.add_handlers(host_pattern, drives_handlers)

22 changes: 15 additions & 7 deletions jupyter_drives/managers/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from ..base import DrivesConfig
from .manager import JupyterDrivesManager


class S3Manager(JupyterDrivesManager):
"""Jupyter drives manager for S3 drives."""

Expand Down Expand Up @@ -47,11 +46,11 @@ async def list_drives(self):
if (self._config.access_key_id and self._config.secret_access_key):
S3Drive = get_driver(Provider.S3)
drives = [S3Drive(self._config.access_key_id, self._config.secret_access_key)]

results = []

for drive in drives:
results += drive.list_containers()

for result in results:
data.append(
{
Expand Down Expand Up @@ -84,14 +83,22 @@ async def mount_drive(self, drive_name):
Args:
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 All @@ -119,7 +126,7 @@ async def mount_drive(self, drive_name):

except Exception as e:
response = {"code": 400, "message": e}

return response

async def unmount_drive(self, drive_name):
Expand Down Expand Up @@ -165,6 +172,7 @@ async def get_contents(self, drive_name, path = ""):
response["code"] = code
return response


async def new_file(self, drive_name, type = "notebook", path = ""):
'''Create a new file or directory from an S3 drive.

Expand Down
30 changes: 15 additions & 15 deletions jupyter_drives/tests/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os


from moto import mock_s3
from moto import mock_aws
from moto.moto_server.threaded_moto_server import ThreadedMotoServer
from libcloud.storage.types import Provider
from libcloud.storage.providers import get_driver
Expand All @@ -17,20 +17,19 @@ def s3_base():
os.environ["access_key_id"] = 'access_key'
os.environ["secret_access_key"] = 'secret_key'

with mock_s3():
with mock_aws():
S3Drive = get_driver(Provider.S3)
drive = S3Drive('access_key', 'secret_key')

yield drive

@pytest.mark.skip(reason="FIX")
async def test_ListJupyterDrives_s3_success(jp_fetch, s3_base):
with mock_s3():
with mock_aws():
# extract S3 drive
drive = s3_base

test_bucket_name_1 = "jupyter-drives-test-bucket-1"
test_bucket_name_2 = "jupyter-drives-test-bucket-2"
test_bucket_name_1 = "jupyter-drive-bucket1"
test_bucket_name_2 = "jupyter-drive-bucket2"

# Create some test containers
drive.create_container(test_bucket_name_1)
Expand All @@ -42,11 +41,12 @@ async def test_ListJupyterDrives_s3_success(jp_fetch, s3_base):
# Then
assert response.code == 200
payload = json.loads(response.body)
assert "jupyter-drives-test-bucket-1" in payload["data"]
assert "jupyter-drives-test-bucket-2" in payload["data"]
assert "jupyter-drive-bucket1" in payload["data"]
assert "jupyter-drive-bucket2" in payload["data"]

@pytest.mark.skip(reason="FIX")
async def test_ListJupyterDrives_s3_empty_list(jp_fetch, s3_base):
with mock_s3():
with mock_aws():
# extract S3 drive
drive = s3_base

Expand All @@ -60,7 +60,7 @@ async def test_ListJupyterDrives_s3_empty_list(jp_fetch, s3_base):

@pytest.mark.skip(reason="FIX")
async def test_ListJupyterDrives_s3_missing_credentials(jp_fetch, s3_base):
with mock_s3():
with mock_aws():
# When
with pytest.raises(tornado.web.HTTPError) as exc_info:
response = await jp_fetch("jupyter-drives", "drives")
Expand All @@ -70,11 +70,11 @@ async def test_ListJupyterDrives_s3_missing_credentials(jp_fetch, s3_base):

@pytest.mark.skip(reason="FIX")
async def test_MountJupyterDriveHandler(jp_fetch, s3_base):
with mock_s3():
with mock_aws():
drive = s3_base

# Create test container to mount
test_bucket_name_1 = "jupyter-drives-test-bucket-1"
test_bucket_name_1 = "jupyter-drive-bucket1"
drive.create_container(test_bucket_name_1)

# When
Expand All @@ -85,16 +85,16 @@ async def test_MountJupyterDriveHandler(jp_fetch, s3_base):

@pytest.mark.skip(reason="ToBeImplemented")
async def test_UnmountJupyterDriveHandler(jp_fetch, s3_base):
with mock_s3():
with mock_aws():
# extract S3 drive
drive = s3_base

# Create test container to mount
test_bucket_name_1 = "jupyter-drives-test-bucket-1"
test_bucket_name_1 = "jupyter-drive-bucket1"
drive.create_container(test_bucket_name_1)

# When
body = {"drive_name": "jupyter-drives-test-bucket-1", "mount_drive": "false" }
body = {"drive_name": "jupyter-drive-bucket1", "mount_drive": "false" }
response = await jp_fetch("jupyter-drives", "drives", body = json.dumps(body), method = "POST")

assert response["code"] == 204
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.2.0",
"jupyterlab-unfold": "0.3.0",
"mkdirp": "^1.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
Expand Down
67 changes: 64 additions & 3 deletions schema/widget.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"jupyter.lab.toolbars": {
"FileBrowser": [
"DrivePanel": [
{
"name": "new-directory",
"command": "filebrowser:create-new-directory",
Expand All @@ -19,6 +19,67 @@
"title": "'@jupyter/drives",
"description": "jupyter-drives settings.",
"type": "object",
"properties": {},
"additionalProperties": false
"jupyter.lab.transform": true,
"properties": {
"toolbar": {
"title": "File browser toolbar items",
"description": "Note: To disable a toolbar item,\ncopy it to User Preferences and add the\n\"disabled\" key. The following example will disable the uploader button:\n{\n \"toolbar\": [\n {\n \"name\": \"uploader\",\n \"disabled\": true\n }\n ]\n}\n\nToolbar description:",
"items": {
"$ref": "#/definitions/toolbarItem"
},
"type": "array",
"default": []
}
},
"additionalProperties": false,
"definitions": {
"toolbarItem": {
"properties": {
"name": {
"title": "Unique name",
"type": "string"
},
"args": {
"title": "Command arguments",
"type": "object"
},
"command": {
"title": "Command id",
"type": "string",
"default": ""
},
"disabled": {
"title": "Whether the item is ignored or not",
"type": "boolean",
"default": false
},
"icon": {
"title": "Item icon id",
"description": "If defined, it will override the command icon",
"type": "string"
},
"label": {
"title": "Item label",
"description": "If defined, it will override the command label",
"type": "string"
},
"caption": {
"title": "Item caption",
"description": "If defined, it will override the command caption",
"type": "string"
},
"type": {
"title": "Item type",
"type": "string",
"enum": ["command", "spacer"]
},
"rank": {
"title": "Item rank",
"type": "number",
"minimum": 0,
"default": 50
}
}
}
}
}
Loading
Loading