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

1import json 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

2from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

3 

4from pydantic.json import pydantic_encoder 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

5from pydantic.utils import Representation 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

6 

7if TYPE_CHECKING: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

8 from typing_extensions import TypedDict 

9 

10 from pydantic.config import BaseConfig 

11 from pydantic.types import ModelOrDc 

12 from pydantic.typing import ReprArgs 

13 

14 Loc = Tuple[Union[int, str], ...] 

15 

16 class _ErrorDictRequired(TypedDict): 

17 loc: Loc 

18 msg: str 

19 type: str 

20 

21 class ErrorDict(_ErrorDictRequired, total=False): 

22 ctx: Dict[str, Any] 

23 

24 

25__all__ = 'ErrorWrapper', 'ValidationError' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

26 

27 

28class ErrorWrapper(Representation): 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

29 __slots__ = 'exc', '_loc' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

30 

31 def __init__(self, exc: Exception, loc: Union[str, 'Loc']) -> None: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

32 self.exc = exc 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

33 self._loc = loc 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

34 

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

40 

41 def __repr_args__(self) -> 'ReprArgs': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

42 return [('exc', self.exc), ('loc', self.loc_tuple())] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

43 

44 

45# ErrorList is something like Union[List[Union[List[ErrorWrapper], ErrorWrapper]], ErrorWrapper] 

46# but recursive, therefore just use: 

47ErrorList = Union[Sequence[Any], ErrorWrapper] 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

48 

49 

50class ValidationError(Representation, ValueError): 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

51 __slots__ = 'raw_errors', 'model', '_error_cache' 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

52 

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

57 

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

66 

67 def json(self, *, indent: Union[None, int, str] = 2) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

68 return json.dumps(self.errors(), indent=indent, default=pydantic_encoder) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

69 

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 ) 

77 

78 def __repr_args__(self) -> 'ReprArgs': 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

79 return [('model', self.model.__name__), ('errors', self.errors())] 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

80 

81 

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

84 

85 

86def _display_error_loc(error: 'ErrorDict') -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

87 return ' -> '.join(str(e) for e in error['loc']) 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

88 

89 

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

97 

98 

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

108 

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

117 

118 

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

127 

128 d: 'ErrorDict' = {'loc': loc, 'msg': msg, 'type': type_} 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

129 

130 if ctx: 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

131 d['ctx'] = ctx 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

132 

133 return d 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

134 

135 

136_EXC_TYPE_CACHE: Dict[Type[Exception], str] = {} 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

137 

138 

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

147 

148 

149def _get_exc_type(cls: Type[Exception]) -> str: 1JKabcdefghijLMklmnopqrstuvwxPQRSTUyNOzABCDEFGHI

150 if issubclass(cls, AssertionError): 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

151 return 'assertion_error' 1JKabcdefghijLMklmnopqrstuvwxyNOzABCDEFGHI

152 

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

157 

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