Skip to content

Commit

Permalink
feat: deprecate litestar.contrib.pydantic (#3852)
Browse files Browse the repository at this point in the history
* feat: deprecate `litestar.contrib.pydantic`
  • Loading branch information
cofin authored Nov 16, 2024
1 parent 98933a3 commit a2b8e48
Show file tree
Hide file tree
Showing 38 changed files with 1,773 additions and 1,440 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ repos:
- id: unasyncd
additional_dependencies: ["ruff"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.7.0"
rev: "v0.7.3"
hooks:
- id: ruff
args: ["--fix"]
Expand Down
10 changes: 10 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@
(PY_CLASS, "litestar.template.Template"),
(PY_CLASS, "litestar.middleware.compression.gzip_facade.GzipCompression"),
(PY_CLASS, "litestar.handlers.http_handlers.decorators._subclass_warning"),
(PY_CLASS, "litestar.background_tasks.P"),
(PY_CLASS, "P.args"),
(PY_CLASS, "P.kwargs"),
(PY_CLASS, "litestar.contrib.jinja.P"),
(PY_CLASS, "litestar.contrib.mako.P"),
(PY_CLASS, "JWTDecodeOptions"),
(PY_CLASS, "litestar.template.base.P"),
(PY_CLASS, "litestar.contrib.pydantic.PydanticDTO"),
(PY_CLASS, "litestar.contrib.pydantic.PydanticPlugin"),
(PY_CLASS, "typing.Self"),
]

nitpick_ignore_regex = [
Expand Down
1 change: 1 addition & 0 deletions docs/reference/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ plugins
flash_messages
htmx
problem_details
pydantic
structlog
sqlalchemy
5 changes: 5 additions & 0 deletions docs/reference/plugins/pydantic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pydantic
========

.. automodule:: litestar.plugins.pydantic
:members:
2 changes: 1 addition & 1 deletion docs/usage/dto/1-abstract-dto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The following factories are currently available:

- :class:`DataclassDTO <litestar.dto.dataclass_dto.DataclassDTO>`
- :class:`MsgspecDTO <litestar.dto.msgspec_dto.MsgspecDTO>`
- :class:`PydanticDTO <litestar.contrib.pydantic.PydanticDTO>`
- :class:`PydanticDTO <litestar.plugins.pydantic.PydanticDTO>`
- :class:`SQLAlchemyDTO <advanced_alchemy.extensions.litestar.dto.SQLAlchemyDTO>`

Using DTO Factories
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/routing/handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ These are used exactly like :func:`@route() <.handlers.route>` with the sole exc
from litestar import delete, get, patch, post, put, head
from litestar.dto import DTOConfig, DTOData
from litestar.contrib.pydantic import PydanticDTO
from litestar.plugins.pydantic import PydanticDTO
from pydantic import BaseModel
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/routing/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Their purpose is to allow users to utilize Python OOP for better code organizati
.. code-block:: python
:caption: Registering a :class:`~.controller.Controller`
from litestar.contrib.pydantic import PydanticDTO
from litestar.plugins.pydantic import PydanticDTO
from litestar.controller import Controller
from litestar.dto import DTOConfig, DTOData
from litestar.handlers import get, post, patch, delete
Expand Down
4 changes: 2 additions & 2 deletions litestar/_openapi/schema_generation/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from polyfactory.utils.predicates import is_union
from typing_extensions import get_args

from litestar.contrib.pydantic.utils import is_pydantic_model_instance
from litestar.openapi.spec import Example
from litestar.plugins.pydantic.utils import is_pydantic_model_instance
from litestar.types import Empty

if TYPE_CHECKING:
Expand Down Expand Up @@ -47,7 +47,7 @@ def _normalize_example_value(value: Any) -> Any:
if isinstance(value, Enum):
value = value.value
if is_pydantic_model_instance(value):
from litestar.contrib.pydantic import _model_dump
from litestar.plugins.pydantic import _model_dump

value = _model_dump(value)
if isinstance(value, (list, set)):
Expand Down
2 changes: 1 addition & 1 deletion litestar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ def _get_default_plugins(plugins: list[PluginProtocol]) -> list[PluginProtocol]:
plugins.append(MsgspecDIPlugin())

with suppress(MissingDependencyException):
from litestar.contrib.pydantic import (
from litestar.plugins.pydantic import (
PydanticDIPlugin,
PydanticInitPlugin,
PydanticPlugin,
Expand Down
116 changes: 29 additions & 87 deletions litestar/contrib/pydantic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
# ruff: noqa: TCH004, F401
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from litestar.plugins import InitPluginProtocol

from .pydantic_di_plugin import PydanticDIPlugin
from .pydantic_dto_factory import PydanticDTO
from .pydantic_init_plugin import PydanticInitPlugin
from .pydantic_schema_plugin import PydanticSchemaPlugin

if TYPE_CHECKING:
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1

from litestar.config.app import AppConfig
from litestar.types.serialization import PydanticV1FieldsListType, PydanticV2FieldsListType
from litestar.utils import warn_deprecation

__all__ = (
"PydanticDTO",
Expand All @@ -25,82 +14,35 @@
)


def _model_dump(model: BaseModel | BaseModelV1, *, by_alias: bool = False) -> dict[str, Any]:
return (
model.model_dump(mode="json", by_alias=by_alias) # pyright: ignore
if hasattr(model, "model_dump")
else {k: v.decode() if isinstance(v, bytes) else v for k, v in model.dict(by_alias=by_alias).items()}
)

def __getattr__(attr_name: str) -> object:
if attr_name in __all__:
from litestar.plugins.pydantic import (
PydanticDIPlugin,
PydanticDTO,
PydanticInitPlugin,
PydanticPlugin,
PydanticSchemaPlugin,
)

def _model_dump_json(model: BaseModel | BaseModelV1, by_alias: bool = False) -> str:
return (
model.model_dump_json(by_alias=by_alias) # pyright: ignore
if hasattr(model, "model_dump_json")
else model.json(by_alias=by_alias) # pyright: ignore
)
warn_deprecation(
deprecated_name=f"litestar.contrib.pydantic.{attr_name}",
version="2.12",
kind="import",
removal_in="3.0",
info=f"importing {attr_name} from 'litestar.contrib.pydantic' is deprecated, please "
f"import it from 'litestar.plugins.pydantic' instead",
)
value = globals()[attr_name] = locals()[attr_name]
return value

raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover

class PydanticPlugin(InitPluginProtocol):
"""A plugin that provides Pydantic integration."""

__slots__ = (
"exclude",
"exclude_defaults",
"exclude_none",
"exclude_unset",
"include",
"prefer_alias",
"validate_strict",
if TYPE_CHECKING:
from litestar.plugins.pydantic import (
PydanticDIPlugin,
PydanticDTO,
PydanticInitPlugin,
PydanticPlugin,
PydanticSchemaPlugin,
)

def __init__(
self,
exclude: PydanticV1FieldsListType | PydanticV2FieldsListType | None = None,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_unset: bool = False,
include: PydanticV1FieldsListType | PydanticV2FieldsListType | None = None,
prefer_alias: bool = False,
validate_strict: bool = False,
) -> None:
"""Pydantic Plugin to support serialization / validation of Pydantic types / models
:param exclude: Fields to exclude during serialization
:param exclude_defaults: Fields to exclude during serialization when they are set to their default value
:param exclude_none: Fields to exclude during serialization when they are set to ``None``
:param exclude_unset: Fields to exclude during serialization when they arenot set
:param include: Fields to exclude during serialization
:param prefer_alias: Use the ``by_alias=True`` flag when dumping models
:param validate_strict: Use ``strict=True`` when calling ``.model_validate`` on Pydantic 2.x models
"""
self.exclude = exclude
self.exclude_defaults = exclude_defaults
self.exclude_none = exclude_none
self.exclude_unset = exclude_unset
self.include = include
self.prefer_alias = prefer_alias
self.validate_strict = validate_strict

def on_app_init(self, app_config: AppConfig) -> AppConfig:
"""Configure application for use with Pydantic.
Args:
app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
"""
app_config.plugins.extend(
[
PydanticInitPlugin(
exclude=self.exclude,
exclude_defaults=self.exclude_defaults,
exclude_none=self.exclude_none,
exclude_unset=self.exclude_unset,
include=self.include,
prefer_alias=self.prefer_alias,
validate_strict=self.validate_strict,
),
PydanticSchemaPlugin(prefer_alias=self.prefer_alias),
PydanticDIPlugin(),
]
)
return app_config
42 changes: 23 additions & 19 deletions litestar/contrib/pydantic/pydantic_di_plugin.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
# ruff: noqa: TCH004, F401
from __future__ import annotations

import inspect
from inspect import Signature
from typing import Any
from typing import TYPE_CHECKING # pragma: no cover

from litestar.contrib.pydantic.utils import is_pydantic_model_class
from litestar.plugins import DIPlugin
from litestar.utils import warn_deprecation # pragma: no cover

__all__ = ("PydanticDIPlugin",) # pragma: no cover

class PydanticDIPlugin(DIPlugin):
def has_typed_init(self, type_: Any) -> bool:
return is_pydantic_model_class(type_)

def get_typed_init(self, type_: Any) -> tuple[Signature, dict[str, Any]]:
try:
model_fields = dict(type_.model_fields)
except AttributeError:
model_fields = {k: model_field.field_info for k, model_field in type_.__fields__.items()}
def __getattr__(attr_name: str) -> object: # pragma: no cover
if attr_name in __all__:
from litestar.plugins.pydantic.plugins.di import PydanticDIPlugin

parameters = [
inspect.Parameter(name=field_name, kind=inspect.Parameter.KEYWORD_ONLY, annotation=Any)
for field_name in model_fields
]
type_hints = {field_name: Any for field_name in model_fields}
return Signature(parameters), type_hints
warn_deprecation(
deprecated_name=f"litestar.contrib.pydantic.pydantic_di_plugin.{attr_name}",
version="2.12",
kind="import",
removal_in="3.0",
info=f"importing {attr_name} from 'litestar.contrib.pydantic.pydantic_di_plugin' is deprecated, please "
f"import it from 'litestar.plugins.pydantic' instead",
)
value = globals()[attr_name] = locals()[attr_name]
return value

raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}")


if TYPE_CHECKING:
from litestar.plugins.pydantic.plugins.di import PydanticDIPlugin
Loading

0 comments on commit a2b8e48

Please sign in to comment.