Skip to content

Commit 51a29d2

Browse files
authored
Merge pull request #234 from cwacek/test-233
Parallel PR to #233 (fixes #232)
2 parents 8b32dfa + 79f984a commit 51a29d2

File tree

5 files changed

+155
-7
lines changed

5 files changed

+155
-7
lines changed

python_jsonschema_objects/descriptors.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ def __set__(self, obj, val):
8080
break
8181
elif isinstance(typ, TypeProxy):
8282
try:
83-
val = typ(**util.coerce_for_expansion(val))
83+
# handle keyword expansion according to expected types
84+
# using keywords like oneOf, value can be an object, array or literal
85+
val = util.coerce_for_expansion(val)
86+
if isinstance(val, dict):
87+
val = typ(**val)
88+
else:
89+
val = typ(val)
8490
val.validate()
8591
except Exception as e:
8692
errors.append("Failed to coerce to '{0}': {1}".format(typ, e))
@@ -119,7 +125,11 @@ def __set__(self, obj, val):
119125
val.validate()
120126

121127
elif isinstance(info["type"], TypeProxy):
122-
val = info["type"](val)
128+
val = util.coerce_for_expansion(val)
129+
if isinstance(val, dict):
130+
val = info["type"](**val)
131+
else:
132+
val = info["type"](val)
123133

124134
elif isinstance(info["type"], TypeRef):
125135
if not isinstance(val, info["type"].ref_class):

python_jsonschema_objects/pattern_properties.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ def _make_type(self, typ, val):
7171
):
7272
return typ(val)
7373

