Skip to content

Commit

Permalink
Add autoclose option to close backend when session closes
Browse files Browse the repository at this point in the history
  • Loading branch information
JWCook committed Jul 23, 2023
1 parent f862e6d commit 4f4a3f7
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 10 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# History


## 0.8.3 (2023-07-TBD)
* Add `autoclose` option to `CacheBackend` to close backend connections when the session context exits.
* Enabled by default for SQLite backend, and disabled by default for other backends.

## 0.8.2 (2023-07-14)
* Add some missing type annotations to backend classes
* Fix passing connection parameters to MongoDB backend
Expand Down
8 changes: 8 additions & 0 deletions aiohttp_client_cache/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(
allowed_methods: Tuple[str, ...] = ('GET', 'HEAD'),
include_headers: bool = False,
ignored_params: Optional[Iterable[str]] = None,
autoclose: bool = False,
cache_control: bool = False,
filter_fn: _FilterFn = lambda r: True,
**kwargs: Any,
Expand All @@ -58,6 +59,7 @@ def __init__(
allowed_methods: Only cache requests with these HTTP methods
include_headers: Cache requests with different headers separately
ignored_params: Request parameters to be excluded from the cache key
autoclose: Close any active backend connections when the session is closed
cache_control: Use Cache-Control response headers
filter_fn: function that takes a :py:class:`aiohttp.ClientResponse` object and
returns a boolean indicating whether or not that response should be cached. Will be
Expand All @@ -70,6 +72,7 @@ def __init__(
self.allowed_methods = allowed_methods
self.cache_control = cache_control
self.filter_fn = filter_fn
self.autoclose = autoclose
self.disabled = False

# Allows multiple redirects or other aliased URLs to point to the same cached response
Expand Down Expand Up @@ -252,6 +255,11 @@ async def close(self):
await self.responses.close()
await self.redirects.close()

async def close_if_enabled(self):
"""Close any active connections, if ``autoclose`` is enabled"""
if self.autoclose:
await self.close()


# TODO: Support yarl.URL like aiohttp does?
# TODO: Implement __aiter__?
Expand Down
5 changes: 0 additions & 5 deletions aiohttp_client_cache/backends/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ def __init__(
self.responses = RedisCache(cache_name, 'responses', address=address, **kwargs)
self.redirects = RedisCache(cache_name, 'redirects', address=address, **kwargs)

async def close(self):
"""Close any active connections"""
await self.responses.close()
await self.redirects.close()


class RedisCache(BaseCache):
"""An async interface for caching objects in Redis.
Expand Down
6 changes: 3 additions & 3 deletions aiohttp_client_cache/backends/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
import aiosqlite

from aiohttp_client_cache.backends import BaseCache, CacheBackend, ResponseOrKey, get_valid_kwargs
from aiohttp_client_cache.signatures import extend_init_signature, sqlite_template
from aiohttp_client_cache.signatures import sqlite_template

bulk_commit_var: ContextVar[bool] = ContextVar('bulk_commit', default=False)


@extend_init_signature(CacheBackend, sqlite_template)
class SQLiteBackend(CacheBackend):
"""Async cache backend for `SQLite <https://www.sqlite.org>`_
(requires `aiosqlite <https://aiosqlite.omnilib.dev>`_)
Expand All @@ -37,9 +36,10 @@ def __init__(
cache_name: str = 'aiohttp-cache',
use_temp: bool = False,
fast_save: bool = False,
autoclose: bool = True,
**kwargs: Any,
):
super().__init__(cache_name=cache_name, **kwargs)
super().__init__(cache_name=cache_name, autoclose=autoclose, **kwargs)
self.responses = SQLitePickleCache(
cache_name, 'responses', use_temp=use_temp, fast_save=fast_save, **kwargs
)
Expand Down
1 change: 1 addition & 0 deletions aiohttp_client_cache/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async def _request(
async def close(self):
"""Close both aiohttp connector and any backend connection(s) on contextmanager exit"""
await super().close()
await self.cache.close_if_enabled()

@asynccontextmanager
async def disabled(self):
Expand Down
4 changes: 3 additions & 1 deletion aiohttp_client_cache/signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def extend_init_signature(super_class: Type, *extra_functions: Callable) -> Call
def wrapper(target_class: Type):
try:
# Modify init signature + docstring
revision = extend_signature(super_class.__init__, *extra_functions)
revision = extend_signature(
super_class.__init__, target_class.__init__, *extra_functions
)
target_class.__init__ = revision(target_class.__init__)
# Include init docs in class docs
target_class.__doc__ = target_class.__doc__ or ''
Expand Down
15 changes: 15 additions & 0 deletions test/integration/base_backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Any, AsyncIterator, Dict, Type
from unittest.mock import patch
from uuid import uuid4

import pytest
Expand Down Expand Up @@ -249,6 +250,20 @@ async def test_cookies_with_redirect(self):
cookies = session.cookie_jar.filter_cookies(httpbin())
assert cookies['test_cookie'].value == 'value'

@skip_37
@patch.object(CacheBackend, 'close')
async def test_autoclose(self, mock_close):
async with self.init_session(autoclose=True) as session:
await session.get(httpbin('get'))
mock_close.assert_called_once()

@skip_37
@patch.object(CacheBackend, 'close')
async def test_autoclose__disabled(self, mock_close):
async with self.init_session(autoclose=False) as session:
await session.get(httpbin('get'))
mock_close.assert_not_called()

async def test_serializer__pickle(self):
"""Without a secret key, plain pickle should be used"""
async with self.init_session() as session:
Expand Down
12 changes: 11 additions & 1 deletion test/integration/test_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import pytest

from aiohttp_client_cache.backends import CacheBackend
from aiohttp_client_cache.backends.sqlite import SQLiteBackend, SQLiteCache, SQLitePickleCache
from test.conftest import CACHE_NAME, skip_37
from test.conftest import CACHE_NAME, httpbin, skip_37
from test.integration import BaseBackendTest, BaseStorageTest

pytestmark = pytest.mark.asyncio
Expand Down Expand Up @@ -145,3 +146,12 @@ class TestSQLitePickleCache(BaseStorageTest):
class TestSQLiteBackend(BaseBackendTest):
backend_class = SQLiteBackend
init_kwargs = {'use_temp': True}

@skip_37
@patch.object(CacheBackend, 'close')
async def test_autoclose__default(self, mock_close):
"""By default, the backend should be closed when the session is closed"""

async with self.init_session() as session:
await session.get(httpbin('get'))
mock_close.assert_called_once()

0 comments on commit 4f4a3f7

Please sign in to comment.