diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f5412d0243cdcd2fe38920973fc8fb8075b8c3f0..d5935112b6f336a55331c48bda1d72e2c0118e97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,16 +27,23 @@ image: $CI_REGISTRY_IMAGE:latest stages: - code_style + - linting - setup - test - deploy - -# check code style + +# check code style code_style: tags: [ docker ] stage: code_style script: - pycodestyle --count ./ + allow_failure: true + +pylint: + tags: [ docker ] + stage: linting + script: - pylint3 --unsafe-load-any-extension=y -d all -e E,F src/caosdb/common allow_failure: true diff --git a/src/caosdb/__init__.py b/src/caosdb/__init__.py index 69f8f24e8eab9f3528eb2133cf8a49cd0d19acdf..92922f79c6b5105abc5e849175c4271561dd92a9 100644 --- a/src/caosdb/__init__.py +++ b/src/caosdb/__init__.py @@ -22,28 +22,28 @@ # ** end header # +from os import environ, getcwd # Import of the connection function (which is used to connect to the DB): from os.path import expanduser, join -from os import getcwd, environ -from caosdb.configuration import configure, get_config +# Import of convenience methods: +import caosdb.apiutils from caosdb.common import administration -from caosdb.connection.connection import configure_connection, get_connection +from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, + REFERENCE, TEXT, LIST) # Import of the basic API classes: -from caosdb.common.models import (QueryTemplate, Permissions, ACL, File, Record, - RecordType, Property, Message, Container, - DropOffBox, Entity, Query, Info, LIST, - DOUBLE, REFERENCE, TEXT, DATETIME, INTEGER, - FILE, BOOLEAN, TIMESPAN, OBLIGATORY, - SUGGESTED, RECOMMENDED, FIX, ALL, NONE) -# Import of exceptions +from caosdb.common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED, + SUGGESTED, Container, DropOffBox, Entity, + File, Info, Message, Permissions, Property, + Query, QueryTemplate, Record, RecordType, + delete, execute_query, get_global_acl, + get_known_permissions, raise_errors) +from caosdb.configuration import configure, get_config +from caosdb.connection.connection import configure_connection, get_connection from caosdb.exceptions import * -from caosdb.common.models import (delete, execute_query, raise_errors, - get_global_acl, get_known_permissions) -# Import of convenience methods: -import caosdb.apiutils # read configuration these files + if "PYCAOSDBINI" in environ: configure(expanduser(environ["PYCAOSDBINI"])) else: diff --git a/src/caosdb/apiutils.py b/src/caosdb/apiutils.py index 63d966b1ec7b61265093215e174aac8be20c1b79..f3887099a93d50740a3b2825bc6e8a9677509079 100644 --- a/src/caosdb/apiutils.py +++ b/src/caosdb/apiutils.py @@ -26,17 +26,15 @@ Some simplified functions for generation of records etc. """ -import os -import random import sys import tempfile from collections.abc import Iterable from subprocess import call -from . import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, REFERENCE, TEXT, - TIMESPAN, get_config) -from .common.models import (Container, Entity, File, Property, Query, Record, - RecordType) +from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, + REFERENCE, TEXT) +from caosdb.common.models import (Container, Entity, File, Property, Query, + Record, RecordType, get_config) def new_record(record_type, name=None, description=None, @@ -84,8 +82,24 @@ def new_record(record_type, name=None, description=None, return r +def id_query(ids): + q = "FIND Entity with " + " OR ".join(["id={}".format(id) for id in ids]) + + return db.execute_query(q) + + +def retrieve_entities_with_ids(entities): + collection = db.Container() + step = 20 + + for i in range(len(entities)//step+1): + collection.extend(id_query(entities[i*step:(i+1)*step])) + + return collection + + def get_type_of_entity_with(id_): - objs = Query("FIND Entity WHICH HAS id={id_}".format(id_=id_)).execute() + objs = retrieve_entities_with_ids([id_]) if len(objs) == 0: raise RuntimeError("ID {} not found.".format(id_)) @@ -225,8 +239,6 @@ class CaosDBPythonEntity(object): return (int(val), True) elif pr == DATETIME: return (val, False) - elif pr == TIMESPAN: - return (val, False) elif pr[0:4] == "LIST": return self._type_converted_list(val, pr) else: diff --git a/src/caosdb/common/datatype.py b/src/caosdb/common/datatype.py new file mode 100644 index 0000000000000000000000000000000000000000..f40b79f9819ea74461f8fcf7135f9baff69ac526 --- /dev/null +++ b/src/caosdb/common/datatype.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2020 Henrik tom Wörden, IndiScale GmbH +# +# 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/>. +# +# ** end header +# + +import re + +from ..exceptions import AmbiguityException, EntityDoesNotExistError + +DOUBLE = "DOUBLE" +REFERENCE = "REFERENCE" +TEXT = "TEXT" +DATETIME = "DATETIME" +INTEGER = "INTEGER" +FILE = "FILE" +BOOLEAN = "BOOLEAN" + + +def LIST(datatype): + if hasattr(datatype, "name"): + datatype = datatype.name + + return "LIST<" + str(datatype) + ">" + + +def get_list_datatype(datatype): + """ returns the datatype of the elements in the list """ + match = re.match("LIST(<|<)(?P<datatype>.*)(>|>)", "LISTING") + + if match is not None: + return match.group("datatype") + else: + return None + + +def is_list_datatype(datatype): + """ returns whether the datatype is a list """ + + return get_list_datatype(datatype) is not None + + +def is_reference(datatype): + """ returns whether the value is a reference + + FILE and REFERENCE properties are examples, but also datatypes that are + RecordTypes + """ + + if datatype in [DOUBLE, BOOLEAN, INTEGER, TEXT, DATETIME]: + + return False + elif is_list_datatype(datatype): + return is_reference(get_list_datatype(datatype)) + else: + return True + + +def get_id_of_datatype(datatype): + """ returns the id of a Record Type + + This is not trivial, as queries may also return children. A check comparing + names is necessary. + + Parameters + ---------- + datatype : string + A datatype, e.g. DOUBLE, or LIST<Person> + + Returns + ------- + The id of the RecordType with the same name as the datatype. + + Raises + ------ + AmbiguityException + If there are more than one entities with the same name as the datatype. + EntityDoesNotExistError + If there is no entity with the name of the datatype. + """ + from caosdb import execute_query + + if is_list_datatype(datatype): + datatype = get_list_datatype(datatype) + q = "FIND RECORDTYPE {}".format(datatype) + + # we cannot use unique=True here, because there might be subtypes + res = execute_query(q) + res = [el for el in res if el.name.lower() == datatype.lower()] + + if len(res) > 1: + raise AmbiguityException( + "Name {} did not lead to unique result; Missing " + "implementation".format(datatype)) + elif len(res) == 1: + raise EntityDoesNotExistError( + "No RecordType named {}".format(datatype)) + + return res[0].id diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index ac9321bbd2384694d6606460d150ef2a64e68b38..bf047f03e237b7d65daa6e67b2064723fd8dea0a 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -50,18 +50,11 @@ from caosdb.exceptions import (AmbiguityException, AuthorizationException, EntityHasNoDatatypeError, TransactionError, UniqueNamesError, UnqualifiedParentsError, UnqualifiedPropertiesError, URITooLongException) +from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, + LIST, REFERENCE, TEXT, is_reference) _ENTITY_URI_SEGMENT = "Entity" -DOUBLE = "DOUBLE" -REFERENCE = "REFERENCE" -TEXT = "TEXT" -DATETIME = "DATETIME" -INTEGER = "INTEGER" -FILE = "FILE" -BOOLEAN = "BOOLEAN" -TIMESPAN = "TIMESPAN" - # importances/inheritance OBLIGATORY = "OBLIGATORY" SUGGESTED = "SUGGESTED" @@ -71,13 +64,6 @@ ALL = "ALL" NONE = "NONE" -def LIST(datatype): - if isinstance(datatype, Entity): - datatype = datatype.name - - return "LIST<" + str(datatype) + ">" - - class Entity(object): """Entity is a generic CaosDB object. @@ -3432,7 +3418,7 @@ class Query(): if len(cresp) > 1 and raise_exception_on_error: raise AmbiguityException("This query wasn't unique") elif len(cresp) == 0 and raise_exception_on_error: - raise TransactionError(msg="No such entity found.") + raise EntityDoesNotExistError("No such entity found.") elif len(cresp) == 1: r = cresp[0] r.messages.extend(cresp.messages) diff --git a/unittests/test_datatype.py b/unittests/test_datatype.py new file mode 100644 index 0000000000000000000000000000000000000000..5a543100c8617374e0f075abf3adbdb2f8044129 --- /dev/null +++ b/unittests/test_datatype.py @@ -0,0 +1,5 @@ +from caosdb import LIST + + +def test_list(): + assert LIST("RT") == "LIST<RT>"