Skip to content

Commit

Permalink
Merge 9430393 into e6af85c
Browse files Browse the repository at this point in the history
  • Loading branch information
lkraider committed May 8, 2017
2 parents e6af85c + 9430393 commit 907277c
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 33 deletions.
79 changes: 46 additions & 33 deletions schematics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __set__(self, instance, value):
"""
field = instance._fields[self.name]
value = field.pre_setattr(value)
instance._data[self.name] = value
instance._data.converted[self.name] = value

def __delete__(self, instance):
"""
Expand Down Expand Up @@ -143,22 +143,32 @@ def _read_options(mcs, name, bases, attrs, options_members):

class ModelDict(ChainMap):

__slots__ = ['_raw', '__valid', '_valid']
__slots__ = ['_unsafe', '_converted', '__valid', '_valid']

def __init__(self, raw=None, valid=None):
self._raw = raw if raw is not None else {}
def __init__(self, unsafe=None, converted=None, valid=None):
self._unsafe = unsafe if unsafe is not None else {}
self._converted = converted if converted is not None else {}
self.__valid = valid if valid is not None else {}
self._valid = MappingProxyType(self.__valid)
super(ModelDict, self).__init__(self._raw, self._valid)
super(ModelDict, self).__init__(self._unsafe, self._converted, self._valid)

@property
def raw(self):
return self._raw
def unsafe(self):
return self._unsafe

@raw.setter
def raw(self, value):
self._raw = value
self.maps[0] = self._raw
@unsafe.setter
def unsafe(self, value):
self._unsafe = value
self.maps[0] = self._unsafe

@property
def converted(self):
return self._converted

@converted.setter
def converted(self, value):
self._converted = value
self.maps[1] = self._converted

@property
def valid(self):
Expand All @@ -167,20 +177,16 @@ def valid(self):
@valid.setter
def valid(self, value):
self._valid = MappingProxyType(value)
self.maps[1] = self._valid
self.maps[2] = self._valid

def __delitem__(self, key):
did_delete = False
try:
del self.__valid[key]
did_delete = True
except KeyError:
pass
try:
del self._raw[key]
did_delete = True
except KeyError:
pass
for data in [self.__valid, self._converted, self._unsafe]:
try:
del data[key]
did_delete = True
except KeyError:
pass
if not did_delete:
raise KeyError(key)

Expand Down Expand Up @@ -210,18 +216,20 @@ class Model(object):

def __init__(self, raw_data=None, trusted_data=None, deserialize_mapping=None,
init=True, partial=True, strict=True, validate=False, app_data=None,
**kwargs):

self._data = ModelDict(valid=trusted_data)

lazy=False, **kwargs):
kwargs.setdefault('init_values', init)
kwargs.setdefault('apply_defaults', init)

if lazy:
self._data = ModelDict(unsafe=raw_data, valid=trusted_data)
return

self._data = ModelDict(valid=trusted_data)
data = self._convert(raw_data,
trusted_data=trusted_data, mapping=deserialize_mapping,
partial=partial, strict=strict, validate=validate, new=True,
app_data=app_data, **kwargs)
self._data.raw = data
self._data.converted = data
if validate:
self.validate()

Expand All @@ -239,7 +247,7 @@ def validate(self, partial=False, convert=True, app_data=None, **kwargs):
are known to have the right datatypes (e.g., when validating immediately
after the initial import). Default: True
"""
if not self._data.raw and partial:
if not self._data.converted and partial:
return # no new input data to validate
try:
data = self._convert(validate=True,
Expand All @@ -251,7 +259,7 @@ def validate(self, partial=False, convert=True, app_data=None, **kwargs):
self._data.valid = valid
raise
finally:
self._data.raw = {}
self._data.converted = {}

def import_data(self, raw_data, recursive=False, **kwargs):
"""
Expand All @@ -261,7 +269,7 @@ def import_data(self, raw_data, recursive=False, **kwargs):
The data to be imported.
"""
data = self._convert(raw_data, trusted_data=dict(self), recursive=recursive, **kwargs)
self._data.update(data)
self._data.converted.update(data)
if kwargs.get('validate'):
self.validate(convert=False)
return self
Expand All @@ -274,9 +282,14 @@ def _convert(self, raw_data=None, context=None, **kwargs):
:param raw_data:
New data to be imported and converted
"""
raw_data = dict(raw_data) if raw_data else self._data.raw
raw_data = dict(raw_data) if raw_data else self._data.converted
kwargs['trusted_data'] = kwargs.get('trusted_data') or {}
kwargs['convert'] = getattr(context, 'convert', kwargs.get('convert', True))
if self._data.unsafe:
self._data.unsafe.update(raw_data)
raw_data = self._data.unsafe
self._data.unsafe = {}
kwargs['convert'] = True
should_validate = getattr(context, 'validate', kwargs.get('validate', False))
func = validate if should_validate else convert
return func(self._schema, self, raw_data=raw_data, oo=True, context=context, **kwargs)
Expand All @@ -292,13 +305,13 @@ def to_primitive(self, role=None, app_data=None, **kwargs):
return to_primitive(self._schema, self, role=role, app_data=app_data, **kwargs)

def serialize(self, *args, **kwargs):
raw_data = self._data.raw
raw_data = self._data.converted
try:
self.validate(apply_defaults=True)
except DataError:
pass
data = self.to_primitive(*args, **kwargs)
self._data.raw = raw_data
self._data.converted = raw_data
return data

def atoms(self):
Expand Down
21 changes: 21 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,24 @@ def test_builtin_validation_exception():
with pytest.raises(ValueError):
raise ValidationError('message')


def test_lazy_conversion_exception():

class Foo(Model):
bar = BooleanType()

foo = Foo(dict(bar='a'), lazy=True)
with pytest.raises(DataError):
foo.validate()


def test_lazy_conversion_and_validation_exception_bundling():

class Signup(Model):
name = StringType(max_length=3)
call_me = BooleanType()

user = Signup({'name': u'Arthur', 'call_me': 'Dent'}, lazy=True)
with pytest.raises(DataError) as exception:
user.validate()
assert set(('name', 'call_me')).issubset(exception.value.errors)

0 comments on commit 907277c

Please sign in to comment.