From a1856a68e5ed2a75b0c1afc648d074199d9b06f6 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Mon, 31 May 2021 21:18:25 +0200 Subject: [PATCH 01/26] added snippet interface and httpclient --- example_scilog.py | 23 +++++----- scilog/__init__.py | 2 +- scilog/authclient.py | 14 +++--- scilog/config.py | 5 ++- scilog/httpclient.py | 84 +++++++++++++++++++++++++++++++++++ scilog/scicat.py | 6 +-- scilog/scilog.py | 101 ++++++++++++++++++++++++++++++++----------- scilog/snippet.py | 72 ++++++++++++++++++++++++++++++ 8 files changed, 257 insertions(+), 50 deletions(-) create mode 100644 scilog/httpclient.py create mode 100644 scilog/snippet.py diff --git a/example_scilog.py b/example_scilog.py index 6a48e2b..74f093b 100755 --- a/example_scilog.py +++ b/example_scilog.py @@ -15,23 +15,24 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) from scilog import SciLog +from scilog import Basesnippet, Paragraph -url = "https://lnode2.psi.ch/api/v1" +tmp = Basesnippet() +tmp.id = "2" + +# url = "https://lnode2.psi.ch/api/v1" +url = "http://[::1]:3000/" log = SciLog(url) #print(log.token) -loc = log.get_snippets(title="location", ownerGroup="admin") +logbooks = log.get_logbooks(ownerGroup=pgroup) -assert len(loc) == 1 -loc_id = loc[0]["id"] -print(loc_id) +assert len(logbooks) == 1 +logbook = logbooks[0] +print(logbook) -lb = log.get_snippets(snippetType="logbook", ownerGroup=pgroup) +log.select_logbook(logbook) -assert len(lb) == 1 -lb_id = lb[0]["id"] -print(lb_id) - -res = log.post_snippet(snippetType="paragraph", ownerGroup=pgroup, parentId=lb_id, textcontent="

from python

") +res = log.send_message("

from python

