Skip to content
Snippets Groups Projects
Commit 1c02a8ec authored by Florian Spreckelsen's avatar Florian Spreckelsen
Browse files

Merge branch 'f-exception-handling' into 'dev'

New exception handling in advanced user tools

See merge request caosdb/caosdb-advanced-user-tools!39
parents 14aaf6bd 8971c672
No related branches found
No related tags found
1 merge request!22Release 0.3
# -*- mode:conf; -*-
# generated
src/caosadvancedtools/version.py
# compiled python and dist stuff
*.egg
......
......@@ -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:
......
......@@ -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.
......
......@@ -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
......@@ -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("/"):
......
......@@ -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
......
......@@ -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:
......
......@@ -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)
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.
@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 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
Parameters
----------
e : TransactionError
TransactionError, the children of which are checked for
possible datamodel problems.
"""
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment