Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform validation on assignment to attribute #94

Merged
merged 4 commits into from
Oct 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ Options:
:allow_extra: whether or not too allow (and include on the model) any extra values in input data (default: ``False``)
:allow_mutation: whether or not models are faux-immutable, e.g. __setattr__ fails (default: ``True``)
:fields: extra information on each field, currently just "alias" is allowed (default: ``None``)
:validate_assignment: whether to perform validation on assignment to attributes or not (default: ``False``)

.. literalinclude:: examples/config.py

Expand Down
10 changes: 9 additions & 1 deletion pydantic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class BaseConfig:
allow_extra = False
allow_mutation = True
fields = {}
validate_assignment = False


def inherit_config(self_config, parent_config) -> BaseConfig:
Expand Down Expand Up @@ -111,7 +112,14 @@ def __setattr__(self, name, value):
raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"')
elif not self.config.allow_mutation:
raise TypeError(f'"{self.__class__.__name__}" is immutable and does not support item assignment')
self.__values__[name] = value
elif self.config.validate_assignment:
value_, error_ = self.fields[name].validate(value, self.values(exclude={name}))
if error_:
raise ValidationError({name: error_})
else:
self.__values__[name] = value_
else:
self.__values__[name] = value

def __getstate__(self):
return self.__values__
Expand Down
34 changes: 33 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from pydantic import BaseModel, ConfigError, NoneBytes, NoneStr, Required, ValidationError
from pydantic import BaseModel, ConfigError, NoneBytes, NoneStr, Required, ValidationError, constr
from pydantic.exceptions import pretty_errors


Expand Down Expand Up @@ -337,3 +337,35 @@ class Config:
with pytest.raises(ValueError) as exc_info:
m.b = 11
assert '"TestModel" object has no field "b"' in str(exc_info)


class ValidateAssignmentModel(BaseModel):
a: int = 2
b: constr(min_length=1)

class Config:
validate_assignment = True


def test_validating_assignment_pass():
p = ValidateAssignmentModel(a=5, b='hello')
p.a = 2
assert p.a == 2
assert p.values() == {'a': 2, 'b': 'hello'}
p.b = 'hi'
assert p.b == 'hi'
assert p.values() == {'a': 2, 'b': 'hi'}


def test_validating_assignment_fail():
p = ValidateAssignmentModel(a=5, b='hello')
with pytest.raises(ValidationError) as exc_info:
p.a = 'b'
assert """error validating input
a:
invalid literal for int() with base 10: 'b' (error_type=ValueError track=int)""" == str(exc_info.value)
with pytest.raises(ValidationError) as exc_info:
p.b = ''
assert """error validating input
b:
length less than minimum allowed: 1 (error_type=ValueError track=ConstrainedStrValue)""" == str(exc_info.value)