Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix interface compatibility by inheriting from Response #254

Merged
Merged
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
3 changes: 2 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

# Unreleased

- Fix `CachedResponse.is_expired` check to consider any errors as "expired"
- Fixed `CachedResponse.is_expired` check to consider any errors as "expired". (!252)
- Now `CachedResponse` inherits from the `aiohttp.ClientResponse`. (#251)

## 0.11.1

Expand Down
36 changes: 23 additions & 13 deletions aiohttp_client_cache/backends/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from copy import deepcopy
import inspect
import pickle
from abc import ABCMeta, abstractmethod
Expand All @@ -8,17 +9,16 @@
from logging import getLogger
from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Union

from aiohttp import ClientResponse
from aiohttp.typedefs import StrOrURL

from aiohttp_client_cache.cache_control import CacheActions, ExpirationPatterns, ExpirationTime
from aiohttp_client_cache.cache_keys import create_key
from aiohttp_client_cache.response import AnyResponse, CachedResponse
from aiohttp_client_cache.response import CachedResponse

ResponseOrKey = Union[CachedResponse, bytes, str, None]
_FilterFn = Union[
Callable[[AnyResponse], bool],
Callable[[AnyResponse], Awaitable[bool]],
Callable[[CachedResponse], bool],
Callable[[CachedResponse], Awaitable[bool]],
]

logger = getLogger(__name__)
Expand Down Expand Up @@ -84,7 +84,7 @@ def __init__(
self.ignored_params = set(ignored_params or [])

async def is_cacheable(
self, response: AnyResponse | None, actions: CacheActions | None = None
self, response: CachedResponse | None, actions: CacheActions | None = None
) -> bool:
"""Perform all checks needed to determine if the given response should be cached"""
if not response:
Expand Down Expand Up @@ -155,12 +155,12 @@ async def get_response(self, key: str) -> CachedResponse | None:
logger.debug(f'Attempting to get cached response for key: {key}')
try:
response = await self.responses.read(key) or await self._get_redirect_response(str(key))
# Catch "quiet" deserialization errors due to upgrading attrs
# Catch "quiet" deserialization errors due to upgrading attrs # TODO: Remove?
if response is not None:
assert response.method # type: ignore
except (AssertionError, AttributeError, KeyError, TypeError, pickle.PickleError):
except (AssertionError, AttributeError, KeyError, TypeError, pickle.PickleError) as e:
logger.debug(repr(e))
response = None

if not response:
logger.debug('No cached response found')
# If the item is expired or filtered out, delete it from the cache
Expand All @@ -181,7 +181,7 @@ async def _get_redirect_response(self, key: str) -> CachedResponse | None:

async def save_response(
self,
response: ClientResponse,
response: CachedResponse,
cache_key: str | None = None,
expires: datetime | None = None,
):
Expand All @@ -193,7 +193,7 @@ async def save_response(
expires: Expiration time to set for the response
"""
cache_key = cache_key or self.create_key(response.method, response.url)
cached_response = await CachedResponse.from_client_response(response, expires)
cached_response = await response.postprocess(expires)
await self.responses.write(cache_key, cached_response)

# Alias any redirect requests to the same cache key
Expand Down Expand Up @@ -305,9 +305,14 @@ def serialize(self, item: ResponseOrKey = None) -> bytes | None:

def deserialize(self, item: ResponseOrKey) -> CachedResponse | str | None:
"""Deserialize a cached URL or response"""
if isinstance(item, (CachedResponse, str)):
if not item:
return None
if isinstance(item, CachedResponse):
item.from_cache = True
return item
return self._serializer.loads(item) if item else None
if isinstance(item, str):
return item
return self._serializer.loads(item)

@staticmethod
def _get_serializer(secret_key, salt):
Expand Down Expand Up @@ -409,6 +414,8 @@ async def read(self, key: str) -> CachedResponse | str | None:
item.reset()
except AttributeError:
pass
else:
item.from_cache = True
return item

async def size(self) -> int:
Expand All @@ -419,4 +426,7 @@ async def values(self) -> AsyncIterable[ResponseOrKey]: # type: ignore
yield value

async def write(self, key: str, item: ResponseOrKey):
self.data[key] = item
# Keep a copy, as any change to the response will also change the cached value.
# While this should not be a problem for any use case I can imagine, we need to
# be consistent with other backends such as MongoDB, Redis, etc.
self.data[key] = deepcopy(item)
8 changes: 5 additions & 3 deletions aiohttp_client_cache/cache_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
from functools import singledispatch
from itertools import chain
from logging import getLogger
from typing import Any, Dict, Mapping, NoReturn, Tuple, Union
from typing import Any, Dict, Mapping, NoReturn, Tuple, Union, TYPE_CHECKING

from aiohttp import ClientResponse
from aiohttp.typedefs import StrOrURL
from attr import define, field
from multidict import CIMultiDict

if TYPE_CHECKING:
from aiohttp_client_cache.response import CachedResponse

# Value that may be set by either Cache-Control headers or CacheBackend params to disable caching
DO_NOT_CACHE = 0

Expand Down Expand Up @@ -129,7 +131,7 @@ def expires(self) -> datetime | None:
"""Convert the user/header-provided expiration value to a datetime"""
return get_expiration_datetime(self.expire_after)

def update_from_response(self, response: ClientResponse):
def update_from_response(self, response: CachedResponse):
"""Update expiration + actions based on response headers, if not previously set by request"""
if not self.cache_control:
return
Expand Down
Loading
Loading