Repository: litestar-org/litestar

Issue:
Lots of `pydantic` warnings: `.dict()` and `.json()` are deprecated
### Description

You can find lots of `DeprecationWarning` instances here: https://github.com/litestar-org/litestar/actions/runs/5578844701/jobs/10193581342

I propose to add a compat layer to call `.model_dump` and `.model_dump_json` on v2 and `.dict` and `.json` on v1, since they are both supported right now.

### URL to code causing the issue

_No response_

### MCVE

_No response_

### Steps to reproduce

_No response_

### Screenshots

_No response_

### Logs

_No response_

### Litestar Version

`main`

### Platform

- [ ] Linux
- [X] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
## Funding
* If you would like to see an issue prioritized, make a pledge towards it!
* We receive the pledge once the issue is completed & verified

<a href="https://polar.sh/litestar-org/litestar/issues/1996">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/1996/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/1996/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:
Sounds good to me

Resolving patch:
diff --git a/litestar/_openapi/schema_generation/examples.py b/litestar/_openapi/schema_generation/examples.py
--- a/litestar/_openapi/schema_generation/examples.py
+++ b/litestar/_openapi/schema_generation/examples.py
@@ -34,7 +34,9 @@ def _normalize_example_value(value: Any) -> Any:
     if isinstance(value, Enum):
         value = value.value
     if is_pydantic_model_instance(value):
-        value = value.dict()
+        from litestar.contrib.pydantic import _model_dump
+
+        value = _model_dump(value)
     if isinstance(value, (list, set)):
         value = [_normalize_example_value(v) for v in value]
     if isinstance(value, dict):
diff --git a/litestar/contrib/pydantic/__init__.py b/litestar/contrib/pydantic/__init__.py
--- a/litestar/contrib/pydantic/__init__.py
+++ b/litestar/contrib/pydantic/__init__.py
@@ -1,5 +1,24 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
 from .pydantic_dto_factory import PydanticDTO
 from .pydantic_init_plugin import PydanticInitPlugin
 from .pydantic_schema_plugin import PydanticSchemaPlugin
 
+if TYPE_CHECKING:
+    import pydantic
+
 __all__ = ("PydanticDTO", "PydanticInitPlugin", "PydanticSchemaPlugin")
