API refactor
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-07 16:25:52 +09:00
parent 76d0d86211
commit 91c7e04474
1171 changed files with 81940 additions and 44117 deletions

View File

@@ -1,10 +1,12 @@
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/plugins#build-a-plugin
"""!!! abstract "Usage Documentation"
[Build a Plugin](../concepts/plugins.md#build-a-plugin)
Plugin interface for Pydantic plugins, and related types.
"""
from __future__ import annotations
from typing import Any, Callable
from typing import Any, Callable, Literal, NamedTuple
from pydantic_core import CoreConfig, CoreSchema, ValidationError
from typing_extensions import Protocol, TypeAlias
@@ -16,17 +18,32 @@ __all__ = (
'ValidateJsonHandlerProtocol',
'ValidateStringsHandlerProtocol',
'NewSchemaReturns',
'SchemaTypePath',
'SchemaKind',
)
NewSchemaReturns: TypeAlias = 'tuple[ValidatePythonHandlerProtocol | None, ValidateJsonHandlerProtocol | None, ValidateStringsHandlerProtocol | None]'
class SchemaTypePath(NamedTuple):
"""Path defining where `schema_type` was defined, or where `TypeAdapter` was called."""
module: str
name: str
SchemaKind: TypeAlias = Literal['BaseModel', 'TypeAdapter', 'dataclass', 'create_model', 'validate_call']
class PydanticPluginProtocol(Protocol):
"""Protocol defining the interface for Pydantic plugins."""
def new_schema_validator(
self,
schema: CoreSchema,
schema_type: Any,
schema_type_path: SchemaTypePath,
schema_kind: SchemaKind,
config: CoreConfig | None,
plugin_settings: dict[str, object],
) -> tuple[
@@ -40,6 +57,9 @@ class PydanticPluginProtocol(Protocol):
Args:
schema: The schema to validate against.
schema_type: The original type which the schema was created from, e.g. the model class.
schema_type_path: Path defining where `schema_type` was defined, or where `TypeAdapter` was called.
schema_kind: The kind of schema to validate against.
config: The config to use for validation.
plugin_settings: Any plugin settings.
@@ -76,6 +96,14 @@ class BaseValidateHandlerProtocol(Protocol):
"""
return
def on_exception(self, exception: Exception) -> None:
"""Callback to be notified of validation exceptions.
Args:
exception: The exception raised during validation.
"""
return
class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
"""Event handler for `SchemaValidator.validate_python`."""
@@ -88,6 +116,8 @@ class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
from_attributes: bool | None = None,
context: dict[str, Any] | None = None,
self_instance: Any | None = None,
by_alias: bool | None = None,
by_name: bool | None = None,
) -> None:
"""Callback to be notified of validation start, and create an instance of the event handler.
@@ -98,6 +128,8 @@ class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
context: The context to use for validation, this is passed to functional validators.
self_instance: An instance of a model to set attributes on from validation, this is used when running
validation from the `__init__` method of a model.
by_alias: Whether to use the field's alias to match the input data to an attribute.
by_name: Whether to use the field's name to match the input data to an attribute.
"""
pass
@@ -112,6 +144,8 @@ class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
strict: bool | None = None,
context: dict[str, Any] | None = None,
self_instance: Any | None = None,
by_alias: bool | None = None,
by_name: bool | None = None,
) -> None:
"""Callback to be notified of validation start, and create an instance of the event handler.
@@ -121,6 +155,8 @@ class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
context: The context to use for validation, this is passed to functional validators.
self_instance: An instance of a model to set attributes on from validation, this is used when running
validation from the `__init__` method of a model.
by_alias: Whether to use the field's alias to match the input data to an attribute.
by_name: Whether to use the field's name to match the input data to an attribute.
"""
pass
@@ -132,7 +168,13 @@ class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
"""Event handler for `SchemaValidator.validate_strings`."""
def on_enter(
self, input: StringInput, *, strict: bool | None = None, context: dict[str, Any] | None = None
self,
input: StringInput,
*,
strict: bool | None = None,
context: dict[str, Any] | None = None,
by_alias: bool | None = None,
by_name: bool | None = None,
) -> None:
"""Callback to be notified of validation start, and create an instance of the event handler.
@@ -140,5 +182,7 @@ class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
input: The string data to be validated.
strict: Whether to validate the object in strict mode.
context: The context to use for validation, this is passed to functional validators.
by_alias: Whether to use the field's alias to match the input data to an attribute.
by_name: Whether to use the field's name to match the input data to an attribute.
"""
pass

View File

@@ -1,16 +1,10 @@
from __future__ import annotations
import sys
import importlib.metadata as importlib_metadata
import os
import warnings
from typing import TYPE_CHECKING, Iterable
from typing_extensions import Final
if sys.version_info >= (3, 8):
import importlib.metadata as importlib_metadata
else:
import importlib_metadata
from collections.abc import Iterable
from typing import TYPE_CHECKING, Final
if TYPE_CHECKING:
from . import PydanticPluginProtocol
@@ -30,10 +24,13 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]:
Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402
"""
disabled_plugins = os.getenv('PYDANTIC_DISABLE_PLUGINS')
global _plugins, _loading_plugins
if _loading_plugins:
# this happens when plugins themselves use pydantic, we return no plugins
return ()
elif disabled_plugins in ('__all__', '1', 'true'):
return ()
elif _plugins is None:
_plugins = {}
# set _loading_plugins so any plugins that use pydantic don't themselves use plugins
@@ -45,6 +42,8 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]:
continue
if entry_point.value in _plugins:
continue
if disabled_plugins is not None and entry_point.name in disabled_plugins.split(','):
continue
try:
_plugins[entry_point.value] = entry_point.load()
except (ImportError, AttributeError) as e:

