Skip to content

Commit 2351b37

Browse files
committed
Merge tag '0.1.1-1' into develop
bugfix: Fix broken as_dict and for_json with arrays.
2 parents f89b240 + 53f576e commit 2351b37

File tree

14 files changed

+625
-92
lines changed

14 files changed

+625
-92
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ coverage.xml
5555
docs/_build/
5656

5757
# PyBuilder
58-
target/
58+
itarget/
59+
.idea

python_jsonschema_objects/__init__.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,27 @@ def __init__(self, schema_uri, resolved={}):
4343
}
4444
)
4545

46+
meta_validator = Draft4Validator(Draft4Validator.META_SCHEMA)
47+
meta_validator.validate(self.schema)
4648
self.validator = Draft4Validator(self.schema,
4749
resolver=self.resolver)
4850

51+
4952
self._classes = None
53+
self._resolved = None
5054

5155
@property
5256
def classes(self):
5357
if self._classes is None:
5458
self._classes = self.build_classes()
5559
return self._classes
5660

61+
def get_class(self, uri):
62+
if self._resolved is None:
63+
self._classes = self.build_classes()
64+
return self._resolved.get(uri, None)
65+
66+
5767
def memory_resolver(self, uri):
5868
return self.mem_resolved[uri[7:]]
5969

@@ -69,7 +79,16 @@ def validate(self, obj):
6979
except jsonschema.ValidationError as e:
7080
raise ValidationError(e)
7181

