Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/249.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Decode cached JSON into the annotated return type on Pydantic v2.
26 changes: 15 additions & 11 deletions fastapi_cache/coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@

import pendulum
from fastapi.encoders import jsonable_encoder
from pydantic import TypeAdapter
from starlette.responses import JSONResponse
from starlette.templating import (
_TemplateResponse as TemplateResponse, # pyright: ignore[reportPrivateUsage]
)


class ModelField:
pass

_T = TypeVar("_T", bound=type)


Expand Down Expand Up @@ -67,12 +64,9 @@ def encode(cls, value: Any) -> bytes:
def decode(cls, value: bytes) -> Any:
raise NotImplementedError

# (Shared) cache for endpoint return types to Pydantic model fields.
# Note that subclasses share this cache! If a subclass overrides the
# decode_as_type method and then stores a different kind of field for a
# given type, do make sure that the subclass provides its own class
# attribute for this cache.
_type_field_cache: ClassVar[Dict[Any, ModelField]] = {}
# Subclasses share this. Override the attribute if decode_as_type stores
# something other than a TypeAdapter for a given type.
_type_adapter_cache: ClassVar[Dict[Any, TypeAdapter[Any]]] = {}

@overload
@classmethod
Expand All @@ -92,7 +86,17 @@ def decode_as_type(cls, value: bytes, *, type_: Optional[_T]) -> Union[_T, Any]:

"""
result = cls.decode(value)
return result
if type_ is None:
return result

adapter: TypeAdapter[Any]
try:
adapter = cls._type_adapter_cache[type_]
except KeyError:
adapter = TypeAdapter(type_)
cls._type_adapter_cache[type_] = adapter
validated: Any = adapter.validate_python(result)
return validated


class JsonCoder(Coder):
Expand Down
6 changes: 5 additions & 1 deletion tests/test_codecs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, Optional, Tuple, Type
from typing import Any, List, Optional, Tuple, Type

import pytest
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -51,6 +51,10 @@ def test_pickle_coder(value: Any) -> None:
({"some_key": 1, "other_key": 2}, None),
(DCItem(name="foo", price=42.0, description="some dataclass item", tax=0.2), DCItem),
(PDItem(name="foo", price=42.0, description="some pydantic item", tax=0.2), PDItem),
(
[PDItem(name="foo", price=42.0, description="some pydantic item", tax=0.2)],
List[PDItem],
),
],
)
def test_json_coder(value: Any, return_type: Type[Any]) -> None:
Expand Down