-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #77 from the-virtual-brain/EBR-79
EBR-79: add access to bucket from widgets
- Loading branch information
Showing
13 changed files
with
291 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ plotly==5.14.0 | |
pythreejs | ||
pyvista>=0.43.0 | ||
ipyreact>=0.4.1 | ||
requests | ||
trame | ||
trame-vuetify | ||
trame-vtk | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# "TheVirtualBrain - Widgets" package | ||
# | ||
# (c) 2022-2024, TVB Widgets Team | ||
# | ||
|
||
from ebrains_drive import BucketApiClient | ||
from tvbwidgets.core.bucket.buckets import ExtendedBuckets | ||
|
||
|
||
class ExtendedBucketApiClient(BucketApiClient): | ||
|
||
def __init__(self, username=None, password=None, token=None, env="") -> None: | ||
super().__init__(username, password, token, env) | ||
self.buckets = ExtendedBuckets(self) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# "TheVirtualBrain - Widgets" package | ||
# | ||
# (c) 2022-2024, TVB Widgets Team | ||
# | ||
|
||
from dataclasses import dataclass | ||
from typing import List | ||
from ebrains_drive.buckets import Buckets | ||
from tvbwidgets.core.exceptions import BucketDTOError | ||
from tvbwidgets.core.logger.builder import get_logger | ||
|
||
LOGGER = get_logger(__name__) | ||
|
||
|
||
@dataclass | ||
class BucketDTO: | ||
name: str | ||
role: str | ||
is_public: bool | ||
|
||
|
||
class ExtendedBuckets(Buckets): | ||
BUCKETS_ENDPOINT = '/v1/buckets' | ||
|
||
def __init__(self, client): | ||
super().__init__(client) | ||
self._available_buckets: List[BucketDTO] = [] | ||
|
||
def list_buckets(self): | ||
# type: () -> List[BucketDTO] | ||
""" | ||
Queries the buckets endpoint for the available buckets for current user | ||
""" | ||
try: | ||
resp = self.client.get(self.BUCKETS_ENDPOINT) | ||
json_resp = resp.json() | ||
updated_available_buckets = [] | ||
for obj in json_resp: | ||
updated_available_buckets.append(BucketDTO(**obj)) | ||
self._available_buckets = updated_available_buckets | ||
return self._available_buckets | ||
except KeyError as e: | ||
LOGGER.error(f'Received unexpected Bucket structure! {str(e)}') | ||
raise BucketDTOError('Unexpected response structure from server!') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# "TheVirtualBrain - Widgets" package | ||
# | ||
# (c) 2022-2024, TVB Widgets Team | ||
# | ||
import os | ||
|
||
import pytest | ||
from ebrains_drive.exceptions import Unauthorized | ||
from tvbwidgets.core.auth import CLB_AUTH | ||
from tvbwidgets.ui.bucket_widget import BucketWidget | ||
|
||
DUMMY_CONTENT = b'test content' | ||
|
||
|
||
class MockBucketDTO: | ||
def __init__(self, name, role='', is_public=True): | ||
self.name = name | ||
self.role = role | ||
self.is_public = is_public | ||
|
||
|
||
class MockFile: | ||
def __init__(self, name): | ||
# type: (str) -> None | ||
self.name = name | ||
|
||
def get_content(self): | ||
return DUMMY_CONTENT | ||
|
||
def get_download_link(self): | ||
return '' | ||
|
||
|
||
class MockBucket: | ||
def __init__(self, files_count=2, name='test_bucket', target='buckets', dataproxy_entity_name='test_bucket'): | ||
self.name = name | ||
self.files = [MockFile(f'file{number}') for number in range(files_count)] | ||
self.target = target | ||
self.dataproxy_entity_name = dataproxy_entity_name | ||
|
||
def ls(self, prefix=''): | ||
return [f for f in self.files if f.name.startswith(prefix)] | ||
|
||
|
||
class MockBuckets: | ||
def __init__(self): | ||
self.buckets = { | ||
'test_bucket': MockBucket() | ||
} | ||
|
||
def get_bucket(self, name): | ||
try: | ||
return self.buckets[name] | ||
except KeyError: | ||
raise Unauthorized('Unauthorized in tests') | ||
|
||
def list_buckets(self): | ||
return [MockBucketDTO(b) for b in self.buckets.keys()] | ||
|
||
|
||
class MockBucketApiClient: | ||
def __init__(self, token=''): | ||
self.token = token | ||
self.buckets = MockBuckets() | ||
|
||
|
||
@pytest.fixture | ||
def mock_client(mocker): | ||
return mocker.patch('tvbwidgets.ui.bucket_widget.ExtendedBucketApiClient', MockBucketApiClient) | ||
|
||
|
||
@pytest.fixture | ||
def mock_requests_get(mocker): | ||
mock_response = mocker.Mock() | ||
mock_response.content = DUMMY_CONTENT | ||
return mocker.patch('requests.get', return_value=mock_response) | ||
|
||
|
||
def test_get_files_in_bucket(mock_client, mock_requests_get): | ||
""" | ||
tests that client returns list of files from bucket | ||
""" | ||
if os.environ.get(CLB_AUTH): | ||
os.environ.pop(CLB_AUTH) | ||
|
||
with pytest.raises(RuntimeError): | ||
BucketWidget() | ||
|
||
os.environ[CLB_AUTH] = "test_auth_token" | ||
widget = BucketWidget() | ||
|
||
# test observe event on buckets dropdown | ||
assert widget.buckets_dropdown.value is None | ||
assert widget.files_list.value is None | ||
assert len(widget.files_list.options) == 0 | ||
widget.buckets_dropdown.value = widget.buckets_dropdown.options[0] | ||
assert len(widget.files_list.options) == 2 | ||
widget.files_list.value = widget.files_list.options[0] | ||
|
||
# test BucketWidget functions | ||
assert widget.get_selected_file_path() == widget.buckets_dropdown.value + '/' + widget.files_list.value | ||
assert widget.get_selected_file_content() == DUMMY_CONTENT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# "TheVirtualBrain - Widgets" package | ||
# | ||
# (c) 2022-2024, TVB Widgets Team | ||
# | ||
|
||
import ipywidgets | ||
import requests | ||
from tvbwidgets.core.auth import get_current_token | ||
from tvbwidgets.core.bucket.bucket_api import ExtendedBucketApiClient | ||
from tvbwidgets.ui.base_widget import TVBWidget | ||
from ebrains_drive.files import DataproxyFile | ||
|
||
|
||
class BucketWidget(ipywidgets.VBox, TVBWidget): | ||
|
||
def __init__(self, **kwargs): | ||
TVBWidget.__init__(self, **kwargs) | ||
bearer_token = get_current_token() | ||
self.client = ExtendedBucketApiClient(token=bearer_token) | ||
|
||
try: | ||
list_buckets = self.client.buckets.list_buckets() | ||
buckets_name = [b.name for b in list_buckets] | ||
except Exception: | ||
self.logger.error("Could not retrieve the list of available Buckets!") | ||
buckets_name = [] | ||
layout = ipywidgets.Layout(width='400px') | ||
self.buckets_dropdown = ipywidgets.Dropdown(description='Bucket', value=None, | ||
options=buckets_name, layout=layout) | ||
self.files_list = ipywidgets.Select(description='Files', value=None, disabled=False, layout=layout) | ||
|
||
self.buckets_dropdown.observe(self.select_bucket, names='value') | ||
|
||
ipywidgets.VBox.__init__(self, [self.buckets_dropdown, self.files_list], **kwargs) | ||
|
||
def get_selected_bucket(self): | ||
return self.buckets_dropdown.value | ||
|
||
def get_selected_file_path(self): | ||
file = self.files_list.value | ||
if file is None: | ||
return file | ||
return self.get_selected_bucket() + "/" + file | ||
|
||
def get_selected_file_content(self): | ||
file_path = self.files_list.value | ||
bucket_name = self.get_selected_bucket() | ||
dataproxy_file = self._get_dataproxy_file(file_path, bucket_name) | ||
response = requests.get(dataproxy_file.get_download_link()) | ||
return response.content | ||
|
||
def select_bucket(self, _): | ||
selected_bucket = self.get_selected_bucket() | ||
bucket = self.client.buckets.get_bucket(selected_bucket) | ||
self.files_list.options = [f.name for f in bucket.ls()] | ||
|
||
def _get_dataproxy_file(self, file_path, bucket_name): | ||
# type: (str, str) -> DataproxyFile | ||
""" | ||
Get the DataProxy file corresponding to the path <file_path> in bucket <bucket_name> | ||
""" | ||
file_path = file_path.lstrip('/') | ||
bucket = self.client.buckets.get_bucket(bucket_name) | ||
# find first dataproxy file corresponding to provided path | ||
dataproxy_file = next((f for f in bucket.ls() if f.name == file_path), None) | ||
return dataproxy_file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.