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

ENH: jsex: UI schemas implemented for merging and arraying

parent 17e1efd5
No related branches found
No related tags found
2 merge requests!89ENH: JsonSchemaExporter accepts do_not_create parameter.,!85more jsonschema export
Pipeline #43532 failed
This commit is part of merge request !89. Comments created here will be created in the context of that merge request.
...@@ -25,7 +25,7 @@ The scope of this json schema is the automatic generation of user interfaces. ...@@ -25,7 +25,7 @@ The scope of this json schema is the automatic generation of user interfaces.
""" """
from collections import OrderedDict from collections import OrderedDict
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
import linkahead as db import linkahead as db
from linkahead.common.datatype import get_list_datatype, is_list_datatype from linkahead.common.datatype import get_list_datatype, is_list_datatype
...@@ -409,7 +409,7 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T ...@@ -409,7 +409,7 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
return exporter.recordtype_to_json_schema(rt, rjsf_uischema=rjsf_uischema) return exporter.recordtype_to_json_schema(rt, rjsf_uischema=rjsf_uischema)
def make_array(schema: dict) -> dict: def make_array(schema: dict, rjsf_uischema: dict = None) -> Union[dict, tuple[dict, dict]]:
"""Create an array of the given schema. """Create an array of the given schema.
The result will look like this: The result will look like this:
...@@ -428,21 +428,33 @@ Parameters ...@@ -428,21 +428,33 @@ Parameters
schema : dict schema : dict
The JSON schema which shall be packed into an array. The JSON schema which shall be packed into an array.
rjsf_uischema : dict, optional
A react-jsonschema-forms ui schema that shall be wrapped as well.
Returns Returns
------- -------
out : dict schema : dict
A JSON schema dict with a top-level array which contains instances of the given schema. A JSON schema dict with a top-level array which contains instances of the given schema.
ui_schema : dict, optional
The wrapped ui schema. Only returned if ``rjsf_uischema`` is given as parameter.
""" """
result = { result = {
"type": "array", "type": "array",
"items": schema, "items": schema,
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
} }
if rjsf_uischema is not None:
ui_schema = {"items": rjsf_uischema}
return result, ui_schema
return result return result
def merge_schemas(schemas: Union[Dict[str, dict], Iterable[dict]]) -> dict: def merge_schemas(schemas: Union[Dict[str, dict], Iterable[dict]],
rjsf_uischemas: Union[Dict[str, dict], Sequence[dict]] = None) -> (
Union[dict, Tuple[dict, dict]]):
"""Merge the given schemata into a single schema. """Merge the given schemata into a single schema.
The result will look like this: The result will look like this:
...@@ -469,24 +481,45 @@ schemas : dict[str, dict] | Iterable[dict] ...@@ -469,24 +481,45 @@ schemas : dict[str, dict] | Iterable[dict]
be used as property names, otherwise the titles of the submitted schemata. If they have no title, be used as property names, otherwise the titles of the submitted schemata. If they have no title,
numbers will be used as a fallback. Note that even with a dict, the original schema's "title" is numbers will be used as a fallback. Note that even with a dict, the original schema's "title" is
not changed. not changed.
rjsf_uischemas : dict[str, dict] | Iterable[dict], optional
If given, also merge the react-jsonschema-forms from this argument and return as the second return
value. If ``schemas`` is a dict, this parameter must also be a dict, if ``schemas`` is only an
iterable, this paramater must support numerical indexing.
Returns Returns
------- -------
out : dict schema : dict
A JSON schema dict with a top-level object which contains the given schemata as properties. A JSON schema dict with a top-level object which contains the given schemata as properties.
uischema : dict
If ``rjsf_uischemas`` was given, this contains the merged UI schemata.
""" """
sub_schemas: dict[str, dict] = OrderedDict() sub_schemas: dict[str, dict] = OrderedDict()
required = [] required = []
ui_schema = None
if isinstance(schemas, dict): if isinstance(schemas, dict):
sub_schemas = schemas sub_schemas = schemas
required = [str(k) for k in schemas.keys()] required = [str(k) for k in schemas.keys()]
if rjsf_uischemas is not None:
if not isinstance(rjsf_uischemas, dict):
raise ValueError("Parameter `rjsf_uischemas` must be a dict, because `schemas` is "
f"as well, but it is a {type(rjsf_uischemas)}.")
ui_schema = {k: rjsf_uischemas[k] for k in schemas.keys()}
else: else:
for i, schema in enumerate(schemas, start=1): for i, schema in enumerate(schemas, start=1):
title = schema.get("title", str(i)) title = schema.get("title", str(i))
sub_schemas[title] = schema sub_schemas[title] = schema
required.append(title) required.append(title)
if rjsf_uischemas is not None:
if not isinstance(rjsf_uischemas, Sequence):
raise ValueError("Parameter `rjsf_uischemas` must be a sequence, because `schemas` "
f"is as well, but it is a {type(rjsf_uischemas)}.")
ui_schema = {}
for i, title in enumerate(sub_schemas.keys()):
ui_schema[title] = rjsf_uischemas[i]
# ui_schema = {"index": ui_schema}
result = { result = {
"type": "object", "type": "object",
...@@ -496,4 +529,6 @@ out : dict ...@@ -496,4 +529,6 @@ out : dict
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
} }
if ui_schema is not None:
return result, ui_schema
return result return result
...@@ -883,3 +883,55 @@ RT4: ...@@ -883,3 +883,55 @@ RT4:
assert (str(uischema["RT4"]) == assert (str(uischema["RT4"]) ==
"{'RT21': {'items': {'RT1': {'ui:widget': 'checkboxes', " "{'RT21': {'items': {'RT1': {'ui:widget': 'checkboxes', "
"'ui:options': {'inline': True}}}}}") "'ui:options': {'inline': True}}}}}")
@patch("linkahead.execute_query", new=Mock(side_effect=_mock_execute_query))
def test_uischema():
model_str = """
RT1:
RT2:
obligatory_properties:
RT1:
datatype: LIST<RT1>
RT3:
obligatory_properties:
RT1:
datatype: LIST<RT1>
"""
model = parse_model_from_string(model_str)
uischema_2 = {}
schema_2 = rtjs(model.get_deep("RT2"), additional_properties=False, do_not_create=["RT1"],
multiple_choice=["RT1"], rjsf_uischema=uischema_2)
uischema_3 = {}
schema_3 = rtjs(model.get_deep("RT3"), additional_properties=False, do_not_create=["RT1"],
multiple_choice=["RT1"], rjsf_uischema=uischema_3)
# Merging #################################################################
# Using dictionaries
schemas_dict = {"schema_2": schema_2, "schema_3": schema_3}
uischemas_dict = {"schema_2": uischema_2["RT2"], "schema_3": uischema_3["RT3"]}
merged_dict, merged_dict_ui = jsex.merge_schemas(schemas_dict, uischemas_dict)
assert merged_dict_ui["schema_2"] == merged_dict_ui["schema_3"]
assert (str(merged_dict_ui["schema_2"])
== "{'RT1': {'ui:widget': 'checkboxes', 'ui:options': {'inline': True}}}")
# Using lists
schemas_list = [schema_2, schema_3]
uischemas_list = [uischema_2["RT2"], uischema_3["RT3"]]
merged_list, merged_list_ui = jsex.merge_schemas(schemas_list, uischemas_list)
assert merged_list["properties"]["RT2"] == merged_dict["properties"]["schema_2"]
assert merged_list_ui["RT2"] == merged_list_ui["RT3"]
assert merged_list_ui["RT2"] == merged_dict_ui["schema_2"]
# Asserting failures
with raises(ValueError):
jsex.merge_schemas(schemas_dict, uischemas_list)
with raises(ValueError):
jsex.merge_schemas(schemas_list, uischemas_dict)
# Arraying ################################################################
array2, array2_ui = jsex.make_array(schema_2, uischema_2["RT2"])
assert array2["items"] == schema_2
assert array2_ui["items"] == uischema_2["RT2"]
assert (str(array2_ui["items"])
== "{'RT1': {'ui:widget': 'checkboxes', 'ui:options': {'inline': True}}}")
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