72-
def build_classes(self):
82+
83+
def build_classes(self,strict=False):
84+
"""
85+
86+
Args:
87+
strict: use this to validate required fields while creating the class
88+
89+
Returns:
90+
91+
"""
7392
builder = classbuilder.ClassBuilder(self.resolver)
7493
for nm, defn in iteritems(self.schema.get('definitions', {})):
7594
uri = util.resolve_ref_uri(
@@ -80,7 +99,9 @@ def build_classes(self):
8099
nm = self.schema['title'] if 'title' in self.schema else self.schema['id']
81100
nm = inflection.parameterize(six.text_type(nm), '_')
82101

83-
builder.construct(nm, self.schema)
102+
kw = {"strict" : strict}
103+
builder.construct(nm, self.schema,**kw)
104+
self._resolved = builder.resolved
84105

85106
return (
86107
util.Namespace.from_mapping(dict(

python_jsonschema_objects/classbuilder.py

Lines changed: 75 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import python_jsonschema_objects.util as util
22
import python_jsonschema_objects.validators as validators
3+
import python_jsonschema_objects.pattern_properties as pattern_properties
34

45
import collections
56
import itertools
@@ -9,6 +10,8 @@
910
import logging
1011
logger = logging.getLogger(__name__)
1112

13+
logger.addHandler(logging.NullHandler())
14+
1215

1316

1417
# Long is no longer a thing in python3.x
@@ -33,16 +36,7 @@ class ProtocolBase(collections.MutableMapping):
3336
"""
3437
__propinfo__ = {}
3538
__required__ = set()
36-
37-
__SCHEMA_TYPES__ = {
38-
'array': list,
39-
'boolean': bool,
40-
'integer': int,
41-
'number': (float, int, long),
42-
'null': type(None),
43-
'string': six.string_types,
44-
'object': dict
45-
}
39+
__object_attr_list__ = set(["_properties", "_extended_properties"])
4640

4741
def as_dict(self):
4842
""" Return a dictionary containing the current values
@@ -55,8 +49,10 @@ def as_dict(self):
5549
for prop in self:
5650
propval = getattr(self, prop)
5751

58-
if isinstance(propval, list):
59-
out[prop] = [getattr(x, 'as_dict', lambda :x)() for x in propval]
52+
if hasattr(propval, 'for_json'):
53+
out[prop] = propval.for_json()
54+
elif isinstance(propval, list):
55+
out[prop] = [getattr(x, 'for_json', lambda:x)() for x in propval]
6056
elif isinstance(propval, (ProtocolBase, LiteralValue)):
6157
out[prop] = propval.as_dict()
6258
elif propval is not None:
@@ -145,7 +141,7 @@ def __new__(cls, **props):
145141
else: # We got nothing
146142
raise validators.ValidationError(
147143
"Unable to instantiate any valid types: \n"
148-
"\n".join("{0}: {1}".format(k, e) for k, e in validation_errors)
144+
"".join("{0}: {1}\n".format(k, e) for k, e in validation_errors)
149145
)
150146

151147
return obj
@@ -165,38 +161,29 @@ def __init__(self, **props):
165161
import sys
166162
raise six.reraise(type(e), type(e)(str(e) + " \nwhile setting '{0}' in {1}".format(
167163
prop, self.__class__.__name__)), sys.exc_info()[2])
168-
164+
if getattr(self, '__strict__', None):
165+
self.validate()
169166
#if len(props) > 0:
170167
# self.validate()
171168

172169
def __setattr__(self, name, val):
173-
if name.startswith("_"):
170+
if name in self.__object_attr_list__:
174171
object.__setattr__(self, name, val)
175172
elif name in self.__propinfo__:
176173
# If its in __propinfo__, then it actually has a property defined.
177174
# The property does special validation, so we actually need to
178175
# run its setter. We get it from the class definition and call
179176
# it directly. XXX Heinous.
180-
prop = self.__class__.__dict__[self.__prop_names__[name]]
177+
prop = getattr(self.__class__, self.__prop_names__[name])
181178
prop.fset(self, val)
182179
else:
183180
# This is an additional property of some kind
184-
typ = getattr(self, '__extensible__', None)
185-
if typ is False:
181+
try:
182+
val = self.__extensible__.instantiate(name, val)
183+
except Exception as e:
186184
raise validators.ValidationError(
187-
"Attempted to set unknown property '{0}', "
188-
"but 'additionalProperties' is false.".format(name))
189-
if typ is True:
190-
# There is no type defined, so just make it a basic literal
191-
# Pick the type based on the type of the values
192-
valtype = [k for k, t in six.iteritems(self.__SCHEMA_TYPES__)
193-
if t is not None and isinstance(val, t)]
194-
valtype = valtype[0]
195-
val = MakeLiteral(name, valtype, val)
196-
elif isinstance(typ, type) and getattr(typ, 'isLiteralClass', None) is True:
197-
val = typ(val)
198-
elif isinstance(typ, type) and util.safe_issubclass(typ, ProtocolBase):
199-
val = typ(**util.coerce_for_expansion(val))
185+
"Attempted to set unknown property '{0}': {1} "
186+
.format(name, e))
200187

201188
self._extended_properties[name] = val
202189

@@ -245,17 +232,17 @@ def serialize(self):
245232

246233
def validate(self):
247234
""" Applies all defined validation to the current
248-
state of the object, and raises an error if
235+
state of the object, and raises an error if
249236
they are not all met.
250-
237+
251238
Raises:
252239
ValidationError: if validations do not pass
253240
"""
254241

255242
propname = lambda x: self.__prop_names__[x]
256243
missing = [x for x in self.__required__
257-
if propname(x) not in self._properties
258-
or self._properties[propname(x)] is None]
244+
if propname(x) not in self._properties or
245+
self._properties[propname(x)] is None]
259246

260247
if len(missing) > 0:
261248
raise validators.ValidationError(
@@ -281,13 +268,14 @@ def validate(self):
281268

282269
return True
283270

271+
284272
def MakeLiteral(name, typ, value, **properties):
285-
properties.update({'type': typ})
286-
klass = type(str(name), tuple((LiteralValue,)), {
287-
'__propinfo__': { '__literal__': properties}
288-
})
273+
properties.update({'type': typ})
274+
klass = type(str(name), tuple((LiteralValue,)), {
275+
'__propinfo__': {'__literal__': properties}
276+
})
289277

290-
return klass(value)
278+
return klass(value)
291279

292280

293281
class TypeProxy(object):
@@ -304,6 +292,7 @@ def __call__(self, *a, **kw):
304292
self.__class__, klass))
305293
try:
306294
obj = klass(*a, **kw)
295+
obj.validate()
307296
except TypeError as e:
308297
validation_errors.append((klass, e))
309298
except validators.ValidationError as e:
@@ -314,7 +303,7 @@ def __call__(self, *a, **kw):
314303
else: # We got nothing
315304
raise validators.ValidationError(
316305
"Unable to instantiate any valid types: \n"
317-
"\n".join("{0}: {1}".format(k, e) for k, e in validation_errors)
306+
"".join("{0}: {1}\n".format(k, e) for k, e in validation_errors)
318307
)
319308

320309

@@ -355,6 +344,9 @@ def __repr__(self):
355344
str(self._value)
356345
)
357346

347+
def __str__(self):
348+
return str(self._value)
349+
358350
def validate(self):
359351
info = self.propinfo('__literal__')
360352

@@ -364,16 +356,14 @@ def validate(self):
364356
if validator is not None:
365357
validator(paramval, self._value, info)
366358

359+
def __eq__(self, other):
360+
return self._value == other
361+
362+
def __hash__(self):
363+
return hash(self._value)
367364

368-
def __cmp__(self, other):
369-
if isinstance(other, six.integer_types):
370-
return cmp(int(self), other)
371-
elif isinstance(other, six.string_types):
372-
return cmp(str(self), other)
373-
elif isinstance(other, float):
374-
return cmp(float(self), other)
375-
else:
376-
return cmp(id(self), id(other))
365+
def __lt__(self, other):
366+
return self._value < other
377367

378368
def __int__(self):
379369
return int(self._value)
@@ -418,7 +408,7 @@ def construct(self, uri, *args, **kw):
418408
logger.debug(util.lazy_format("Constructed {0}", ret))
419409
return ret
420410

421-
def _construct(self, uri, clsdata, parent=(ProtocolBase,)):
411+
def _construct(self, uri, clsdata, parent=(ProtocolBase,),**kw):
422412

