diff --git a/jupyter-openbis-extension/server.py b/jupyter-openbis-extension/server.py index ec0ef7f2cf48f957ce696e2a25e42362a3be61eb..996a942b3b379a113ffc035a5599f81a1ee6853f 100644 --- a/jupyter-openbis-extension/server.py +++ b/jupyter-openbis-extension/server.py @@ -3,12 +3,8 @@ from notebook.base.handlers import IPythonHandler from pybis import Openbis import numpy as np -import json import os -import urllib -from urllib.parse import unquote, unquote_plus -from subprocess import check_output -from urllib.parse import urlsplit, urlunsplit +from urllib.parse import unquote import yaml openbis_connections = {} @@ -17,18 +13,29 @@ openbis_connections = {} def _jupyter_server_extension_paths(): return [{'module': 'jupyter-openbis-extension.server'}] -def _load_configuration(filename='openbis-connections.yaml'): - - home = os.path.expanduser("~") - abs_filename = os.path.join(home, '.jupyter', filename) - - with open(abs_filename, 'r') as stream: - try: - config = yaml.safe_load(stream) - return config - except yaml.YAMLexception as exc: - print(exc) - return None +def _load_configuration(paths, filename='openbis-connections.yaml'): + + if paths is None: + paths = [] + home = os.path.expanduser("~") + paths.append(os.path.join(home, '.jupyter')) + + # look in all config file paths of jupyter + # for openbis connection files and load them + connections = [] + for path in paths: + abs_filename = os.path.join(path, filename) + if os.path.isfile(abs_filename): + with open(abs_filename, 'r') as stream: + try: + config = yaml.safe_load(stream) + for connection in config['connections']: + connections.append(connection) + except yaml.YAMLexception as exc: + print(exc) + return None + + return connections def load_jupyter_server_extension(nb_server_app): @@ -37,20 +44,29 @@ def load_jupyter_server_extension(nb_server_app): """ # load the configuration file - #print(dir(nb_server_app)) - print(nb_server_app.config_file_paths) - config = _load_configuration() - if config is not None: - for conn in config['connections']: - try: - register_connection(conn) - except Exception as exc: - print(exc) - + # and register the openBIS connections. + # If username and password is available, try to connect to the server + connections = _load_configuration( + paths = nb_server_app.config_file_paths, + filename = 'openbis-connections.yaml' + ) + for connection_info in connections: + conn = register_connection(connection_info) + print("Registered: {}".format(conn.url)) + if conn.username and conn.password: + try: + conn.login() + print("Successfully connected to: {}".format(conn.url)) + except ValueError: + print("Incorrect username or password for: {}".format(conn.url)) + except Exception: + print("Cannot establish connection to: {}".format(conn.url)) + + # Add URL handlers to our web_app + # see Tornado documentation: https://www.tornadoweb.org web_app = nb_server_app.web_app host_pattern = '.*$' - base_url = web_app.settings['base_url'] # DataSet download @@ -61,7 +77,9 @@ def load_jupyter_server_extension(nb_server_app): '/openbis/dataset/(?P<connection_name>.*)?/(?P<permId>.*)?/(?P<downloadPath>.*)'), DataSetDownloadHandler )] - ) + ) + + # DataSet upload web_app.add_handlers( host_pattern, [( url_path_join( base_url, @@ -69,7 +87,21 @@ def load_jupyter_server_extension(nb_server_app): ), DataSetUploadHandler )] - ) + ) + + # DataSet-Types + web_app.add_handlers( + host_pattern, + [( + url_path_join( + base_url, + '/openbis/datasetTypes/(?P<connection_name>.*)' + ), + DataSetTypesHandler + )] + ) + + # DataSets for Sample identifier/permId web_app.add_handlers( host_pattern, [( @@ -79,12 +111,29 @@ def load_jupyter_server_extension(nb_server_app): ), SampleHandler )] - ) + ) + + # OpenBIS connections + web_app.add_handlers( + host_pattern, + [( + url_path_join( + base_url, + '/openbis/conns' + ), + OpenBISConnections + )] + ) + + # Modify / reconnect to a connection web_app.add_handlers( host_pattern, [( - url_path_join(base_url, '/openbis/conn'), - OpenBISHandler + url_path_join( + base_url, + '/openbis/conn/(?P<connection_name>.*)' + ), + OpenBISConnectionHandler )] ) @@ -92,110 +141,174 @@ def load_jupyter_server_extension(nb_server_app): def register_connection(connection_info): - + conn = OpenBISConnection( - name = connection_info['name'], - url = connection_info['url'], - verify_certificates = connection_info['verify_certificates'], - username = connection_info['username'], - password = connection_info['password'], - status = 'not connected', + name = connection_info.get('name'), + url = connection_info.get('url'), + verify_certificates = connection_info.get('verify_certificates', False), + username = connection_info.get('username'), + password = connection_info.get('password'), + status = 'not connected', ) openbis_connections[conn.name] = conn + return conn - try: - openbis = Openbis( - url = conn.url, - verify_certificates = conn.verify_certificates - ) - conn.openbis = openbis - conn.ds_types = {} - - openbis.login( - username = conn.username, - password = conn.password - ) - conn.status = 'connected' - - print('connected to {}'.format(conn.name)) - except Exception as exc: - conn.status = 'FAILED: {}'.format(exc) - print('ERROR: could not connected to {}. Reason: {}'.format(conn.name, exc)) - raise exc - - try: - # add dataset types to the connection - dataset_types = conn.openbis.get_dataset_types() - dts = dataset_types.df.to_dict(orient='records') - for dt in dts: - dataset_type = conn.openbis.get_dataset_type(dt['code']) - pa = dataset_type.get_propertyAssignments() - pa_dict = pa.to_dict(orient='records') - dt['propertyAssignments'] = pa_dict - conn.ds_types = dts - except Exception as e: - print('ERROR: {}'.format(e)) - - -def check_connection(connection_name): - """Checks whether connection is valid and session is still active - and tries to reconnect, if possible. Returns the active session - """ - - if connection_name not in openbis_connections: - return None - - conn = openbis_connections[connection_name] - if not conn.openbis.isSessionActive(): - conn.openbis.login(conn.username, conn.password) - class OpenBISConnection: """register an openBIS connection """ def __init__(self, **kwargs): + for needed_key in ['name', 'url']: + if needed_key not in kwargs: + raise KeyError("{} is missing".format(needed_key)) + for key in kwargs: setattr(self, key, kwargs[key]) + openbis = Openbis( + url = self.url, + verify_certificates = self.verify_certificates + ) + self.openbis = openbis + self.status = "not connected" + def is_session_active(self): return self.openbis.is_session_active() - def reconnect(self): - self.openbis.login(self.username, self.password) - - -class OpenBISHandler(IPythonHandler): - """Handle the requests to /openbis/conn - """ + def check_status(self): + if self.openbis.is_session_active(): + self.status = "connected" + else: + self.status = "not connected" + + def login(self, username=None, password=None): + if username is None: + username=self.username + if password is None: + password=self.password + self.openbis.login( + username = username, + password = password + ) + # store username and password in memory + self.username = username + self.password = password + self.status = 'connected' + + def get_info(self): + return { + 'name' : self.name, + 'url' : self.url, + 'status' : self.status, + 'username': self.username, + 'password': self.password, + } + +class OpenBISConnections(IPythonHandler): + + def post(self): + """create a new connection + + :return: a new connection object + """ + data = self.get_json_body() + conn = register_connection(data) + if conn.username and conn.password: + try: + conn.login() + except Exception: + pass + self.get() + return def get(self): """returns all available openBIS connections """ + connections= [] for conn in openbis_connections.values(): + conn.check_status() + connections.append(conn.get_info()) + + self.write({ + 'status' : 200, + 'connections': connections, + 'cwd' : os.getcwd() + }) + return + + +class OpenBISConnectionHandler(IPythonHandler): + """Handle the requests to /openbis/conn + """ + + def put(self, connection_name): + """reconnect to a current connection + :return: an updated connection object + """ + data = self.get_json_body() - connections.append({ - 'name' : conn.name, - 'url' : conn.url, - 'status' : conn.status, - 'ds_types': conn.ds_types, + try: + conn = openbis_connections[connection_name] + except KeyError: + self.set_status(404) + self.write({ + "reason" : 'No such connection: {}'.format(data) }) + return + + try: + conn.login(data.get('username'), data.get('password')) + except ConnectionError: + self.set_status(500) + self.write({ + "reason": "Could not establish connection to {}".format(connection_name) + }) + return + except ValueError: + self.set_status(401) + self.write({ + "reason": "Incorrect username or password for {}".format(connection_name) + }) + return self.write({ 'status' : 200, - 'connections': connections, + 'connection': conn.get_info(), + 'cwd' : os.getcwd() + }) + + def get(self, connection_name): + """returns information about a connection name + """ + + try: + conn = openbis_connections[connection_name] + except KeyError: + self.set_status(404) + self.write({ + "reason" : 'No such connection: {}'.format(data) + }) + return + + conn.check_status() + + self.write({ + 'status' : 200, + 'connection': conn.get_info(), 'cwd' : os.getcwd() }) return + class SampleHandler(IPythonHandler): """Handle the requests for /openbis/sample/connection/permId""" def get_datasets(self, conn, permId): if not conn.is_session_active(): try: - conn.reconnect() + conn.login() except Exception as exc: self.write({ "reason" : 'connection to {} could not be established: {}'.format(conn.name, exc) @@ -229,6 +342,7 @@ class SampleHandler(IPythonHandler): params['connection_name'] ) }) + return datasets = self.get_datasets(conn, params['permId']) if datasets is not None: @@ -246,8 +360,9 @@ class DataSetDownloadHandler(IPythonHandler): def download_data(self, conn, permId, downloadPath=None): if not conn.is_session_active(): try: - conn.reconnect() + conn.login() except Exception as exc: + self.set_status(500) self.write({ "reason" : 'connection to {} could not be established: {}'.format(conn.name, exc) }) @@ -266,6 +381,7 @@ class DataSetDownloadHandler(IPythonHandler): try: destination = dataset.download(destination=downloadPath) except Exception as exc: + self.set_status(500) self.write({ "reason": 'Data for DataSet {} could not be downloaded: {}'.format(permId, exc) }) @@ -274,6 +390,7 @@ class DataSetDownloadHandler(IPythonHandler): # return success message path = os.path.join(downloadPath, dataset.permId) self.write({ + 'url' : conn.url, 'permId' : dataset.permId, 'path' : path, 'dataStore' : dataset.dataStore, @@ -290,6 +407,7 @@ class DataSetDownloadHandler(IPythonHandler): try: conn = openbis_connections[params['connection_name']] except KeyError: + self.set_status(404) self.write({ "reason":'connection {} was not found'.format(params['connection_name']) }) @@ -298,14 +416,52 @@ class DataSetDownloadHandler(IPythonHandler): results = self.download_data(conn=conn, permId=params['permId'], downloadPath=params['downloadPath']) +class DataSetTypesHandler(IPythonHandler): + def get(self, **params): + """Handle a request to /openbis/datasetTypes/connection_name + """ + + try: + conn = openbis_connections[params['connection_name']] + except KeyError: + self.set_status(404) + self.write({ + "reason":'connection {} was not found'.format(params['connection_name']) + }) + return + + try: + dataset_types = conn.openbis.get_dataset_types() + dts = dataset_types.df.to_dict(orient='records') + + # get property assignments for every dataset-type + # and add it to the dataset collection + for dt in dts: + dataset_type = conn.openbis.get_dataset_type(dt['code']) + pa = dataset_type.get_propertyAssignments() + pa_dict = pa.to_dict(orient='records') + dt['propertyAssignments'] = pa_dict + + self.write({ + "dataSetTypes": dts + }) + return + + except Exception as e: + self.set_status(500) + self.write({ + "reason":'Could not fetch dataset-types: {}'.format(e) + }) + return + + class DataSetUploadHandler(IPythonHandler): """Handle the requests for /openbis/dataset/connection""" - def upload_data(self, conn, data): if not conn.is_session_active(): try: - conn.reconnect() + conn.login() except Exception as exc: self.write({ "reason": 'connection to {} could not be established: {}'.format(conn.name, exc)