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

Merge branch 'f-more-jsonschema-export' into 'dev'

ENH: JsonSchemaExporter accepts do_not_create parameter.

See merge request !82
parents d605fd58 0af91939
No related branches found
No related tags found
2 merge requests!89ENH: JsonSchemaExporter accepts do_not_create parameter.,!82ENH: JsonSchemaExporter accepts do_not_create parameter.
Pipeline #43214 passed
......@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
added.
* The `json_schema_exporter` module which introduces tools to create a json
schema from a RecordType, e.g., for the usage in web forms.
* `DataModel.get_deep(name: str)` method which uses the DataModel as a kind of cache pool.
### Changed ###
......
This diff is collapsed.
......@@ -262,3 +262,37 @@ class DataModel(dict):
all_ents[prop.name] = prop
return list(all_ents.values())
def get_deep(self, name: str, visited: set = None):
"""Attempt to resolve references for the given ``name``.
This methods only uses data which is available in this datamodel, which acts kind of like a
cache pool.
Note that this may change this data model (subsequent "get" like calls may also return deep
content.)
"""
entity = self.get(name)
if not entity:
return entity
if not visited:
visited = set()
# new_props = []
for prop in list(entity.get_properties()): # Make a change-resistant list copy.
if prop.name in visited:
continue
visited.add(prop.name)
if prop.name in self:
deep_prop = self.get_deep(prop.name, visited=visited)
linked_prop = entity.get_property(prop)
if not linked_prop.datatype:
if deep_prop.role == "Property":
linked_prop.datatype = deep_prop.datatype
elif deep_prop.role == "RecordType":
linked_prop.datatype = deep_prop
if deep_prop.description:
linked_prop.description = deep_prop.description
else:
print(f"Referenced property \"{prop.name}\" not found in data model.")
return entity
......@@ -2,6 +2,7 @@ import unittest
import caosdb as db
from caosadvancedtools.models.data_model import DataModel
from caosadvancedtools.models.parser import parse_model_from_string
class DataModelTest(unittest.TestCase):
......@@ -33,3 +34,31 @@ class DataModelTest(unittest.TestCase):
DataModel.sync_ids_by_name(l1, l2)
assert l1["TestRecord"].id == rt.id
assert l1["TestRecord2"].id < 0
def test_get_deep(self):
model_recursive_str = """
RT1:
description: some description
obligatory_properties:
RT1:
"""
model_recursive = parse_model_from_string(model_recursive_str)
prop1 = model_recursive["RT1"].get_property("RT1")
assert prop1.datatype is None
# TODO The next line actually changes model_recursive in place, is this OK?
RT1 = model_recursive.get_deep("RT1")
assert model_recursive["RT1"] == RT1
model_unresolved_str = """
RT1:
description: some description
obligatory_properties:
unresolved:
"""
model_unresolved = parse_model_from_string(model_unresolved_str)
rt1_unresolved = model_unresolved["RT1"]
prop_unresolved = model_unresolved.get_deep("unresolved")
assert prop_unresolved.datatype is None
rt1_deep = model_unresolved.get_deep("RT1")
assert rt1_deep == rt1_unresolved
assert rt1_deep is rt1_unresolved
......@@ -20,6 +20,10 @@
# with this program. If not, see <https://www.gnu.org/licenses/>.
#
"""Tests the Json schema exporter."""
import json
import linkahead as db
from jsonschema import FormatChecker, validate, ValidationError
......@@ -27,6 +31,16 @@ from pytest import raises
from unittest.mock import Mock, patch
from caosadvancedtools.json_schema_exporter import recordtype_to_json_schema as rtjs
from caosadvancedtools.models.parser import parse_model_from_string
RT1 = parse_model_from_string("""
RT1:
description: some description
obligatory_properties:
some_date:
datatype: DATETIME
description: Just some date
""").get_deep("RT1")
def _mock_execute_query(query_string, unique=False, **kwargs):
......@@ -60,11 +74,16 @@ def _mock_execute_query(query_string, unique=False, **kwargs):
return referencing_type_records
elif query_string == "FIND RECORDTYPE WITH name='ReferencingType'" and unique is True:
return referencing_type_rt
elif query_string == "SELECT name, id FROM RECORD 'RT1'":
return referencing_type_records # wrong types, but who cares for the test?
elif query_string == "FIND RECORDTYPE WITH name='RT1'" and unique is True:
return RT1
elif query_string == "SELECT name, id FROM RECORD":
return all_records
elif query_string == "SELECT name, id FROM FILE":
return all_files
else:
print(f"Query string: {query_string}")
return db.Container()
......@@ -546,3 +565,119 @@ def test_broken():
rtjs(rt)
assert "MultiProp" in str(nie)
assert str(nie).startswith("Creating a schema for multi-properties is not specified.")
@patch("linkahead.execute_query", new=Mock(side_effect=_mock_execute_query))
def test_reference_options():
"""Testing miscellaneous options.
"""
model_str = """
RT1:
description: some description
obligatory_properties:
some_date:
datatype: DATETIME
description: Just some date
RT2:
obligatory_properties:
RT1:
"""
model = parse_model_from_string(model_str)
# First test: without reference
rt1_dict = rtjs(model.get_deep("RT1"))
assert json.dumps(rt1_dict, indent=2) == """{
"type": "object",
"required": [
"some_date"
],
"additionalProperties": true,
"properties": {
"some_date": {
"description": "Just some date",
"anyOf": [
{
"type": "string",
"format": "date"
},
{
"type": "string",
"format": "date-time"
}
]
}
},
"$schema": "https://json-schema.org/draft/2019-09/schema",
"title": "RT1",
"description": "some description"
}"""
# Second test: with reference
rt2_deep = model.get_deep("RT2")
rt2_dict = rtjs(rt2_deep)
assert json.dumps(rt2_dict, indent=2) == """{
"type": "object",
"required": [
"RT1"
],
"additionalProperties": true,
"properties": {
"RT1": {
"description": "some description",
"oneOf": [
{
"title": "Existing entries",
"enum": [
"103",
"104, referencing"
]
},
{
"type": "object",
"required": [
"some_date"
],
"additionalProperties": true,
"properties": {
"some_date": {
"description": "Just some date",
"anyOf": [
{
"type": "string",
"format": "date"
},
{
"type": "string",
"format": "date-time"
}
]
}
},
"title": "Create new"
}
]
}
},
"$schema": "https://json-schema.org/draft/2019-09/schema",
"title": "RT2"
}"""
# Third test: Reference prop shall be only existing references, no option to create new ones.
rt2_dict = rtjs(model.get_deep("RT2"), do_not_create=["RT1"])
assert json.dumps(rt2_dict, indent=2) == """{
"type": "object",
"required": [
"RT1"
],
"additionalProperties": true,
"properties": {
"RT1": {
"description": "some description",
"enum": [
"103",
"104, referencing"
]
}
},
"$schema": "https://json-schema.org/draft/2019-09/schema",
"title": "RT2"
}"""
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