diff --git a/python_jsonschema_objects/__init__.py b/python_jsonschema_objects/__init__.py index a463ca2..65508b9 100644 --- a/python_jsonschema_objects/__init__.py +++ b/python_jsonschema_objects/__init__.py @@ -183,7 +183,13 @@ def validate(self, obj): except jsonschema.ValidationError as e: raise ValidationError(e) - def build_classes(self, strict=False, named_only=False, standardize_names=True): + def build_classes( + self, + strict=False, + named_only=False, + standardize_names=True, + any_of: typing.Optional[typing.Literal["use-first"]] = None, + ): """ Build all of the classes named in the JSONSchema. @@ -201,12 +207,15 @@ def build_classes(self, strict=False, named_only=False, standardize_names=True): generated). standardize_names: (bool) If true (the default), class names will be transformed by camel casing + any_of: (literal) If not set to None, defines the way anyOf clauses are resolved: + - 'use-first': Generate to the first matching schema in the list under the anyOf + - None: default behavior, anyOf is not supported in the schema Returns: A namespace containing all the generated classes """ - kw = {"strict": strict} + kw = {"strict": strict, "any_of": any_of} builder = classbuilder.ClassBuilder(self.resolver) for nm, defn in six.iteritems(self.schema.get("definitions", {})): resolved = self.resolver.lookup("#/definitions/" + nm) diff --git a/python_jsonschema_objects/classbuilder.py b/python_jsonschema_objects/classbuilder.py index b099f2c..211b9c8 100644 --- a/python_jsonschema_objects/classbuilder.py +++ b/python_jsonschema_objects/classbuilder.py @@ -320,7 +320,6 @@ def validate(self): for prop, val in six.iteritems(self._properties): if val is None: continue - if isinstance(val, ProtocolBase): val.validate() elif getattr(val, "isLiteralClass", None) is True: @@ -502,9 +501,22 @@ def construct(self, uri, *args, **kw): def _construct(self, uri, clsdata, parent=(ProtocolBase,), **kw): if "anyOf" in clsdata: - raise NotImplementedError("anyOf is not supported as bare property") + if kw.get("any_of", None) is None: + raise NotImplementedError( + "anyOf is not supported as bare property (workarounds available by setting any_of flag)" + ) + if kw["any_of"] == "use-first": + # Patch so the first anyOf becomes a single oneOf + clsdata["oneOf"] = [ + clsdata["anyOf"].pop(0), + ] + del clsdata["anyOf"] + else: + raise NotImplementedError( + f"anyOf workaround is not a recognized type (any_of = {kw['any_of']})" + ) - elif "oneOf" in clsdata: + if "oneOf" in clsdata: """If this object itself has a 'oneOf' designation, then construct a TypeProxy. """ @@ -561,7 +573,7 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,), **kw): uri, item_constraint=clsdata_copy.pop("items"), classbuilder=self, - **clsdata_copy + **clsdata_copy, ) return self.resolved[uri] @@ -699,6 +711,7 @@ def _build_object(self, nm, clsdata, parents, **kw): else: uri = "{0}/{1}_{2}".format(nm, prop, "") try: + # NOTE: Currently anyOf workaround is applied on import, not here for serialization if "oneOf" in detail["items"]: typ = TypeProxy( self.construct_objects( diff --git a/test/test_feature_51.py b/test/test_feature_51.py new file mode 100644 index 0000000..4716249 --- /dev/null +++ b/test/test_feature_51.py @@ -0,0 +1,70 @@ +import pytest + +import python_jsonschema_objects as pjo + + +def test_simple_array_anyOf(): + basicSchemaDefn = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "properties": {"ExampleAnyOf": {"$ref": "#/definitions/exampleAnyOf"}}, + "required": ["ExampleAnyOf"], + "type": "object", + "definitions": { + "exampleAnyOf": { + # "type": "string", "format": "email" + "anyOf": [ + {"type": "string", "format": "email"}, + {"type": "string", "maxlength": 0}, + ] + } + }, + } + + builder = pjo.ObjectBuilder(basicSchemaDefn) + + ns = builder.build_classes(any_of="use-first") + ns.Test().from_json('{"ExampleAnyOf" : "test@example.com"}') + + with pytest.raises(pjo.ValidationError): + # Because string maxlength 0 is not selected, as we are using the first validation in anyOf: + ns.Test().from_json('{"ExampleAnyOf" : ""}') + # Because this does not match the email format: + ns.Test().from_json('{"ExampleAnyOf" : "not-an-email"}') + + # Does it also work when not deserializing? + x = ns.Test() + with pytest.raises(pjo.ValidationError): + x.ExampleAnyOf = "" + + with pytest.raises(pjo.ValidationError): + x.ExampleAnyOf = "not-an-email" + + x.ExampleAnyOf = "test@example.com" + out = x.serialize() + y = ns.Test.from_json(out) + assert y.ExampleAnyOf == "test@example.com" + + +def test_simple_array_anyOf_withoutConfig(): + basicSchemaDefn = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "properties": {"ExampleAnyOf": {"$ref": "#/definitions/exampleAnyOf"}}, + "required": ["ExampleAnyOf"], + "type": "object", + "definitions": { + "exampleAnyOf": { + # "type": "string", "format": "email" + "anyOf": [ + {"type": "string", "format": "email"}, + {"type": "string", "maxlength": 0}, + ] + } + }, + } + + builder = pjo.ObjectBuilder(basicSchemaDefn) + + with pytest.raises(NotImplementedError): + builder.build_classes()