Skip to content
Snippets Groups Projects
Verified Commit d75169fc authored by Daniel Hornung's avatar Daniel Hornung
Browse files

TEST: Added tests.

parent c6d0cf31
No related branches found
No related tags found
2 merge requests!89ENH: JsonSchemaExporter accepts do_not_create parameter.,!76Add to existing model
Pipeline #40040 passed
......@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ###
* Parsing from YAML now allows to give an existing model to which the YAML data model shall be
added.
### Changed ###
* A bit better error handling in the yaml model parser.
......
......@@ -60,7 +60,8 @@ class DataModel(dict):
different purpose (e.g. someone else's experiment).
DataModel inherits from dict. The keys are always the names of the
entities. Thus you cannot have unnamed entities in your model.
entities. Thus you cannot have unnamed or ambiguously named entities in your
model.
Example:
......
# This file is a part of the CaosDB Project.
#
# Copyright (C) 2022 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
# Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com>
# Copyright (C) 2023 Daniel Hornung <d.hornung@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
......@@ -42,7 +42,7 @@ import re
import sys
import yaml
from typing import List
from typing import List, Optional
from warnings import warn
import jsonschema
......@@ -140,15 +140,29 @@ class JsonSchemaDefinitionError(RuntimeError):
super().__init__(msg)
def parse_model_from_yaml(filename, existing_model=None):
"""Shortcut if the Parser object is not needed."""
def parse_model_from_yaml(filename, existing_model: Optional[dict] = None):
"""Shortcut if the Parser object is not needed.
Parameters
----------
existing_model : dict, optional
An existing model to which the created model shall be added.
"""
parser = Parser()
return parser.parse_model_from_yaml(filename, existing_model=existing_model)
def parse_model_from_string(string, existing_model=None):
"""Shortcut if the Parser object is not needed."""
def parse_model_from_string(string, existing_model: Optional[dict] = None):
"""Shortcut if the Parser object is not needed.
Parameters
----------
existing_model : dict, optional
An existing model to which the created model shall be added.
"""
parser = Parser()
return parser.parse_model_from_string(string, existing_model=existing_model)
......@@ -158,28 +172,37 @@ def parse_model_from_json_schema(
filename: str,
top_level_recordtype: bool = True,
types_for_missing_array_items: dict = {},
ignore_unspecified_array_items: bool = False
ignore_unspecified_array_items: bool = False,
existing_model: Optional[dict] = None
):
"""Return a datamodel parsed from a json schema definition.
Parameters
----------
filename : str
The path of the json schema file that is to be parsed
top_level_recordtype : bool, optional
Whether there is a record type defined at the top level of the
schema. Default is true.
types_for_missing_array_items : dict, optional
dictionary containing fall-back types for json entries with `type:
array` but without `items` specification. Default is an empty dict.
ignore_unspecified_array_items : bool, optional
Whether to ignore `type: array` entries the type of which is not
specified by their `items` property or given in
`types_for_missing_array_items`. An error is raised if they are not
ignored. Default is False.
existing_model : dict, optional
An existing model to which the created model shall be added.
Returns
-------
out : Datamodel
The datamodel generated from the input schema which then can be used for
synchronizing with CaosDB.
......@@ -207,7 +230,7 @@ class Parser(object):
self.model = {}
self.treated = []
def parse_model_from_yaml(self, filename, existing_model=None):
def parse_model_from_yaml(self, filename, existing_model: Optional[dict] = None):
"""Create and return a data model from the given file.
Parameters
......@@ -215,6 +238,9 @@ class Parser(object):
filename : str
The path to the YAML file.
existing_model : dict, optional
An existing model to which the created model shall be added.
Returns
-------
out : DataModel
......@@ -225,7 +251,7 @@ class Parser(object):
return self._create_model_from_dict(ymlmodel, existing_model=existing_model)
def parse_model_from_string(self, string, existing_model=None):
def parse_model_from_string(self, string, existing_model: Optional[dict] = None):
"""Create and return a data model from the given YAML string.
Parameters
......@@ -233,6 +259,9 @@ class Parser(object):
string : str
The YAML string.
existing_model : dict, optional
An existing model to which the created model shall be added.
Returns
-------
out : DataModel
......@@ -242,7 +271,7 @@ class Parser(object):
return self._create_model_from_dict(ymlmodel, existing_model=existing_model)
def _create_model_from_dict(self, ymlmodel, existing_model=None):
def _create_model_from_dict(self, ymlmodel, existing_model: Optional[dict] = None):
"""Create and return a data model out of the YAML dict `ymlmodel`.
Parameters
......@@ -251,7 +280,7 @@ class Parser(object):
The dictionary parsed from a YAML file.
existing_model : dict, optional
An existing model to which the ymlmodel shall be added.
An existing model to which the created model shall be added.
Returns
-------
......@@ -356,8 +385,7 @@ class Parser(object):
if definition is None:
return
if (self.model[name] is None
and isinstance(definition, dict)
if (self.model[name] is None and isinstance(definition, dict)
# is it a property
and "datatype" in definition
# but not simply an RT of the model
......
# This file is a part of the CaosDB Project.
#
# Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
# Copyright (C) 2023 Daniel Hornung <d.hornung@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 unittest
from datetime import date
from tempfile import NamedTemporaryFile
......@@ -340,6 +358,35 @@ A:
assert "line {}".format(line) in yde.exception.args[0]
def test_existing_model():
"""Parsing more than one model may require to append to existing models."""
model_str_1 = """
A:
obligatory_properties:
number:
datatype: INTEGER
"""
model_str_2 = """
B:
obligatory_properties:
A:
"""
model_1 = parse_model_from_string(model_str_1)
model_2 = parse_model_from_string(model_str_2, existing_model=model_1)
for ent in ["A", "B", "number"]:
assert ent in model_2
model_str_redefine = """
number:
datatype: DOUBLE
description: Hello number!
"""
model_redefine = parse_model_from_string(model_str_redefine, existing_model=model_1)
print(model_redefine)
assert model_redefine["number"].description == "Hello number!"
assert model_redefine["number"].datatype == db.INTEGER # FIXME Shouldn't this be DOUBLE?
def test_define_role():
model = """
A:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment