diff --git a/HISTORY.rst b/HISTORY.rst index a48109c..b94ba3c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- + +v0.32 (unreleased) +.................. +* add model name to ``ValidationError`` error message, #676 by @dmontagu + v0.31.1 (2019-07-31) .................... * fix json generation for ``EnumError``, #697 by @dmontagu @@ -20,6 +25,7 @@ v0.31 (2019-07-24) * add ``Config.keep_untouched`` for custom descriptors support, #679 by @MrMrRobat * use ``inspect.cleandoc`` internally to get model description, #657 by @tiangolo * add ``Color`` to schema generation, by @euri10 +* add documentation for Literal type, #651 by @dmontagu v0.30.1 (2019-07-15) .................... diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index a0d9ebc..06c4a48 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -44,7 +44,7 @@ def setattr_validate_assignment(self: 'DataclassType', name: str, value: Any) -> d.pop(name) value, error_ = self.__pydantic_model__.__fields__[name].validate(value, d, loc=name, cls=self.__class__) if error_: - raise ValidationError([error_]) + raise ValidationError([error_], type(self)) object.__setattr__(self, name, value) diff --git a/pydantic/error_wrappers.py b/pydantic/error_wrappers.py index daf0e53..f483fa6 100644 --- a/pydantic/error_wrappers.py +++ b/pydantic/error_wrappers.py @@ -49,10 +49,11 @@ ErrorList = Union[Sequence[Any], ErrorWrapper] class ValidationError(ValueError): - __slots__ = ('raw_errors',) + __slots__ = ('raw_errors', 'model') - def __init__(self, errors: Sequence[ErrorList]) -> None: + def __init__(self, errors: Sequence[ErrorList], model: Type[Any]) -> None: self.raw_errors = errors + self.model = model @lru_cache() def errors(self) -> List[Dict[str, Any]]: @@ -64,7 +65,10 @@ class ValidationError(ValueError): def __str__(self) -> str: errors = self.errors() no_errors = len(errors) - return f'{no_errors} validation error{"" if no_errors == 1 else "s"}\n{display_errors(errors)}' + return ( + f'{no_errors} validation error{"" if no_errors == 1 else "s"} for {self.model.__name__}\n' + f'{display_errors(errors)}' + ) def display_errors(errors: List[Dict[str, Any]]) -> str: diff --git a/pydantic/main.py b/pydantic/main.py index 624ba2a..d59736d 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -293,7 +293,7 @@ class BaseModel(metaclass=MetaModel): elif self.__config__.validate_assignment: value_, error_ = self.fields[name].validate(value, self.dict(exclude={name}), loc=name) if error_: - raise ValidationError([error_]) + raise ValidationError([error_], type(self)) else: self.__values__[name] = value_ self.__fields_set__.add(name) @@ -372,7 +372,7 @@ class BaseModel(metaclass=MetaModel): obj = dict(obj) except (TypeError, ValueError) as e: exc = TypeError(f'{cls.__name__} expected dict not {type(obj).__name__}') - raise ValidationError([ErrorWrapper(exc, loc='__obj__')]) from e + raise ValidationError([ErrorWrapper(exc, loc='__obj__')], cls) from e return cls(**obj) @classmethod @@ -390,7 +390,7 @@ class BaseModel(metaclass=MetaModel): b, proto=proto, content_type=content_type, encoding=encoding, allow_pickle=allow_pickle ) except (ValueError, TypeError, UnicodeDecodeError) as e: - raise ValidationError([ErrorWrapper(e, loc='__obj__')]) + raise ValidationError([ErrorWrapper(e, loc='__obj__')], cls) return cls.parse_obj(obj) @classmethod @@ -783,9 +783,15 @@ def validate_model( # noqa: C901 (ignore complexity) for f in sorted(extra): errors.append(ErrorWrapper(ExtraError(), loc=f, config=config)) - if not raise_exc: - return values, fields_set, ValidationError(errors) if errors else None - + err = None if errors: - raise ValidationError(errors) + model_type = model if isinstance(model, type) else type(model) + err = ValidationError(errors, model_type) + + if not raise_exc: + return values, fields_set, err + + if err: + raise err + return values, fields_set, None diff --git a/tests/test_error_wrappers.py b/tests/test_error_wrappers.py index 9092d03..fc7bfce 100644 --- a/tests/test_error_wrappers.py +++ b/tests/test_error_wrappers.py @@ -135,7 +135,7 @@ from pydantic.error_wrappers import ValidationError, flatten_errors, get_exc_typ ( '__str__', """\ -11 validation errors +11 validation errors for Model a value is not a valid integer (type=type_error.integer) b -> x @@ -227,7 +227,7 @@ def test_single_error(): Model(x='x') expected = """\ -1 validation error +1 validation error for Model x value is not a valid integer (type=type_error.integer)""" assert str(exc_info.value) == expected @@ -239,7 +239,7 @@ x assert ( str(exc_info.value) == """\ -1 validation error +1 validation error for Model x field required (type=value_error.missing)""" ) @@ -261,3 +261,19 @@ def test_nested_error(): expected = [{'loc': ('data1', 0, 'data2', 0, 'x'), 'msg': 'field required', 'type': 'value_error.missing'}] assert exc_info.value.errors() == expected + + +def test_validate_assignment_error(): + class Model(BaseModel): + x: int + + class Config: + validate_assignment = True + + model = Model(x=1) + with pytest.raises(ValidationError) as exc_info: + model.x = 'a' + assert ( + str(exc_info.value) + == '1 validation error for Model\nx\n value is not a valid integer (type=type_error.integer)' + )