diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index ea537ffe8c44a7a7fb79c2d4080b63f9b3da2284..9085c8e4d9b95c6b71ea66951342960f8b934793 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -4,9 +4,10 @@ # # Copyright (C) 2018 Research Group Biomedical Physics, # Max-Planck-Institute for Dynamics and Self-Organization Göttingen -# Copyright (C) 2020-2023 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2020-2024 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2020-2023 Florian Spreckelsen <f.spreckelsen@indiscale.com> # Copyright (C) 2020-2022 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -34,6 +35,7 @@ transactions. from __future__ import annotations # Can be removed with 3.10. from __future__ import print_function, unicode_literals +from enum import Enum import re import sys @@ -45,7 +47,11 @@ from os import listdir from os.path import isdir from random import randint from tempfile import NamedTemporaryFile -from typing import Any, Optional +from typing import Any, Literal, Optional, Type, Union, TYPE_CHECKING, List + +if TYPE_CHECKING: + from datetime import datetime + from warnings import warn from lxml import etree @@ -71,13 +77,23 @@ from .versioning import Version _ENTITY_URI_SEGMENT = "Entity" + # importances/inheritance -OBLIGATORY = "OBLIGATORY" -SUGGESTED = "SUGGESTED" -RECOMMENDED = "RECOMMENDED" -FIX = "FIX" -ALL = "ALL" -NONE = "NONE" +class Inheritance(Enum): + OBLIGATORY = "OBLIGATORY" + SUGGESTED = "SUGGESTED" + RECOMMENDED = "RECOMMENDED" + FIX = "FIX" + ALL = "ALL" + NONE = "NONE" + + +OBLIGATORY = Inheritance.OBLIGATORY +SUGGESTED = Inheritance.SUGGESTED +RECOMMENDED = Inheritance.RECOMMENDED +FIX = Inheritance.FIX +ALL = Inheritance.ALL +NONE = Inheritance.NONE SPECIAL_ATTRIBUTES = ["name", "role", "datatype", "description", @@ -97,8 +113,16 @@ class Entity: by the user to control several server-side plug-ins. """ - def __init__(self, name=None, id=None, description=None, # @ReservedAssignment - datatype=None, value=None, **kwargs): + def __init__( + self, + name: Optional[str] = None, + id: Optional[int] = None, + description: Optional[str] = None, # @ReservedAssignment + datatype: Optional[str] = None, + value=None, + **kwargs, + ): + self.__role = kwargs["role"] if "role" in kwargs else None self._checksum = None self._size = None @@ -119,7 +143,7 @@ class Entity: self.path = None self.file = None self.unit = None - self.acl = None + self.acl: Optional[ACL] = None self.permissions = None self.is_valid = lambda: False self.is_deleted = lambda: False @@ -323,8 +347,15 @@ class Entity: def pickup(self, new_pickup): self.__pickup = new_pickup - def grant(self, realm=None, username=None, role=None, - permission=None, priority=False, revoke_denial=True): + def grant( + self, + realm: Optional[str] = None, + username: Optional[str] = None, + role: Optional[str] = None, + permission: str = None, + priority: bool = False, + revoke_denial: bool = True, + ): """Grant a permission to a user or role for this entity. You must specify either only the username and the realm, or only the @@ -518,8 +549,29 @@ class Entity: return self - def add_property(self, property=None, value=None, id=None, name=None, description=None, datatype=None, - unit=None, importance=None, inheritance=None): # @ReservedAssignment + def add_property( + self, + property: Union[int, str, Entity, None] = None, + value: Union[ + int, + str, + bool, + datetime, + Entity, + List[int], + List[str], + List[bool], + List[Entity], + None, + ] = None, + id: Optional[int] = None, + name: Optional[str] = None, + description: Optional[str] = None, + datatype: Optional[str] = None, + unit: Optional[str] = None, + importance: Optional[str] = None, + inheritance: Union[str, Inheritance, None] = None, + ) -> Entity: # @ReservedAssignment """Add a property to this entity. The first parameter is meant to identify the property entity either via @@ -689,7 +741,13 @@ class Entity: return self - def add_parent(self, parent=None, id=None, name=None, inheritance=None): # @ReservedAssignment + def add_parent( + self, + parent: Union[Entity, int, str, None] = None, + id: Optional[int] = None, + name: Optional[str] = None, + inheritance: Union[str, Inheritance, None] = None, + ): # @ReservedAssignment """Add a parent to this entity. Parameters @@ -703,7 +761,7 @@ class Entity: name : str Name of the parent entity. Ignored if `parent is not none`. - inheritance : str + inheritance : str, Inheritance One of ``obligatory``, ``recommended``, ``suggested``, or ``fix``. Specifies the minimum importance which parent properties need to have to be inherited by this entity. If no `inheritance` is given, no properties will be inherited by the child. @@ -810,7 +868,7 @@ out: bool return self.parents - def get_parents_recursively(self, retrieve: bool = True): + def get_parents_recursively(self, retrieve: bool = True) -> List[Entity]: """Get all ancestors of this entity. Parameters @@ -825,12 +883,12 @@ out: List[Entity] The parents of this Entity """ - all_parents = [] + all_parents: List[Entity] = [] self._get_parent_recursively(all_parents, retrieve=retrieve) return all_parents - def _get_parent_recursively(self, all_parents: list, retrieve: bool = True): + def _get_parent_recursively(self, all_parents: List[Entity], retrieve: bool = True): """Get all ancestors with a little helper. As a side effect of this method, the ancestors are added to @@ -861,7 +919,7 @@ out: List[Entity] all_parents.append(w_parent) w_parent._get_parent_recursively(all_parents, retrieve=retrieve) - def get_parent(self, key): + def get_parent(self, key: Union[int, Entity, str]) -> Union[Entity, None]: """Return the first parent matching the key or None if no match exists. Parameters @@ -1393,8 +1451,14 @@ out: List[Entity] return Container().append(self).retrieve( unique=unique, raise_exception_on_error=raise_exception_on_error, flags=flags) - def insert(self, raise_exception_on_error=True, unique=True, - sync=True, strict=False, flags=None): + def insert( + self, + raise_exception_on_error=True, + unique=True, + sync=True, + strict=False, + flags: Optional[dict] = None, + ): """Insert this entity into a LinkAhead server. A successful insertion will generate a new persistent ID for this entity. This entity can be identified, retrieved, updated, and deleted via this ID until it has @@ -1877,7 +1941,13 @@ class Property(Entity): class Message(object): - def __init__(self, type=None, code=None, description=None, body=None): # @ReservedAssignment + def __init__( + self, + type: Optional[str] = None, + code: Optional[int] = None, + description=None, + body: str = None, + ): # @ReservedAssignment self.description = description self.type = type if type is not None else "Info" self.code = int(code) if code is not None else None @@ -1925,7 +1995,7 @@ class RecordType(Entity): property=property, id=id, name=name, description=description, datatype=datatype, value=value, unit=unit, importance=importance, inheritance=inheritance) - def add_parent(self, parent=None, id=None, name=None, inheritance=OBLIGATORY): + def add_parent(self, parent=None, id=None, name=None, inheritance:Union[str, Inheritance]=OBLIGATORY): """Add a parent to this RecordType Parameters @@ -4692,7 +4762,7 @@ class Permissions(): return str(self._perms) -def parse_xml(xml): +def parse_xml(xml: Union[str, etree._Element]): """parse a string or tree representation of an xml document to a set of entities (records, recordtypes, properties, or files). @@ -4701,9 +4771,9 @@ def parse_xml(xml): """ if isinstance(xml, etree._Element): - elem = xml + elem: etree._Element = xml else: - elem = etree.fromstring(xml) + elem: etree._Element = etree.fromstring(xml) return _parse_single_xml_element(elem) @@ -4762,7 +4832,7 @@ def _parse_single_xml_element(elem): "code"), description=elem.get("description"), body=elem.text) -def _evaluate_and_add_error(parent_error, ent): +def _evaluate_and_add_error(parent_error: TransactionError, ent: Entity): """Evaluate the error message(s) attached to entity and add a corresponding exception to parent_error. @@ -4865,7 +4935,7 @@ def _evaluate_and_add_error(parent_error, ent): return parent_error -def raise_errors(arg0): +def raise_errors(arg0: Union[Entity, QueryTemplate, Container]): """Raise a TransactionError depending on the error code(s) inside Entity, QueryTemplate or Container arg0. More detailed errors may be attached to the TransactionError depending on the contents of @@ -4892,7 +4962,7 @@ def raise_errors(arg0): raise transaction_error -def delete(ids, raise_exception_on_error=True): +def delete(ids:Union[List[int], range], raise_exception_on_error=True): c = Container() if isinstance(ids, list) or isinstance(ids, range):