def test_update_from_response__disabled(): """Response headers should not be used if cache_control=False""" url = 'https://img.site.com/base/img.jpg' headers = [('Cache-Control', 'max-age=60')] response = MagicMock(url=url, headers=CIMultiDictProxy(CIMultiDict(headers))) actions = CacheActions(key='key', cache_control=False, expire_after=30) actions.update_from_response(response) assert actions.expire_after == 30
def test_update_from_response(headers, expected_expiration): """Test with Cache-Control response headers""" url = 'https://img.site.com/base/img.jpg' response = MagicMock(url=url, headers=CIMultiDictProxy(CIMultiDict(headers))) actions = CacheActions(key='key', cache_control=True) actions.update_from_response(response) if expected_expiration == DO_NOT_CACHE: assert actions.skip_write is True else: assert actions.expire_after == expected_expiration assert actions.skip_write is False
def test_init_from_request( get_url_expiration, request_expire_after, url_expire_after, header_expire_after, expected_expiration, ): """Test precedence with various combinations or per-request, per-session, per-URL, and Cache-Control expiration """ url = 'https://img.site.com/base/img.jpg' get_url_expiration.return_value = url_expire_after headers = { 'Cache-Control': f'max-age={header_expire_after}' } if header_expire_after else {} actions = CacheActions.from_request( key='key', url=URL(url), request_expire_after=request_expire_after, session_expire_after=1, cache_control=True, headers=headers, ) assert actions.expire_after == expected_expiration
async def test_session__cache_miss(mock_request): cache = MagicMock(spec=CacheBackend) cache.request.return_value = None, CacheActions() session = CachedSession(cache=cache) await session.get('http://test.url') assert mock_request.called is True
async def test_session__default_attrs(mock_request): cache = MagicMock(spec=CacheBackend) cache.request.return_value = None, CacheActions() session = CachedSession(cache=cache) response = await session.get('http://test.url') assert response.from_cache is False and response.is_expired is False
async def request( self, method: str, url: StrOrURL, expire_after: ExpirationTime = None, **kwargs, ) -> Tuple[Optional[CachedResponse], CacheActions]: """Fetch a cached response based on request info Args: method: HTTP method url: Request URL expire_after: Expiration time to set only for this request; overrides ``CachedSession.expire_after``, and accepts all the same values. kwargs: All other request arguments """ key = self.create_key(method, url, **kwargs) actions = CacheActions.from_request( key, url=url, request_expire_after=expire_after, session_expire_after=self.expire_after, urls_expire_after=self.urls_expire_after, cache_control=self.cache_control, **kwargs, ) # Skip reading from the cache, if specified by request headers response = None if actions.skip_read else await self.get_response( actions.key) return response, actions
async def test_all_param_types(mock_request, params) -> None: """Ensure that CachedSession.request() acceepts all the same parameter types as aiohttp""" cache = MagicMock(spec=CacheBackend) cache.request.return_value = None, CacheActions() session = CachedSession(cache=cache) response = await session.get('http://test.url', params=params) assert response.from_cache is False
async def test_session__cache_hit(mock_request): cache = MagicMock(spec=CacheBackend) response = AsyncMock(is_expired=False, url=URL('https://test.com')) cache.request.return_value = response, CacheActions() session = CachedSession(cache=cache) await session.get('http://test.url') assert mock_request.called is False
async def test_save_response(mock_is_cacheable): cache = CacheBackend() mock_response = get_mock_response() mock_response.history = [MagicMock(method='GET', url='test')] redirect_key = cache.create_key('GET', 'test') await cache.save_response(mock_response, CacheActions(key='key')) cached_response = await cache.responses.read('key') assert cached_response and isinstance(cached_response, CachedResponse) assert await cache.redirects.read(redirect_key) == 'key'
async def test_session__empty_cookies(mock_request): """Previous versions didn't set cookies if they were empty. Just make sure it doesn't explode.""" cache = MagicMock(spec=CacheBackend) response = AsyncMock(is_expired=False, url=URL('https://test.com'), cookies=None) cache.request.return_value = response, CacheActions() session = CachedSession(cache=cache) session.cookie_jar.clear() await session.get('http://test.url') assert not session.cookie_jar.filter_cookies('https://test.com')
def test_ignored_headers(directive): """Ensure that currently unimplemented Cache-Control headers do not affect behavior""" url = 'https://img.site.com/base/img.jpg' headers = {'Cache-Control': directive} # expiration = get_expiration(response, session_expire_after=1, cache_control=True) actions = CacheActions.from_request( key='key', url=URL(url), session_expire_after=1, cache_control=True, headers=headers, ) assert actions.expire_after == 1
def test_init_from_headers(headers, expected_expiration): """Test with Cache-Control request headers""" actions = CacheActions.from_headers(key='key', headers=CIMultiDict(headers)) assert actions.key == 'key' if expected_expiration == DO_NOT_CACHE: assert actions.skip_read is True assert actions.skip_write is True else: assert actions.expire_after == expected_expiration assert actions.skip_read is False assert actions.skip_write is False
async def save_response(self, response: ClientResponse, actions: CacheActions): """Save a response to the cache Args: response: Response to save actions: Specific cache actions to take """ actions.update_from_response(response) if not self.is_cacheable(response, actions): logger.debug(f'Not caching response for key: {actions.key}') return logger.debug(f'Saving response for key: {actions.key}') cached_response = await CachedResponse.from_client_response( response, actions.expires) await self.responses.write(actions.key, cached_response) # Alias any redirect requests to the same cache key for r in response.history: await self.redirects.write(self.create_key(r.method, r.url), actions.key)
async def test_session__cookies(mock_request): cache = MagicMock(spec=CacheBackend) response = AsyncMock( is_expired=False, url=URL('https://test.com'), cookies=SimpleCookie({'test_cookie': 'value'}), ) cache.request.return_value = response, CacheActions() session = CachedSession(cache=cache) session.cookie_jar.clear() await session.get('http://test.url') cookies = session.cookie_jar.filter_cookies('https://test.com') assert cookies['test_cookie'].value == 'value'
def test_init_from_settings(url, request_expire_after, expected_expiration): """Test with per-session, per-request, and per-URL expiration""" urls_expire_after = { '*.site_1.com': timedelta(hours=12), 'site_2.com/resource_1': timedelta(hours=20), 'site_2.com/resource_2': timedelta(days=7), 'site_2.com/static': -1, } actions = CacheActions.from_settings( key='key', url=URL(url), request_expire_after=request_expire_after, session_expire_after=1, urls_expire_after=urls_expire_after, ) assert actions.expire_after == expected_expiration