+
+
+def _model_dump(model: pydantic.BaseModel, *, by_alias: bool = False) -> dict[str, Any]:
+    return (
+        model.model_dump(mode="json", by_alias=by_alias)
+        if hasattr(model, "model_dump")
+        else model.dict(by_alias=by_alias)
+    )
+
+
+def _model_dump_json(model: pydantic.BaseModel) -> str:
+    return model.model_dump_json() if hasattr(model, "model_dump_json") else model.json()
diff --git a/litestar/contrib/pydantic/pydantic_init_plugin.py b/litestar/contrib/pydantic/pydantic_init_plugin.py
--- a/litestar/contrib/pydantic/pydantic_init_plugin.py
+++ b/litestar/contrib/pydantic/pydantic_init_plugin.py
@@ -90,9 +90,7 @@ def decoders(cls) -> list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]
     @staticmethod
     def _create_pydantic_v1_encoders() -> dict[Any, Callable[[Any], Any]]:  # pragma: no cover
         return {
-            pydantic.BaseModel: lambda model: {
-                k: v.decode() if isinstance(v, bytes) else v for k, v in model.dict().items()
-            },
+            pydantic.BaseModel: lambda model: model.dict(),
             pydantic.SecretField: str,
             pydantic.StrictBool: int,
             pydantic.color.Color: str,  # pyright: ignore


Test patch:
diff --git a/litestar/testing/request_factory.py b/litestar/testing/request_factory.py
--- a/litestar/testing/request_factory.py
+++ b/litestar/testing/request_factory.py
@@ -266,7 +266,9 @@ def _create_request_with_data(
 
                 data = attrs_as_dict(data)  # type: ignore[arg-type]
             elif is_pydantic_model_instance(data):
-                data = data.model_dump(mode="json") if hasattr(data, "model_dump") else data.dict()
+                from litestar.contrib.pydantic import _model_dump
+
+                data = _model_dump(data)
 
             if request_media_type == RequestEncodingType.JSON:
                 encoding_headers, stream = httpx_encode_json(data)
diff --git a/tests/e2e/test_routing/test_path_resolution.py b/tests/e2e/test_routing/test_path_resolution.py
--- a/tests/e2e/test_routing/test_path_resolution.py
+++ b/tests/e2e/test_routing/test_path_resolution.py
@@ -4,6 +4,7 @@
 import pytest
 
 from litestar import Controller, MediaType, delete, get, post
+from litestar.contrib.pydantic import _model_dump
 from litestar.status_codes import (
     HTTP_200_OK,
     HTTP_204_NO_CONTENT,
@@ -105,7 +106,7 @@ def test_method(self) -> PydanticPerson:
     with create_test_client([MyController, delete_handler] if delete_handler else MyController) as client:
         response = client.get(decorator_path or test_path)
         assert response.status_code == HTTP_200_OK, response.json()
-        assert response.json() == person_instance.dict()
+        assert response.json() == _model_dump(person_instance)
         if delete_handler:
             delete_response = client.delete("/")
             assert delete_response.status_code == HTTP_204_NO_CONTENT
diff --git a/tests/unit/test_contrib/test_jwt/test_auth.py b/tests/unit/test_contrib/test_jwt/test_auth.py
--- a/tests/unit/test_contrib/test_jwt/test_auth.py
+++ b/tests/unit/test_contrib/test_jwt/test_auth.py
@@ -10,6 +10,7 @@
 
 from litestar import Litestar, Request, Response, get
 from litestar.contrib.jwt import JWTAuth, JWTCookieAuth, OAuth2PasswordBearerAuth, Token
+from litestar.contrib.pydantic import _model_dump
 from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_401_UNAUTHORIZED
 from litestar.stores.memory import MemoryStore
 from litestar.testing import create_test_client
@@ -72,7 +73,7 @@ async def retrieve_user_handler(token: Token, _: "ASGIConnection") -> Any:
     @get("/my-endpoint", middleware=[jwt_auth.middleware])
     def my_handler(request: Request["User", Token, Any]) -> None:
         assert request.user
-        assert request.user.dict() == user.dict()
+        assert _model_dump(request.user) == _model_dump(user)
         assert request.auth.sub == str(user.id)
 
     @get("/login")
@@ -174,7 +175,7 @@ async def retrieve_user_handler(token: Token, connection: Any) -> Any:
     @get("/my-endpoint", middleware=[jwt_auth.middleware])
     def my_handler(request: Request["User", Token, Any]) -> None:
         assert request.user
-        assert request.user.dict() == user.dict()
+        assert _model_dump(request.user) == _model_dump(user)
         assert request.auth.sub == str(user.id)
 
     @get("/login")
@@ -403,7 +404,7 @@ async def retrieve_user_handler(token: Token, connection: "ASGIConnection[Any, A
         retrieve_user_handler=retrieve_user_handler,
         token_secret="abc1234",
         exclude=["/"],
-        type_encoders={BaseModel: lambda m: m.dict(by_alias=True)},
+        type_encoders={BaseModel: lambda m: _model_dump(m, by_alias=True)},
     )
 
     @get()
diff --git a/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py b/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py
--- a/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py
+++ b/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py
@@ -26,6 +26,7 @@
     constr,
 )
 
+from litestar.contrib.pydantic import _model_dump, _model_dump_json
 from litestar.contrib.pydantic.pydantic_init_plugin import PydanticInitPlugin
 
 if VERSION.startswith("1"):
@@ -170,11 +171,12 @@ def test_default_serializer(model: BaseModel, attribute_name: str, expected: Any
 
 
 def test_serialization_of_model_instance(model: BaseModel) -> None:
-    assert serializer(model) == model.model_dump(mode="json") if hasattr(model, "model_dump") else model.dict()
+    assert serializer(getattr(model, "conbytes")) == b"hello"
+    assert serializer(model) == _model_dump(model)
 
 
 def test_pydantic_json_compatibility(model: BaseModel) -> None:
-    raw = model.model_dump_json() if hasattr(model, "model_dump_json") else model.json()
+    raw = _model_dump_json(model)
     encoded_json = encode_json(model, serializer=get_serializer(PydanticInitPlugin.encoders()))
 
     raw_result = json.loads(raw)
@@ -202,15 +204,13 @@ def test_decode_json_raises_serialization_exception(model: BaseModel, decoder: A
 
 
 def test_decode_json_typed(model: BaseModel) -> None:
-    dumped_model = model.model_dump_json() if hasattr(model, "model_dump_json") else model.json()
+    dumped_model = _model_dump_json(model)
     decoded_model = decode_json(value=dumped_model, target_type=Model, type_decoders=PydanticInitPlugin.decoders())
-    assert (
-        decoded_model.model_dump_json() if hasattr(decoded_model, "model_dump_json") else decoded_model.json()
-    ) == dumped_model
+    assert _model_dump_json(decoded_model) == dumped_model
 
 
 def test_decode_msgpack_typed(model: BaseModel) -> None:
-    model_json = model.json()
+    model_json = _model_dump_json(model)
     assert (
         decode_msgpack(
             encode_msgpack(model, serializer=get_serializer(PydanticInitPlugin.encoders())),
diff --git a/tests/unit/test_controller.py b/tests/unit/test_controller.py
--- a/tests/unit/test_controller.py
+++ b/tests/unit/test_controller.py
@@ -16,6 +16,7 @@
     websocket,
 )
 from litestar.connection import WebSocket
+from litestar.contrib.pydantic import _model_dump
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT
 from litestar.testing import create_test_client
@@ -59,7 +60,8 @@ def test_method(self) -> return_annotation:
         response = client.request(http_method, test_path)
         assert response.status_code == expected_status_code
         if return_value:
-            assert response.json() == return_value.dict() if isinstance(return_value, BaseModel) else return_value
+            if isinstance(return_value, BaseModel):
+                assert response.json() == _model_dump(return_value)
 
 
 def test_controller_with_websocket_handler() -> None:
diff --git a/tests/unit/test_dto/test_factory/test_integration.py b/tests/unit/test_dto/test_factory/test_integration.py
--- a/tests/unit/test_dto/test_factory/test_integration.py
+++ b/tests/unit/test_dto/test_factory/test_integration.py
@@ -13,7 +13,7 @@
 
 from litestar import Litestar, patch, post
 from litestar.connection.request import Request
-from litestar.contrib.pydantic import PydanticDTO
+from litestar.contrib.pydantic import PydanticDTO, _model_dump_json
 from litestar.datastructures import UploadFile
 from litestar.dto import DataclassDTO, DTOConfig, DTOData, MsgspecDTO, dto_field
 from litestar.dto.types import RenameStrategy
@@ -732,7 +732,7 @@ def handler(data: PydanticUser, request: Request) -> dict:
         headers = {"Content-Type": "application/json; charset=utf-8"}
         received = client.post(
             "/",
-            content=data.json(),
+            content=_model_dump_json(data),
             headers=headers,
         )
         required = list(received.json()["components"]["schemas"].values())[0]["required"]
diff --git a/tests/unit/test_handlers/test_http_handlers/test_to_response.py b/tests/unit/test_handlers/test_http_handlers/test_to_response.py
--- a/tests/unit/test_handlers/test_http_handlers/test_to_response.py
+++ b/tests/unit/test_handlers/test_http_handlers/test_to_response.py
@@ -13,6 +13,7 @@
 from litestar._signature import SignatureModel
 from litestar.background_tasks import BackgroundTask
 from litestar.contrib.jinja import JinjaTemplateEngine
+from litestar.contrib.pydantic import _model_dump
 from litestar.datastructures import Cookie, ResponseHeader
 from litestar.response.base import ASGIResponse
 from litestar.response.file import ASGIFileResponse, File
@@ -101,7 +102,7 @@ async def test_function(data: PydanticPerson) -> PydanticPerson:
         app=Litestar(route_handlers=[test_function]),
         request=RequestFactory().get(route_handler=test_function),
     )
-    assert loads(response.body) == person_instance.dict()  # type: ignore
+    assert loads(response.body) == _model_dump(person_instance)  # type: ignore
 
 
 async def test_to_response_returning_litestar_response() -> None:
diff --git a/tests/unit/test_kwargs/test_json_data.py b/tests/unit/test_kwargs/test_json_data.py
--- a/tests/unit/test_kwargs/test_json_data.py
+++ b/tests/unit/test_kwargs/test_json_data.py
@@ -1,4 +1,5 @@
 from litestar import post
+from litestar.contrib.pydantic import _model_dump
 from litestar.params import Body
 from litestar.status_codes import HTTP_201_CREATED
 from litestar.testing import create_test_client
@@ -12,7 +13,7 @@ def test_method(data: Form = Body()) -> None:
         assert isinstance(data, Form)
 
     with create_test_client(test_method) as client:
-        response = client.post("/test", json=Form(name="Moishe Zuchmir", age=30, programmer=True).dict())
+        response = client.post("/test", json=_model_dump(Form(name="Moishe Zuchmir", age=30, programmer=True)))
         assert response.status_code == HTTP_201_CREATED
 
 
diff --git a/tests/unit/test_kwargs/test_multipart_data.py b/tests/unit/test_kwargs/test_multipart_data.py
--- a/tests/unit/test_kwargs/test_multipart_data.py
+++ b/tests/unit/test_kwargs/test_multipart_data.py
@@ -10,6 +10,7 @@
 from pydantic import BaseConfig, BaseModel
 
 from litestar import Request, post
+from litestar.contrib.pydantic import _model_dump, _model_dump_json
 from litestar.datastructures.upload_file import UploadFile
 from litestar.enums import RequestEncodingType
 from litestar.params import Body
@@ -89,7 +90,7 @@ def test_request_body_multi_part(t_type: Type[Any]) -> None:
     body = Body(media_type=RequestEncodingType.MULTI_PART)
 
     test_path = "/test"
-    data = Form(name="Moishe Zuchmir", age=30, programmer=True).dict()
+    data = _model_dump(Form(name="Moishe Zuchmir", age=30, programmer=True))
 
     @post(path=test_path)
     def test_method(data: t_type = body) -> None:  # type: ignore
@@ -120,7 +121,7 @@ async def test_method(data: MultiPartFormWithMixedFields = Body(media_type=Reque
         response = client.post(
             "/form",
             files={"image": ("image.png", b"data")},
-            data={"tags": ["1", "2", "3"], "profile": person.json()},
+            data={"tags": ["1", "2", "3"], "profile": _model_dump_json(person)},
         )
         assert response.status_code == HTTP_201_CREATED
 
diff --git a/tests/unit/test_kwargs/test_reserved_kwargs_injection.py b/tests/unit/test_kwargs/test_reserved_kwargs_injection.py
--- a/tests/unit/test_kwargs/test_reserved_kwargs_injection.py
+++ b/tests/unit/test_kwargs/test_reserved_kwargs_injection.py
@@ -17,6 +17,7 @@
     post,
     put,
 )
+from litestar.contrib.pydantic import _model_dump
 from litestar.datastructures.state import ImmutableState, State
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.status_codes import (
@@ -100,7 +101,7 @@ def test_method(self, data: PydanticPerson) -> None:
             assert data == person_instance
 
     with create_test_client(MyController) as client:
-        response = client.request(http_method, test_path, json=person_instance.dict())
+        response = client.request(http_method, test_path, json=_model_dump(person_instance))
         assert response.status_code == expected_status_code
 
 
@@ -126,7 +127,7 @@ def test_method(self, data: List[PydanticPerson]) -> None:
             assert data == people
 
     with create_test_client(MyController) as client:
-        response = client.request(http_method, test_path, json=[p.dict() for p in people])
+        response = client.request(http_method, test_path, json=[_model_dump(p) for p in people])
         assert response.status_code == expected_status_code
 
 
diff --git a/tests/unit/test_kwargs/test_url_encoded_data.py b/tests/unit/test_kwargs/test_url_encoded_data.py
--- a/tests/unit/test_kwargs/test_url_encoded_data.py
+++ b/tests/unit/test_kwargs/test_url_encoded_data.py
@@ -1,6 +1,7 @@
 from typing import Optional
 
 from litestar import post
+from litestar.contrib.pydantic import _model_dump
 from litestar.enums import RequestEncodingType
 from litestar.params import Body
 from litestar.status_codes import HTTP_201_CREATED
@@ -15,7 +16,7 @@ def test_method(data: Form = Body(media_type=RequestEncodingType.URL_ENCODED)) -
         assert isinstance(data, Form)
 
     with create_test_client(test_method) as client:
-        response = client.post("/test", data=Form(name="Moishe Zuchmir", age=30, programmer=True).dict())
+        response = client.post("/test", data=_model_dump(Form(name="Moishe Zuchmir", age=30, programmer=True)))
         assert response.status_code == HTTP_201_CREATED
 
 
diff --git a/tests/unit/test_response/test_serialization.py b/tests/unit/test_response/test_serialization.py
--- a/tests/unit/test_response/test_serialization.py
+++ b/tests/unit/test_response/test_serialization.py
@@ -8,7 +8,7 @@
 from pydantic import SecretStr
 
 from litestar import MediaType, Response
-from litestar.contrib.pydantic import PydanticInitPlugin
+from litestar.contrib.pydantic import PydanticInitPlugin, _model_dump
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.serialization import get_serializer
 from tests import (
@@ -37,9 +37,9 @@ class _TestEnum(enum.Enum):
         [person, PydanticPerson],
         [{"key": 123}, Dict[str, int]],
         [[{"key": 123}], List[Dict[str, int]]],
-        [VanillaDataClassPerson(**person.dict()), VanillaDataClassPerson],
-        [PydanticDataClassPerson(**person.dict()), PydanticDataClassPerson],
-        [MsgSpecStructPerson(**person.dict()), MsgSpecStructPerson],
+        [VanillaDataClassPerson(**_model_dump(person)), VanillaDataClassPerson],
+        [PydanticDataClassPerson(**_model_dump(person)), PydanticDataClassPerson],
+        [MsgSpecStructPerson(**_model_dump(person)), MsgSpecStructPerson],
         [{"enum": _TestEnum.A}, Dict[str, _TestEnum]],
         [{"secret": secret}, Dict[str, SecretStr]],
         [{"pure_path": pure_path}, Dict[str, PurePath]],
diff --git a/tests/unit/test_security/test_session_auth.py b/tests/unit/test_security/test_session_auth.py
--- a/tests/unit/test_security/test_session_auth.py
+++ b/tests/unit/test_security/test_session_auth.py
@@ -9,6 +9,7 @@
 )
 
 from litestar import Litestar, Request, delete, get, post
+from litestar.contrib.pydantic import _model_dump
 from litestar.middleware.session.server_side import (
     ServerSideSessionBackend,
     ServerSideSessionConfig,
@@ -38,7 +39,7 @@ def test_authentication(session_backend_config_memory: ServerSideSessionConfig)
 
     @post("/login")
     def login_handler(request: "Request[Any, Any, Any]", data: User) -> None:
-        request.set_session(data.dict())
+        request.set_session(_model_dump(data))
 
     @delete("/user/{user_id:str}")
     def delete_user_handler(request: "Request[User, Any, Any]") -> None:
diff --git a/tests/unit/test_testing/test_request_factory.py b/tests/unit/test_testing/test_request_factory.py
--- a/tests/unit/test_testing/test_request_factory.py
+++ b/tests/unit/test_testing/test_request_factory.py
@@ -5,6 +5,7 @@
 from pydantic import BaseModel
 
 from litestar import HttpMethod, Litestar, get
+from litestar.contrib.pydantic import _model_dump
 from litestar.datastructures import Cookie, MultiDict
 from litestar.enums import ParamType, RequestEncodingType
 from litestar.testing import RequestFactory
@@ -65,7 +66,7 @@ def test_request_factory_build_headers() -> None:
 
 @pytest.mark.parametrize("data_cls", [PydanticPerson, VanillaDataClassPerson, AttrsPerson, MsgSpecStructPerson])
 async def test_request_factory_create_with_data(data_cls: DataContainerType) -> None:
-    person = PydanticPersonFactory.build().dict()
+    person = _model_dump(PydanticPersonFactory.build())
     request = RequestFactory()._create_request_with_data(
         HttpMethod.POST,
         "/",
@@ -78,7 +79,7 @@ async def test_request_factory_create_with_data(data_cls: DataContainerType) ->
 @pytest.mark.parametrize(
     "request_media_type, verify_data",
     [
-        [RequestEncodingType.JSON, lambda data: json.loads(data) == pet.dict()],
+        [RequestEncodingType.JSON, lambda data: json.loads(data) == _model_dump(pet)],
         [RequestEncodingType.MULTI_PART, lambda data: "Content-Disposition" in data],
         [
             RequestEncodingType.URL_ENCODED,
@@ -92,7 +93,7 @@ async def test_request_factory_create_with_content_type(
     request = RequestFactory()._create_request_with_data(
         HttpMethod.POST,
         "/",
-        data=pet.dict(),
+        data=_model_dump(pet),
         request_media_type=request_media_type,
     )
     assert request.headers["Content-Type"].startswith(request_media_type.value)
@@ -196,4 +197,4 @@ async def test_request_factory_post_put_patch(factory: Callable, method: HttpMet
     assert len(request.headers.keys()) == 3
     assert request.headers.get("header1") == "value1"
     body = await request.body()
-    assert json.loads(body) == pet.dict()
+    assert json.loads(body) == _model_dump(pet)