Skip to content

Parallel PR to #233 (fixes #232) #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 15, 2023
14 changes: 12 additions & 2 deletions python_jsonschema_objects/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ def __set__(self, obj, val):
break
elif isinstance(typ, TypeProxy):
try:
val = typ(**util.coerce_for_expansion(val))
# handle keyword expansion according to expected types
# using keywords like oneOf, value can be an object, array or literal
val = util.coerce_for_expansion(val)
if isinstance(val, dict):
val = typ(**val)
else:
val = typ(val)
val.validate()
except Exception as e:
errors.append("Failed to coerce to '{0}': {1}".format(typ, e))
Expand Down Expand Up @@ -119,7 +125,11 @@ def __set__(self, obj, val):
val.validate()

elif isinstance(info["type"], TypeProxy):
val = info["type"](val)
val = util.coerce_for_expansion(val)
if isinstance(val, dict):
val = info["type"](**val)
else:
val = info["type"](val)

elif isinstance(info["type"], TypeRef):
if not isinstance(val, info["type"].ref_class):
Expand Down
10 changes: 9 additions & 1 deletion python_jsonschema_objects/pattern_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ def _make_type(self, typ, val):
):
return typ(val)

if isinstance(typ, cb.TypeProxy):
val = util.coerce_for_expansion(val)
if isinstance(val, dict):
val = typ(**val)
else:
val = typ(val)
return val

raise validators.ValidationError(
"additionalProperty type {0} was neither a literal "
"nor a schema wrapper: {1}".format(typ, val)
Expand All @@ -95,7 +103,7 @@ def instantiate(self, name, val):
valtype = valtype[0]
return MakeLiteral(name, valtype, val)

elif isinstance(self._additional_type, type):
elif isinstance(self._additional_type, (type, cb.TypeProxy)):
return self._make_type(self._additional_type, val)

raise validators.ValidationError(
Expand Down
2 changes: 2 additions & 0 deletions test/test_nested_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
@pytest.fixture
def nested_arrays():
return {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "example",
"properties": {
"foo": {
"type": "array",
"items": {
"type": "array",
# FIXME: not supported anymore in https://json-schema.org/draft/2020-12
"items": [{"type": "number"}, {"type": "number"}],
},
}
Expand Down
129 changes: 129 additions & 0 deletions test/test_regression_232.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import jsonschema
import pytest
import python_jsonschema_objects as pjo

schema = {
"$schema": "http://json-schema.org/draft-07/schema",
"title": "myschema",
"type": "object",
"definitions": {
"RefObject": {
"title": "Ref Object",
"properties": {"location": {"$ref": "#/definitions/Location"}},
},
"MapObject": {
"title": "Map Object",
"additionalProperties": {"$ref": "#/definitions/Location"},
},
"MainObject": {
"title": "Main Object",
"additionalProperties": False,
"type": "object",
"properties": {
"location": {
"title": "location",
"oneOf": [
{"$ref": "#/definitions/UNIQUE_STRING"},
{"$ref": "#/definitions/Location"},
],
}
},
},
"Location": {
"title": "Location",
"description": "A Location represents a span on a specific sequence.",
"oneOf": [
{"$ref": "#/definitions/LocationIdentifier"},
{"$ref": "#/definitions/LocationTyped"},
],
},
"LocationIdentifier": {
"type": "integer",
"minimum": 1,
},
"LocationTyped": {
"additionalProperties": False,
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["Location"],
"default": "Location",
}
},
},
"UNIQUE_STRING": {
"additionalProperties": False,
"type": "string",
"pattern": "^\\w[^:]*:.+$",
},
},
}


@pytest.fixture
def schema_json():
return schema


def test_nested_oneof_with_different_types(schema_json):
builder = pjo.ObjectBuilder(schema_json)
ns = builder.build_classes()

resolver = jsonschema.RefResolver.from_schema(schema_json)
main_obj = schema_json["definitions"]["MainObject"]

test1 = {"location": 12345}
test2 = {"location": {"type": "Location"}}
test3 = {"location": "unique:12"}
jsonschema.validate(test1, main_obj, resolver=resolver)
jsonschema.validate(test2, main_obj, resolver=resolver)
jsonschema.validate(test3, main_obj, resolver=resolver)

obj1 = ns.MainObject(**test1)
obj2 = ns.MainObject(**test2)
obj3 = ns.MainObject(**test3)

assert obj1.location == 12345
assert obj2.location.type == "Location"
assert obj3.location == "unique:12"


def test_nested_oneof_with_different_types_by_reference(schema_json):
builder = pjo.ObjectBuilder(schema_json)
ns = builder.build_classes()

resolver = jsonschema.RefResolver.from_schema(schema_json)
ref_obj = schema_json["definitions"]["RefObject"]

test1 = {"location": 12345}
test2 = {"location": {"type": "Location"}}
jsonschema.validate(test1, ref_obj, resolver=resolver)
jsonschema.validate(test2, ref_obj, resolver=resolver)

obj1 = ns.RefObject(**test1)
obj2 = ns.RefObject(**test2)

assert obj1.location == 12345
assert obj2.location.type == "Location"


def test_nested_oneof_with_different_types_in_additional_properties(schema_json):
builder = pjo.ObjectBuilder(schema_json)
ns = builder.build_classes()

resolver = jsonschema.RefResolver.from_schema(schema_json)
map_obj = schema_json["definitions"]["MapObject"]

x_prop_name = "location-id"

test1 = {x_prop_name: 12345}
test2 = {x_prop_name: {"type": "Location"}}
jsonschema.validate(test1, map_obj, resolver=resolver)
jsonschema.validate(test2, map_obj, resolver=resolver)

obj1 = ns.MapObject(**test1)
obj2 = ns.MapObject(**test2)

assert obj1[x_prop_name] == 12345
assert obj2[x_prop_name].type == "Location"
7 changes: 3 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@

[tox]
envlist = py{35,36,37,38}-jsonschema{23,24,25,26,30}-markdown{2,3}
envlist = py{36,37,38}-jsonschema{23,24,25,26,30}-markdown{2,3}
skip_missing_interpreters = true

[gh-actions]
python =
3.5: py35
3.6: py36
3.7: py37
3.8: py38


[testenv]
;install_command = pip install {opts} {packages}
commands = coverage run {envbindir}/py.test --doctest-glob='python_jsonschema_objects/*.md' {posargs}
coverage xml --omit=*test* --include=*python_jsonschema_objects*
commands = python -m coverage run {envbindir}/py.test --doctest-glob='python_jsonschema_objects/*.md' {posargs}
python -m coverage xml --omit="*test*"
deps =
coverage
pytest
Expand Down