Skip to content

Commit 03be156

Browse files
authored
bugfix: Support oneOf in array types. (#56)
* bugfix: Support oneOf in array types. Also make top-level array types expose the same interface as ProtocolBase (from_json, serialize). Fixes #49 * bugfix: call validate() when instantiating TypeProxy objects There are validations that occur based on the presence of properties (i.e. you cannot assign an object with invalid properties), and there are different validations that are applied on demand or when serializing by calling validate() (these are things like 'required' and range checks). When deciding which type to use from a TypeProxy, we need to make sure we call validate() because we want to make sure that we correctly determine which type is valid.
1 parent f6e2f9e commit 03be156

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

python_jsonschema_objects/classbuilder.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ def validate(self):
255255

256256
propname = lambda x: self.__prop_names__[x]
257257
missing = [x for x in self.__required__
258-
if propname(x) not in self._properties
259-
or self._properties[propname(x)] is None]
258+
if propname(x) not in self._properties or
259+
self._properties[propname(x)] is None]
260260

261261
if len(missing) > 0:
262262
raise validators.ValidationError(
@@ -305,6 +305,7 @@ def __call__(self, *a, **kw):
305305
self.__class__, klass))
306306
try:
307307
obj = klass(*a, **kw)
308+
obj.validate()
308309
except TypeError as e:
309310
validation_errors.append((klass, e))
310311
except validators.ValidationError as e:

python_jsonschema_objects/validators.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ def __init__(self, ary):
166166
raise TypeError("Invalid value given to array validator: {0}"
167167
.format(ary))
168168

169+
@classmethod
170+
def from_json(cls, jsonmsg):
171+
import json
172+
msg = json.loads(jsonmsg)
173+
obj = cls(msg)
174+
obj.validate()
175+
return obj
176+
177+
@classmethod
178+
def serialize(self):
179+
self.validate()
180+
enc = util.ProtocolJSONEncoder()
181+
return enc.encode(self)
182+
169183
def validate(self):
170184
converted = self.validate_items()
171185
self.validate_length()
@@ -238,10 +252,12 @@ def validate_items(self):
238252
val = elem
239253
val.validate()
240254
typed_elems.append(val)
255+
241256
elif util.safe_issubclass(typ, ArrayValidator):
242257
val = typ(elem)
243258
val.validate()
244259
typed_elems.append(val)
260+
245261
elif isinstance(typ, classbuilder.TypeProxy):
246262
try:
247263
if isinstance(elem, (six.string_types, six.integer_types, float)):
@@ -251,8 +267,9 @@ def validate_items(self):
251267
except TypeError as e:
252268
raise ValidationError("'{0}' is not a valid value for '{1}': {2}"
253269
.format(elem, typ, e))
254-
val.validate()
255-
typed_elems.append(val)
270+
else:
271+
val.validate()
272+
typed_elems.append(val)
256273

257274
return typed_elems
258275

@@ -314,11 +331,32 @@ def create(name, item_constraint=None, **addl_constraints):
314331

315332
item_constraint = klassbuilder.resolved[uri]
316333

317-
elif isdict and item_constraint['type'] == 'array':
334+
elif isdict and item_constraint.get('type') == 'array':
335+
# We need to create a sub-array validator.
318336
item_constraint = ArrayValidator.create(name + "#sub",
319337
item_constraint=item_constraint[
320338
'items'],
321339
addl_constraints=item_constraint)
340+
elif isdict and 'oneOf' in item_constraint:
341+
# We need to create a TypeProxy validator
342+
uri = "{0}_{1}".format(name, "<anonymous_list_type>")
343+
type_array = []
344+
for i, item_detail in enumerate(item_constraint['oneOf']):
345+
if '$ref' in item_detail:
346+
subtype = klassbuilder.construct(
347+
util.resolve_ref_uri(
348+
klassbuilder.resolver.resolution_scope,
349+
item_detail['$ref']),
350+
item_detail)
351+
else:
352+
subtype = klassbuilder.construct(
353+
uri + "_%s" % i, item_detail)
354+
355+
type_array.append(subtype)
356+
357+
item_constraint = classbuilder.TypeProxy(type_array)
358+
359+
322360

323361
props['__itemtype__'] = item_constraint
324362

test/test_regression_49.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
3+
from jsonschema import validate
4+
import python_jsonschema_objects as pjo
5+
import json
6+
7+
8+
@pytest.fixture
9+
def bad_schema_49():
10+
return {
11+
'title': 'example',
12+
'type': 'array',
13+
'items': {
14+
'oneOf': [
15+
{
16+
'type': 'object',
17+
'properties': {
18+
'a': {
19+
'type': 'string',
20+
}
21+
},
22+
'required': ['a'],
23+
},
24+
{
25+
'type': 'object',
26+
'properties': {
27+
'b': {
28+
'type': 'string',
29+
}
30+
},
31+
'required': ['b'],
32+
},
33+
]
34+
},
35+
}
36+
37+
38+
@pytest.fixture
39+
def instance():
40+
return [{"a": ''}, {"b": ""}]
41+
42+
43+
def test_is_valid_jsonschema(bad_schema_49, instance):
44+
validate(instance, bad_schema_49)
45+
46+
47+
def test_regression_49(bad_schema_49, instance):
48+
builder = pjo.ObjectBuilder(bad_schema_49)
49+
ns = builder.build_classes()
50+
51+
ns.Example.from_json(json.dumps(instance))

0 commit comments

Comments
 (0)