-
Notifications
You must be signed in to change notification settings - Fork 6
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 #3 from DenisaCG/listAvailableDrives
Set up backend structure and list all available drives
- Loading branch information
Showing
13 changed files
with
655 additions
and
38 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,35 @@ | ||
import pytest | ||
from traitlets.config import Config | ||
|
||
pytest_plugins = ("pytest_jupyter.jupyter_server", ) | ||
|
||
|
||
@pytest.fixture | ||
def jp_server_config(jp_server_config): | ||
return {"ServerApp": {"jpserver_extensions": {"jupyter_drives": True}}} | ||
return { | ||
"ServerApp": {"jpserver_extensions": {"jupyter_drives": True}}, | ||
"DrivesConfig": {"api_base_url": "https://s3.eu-north-1.amazonaws.com/", "access_key_id": "valid", "secret_access_key":"valid"}, | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def drives_base_config(): | ||
return Config() | ||
|
||
|
||
@pytest.fixture | ||
def drives_s3_config(drives_base_config): | ||
return drives_base_config() | ||
|
||
|
||
@pytest.fixture | ||
def drives_s3_manager(drives_base_config): | ||
from .jupyter_drives.managers.s3 import S3Manager | ||
|
||
return S3Manager(drives_base_config) | ||
|
||
|
||
@pytest.fixture | ||
def drives_valid_s3_manager(drives_s3_manager): | ||
drives_s3_manager._config.access_key_id = "valid" | ||
drives_s3_manager._config.secret_access = "valid" | ||
return drives_s3_manager |
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,63 @@ | ||
import entrypoints | ||
from traitlets import Enum, Unicode, default | ||
from traitlets.config import Configurable | ||
|
||
# Supported third-party services | ||
MANAGERS = {} | ||
|
||
for entry in entrypoints.get_group_all("jupyter_drives.manager_v1"): | ||
MANAGERS[entry.name] = entry | ||
|
||
class DrivesConfig(Configurable): | ||
""" | ||
Allows configuration of supported drives via jupyter_notebook_config.py | ||
""" | ||
|
||
session_token = Unicode( | ||
None, | ||
config=True, | ||
allow_none=True, | ||
help="A session access token to authenticate.", | ||
) | ||
|
||
access_key_id = Unicode( | ||
None, | ||
config=True, | ||
allow_none=True, | ||
help="The id of the access key for the bucket.", | ||
) | ||
|
||
secret_access_key= Unicode( | ||
None, | ||
config=True, | ||
allow_none=True, | ||
help="The secret access key for the bucket.", | ||
) | ||
|
||
region_name = Unicode( | ||
"eu-north-1", | ||
config = True, | ||
help = "Region name.", | ||
) | ||
|
||
api_base_url = Unicode( | ||
config=True, | ||
help="Base URL of the provider service REST API.", | ||
) | ||
|
||
@default("api_base_url") | ||
def set_default_api_base_url(self): | ||
# for AWS S3 drives | ||
if self.provider == "s3": | ||
return "https://s3.amazonaws.com/" # region? https://s3.<region>.amazonaws.com/ | ||
|
||
# for Google Cloud Storage drives | ||
elif self.provider == "gcs": | ||
return "https://www.googleapis.com/" | ||
|
||
provider = Enum( | ||
MANAGERS.keys(), | ||
default_value="s3", | ||
config=True, | ||
help="The source control provider.", | ||
) |
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 |
---|---|---|
@@ -1,24 +1,91 @@ | ||
""" | ||
Module with all of the individual handlers, which will return the results to the frontend. | ||
""" | ||
import json | ||
import logging | ||
import traceback | ||
from typing import Optional | ||
|
||
from jupyter_server.base.handlers import APIHandler | ||
from jupyter_server.utils import url_path_join | ||
import tornado | ||
import traitlets | ||
|
||
class RouteHandler(APIHandler): | ||
# The following decorator should be present on all verb methods (head, get, post, | ||
# patch, put, delete, options) to ensure only authorized user can request the | ||
# Jupyter server | ||
from .base import MANAGERS, DrivesConfig | ||
from .managers.manager import JupyterDrivesManager | ||
|
||
NAMESPACE = "jupyter-drives" | ||
|
||
class JupyterDrivesAPIHandler(APIHandler): | ||
""" | ||
Base handler for jupyter-drives specific API handlers | ||
""" | ||
def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager): | ||
self._jp_log = logger | ||
self._manager = manager | ||
|
||
def write_error(self, status_code, **kwargs): | ||
""" | ||
Override Tornado's RequestHandler.write_error for customized error handlings | ||
This method will be called when an exception is raised from a handler | ||
""" | ||
self.set_header("Content-Type", "application/json") | ||
reply = {"error": "Unhandled error"} | ||
exc_info = kwargs.get("exc_info") | ||
if exc_info: | ||
e = exc_info[1] | ||
if isinstance(e, tornado.web.HTTPError): | ||
reply["error"] = e.reason | ||
if hasattr(e, "error_code"): | ||
reply["error_code"] = e.error_code | ||
else: | ||
reply["error"] = "".join(traceback.format_exception(*exc_info)) | ||
self.finish(json.dumps(reply)) | ||
|
||
class ListJupyterDrives(JupyterDrivesAPIHandler): | ||
""" | ||
Returns list of available drives. | ||
""" | ||
# Later on, filters can be added for the listing | ||
@tornado.web.authenticated | ||
def get(self): | ||
self.finish(json.dumps({ | ||
"data": "This is /jupyter-drives/get-example endpoint!" | ||
})) | ||
async def get(self): | ||
drives, error = await self._manager.list_drives() | ||
self.finish(json.dumps(drives)) | ||
get_logger().debug(error) | ||
|
||
default_handlers = [ | ||
("drives", ListJupyterDrives) | ||
] | ||
|
||
def setup_handlers(web_app): | ||
def setup_handlers(web_app: tornado.web.Application, config: traitlets.config.Config, log: Optional[logging.Logger] = None): | ||
host_pattern = ".*$" | ||
base_url = url_path_join(web_app.settings["base_url"], NAMESPACE) | ||
|
||
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() | ||
manager_factory = entry_point.load() | ||
log.info(f"JupyterDrives Manager Class {manager_factory}") | ||
try: | ||
manager = manager_factory(config) | ||
except Exception as err: | ||
import traceback | ||
logging.error("JupyterDrives Manager Exception", exc_info=1) | ||
raise err | ||
|
||
handlers = [ | ||
( | ||
url_path_join(base_url, pattern), | ||
handler, | ||
{"logger": log, "manager": manager} | ||
) | ||
for pattern, handler in default_handlers | ||
] | ||
|
||
log.debug(f"Jupyter-Drives Handlers: {handlers}") | ||
|
||
base_url = web_app.settings["base_url"] | ||
route_pattern = url_path_join(base_url, "jupyter-drives", "get-example") | ||
handlers = [(route_pattern, RouteHandler)] | ||
web_app.add_handlers(host_pattern, handlers) |
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,19 @@ | ||
import logging | ||
|
||
from traitlets.config import Application | ||
|
||
class _ExtensionLogger: | ||
_LOGGER = None #type: Optional[logging.Logger] | ||
|
||
@classmethod | ||
def get_logger(cls) -> logging.Logger: | ||
if cls._LOGGER is None: | ||
app = Application.instance() | ||
cls._LOGGER = logging.getLogger( | ||
"{!s}.jupyter_drives".format(app.log.name) | ||
) | ||
Application.clear_instance() | ||
|
||
return cls._LOGGER | ||
|
||
get_logger = _ExtensionLogger.get_logger |
Empty file.
Oops, something went wrong.