Skip to content
Snippets Groups Projects
Commit b280e843 authored by Henrik tom Wörden's avatar Henrik tom Wörden
Browse files

Merge branch 'release-0.4.1' into 'main'

REL: Release 0.4.1

See merge request !43
parents dc059561 3febef54
No related branches found
No related tags found
1 merge request!43REL: Release 0.4.1
Pipeline #22469 passed
Showing
with 141 additions and 42 deletions
...@@ -18,6 +18,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -18,6 +18,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### ### Security ###
## [0.4.1] - 2022-05-03 ##
(Henrik tom Wörden)
### Changed ###
- `JsonSchemaParser` now identifies `name` properties in the schema with the
CaosDB name property.
### Fixed ###
- [#40](https://gitlab.com/caosdb/caosdb-advanced-user-tools/-/issues/40)
`assure_object_is_in_list` now handles adding objects to an initially empty list correctly.
## [0.4.0] - 2022-04-05 ## ## [0.4.0] - 2022-04-05 ##
### Added ### ### Added ###
......
...@@ -9,7 +9,7 @@ guidelines of the CaosDB Project ...@@ -9,7 +9,7 @@ guidelines of the CaosDB Project
* All tests are passing. * All tests are passing.
* FEATURES.md is up-to-date and a public API is being declared in that document. * FEATURES.md is up-to-date and a public API is being declared in that document.
* CHANGELOG.md is up-to-date. * CHANGELOG.md is up-to-date.
* DEPENDENCIES.md is up-to-date. * dependencies in `setup.py` are up-to-date.
## Steps ## Steps
......
--- ---
responsible: Responsible, Only responsible: Responsible, Only
description: A description of this example analysis. description: A description of another example analysis.
sources: sources:
- file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat" - file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
......
--- ---
responsible: responsible:
- Only Responsible MPI DS - Only Responsible MPI DS
description: A description of this example analysis. description: A description of another example analysis.
sources: sources:
- file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat" - file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
responsible: responsible:
- Some Responsible - Some Responsible
- Responsible, No, MPI DS - Responsible, No, MPI DS
description: A description of this example analysis. description: A description of another example analysis.
sources: sources:
- file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat" - file: "/ExperimentalData/2010_TestProject/2019-02-03/*.dat"
......
...@@ -35,14 +35,17 @@ echo "Testing the crawler database" ...@@ -35,14 +35,17 @@ echo "Testing the crawler database"
python3 -m pytest test_crawler_with_cfoods.py python3 -m pytest test_crawler_with_cfoods.py
echo "make a change" echo "make a change"
cd extroot cd extroot
egrep -liRZ 'A description of another example' . | xargs -0 -l sed -i -e 's/A description of another example/A description of this example/g' egrep -liRZ 'A description of another example' . \
| xargs -0 -l sed -i -e 's/A description of another example/A description of this example/g'
# remove a file to check that this does not lead to a crawler crash # remove a file to check that this does not lead to a crawler crash
mv DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx_back mv DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx \
DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx_back
cd .. cd ..
echo "run crawler" echo "run crawler"
./crawl.py / | tee $OUT ./crawl.py / | tee $OUT
# rename the moved file # rename the moved file
mv extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx_back extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx mv extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx_back \
extroot/DataAnalysis/2010_TestProject/2019-02-03_something/README.xlsx
# check whether there was something UNAUTHORIZED # check whether there was something UNAUTHORIZED
grep "There where unauthorized changes" $OUT grep "There where unauthorized changes" $OUT
# get the id of the run which is the last field of the output string # get the id of the run which is the last field of the output string
...@@ -59,7 +62,8 @@ fi ...@@ -59,7 +62,8 @@ fi
set -e set -e
echo "Undoing previous changes to extroot content..." echo "Undoing previous changes to extroot content..."
cd extroot cd extroot
egrep -liRZ 'A description of this example' . | xargs -0 -l sed -i -e 's/A description of this example/A description of another example/g' egrep -liRZ 'A description of this example' . \
| xargs -0 -l sed -i -e 's/A description of this example/A description of another example/g'
cd .. cd ..
echo "Done." echo "Done."
python3 test_table.py python3 test_table.py
......
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
# #
# ** header v3.0
# This file is a part of the CaosDB Project. # This file is a part of the CaosDB Project.
# #
# Copyright (C) 2022 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics # Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics
# Copyright (C) 2021 Florian Spreckelsen <florian.spreckelsen@med.uni-goettingen.de> # Copyright (C) 2021 Florian Spreckelsen <florian.spreckelsen@med.uni-goettingen.de>
# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
# #
# 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
# it under the terms of the GNU Affero General Public License as # the terms of the GNU Affero General Public License as published by the Free
# published by the Free Software Foundation, either version 3 of the # Software Foundation, either version 3 of the License, or (at your option) any
# License, or (at your option) any later version. # later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful, but WITHOUT
# but WITHOUT ANY WARRANTY; without even the implied warranty of # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# GNU Affero General Public License for more details. # details.
# #
# 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
# along with this program. If not, see <https://www.gnu.org/licenses/>. # with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ** end header
"""Integration tests for the `assure_...` functions from """Integration tests for the `assure_...` functions from
`caosadvancedtools.cfood`. They mainly test the in-place updates when `caosadvancedtools.cfood`. They mainly test the in-place updates when
no `to_be_updated` is specified. no `to_be_updated` is specified.
...@@ -90,3 +89,29 @@ def test_assure_list_in_place(): ...@@ -90,3 +89,29 @@ def test_assure_list_in_place():
assert len(rec2.get_property(ref_rt.name).value) == 3 assert len(rec2.get_property(ref_rt.name).value) == 3
assert ref_rec2.id in rec2.get_property(ref_rt.name).value assert ref_rec2.id in rec2.get_property(ref_rt.name).value
assert ref_rec3.id in rec2.get_property(ref_rt.name).value assert ref_rec3.id in rec2.get_property(ref_rt.name).value
def test_add_to_empty_list():
"""See https://gitlab.com/caosdb/caosdb-advanced-user-tools/-/issues/40."""
# @author Florian Spreckelsen
# @date 2022-04-19
referenced_rt = db.RecordType(name="TestReferencedType").insert()
list_prop = db.Property(name="TestListProp",
datatype=db.LIST(referenced_rt)).insert()
referencing_rt = db.RecordType(
name="TestReferencingType").add_property(list_prop).insert()
db.Record(name="TestReferencedRecord").add_parent(referenced_rt).insert()
db.Record(name="TestReferencingRecord").add_parent(
referencing_rt).add_property(list_prop, value=[]).insert()
referenced_rec = db.execute_query("FIND TestReferencedRecord", unique=True)
referencing_rec = db.execute_query(
"FIND TestReferencingRecord", unique=True)
assure_object_is_in_list(referenced_rec, referencing_rec, list_prop.name)
referencing_rec = db.execute_query(
"FIND TestReferencingRecord", unique=True)
assert referencing_rec.get_property(list_prop.name).value == [
referenced_rec.id]
...@@ -47,9 +47,9 @@ from setuptools import find_packages, setup ...@@ -47,9 +47,9 @@ from setuptools import find_packages, setup
MAJOR = 0 MAJOR = 0
MINOR = 4 MINOR = 4
MICRO = 0 MICRO = 1
PRE = "" # e.g. rc0, alpha.1, 0.beta-23 PRE = "" # e.g. rc0, alpha.1, 0.beta-23
ISRELEASED = False ISRELEASED = True
if PRE: if PRE:
VERSION = "{}.{}.{}-{}".format(MAJOR, MINOR, MICRO, PRE) VERSION = "{}.{}.{}-{}".format(MAJOR, MINOR, MICRO, PRE)
......
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
# #
# ** header v3.0
# This file is a part of the CaosDB Project. # This file is a part of the CaosDB 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,2020 IndiScale GmbH <info@indiscale.com> # Copyright (C) 2019-2022 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2019,2020 Henrik tom Wörden # Copyright (C) 2019,2020 Henrik tom Wörden
# Copyright (C) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com> # Copyright (C) 2020-2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
# Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics # Copyright (C) 2021 University Medical Center Göttingen, Institute for Medical Informatics
# Copyright (C) 2021 Florian Spreckelsen <florian.spreckelsen@med.uni-goettingen.de> # Copyright (C) 2021 Florian Spreckelsen <florian.spreckelsen@med.uni-goettingen.de>
# #
...@@ -24,8 +23,6 @@ ...@@ -24,8 +23,6 @@
# #
# 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
""" Defines how something that shall be inserted into CaosDB is treated. """ Defines how something that shall be inserted into CaosDB is treated.
CaosDB can automatically be filled with Records based on some structure, a file CaosDB can automatically be filled with Records based on some structure, a file
...@@ -415,11 +412,13 @@ def assure_object_is_in_list(obj, containing_object, property_name, ...@@ -415,11 +412,13 @@ def assure_object_is_in_list(obj, containing_object, property_name,
datatype=datatype) datatype=datatype)
# TODO: case where multiple times the same property exists is not treated # TODO: case where multiple times the same property exists is not treated
if not isinstance(containing_object.get_property(property_name).value, list): list_prop = containing_object.get_property(property_name)
containing_object.get_property(property_name).value = [ if list_prop.value is None:
containing_object.get_property(property_name).value] list_prop.value = []
containing_object.get_property(property_name).datatype = datatype elif not isinstance(list_prop.value, list):
current_list = containing_object.get_property(property_name).value list_prop.value = [list_prop.value]
list_prop.datatype = datatype
current_list = list_prop.value
if not isinstance(obj, list): if not isinstance(obj, list):
objects = [obj] objects = [obj]
...@@ -674,7 +673,8 @@ def assure_has_property(entity, name, value, to_be_updated=None, ...@@ -674,7 +673,8 @@ def assure_has_property(entity, name, value, to_be_updated=None,
tmp_value = el.value.id tmp_value = el.value.id
if isinstance(tmp_value, list): if isinstance(tmp_value, list):
tmp_value = [i.id if isinstance(i, db.Entity) else i for i in tmp_value] tmp_value = [i.id if isinstance(
i, db.Entity) else i for i in tmp_value]
if tmp_value == value: if tmp_value == value:
contained = True contained = True
...@@ -810,7 +810,9 @@ class RowCFood(AbstractCFood): ...@@ -810,7 +810,9 @@ class RowCFood(AbstractCFood):
for key, value in self.item.iteritems(): for key, value in self.item.iteritems():
if key in self.unique_cols: if key in self.unique_cols:
continue continue
rec.add_property(key, value) assure_property_is(rec, key,
value,
to_be_updated=self.to_be_updated)
class CMeal(): class CMeal():
......
...@@ -392,7 +392,6 @@ class Crawler(object): ...@@ -392,7 +392,6 @@ class Crawler(object):
for cfood in cfoods: for cfood in cfoods:
try: try:
cfood.create_identifiables() cfood.create_identifiables()
self._cached_find_or_insert_identifiables(cfood.identifiables) self._cached_find_or_insert_identifiables(cfood.identifiables)
cfood.update_identifiables() cfood.update_identifiables()
......
...@@ -566,9 +566,11 @@ class Parser(object): ...@@ -566,9 +566,11 @@ class Parser(object):
db.BOOLEAN]: db.BOOLEAN]:
if is_list: if is_list:
value.datatype = db.LIST(db.__getattribute__(dtype)) # pylint: disable=no-member value.datatype = db.LIST(db.__getattribute__( # pylint: disable=no-member
dtype))
else: else:
value.datatype = db.__getattribute__(dtype) # pylint: disable=no-member value.datatype = db.__getattribute__( # pylint: disable=no-member
dtype)
continue continue
...@@ -632,8 +634,9 @@ class JsonSchemaParser(Parser): ...@@ -632,8 +634,9 @@ class JsonSchemaParser(Parser):
return self._create_model_from_dict(model_dict) return self._create_model_from_dict(model_dict)
def _create_model_from_dict(self, model_dict: [dict, List[dict]]): def _create_model_from_dict(self, model_dict: [dict, List[dict]]):
"""Parse a dictionary read in from the model definition in a json schema and """Parse a dictionary and return the Datamodel created from it.
return the Datamodel created from it.
The dictionary was typically created from the model definition in a json schema file.
Parameters Parameters
---------- ----------
...@@ -692,6 +695,15 @@ class JsonSchemaParser(Parser): ...@@ -692,6 +695,15 @@ class JsonSchemaParser(Parser):
# Each element must have a specific type # Each element must have a specific type
raise JsonSchemaDefinitionError( raise JsonSchemaDefinitionError(
f"`type` is missing in element {name}.") f"`type` is missing in element {name}.")
if name == "name":
# This is identified with the CaosDB name property as long as the
# type is correct.
if not elt["type"] == "string":
raise JsonSchemaDefinitionError(
"The 'name' property must be string-typed, otherwise it cannot "
"be identified with CaosDB's name property."
)
return None, force_list
if "enum" in elt: if "enum" in elt:
ent = self._treat_enum(elt, name) ent = self._treat_enum(elt, name)
elif elt["type"] in JSON_SCHEMA_ATOMIC_TYPES: elif elt["type"] in JSON_SCHEMA_ATOMIC_TYPES:
...@@ -726,6 +738,10 @@ class JsonSchemaParser(Parser): ...@@ -726,6 +738,10 @@ class JsonSchemaParser(Parser):
else: else:
name = self._stringify(key) name = self._stringify(key)
prop_ent, force_list = self._treat_element(prop, name) prop_ent, force_list = self._treat_element(prop, name)
if prop_ent is None:
# Nothing to be appended since the property has to be
# treated specially.
continue
importance = db.OBLIGATORY if key in required else db.RECOMMENDED importance = db.OBLIGATORY if key in required else db.RECOMMENDED
if not force_list: if not force_list:
rt.add_property(prop_ent, importance=importance) rt.add_property(prop_ent, importance=importance)
......
...@@ -27,9 +27,9 @@ copyright = '2021, IndiScale GmbH' ...@@ -27,9 +27,9 @@ copyright = '2021, IndiScale GmbH'
author = 'Daniel Hornung' author = 'Daniel Hornung'
# The short X.Y version # The short X.Y version
version = '0.4.0' version = '0.4.1'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '0.4.0' release = '0.4.1'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
......
{
"title": "Dataset",
"type": "object",
"properties": {
"name": { "type": "string", "description": "Name of this dataset" },
"date_time": { "type": "string", "format": "date-time" },
"date": { "type": "string", "format": "date" },
"integer": { "type": "integer", "description": "Some integer property" },
"boolean": { "type": "boolean" },
"number_prop": { "type": "number", "description": "Some float property" }
}
}
{
"title": "Dataset",
"type": "object",
"properties": {
"name": { "type": "boolean", "description": "Name of this dataset" },
"date_time": { "type": "string", "format": "date-time" },
"date": { "type": "string", "format": "date" },
"integer": { "type": "integer", "description": "Some integer property" },
"boolean": { "type": "boolean" },
"number_prop": { "type": "number", "description": "Some float property" }
}
}
...@@ -340,3 +340,19 @@ def test_list(): ...@@ -340,3 +340,19 @@ def test_list():
assert model[name].name == name assert model[name].name == name
assert len(model[name].parents) == 1 assert len(model[name].parents) == 1
assert model[name].has_parent(model["license"]) assert model[name].has_parent(model["license"])
def test_name_property():
model = parse_model_from_json_schema(os.path.join(
FILEPATH, "datamodel_name.schema.json"))
dataset_rt = model["Dataset"]
assert dataset_rt.get_property("name") is None
assert "name" not in model
with pytest.raises(JsonSchemaDefinitionError) as err:
broken = parse_model_from_json_schema(os.path.join(
FILEPATH, "datamodel_name_wrong_type.schema.json"))
assert str(err.value).startswith(
"The 'name' property must be string-typed, otherwise it cannot be identified with CaosDB's "
"name property.")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment