Skip to content

Commit

Permalink
Merge pull request #317 from schematics/development
Browse files Browse the repository at this point in the history
Release 1.1.0
  • Loading branch information
kracekumar committed Jul 12, 2015
2 parents 1e49aab + e205bfc commit 7db1331
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 19 deletions.
11 changes: 11 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
1.1.0 / 2015-07-12
==================
* [Feature] (`#303 <https://github.com/schematics/schematics/pull/303>`_) fix ListType, validate_items adds to errors list just field name without...
* [Feature] (`#304 <https://github.com/schematics/schematics/pull/304>`_) Include Partial Data when Raising ModelConversionError
* [Feature] (`#305 <https://github.com/schematics/schematics/pull/305>`_) Updated domain verifications to fit to RFC/working standards
* [Feature] (`#308 <https://github.com/schematics/schematics/pull/308>`_) Grennady ordered validation
* [Feature] (`#309 <https://github.com/schematics/schematics/pull/309>`_) improves date_time_type error message for custom formats
* [Feature] (`#310 <https://github.com/schematics/schematics/pull/310>`_) accept optional 'Z' suffix for UTC date_time_type format
* [Feature] (`#311 <https://github.com/schematics/schematics/pull/311>`_) Remove commented lines from models.py
* [Feature] (`#230 <https://github.com/schematics/schematics/pull/230>`_) Message normalization

1.0.4 / 2015-04-13
==================
* [Example] (`#286 <https://github.com/schematics/schematics/pull/286>`_) Add schematics usage with Django
Expand Down
2 changes: 1 addition & 1 deletion schematics/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

version_info = ('1', '0', '4')
version_info = ('1', '1', '0')

__version__ = '{0}.{1}.{2}'.format(*version_info)
4 changes: 3 additions & 1 deletion schematics/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class ConversionError(BaseError, TypeError):


class ModelConversionError(ConversionError):
pass
def __init__(self, messages, partial_data=None):
super(ModelConversionError, self).__init__(messages)
self.partial_data = partial_data


class ValidationError(BaseError, ValueError):
Expand Down
7 changes: 0 additions & 7 deletions schematics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,6 @@ def _read_options(mcs, name, bases, attrs):
def fields(cls):
return cls._fields

# def __iter__(self):
# return itertools.chain(
# self.fields.iteritems(),
# self._unbound_fields.iteritems(),
# self._unbound_serializables.iteritems()
# )

@add_metaclass(ModelMeta)
class Model(object):
Expand All @@ -237,7 +231,6 @@ class Model(object):
possible to convert the raw data into richer Python constructs.
"""

#__metaclass__ = ModelMeta
__optionsclass__ = ModelOptions

def __init__(self, raw_data=None, deserialize_mapping=None, strict=True):
Expand Down
2 changes: 1 addition & 1 deletion schematics/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def import_loop(cls, instance_or_dict, field_converter, context=None,
errors[serialized_field_name] = exc.messages

if errors:
raise ModelConversionError(errors)
raise ModelConversionError(errors, data)

return data

Expand Down
21 changes: 16 additions & 5 deletions schematics/types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ class URLType(StringType):

URL_REGEX = re.compile(
r'^https?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,2000}[A-Z0-9])?\.)+[A-Z]{2,63}\.?|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
Expand Down Expand Up @@ -427,7 +427,7 @@ class EmailType(StringType):
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016'
r'-\177])*"'
# domain
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,32}\.?$',
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,2000}[A-Z0-9])?\.)+[A-Z]{2,63}\.?$',
re.IGNORECASE
)

Expand Down Expand Up @@ -678,16 +678,21 @@ class DateTimeType(BaseType):
:param formats:
A value or list of values suitable for ``datetime.datetime.strptime``
parsing. Default: `('%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S')`
parsing. Default: `('%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S',
'%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%SZ')`
:param serialized_format:
The output format suitable for Python ``strftime``. Default: ``'%Y-%m-%dT%H:%M:%S.%f'``
"""

DEFAULT_FORMATS = ('%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S')
DEFAULT_FORMATS = (
'%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S',
'%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%SZ',
)
SERIALIZED_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'

MESSAGES = {
'parse_formats': u'Could not parse {0}. Valid formats: {1}',
'parse': u"Could not parse {0}. Should be ISO8601.",
}

Expand Down Expand Up @@ -725,7 +730,13 @@ def to_native(self, value, context=None):
return datetime.datetime.strptime(value, fmt)
except (ValueError, TypeError):
continue
raise ConversionError(self.messages['parse'].format(value))
if self.formats == self.DEFAULT_FORMATS:
message = self.messages['parse'].format(value)
else:
message = self.messages['parse_formats'].format(
value, ", ".join(self.formats)
)
raise ConversionError(message)

def to_primitive(self, value, context=None):
if callable(self.serialized_format):
Expand Down
3 changes: 1 addition & 2 deletions schematics/types/compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,7 @@ def validate_items(self, items):
try:
self.field.validate(item)
except ValidationError as exc:
errors += exc.messages

errors.append(exc.messages)
if errors:
raise ValidationError(errors)

Expand Down
5 changes: 3 additions & 2 deletions schematics/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ def _validate_model(cls, data):
"""
errors = {}
invalid_fields = []
for field_name, value in data.items():
if field_name in cls._validator_functions:
for field_name, field in cls._fields.iteritems():
if field_name in cls._validator_functions and field_name in data:
value = data[field_name]
try:
context = data
cls._validator_functions[field_name](cls, context, value)
Expand Down
14 changes: 14 additions & 0 deletions tests/test_list_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,20 @@ class Card(Model):
assert errors['users'] == [u'This field is required.']


def test_list_model_field_exception_with_full_message():
class User(Model):
name = StringType(max_length=1)

class Group(Model):
users = ListType(ModelType(User))

g = Group({'users': [{'name': "ToLongName"}]})

with pytest.raises(ValidationError) as exception:
g.validate()
assert exception.value.messages == {'users': [{'name': ['String value is too long.']}]}


def test_stop_validation():
def raiser(x):
raise StopValidation({'something': 'bad'})
Expand Down
17 changes: 17 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,23 @@ class User(Model):
}


def test_returns_partial_data_with_conversion_errors():
class User(Model):
name = StringType(required=True)
age = IntType(required=True)
account_level = IntType()

with pytest.raises(ModelConversionError) as exception:
User({"name": "Jóhann", "age": "100 years", "account_level": "3"})

partial_data = exception.value.partial_data

assert partial_data == {
"name": u"Jóhann",
"account_level": 3,
}


def test_field_default():
class User(Model):
name = StringType(default=u'Doggy')
Expand Down
24 changes: 24 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ def validate_call_me(self, data, value):
Signup({'name': u'Brad', 'call_me': True}).validate()


def test_multi_key_validation_fields_order():
class Signup(Model):
name = StringType()
call_me = BooleanType(default=False)

def validate_name(self, data, value):
if data['name'] == u'Brad':
value = u'Joe'
data['name'] = value
return value
return value

def validate_call_me(self, data, value):
if data['name'] == u'Joe':
raise ValidationError(u"Don't try to decept me! You're Joe!")
return value

Signup({'name': u'Tom'}).validate()

with pytest.raises(ValidationError):
Signup({'name': u'Brad'}).validate()



def test_basic_error():
class School(Model):
name = StringType(required=True)
Expand Down

0 comments on commit 7db1331

Please sign in to comment.