Skip to content
Snippets Groups Projects
Verified Commit b093d5c1 authored by Daniel Hornung's avatar Daniel Hornung
Browse files

Merge branch 'f-no-error-compare' into f-high-level-serialize

parents da7a10d5 736951e6
Branches
Tags
2 merge requests!189ENH: add convenience functions,!185High level API serialization
Pipeline #50700 passed with warnings
Showing
with 288 additions and 205 deletions
#!/usr/bin/env python3 #!/usr/bin/env python3
"""A small example to get started with caosdb-pylib. # This file is a part of the LinkAhead Project.
#
# Copyright (C) 2024 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2024 Daniel Hornung <d.hornung@indiscale.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""A small example to get started with linkahead-pylib.
Make sure that a `pylinkahead.ini` is readable at one of the expected locations. Make sure that a `pylinkahead.ini` is readable at one of the expected locations.
""" """
import random import random
import linkahead as db import caosdb as db
def reconfigure_connection(): def reconfigure_connection():
...@@ -17,12 +35,12 @@ def reconfigure_connection(): ...@@ -17,12 +35,12 @@ def reconfigure_connection():
def main(): def main():
"""Shows a few examples how to use the CaosDB library.""" """Shows a few examples how to use the LinkAhead library."""
conf = dict(db.configuration.get_config().items("Connection")) conf = dict(db.configuration.get_config().items("Connection"))
print("##### Config:\n{}\n".format(conf)) print("##### Config:\n{}\n".format(conf))
if conf["cacert"] == "/path/to/caosdb.ca.pem": if conf["cacert"] == "/path/to/caosdb.ca.pem":
print("Very likely, the path the the TLS certificate is not correct, " print("Very likely, the path to the TLS certificate is not correct, "
"please fix it.") "please fix it.")
# Query the server, the result is a Container # Query the server, the result is a Container
......
[aliases] [aliases]
test=pytest test=pytest
[pycodestyle] [pycodestyle]
ignore=E501,E121,E123,E126,E226,E24,E704,W503,W504 ignore=E501,E121,E123,E126,E226,E24,E704,W503,W504
[mypy]
ignore_missing_imports = True
# [mypy-linkahead.*]
# check_untyped_defs = True
\ No newline at end of file
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> # Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
# Copyright (C) 2020-2022 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2020-2022 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -29,7 +31,7 @@ from __future__ import annotations ...@@ -29,7 +31,7 @@ from __future__ import annotations
import logging import logging
import warnings import warnings
from collections.abc import Iterable from collections.abc import Iterable
from typing import Any, Dict, List, Union, Optional, Tuple from typing import Any, Union, Optional
from .common.datatype import is_reference from .common.datatype import is_reference
from .common.models import (SPECIAL_ATTRIBUTES, Container, Entity, File, from .common.models import (SPECIAL_ATTRIBUTES, Container, Entity, File,
...@@ -94,14 +96,14 @@ def new_record(record_type: Union[str], ...@@ -94,14 +96,14 @@ def new_record(record_type: Union[str],
return r return r
def id_query(ids: List[int]) -> Container: def id_query(ids: list[int]) -> Container:
warnings.warn("Please use 'create_id_query', which only creates" warnings.warn("Please use 'create_id_query', which only creates"
"the string.", DeprecationWarning) "the string.", DeprecationWarning)
return execute_query(create_id_query(ids)) # type: ignore return execute_query(create_id_query(ids)) # type: ignore
def create_id_query(ids: List[int]) -> str: def create_id_query(ids: list[int]) -> str:
return "FIND ENTITY WITH " + " OR ".join( return "FIND ENTITY WITH " + " OR ".join(
["ID={}".format(id) for id in ids]) ["ID={}".format(id) for id in ids])
...@@ -133,7 +135,7 @@ def retrieve_entity_with_id(eid: int): ...@@ -133,7 +135,7 @@ def retrieve_entity_with_id(eid: int):
return execute_query("FIND ENTITY WITH ID={}".format(eid), unique=True) return execute_query("FIND ENTITY WITH ID={}".format(eid), unique=True)
def retrieve_entities_with_ids(entities: List) -> Container: def retrieve_entities_with_ids(entities: list) -> Container:
collection = Container() collection = Container()
step = 20 step = 20
...@@ -180,7 +182,7 @@ def getCommitIn(folder): ...@@ -180,7 +182,7 @@ def getCommitIn(folder):
def compare_entities(old_entity: Entity, def compare_entities(old_entity: Entity,
new_entity: Entity, new_entity: Entity,
compare_referenced_records: bool = False compare_referenced_records: bool = False
) -> Tuple[Dict[str, Any], Dict[str, Any]]: ) -> tuple[dict[str, Any], dict[str, Any]]:
"""Compare two entites. """Compare two entites.
Return a tuple of dictionaries, the first index belongs to additional information for old Return a tuple of dictionaries, the first index belongs to additional information for old
...@@ -214,15 +216,15 @@ def compare_entities(old_entity: Entity, ...@@ -214,15 +216,15 @@ def compare_entities(old_entity: Entity,
identical records are stored in different objects. Default is False. identical records are stored in different objects. Default is False.
""" """
olddiff: Dict[str, Any] = {"properties": {}, "parents": []} olddiff: dict[str, Any] = {"properties": {}, "parents": []}
newdiff: Dict[str, Any] = {"properties": {}, "parents": []} newdiff: dict[str, Any] = {"properties": {}, "parents": []}
if old_entity is new_entity: if old_entity is new_entity:
return (olddiff, newdiff) return (olddiff, newdiff)
if type(old_entity) is not type(new_entity): #if type(old_entity) is not type(new_entity):
raise ValueError( # raise ValueError(
"Comparison of different Entity types is not supported.") # "Comparison of different Entity types is not supported.")
for attr in SPECIAL_ATTRIBUTES: for attr in SPECIAL_ATTRIBUTES:
try: try:
...@@ -295,12 +297,15 @@ def compare_entities(old_entity: Entity, ...@@ -295,12 +297,15 @@ def compare_entities(old_entity: Entity,
elif isinstance(prop.value, list) and isinstance(matching[0].value, list): elif isinstance(prop.value, list) and isinstance(matching[0].value, list):
# all elements in both lists actually are entity objects # all elements in both lists actually are entity objects
# TODO: check, whether mixed cases can be allowed or should lead to an error # TODO: check, whether mixed cases can be allowed or should lead to an error
if all([isinstance(x, Entity) for x in prop.value]) and all([isinstance(x, Entity) for x in matching[0].value]): if (all([isinstance(x, Entity) for x in prop.value])
and all([isinstance(x, Entity) for x in matching[0].value])):
# can't be the same if the lengths are different # can't be the same if the lengths are different
if len(prop.value) == len(matching[0].value): if len(prop.value) == len(matching[0].value):
# do a one-by-one comparison; the values are the same, if all diffs are empty # do a one-by-one comparison:
# the values are the same if all diffs are empty
same_value = all( same_value = all(
[empty_diff(x, y, False) for x, y in zip(prop.value, matching[0].value)]) [empty_diff(x, y, False) for x, y
in zip(prop.value, matching[0].value)])
if not same_value: if not same_value:
olddiff["properties"][prop.name]["value"] = prop.value olddiff["properties"][prop.name]["value"] = prop.value
...@@ -333,7 +338,8 @@ def compare_entities(old_entity: Entity, ...@@ -333,7 +338,8 @@ def compare_entities(old_entity: Entity,
return (olddiff, newdiff) return (olddiff, newdiff)
def empty_diff(old_entity: Entity, new_entity: Entity, compare_referenced_records: bool = False) -> bool: def empty_diff(old_entity: Entity, new_entity: Entity,
compare_referenced_records: bool = False) -> bool:
"""Check whether the `compare_entities` found any differences between """Check whether the `compare_entities` found any differences between
old_entity and new_entity. old_entity and new_entity.
...@@ -606,7 +612,7 @@ def resolve_reference(prop: Property): ...@@ -606,7 +612,7 @@ def resolve_reference(prop: Property):
prop.value = retrieve_entity_with_id(prop.value) prop.value = retrieve_entity_with_id(prop.value)
def create_flat_list(ent_list: List[Entity], flat: List[Entity]): def create_flat_list(ent_list: list[Entity], flat: list[Entity]):
""" """
Recursively adds all properties contained in entities from ent_list to Recursively adds all properties contained in entities from ent_list to
the output list flat. Each element will only be added once to the list. the output list flat. Each element will only be added once to the list.
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2023 Henrik tom Wörden <h.tomwoerden@indiscale.com> # Copyright (C) 2023 Henrik tom Wörden <h.tomwoerden@indiscale.com>
# Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com> # Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com>
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -35,7 +37,7 @@ See also ...@@ -35,7 +37,7 @@ See also
from __future__ import annotations from __future__ import annotations
from enum import Enum from enum import Enum
from functools import lru_cache from functools import lru_cache
from typing import Union, Optional, Tuple, Any, Dict from typing import Any, Optional, Union
from .exceptions import EmptyUniqueQueryError, QueryNotUniqueError from .exceptions import EmptyUniqueQueryError, QueryNotUniqueError
from .utils import get_entity from .utils import get_entity
...@@ -46,7 +48,7 @@ from .common.models import execute_query, Entity, Container ...@@ -46,7 +48,7 @@ from .common.models import execute_query, Entity, Container
DEFAULT_SIZE = 33333 DEFAULT_SIZE = 33333
# This dict cache is solely for filling the real cache manually (e.g. to reuse older query results) # This dict cache is solely for filling the real cache manually (e.g. to reuse older query results)
_DUMMY_CACHE: Dict[Union[str, int], Any] = {} _DUMMY_CACHE: dict[Union[str, int], Any] = {}
class AccessType(Enum): class AccessType(Enum):
...@@ -63,7 +65,7 @@ class AccessType(Enum): ...@@ -63,7 +65,7 @@ class AccessType(Enum):
def cached_get_entity_by(eid: Union[str, int, None] = None, def cached_get_entity_by(eid: Union[str, int, None] = None,
name: Optional[str] = None, name: Optional[str] = None,
path: Optional[str] = None, path: Optional[str] = None,
query: Optional[str] = None) -> Union[Entity, Tuple[None]]: query: Optional[str] = None) -> Union[Entity, tuple[None]]:
"""Return a single entity that is identified uniquely by one argument. """Return a single entity that is identified uniquely by one argument.
You must supply exactly one argument. You must supply exactly one argument.
...@@ -178,7 +180,7 @@ def cache_initialize(maxsize: int = DEFAULT_SIZE) -> None: ...@@ -178,7 +180,7 @@ def cache_initialize(maxsize: int = DEFAULT_SIZE) -> None:
_cached_access = lru_cache(maxsize=maxsize)(_cached_access.__wrapped__) _cached_access = lru_cache(maxsize=maxsize)(_cached_access.__wrapped__)
def cache_fill(items: Dict[Union[str, int], Any], def cache_fill(items: dict[Union[str, int], Any],
kind: AccessType = AccessType.EID, kind: AccessType = AccessType.EID,
unique: bool = True) -> None: unique: bool = True) -> None:
"""Add entries to the cache manually. """Add entries to the cache manually.
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
# ** end header # ** end header
# #
from __future__ import annotations from __future__ import annotations
"""missing docstring.""" """Utility functions for server and user administration."""
import random import random
import re import re
...@@ -38,7 +38,7 @@ from ..exceptions import (EntityDoesNotExistError, HTTPClientError, ...@@ -38,7 +38,7 @@ from ..exceptions import (EntityDoesNotExistError, HTTPClientError,
ServerConfigurationException) ServerConfigurationException)
from .utils import xml2str from .utils import xml2str
from typing import Dict, Optional, TYPE_CHECKING, Union from typing import Optional, TYPE_CHECKING, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from ..common.models import Entity from ..common.models import Entity
...@@ -69,7 +69,7 @@ def set_server_property(key: str, value: str): ...@@ -69,7 +69,7 @@ def set_server_property(key: str, value: str):
"Debug mode in server is probably disabled.") from None "Debug mode in server is probably disabled.") from None
def get_server_properties() -> Dict[str, Optional[str]]: def get_server_properties() -> dict[str, Optional[str]]:
"""get_server_properties. """get_server_properties.
Get all server properties as a dict. Get all server properties as a dict.
...@@ -88,7 +88,7 @@ def get_server_properties() -> Dict[str, Optional[str]]: ...@@ -88,7 +88,7 @@ def get_server_properties() -> Dict[str, Optional[str]]:
"Debug mode in server is probably disabled.") from None "Debug mode in server is probably disabled.") from None
xml = etree.parse(body) xml = etree.parse(body)
props: Dict[str, Optional[str]] = dict() props: dict[str, Optional[str]] = dict()
for elem in xml.getroot(): for elem in xml.getroot():
props[elem.tag] = elem.text props[elem.tag] = elem.text
...@@ -184,7 +184,7 @@ def _update_user(name: str, ...@@ -184,7 +184,7 @@ def _update_user(name: str,
email: Optional[str] = None, email: Optional[str] = None,
entity: Optional[Entity] = None, **kwargs): entity: Optional[Entity] = None, **kwargs):
con = get_connection() con = get_connection()
params: Dict[str, Optional[str]] = {} params: dict[str, Optional[str]] = {}
if password is not None: if password is not None:
params["password"] = password params["password"] = password
...@@ -218,7 +218,7 @@ def _insert_user(name: str, ...@@ -218,7 +218,7 @@ def _insert_user(name: str,
email: Optional[str] = None, email: Optional[str] = None,
entity: Optional[Entity] = None, **kwargs): entity: Optional[Entity] = None, **kwargs):
con = get_connection() con = get_connection()
params: Dict[str, Union[str, Entity]] = {"username": name} params: dict[str, Union[str, Entity]] = {"username": name}
if password is not None: if password is not None:
params["password"] = password params["password"] = password
......
...@@ -24,11 +24,10 @@ ...@@ -24,11 +24,10 @@
# #
from __future__ import annotations from __future__ import annotations
import re import re
import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING and sys.version_info > (3, 7): if TYPE_CHECKING:
from typing import Literal, Union, List from typing import Literal, Union
from linkahead.common.models import Entity, Container from linkahead.common.models import Entity, Container
DATATYPE = Literal["DOUBLE", "REFERENCE", "TEXT", "DATETIME", "INTEGER", "FILE", "BOOLEAN"] DATATYPE = Literal["DOUBLE", "REFERENCE", "TEXT", "DATETIME", "INTEGER", "FILE", "BOOLEAN"]
...@@ -46,8 +45,7 @@ BOOLEAN = "BOOLEAN" ...@@ -46,8 +45,7 @@ BOOLEAN = "BOOLEAN"
def LIST(datatype: Union[str, Entity, DATATYPE]) -> str: def LIST(datatype: Union[str, Entity, DATATYPE]) -> str:
# FIXME May be ambiguous (if name duplicate) or insufficient (if only ID exists). # FIXME May be ambiguous (if name duplicate) or insufficient (if only ID exists).
if hasattr(datatype, "name"): datatype = getattr(datatype, "name", datatype)
datatype = datatype.name
return "LIST<" + str(datatype) + ">" return "LIST<" + str(datatype) + ">"
...@@ -179,7 +177,7 @@ def get_id_of_datatype(datatype: str) -> int: ...@@ -179,7 +177,7 @@ def get_id_of_datatype(datatype: str) -> int:
res: Container = execute_query(q) # type: ignore res: Container = execute_query(q) # type: ignore
if isinstance(res, int): if isinstance(res, int):
raise ValueError("FIND RECORDTYPE query returned an `int`") raise ValueError("FIND RECORDTYPE query returned an `int`")
res: List[Entity] = [el for el in res if el.name.lower() == datatype.lower()] # type: ignore res: list[Entity] = [el for el in res if el.name.lower() == datatype.lower()] # type: ignore
if len(res) > 1: if len(res) > 1:
raise QueryNotUniqueError( raise QueryNotUniqueError(
......
This diff is collapsed.
...@@ -26,7 +26,7 @@ from lxml import etree ...@@ -26,7 +26,7 @@ from lxml import etree
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import sys import sys
if TYPE_CHECKING and sys.version_info > (3, 7): if TYPE_CHECKING:
from typing import Optional from typing import Optional
from linkahead.common.models import ACL, ACI from linkahead.common.models import ACL, ACI
......
...@@ -26,14 +26,13 @@ ...@@ -26,14 +26,13 @@
Currently this module defines nothing but a single class, `Version`. Currently this module defines nothing but a single class, `Version`.
""" """
from __future__ import absolute_import, annotations from __future__ import annotations
from .utils import xml2str from .utils import xml2str
from lxml import etree from lxml import etree
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import sys if TYPE_CHECKING:
if TYPE_CHECKING and sys.version_info > (3, 7): from typing import Optional, List, Union
from typing import Optional, List, Union, Literal
class Version(): class Version():
...@@ -206,10 +205,8 @@ object.""" ...@@ -206,10 +205,8 @@ object."""
version : Version version : Version
a new version instance a new version instance
""" """
predecessors = [Version.from_xml( predecessors = [Version.from_xml(p) for p in xml if p.tag.lower() == "predecessor"]
p) for p in xml if p.tag.lower() == "predecessor"] successors = [Version.from_xml(s) for s in xml if s.tag.lower() == "successor"]
successors = [Version.from_xml(s)
for s in xml if s.tag.lower() == "successor"]
return Version(id=xml.get("id"), date=xml.get("date"), return Version(id=xml.get("id"), date=xml.get("date"),
is_head=xml.get("head"), is_head=xml.get("head"),
is_complete_history=xml.get("completeHistory"), is_complete_history=xml.get("completeHistory"),
......
...@@ -37,7 +37,7 @@ from configparser import ConfigParser ...@@ -37,7 +37,7 @@ from configparser import ConfigParser
from os import environ, getcwd from os import environ, getcwd
from os.path import expanduser, isfile, join from os.path import expanduser, isfile, join
from typing import Dict, Union, Callable, Optional from typing import Union, Callable, Optional
_pycaosdbconf = ConfigParser(allow_no_value=False) _pycaosdbconf = ConfigParser(allow_no_value=False)
...@@ -72,8 +72,8 @@ def get_config() -> ConfigParser: ...@@ -72,8 +72,8 @@ def get_config() -> ConfigParser:
return _pycaosdbconf return _pycaosdbconf
def config_to_yaml(config: ConfigParser) -> Dict[str, Dict[str, Union[int, str, bool]]]: def config_to_yaml(config: ConfigParser) -> dict[str, dict[str, Union[int, str, bool]]]:
valobj: Dict[str, Dict[str, Union[int, str, bool]]] = {} valobj: dict[str, dict[str, Union[int, str, bool]]] = {}
for s in config.sections(): for s in config.sections():
valobj[s] = {} valobj[s] = {}
for key, value in config[s].items(): for key, value in config[s].items():
...@@ -88,7 +88,7 @@ def config_to_yaml(config: ConfigParser) -> Dict[str, Dict[str, Union[int, str, ...@@ -88,7 +88,7 @@ def config_to_yaml(config: ConfigParser) -> Dict[str, Dict[str, Union[int, str,
return valobj return valobj
def validate_yaml_schema(valobj: Dict[str, Dict[str, Union[int, str, bool]]]): def validate_yaml_schema(valobj: dict[str, dict[str, Union[int, str, bool]]]):
if optional_jsonschema_validate: if optional_jsonschema_validate:
with open(os.path.join(os.path.dirname(__file__), "schema-pycaosdb-ini.yml")) as f: with open(os.path.join(os.path.dirname(__file__), "schema-pycaosdb-ini.yml")) as f:
schema = yaml.load(f, Loader=yaml.SafeLoader) schema = yaml.load(f, Loader=yaml.SafeLoader)
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
A CredentialsProvider which reads the password from the input line. A CredentialsProvider which reads the password from the input line.
""" """
from __future__ import absolute_import, unicode_literals, print_function, annotations from __future__ import annotations
from .interface import CredentialsProvider, CredentialsAuthenticator from .interface import CredentialsProvider, CredentialsAuthenticator
from typing import Optional from typing import Optional
import getpass import getpass
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# ** header v3.0
# This file is a part of the LinkAhead Project. # This file is a part of the LinkAhead Project.
# #
# Copyright (C) 2018 Research Group Biomedical Physics, # Copyright (C) 2018 Research Group Biomedical Physics,
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -18,14 +19,13 @@ ...@@ -18,14 +19,13 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ** end header
#
"""This module provides the interfaces for authenticating requests to the """This module provides the interfaces for authenticating requests to the
LinkAhead server. LinkAhead server.
Implementing modules muts provide a `get_authentication_provider()` method. Implementing modules must provide a `get_authentication_provider()` method.
""" """
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging import logging
...@@ -33,10 +33,10 @@ from ..utils import urlencode ...@@ -33,10 +33,10 @@ from ..utils import urlencode
from ..interface import CaosDBServerConnection from ..interface import CaosDBServerConnection
from ..utils import parse_auth_token, auth_token_to_cookie from ..utils import parse_auth_token, auth_token_to_cookie
from ...exceptions import LoginFailedError from ...exceptions import LoginFailedError
from typing import TYPE_CHECKING, Dict, Optional from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
from ..interface import CaosDBHTTPResponse from ..interface import CaosDBHTTPResponse
QueryDict = Dict[str, Optional[str]] QueryDict = dict[str, Optional[str]]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# ** header v3.0
# This file is a part of the LinkAhead Project. # This file is a part of the LinkAhead Project.
# #
# Copyright (C) 2018 Research Group Biomedical Physics, # Copyright (C) 2018 Research Group Biomedical Physics,
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
# Copyright (c) 2019 Daniel Hornung # Copyright (c) 2019 Daniel Hornung
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
# ** end header # ** end header
# #
"""Connection to a LinkAhead server.""" """Connection to a LinkAhead server."""
from __future__ import absolute_import, print_function, unicode_literals, annotations from __future__ import annotations
import logging import logging
import ssl import ssl
...@@ -33,7 +33,6 @@ from builtins import str # pylint: disable=redefined-builtin ...@@ -33,7 +33,6 @@ from builtins import str # pylint: disable=redefined-builtin
from errno import EPIPE as BrokenPipe from errno import EPIPE as BrokenPipe
from socket import error as SocketError from socket import error as SocketError
from urllib.parse import ParseResult, quote, urlparse from urllib.parse import ParseResult, quote, urlparse
from warnings import warn
from requests import Session as HTTPSession from requests import Session as HTTPSession
from requests.adapters import HTTPAdapter from requests.adapters import HTTPAdapter
...@@ -52,15 +51,13 @@ try: ...@@ -52,15 +51,13 @@ try:
except ModuleNotFoundError: except ModuleNotFoundError:
version = "uninstalled" version = "uninstalled"
from pkg_resources import resource_filename
from .encode import MultipartYielder, ReadableMultiparts from .encode import MultipartYielder, ReadableMultiparts
from .interface import CaosDBHTTPResponse, CaosDBServerConnection from .interface import CaosDBHTTPResponse, CaosDBServerConnection
from .utils import make_uri_path, parse_url, urlencode from .utils import make_uri_path, urlencode
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING and sys.version_info > (3, 7): if TYPE_CHECKING:
from typing import Optional, List, Any, Iterator, Dict, Union from typing import Optional, Any, Iterator, Union
from requests.models import Response from requests.models import Response
from ssl import _SSLMethod from ssl import _SSLMethod
from .authentication.interface import AbstractAuthenticator, CredentialsAuthenticator from .authentication.interface import AbstractAuthenticator, CredentialsAuthenticator
...@@ -171,7 +168,7 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): ...@@ -171,7 +168,7 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection):
def request(self, def request(self,
method: str, path: str, method: str, path: str,
headers: Optional[Dict[str, str]] = None, headers: Optional[dict[str, str]] = None,
body: Union[str, bytes, None] = None, body: Union[str, bytes, None] = None,
**kwargs) -> _WrappedHTTPResponse: **kwargs) -> _WrappedHTTPResponse:
"""request. """request.
...@@ -250,7 +247,8 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): ...@@ -250,7 +247,8 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection):
"do so via linkahead.configure_connection(...) or in a config " "do so via linkahead.configure_connection(...) or in a config "
"file.") "file.")
url_string: str = config["url"] url_string: str = config["url"]
if (not url_string.lower().startswith("https://") and not url_string.lower().startswith("http://")): if (not url_string.lower().startswith("https://")
and not url_string.lower().startswith("http://")):
raise LinkAheadConnectionError("The connection url is expected " raise LinkAheadConnectionError("The connection url is expected "
"to be a http or https url and " "to be a http or https url and "
"must include the url scheme " "must include the url scheme "
...@@ -289,7 +287,7 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection): ...@@ -289,7 +287,7 @@ class _DefaultCaosDBServerConnection(CaosDBServerConnection):
if "timeout" in config: if "timeout" in config:
self._timeout = config["timeout"] self._timeout = config["timeout"]
def _setup_ssl(self, config: Dict[str, Any]): def _setup_ssl(self, config: dict[str, Any]):
if "ssl_version" in config and config["cacert"] is not None: if "ssl_version" in config and config["cacert"] is not None:
ssl_version = getattr(ssl, config["ssl_version"]) ssl_version = getattr(ssl, config["ssl_version"])
else: else:
...@@ -431,7 +429,9 @@ def configure_connection(**kwargs): ...@@ -431,7 +429,9 @@ def configure_connection(**kwargs):
auth_token : str (optional) auth_token : str (optional)
An authentication token which has been issued by the LinkAhead Server. An authentication token which has been issued by the LinkAhead Server.
Implies `password_method="auth_token"` if set. An example token string would be `["O","OneTimeAuthenticationToken","anonymous",["administration"],[],1592995200000,604800000,"3ZZ4WKRB-5I7DG2Q6-ZZE6T64P-VQ","197d0d081615c52dc18fb323c300d7be077beaad4020773bb58920b55023fa6ee49355e35754a4277b9ac525c882bcd3a22e7227ba36dfcbbdbf8f15f19d1ee9",1,30000]`. Implies `password_method="auth_token"` if set. An example token string would be
``["O","OneTimeAuthenticationToken","anonymous",["administration"],[],1592995200000,
604800000,"3ZZ4WKRB-5I7DG2Q6-ZZE6T64P-VQ","197d0d081615...1ee9",1,30000]``.
https_proxy : str, optional https_proxy : str, optional
Define a proxy for the https connections, e.g. `http://localhost:8888`, Define a proxy for the https connections, e.g. `http://localhost:8888`,
...@@ -599,8 +599,8 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -599,8 +599,8 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
return self return self
def retrieve(self, def retrieve(self,
entity_uri_segments: Optional[List[str]] = None, entity_uri_segments: Optional[list[str]] = None,
query_dict: Optional[Dict[str, Optional[str]]] = None, query_dict: Optional[dict[str, Optional[str]]] = None,
**kwargs) -> CaosDBHTTPResponse: **kwargs) -> CaosDBHTTPResponse:
path = make_uri_path(entity_uri_segments, query_dict) path = make_uri_path(entity_uri_segments, query_dict)
...@@ -608,8 +608,9 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -608,8 +608,9 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
return http_response return http_response
def delete(self, entity_uri_segments: Optional[List[str]] = None, def delete(self, entity_uri_segments: Optional[list[str]] = None,
query_dict: Optional[Dict[str, Optional[str]]] = None, **kwargs) -> CaosDBHTTPResponse: query_dict: Optional[dict[str, Optional[str]]] = None, **kwargs) -> (
CaosDBHTTPResponse):
path = make_uri_path(entity_uri_segments, query_dict) path = make_uri_path(entity_uri_segments, query_dict)
http_response = self._http_request( http_response = self._http_request(
...@@ -617,8 +618,9 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -617,8 +618,9 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
return http_response return http_response
def update(self, entity_uri_segment: Optional[List[str]], def update(self, entity_uri_segment: Optional[list[str]],
query_dict: Optional[Dict[str, Optional[str]]] = None, **kwargs) -> CaosDBHTTPResponse: query_dict: Optional[dict[str, Optional[str]]] = None, **kwargs) -> (
CaosDBHTTPResponse):
path = make_uri_path(entity_uri_segment, query_dict) path = make_uri_path(entity_uri_segment, query_dict)
http_response = self._http_request(method="PUT", path=path, **kwargs) http_response = self._http_request(method="PUT", path=path, **kwargs)
...@@ -640,13 +642,15 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -640,13 +642,15 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
return self._form_data_request( return self._form_data_request(
method="PUT", path=entity_uri_segment, params=params) method="PUT", path=entity_uri_segment, params=params)
def post_form_data(self, entity_uri_segment: str, params: Dict[str, Optional[str]]) -> CaosDBHTTPResponse: def post_form_data(self, entity_uri_segment: str, params: dict[str, Optional[str]]) -> (
CaosDBHTTPResponse):
return self._form_data_request( return self._form_data_request(
method="POST", method="POST",
path=entity_uri_segment, path=entity_uri_segment,
params=params) params=params)
def _form_data_request(self, method: str, path: str, params: Dict[str, Optional[str]]) -> CaosDBHTTPResponse: def _form_data_request(self, method: str, path: str, params: dict[str, Optional[str]]) -> (
CaosDBHTTPResponse):
body = urlencode(params) body = urlencode(params)
headers = {} headers = {}
headers["Content-Type"] = "application/x-www-form-urlencoded" headers["Content-Type"] = "application/x-www-form-urlencoded"
...@@ -658,8 +662,8 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -658,8 +662,8 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
return response return response
def insert(self, entity_uri_segment: Optional[List[str]], def insert(self, entity_uri_segment: Optional[list[str]],
query_dict: Optional[Dict[str, Optional[str]]] = None, query_dict: Optional[dict[str, Optional[str]]] = None,
body: Union[str, bytes, None] = None, **kwargs) -> CaosDBHTTPResponse: body: Union[str, bytes, None] = None, **kwargs) -> CaosDBHTTPResponse:
path = make_uri_path(entity_uri_segment, query_dict) path = make_uri_path(entity_uri_segment, query_dict)
...@@ -686,7 +690,7 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -686,7 +690,7 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
self._authenticator.logout() self._authenticator.logout()
def _http_request(self, method: str, path: str, def _http_request(self, method: str, path: str,
headers: Optional[Dict["str", Any]] = None, headers: Optional[dict["str", Any]] = None,
body: Union[str, bytes, None] = None, **kwargs): body: Union[str, bytes, None] = None, **kwargs):
try: try:
return self._retry_http_request(method=method, path=path, return self._retry_http_request(method=method, path=path,
...@@ -713,7 +717,7 @@ class _Connection(object): # pylint: disable=useless-object-inheritance ...@@ -713,7 +717,7 @@ class _Connection(object): # pylint: disable=useless-object-inheritance
def _retry_http_request(self, def _retry_http_request(self,
method: str, method: str,
path: str, path: str,
headers: Optional[Dict["str", Any]], headers: Optional[dict["str", Any]],
body: Union[str, bytes, None], **kwargs) -> CaosDBHTTPResponse: body: Union[str, bytes, None], **kwargs) -> CaosDBHTTPResponse:
if hasattr(body, "encode") and body is not None: if hasattr(body, "encode") and body is not None:
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# #
# Copyright (C) 2018 Research Group Biomedical Physics, # Copyright (C) 2018 Research Group Biomedical Physics,
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -63,8 +65,8 @@ import os ...@@ -63,8 +65,8 @@ import os
import mimetypes import mimetypes
from email.header import Header from email.header import Header
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import sys
if TYPE_CHECKING and sys.version_info > (3, 7): if TYPE_CHECKING:
from typing import Optional from typing import Optional
...@@ -152,14 +154,6 @@ class MultipartParam(object): ...@@ -152,14 +154,6 @@ class MultipartParam(object):
except BaseException: except BaseException:
raise ValueError("Could not determine filesize") raise ValueError("Could not determine filesize")
def __cmp__(self, other):
attrs = [
'name', 'value', 'filename', 'filetype', 'filesize', 'fileobj'
]
myattrs = [getattr(self, a) for a in attrs]
oattrs = [getattr(other, a) for a in attrs]
return cmp(myattrs, oattrs)
def reset(self): def reset(self):
"""Reset the file object's read pointer.""" """Reset the file object's read pointer."""
if self.fileobj is not None: if self.fileobj is not None:
......
...@@ -29,7 +29,7 @@ from warnings import warn ...@@ -29,7 +29,7 @@ from warnings import warn
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Dict, Union from typing import Optional, Union
class CaosDBHTTPResponse(ABC): class CaosDBHTTPResponse(ABC):
...@@ -59,7 +59,7 @@ class CaosDBHTTPResponse(ABC): ...@@ -59,7 +59,7 @@ class CaosDBHTTPResponse(ABC):
"""Status code of the response.""" """Status code of the response."""
@abstractmethod @abstractmethod
def getheaders(self) -> Dict[str, str]: def getheaders(self) -> dict[str, str]:
"""Return all headers.""" """Return all headers."""
def __enter__(self): def __enter__(self):
...@@ -85,7 +85,7 @@ class CaosDBServerConnection(ABC): ...@@ -85,7 +85,7 @@ class CaosDBServerConnection(ABC):
def request(self, def request(self,
method: str, method: str,
path: str, path: str,
headers: Optional[Dict[str, str]] = None, headers: Optional[dict[str, str]] = None,
body: Union[str, bytes, None] = None, body: Union[str, bytes, None] = None,
**kwargs) -> CaosDBHTTPResponse: **kwargs) -> CaosDBHTTPResponse:
"""Abstract method. Implement this method for HTTP requests to the """Abstract method. Implement this method for HTTP requests to the
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# #
# Copyright (C) 2018 Research Group Biomedical Physics, # Copyright (C) 2018 Research Group Biomedical Physics,
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
# Copyright (C) 2024 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
...@@ -22,19 +24,18 @@ ...@@ -22,19 +24,18 @@
# ** end header # ** end header
# #
"""Utility functions for the connection module.""" """Utility functions for the connection module."""
from __future__ import unicode_literals, print_function, annotations from __future__ import annotations
from builtins import str as unicode
from urllib.parse import (urlencode as _urlencode, quote as _quote,
urlparse, urlunparse, unquote as _unquote)
import re import re
from urllib.parse import (urlencode as _urlencode, quote as _quote,
urlparse, urlunparse, unquote)
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import sys if TYPE_CHECKING:
if TYPE_CHECKING and sys.version_info > (3, 7): from typing import Optional
from typing import Optional, Dict, List
def urlencode(query: Dict[str, Optional[str]]) -> str: def urlencode(query: dict[str, Optional[str]]) -> str:
"""Convert a dict of into a url-encoded (unicode) string. """Convert a dict of into a url-encoded (unicode) string.
This is basically a python2/python3 compatibility wrapper for the respective This is basically a python2/python3 compatibility wrapper for the respective
...@@ -84,8 +85,8 @@ modules when they are called with only the query parameter. ...@@ -84,8 +85,8 @@ modules when they are called with only the query parameter.
})) }))
def make_uri_path(segments: Optional[List[str]] = None, def make_uri_path(segments: Optional[list[str]] = None,
query: Optional[Dict[str, Optional[str]]] = None) -> str: query: Optional[dict[str, Optional[str]]] = None) -> str:
"""Url-encode all segments, concat them with slashes and append the query. """Url-encode all segments, concat them with slashes and append the query.
Examples Examples
...@@ -141,18 +142,6 @@ def parse_url(url: str): ...@@ -141,18 +142,6 @@ def parse_url(url: str):
_PATTERN = re.compile(r"^SessionToken=([^;]*);.*$") _PATTERN = re.compile(r"^SessionToken=([^;]*);.*$")
def unquote(string) -> str:
"""unquote.
Decode an urlencoded string into a plain text string.
"""
bts = _unquote(string)
if hasattr(bts, "decode"):
# python 2
return bts.decode("utf-8")
return bts
def parse_auth_token(cookie: Optional[str]) -> Optional[str]: def parse_auth_token(cookie: Optional[str]) -> Optional[str]:
"""parse_auth_token. """parse_auth_token.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment