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

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

ENH: jsex: Allowing to update schemata with any other dicts.

See merge request !86
parents 298937e4 e8caa024
No related branches found
No related tags found
2 merge requests!89ENH: JsonSchemaExporter accepts do_not_create parameter.,!86ENH: jsex: Allowing to update schemata with any other dicts.
Pipeline #43778 passed
......@@ -39,6 +39,8 @@ class JsonSchemaExporter:
name_property_for_new_records: bool = False,
description_property_for_new_records: bool = False,
additional_options_for_text_props: dict = None,
additional_json_schema: Dict[str, dict] = None,
additional_ui_schema: Dict[str, dict] = None,
units_in_description: bool = True,
do_not_create: List[str] = None,
do_not_retrieve: List[str] = None,
......@@ -61,6 +63,10 @@ class JsonSchemaExporter:
additional_options_for_text_props : dict, optional
Dictionary containing additional "pattern" or "format" options for
string-typed properties. Optional, default is empty.
additional_json_schema : dict[str, dict], optional
Additional schema content for elements of the given names.
additional_ui_schema : dict[str, dict], optional
Additional ui schema content for elements of the given names.
units_in_description : bool, optional
Whether to add the unit of a LinkAhead property (if it has any) to the
description of the corresponding schema entry. If set to false, an
......@@ -83,6 +89,10 @@ class JsonSchemaExporter:
"""
if not additional_options_for_text_props:
additional_options_for_text_props = {}
if not additional_json_schema:
additional_json_schema = {}
if not additional_ui_schema:
additional_ui_schema = {}
if not do_not_create:
do_not_create = []
if not do_not_retrieve:
......@@ -94,6 +104,8 @@ class JsonSchemaExporter:
self._name_property_for_new_records = name_property_for_new_records
self._description_property_for_new_records = description_property_for_new_records
self._additional_options_for_text_props = additional_options_for_text_props
self._additional_json_schema = additional_json_schema
self._additional_ui_schema = additional_ui_schema
self._units_in_description = units_in_description
self._do_not_create = do_not_create
self._do_not_retrieve = do_not_retrieve
......@@ -117,11 +129,18 @@ class JsonSchemaExporter:
def _make_segment_from_prop(self, prop: db.Property) -> Tuple[OrderedDict, dict]:
"""Return the JSON Schema and ui schema segments for the given property.
The result may either be a simple json schema segment, such as a `string
<https://json-schema.org/understanding-json-schema/reference/string>`_ element (or another
simple type), a combination such as `anyOf
<https://json-schema.org/understanding-json-schema/reference/combining#anyof>`_ or an `array
<https://json-schema.org/understanding-json-schema/reference/array>`_ element
Parameters
----------
prop : db.Property
The property to be transformed.
"""
json_prop = OrderedDict()
ui_schema: dict = {}
if prop.datatype == db.TEXT or prop.datatype == db.DATETIME:
text_format = None
......@@ -139,9 +158,9 @@ class JsonSchemaExporter:
# options list.
text_format = ["date", "date-time"]
return self._make_text_property(prop.description, text_format, text_pattern), ui_schema
json_prop = self._make_text_property(prop.description, text_format, text_pattern)
return self._customize(json_prop, ui_schema, prop)
json_prop = OrderedDict()
if prop.description:
json_prop["description"] = prop.description
if self._units_in_description and prop.unit:
......@@ -216,13 +235,31 @@ class JsonSchemaExporter:
raise ValueError(
f"Unknown or no property datatype. Property {prop.name} with type {prop.datatype}")
return json_prop, ui_schema
return self._customize(json_prop, ui_schema, prop)
@staticmethod
def _make_text_property(description="", text_format=None, text_pattern=None):
prop = {
def _make_text_property(description="", text_format=None, text_pattern=None) -> OrderedDict:
"""Create a text element.
Can be a `string <https://json-schema.org/understanding-json-schema/reference/string>`_
element or an `anyOf
<https://json-schema.org/understanding-json-schema/reference/combining#anyof>`_ combination
thereof.
Example:
.. code-block:: json
{
"type": "string",
"description": "Some description",
"pattern": "[0-9]{2..4}-[0-9]{2-4}",
"format": "hostname",
}
"""
prop: OrderedDict[str, Union[str, list]] = OrderedDict({
"type": "string"
}
})
if description:
prop["description"] = description
if text_format is not None:
......@@ -265,6 +302,22 @@ class JsonSchemaExporter:
def _make_segment_from_recordtype(self, rt: db.RecordType) -> Tuple[OrderedDict, dict]:
"""Return Json schema and uischema segments for the given RecordType.
The result is an element of type `object
<https://json-schema.org/understanding-json-schema/reference/object>`_ and typically
contains more properties:
.. code-block:: json
{
"type": "object",
"title": "MyRecordtypeName",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
}
}
"""
schema: OrderedDict[str, Any] = OrderedDict({
"type": "object"
......@@ -299,6 +352,38 @@ class JsonSchemaExporter:
return schema, ui_schema
def _customize(self, schema: OrderedDict, ui_schema: dict, entity: db.Entity = None) -> (
Tuple[OrderedDict, dict]):
"""Generic customization method.
Walk over the available customization stores and apply all applicable ones. No specific order is
guaranteed (as of now).
Parameters
----------
schema, ui_schema : dict
The input schemata.
entity: db.Entity : , optional
An Entity object, may be useful in the future for customizers.
Returns
-------
out : Tuple[dict, dict]
The modified input schemata.
"""
name = schema.get("title", None)
if entity and entity.name:
name = entity.name
for key, add_schema in self._additional_json_schema.items():
if key == name:
schema.update(add_schema)
for key, add_schema in self._additional_ui_schema.items():
if key == name:
ui_schema.update(add_schema)
return schema, ui_schema
def recordtype_to_json_schema(self, rt: db.RecordType, rjsf: bool = False) -> Union[
dict, Tuple[dict, dict]]:
"""Create a jsonschema from a given RecordType that can be used, e.g., to
......@@ -327,6 +412,7 @@ class JsonSchemaExporter:
schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
if rt.description:
schema["description"] = rt.description
schema, inner_uischema = self._customize(schema, inner_uischema, rt)
if rjsf:
uischema = {}
......@@ -340,6 +426,8 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
name_property_for_new_records: bool = False,
description_property_for_new_records: bool = False,
additional_options_for_text_props: Optional[dict] = None,
additional_json_schema: Dict[str, dict] = None,
additional_ui_schema: Dict[str, dict] = None,
units_in_description: bool = True,
do_not_create: List[str] = None,
do_not_retrieve: List[str] = None,
......@@ -369,6 +457,10 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
additional_options_for_text_props : dict, optional
Dictionary containing additional "pattern" or "format" options for
string-typed properties. Optional, default is empty.
additional_json_schema : dict[str, dict], optional
Additional schema content for elements of the given names.
additional_ui_schema : dict[str, dict], optional
Additional ui schema content for elements of the given names.
units_in_description : bool, optional
Whether to add the unit of a LinkAhead property (if it has any) to the
description of the corresponding schema entry. If set to false, an
......@@ -406,6 +498,8 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T
name_property_for_new_records=name_property_for_new_records,
description_property_for_new_records=description_property_for_new_records,
additional_options_for_text_props=additional_options_for_text_props,
additional_json_schema=additional_json_schema,
additional_ui_schema=additional_ui_schema,
units_in_description=units_in_description,
do_not_create=do_not_create,
do_not_retrieve=do_not_retrieve,
......
......@@ -99,7 +99,7 @@ RT3:
elif query_string == "SELECT name, id FROM FILE":
return all_files
else:
print(f"Query string: {query_string}")
# print(f"Query string: {query_string}")
if unique is True:
return db.Entity()
return db.Container()
......@@ -929,3 +929,69 @@ RT3:
assert array2_ui["items"] == uischema_2
assert (str(array2_ui["items"])
== "{'RT1': {'ui:widget': 'checkboxes', 'ui:options': {'inline': True}}}")
def test_schema_customization_with_dicts():
"""Testing the ``additional_json_schema`` and ``additional_ui_schema`` parameters."""
model_str = """
RT1:
RT21:
obligatory_properties:
RT1:
datatype: LIST<RT1>
text:
datatype: TEXT
description: Some description
RT3:
obligatory_properties:
number:
datatype: INTEGER
"""
model = parse_model_from_string(model_str)
custom_schema = {
"RT21": {
"minProperties": 2,
},
"text": {
"format": "email",
"description": "Better description.",
},
"number": {
"minimum": 0,
"exclusiveMaximum": 100,
},
}
custom_ui_schema = {
"text": {
"ui:help": "Hint: keep it short.",
"ui:widget": "password",
},
"number": {
"ui:order": 2,
}
}
schema_21, uischema_21 = rtjs(model.get_deep("RT21"), additional_properties=False,
do_not_create=["RT1"], rjsf=True)
assert len(uischema_21) == 0
assert schema_21["properties"]["text"]["description"] == "Some description"
assert "format" not in schema_21["properties"]["text"]
schema_21, uischema_21 = rtjs(model.get_deep("RT21"), additional_properties=False,
additional_json_schema=custom_schema,
additional_ui_schema=custom_ui_schema, do_not_create=["RT1"],
rjsf=True)
assert (str(uischema_21)
== "{'text': {'ui:help': 'Hint: keep it short.', 'ui:widget': 'password'}}")
assert schema_21["properties"]["text"]["description"] == "Better description."
assert schema_21["properties"]["text"].get("format") == "email"
assert schema_21.get("minProperties") == 2
schema_3, uischema_3 = rtjs(model.get_deep("RT3"), additional_properties=False,
additional_json_schema=custom_schema,
additional_ui_schema=custom_ui_schema, rjsf=True)
assert (json.dumps(schema_3["properties"]["number"]) ==
'{"type": "integer", "minimum": 0, "exclusiveMaximum": 100}')
assert (str(uischema_3) == "{'number': {'ui:order': 2}}")
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