View File

@@ -1,14 +1,16 @@
"""Pluggable schema validator for pydantic."""
from __future__ import annotations
import functools
from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
from pydantic_core import CoreConfig, CoreSchema, SchemaValidator, ValidationError
from typing_extensions import Literal, ParamSpec
from typing_extensions import ParamSpec
if TYPE_CHECKING:
from . import BaseValidateHandlerProtocol, PydanticPluginProtocol
from . import BaseValidateHandlerProtocol, PydanticPluginProtocol, SchemaKind, SchemaTypePath
P = ParamSpec('P')
@@ -18,18 +20,33 @@ events: list[Event] = list(Event.__args__) # type: ignore
def create_schema_validator(
schema: CoreSchema, config: CoreConfig | None = None, plugin_settings: dict[str, Any] | None = None
) -> SchemaValidator:
schema: CoreSchema,
schema_type: Any,
schema_type_module: str,
schema_type_name: str,
schema_kind: SchemaKind,
config: CoreConfig | None = None,
plugin_settings: dict[str, Any] | None = None,
) -> SchemaValidator | PluggableSchemaValidator:
"""Create a `SchemaValidator` or `PluggableSchemaValidator` if plugins are installed.
Returns:
If plugins are installed then return `PluggableSchemaValidator`, otherwise return `SchemaValidator`.
"""
from . import SchemaTypePath
from ._loader import get_plugins
plugins = get_plugins()
if plugins:
return PluggableSchemaValidator(schema, config, plugins, plugin_settings or {}) # type: ignore
return PluggableSchemaValidator(
schema,
schema_type,
SchemaTypePath(schema_type_module, schema_type_name),
schema_kind,
config,
plugins,
plugin_settings or {},
)
else:
return SchemaValidator(schema, config)
@@ -42,6 +59,9 @@ class PluggableSchemaValidator:
def __init__(
self,
schema: CoreSchema,
schema_type: Any,
schema_type_path: SchemaTypePath,
schema_kind: SchemaKind,
config: CoreConfig | None,
plugins: Iterable[PydanticPluginProtocol],
plugin_settings: dict[str, Any],
@@ -52,7 +72,12 @@ class PluggableSchemaValidator:
json_event_handlers: list[BaseValidateHandlerProtocol] = []
strings_event_handlers: list[BaseValidateHandlerProtocol] = []
for plugin in plugins:
p, j, s = plugin.new_schema_validator(schema, config, plugin_settings)
try:
p, j, s = plugin.new_schema_validator(
schema, schema_type, schema_type_path, schema_kind, config, plugin_settings
)
except TypeError as e: # pragma: no cover
raise TypeError(f'Error using plugin `{plugin.__module__}:{plugin.__class__.__name__}`: {e}') from e
if p is not None:
python_event_handlers.append(p)
if j is not None:
@@ -75,6 +100,7 @@ def build_wrapper(func: Callable[P, R], event_handlers: list[BaseValidateHandler
on_enters = tuple(h.on_enter for h in event_handlers if filter_handlers(h, 'on_enter'))
on_successes = tuple(h.on_success for h in event_handlers if filter_handlers(h, 'on_success'))
on_errors = tuple(h.on_error for h in event_handlers if filter_handlers(h, 'on_error'))
on_exceptions = tuple(h.on_exception for h in event_handlers if filter_handlers(h, 'on_exception'))
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
@@ -87,6 +113,10 @@ def build_wrapper(func: Callable[P, R], event_handlers: list[BaseValidateHandler
for on_error_handler in on_errors:
on_error_handler(error)
raise
except Exception as exception:
for on_exception_handler in on_exceptions:
on_exception_handler(exception)
raise
else:
for on_success_handler in on_successes:
on_success_handler(result)