Skip to content

Commit 36cdbd6

Browse files
committed
Merge tag '0.0.20' into develop
Version 0.0.20 + Adds support for `oneOf` in array `items` definitions + This the the pattern where an array can have multiple differnt types, but from a restricted set. Required setting up a TypeProxy object to validate array types. + Fixes bug #39 where array item references weren't resolved generally. h/t @blr246
2 parents 2c48ca5 + 82c8fc5 commit 36cdbd6

File tree

6 files changed

+183
-5
lines changed

6 files changed

+183
-5
lines changed

python_jsonschema_objects/classbuilder.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,34 @@ def MakeLiteral(name, typ, value, **properties):
288288
return klass(value)
289289

290290

291+
class TypeProxy(object):
292+
293+
def __init__(self, types):
294+
self._types = types
295+
296+
def __call__(self, *a, **kw):
297+
validation_errors = []
298+
valid_types = self._types
299+
for klass in valid_types:
300+
logger.debug(util.lazy_format(
301+
"Attempting to instantiate {0} as {1}",
302+
self.__class__, klass))
303+
try:
304+
obj = klass(*a, **kw)
305+
except TypeError as e:
306+
validation_errors.append((klass, e))
307+
except validators.ValidationError as e:
308+
validation_errors.append((klass, e))
309+
else:
310+
return obj
311+
312+
else: # We got nothing
313+
raise validators.ValidationError(
314+
"Unable to instantiate any valid types: \n"
315+
"\n".join("{0}: {1}".format(k, e) for k, e in validation_errors)
316+
)
317+
318+
291319
class LiteralValue(object):
292320
"""Docstring for LiteralValue """
293321

@@ -426,7 +454,7 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,)):
426454

