Coverage for pydantic/plugin/_schema_validator.py: 100.00%

70 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-02 16:20 +0000

1"""Pluggable schema validator for pydantic.""" 

2 

3from __future__ import annotations 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

4 

5import functools 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

6from collections.abc import Iterable 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

7from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

8 

9from pydantic_core import CoreConfig, CoreSchema, SchemaValidator, ValidationError 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

10from typing_extensions import ParamSpec 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

11 

12if TYPE_CHECKING: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

13 from . import BaseValidateHandlerProtocol, PydanticPluginProtocol, SchemaKind, SchemaTypePath 

14 

15 

16P = ParamSpec('P') 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

17R = TypeVar('R') 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

18Event = Literal['on_validate_python', 'on_validate_json', 'on_validate_strings'] 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

19events: list[Event] = list(Event.__args__) # type: ignore 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

20 

21 

22def create_schema_validator( 1abcdefghijklmnopqrstuvJwxyzABCDEFG

23 schema: CoreSchema, 

24 schema_type: Any, 

25 schema_type_module: str, 

26 schema_type_name: str, 

27 schema_kind: SchemaKind, 

28 config: CoreConfig | None = None, 

29 plugin_settings: dict[str, Any] | None = None, 

30) -> SchemaValidator | PluggableSchemaValidator: 

31 """Create a `SchemaValidator` or `PluggableSchemaValidator` if plugins are installed. 

32 

33 Returns: 

34 If plugins are installed then return `PluggableSchemaValidator`, otherwise return `SchemaValidator`. 

35 """ 

36 from . import SchemaTypePath 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

37 from ._loader import get_plugins 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

38 

39 plugins = get_plugins() 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

40 if plugins: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

41 return PluggableSchemaValidator( 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

42 schema, 

43 schema_type, 

44 SchemaTypePath(schema_type_module, schema_type_name), 

45 schema_kind, 

46 config, 

47 plugins, 

48 plugin_settings or {}, 

49 ) 

50 else: 

51 return SchemaValidator(schema, config) 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

52 

53 

54class PluggableSchemaValidator: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

55 """Pluggable schema validator.""" 

56 

57 __slots__ = '_schema_validator', 'validate_json', 'validate_python', 'validate_strings' 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

58 

59 def __init__( 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

60 self, 

61 schema: CoreSchema, 

62 schema_type: Any, 

63 schema_type_path: SchemaTypePath, 

64 schema_kind: SchemaKind, 

65 config: CoreConfig | None, 

66 plugins: Iterable[PydanticPluginProtocol], 

67 plugin_settings: dict[str, Any], 

68 ) -> None: 

69 self._schema_validator = SchemaValidator(schema, config) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

70 

71 python_event_handlers: list[BaseValidateHandlerProtocol] = [] 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

72 json_event_handlers: list[BaseValidateHandlerProtocol] = [] 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

73 strings_event_handlers: list[BaseValidateHandlerProtocol] = [] 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

74 for plugin in plugins: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

75 try: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

76 p, j, s = plugin.new_schema_validator( 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

77 schema, schema_type, schema_type_path, schema_kind, config, plugin_settings 

78 ) 

79 except TypeError as e: # pragma: no cover 

80 raise TypeError(f'Error using plugin `{plugin.__module__}:{plugin.__class__.__name__}`: {e}') from e 

81 if p is not None: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

82 python_event_handlers.append(p) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

83 if j is not None: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

84 json_event_handlers.append(j) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

85 if s is not None: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

86 strings_event_handlers.append(s) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

87 

88 self.validate_python = build_wrapper(self._schema_validator.validate_python, python_event_handlers) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

89 self.validate_json = build_wrapper(self._schema_validator.validate_json, json_event_handlers) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

90 self.validate_strings = build_wrapper(self._schema_validator.validate_strings, strings_event_handlers) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

91 

92 def __getattr__(self, name: str) -> Any: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

93 return getattr(self._schema_validator, name) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

94 

95 

96def build_wrapper(func: Callable[P, R], event_handlers: list[BaseValidateHandlerProtocol]) -> Callable[P, R]: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

97 if not event_handlers: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

98 return func 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

99 else: 

100 on_enters = tuple(h.on_enter for h in event_handlers if filter_handlers(h, 'on_enter')) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

101 on_successes = tuple(h.on_success for h in event_handlers if filter_handlers(h, 'on_success')) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

102 on_errors = tuple(h.on_error for h in event_handlers if filter_handlers(h, 'on_error')) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

103 on_exceptions = tuple(h.on_exception for h in event_handlers if filter_handlers(h, 'on_exception')) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

104 

105 @functools.wraps(func) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

106 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

107 for on_enter_handler in on_enters: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

108 on_enter_handler(*args, **kwargs) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

109 

110 try: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

111 result = func(*args, **kwargs) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

112 except ValidationError as error: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

113 for on_error_handler in on_errors: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

114 on_error_handler(error) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

115 raise 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

116 except Exception as exception: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

117 for on_exception_handler in on_exceptions: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

118 on_exception_handler(exception) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

119 raise 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

120 else: 

121 for on_success_handler in on_successes: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

122 on_success_handler(result) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

123 return result 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

124 

125 return wrapper 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

126 

127 

128def filter_handlers(handler_cls: BaseValidateHandlerProtocol, method_name: str) -> bool: 1abcdefghijkHIlmnopqrstuvJwxyzABCDEFG

129 """Filter out handler methods which are not implemented by the plugin directly - e.g. are missing 

130 or are inherited from the protocol. 

131 """ 

132 handler = getattr(handler_cls, method_name, None) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

133 if handler is None: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

134 return False 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

135 elif handler.__module__ == 'pydantic.plugin': 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

136 # this is the original handler, from the protocol due to runtime inheritance 

137 # we don't want to call it 

138 return False 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG

139 else: 

140 return True 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG