Skip to content

Commit

Permalink
Added validation_error_cause to config. (#7626)
Browse files Browse the repository at this point in the history
  • Loading branch information
zakstucke committed Oct 27, 2023
1 parent 0ca1cf2 commit e4953e8
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pydantic/_internal/_config.py
Expand Up @@ -81,6 +81,7 @@ class ConfigWrapper:
json_schema_mode_override: Literal['validation', 'serialization', None]
coerce_numbers_to_str: bool
regex_engine: Literal['rust-regex', 'python-re']
validation_error_cause: bool

def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True):
if check:
Expand Down Expand Up @@ -178,6 +179,7 @@ def dict_not_none(**kwargs: Any) -> Any:
hide_input_in_errors=self.config_dict.get('hide_input_in_errors'),
coerce_numbers_to_str=self.config_dict.get('coerce_numbers_to_str'),
regex_engine=self.config_dict.get('regex_engine'),
validation_error_cause=self.config_dict.get('validation_error_cause'),
)
)
return core_config
Expand Down Expand Up @@ -251,6 +253,7 @@ def _context_manager() -> Iterator[None]:
json_schema_mode_override=None,
coerce_numbers_to_str=False,
regex_engine='rust-regex',
validation_error_cause=False,
)


Expand Down
11 changes: 11 additions & 0 deletions pydantic/config.py
Expand Up @@ -857,5 +857,16 @@ class Model(BaseModel):
```
"""

validation_error_cause: bool
"""
If `True`, python exceptions that were part of a validation failure will be shown as an exception group as a cause. Can be useful for debugging. Defaults to `False`.
Note:
Python 3.10 and older don't support exception groups natively. <=3.10, backport must be installed: `pip install exceptiongroup`.
Note:
The structure of validation errors are likely to change in future pydantic versions. Pydantic offers no guarantees about the structure of validation errors. Should be used for visual traceback debugging only.
"""


__getattr__ = getattr_migration(__name__)
29 changes: 29 additions & 0 deletions tests/test_config.py
Expand Up @@ -19,6 +19,7 @@
PydanticSchemaGenerationError,
ValidationError,
create_model,
field_validator,
validate_call,
)
from pydantic._internal._config import ConfigWrapper, config_defaults
Expand Down Expand Up @@ -538,6 +539,34 @@ def test_config_wrapper_match():
), 'ConfigDict and ConfigWrapper must have the same annotations (except ConfigWrapper.config_dict)'


@pytest.mark.skipif(sys.version_info < (3, 11), reason='requires backport pre 3.11, fully tested in pydantic core')
def test_config_validation_error_cause():
class Foo(BaseModel):
foo: int

@field_validator('foo')
def check_foo(cls, v):
assert v > 5, 'Must be greater than 5'

# Should be disabled by default:
with pytest.raises(ValidationError) as exc_info:
Foo(foo=4)
assert exc_info.value.__cause__ is None

Foo.model_config = ConfigDict(validation_error_cause=True)
Foo.model_rebuild(force=True)
with pytest.raises(ValidationError) as exc_info:
Foo(foo=4)
# Confirm python error attached as a cause, and error location specified in a note:
assert exc_info.value.__cause__ is not None
assert isinstance(exc_info.value.__cause__, ExceptionGroup)
assert len(exc_info.value.__cause__.exceptions) == 1
src_exc = exc_info.value.__cause__.exceptions[0]
assert repr(src_exc) == "AssertionError('Must be greater than 5\\nassert 4 > 5')"
assert len(src_exc.__notes__) == 1
assert src_exc.__notes__[0] == '\nPydantic: cause of loc: foo'


@pytest.mark.skipif(sys.version_info < (3, 10), reason='different on older versions')
def test_config_defaults_match():
localns = {'_GenerateSchema': GenerateSchema, 'GenerateSchema': GenerateSchema}
Expand Down

0 comments on commit e4953e8

Please sign in to comment.