423413
if 'anyOf' in clsdata:
424414
raise NotImplementedError(
@@ -437,7 +427,7 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,)):
437427
self.resolved[uri] = self._build_object(
438428
uri,
439429
clsdata,
440-
parents)
430+
parents,**kw)
441431
return self.resolved[uri]
442432

443433
elif '$ref' in clsdata:
@@ -473,13 +463,25 @@ def _construct(self, uri, clsdata, parent=(ProtocolBase,)):
473463
**clsdata_copy)
474464
return self.resolved[uri]
475465

466+
elif isinstance(clsdata.get('type'), list):
467+
types = []
468+
for i, item_detail in enumerate(clsdata['type']):
469+
subdata = {k: v for k, v in six.iteritems(clsdata) if k != 'type'}
470+
subdata['type'] = item_detail
471+
types.append(self._build_literal(
472+
uri + "_%s" % i,
473+
subdata))
474+
475+
self.resolved[uri] = TypeProxy(types)
476+
return self.resolved[uri]
477+
476478
elif (clsdata.get('type', None) == 'object' or
477479
clsdata.get('properties', None) is not None or
478480
clsdata.get('additionalProperties', False)):
479481
self.resolved[uri] = self._build_object(
480482
uri,
481483
clsdata,
482-
parent)
484+
parent,**kw)
483485
return self.resolved[uri]
484486
elif clsdata.get('type') in ('integer', 'number', 'string', 'boolean', 'null'):
485487
self.resolved[uri] = self._build_literal(
@@ -513,7 +515,7 @@ def _build_literal(self, nm, clsdata):
513515

514516
return cls
515517

516-
def _build_object(self, nm, clsdata, parents):
518+
def _build_object(self, nm, clsdata, parents,**kw):
517519
logger.debug(util.lazy_format("Building object {0}", nm))
518520

519521
props = {}
@@ -640,28 +642,10 @@ def _build_object(self, nm, clsdata, parents):
640642
# Need a validation to check that it meets one of them
641643
props['__validation__'] = {'type': klasses}
642644

643-
props['__extensible__'] = True
644-
if 'additionalProperties' in clsdata:
645-
addlProp = clsdata['additionalProperties']
646-
647-
if addlProp is False:
648-
props['__extensible__'] = False
649-
elif addlProp is True:
650-
props['__extensible__'] = True
651-
else:
652-
if '$ref' in addlProp:
653-
refs = self.resolve_classes([addlProp])
654-
else:
655-
uri = "{0}/{1}_{2}".format(nm,
656-
"<additionalProperties>", "<anonymous>")
657-
self.resolved[uri] = self.construct(
658-
uri,
659-
addlProp,
660-
(ProtocolBase,))
661-
refs = [self.resolved[uri]]
662-
663-
props['__extensible__'] = refs[0]
664-
645+
props['__extensible__'] = pattern_properties.ExtensibleValidator(
646+
nm,
647+
clsdata,
648+
self)
665649

666650
props['__prop_names__'] = name_translation
667651

@@ -679,7 +663,8 @@ def _build_object(self, nm, clsdata, parents):
679663
.format(nm, invalid_requires))
680664

681665
props['__required__'] = required
682-
666+
if required and kw.get("strict"):
667+
props['__strict__'] = True
683668
cls = type(str(nm.split('/')[-1]), tuple(parents), props)
684669

685670
return cls
@@ -703,7 +688,9 @@ def setprop(self, val):
703688
if not isinstance(typ, dict):
704689
type_checks.append(typ)
705690
continue
706-
typ = ProtocolBase.__SCHEMA_TYPES__[typ['type']]
691+
typ = next(t
692+
for n, t in validators.SCHEMA_TYPE_MAPPING
693+
if typ['type'] == t)
707694
if typ is None:
708695
typ = type(None)
709696
if isinstance(typ, (list, tuple)):
@@ -759,6 +746,11 @@ def setprop(self, val):
759746
instance = info['validator'](val)
760747
val = instance.validate()
761748

749+
elif util.safe_issubclass(info['type'], validators.ArrayValidator):
750+
# An array type may have already been converted into an ArrayValidator
751+
instance = info['type'](val)
752+
val = instance.validate()
753+
762754
elif getattr(info['type'], 'isLiteralClass', False) is True:
763755
if not isinstance(val, info['type']):
764756
validator = info['type'](val)
@@ -769,12 +761,16 @@ def setprop(self, val):
769761
val = info['type'](**util.coerce_for_expansion(val))
770762

771763
val.validate()
764+
765+
elif isinstance(info['type'], TypeProxy):
766+
val = info['type'](val)
767+
772768
elif info['type'] is None:
773769
# This is the null value
774770
if val is not None:
775771
raise validators.ValidationError(
776772
"None is only valid value for null")
777-
773+
778774
else:
779775
raise TypeError("Unknown object type: '{0}'".format(info['type']))
780776

0 commit comments

Comments
 (0)