From a5143cec1275fffcb3c4f6b83644113f3e94d3ae Mon Sep 17 00:00:00 2001 From: vermeul <swen@ethz.ch> Date: Fri, 5 Aug 2022 16:55:38 +0200 Subject: [PATCH] feat: implement personal access token methods --- pybis/src/python/pybis/attribute.py | 2 +- pybis/src/python/pybis/definitions.py | 11 ++ pybis/src/python/pybis/pybis.py | 215 +++++++++++++++++++------- 3 files changed, 167 insertions(+), 61 deletions(-) diff --git a/pybis/src/python/pybis/attribute.py b/pybis/src/python/pybis/attribute.py index 405b21a3c36..880886870c0 100644 --- a/pybis/src/python/pybis/attribute.py +++ b/pybis/src/python/pybis/attribute.py @@ -115,7 +115,7 @@ class AttrHolder: elif attr.endswith("Date"): self.__dict__["_" + attr] = format_timestamp(data.get(attr)) - elif attr in ["registrator", "modifier", "dataProducer"]: + elif attr in ["registrator", "modifier", "dataProducer", "owner"]: self.__dict__["_" + attr] = extract_person(data.get(attr)) else: diff --git a/pybis/src/python/pybis/definitions.py b/pybis/src/python/pybis/definitions.py index 3136f86e3af..3fa68259bbd 100644 --- a/pybis/src/python/pybis/definitions.py +++ b/pybis/src/python/pybis/definitions.py @@ -106,6 +106,14 @@ def openbis_definitions(entity): "delete": {"@type": "as.dto.dataset.delete.DataSetTypeDeletionOptions"}, "identifier": "typeId", }, + "personalAccessToken": { + "attrs_new": "sessionName validFromDate validToDate accessDate".split(), + "attrs_up": "".split(), + "attrs": "permId sessionName validFromDate validToDate accessDate owner registrator registrationDate modifier modificationDate".split(), + "search": {"@type": "as.dto.pat.search.PersonalAccessTokenSearchCriteria"}, + "delete": {"@type": "as.dto.pat.delete.PersonalAccessTokenDeletionOptions"}, + "identifier": "permId", + }, "experimentType": { "attrs_new": "code description validationPlugin".split(), "attrs_up": "description modificationDate validationPlugin".split(), @@ -296,6 +304,9 @@ get_definition_for_entity = openbis_definitions # Alias fetch_option = { + "personalAccessToken": { + "@type": "as.dto.pat.fetchoptions.PersonalAccessTokenFetchOptions" + }, "space": {"@type": "as.dto.space.fetchoptions.SpaceFetchOptions"}, "project": { "@type": "as.dto.project.fetchoptions.ProjectFetchOptions", diff --git a/pybis/src/python/pybis/pybis.py b/pybis/src/python/pybis/pybis.py index b9279007302..6f0c6a1314d 100644 --- a/pybis/src/python/pybis/pybis.py +++ b/pybis/src/python/pybis/pybis.py @@ -11,18 +11,17 @@ Work with openBIS using Python. from __future__ import print_function import copy -import errno import json -import logging import os -import random import re import subprocess import sys import time +from xml.dom import NotSupportedErr import zlib from collections import defaultdict, namedtuple from datetime import datetime +from dateutil.relativedelta import relativedelta from urllib.parse import quote, urljoin, urlparse import pandas as pd @@ -31,6 +30,7 @@ import urllib3 from pandas import DataFrame, Series from tabulate import tabulate from texttable import Texttable +from typing import Optional from . import data_set as pbds from .dataset import DataSet @@ -116,6 +116,7 @@ def get_search_type_for_entity(entity, operator=None): {'@type': 'as.dto.space.search.SpaceSearchCriteria'} """ search_criteria = { + "personalAccessToken": "as.dto.pat.search.PersonalAccessTokenSearchCriteria", "space": "as.dto.space.search.SpaceSearchCriteria", "userId": "as.dto.person.search.UserIdSearchCriteria", "email": "as.dto.person.search.EmailSearchCriteria", @@ -169,6 +170,8 @@ def _type_for_id(ident, entity): return {"permId": ident, "@type": "as.dto.tag.id.TagPermId"} else: return {"code": ident, "@type": "as.dto.tag.id.TagCode"} + if entity == "personalAccessToken": + return {"permId": ident, "@type": "as.dto.pat.id.PersonalAccessTokenPermId"} entities = { "sample": "Sample", @@ -938,6 +941,7 @@ class Openbis: self.use_cache = use_cache self.cache = {} self.server_information = None + self.token = None if ( token is not None ): # We try to set the token, during initialisation instead of errors, a message is printed @@ -1016,6 +1020,8 @@ class Openbis: "get_object_types()", "get_property_types()", "get_property_type()", + "get_personal_access_tokens()", + "get_personal_access_token()", "new_property_type()", "get_semantic_annotations()", "get_semantic_annotation()", @@ -1054,6 +1060,7 @@ class Openbis: "new_material_type()", "new_semantic_annotation()", "new_transaction()", + "create_personal_access_token()", "set_token()", ] @@ -1886,6 +1893,143 @@ class Openbis: df_initializer=create_data_frame, ) + def create_personal_access_token( + self, sessionName: str, validFrom: datetime = None, validTo: datetime = None + ) -> str: + """Creates a new personal access token (PAT)""" + + if validFrom is None: + validFrom = datetime.now() + if validTo is None: + validTo = datetime.now() + relativedelta(years=1) + + entity = "personalAccessToken" + request = { + "method": get_method_for_entity(entity, "create"), + "params": [ + self.token, + { + "@type": "as.dto.pat.create.PersonalAccessTokenCreation", + "sessionName": sessionName, + "validFromDate": int(validFrom.timestamp() * 1000), + "validToDate": int(validTo.timestamp() * 1000), + }, + ], + } + try: + resp = self._post_request(self.as_v3, request) + except ValueError as exc: + raise NotSupportedErr( + "Your openBIS instance does not support personal access tokens. Please upgrade your server and activate them." + ) + try: + token = resp[0]["permId"] + return token + except KeyError: + pass + # if "error" in resp and resp["error"]["message"] == "method not found": + + def get_personal_access_tokens( + self, permId=None, start_with=None, count=None, **search_args + ): + """Get Personal Access Tokens""" + entity = "personalAccessToken" + + search_criteria = get_search_criteria(entity, **search_args) + if permId: + sub_crit = _subcriteria_for_permid(permids=permId, entity=entity) + search_criteria["criteria"].append(sub_crit) + fetchopts = get_fetchoption_for_entity(entity) + fetchopts["from"] = start_with + fetchopts["count"] = count + + for person in ["owner", "registrator", "modifier"]: + fetchopts[person] = get_fetchoption_for_entity(person) + request = { + "method": get_method_for_entity(entity, "search"), + "params": [self.token, search_criteria, fetchopts], + } + try: + resp = self._post_request(self.as_v3, request) + except ValueError: + raise NotSupportedErr( + "This method is not supported by your openBIS instance." + ) + + defs = get_definition_for_entity(entity) + + def create_data_frame(attrs, props, response): + attrs = defs["attrs"] + objects = response["objects"] + if len(objects) == 0: + persons = DataFrame(columns=attrs) + else: + parse_jackson(objects) + + pats = DataFrame(objects) + pats["permId"] = pats["permId"].map(extract_permid) + for date in [ + "validFromDate", + "validToDate", + "accessDate", + "registrationDate", + "modificationDate", + ]: + pats[date] = pats[date].map(format_timestamp) + for person in ["owner", "registrator", "modifier"]: + pats[person] = pats[person].map(extract_person) + return pats[attrs] + + return Things( + openbis_obj=self, + entity=entity, + identifier_name="permId", + single_item_method=self.get_personal_access_token, + start_with=start_with, + count=count, + totalCount=resp.get("totalCount"), + response=resp, + df_initializer=create_data_frame, + ) + + def get_personal_access_token(self, permId, only_data=False): + entity = "personalAccessToken" + identifiers = [] + only_one = True + if isinstance(permId, list): + only_one = False + for ident in permId: + identifiers.append(_type_for_id(ident, entity)) + else: + identifiers.append(_type_for_id(permId, entity)) + + defs = get_definition_for_entity(entity) + fetchopts = get_fetchoption_for_entity(entity) + for person in ["owner", "registrator", "modifier"]: + fetchopts[person] = get_fetchoption_for_entity(person) + request = { + "method": get_method_for_entity(entity, "get"), + "params": [self.token, identifiers, fetchopts], + } + resp = self._post_request(self.as_v3, request) + if only_one: + if len(resp) == 0: + raise ValueError(f"no such {entity} found: {permId}") + + parse_jackson(resp) + for permId in resp: + if only_data: + return resp[permId] + else: + return PersonalAccessToken( + openbis_obj=self, + data=resp[permId], + # single_item_method_name=self.get_personal_access_token, + # count=len(resp), + # totalCount=len(resp), + # response=resp, + ) + def get_persons(self, start_with=None, count=None, **search_args): """Get openBIS users""" @@ -2107,10 +2251,6 @@ class Openbis: b) property is not defined for this sampleType """ - logger = logging.getLogger("get_samples") - logger.setLevel(logging.CRITICAL) - logger.addHandler(logging.StreamHandler(sys.stdout)) - if collection is not None: experiment = collection if attrs is None: @@ -2200,23 +2340,11 @@ class Openbis: ], } - time1 = now() - logger.debug("get_samples posting request") resp = self._post_request(self.as_v3, request) - time2 = now() - - logger.debug(f"get_samples got response. Delay: {time2 - time1}") parse_jackson(resp) - time3 = now() - response = resp["objects"] - logger.debug(f"get_samples got JSON. Delay: {time3 - time2}") - - time4 = now() - - logger.debug(f"get_samples after result mapping. Delay: {time4 - time3}") result = self._sample_list_for_response( response=response, @@ -2228,9 +2356,6 @@ class Openbis: parsed=True, ) - time5 = now() - - logger.debug(f"get_samples computed final result. Delay: {time5 - time4}") return result get_objects = get_samples # Alias @@ -4420,24 +4545,9 @@ class Openbis: totalCount=0, parsed=False, ): - logger = logging.getLogger("_sample_list_for_response") - logger.setLevel(logging.CRITICAL) - logger.disabled = True - logger.addHandler(logging.StreamHandler(sys.stdout)) - - time1 = now() - - logger.debug("_sample_list_for_response before parsing JSON") if not parsed: parse_jackson(response) - time2 = now() - - logger.debug(f"_sample_list_for_response got response. Delay: {time2 - time1}") - - time6 = now() - logger.debug("_sample_list_for_response computing result.") - def create_data_frame(attrs, props, response): """returns a Things object, containing a DataFrame plus additional information""" @@ -4449,12 +4559,6 @@ class Openbis: return return_attribute - logger = logging.getLogger("create_data_frame") - logger.setLevel(logging.CRITICAL) - logger.addHandler(logging.StreamHandler(sys.stdout)) - - time2 = now() - if attrs is None: attrs = [] default_attrs = [ @@ -4479,10 +4583,6 @@ class Openbis: display_attrs.append(prop) samples = DataFrame(columns=display_attrs) else: - time3 = now() - logger.debug( - f"createDataFrame computing attributes. Delay: {time3 - time2}" - ) samples = DataFrame(response) for attr in attrs: @@ -4515,11 +4615,6 @@ class Openbis: samples["permId"] = samples["permId"].map(extract_permid) samples["type"] = samples["type"].map(extract_nested_permid) - time4 = now() - logger.debug( - f"_sample_list_for_response computed attributes. Delay: {time4 - time3}" - ) - for prop in props: if prop == "*": # include all properties in dataFrame. @@ -4545,10 +4640,6 @@ class Openbis: samples.loc[i, prop.upper()] = "" display_attrs.append(prop.upper()) - time5 = now() - logger.debug( - f"_sample_list_for_response computed properties. Delay: {time5 - time4}" - ) return samples[display_attrs] def create_objects(response): @@ -4577,10 +4668,6 @@ class Openbis: props=props, ) - time7 = now() - logger.debug( - f"_sample_list_for_response computed result. Delay: {time7 - time6}" - ) return result @staticmethod @@ -5144,3 +5231,11 @@ class PropertyType( class Plugin(OpenBisObject, entity="plugin", single_item_method_name="get_plugin"): pass + + +class PersonalAccessToken( + OpenBisObject, + entity="personalAccessToken", + single_item_method_name="get_personal_access_token", +): + pass -- GitLab