") print(res) snips = log.get_snippets(snippetType="paragraph", ownerGroup=pgroup) diff --git a/scilog/__init__.py b/scilog/__init__.py index 0fd2f3d..b3dbd1c 100644 --- a/scilog/__init__.py +++ b/scilog/__init__.py @@ -1,5 +1,5 @@ from .scicat import SciCat from .scilog import SciLog - +from .scilog import Basesnippet, Paragraph diff --git a/scilog/authclient.py b/scilog/authclient.py index f4273aa..a74d55c 100644 --- a/scilog/authclient.py +++ b/scilog/authclient.py @@ -4,13 +4,13 @@ from .config import Config from .utils import typename -AUTH_HEADERS = { +HEADER_JSON = { "Content-type": "application/json", "Accept": "application/json" } -class AuthClient(ABC): +class AuthMixin(ABC): def __init__(self, address): self.address = address.rstrip("/") @@ -27,14 +27,11 @@ class AuthClient(ABC): def authenticate(self, username, password): raise NotImplementedError - @property - def auth_headers(self): - headers = AUTH_HEADERS.copy() - headers["Authorization"] = self.token - return headers - @property def token(self): + return self._retrieve_token() + + def _retrieve_token(self): username = getpass.getuser() token = self._token if token is None: @@ -45,6 +42,7 @@ class AuthClient(ABC): password = getpass.getpass(prompt=f"{tn} password for {username}: ") token = self.authenticate(username, password) self.config[username] = self._token = token + return token diff --git a/scilog/config.py b/scilog/config.py index bcb69a8..bd5a3ef 100644 --- a/scilog/config.py +++ b/scilog/config.py @@ -1,6 +1,6 @@ from pathlib import Path import json - +import os class Config(dict): @@ -29,6 +29,9 @@ class Config(dict): def _save(self): json_save(self, self.fname) + def delete(self): + print(self.fname) + os.remove(self.fname) diff --git a/scilog/httpclient.py b/scilog/httpclient.py new file mode 100644 index 0000000..9d79ff3 --- /dev/null +++ b/scilog/httpclient.py @@ -0,0 +1,84 @@ +import requests +import functools +import json +from abc import ABC, abstractmethod + +from .authclient import AuthMixin, AuthError, HEADER_JSON + +def authenticated(func): + @functools.wraps(func) + def authenticated_call(*args, **kwargs): + if not issubclass(type(args[0]), HttpClient): + raise AttributeError("First argument must be an instance of HttpClient") + if "headers" in kwargs: + kwargs["headers"]["Authorization"] = args[0].token + else: + kwargs["headers"] = {} + kwargs["headers"]["Authorization"] = args[0].token + + return func(*args, **kwargs) + return authenticated_call + +class HttpClient(AuthMixin): + def __init__(self, address): + self.address = address + self._verify_certificate = True + super().__init__(address) + + def authenticate(self, username, password): + auth_payload = { + "principal": username, + "password": password + } + res = self._login(auth_payload, HEADER_JSON) + try: + token = "Bearer " + res["token"] + except KeyError as e: + raise AuthError(res) from e + else: + return token + + @authenticated + def get_request(self, url, params=None, headers=None, timeout=10): + response = requests.get(url, params=params, headers=headers, timeout=timeout, verify=self._verify_certificate) + if response.ok: + return response.json() + else: + if response.reason == "Unauthorized": + self.config.delete() + self._retrieve_token() + raise response.raise_for_status() + else: + raise response.raise_for_status() + + + @authenticated + def post_request(self, url, payload=None, headers=None, timeout=10): + response = requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() + return response + + def _login(self, payload=None, headers=None, timeout=10): + return requests.post(self.address + "/users/login", json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() + + def typename(self, obj): + return type(obj).__name__ + + @staticmethod + def make_filter(where:dict=None, limit:int=0, skip:int=0, fields:dict=None, include:dict=None, order:list=None): + filt = dict() + if where is not None: + items = [{k: v} for k, v in where.items()] + filt["where"] = {"and": items} + if limit > 0: + filt["limit"] = limit + if skip > 0: + filt["skip"] = skip + if fields is not None: + filt["fields"] = include + if order is not None: + filt["order"] = order + filt = json.dumps(filt) + return {"filter": filt} + + + diff --git a/scilog/scicat.py b/scilog/scicat.py index 91f21db..c06fad7 100644 --- a/scilog/scicat.py +++ b/scilog/scicat.py @@ -1,8 +1,8 @@ -from .authclient import AuthClient, AuthError, AUTH_HEADERS +from .authclient import AuthMixin, AuthError, HEADER_JSON from .utils import post_request, get_request -class SciCat(AuthClient): +class SciCat(AuthMixin): def authenticate(self, username, password): url = self.address + "/users/login" @@ -10,7 +10,7 @@ class SciCat(AuthClient): "username": username, "password": password } - res = post_request(url, auth_payload, AUTH_HEADERS) + res = post_request(url, auth_payload, HEADER_JSON) try: token = res["id"] except KeyError as e: diff --git a/scilog/scilog.py b/scilog/scilog.py index d9109a3..461d157 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,41 +1,90 @@ -from .authclient import AuthClient, AuthError, AUTH_HEADERS -from .utils import post_request, get_request +from __future__ import annotations + +from .authclient import AuthMixin, AuthError, HEADER_JSON from .mkfilt import make_filter +from .httpclient import HttpClient +from .snippet import Snippet +from typing import TypeVar, Union, List, Type, get_type_hints +import functools -class SciLog(AuthClient): +class Basesnippet(Snippet): + def __init__(self): + super().__init__() + self.set_properties( + id=str, + parentId=str, + ownerGroup=str, + accessGroups=list, + snippetType=str, + isPrivate=bool, + createdAt=str, + createdBy=str, + updatedAt=str, + updateBy=str, + subsnippets=List[type(Basesnippet)], + tags=List[str], + dashboardName=str, + files=str, + location=str, + defaultOrder=int, + linkType=str, + versionable=bool, + deleted=bool) + - def authenticate(self, username, password): - url = self.address + "/users/login" - auth_payload = { - "principal": username, - "password": password - } - res = post_request(url, auth_payload, AUTH_HEADERS) - try: - token = "Bearer " + res["token"] - except KeyError as e: - raise SciLogAuthError(res) from e - else: - return token +class Paragraph(Basesnippet): + def __init__(self): + super().__init__() + self.set_properties(textcontent=str, isMessage=str) + + +class SciLogRestAPI(HttpClient): + def __init__(self, url): + super().__init__(url) + self._verify_certificate = False + + +class SciLog(): + + def __init__(self, url="https://lnode2.psi.ch/api/v1"): + self.http_client = SciLogRestAPI(url) + self.logbook_id = None + self.owner_group = None + + def select_logbook(self, logbook:type(Basesnippet)): + self.logbook_id = logbook.id + self.owner_group = logbook.ownerGroup def get_snippets(self, **kwargs): - url = self.address + "/basesnippets" - params = make_filter(**kwargs) - headers = self.auth_headers - return get_request(url, params=params, headers=headers) + url = self.http_client.address + "/basesnippets" + params = self.http_client.make_filter(where=kwargs) + headers = HEADER_JSON.copy() + return self.http_client.get_request(url, params=params, headers=headers) + + def send_message(self, msg, **kwargs): + url = self.http_client.address + "/basesnippets" + payload = kwargs + kwargs["textcontent"] = msg + headers = HEADER_JSON.copy() + return self.http_client.post_request(url, payload=payload, headers=headers) def post_snippet(self, **kwargs): - url = self.address + "/basesnippets" + url = self.http_client.address + "/basesnippets" payload = kwargs - headers = self.auth_headers - return post_request(url, payload=payload, headers=headers) + headers = HEADER_JSON.copy() + return self.http_client.post_request(url, payload=payload, headers=headers) + def get_logbooks(self, **kwargs): + url = self.http_client.address + "/basesnippets" + snippet = Basesnippet() + snippet.import_dict(kwargs) + snippet.snippetType = "logbook" + params = self.http_client.make_filter(where=snippet.to_dict(include_none=False)) + headers = HEADER_JSON.copy() + return Basesnippet.from_http_response(self.http_client.get_request(url, params=params, headers=headers)) class SciLogAuthError(AuthError): pass - - - diff --git a/scilog/snippet.py b/scilog/snippet.py new file mode 100644 index 0000000..1bde29b --- /dev/null +++ b/scilog/snippet.py @@ -0,0 +1,72 @@ +from typing import Type, get_type_hints, TypeVar +import functools + +def typechecked(func): + @functools.wraps(func) + def typechecked_call(*args, **kwargs): + func_types = get_type_hints(func) + for index, key in enumerate(func_types.keys()): + if key != "return": + assert func_types[key] == type(args[index+1]), f"{repr(func)} expected to receive input of type {func_types[key].__name__} but received {type(args[index+1]).__name__}" + return func(*args, **kwargs) + return typechecked_call + +def property_maker(cls, name, type_name): + storage_name = '_' + name + + @property + def prop(self) -> type_name: + return getattr(self, storage_name) + + @prop.setter + @typechecked + def prop(self, value: type_name) -> None: + setattr(self, storage_name, value) + + return prop + +class Snippet(dict): + def __init__(self, **kwargs): + self._properties = [] + self.set_properties(**kwargs) + + def set_properties(self, **kwargs): + for key, value in kwargs.items(): + storage_name = '_' + key + setattr(Snippet, storage_name, None) + setattr(Snippet, key, property_maker(self, key, value)) + self._properties.append(key) + + def to_dict(self, include_none=True): + if include_none: + return {key: getattr(self, key) for key in self._properties } + else: + return {key: getattr(self, key) for key in self._properties if getattr(self, key) is not None} + + def import_dict(self, properties): + for key in properties.keys(): + setattr(self, key, properties[key]) + + @classmethod + def from_dict(cls, properties): + tmp = cls() + tmp.import_dict(properties) + return tmp + + def __str__(self): + return f"{type(self).__name__}" + + @classmethod + def from_http_response(cls, response): + if type(response)==list: + return [cls.from_dict(resp) for resp in response] + else: + return cls.from_dict(response) + + + + +if __name__ == "__main__": + tmp = Snippet(id=str, textcontent=str, defaultOrder=int) + print(tmp.id) + tmp.id = 2 \ No newline at end of file From b44101b394a68682be583d1456c77d9204342d46 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Mon, 31 May 2021 21:25:02 +0200 Subject: [PATCH 02/26] moved basesnippet and paragraph to snippet.py --- scilog/__init__.py | 2 +- scilog/scilog.py | 33 +-------------------------------- scilog/snippet.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/scilog/__init__.py b/scilog/__init__.py index b3dbd1c..a15b7b8 100644 --- a/scilog/__init__.py +++ b/scilog/__init__.py @@ -1,5 +1,5 @@ from .scicat import SciCat from .scilog import SciLog -from .scilog import Basesnippet, Paragraph +from .snippet import Basesnippet, Paragraph diff --git a/scilog/scilog.py b/scilog/scilog.py index 461d157..fa0f2db 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -3,42 +3,11 @@ from __future__ import annotations from .authclient import AuthMixin, AuthError, HEADER_JSON from .mkfilt import make_filter from .httpclient import HttpClient -from .snippet import Snippet +from .snippet import Snippet, Basesnippet from typing import TypeVar, Union, List, Type, get_type_hints import functools -class Basesnippet(Snippet): - def __init__(self): - super().__init__() - self.set_properties( - id=str, - parentId=str, - ownerGroup=str, - accessGroups=list, - snippetType=str, - isPrivate=bool, - createdAt=str, - createdBy=str, - updatedAt=str, - updateBy=str, - subsnippets=List[type(Basesnippet)], - tags=List[str], - dashboardName=str, - files=str, - location=str, - defaultOrder=int, - linkType=str, - versionable=bool, - deleted=bool) - - - -class Paragraph(Basesnippet): - def __init__(self): - super().__init__() - self.set_properties(textcontent=str, isMessage=str) - class SciLogRestAPI(HttpClient): def __init__(self, url): diff --git a/scilog/snippet.py b/scilog/snippet.py index 1bde29b..31939ad 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -64,6 +64,36 @@ class Snippet(dict): return cls.from_dict(response) +class Basesnippet(Snippet): + def __init__(self): + super().__init__() + self.set_properties( + id=str, + parentId=str, + ownerGroup=str, + accessGroups=list, + snippetType=str, + isPrivate=bool, + createdAt=str, + createdBy=str, + updatedAt=str, + updateBy=str, + subsnippets=list, + tags=list, + dashboardName=str, + files=str, + location=str, + defaultOrder=int, + linkType=str, + versionable=bool, + deleted=bool) + + + +class Paragraph(Basesnippet): + def __init__(self): + super().__init__() + self.set_properties(textcontent=str, isMessage=str) if __name__ == "__main__": From a57ef9879e48a1ece2a4ece2fda72d2a35f76469 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Tue, 1 Jun 2021 09:14:03 +0200 Subject: [PATCH 03/26] bug fixes --- scilog/httpclient.py | 5 +---- scilog/scilog.py | 38 ++++++++++++++++++++++++++++---------- scilog/snippet.py | 4 +++- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 9d79ff3..01b226a 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -46,10 +46,7 @@ class HttpClient(AuthMixin): else: if response.reason == "Unauthorized": self.config.delete() - self._retrieve_token() - raise response.raise_for_status() - else: - raise response.raise_for_status() + raise response.raise_for_status() @authenticated diff --git a/scilog/scilog.py b/scilog/scilog.py index fa0f2db..9ed13a5 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -3,11 +3,26 @@ from __future__ import annotations from .authclient import AuthMixin, AuthError, HEADER_JSON from .mkfilt import make_filter from .httpclient import HttpClient -from .snippet import Snippet, Basesnippet +from .snippet import Snippet, Basesnippet, Paragraph from typing import TypeVar, Union, List, Type, get_type_hints import functools - +def pinned_to_logbook(logbook_keys): + def pinned_to_logbook_inner(func): + @functools.wraps(func) + def pinned_to_logbook_call(*args, **kwargs): + if isinstance(args[0].logbook, Basesnippet): + for key in logbook_keys: + if key not in kwargs.keys(): + if key == "parentId": + kwargs[key] = args[0].logbook.id + else: + kwargs[key] = getattr(args[0].logbook, key) + else: + raise Warning("No logbook selected.") + return func(*args, **kwargs) + return pinned_to_logbook_call + return pinned_to_logbook_inner class SciLogRestAPI(HttpClient): def __init__(self, url): @@ -19,31 +34,34 @@ class SciLog(): def __init__(self, url="https://lnode2.psi.ch/api/v1"): self.http_client = SciLogRestAPI(url) - self.logbook_id = None - self.owner_group = None + self.logbook = None def select_logbook(self, logbook:type(Basesnippet)): - self.logbook_id = logbook.id - self.owner_group = logbook.ownerGroup + self.logbook = logbook + @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) def get_snippets(self, **kwargs): url = self.http_client.address + "/basesnippets" params = self.http_client.make_filter(where=kwargs) headers = HEADER_JSON.copy() - return self.http_client.get_request(url, params=params, headers=headers) + return Basesnippet.from_http_response(self.http_client.get_request(url, params=params, headers=headers)) + @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) def send_message(self, msg, **kwargs): url = self.http_client.address + "/basesnippets" payload = kwargs - kwargs["textcontent"] = msg + snippet = Paragraph() + snippet.import_dict(kwargs) + snippet.textcontent = msg headers = HEADER_JSON.copy() - return self.http_client.post_request(url, payload=payload, headers=headers) + return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=headers)) + @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) def post_snippet(self, **kwargs): url = self.http_client.address + "/basesnippets" payload = kwargs headers = HEADER_JSON.copy() - return self.http_client.post_request(url, payload=payload, headers=headers) + return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=headers)) def get_logbooks(self, **kwargs): url = self.http_client.address + "/basesnippets" diff --git a/scilog/snippet.py b/scilog/snippet.py index 31939ad..cb460a2 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -81,12 +81,13 @@ class Basesnippet(Snippet): subsnippets=list, tags=list, dashboardName=str, - files=str, + files=list, location=str, defaultOrder=int, linkType=str, versionable=bool, deleted=bool) + self.snippetType = "basesnippet" @@ -94,6 +95,7 @@ class Paragraph(Basesnippet): def __init__(self): super().__init__() self.set_properties(textcontent=str, isMessage=str) + self.snippetType = "paragraph" if __name__ == "__main__": From 0cc2cc390f18ea6165f4f5b0d71edd46227f4fa0 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Tue, 1 Jun 2021 09:26:55 +0200 Subject: [PATCH 04/26] bug fix --- scilog/scilog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scilog/scilog.py b/scilog/scilog.py index 9ed13a5..798441a 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -49,10 +49,10 @@ class SciLog(): @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) def send_message(self, msg, **kwargs): url = self.http_client.address + "/basesnippets" - payload = kwargs snippet = Paragraph() snippet.import_dict(kwargs) - snippet.textcontent = msg + snippet.textcontent = msg + payload = snippet.to_dict(include_none=False) headers = HEADER_JSON.copy() return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=headers)) From adf4253d72401c2df4966bf5baeacb3e4dde359b Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 08:47:13 +0200 Subject: [PATCH 05/26] os -> pathlib --- scilog/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scilog/config.py b/scilog/config.py index bd5a3ef..d550974 100644 --- a/scilog/config.py +++ b/scilog/config.py @@ -1,6 +1,6 @@ from pathlib import Path import json -import os + class Config(dict): @@ -29,9 +29,9 @@ class Config(dict): def _save(self): json_save(self, self.fname) + def delete(self): - print(self.fname) - os.remove(self.fname) + self.fname.unlink() From db884be531aeefee8087f0ebe6fcc6c85a2ba7ee Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:01:35 +0200 Subject: [PATCH 06/26] removed now unused file --- scilog/mkfilt.py | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 scilog/mkfilt.py diff --git a/scilog/mkfilt.py b/scilog/mkfilt.py deleted file mode 100644 index d11097b..0000000 --- a/scilog/mkfilt.py +++ /dev/null @@ -1,11 +0,0 @@ -import json - - -def make_filter(**kwargs): - items = [{k: v} for k, v in kwargs.items()] - filt = {"where": {"and": items}} - filt = json.dumps(filt) - return {"filter": filt} - - - From c32aa34bf405380408c376414d4895d7edd035d0 Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:04:43 +0200 Subject: [PATCH 07/26] moved HEADER_JSON copy to httpclient --- scilog/httpclient.py | 4 ++-- scilog/scilog.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 01b226a..b8e0d0c 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -11,10 +11,10 @@ def authenticated(func): if not issubclass(type(args[0]), HttpClient): raise AttributeError("First argument must be an instance of HttpClient") if "headers" in kwargs: - kwargs["headers"]["Authorization"] = args[0].token + kwargs["headers"] = kwargs["headers"].copy() else: kwargs["headers"] = {} - kwargs["headers"]["Authorization"] = args[0].token + kwargs["headers"]["Authorization"] = args[0].token return func(*args, **kwargs) return authenticated_call diff --git a/scilog/scilog.py b/scilog/scilog.py index 798441a..faf57c7 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,7 +1,6 @@ from __future__ import annotations from .authclient import AuthMixin, AuthError, HEADER_JSON -from .mkfilt import make_filter from .httpclient import HttpClient from .snippet import Snippet, Basesnippet, Paragraph from typing import TypeVar, Union, List, Type, get_type_hints @@ -51,17 +50,15 @@ class SciLog(): url = self.http_client.address + "/basesnippets" snippet = Paragraph() snippet.import_dict(kwargs) - snippet.textcontent = msg + snippet.textcontent = msg payload = snippet.to_dict(include_none=False) - headers = HEADER_JSON.copy() - return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=headers)) + return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=HEADER_JSON)) @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) def post_snippet(self, **kwargs): url = self.http_client.address + "/basesnippets" payload = kwargs - headers = HEADER_JSON.copy() - return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=headers)) + return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=HEADER_JSON)) def get_logbooks(self, **kwargs): url = self.http_client.address + "/basesnippets" @@ -69,9 +66,11 @@ class SciLog(): snippet.import_dict(kwargs) snippet.snippetType = "logbook" params = self.http_client.make_filter(where=snippet.to_dict(include_none=False)) - headers = HEADER_JSON.copy() - return Basesnippet.from_http_response(self.http_client.get_request(url, params=params, headers=headers)) + return Basesnippet.from_http_response(self.http_client.get_request(url, params=params, headers=HEADER_JSON)) class SciLogAuthError(AuthError): pass + + + From 84d404ba027fda436f222f479d4638db4f445e9c Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:05:28 +0200 Subject: [PATCH 08/26] some newlines and a todo note --- scilog/__init__.py | 1 + scilog/authclient.py | 1 - scilog/scicat.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scilog/__init__.py b/scilog/__init__.py index a15b7b8..5790e10 100644 --- a/scilog/__init__.py +++ b/scilog/__init__.py @@ -3,3 +3,4 @@ from .scicat import SciCat from .scilog import SciLog from .snippet import Basesnippet, Paragraph + diff --git a/scilog/authclient.py b/scilog/authclient.py index a74d55c..b9f0670 100644 --- a/scilog/authclient.py +++ b/scilog/authclient.py @@ -42,7 +42,6 @@ class AuthMixin(ABC): password = getpass.getpass(prompt=f"{tn} password for {username}: ") token = self.authenticate(username, password) self.config[username] = self._token = token - return token diff --git a/scilog/scicat.py b/scilog/scicat.py index c06fad7..ed0118f 100644 --- a/scilog/scicat.py +++ b/scilog/scicat.py @@ -22,7 +22,7 @@ class SciCat(AuthMixin): @property def proposals(self): url = self.address + "/proposals" - headers = self.auth_headers + headers = self.auth_headers #TODO return get_request(url, headers=headers) From 9135bf7fb8ed9ff2bbaf3bf2a9799f2a6e4ea66d Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:07:15 +0200 Subject: [PATCH 09/26] renamed authclient.py -> authmixin.py --- scilog/{authclient.py => authmixin.py} | 0 scilog/httpclient.py | 2 +- scilog/scicat.py | 2 +- scilog/scilog.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename scilog/{authclient.py => authmixin.py} (100%) diff --git a/scilog/authclient.py b/scilog/authmixin.py similarity index 100% rename from scilog/authclient.py rename to scilog/authmixin.py diff --git a/scilog/httpclient.py b/scilog/httpclient.py index b8e0d0c..820d44e 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -3,7 +3,7 @@ import functools import json from abc import ABC, abstractmethod -from .authclient import AuthMixin, AuthError, HEADER_JSON +from .authmixin import AuthMixin, AuthError, HEADER_JSON def authenticated(func): @functools.wraps(func) diff --git a/scilog/scicat.py b/scilog/scicat.py index ed0118f..911e5bf 100644 --- a/scilog/scicat.py +++ b/scilog/scicat.py @@ -1,4 +1,4 @@ -from .authclient import AuthMixin, AuthError, HEADER_JSON +from .authmixin import AuthMixin, AuthError, HEADER_JSON from .utils import post_request, get_request diff --git a/scilog/scilog.py b/scilog/scilog.py index faf57c7..1d76bdb 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,6 +1,6 @@ from __future__ import annotations -from .authclient import AuthMixin, AuthError, HEADER_JSON +from .authmixin import AuthMixin, AuthError, HEADER_JSON from .httpclient import HttpClient from .snippet import Snippet, Basesnippet, Paragraph from typing import TypeVar, Union, List, Type, get_type_hints From 51554eb63d399f0f49ceab30a5533b8da4bed4c3 Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:09:37 +0200 Subject: [PATCH 10/26] removed trailing spaces --- scilog/httpclient.py | 6 +++--- scilog/snippet.py | 38 ++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 820d44e..67f3a92 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -4,7 +4,7 @@ import json from abc import ABC, abstractmethod from .authmixin import AuthMixin, AuthError, HEADER_JSON - + def authenticated(func): @functools.wraps(func) def authenticated_call(*args, **kwargs): @@ -43,12 +43,12 @@ class HttpClient(AuthMixin): response = requests.get(url, params=params, headers=headers, timeout=timeout, verify=self._verify_certificate) if response.ok: return response.json() - else: + else: if response.reason == "Unauthorized": self.config.delete() raise response.raise_for_status() - + @authenticated def post_request(self, url, payload=None, headers=None, timeout=10): response = requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() diff --git a/scilog/snippet.py b/scilog/snippet.py index cb460a2..712d437 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -1,5 +1,6 @@ +import functools from typing import Type, get_type_hints, TypeVar -import functools + def typechecked(func): @functools.wraps(func) @@ -11,6 +12,7 @@ def typechecked(func): return func(*args, **kwargs) return typechecked_call + def property_maker(cls, name, type_name): storage_name = '_' + name @@ -25,11 +27,14 @@ def property_maker(cls, name, type_name): return prop -class Snippet(dict): + + +class Snippet: + def __init__(self, **kwargs): self._properties = [] self.set_properties(**kwargs) - + def set_properties(self, **kwargs): for key, value in kwargs.items(): storage_name = '_' + key @@ -64,19 +69,20 @@ class Snippet(dict): return cls.from_dict(response) + class Basesnippet(Snippet): def __init__(self): super().__init__() self.set_properties( - id=str, - parentId=str, - ownerGroup=str, - accessGroups=list, - snippetType=str, - isPrivate=bool, - createdAt=str, - createdBy=str, - updatedAt=str, + id=str, + parentId=str, + ownerGroup=str, + accessGroups=list, + snippetType=str, + isPrivate=bool, + createdAt=str, + createdBy=str, + updatedAt=str, updateBy=str, subsnippets=list, tags=list, @@ -88,7 +94,7 @@ class Basesnippet(Snippet): versionable=bool, deleted=bool) self.snippetType = "basesnippet" - + class Paragraph(Basesnippet): @@ -98,7 +104,11 @@ class Paragraph(Basesnippet): self.snippetType = "paragraph" + if __name__ == "__main__": tmp = Snippet(id=str, textcontent=str, defaultOrder=int) print(tmp.id) - tmp.id = 2 \ No newline at end of file + tmp.id = 2 + + + From d0deb73522af7e8599007c398b0c18cbc79b2533 Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:12:25 +0200 Subject: [PATCH 11/26] import order and spaces --- scilog/httpclient.py | 6 +++--- scilog/scilog.py | 7 +++++-- scilog/snippet.py | 2 -- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 67f3a92..92b0206 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -2,9 +2,9 @@ import requests import functools import json from abc import ABC, abstractmethod - from .authmixin import AuthMixin, AuthError, HEADER_JSON + def authenticated(func): @functools.wraps(func) def authenticated_call(*args, **kwargs): @@ -15,11 +15,12 @@ def authenticated(func): else: kwargs["headers"] = {} kwargs["headers"]["Authorization"] = args[0].token - return func(*args, **kwargs) return authenticated_call + class HttpClient(AuthMixin): + def __init__(self, address): self.address = address self._verify_certificate = True @@ -48,7 +49,6 @@ class HttpClient(AuthMixin): self.config.delete() raise response.raise_for_status() - @authenticated def post_request(self, url, payload=None, headers=None, timeout=10): response = requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() diff --git a/scilog/scilog.py b/scilog/scilog.py index 1d76bdb..1cc7e53 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,10 +1,12 @@ from __future__ import annotations +import functools +from typing import TypeVar, Union, List, Type, get_type_hints + from .authmixin import AuthMixin, AuthError, HEADER_JSON from .httpclient import HttpClient from .snippet import Snippet, Basesnippet, Paragraph -from typing import TypeVar, Union, List, Type, get_type_hints -import functools + def pinned_to_logbook(logbook_keys): def pinned_to_logbook_inner(func): @@ -23,6 +25,7 @@ def pinned_to_logbook(logbook_keys): return pinned_to_logbook_call return pinned_to_logbook_inner + class SciLogRestAPI(HttpClient): def __init__(self, url): super().__init__(url) diff --git a/scilog/snippet.py b/scilog/snippet.py index 712d437..53b619b 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -28,7 +28,6 @@ def property_maker(cls, name, type_name): return prop - class Snippet: def __init__(self, **kwargs): @@ -96,7 +95,6 @@ class Basesnippet(Snippet): self.snippetType = "basesnippet" - class Paragraph(Basesnippet): def __init__(self): super().__init__() From a026df3120fd087d47a84f4815ad421f367b6291 Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:17:02 +0200 Subject: [PATCH 12/26] removed unused imports; formatting --- scilog/httpclient.py | 2 +- scilog/scilog.py | 5 +---- scilog/snippet.py | 8 +++++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 92b0206..684ee07 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -1,7 +1,7 @@ import requests import functools import json -from abc import ABC, abstractmethod + from .authmixin import AuthMixin, AuthError, HEADER_JSON diff --git a/scilog/scilog.py b/scilog/scilog.py index 1cc7e53..cf88da5 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,9 +1,6 @@ from __future__ import annotations -import functools -from typing import TypeVar, Union, List, Type, get_type_hints - -from .authmixin import AuthMixin, AuthError, HEADER_JSON +from .authmixin import AuthError, HEADER_JSON from .httpclient import HttpClient from .snippet import Snippet, Basesnippet, Paragraph diff --git a/scilog/snippet.py b/scilog/snippet.py index 53b619b..3c323a2 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -1,5 +1,5 @@ import functools -from typing import Type, get_type_hints, TypeVar +from typing import get_type_hints def typechecked(func): @@ -62,7 +62,7 @@ class Snippet: @classmethod def from_http_response(cls, response): - if type(response)==list: + if type(response) == list: return [cls.from_dict(resp) for resp in response] else: return cls.from_dict(response) @@ -91,10 +91,12 @@ class Basesnippet(Snippet): defaultOrder=int, linkType=str, versionable=bool, - deleted=bool) + deleted=bool + ) self.snippetType = "basesnippet" + class Paragraph(Basesnippet): def __init__(self): super().__init__() From 650f7be38d317e74b2362eb581d4b444d62fb2b4 Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:20:35 +0200 Subject: [PATCH 13/26] fixed Config initialized via update method --- scilog/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scilog/config.py b/scilog/config.py index d550974..35fdb02 100644 --- a/scilog/config.py +++ b/scilog/config.py @@ -11,7 +11,7 @@ class Config(dict): folder = Path.home() self.fname = folder / fname content = self._load() - super().update(content) + super().__init__(content) def __setitem__(self, name, value): self.update(**{name: value}) From f36e2534c46acad98acafb94f5648c8fc5d1edae Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:30:43 +0200 Subject: [PATCH 14/26] some cleanup --- scilog/httpclient.py | 5 +---- scilog/snippet.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 684ee07..9d433e7 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -1,6 +1,6 @@ -import requests import functools import json +import requests from .authmixin import AuthMixin, AuthError, HEADER_JSON @@ -57,9 +57,6 @@ class HttpClient(AuthMixin): def _login(self, payload=None, headers=None, timeout=10): return requests.post(self.address + "/users/login", json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() - def typename(self, obj): - return type(obj).__name__ - @staticmethod def make_filter(where:dict=None, limit:int=0, skip:int=0, fields:dict=None, include:dict=None, order:list=None): filt = dict() diff --git a/scilog/snippet.py b/scilog/snippet.py index 3c323a2..3dcb985 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -1,5 +1,6 @@ import functools from typing import get_type_hints +from .utils import typename def typechecked(func): @@ -13,7 +14,7 @@ def typechecked(func): return typechecked_call -def property_maker(cls, name, type_name): +def property_maker(name, type_name): storage_name = '_' + name @property @@ -31,6 +32,7 @@ def property_maker(cls, name, type_name): class Snippet: def __init__(self, **kwargs): + super().__init__() self._properties = [] self.set_properties(**kwargs) @@ -38,12 +40,12 @@ class Snippet: for key, value in kwargs.items(): storage_name = '_' + key setattr(Snippet, storage_name, None) - setattr(Snippet, key, property_maker(self, key, value)) + setattr(Snippet, key, property_maker(key, value)) self._properties.append(key) def to_dict(self, include_none=True): if include_none: - return {key: getattr(self, key) for key in self._properties } + return {key: getattr(self, key) for key in self._properties} else: return {key: getattr(self, key) for key in self._properties if getattr(self, key) is not None} @@ -53,16 +55,16 @@ class Snippet: @classmethod def from_dict(cls, properties): - tmp = cls() - tmp.import_dict(properties) - return tmp + new = cls() + new.import_dict(properties) + return new def __str__(self): - return f"{type(self).__name__}" + return typename(self) @classmethod def from_http_response(cls, response): - if type(response) == list: + if isinstance(response, list): return [cls.from_dict(resp) for resp in response] else: return cls.from_dict(response) From b4794554286192695514f810b3e5d9fcb07ce7ac Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:34:39 +0200 Subject: [PATCH 15/26] some more cleanup --- scilog/httpclient.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 9d433e7..6457e8d 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -7,15 +7,15 @@ from .authmixin import AuthMixin, AuthError, HEADER_JSON def authenticated(func): @functools.wraps(func) - def authenticated_call(*args, **kwargs): - if not issubclass(type(args[0]), HttpClient): + def authenticated_call(client, *args, **kwargs): + if not isinstance(client, HttpClient): raise AttributeError("First argument must be an instance of HttpClient") if "headers" in kwargs: kwargs["headers"] = kwargs["headers"].copy() else: kwargs["headers"] = {} - kwargs["headers"]["Authorization"] = args[0].token - return func(*args, **kwargs) + kwargs["headers"]["Authorization"] = client.token + return func(client, *args, **kwargs) return authenticated_call @@ -51,8 +51,7 @@ class HttpClient(AuthMixin): @authenticated def post_request(self, url, payload=None, headers=None, timeout=10): - response = requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() - return response + return requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() def _login(self, payload=None, headers=None, timeout=10): return requests.post(self.address + "/users/login", json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() From 4f84b392646c344c23e24c0757ca3f8409906bba Mon Sep 17 00:00:00 2001 From: NichtJens Date: Tue, 1 Jun 2021 09:48:04 +0200 Subject: [PATCH 16/26] merge cleanup --- scilog/scilog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scilog/scilog.py b/scilog/scilog.py index cf88da5..7dcd58c 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,4 +1,5 @@ from __future__ import annotations +import functools from .authmixin import AuthError, HEADER_JSON from .httpclient import HttpClient @@ -51,7 +52,7 @@ class SciLog(): snippet = Paragraph() snippet.import_dict(kwargs) snippet.textcontent = msg - payload = snippet.to_dict(include_none=False) + payload = snippet.to_dict(include_none=False) return Basesnippet.from_http_response(self.http_client.post_request(url, payload=payload, headers=HEADER_JSON)) @pinned_to_logbook(["parentId", "ownerGroup", "accessGroups"]) From f86192f85a1b46950128a598f0a39a836dd529e9 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 10:07:46 +0200 Subject: [PATCH 17/26] more cleanup --- scilog/authmixin.py | 1 - scilog/httpclient.py | 2 +- scilog/scicat.py | 1 - scilog/snippet.py | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scilog/authmixin.py b/scilog/authmixin.py index b9f0670..be0d395 100644 --- a/scilog/authmixin.py +++ b/scilog/authmixin.py @@ -22,7 +22,6 @@ class AuthMixin(ABC): tn = typename(self) return f"{tn} @ {self.address}" - @abstractmethod def authenticate(self, username, password): raise NotImplementedError diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 6457e8d..2479a0c 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -60,7 +60,7 @@ class HttpClient(AuthMixin): def make_filter(where:dict=None, limit:int=0, skip:int=0, fields:dict=None, include:dict=None, order:list=None): filt = dict() if where is not None: - items = [{k: v} for k, v in where.items()] + items = [where.copy()] filt["where"] = {"and": items} if limit > 0: filt["limit"] = limit diff --git a/scilog/scicat.py b/scilog/scicat.py index 911e5bf..dafb22e 100644 --- a/scilog/scicat.py +++ b/scilog/scicat.py @@ -18,7 +18,6 @@ class SciCat(AuthMixin): else: return token - @property def proposals(self): url = self.address + "/proposals" diff --git a/scilog/snippet.py b/scilog/snippet.py index 3dcb985..4b82a9f 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -29,10 +29,10 @@ def property_maker(name, type_name): return prop + class Snippet: def __init__(self, **kwargs): - super().__init__() self._properties = [] self.set_properties(**kwargs) From cf03c4e4907a4e41ffae761a01b865547f715f34 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 10:18:53 +0200 Subject: [PATCH 18/26] raise Warning() -> warnings.warn() --- scilog/scilog.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scilog/scilog.py b/scilog/scilog.py index 7dcd58c..22e55bb 100644 --- a/scilog/scilog.py +++ b/scilog/scilog.py @@ -1,5 +1,6 @@ from __future__ import annotations import functools +import warnings from .authmixin import AuthError, HEADER_JSON from .httpclient import HttpClient @@ -9,17 +10,17 @@ from .snippet import Snippet, Basesnippet, Paragraph def pinned_to_logbook(logbook_keys): def pinned_to_logbook_inner(func): @functools.wraps(func) - def pinned_to_logbook_call(*args, **kwargs): - if isinstance(args[0].logbook, Basesnippet): - for key in logbook_keys: - if key not in kwargs.keys(): - if key == "parentId": - kwargs[key] = args[0].logbook.id - else: - kwargs[key] = getattr(args[0].logbook, key) + def pinned_to_logbook_call(log, *args, **kwargs): + if not isinstance(log.logbook, Basesnippet): + warnings.warn("No logbook selected.") else: - raise Warning("No logbook selected.") - return func(*args, **kwargs) + for key in logbook_keys: + if key not in kwargs: + if key == "parentId": + kwargs[key] = log.logbook.id + else: + kwargs[key] = getattr(log.logbook, key) + return func(log, *args, **kwargs) return pinned_to_logbook_call return pinned_to_logbook_inner From cbca10f3473046b8551aff569c270cf98cf44611 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 10:30:41 +0200 Subject: [PATCH 19/26] refactored typechecked() --- scilog/snippet.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scilog/snippet.py b/scilog/snippet.py index 4b82a9f..31a3586 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -5,12 +5,14 @@ from .utils import typename def typechecked(func): @functools.wraps(func) - def typechecked_call(*args, **kwargs): - func_types = get_type_hints(func) - for index, key in enumerate(func_types.keys()): - if key != "return": - assert func_types[key] == type(args[index+1]), f"{repr(func)} expected to receive input of type {func_types[key].__name__} but received {type(args[index+1]).__name__}" - return func(*args, **kwargs) + def typechecked_call(obj, *args, **kwargs): + type_hints = get_type_hints(func) + del type_hints["return"] + for arg, dtype in zip(args, type_hints): + arg_type = type(arg) + if dtype != arg_type: + raise TypeError(f"{func} expected to receive input of type {dtype.__name__} but received {arg_type.__name__}") + return func(obj, *args, **kwargs) return typechecked_call From a1bda2c0b3c8c9ba03b26df6c837a01a0b46aee9 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 10:56:49 +0200 Subject: [PATCH 20/26] refactor --- scilog/snippet.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/scilog/snippet.py b/scilog/snippet.py index 31a3586..8aa4df2 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -16,16 +16,16 @@ def typechecked(func): return typechecked_call -def property_maker(name, type_name): +def property_maker(name, dtype): storage_name = '_' + name @property - def prop(self) -> type_name: + def prop(self) -> dtype: return getattr(self, storage_name) @prop.setter @typechecked - def prop(self, value: type_name) -> None: + def prop(self, value: dtype) -> None: setattr(self, storage_name, value) return prop @@ -34,16 +34,17 @@ def property_maker(name, type_name): class Snippet: - def __init__(self, **kwargs): + def __init__(self, snippetType="snippet", **kwargs): self._properties = [] + self.snippetType = snippetType self.set_properties(**kwargs) def set_properties(self, **kwargs): - for key, value in kwargs.items(): - storage_name = '_' + key + for name, dtype in kwargs.items(): + storage_name = '_' + name setattr(Snippet, storage_name, None) - setattr(Snippet, key, property_maker(key, value)) - self._properties.append(key) + setattr(Snippet, name, property_maker(name, dtype)) + self._properties.append(name) def to_dict(self, include_none=True): if include_none: @@ -52,8 +53,7 @@ class Snippet: return {key: getattr(self, key) for key in self._properties if getattr(self, key) is not None} def import_dict(self, properties): - for key in properties.keys(): - setattr(self, key, properties[key]) + self.__dict__.update(properties) @classmethod def from_dict(cls, properties): @@ -74,9 +74,9 @@ class Snippet: class Basesnippet(Snippet): + def __init__(self): - super().__init__() - self.set_properties( + props = dict( id=str, parentId=str, ownerGroup=str, @@ -97,15 +97,18 @@ class Basesnippet(Snippet): versionable=bool, deleted=bool ) - self.snippetType = "basesnippet" + super().__init__(snippetType="basesnippet", **props) class Paragraph(Basesnippet): + def __init__(self): - super().__init__() - self.set_properties(textcontent=str, isMessage=str) - self.snippetType = "paragraph" + props = dict( + textcontent=str, + isMessage=str + ) + super().__init__(snippetType="paragraph", **props) From f2aa686aa8b62fa658a3b3c59aa819fa84726059 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 11:20:00 +0200 Subject: [PATCH 21/26] fixed typechecked() --- scilog/snippet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scilog/snippet.py b/scilog/snippet.py index 8aa4df2..906b4b6 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -8,7 +8,7 @@ def typechecked(func): def typechecked_call(obj, *args, **kwargs): type_hints = get_type_hints(func) del type_hints["return"] - for arg, dtype in zip(args, type_hints): + for arg, dtype in zip(args, type_hints.values()): arg_type = type(arg) if dtype != arg_type: raise TypeError(f"{func} expected to receive input of type {dtype.__name__} but received {arg_type.__name__}") From fff194c0243cf9d5b7065bd7861aeb50163f3c96 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 12:51:38 +0200 Subject: [PATCH 22/26] print logbooks; added note on making url configurable --- example_scilog.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example_scilog.py b/example_scilog.py index 74f093b..daf0b58 100755 --- a/example_scilog.py +++ b/example_scilog.py @@ -20,11 +20,13 @@ from scilog import Basesnippet, Paragraph tmp = Basesnippet() tmp.id = "2" -# url = "https://lnode2.psi.ch/api/v1" -url = "http://[::1]:3000/" +#TODO make this selectable +url = "https://lnode2.psi.ch/api/v1" +#url = "http://[::1]:3000/" log = SciLog(url) #print(log.token) logbooks = log.get_logbooks(ownerGroup=pgroup) +print(logbooks) assert len(logbooks) == 1 logbook = logbooks[0] From 9b3e27bb59e56ccbdccac4a77f91f59d44f45fab Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 12:52:41 +0200 Subject: [PATCH 23/26] fixed import_dict(); fixed Snippet constr. arguments --- scilog/snippet.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scilog/snippet.py b/scilog/snippet.py index 906b4b6..bbfab50 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -34,10 +34,10 @@ def property_maker(name, dtype): class Snippet: - def __init__(self, snippetType="snippet", **kwargs): + def __init__(self, snippetType="snippet"): self._properties = [] + self.set_properties(snippetType=str) self.snippetType = snippetType - self.set_properties(**kwargs) def set_properties(self, **kwargs): for name, dtype in kwargs.items(): @@ -53,7 +53,8 @@ class Snippet: return {key: getattr(self, key) for key in self._properties if getattr(self, key) is not None} def import_dict(self, properties): - self.__dict__.update(properties) + for name, value in properties.items(): + setattr(self, name, value) @classmethod def from_dict(cls, properties): @@ -75,13 +76,13 @@ class Snippet: class Basesnippet(Snippet): - def __init__(self): - props = dict( + def __init__(self, snippetType="basesnippet"): + super().__init__(snippetType=snippetType) + self.set_properties( id=str, parentId=str, ownerGroup=str, accessGroups=list, - snippetType=str, isPrivate=bool, createdAt=str, createdBy=str, @@ -97,18 +98,17 @@ class Basesnippet(Snippet): versionable=bool, deleted=bool ) - super().__init__(snippetType="basesnippet", **props) class Paragraph(Basesnippet): - def __init__(self): - props = dict( + def __init__(self, snippetType="paragraph"): + super().__init__(snippetType=snippetType) + self.set_properties( textcontent=str, isMessage=str ) - super().__init__(snippetType="paragraph", **props) From df708a579913bc46d4bed3bb8e361c4984af24f7 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 1 Jun 2021 12:57:20 +0200 Subject: [PATCH 24/26] made url selectable --- example_scilog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/example_scilog.py b/example_scilog.py index daf0b58..d50708a 100755 --- a/example_scilog.py +++ b/example_scilog.py @@ -2,11 +2,13 @@ import argparse -parser = argparse.ArgumentParser() +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("pgroup", help="Expected form: p12345") +parser.add_argument("-u", "--url", default="https://lnode2.psi.ch/api/v1", help="Server address") clargs = parser.parse_args() pgroup = clargs.pgroup +url = clargs.url import urllib3 @@ -20,9 +22,6 @@ from scilog import Basesnippet, Paragraph tmp = Basesnippet() tmp.id = "2" -#TODO make this selectable -url = "https://lnode2.psi.ch/api/v1" -#url = "http://[::1]:3000/" log = SciLog(url) #print(log.token) logbooks = log.get_logbooks(ownerGroup=pgroup) From fe6bf79fa644002f5c3e1cdf6c75b815ba99e4d2 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Tue, 1 Jun 2021 15:54:16 +0200 Subject: [PATCH 25/26] fixed scicat for new httpclient --- scilog/httpclient.py | 3 ++- scilog/scicat.py | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/scilog/httpclient.py b/scilog/httpclient.py index 2479a0c..e01d99a 100644 --- a/scilog/httpclient.py +++ b/scilog/httpclient.py @@ -24,6 +24,7 @@ class HttpClient(AuthMixin): def __init__(self, address): self.address = address self._verify_certificate = True + self.login_path = self.address + "/users/login" super().__init__(address) def authenticate(self, username, password): @@ -54,7 +55,7 @@ class HttpClient(AuthMixin): return requests.post(url, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() def _login(self, payload=None, headers=None, timeout=10): - return requests.post(self.address + "/users/login", json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() + return requests.post(self.login_path, json=payload, headers=headers, timeout=timeout, verify=self._verify_certificate).json() @staticmethod def make_filter(where:dict=None, limit:int=0, skip:int=0, fields:dict=None, include:dict=None, order:list=None): diff --git a/scilog/scicat.py b/scilog/scicat.py index dafb22e..a351340 100644 --- a/scilog/scicat.py +++ b/scilog/scicat.py @@ -1,28 +1,35 @@ from .authmixin import AuthMixin, AuthError, HEADER_JSON -from .utils import post_request, get_request +from .httpclient import HttpClient -class SciCat(AuthMixin): +class SciCatRestAPI(HttpClient): + def __init__(self, url): + super().__init__(url) + self.login_path = "https://dacat.psi.ch/auth/msad" def authenticate(self, username, password): - url = self.address + "/users/login" auth_payload = { "username": username, "password": password } - res = post_request(url, auth_payload, HEADER_JSON) + res = self._login(auth_payload, HEADER_JSON) try: - token = res["id"] + token = res["access_token"] except KeyError as e: raise SciCatAuthError(res) from e else: return token +class SciCat(): + + def __init__(self, url="https://dacat.psi.ch/api/v3/"): + self.http_client = SciCatRestAPI(url) + + @property def proposals(self): - url = self.address + "/proposals" - headers = self.auth_headers #TODO - return get_request(url, headers=headers) + url = self.http_client.address + "/proposals" + return self.http_client.get_request(url, headers=HEADER_JSON) From 9f94a63a0c4d3399c1aa9d95d3f82f8e036245ed Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Wed, 9 Jun 2021 11:52:11 +0200 Subject: [PATCH 26/26] attach properties to type(self) instead of Snippet; renamed set_properties -> init_properties --- scilog/snippet.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scilog/snippet.py b/scilog/snippet.py index bbfab50..c717b00 100644 --- a/scilog/snippet.py +++ b/scilog/snippet.py @@ -36,14 +36,15 @@ class Snippet: def __init__(self, snippetType="snippet"): self._properties = [] - self.set_properties(snippetType=str) + self.init_properties(snippetType=str) self.snippetType = snippetType - def set_properties(self, **kwargs): + def init_properties(self, **kwargs): for name, dtype in kwargs.items(): storage_name = '_' + name - setattr(Snippet, storage_name, None) - setattr(Snippet, name, property_maker(name, dtype)) + cls = type(self) + setattr(cls, storage_name, None) + setattr(cls, name, property_maker(name, dtype)) self._properties.append(name) def to_dict(self, include_none=True): @@ -78,7 +79,7 @@ class Basesnippet(Snippet): def __init__(self, snippetType="basesnippet"): super().__init__(snippetType=snippetType) - self.set_properties( + self.init_properties( id=str, parentId=str, ownerGroup=str, @@ -105,7 +106,7 @@ class Paragraph(Basesnippet): def __init__(self, snippetType="paragraph"): super().__init__(snippetType=snippetType) - self.set_properties( + self.init_properties( textcontent=str, isMessage=str )