diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc2e12f8c6722526028a5670197be1ace229d53..b19034e7e4d88edb24d56e421c2e1b51a0a40968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### +- CaosDB internal properties `name`, `unit` and `description` can now be used via the `extern` + keyword in YAML datamodel specifications. + ### Security ### ## [0.3.1] - 2021-12-06 ## diff --git a/integrationtests/test.sh b/integrationtests/test.sh index 13d197750a20309b7f6231522adf05cd2293fffd..a142d917215eb7469faab9c66a581539ce867e4e 100755 --- a/integrationtests/test.sh +++ b/integrationtests/test.sh @@ -57,10 +57,11 @@ then exit 1 fi set -e -echo "undo changes" +echo "Undoing previous changes to extroot content..." 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' cd .. +echo "Done." python3 test_table.py # TODO the following test deletes lots of the data inserted by the crawler echo "Testing im and export" @@ -85,5 +86,8 @@ python3 -m pytest test_base_table_exporter_integration.py echo "Testing json-schema datamodel parser" python3 -m pytest test_json_schema_datamodel_parser.py +echo "Testing yaml datamodel parser" +python3 -m pytest test_yaml_parser.py + # Obsolete due to teardown in the above test. # echo "/n/n/n YOU NEED TO RESTART THE SERVER TO REDO TESTS!!!" diff --git a/integrationtests/test_yaml_parser.py b/integrationtests/test_yaml_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..e2a2c4c056ced56d2605d93914186c2cba97e137 --- /dev/null +++ b/integrationtests/test_yaml_parser.py @@ -0,0 +1,69 @@ +# encoding: utf-8 +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# 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/>. +# + +import caosdb as db +from caosadvancedtools.models.parser import parse_model_from_string + + +def _delete_everything(): + ents = db.execute_query("FIND ENTITY WITH ID > 99") + if ents: + ents.delete() + + +def setup_module(): + _delete_everything() + + +def teardown_module(): + _delete_everything() + + +def test_internal_props_in_extern(): + """Test adding the internal `name` property as a parent to an existing + property. + + """ + + model = """ +extern: +- name +- test_name +- description +- unit +test_name: + inherit_from_suggested: + - name + - description + - unit +""" + db.Property(name="test_name", datatype=db.TEXT).insert() + ents = parse_model_from_string(model) + ents.sync_data_model(noquestion=True) + + test_prop = db.Property(name="test_name").retrieve() + assert len(test_prop.parents) == 3 + desc_prop = db.Property(name="description").retrieve() + name_prop = db.Property(name="name").retrieve() + unit_prop = db.Property(name="unit").retrieve() + assert test_prop.has_parent(desc_prop) + assert test_prop.has_parent(name_prop) + assert test_prop.has_parent(unit_prop) diff --git a/src/caosadvancedtools/models/data_model.py b/src/caosadvancedtools/models/data_model.py index e198d15ca2c56eceec29c356cabdf28ac44895b2..d9079e6196b4751ca86ba41275108330b946d57c 100644 --- a/src/caosadvancedtools/models/data_model.py +++ b/src/caosadvancedtools/models/data_model.py @@ -33,6 +33,13 @@ import caosdb as db from caosdb.apiutils import compare_entities, describe_diff +CAOSDB_INTERNAL_PROPERTIES = [ + "description", + "name", + "unit", +] + + class DataModel(dict): """Provides tools for managing a data model. @@ -129,8 +136,13 @@ class DataModel(dict): any_change = False for ent in existing_entities: - q = db.Query("FIND * with id={}".format(ent.id)) - ref = q.execute(unique=True) + if ent.name in CAOSDB_INTERNAL_PROPERTIES: + # Workaround for the usage of internal properties like name + # in via the extern keyword: + ref = db.Property(name=ent.name).retrieve() + else: + query = db.Query(f"FIND * with id={ent.id}") + ref = query.execute(unique=True) diff = (describe_diff(*compare_entities(ent, ref ), name=ent.name)) diff --git a/src/caosadvancedtools/models/parser.py b/src/caosadvancedtools/models/parser.py index a71badf3ee0c32ff331007cc7e99b43fb033dbef..66ea016b818f6f2c0a0a03797db7aa22848dfeb8 100644 --- a/src/caosadvancedtools/models/parser.py +++ b/src/caosadvancedtools/models/parser.py @@ -45,7 +45,7 @@ from typing import List import jsonschema import caosdb as db -from .data_model import DataModel +from .data_model import CAOSDB_INTERNAL_PROPERTIES, DataModel # Keywords which are allowed in data model descriptions. KEYWORDS = ["parent", # deprecated, use inherit_from_* instead: @@ -234,6 +234,9 @@ class Parser(object): ymlmodel["extern"] = [] for name in ymlmodel["extern"]: + if name in CAOSDB_INTERNAL_PROPERTIES: + self.model[name] = db.Property(name=name).retrieve() + continue for role in ("Property", "RecordType", "Record", "File"): if db.execute_query("COUNT {} {}".format(role, name)) > 0: self.model[name] = db.execute_query( diff --git a/src/doc/yaml_interface.rst b/src/doc/yaml_interface.rst index dcf4c5d6c7a674bd8d32d92df0a509e511af26f5..476e92829238a0fc9dac851c61790c022e9fcde9 100644 --- a/src/doc/yaml_interface.rst +++ b/src/doc/yaml_interface.rst @@ -50,7 +50,7 @@ This example defines 3 ``RecordType``s: - A Person with a ``firstName`` and a ``lastName`` (as recommended properties) - A ``LabbookEntry`` with multiple recommended properties of different data types - It is assumed that the server knows a RecordType or Property with the name - "Textfile". + ``Textfile``. One major advantage of using this interface (in contrast to the standard python interface) is that properties can be defined and added to record types "on-the-fly". E.g. the three lines for ``firstName`` as sub entries of ``Person`` have two effects on CaosDB: @@ -66,7 +66,8 @@ Note the difference between the three property declarations of ``LabbookEntry``: - ``responsible``: This defines and adds a property with name "responsible" to ``LabbookEntry`, which has a datatype ``Person``. ``Person`` is defined above. - ``firstName``: This defines and adds a property with the standard data type ``TEXT`` to record type ``Person``. -If the data model depends on already existing parts, those can be added using the ``extern`` keyword. +If the data model depends on record types or properties which already exist in CaosDB, those can be +added using the ``extern`` keyword: ``extern`` takes a list of previously defined names. Datatypes ---------