From 51c185ce417bb2d7eb7f5699c43d9f824633bdc1 Mon Sep 17 00:00:00 2001 From: florian <f.spreckelsen@inidscale.com> Date: Tue, 14 Nov 2023 16:25:47 +0100 Subject: [PATCH] ENH: Add option to wrap file properties in objects ... ... i.e., a workaround for https://github.com/rjsf-team/react-jsonschema-form/issues/3957. --- src/caosadvancedtools/json_schema_exporter.py | 59 +++++++++++++++++-- unittests/test_json_schema_exporter.py | 35 +++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/caosadvancedtools/json_schema_exporter.py b/src/caosadvancedtools/json_schema_exporter.py index 7ed78205..801a4861 100644 --- a/src/caosadvancedtools/json_schema_exporter.py +++ b/src/caosadvancedtools/json_schema_exporter.py @@ -46,6 +46,7 @@ class JsonSchemaExporter: do_not_retrieve: List[str] = None, no_remote: bool = False, multiple_choice: List[str] = None, + wrap_files_in_objects: bool = False, ): """Set up a JsonSchemaExporter, which can then be applied on RecordTypes. @@ -86,6 +87,13 @@ class JsonSchemaExporter: A list of reference Property names which shall be denoted as multiple choice properties. This means that each option in this property may be selected at most once. This is not implemented yet if the Property is not in ``do_not_create`` as well. + wrap_files_in_objects : bool, optional + Whether (lists of) files should be wrapped into an array of objects + that have a file property. The sole purpose of this wrapping is to + provide a workaround for a `react-jsonschema-form + bug<https://github.com/rjsf-team/react-jsonschema-form/issues/3957>`_ + so only set this to True if you're using the exported schema with + react-json-form and you are experiencing the bug. Default is False. """ if not additional_options_for_text_props: additional_options_for_text_props = {} @@ -111,6 +119,7 @@ class JsonSchemaExporter: self._do_not_retrieve = do_not_retrieve self._no_remote = no_remote self._multiple_choice = multiple_choice + self._wrap_files_in_objects = wrap_files_in_objects @staticmethod def _make_required_list(rt: db.RecordType): @@ -177,7 +186,9 @@ class JsonSchemaExporter: json_prop["type"] = "integer" elif prop.datatype == db.DOUBLE: json_prop["type"] = "number" - elif is_list_datatype(prop.datatype): + elif is_list_datatype(prop.datatype) and not ( + self._wrap_files_in_objects and get_list_datatype(prop.datatype, + strict=True) == db.FILE): json_prop["type"] = "array" list_element_prop = db.Property( name=prop.name, datatype=get_list_datatype(prop.datatype, strict=True)) @@ -196,9 +207,39 @@ class JsonSchemaExporter: json_prop["enum"] = values if prop.name in self._multiple_choice: json_prop["uniqueItems"] = True - elif prop.datatype == db.FILE: - json_prop["type"] = "string" - json_prop["format"] = "data-url" + elif prop.datatype == db.FILE or ( + self._wrap_files_in_objects and + is_list_datatype(prop.datatype) and + get_list_datatype(prop.datatype, strict=True) == db.FILE + ): + if self._wrap_files_in_objects: + # Workaround for react-jsonschema-form bug + # https://github.com/rjsf-team/react-jsonschema-form/issues/3957: + # Wrap all FILE references (regardless whether lists or + # scalars) in an array of objects that have a file property, + # since objects can be deleted, files can't. + json_prop["type"] = "array" + json_prop["items"] = { + "type": "object", + "title": "Next file", + # The wrapper object must wrap a file and can't be empty. + "required": ["file"], + # Wrapper objects must only contain the wrapped file. + "additionalProperties": False, + "properties": { + "file": { + "title": "Enter your file.", + "type": "string", + "format": "data-url" + } + } + } + if not is_list_datatype(prop.datatype): + # Scalar file, so the array has maximum length 1 + json_prop["maxItems"] = 1 + else: + json_prop["type"] = "string" + json_prop["format"] = "data-url" else: prop_name = prop.datatype if isinstance(prop.datatype, db.Entity): @@ -434,6 +475,7 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T no_remote: bool = False, multiple_choice: List[str] = None, rjsf: bool = False, + wrap_files_in_objects: bool = False ) -> Union[dict, Tuple[dict, 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. @@ -483,6 +525,14 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T rjsf : bool, optional If True, uiSchema definitions for react-jsonschema-forms will be output as the second return value. Default is False. + wrap_files_in_objects : bool, optional + Whether (lists of) files should be wrapped into an array of objects that + have a file property. The sole purpose of this wrapping is to provide a + workaround for a `react-jsonschema-form + bug<https://github.com/rjsf-team/react-jsonschema-form/issues/3957>`_ so + only set this to True if you're using the exported schema with + react-json-form and you are experiencing the bug. Default is False. + Returns ------- @@ -505,6 +555,7 @@ def recordtype_to_json_schema(rt: db.RecordType, additional_properties: bool = T do_not_retrieve=do_not_retrieve, no_remote=no_remote, multiple_choice=multiple_choice, + wrap_files_in_objects=wrap_files_in_objects ) return exporter.recordtype_to_json_schema(rt, rjsf=rjsf) diff --git a/unittests/test_json_schema_exporter.py b/unittests/test_json_schema_exporter.py index 9adccc45..5b3cd416 100644 --- a/unittests/test_json_schema_exporter.py +++ b/unittests/test_json_schema_exporter.py @@ -331,10 +331,6 @@ def test_rt_with_list_props(): @patch("linkahead.execute_query", new=Mock(side_effect=_mock_execute_query)) def test_rt_with_references(): - """References and lists of references to files will come later, so test if - the errors are thrown correctly. - - """ rt = db.RecordType() rt.add_property(name="RefProp", datatype=db.REFERENCE) @@ -560,6 +556,21 @@ def test_rt_with_references(): assert schema["properties"]["FileProp"]["type"] == "string" assert schema["properties"]["FileProp"]["format"] == "data-url" + # wrap in array (cf. https://github.com/rjsf-team/react-jsonschema-form/issues/3957) + schema = rtjs(rt, wrap_files_in_objects=True) + assert schema["properties"]["FileProp"]["type"] == "array" + assert schema["properties"]["FileProp"]["maxItems"] == 1 + assert "items" in schema["properties"]["FileProp"] + items = schema["properties"]["FileProp"]["items"] + assert items["type"] == "object" + assert len(items["required"]) == 1 + assert "file" in items["required"] + assert items["additionalProperties"] is False + assert len(items["properties"]) == 1 + assert "file" in items["properties"] + assert items["properties"]["file"]["type"] == "string" + assert items["properties"]["file"]["format"] == "data-url" + rt = db.RecordType() rt.add_property(name="FileProp", datatype=db.LIST(db.FILE)) @@ -568,6 +579,22 @@ def test_rt_with_references(): assert schema["properties"]["FileProp"]["items"]["type"] == "string" assert schema["properties"]["FileProp"]["items"]["format"] == "data-url" + # wrap in array (cf. https://github.com/rjsf-team/react-jsonschema-form/issues/3957) + print(schema) + schema = rtjs(rt, wrap_files_in_objects=True) + assert schema["properties"]["FileProp"]["type"] == "array" + assert "maxItems" not in schema["properties"]["FileProp"] + assert "items" in schema["properties"]["FileProp"] + items = schema["properties"]["FileProp"]["items"] + assert items["type"] == "object" + assert len(items["required"]) == 1 + assert "file" in items["required"] + assert items["additionalProperties"] is False + assert len(items["properties"]) == 1 + assert "file" in items["properties"] + assert items["properties"]["file"]["type"] == "string" + assert items["properties"]["file"]["format"] == "data-url" + def test_broken(): -- GitLab