427455
with self.resolver.resolving(uri) as resolved:
428456
self.resolved[uri] = None # Set incase there is a circular reference in schema definition
429-
self.resolved[uri] = self._build_object(
457+
self.resolved[uri] = self.construct(
430458
uri,
431459
resolved,
432460
parent)
@@ -552,7 +580,19 @@ def _build_object(self, nm, clsdata, parents):
552580
uri = "{0}/{1}_{2}".format(nm,
553581
prop, "<anonymous_field>")
554582
try:
555-
typ = self.construct(uri, detail['items'])
583+
if 'oneOf' in detail['items']:
584+
typ = TypeProxy([
585+
self.construct(uri + "_%s" % i, item_detail)
586+
if '$ref' not in item_detail else
587+
self.construct(util.resolve_ref_uri(
588+
self.resolver.resolution_scope,
589+
item_detail['$ref']),
590+
item_detail)
591+
592+
for i, item_detail in enumerate(detail['items']['oneOf'])]
593+
)
594+
else:
595+
typ = self.construct(uri, detail['items'])
556596
propdata = {'type': 'array',
557597
'validator': validators.ArrayValidator.create(uri, item_constraint=typ,
558598
addl_constraints=detail)}

python_jsonschema_objects/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __init__(self, fmt, *args, **kwargs):
1212
self.kwargs = kwargs
1313

1414
def __str__(self):
15-
self.fmt.format(*self.args, **self.kwargs)
15+
return self.fmt.format(*self.args, **self.kwargs)
1616

1717

1818
def safe_issubclass(x, y):

python_jsonschema_objects/validators.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import six
22
from python_jsonschema_objects import util
3+
import collections
34

45

56
class ValidationError(Exception):
@@ -218,9 +219,13 @@ def validate_items(self):
218219
elif util.safe_issubclass(typ, classbuilder.ProtocolBase):
219220
if not isinstance(elem, typ):
220221
try:
221-
val = typ(**util.coerce_for_expansion(elem))
222+
if isinstance(elem, (six.string_types, six.integer_types, float)):
223+
val = typ(elem)
224+
else:
225+
val = typ(**util.coerce_for_expansion(elem))
222226
except TypeError as e:
223-
raise ValidationError("'{0}' is not a valid value for '{1}'".format(elem, typ))
227+
raise ValidationError("'{0}' is not a valid value for '{1}': {2}"
228+
.format(elem, typ, e))
224229
else:
225230
val = elem
226231
val.validate()
@@ -229,6 +234,17 @@ def validate_items(self):
229234
val = typ(elem)
230235
val.validate()
231236
typed_elems.append(val)
237+
elif isinstance(typ, classbuilder.TypeProxy):
238+
try:
239+
if isinstance(elem, (six.string_types, six.integer_types, float)):
240+
val = typ(elem)
241+
else:
242+
val = typ(**util.coerce_for_expansion(elem))
243+
except TypeError as e:
244+
raise ValidationError("'{0}' is not a valid value for '{1}': {2}"
245+
.format(elem, typ, e))
246+
val.validate()
247+
typed_elems.append(val)
232248

233249
return typed_elems
234250

@@ -257,6 +273,8 @@ def create(name, item_constraint=None, **addl_constraints):
257273
if not any([isdict, isklass]):
258274
raise TypeError(
259275
"Item constraint (position {0}) is not a schema".format(i))
276+
elif isinstance(item_constraint, classbuilder.TypeProxy):
277+
pass
260278
else:
261279
isdict = isinstance(item_constraint, (dict,))
262280
isklass = isinstance( item_constraint, type) and util.safe_issubclass(

test/test_pytest.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,62 @@ def test_regression_9():
2020
builder = pjs.ObjectBuilder(schema)
2121
builder.build_classes()
2222

23+
def test_arrays_can_have_reffed_items_of_mixed_type():
24+
schema = {
25+
"$schema": "http://json-schema.org/schema#",
26+
"id": "test",
27+
"type": "object",
28+
"properties": {
29+
"list": {
30+
"type": "array",
31+
"items": {"oneOf": [
32+
{"$ref": "#/definitions/foo"},
33+
{
34+
"type": "object",
35+
"properties": {
36+
"bar": {"type":"string"}
37+
},
38+
"required": ["bar"]
39+
}
40+
]},
41+
}
42+
},
43+
"definitions": {
44+
"foo": {
45+
"type": "string"
46+
}
47+
}
48+
}
49+
builder = pjs.ObjectBuilder(schema)
50+
ns = builder.build_classes()
51+
52+
ns.Test(list=["foo", "bar"])
53+
ns.Test(list=[{"bar": "nice"}, "bar"])
54+
with pytest.raises(pjs.ValidationError):
55+
ns.Test(list=[100])
56+
57+
58+
def test_regression_39():
59+
builder = pjs.ObjectBuilder("test/thing-two.json")
60+
ns = builder.build_classes()
61+
62+
for thing in ('BarMessage', 'BarGroup', "Bar", "Header"):
63+
assert thing in ns
64+
65+
x = ns.BarMessage(id="message_id",
66+
title="my bar group",
67+
bars=[{"name": "Freddies Half Shell"}]
68+
)
69+
70+
x.validate()
71+
72+
# Now an invalid one
73+
with pytest.raises(pjs.ValidationError):
74+
ns.BarMessage(id="message_id",
75+
title="my bar group",
76+
bars=[{"Address": "I should have a name"}]
77+
)
78+
2379

2480
def test_loads_markdown_schema_extraction(markdown_examples):
2581
assert 'Other' in markdown_examples

test/thing-one.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$schema": "http://json-schema.org/schema#",
3+
"id": "thing_one",
4+
"title": "thing_one",
5+
"description": "The first thing.",
6+
"type": "object",
7+
"definitions": {
8+
"bar": {
9+
"description": "a bar",
10+
"properties": {
11+
"name": {
12+
"description": "unique name for bar",
13+
"type": "string",
14+
"example": "awesome-bar"
15+
}
16+
},
17+
"required": ["name"]
18+
},
19+
"bar_group": {
20+
"description": "a group of bars",
21+
"properties": {
22+
"title": {
23+
"description": "group title",
24+
"type": "string"
25+
},
26+
"bars": {
27+
"description": "list of bars",
28+
"type": "array",
29+
"items": {
30+
"$ref": "file:///thing-one.json#/definitions/bar"
31+
}
32+
}
33+
}
34+
}
35+
}
36+
}

test/thing-two.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "http://json-schema.org/schema#",
3+
"id": "thing_two",
4+
"title": "thing_two",
5+
"description": "The second thing.",
6+
"type": "object",
7+
"properties": {
8+
"message": {"$ref": "#/definitions/bar_message"}
9+
},
10+
"definitions": {
11+
"header": {
12+
"description": "a message header",
13+
"properties": {
14+
"id": {
15+
"description": "message id",
16+
"type": "string"
17+
}
18+
}
19+
},
20+
"bar_message": {
21+
"description": "a bar message",
22+
"allOf": [
23+
{"$ref": "file:///thing-two.json#/definitions/header"},
24+
{"$ref": "file:///thing-one.json#/definitions/bar_group"}
25+
]
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)