From 5b8df05b855af46ea862f38283a4949248fde742 Mon Sep 17 00:00:00 2001 From: fspreck <f.spreckelsen@indiscale.com> Date: Fri, 13 Oct 2023 17:54:57 +0200 Subject: [PATCH] ENH: Implement list and scalar properties --- src/caosadvancedtools/json_schema_exporter.py | 89 +++++++++++++++++-- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/src/caosadvancedtools/json_schema_exporter.py b/src/caosadvancedtools/json_schema_exporter.py index bb4d0f22..e7f54151 100644 --- a/src/caosadvancedtools/json_schema_exporter.py +++ b/src/caosadvancedtools/json_schema_exporter.py @@ -20,6 +20,8 @@ # with this program. If not, see <https://www.gnu.org/licenses/>. # +import re + import linkahead as db @@ -33,15 +35,76 @@ def _make_required_list(rt: db.RecordType): return required -def _make_prop_from_prop(prop): +def _extract_elemet_dtyp_from_list_dtype(list_dtype): + + if not list_dtype.lower().startswith("list"): + + raise ValueError(f"Not a list dtype: {list_dtype}") + + pattern = r"^[Ll][Ii][Ss][Tt](<(?P<dtype1>.*)>|\((?P<dtype2>.*)\))$" + comp = re.compile(pattern) + mtch = comp.match(list_dtype) + if mtch is None: + + raise ValueError(f"Not a list dtype: {list_dtype}") + + elif "dtype1" in mtch.groupdict() and mtch.groupdict()["dtype1"] is not None: + return mtch.groupdict()["dtype1"] + + elif "dtype2" in mtch.groupdict() and mtch.groupdict()["dtype2"] is not None: + return mtch.groupdict()["dtype2"] + + else: + raise ValueError(f"Not a list dtype: {list_dtype}") - if prop.datatype == db.TEXT: - return _make_text_property(prop.description) - elif prop.datatype == db.DATETIME: - return _make_text_property(prop.description, "date-time") + +def _make_prop_from_prop(prop, additional_options_for_text_props): + + if prop.is_reference(): + + raise NotImplementedError( + "Reference properties are not supported in this version of the json schema exporter." + ) + + if prop.datatype == db.TEXT or prop.datatype == db.DATETIME: + text_format = None + text_pattern = None + if prop.name in additional_options_for_text_props: + if "pattern" in additional_options_for_text_props[prop.name]: + text_pattern = additional_options_for_text_props[prop.name]["pattern"] + if "format" in additional_options_for_text_props[prop.name]: + text_format = additional_options_for_text_props[prop.name]["format"] + elif prop.datatype == db.DATETIME: + # Set the date or datetime format if only a pattern is given ... + text_format = ["date", "date-time"] + elif prop.datatype == db.DATETIME: + # ... again, for those props that don't appear in the additional + # options list. + text_format = ["date", "date-time"] + + return _make_text_property(prop.description, text_format, text_pattern) json_prop = {} - # if prop.description + if prop.description: + json_prop["description"] = prop.description + + if prop.datatype == db.BOOLEAN: + json_prop["type"] = "boolean" + elif prop.datatype == db.INTEGER: + json_prop["type"] = "integer" + elif prop.datatype == db.DOUBLE: + json_prop["type"] = "number" + elif prop.datatype.startswith("LIST"): + json_prop["type"] = "array" + list_element_prop = db.Property( + name=prop.name, datatype=_extract_elemet_dtyp_from_list_dtype(prop.datatype)) + json_prop["items"] = _make_prop_from_prop( + list_element_prop, additional_options_for_text_props) + else: + raise ValueError( + f"Unknown or no property datatype. Property {prop.name} with type {prop.datatype}") + + return json_prop def _make_text_property(description="", text_format=None, text_pattern=None): @@ -52,7 +115,10 @@ def _make_text_property(description="", text_format=None, text_pattern=None): if description: prop["description"] = description if text_format is not None: - prop["format"] = text_format + if isinstance(text_format, list): + prop["anyOf"] = [{"format": tf} for tf in text_format] + else: + prop["format"] = text_format if text_pattern is not None: prop["pattern"] = text_pattern @@ -60,7 +126,8 @@ def _make_text_property(description="", text_format=None, text_pattern=None): def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = True, - name_and_description_in_properties: bool = False): + name_and_description_in_properties: bool = False, + additional_options_for_text_props: dict = {}): """Create a jsonschema from a given RecordType that can be used, e.g., to validate a json specifying a record of the given type. @@ -74,6 +141,9 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T name_and_description_in_properties : bool, optional Whether to include name and description in the `properties` section of the schema to be exported. Optional, default is False. + additional_options_for_text_props : dict, optional + Dictionary containing additional "pattern" or "format" options for + string-typed properties. Optional, default is empty. Returns ------- @@ -99,5 +169,8 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T props["name"] = _make_text_property("The name of the Record to be created") props["description"] = _make_text_property("The description of the Record to be created") + for prop in rt.properties: + props[prop.name] = _make_prop_from_prop(prop, additional_options_for_text_props) + schema["properties"] = props return schema -- GitLab