Skip to content
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
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
configured JSON serializer, including bytes serializers.
- Fixed cache key collisions between request bodies, such as `data=b'false'` and `json=False`, and between request bodies and headers. Existing cache entries won't be reused after upgrading.
- Fixed cache key collisions between `data=` bodies that aiohttp sends differently, such as `b'x'` and `'x'`, `{'a': 1}` and `'a=1'`, `{'a': 'b&c=d'}` and `{'a': 'b', 'c': 'd'}`, `{'a': b'x'}` and `{'a': 'x'}`, or `{'a': [1, 2]}` and `{'a': '[1, 2]'}`. Header values are now URL-encoded in cache keys too.
- Fixed cache keys for relative URLs to include the session's `base_url`, so sessions with different base URLs no longer return each other's cached responses. Existing cache entries for relative URLs won't be reused after upgrading.
- Added `CachedSession.has_url()` and `CachedSession.delete_url()`, which create keys like the session's requests: with its JSON serializer and, with `include_headers`, its default headers.
- Requests with HTTP methods that aren't in `allowed_methods` now skip the cache entirely, so their JSON bodies are no longer serialized for a cache key. Added `CacheBackend.is_method_allowed()`.
- Added `_ExpandedRequestOptions.refresh` to satisfy Pyright type checking.
Expand Down
20 changes: 16 additions & 4 deletions aiohttp_client_cache/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from aiohttp import ClientSession
from aiohttp.typedefs import StrOrURL
from yarl import URL

from aiohttp_client_cache.backends import CacheBackend, get_valid_kwargs
from aiohttp_client_cache.cache_control import CacheActions, ExpirationTime, compose_refresh_headers
Expand Down Expand Up @@ -77,11 +78,12 @@ async def _request(
# Attempt to fetch cached response
headers = self._prepare_headers(kwargs.get('headers', None))
kwargs['headers'] = headers
url = self._resolve_url(str_or_url)
key = self.cache.create_key(
method, str_or_url, json_serialize=self._cache_json_serialize, **kwargs
method, url, json_serialize=self._cache_json_serialize, **kwargs
)
actions = self.cache.create_cache_actions(
key, str_or_url, expire_after=expire_after, refresh=refresh, **kwargs
key, url, expire_after=expire_after, refresh=refresh, **kwargs
)

if actions.skip_read:
Expand Down Expand Up @@ -169,11 +171,21 @@ async def _refresh_cached_response(

async def delete_url(self, url: StrOrURL, method: str = 'GET', **kwargs: Any):
"""Like :py:meth:`.CacheBackend.delete_url`, with keys created like this session's requests"""
await self.cache.delete_url(url, method, **self._cache_key_kwargs(**kwargs))
await self.cache.delete_url(
self._resolve_url(url), method, **self._cache_key_kwargs(**kwargs)
)

async def has_url(self, url: StrOrURL, method: str = 'GET', **kwargs: Any) -> bool:
"""Like :py:meth:`.CacheBackend.has_url`, with keys created like this session's requests"""
return await self.cache.has_url(url, method, **self._cache_key_kwargs(**kwargs))
return await self.cache.has_url(
self._resolve_url(url), method, **self._cache_key_kwargs(**kwargs)
)

def _resolve_url(self, str_or_url: StrOrURL) -> URL:
url = URL(str_or_url)
if self._base_url and not url.is_absolute():
return self._base_url.join(url)
return url

def _cache_key_kwargs(self, **kwargs: Any) -> dict[str, Any]:
return {
Expand Down
26 changes: 26 additions & 0 deletions test/unit/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,29 @@ async def ok(request):
assert await session.has_url(url)
await session.delete_url(url)
assert await cache.responses.size() == 0


async def test_session__base_url_in_cache_key(aiohttp_server):
base_urls = []
for text in ['a', 'b']:

async def handler(request, text=text):
return web.Response(text=text)

app = web.Application()
app.router.add_get('/x', handler)
base_urls.append((await aiohttp_server(app)).make_url('/'))

cache = CacheBackend()
for base_url, expected_text in zip(base_urls, ['a', 'b'], strict=True):
async with CachedSession(base_url, cache=cache) as session:
response = await session.get('/x')
assert await response.text() == expected_text
assert response.from_cache is False
assert await session.has_url('/x')
assert await session.has_url(base_url / 'x')

async with CachedSession(base_urls[0], cache=cache) as session:
await session.delete_url('/x')
assert not await cache.has_url(base_urls[0] / 'x')
assert await cache.has_url(base_urls[1] / 'x')
Loading