74+
if isinstance(typ, cb.TypeProxy):
75+
val = util.coerce_for_expansion(val)
76+
if isinstance(val, dict):
77+
val = typ(**val)
78+
else:
79+
val = typ(val)
80+
return val
81+
7482
raise validators.ValidationError(
7583
"additionalProperty type {0} was neither a literal "
7684
"nor a schema wrapper: {1}".format(typ, val)
@@ -95,7 +103,7 @@ def instantiate(self, name, val):
95103
valtype = valtype[0]
96104
return MakeLiteral(name, valtype, val)
97105

98-
elif isinstance(self._additional_type, type):
106+
elif isinstance(self._additional_type, (type, cb.TypeProxy)):
99107
return self._make_type(self._additional_type, val)
100108

101109
raise validators.ValidationError(

test/test_nested_arrays.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
@pytest.fixture
1010
def nested_arrays():
1111
return {
12+
"$schema": "http://json-schema.org/draft-04/schema#",
1213
"title": "example",
1314
"properties": {
1415
"foo": {
1516
"type": "array",
1617
"items": {
1718
"type": "array",
19+
# FIXME: not supported anymore in https://json-schema.org/draft/2020-12
1820
"items": [{"type": "number"}, {"type": "number"}],
1921
},
2022
}

test/test_regression_232.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import jsonschema
2+
import pytest
3+
import python_jsonschema_objects as pjo
4+
5+
schema = {
6+
"$schema": "http://json-schema.org/draft-07/schema",
7+
"title": "myschema",
8+
"type": "object",
9+
"definitions": {
10+
"RefObject": {
11+
"title": "Ref Object",
12+
"properties": {"location": {"$ref": "#/definitions/Location"}},
13+
},
14+
"MapObject": {
15+
"title": "Map Object",
16+
"additionalProperties": {"$ref": "#/definitions/Location"},
17+
},
18+
"MainObject": {
19+
"title": "Main Object",
20+
"additionalProperties": False,
21+
"type": "object",
22+
"properties": {
23+
"location": {
24+
"title": "location",
25+
"oneOf": [
26+
{"$ref": "#/definitions/UNIQUE_STRING"},
27+
{"$ref": "#/definitions/Location"},
28+
],
29+
}
30+
},
31+
},
32+
"Location": {
33+
"title": "Location",
34+
"description": "A Location represents a span on a specific sequence.",
35+
"oneOf": [
36+
{"$ref": "#/definitions/LocationIdentifier"},
37+
{"$ref": "#/definitions/LocationTyped"},
38+
],
39+
},
40+
"LocationIdentifier": {
41+
"type": "integer",
42+
"minimum": 1,
43+
},
44+
"LocationTyped": {
45+
"additionalProperties": False,
46+
"type": "object",
47+
"properties": {
48+
"type": {
49+
"type": "string",
50+
"enum": ["Location"],
51+
"default": "Location",
52+
}
53+
},
54+
},
55+
"UNIQUE_STRING": {
56+
"additionalProperties": False,
57+
"type": "string",
58+
"pattern": "^\\w[^:]*:.+$",
59+
},
60+
},
61+
}
62+
63+
64+
@pytest.fixture
65+
def schema_json():
66+
return schema
67+
68+
69+
def test_nested_oneof_with_different_types(schema_json):
70+
builder = pjo.ObjectBuilder(schema_json)
71+
ns = builder.build_classes()
72+
73+
resolver = jsonschema.RefResolver.from_schema(schema_json)
74+
main_obj = schema_json["definitions"]["MainObject"]
75+
76+
test1 = {"location": 12345}
77+
test2 = {"location": {"type": "Location"}}
78+
test3 = {"location": "unique:12"}
79+
jsonschema.validate(test1, main_obj, resolver=resolver)
80+
jsonschema.validate(test2, main_obj, resolver=resolver)
81+
jsonschema.validate(test3, main_obj, resolver=resolver)
82+
83+
obj1 = ns.MainObject(**test1)
84+
obj2 = ns.MainObject(**test2)
85+
obj3 = ns.MainObject(**test3)
86+
87+
assert obj1.location == 12345
88+
assert obj2.location.type == "Location"
89+
assert obj3.location == "unique:12"
90+
91+
92+
def test_nested_oneof_with_different_types_by_reference(schema_json):
93+
builder = pjo.ObjectBuilder(schema_json)
94+
ns = builder.build_classes()
95+
96+
resolver = jsonschema.RefResolver.from_schema(schema_json)
97+
ref_obj = schema_json["definitions"]["RefObject"]
98+
99+
test1 = {"location": 12345}
100+
test2 = {"location": {"type": "Location"}}
101+
jsonschema.validate(test1, ref_obj, resolver=resolver)
102+
jsonschema.validate(test2, ref_obj, resolver=resolver)
103+
104+
obj1 = ns.RefObject(**test1)
105+
obj2 = ns.RefObject(**test2)
106+
107+
assert obj1.location == 12345
108+
assert obj2.location.type == "Location"
109+
110+
111+
def test_nested_oneof_with_different_types_in_additional_properties(schema_json):
112+
builder = pjo.ObjectBuilder(schema_json)
113+
ns = builder.build_classes()
114+
115+
resolver = jsonschema.RefResolver.from_schema(schema_json)
116+
map_obj = schema_json["definitions"]["MapObject"]
117+
118+
x_prop_name = "location-id"
119+
120+
test1 = {x_prop_name: 12345}
121+
test2 = {x_prop_name: {"type": "Location"}}
122+
jsonschema.validate(test1, map_obj, resolver=resolver)
123+
jsonschema.validate(test2, map_obj, resolver=resolver)
124+
125+
obj1 = ns.MapObject(**test1)
126+
obj2 = ns.MapObject(**test2)
127+
128+
assert obj1[x_prop_name] == 12345
129+
assert obj2[x_prop_name].type == "Location"

tox.ini

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11

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

66
[gh-actions]
77
python =
8-
3.5: py35
98
3.6: py36
109
3.7: py37
1110
3.8: py38
1211

1312

1413
[testenv]
1514
;install_command = pip install {opts} {packages}
16-
commands = coverage run {envbindir}/py.test --doctest-glob='python_jsonschema_objects/*.md' {posargs}
17-
coverage xml --omit=*test* --include=*python_jsonschema_objects*
15+
commands = python -m coverage run {envbindir}/py.test --doctest-glob='python_jsonschema_objects/*.md' {posargs}
16+
python -m coverage xml --omit="*test*"
1817
deps =
1918
coverage
2019
pytest

0 commit comments

Comments
 (0)