diff --git a/.gitignore b/.gitignore index bea8a04f8e93b7659fdc4d7b8d5246a19b8759ad..e2526574b37539d054397d49bbefcadcc9dce654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # -*- mode:conf; -*- +# generated +src/caosadvancedtools/version.py # compiled python and dist stuff *.egg diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b573a53f424ccdbe3d47c426e497df15dbc1257..8ad682bf6bac6b50ed6a98ffe42b94f2c96aabb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -109,9 +109,7 @@ style: stage: style image: $CI_REGISTRY_IMAGE script: - # For the moment, ignore type comparisons in datamodel_problems.py - - autopep8 -ar --diff --exit-code --exclude ./src/caosadvancedtools/datamodel_problems.py . - - autopep8 -ar --diff --exit-code --ignore E721 ./src/caosadvancedtools/datamodel_problems.py + - autopep8 -ar --diff --exit-code . allow_failure: true unittest: diff --git a/CHANGELOG.md b/CHANGELOG.md index f9519355322f23a30ed156b87b8688d29cbd6a20..3973a4b3f6b0098b871abf6394e5b9158b3e43c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - instead of `get_entity`, type-specific functions are used in `cfood.py` when the type of the entity in question is known. - Logger is used instead of `print` for errors in `crawler.py`. +- complies with new exception handling, i.e., TransactionErros with + children being raised in all cases of EntityErrors (see + [#32](https://gitlab.com/caosdb/caosdb-pylib/-/issues/32) in + caosdb-pylib) - `caosadvancedtools.cfood.assure_object_is_in_list` conducts in-place updates if no `to_be_updated` object is supplied. diff --git a/integrationtests/test_datamodel_problems.py b/integrationtests/test_datamodel_problems.py index d9af69c79007bae5212f16d18363ff18b4ba9d32..7d56f4da8eea34604ed1c820e14555f087c353bd 100644 --- a/integrationtests/test_datamodel_problems.py +++ b/integrationtests/test_datamodel_problems.py @@ -30,7 +30,8 @@ class in crawler.py and cfood.py can be found in full-tests. import caosdb as db import pytest from caosadvancedtools.datamodel_problems import DataModelProblems -from caosdb.exceptions import (UnqualifiedParentsError, +from caosdb.exceptions import (TransactionError, + UnqualifiedParentsError, UnqualifiedPropertiesError) @@ -65,8 +66,9 @@ def test_missing_parent(): missing_name = "TestType" rec = db.Record(name="TestRecord") rec.add_parent(name=missing_name) - with pytest.raises(UnqualifiedParentsError): + with pytest.raises(TransactionError) as te: _insert_and_evaluate_exception(rec) + assert te.value.has_error(UnqualifiedParentsError) assert missing_name in DataModelProblems.missing @@ -74,8 +76,9 @@ def test_missing_property(): """Test if missing Property is in datamodel problems.""" missing_name = "TestProp" rec = db.Record(name="TestRecord").add_property(name=missing_name) - with pytest.raises(UnqualifiedPropertiesError): + with pytest.raises(TransactionError) as te: _insert_and_evaluate_exception(rec) + assert te.value.has_error(UnqualifiedPropertiesError) assert missing_name in DataModelProblems.missing @@ -89,8 +92,9 @@ def test_missing_property_existing_type(): db.RecordType(name=existing_rt).insert() rec = db.Record(name="TestRecord").add_parent(name=existing_rt) rec.add_property(name=missing_prop) - with pytest.raises(UnqualifiedPropertiesError): + with pytest.raises(TransactionError) as te: _insert_and_evaluate_exception(rec) + assert te.value.has_error(UnqualifiedPropertiesError) assert missing_prop in DataModelProblems.missing assert existing_rt not in DataModelProblems.missing @@ -107,7 +111,8 @@ def test_wrong_property_value(): prop = db.Property(name=prop_name, datatype=prop_dtype).insert() rec = db.Record(name="TestRecord").add_parent( name=rt_name).add_property(name=prop_name, value="bla") - with pytest.raises(UnqualifiedPropertiesError): + with pytest.raises(TransactionError) as te: _insert_and_evaluate_exception(rec) + assert te.value.has_error(UnqualifiedPropertiesError) # Should be empty assert not DataModelProblems.missing diff --git a/src/caosadvancedtools/cfood.py b/src/caosadvancedtools/cfood.py index 680f592bdca62f7a0a31c54484568da99eea95dc..8ce1dced48ba12e62717fe5bd788178e1e5a9488 100644 --- a/src/caosadvancedtools/cfood.py +++ b/src/caosadvancedtools/cfood.py @@ -6,6 +6,7 @@ # # Copyright (C) 2018 Research Group Biomedical Physics, # Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2019,2020 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2019,2020 Henrik tom Wörden # Copyright (C) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com> # Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics @@ -46,7 +47,8 @@ from abc import ABCMeta, abstractmethod from datetime import datetime import caosdb as db -from caosdb.exceptions import AmbiguityException, EntityDoesNotExistError +from caosdb.exceptions import (BadQueryError, EmptyUniqueQueryError, + QueryNotUniqueError, TransactionError) from .datamodel_problems import DataModelProblems from .guard import global_guard as guard @@ -75,12 +77,12 @@ def get_entity(name): def get_property(name): - """Returns the record type with a given name, preferably from a local + """Returns the property with a given name, preferably from a local cache. If the local cache does not contain the record type, try to - retrieve it from CaosDB. If it does not exist, add it to the data - model problems + retrieve it from CaosDB. If it does not exist, see whether it + could be a record type used as a property. """ @@ -89,7 +91,7 @@ def get_property(name): prop = db.execute_query("FIND Property with name='{}'".format( name), unique=True) - except (EntityDoesNotExistError, AmbiguityException): + except (EmptyUniqueQueryError, QueryNotUniqueError): # Property might actually be a RecordTypes prop = get_recordtype(name) PROPERTIES[name] = prop @@ -127,7 +129,7 @@ def get_recordtype(name): try: rec = db.execute_query("FIND RecordType WITH name='{}'".format(name), unique=True) - except (EntityDoesNotExistError, AmbiguityException) as e: + except (EmptyUniqueQueryError, QueryNotUniqueError) as e: DataModelProblems.add(name) raise e RECORDTYPES[name] = rec @@ -266,7 +268,7 @@ def get_entity_for_path(path): FILES[path] = db.execute_query(q, unique=True) return FILES[path] - except EntityDoesNotExistError: + except BadQueryError: path_prefix = "**" if not path.startswith("/"): diff --git a/src/caosadvancedtools/converter/labfolder_api.py b/src/caosadvancedtools/converter/labfolder_api.py index 567ee5a8aa7fdb1176fcbcc9bff96dfb6a19b821..a29d965b1598285105a06871ee1017adfdf4e222 100644 --- a/src/caosadvancedtools/converter/labfolder_api.py +++ b/src/caosadvancedtools/converter/labfolder_api.py @@ -82,7 +82,7 @@ class Importer(object): p = db.Property(name=element['title'], unit=element['unit'], datatype=db.DOUBLE) try: p.insert() - except db.exceptions.EntityError as e: + except db.exceptions.TransactionError as e: print(e) return diff --git a/src/caosadvancedtools/crawler.py b/src/caosadvancedtools/crawler.py index 1083f8c0fa027d014842f4368d4e732c80cc2b48..5dccdd8ce04daf6b6c15c676d195ce02c8d6ae12 100644 --- a/src/caosadvancedtools/crawler.py +++ b/src/caosadvancedtools/crawler.py @@ -48,7 +48,7 @@ from datetime import datetime from sqlite3 import IntegrityError import caosdb as db -from caosdb.exceptions import TransactionError +from caosdb.exceptions import BadQueryError from .cache import Cache, UpdateCache, get_pretty_xml from .cfood import RowCFood, add_files, get_ids_for_entities_with_names @@ -653,7 +653,8 @@ carefully and if the changes are ok, click on the following link: # is using the unique keyword try: r = q.execute(unique=True) - except TransactionError: + except BadQueryError: + # either empty or ambiguous response r = None # if r is not None: diff --git a/src/caosadvancedtools/datamodel_problems.py b/src/caosadvancedtools/datamodel_problems.py index 224744ea763de97b5710e81da93ad9f638c82ea5..df5b7e56dfcc939e2eabf6454cb6e81b22a37727 100644 --- a/src/caosadvancedtools/datamodel_problems.py +++ b/src/caosadvancedtools/datamodel_problems.py @@ -28,6 +28,7 @@ or updating entities with missing parents and/or properties. """ from caosdb.exceptions import (EntityDoesNotExistError, + TransactionError, UnqualifiedParentsError, UnqualifiedPropertiesError) @@ -42,48 +43,51 @@ class DataModelProblems(object): DataModelProblems.missing.add(ent) @staticmethod - def evaluate_exception(e): - """Take an exception, see whether it was caused by datamodel problems, - and update missing parents and/or properties if this was the - case. Otherwise, raise the exception. + def _evaluate_unqualified(e): + """Evaluate all UnqualifiedParentsErrors and + UnqualifiedPropertiesErrors and check for possible datamodel + problems. """ - # type(e) == type(Exception()) seems to be necessary because - # of EntityMultiErrors that are instances of (all? some of?) - # theirchild errors. So isinstance doesn't show the desired - # behavior. + # UnqualifiedParentsErrors are always possible problems: + if isinstance(e, UnqualifiedParentsError): + for err in e.errors: + DataModelProblems.add(err.entity.name) + elif isinstance(e, UnqualifiedPropertiesError): + # Only those UnqualifiedPropertiesErrors that were caused + # by (at least) an EntityDoesNotExistError are possible + # datamodel problems + for err in e.errors: + if isinstance(err, EntityDoesNotExistError): + DataModelProblems.add(err.entity.name) + # If there is at least one UnqualifiedParentsError or at least + # one UnqualifiedPropertiesError on some level below, go + # through the children. + elif (e.has_error(UnqualifiedParentsError) or + e.has_error(UnqualifiedPropertiesError)): + for err in e.errors: + DataModelProblems._evaluate_unqualified(err) + + @staticmethod + def evaluate_exception(e): + """Take a TransactionError, see whether it was caused by datamodel + problems, and update missing parents and/or properties if this + was the case. Afterwards, raise the exception. - if type(e) == type(UnqualifiedPropertiesError()): - for err in e.get_errors(): - # Here, it doesn't matter if there is an actual - # EntityDoesNotExistError or a MultiEntityError - # including an EntityDoesNotExistError. The latter - # case happens when a wrong entity with a value is - # given since then, an EntityHasNoDatatypeError is - # raised as well. Still, the problem is the missing - # property, so this is okay. + Parameters + ---------- + e : TransactionError + TransactionError, the children of which are checked for + possible datamodel problems. - if isinstance(err, EntityDoesNotExistError): - property_missing = True - DataModelProblems.add(err.get_entity().name) - raise e - elif type(e) == type(UnqualifiedParentsError()): - # This is always caused by missing/wrong parents + """ + if not isinstance(e, TransactionError): + raise ValueError( + "Only TransactionErrors can be checked for datamodel problems") + + if (e.has_error(UnqualifiedParentsError) or + e.has_error(UnqualifiedPropertiesError)): + for err in e.errors: + DataModelProblems._evaluate_unqualified(err) - for err in e.get_errors(): - DataModelProblems.add(err.get_entity().name) - raise e - # This is the ugly workaround for a MultiEntityError that - # stems from a UnqualifiedParentsError: an - # EntityDoesNotExistError is raised AND the causing entity has - # type PARENT. - elif ((type(e) == type(EntityDoesNotExistError())) and - ((str(type(e.get_entity()).__name__).upper() == "PARENT"))): - DataModelProblems.add(e.get_entity().name) - raise e - # Evaluate children of real MultiEntityErrors: - elif hasattr(e, "errors") and len(e.get_errors()) > 0: - for err in e.get_errors(): - DataModelProblems.evaluate_exception(err) - else: - raise e + raise e