Coverage for pydantic/error_wrappers.py: 100.00%
88 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
1import json 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
2from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
4from pydantic.json import pydantic_encoder 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
5from pydantic.utils import Representation 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
7if TYPE_CHECKING: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
8 from typing_extensions import TypedDict
10 from pydantic.config import BaseConfig
11 from pydantic.types import ModelOrDc
12 from pydantic.typing import ReprArgs
14 Loc = Tuple[Union[int, str], ...]
16 class _ErrorDictRequired(TypedDict):
17 loc: Loc
18 msg: str
19 type: str
21 class ErrorDict(_ErrorDictRequired, total=False):
22 ctx: Dict[str, Any]
25__all__ = 'ErrorWrapper', 'ValidationError' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
28class ErrorWrapper(Representation): 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
29 __slots__ = 'exc', '_loc' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
31 def __init__(self, exc: Exception, loc: Union[str, 'Loc']) -> None: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
32 self.exc = exc 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
33 self._loc = loc 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
35 def loc_tuple(self) -> 'Loc': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
36 if isinstance(self._loc, tuple): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
37 return self._loc 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
38 else:
39 return (self._loc,) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
41 def __repr_args__(self) -> 'ReprArgs': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
42 return [('exc', self.exc), ('loc', self.loc_tuple())] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
45# ErrorList is something like Union[List[Union[List[ErrorWrapper], ErrorWrapper]], ErrorWrapper]
46# but recursive, therefore just use:
47ErrorList = Union[Sequence[Any], ErrorWrapper] 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
50class ValidationError(Representation, ValueError): 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
51 __slots__ = 'raw_errors', 'model', '_error_cache' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
53 def __init__(self, errors: Sequence[ErrorList], model: 'ModelOrDc') -> None: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
54 self.raw_errors = errors 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
55 self.model = model 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
56 self._error_cache: Optional[List['ErrorDict']] = None 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
58 def errors(self) -> List['ErrorDict']: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
59 if self._error_cache is None: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
60 try: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
61 config = self.model.__config__ # type: ignore 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
62 except AttributeError: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
63 config = self.model.__pydantic_model__.__config__ # type: ignore 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
64 self._error_cache = list(flatten_errors(self.raw_errors, config)) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
65 return self._error_cache 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
67 def json(self, *, indent: Union[None, int, str] = 2) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
68 return json.dumps(self.errors(), indent=indent, default=pydantic_encoder) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
70 def __str__(self) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
71 errors = self.errors() 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
72 no_errors = len(errors) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
73 return ( 1abcdefghijklmnopqrstuvwxyzABCDEFGHI
74 f'{no_errors} validation error{"" if no_errors == 1 else "s"} for {self.model.__name__}\n'
75 f'{display_errors(errors)}'
76 )
78 def __repr_args__(self) -> 'ReprArgs': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
79 return [('model', self.model.__name__), ('errors', self.errors())] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
82def display_errors(errors: List['ErrorDict']) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
83 return '\n'.join(f'{_display_error_loc(e)}\n {e["msg"]} ({_display_error_type_and_ctx(e)})' for e in errors) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
86def _display_error_loc(error: 'ErrorDict') -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
87 return ' -> '.join(str(e) for e in error['loc']) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
90def _display_error_type_and_ctx(error: 'ErrorDict') -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
91 t = 'type=' + error['type'] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
92 ctx = error.get('ctx') 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
93 if ctx: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
94 return t + ''.join(f'; {k}={v}' for k, v in ctx.items()) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
95 else:
96 return t 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
99def flatten_errors( 1abcdefghijklmnopqrstuvwxPQRSTUyzABCDEFGHI
100 errors: Sequence[Any], config: Type['BaseConfig'], loc: Optional['Loc'] = None
101) -> Generator['ErrorDict', None, None]:
102 for error in errors: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
103 if isinstance(error, ErrorWrapper): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
104 if loc: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
105 error_loc = loc + error.loc_tuple() 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
106 else:
107 error_loc = error.loc_tuple() 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
109 if isinstance(error.exc, ValidationError): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
110 yield from flatten_errors(error.exc.raw_errors, config, error_loc) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
111 else:
112 yield error_dict(error.exc, config, error_loc) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
113 elif isinstance(error, list): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
114 yield from flatten_errors(error, config, loc=loc) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
115 else:
116 raise RuntimeError(f'Unknown error object: {error}') 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
119def error_dict(exc: Exception, config: Type['BaseConfig'], loc: 'Loc') -> 'ErrorDict': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
120 type_ = get_exc_type(exc.__class__) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
121 msg_template = config.error_msg_templates.get(type_) or getattr(exc, 'msg_template', None) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
122 ctx = exc.__dict__ 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
123 if msg_template: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
124 msg = msg_template.format(**ctx) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
125 else:
126 msg = str(exc) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
128 d: 'ErrorDict' = {'loc': loc, 'msg': msg, 'type': type_} 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
130 if ctx: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
131 d['ctx'] = ctx 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
133 return d 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
136_EXC_TYPE_CACHE: Dict[Type[Exception], str] = {} 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
139def get_exc_type(cls: Type[Exception]) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
140 # slightly more efficient than using lru_cache since we don't need to worry about the cache filling up
141 try: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
142 return _EXC_TYPE_CACHE[cls] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
143 except KeyError: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
144 r = _get_exc_type(cls) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
145 _EXC_TYPE_CACHE[cls] = r 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
146 return r 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
149def _get_exc_type(cls: Type[Exception]) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI
150 if issubclass(cls, AssertionError): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
151 return 'assertion_error' 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
153 base_name = 'type_error' if issubclass(cls, TypeError) else 'value_error' 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
154 if cls in (TypeError, ValueError): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
155 # just TypeError or ValueError, no extra code
156 return base_name 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
158 # if it's not a TypeError or ValueError, we just take the lowercase of the exception name
159 # no chaining or snake case logic, use "code" for more complex error types.
160 code = getattr(cls, 'code', None) or cls.__name__.replace('Error', '').lower() 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI
161 return base_name + '.' + code 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI