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
......@@ -25,7 +25,7 @@ The scope of this json schema is the automatic generation of user interfaces.
"""
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
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
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.
The result will look like this:
......@@ -428,21 +428,33 @@ Parameters
schema : dict
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
-------
out : dict
schema : dict
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 = {
"type": "array",
"items": 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
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.
The result will look like this:
......@@ -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,
numbers will be used as a fallback. Note that even with a dict, the original schema's "title" is
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
-------
out : dict
schema : dict
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()
required = []
ui_schema = None
if isinstance(schemas, dict):
sub_schemas = schemas
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:
for i, schema in enumerate(schemas, start=1):
title = schema.get("title", str(i))
sub_schemas[title] = schema
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 = {
"type": "object",
......@@ -496,4 +529,6 @@ out : dict
"$schema": "https://json-schema.org/draft/2020-12/schema",
}
if ui_schema is not None:
return result, ui_schema
return result
......@@ -883,3 +883,55 @@ RT4:
assert (str(uischema["RT4"]) ==
"{'RT21': {'items': {'RT1': {'ui:widget': 'checkboxes', "
"'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