diff --git a/src/caosadvancedtools/json_schema_exporter.py b/src/caosadvancedtools/json_schema_exporter.py index 5c9362fd3551ade773fa452d9f6543c36221c88b..c4af88ce40aee40fe7ff296ce4ab0b6940f45a3d 100644 --- a/src/caosadvancedtools/json_schema_exporter.py +++ b/src/caosadvancedtools/json_schema_exporter.py @@ -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 diff --git a/unittests/test_json_schema_exporter.py b/unittests/test_json_schema_exporter.py index 8fe43bdaa4c4a478310ba3eb682f6254f27b269f..3176d89cb65c0a50dc336e6cdec18f8f2ac314cd 100644 --- a/unittests/test_json_schema_exporter.py +++ b/unittests/test_json_schema_exporter.py @@ -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}}}")