Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cattrs/v.py: 13%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

47 statements  

1"""Cattrs validation.""" 

2 

3from typing import Callable, Union 

4 

5from .errors import ( 

6 ClassValidationError, 

7 ForbiddenExtraKeysError, 

8 IterableValidationError, 

9) 

10 

11__all__ = ["format_exception", "transform_error"] 

12 

13 

14def format_exception(exc: BaseException, type: Union[type, None]) -> str: 

15 """The default exception formatter, handling the most common exceptions. 

16 

17 The following exceptions are handled specially: 

18 

19 * `KeyErrors` (`required field missing`) 

20 * `ValueErrors` (`invalid value for type, expected <type>` or just `invalid value`) 

21 * `TypeErrors` (`invalid value for type, expected <type>` and a couple special 

22 cases for iterables) 

23 * `cattrs.ForbiddenExtraKeysError` 

24 * some `AttributeErrors` (special cased for structing mappings) 

25 """ 

26 if isinstance(exc, KeyError): 

27 res = "required field missing" 

28 elif isinstance(exc, ValueError): 

29 if type is not None: 

30 tn = type.__name__ if hasattr(type, "__name__") else repr(type) 

31 res = f"invalid value for type, expected {tn}" 

32 else: 

33 res = "invalid value" 

34 elif isinstance(exc, TypeError): 

35 if type is None: 

36 if exc.args[0].endswith("object is not iterable"): 

37 res = "invalid value for type, expected an iterable" 

38 else: 

39 res = f"invalid type ({exc})" 

40 else: 

41 tn = type.__name__ if hasattr(type, "__name__") else repr(type) 

42 res = f"invalid value for type, expected {tn}" 

43 elif isinstance(exc, ForbiddenExtraKeysError): 

44 res = f"extra fields found ({', '.join(exc.extra_fields)})" 

45 elif isinstance(exc, AttributeError) and exc.args[0].endswith( 

46 "object has no attribute 'items'" 

47 ): 

48 # This was supposed to be a mapping (and have .items()) but it something else. 

49 res = "expected a mapping" 

50 else: 

51 res = f"unknown error ({exc})" 

52 

53 return res 

54 

55 

56def transform_error( 

57 exc: Union[ClassValidationError, IterableValidationError, BaseException], 

58 path: str = "$", 

59 format_exception: Callable[ 

60 [BaseException, Union[type, None]], str 

61 ] = format_exception, 

62) -> list[str]: 

63 """Transform an exception into a list of error messages. 

64 

65 To get detailed error messages, the exception should be produced by a converter 

66 with `detailed_validation` set. 

67 

68 By default, the error messages are in the form of `{description} @ {path}`. 

69 

70 While traversing the exception and subexceptions, the path is formed: 

71 

72 * by appending `.{field_name}` for fields in classes 

73 * by appending `[{int}]` for indices in iterables, like lists 

74 * by appending `[{str}]` for keys in mappings, like dictionaries 

75 

76 :param exc: The exception to transform into error messages. 

77 :param path: The root path to use. 

78 :param format_exception: A callable to use to transform `Exceptions` into 

79 string descriptions of errors. 

80 

81 .. versionadded:: 23.1.0 

82 """ 

83 errors = [] 

84 if isinstance(exc, IterableValidationError): 

85 with_notes, without = exc.group_exceptions() 

86 for exc, note in with_notes: 

87 p = f"{path}[{note.index!r}]" 

88 if isinstance(exc, (ClassValidationError, IterableValidationError)): 

89 errors.extend(transform_error(exc, p, format_exception)) 

90 else: 

91 errors.append(f"{format_exception(exc, note.type)} @ {p}") 

92 for exc in without: 

93 errors.append(f"{format_exception(exc, None)} @ {path}") 

94 elif isinstance(exc, ClassValidationError): 

95 with_notes, without = exc.group_exceptions() 

96 for exc, note in with_notes: 

97 p = f"{path}.{note.name}" 

98 if isinstance(exc, (ClassValidationError, IterableValidationError)): 

99 errors.extend(transform_error(exc, p, format_exception)) 

100 else: 

101 errors.append(f"{format_exception(exc, note.type)} @ {p}") 

102 for exc in without: 

103 errors.append(f"{format_exception(exc, None)} @ {path}") 

104 else: 

105 errors.append(f"{format_exception(exc, None)} @ {path}") 

106 return errors