From 2fa1b3926f78c145b826729333aa568cc4eac186 Mon Sep 17 00:00:00 2001 From: Jordan Cook Date: Mon, 26 Jul 2021 18:06:35 -0500 Subject: [PATCH] WIP: Fix parsing HTTP dates from response headers --- HISTORY.md | 5 +++++ aiohttp_client_cache/__init__.py | 2 +- aiohttp_client_cache/cache_control.py | 7 ++++--- aiohttp_client_cache/response.py | 6 +++++- pyproject.toml | 2 +- test/conftest.py | 2 +- test/unit/test_response.py | 5 +++++ 7 files changed, 22 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index b8fcf0b..2df73df 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # History +## 0.4.3 (2021-07-27) +* Fix bug in which reponse header `Expires` was used for cache expiration even with `cache_control=False` +* Fix bug in which HTTP dates parsed from response headers weren't converted to UTC +* Add handling for invalid timestamps in `CachedResponse.is_expired` + ## 0.4.2 (2021-07-26) * Fix handling of `CachedResponse.encoding` when the response body is `None` diff --git a/aiohttp_client_cache/__init__.py b/aiohttp_client_cache/__init__.py index 2de53b6..84d7296 100644 --- a/aiohttp_client_cache/__init__.py +++ b/aiohttp_client_cache/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.2' +__version__ = '0.4.3' # flake8: noqa: F401, F403 try: diff --git a/aiohttp_client_cache/cache_control.py b/aiohttp_client_cache/cache_control.py index fc3a4c6..b9ad8c5 100644 --- a/aiohttp_client_cache/cache_control.py +++ b/aiohttp_client_cache/cache_control.py @@ -138,14 +138,15 @@ def coalesce(*values: Any, default=None) -> Any: def get_expiration_datetime(expire_after: ExpirationTime) -> Optional[datetime]: """Convert an expiration value in any supported format to an absolute datetime""" logger.debug(f'Determining expiration time based on: {expire_after}') + if isinstance(expire_after, str): + expire_after = parse_http_date(expire_after) if expire_after is None or expire_after == -1: return None - elif isinstance(expire_after, datetime): + if isinstance(expire_after, datetime): return to_utc(expire_after) - elif isinstance(expire_after, str): - return parse_http_date(expire_after) if not isinstance(expire_after, timedelta): + assert isinstance(expire_after, (int, float)) expire_after = timedelta(seconds=expire_after) return datetime.utcnow() + expire_after diff --git a/aiohttp_client_cache/response.py b/aiohttp_client_cache/response.py index 163adf7..fde928e 100644 --- a/aiohttp_client_cache/response.py +++ b/aiohttp_client_cache/response.py @@ -131,7 +131,11 @@ def host(self) -> str: @property def is_expired(self) -> bool: """Determine if this cached response is expired""" - return self.expires is not None and datetime.utcnow() > self.expires + # If for any reason the expiration check fails, consider it expired and fetch a new response + try: + return self.expires is not None and datetime.utcnow() > self.expires + except (AttributeError, TypeError, ValueError): + return False @property def links(self) -> LinkMultiDict: diff --git a/pyproject.toml b/pyproject.toml index 5dbca7e..7af5192 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiohttp-client-cache" -version = "0.4.2" +version = "0.4.3" description = "Persistent cache for aiohttp requests" authors = ["Jordan Cook"] license = "MIT License" diff --git a/test/conftest.py b/test/conftest.py index e5e6a6b..43660b4 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -28,7 +28,7 @@ ] HTTPDATE_STR = 'Fri, 16 APR 2021 21:13:00 GMT' -HTTPDATE_DATETIME = datetime(2021, 4, 16, 21, 13, tzinfo=timezone.utc) +HTTPDATE_DATETIME = datetime(2021, 4, 16, 21, 13) # Configure logging for pytest session diff --git a/test/unit/test_response.py b/test/unit/test_response.py index a38b952..e756794 100644 --- a/test/unit/test_response.py +++ b/test/unit/test_response.py @@ -68,6 +68,11 @@ async def test_is_expired(aiohttp_client): assert response.is_expired is True +async def test_is_expired__invalid(aiohttp_client): + response = await get_test_response(aiohttp_client, expires='asdf') + assert response.is_expired is False + + async def test_content_disposition(aiohttp_client): response = await get_test_response(aiohttp_client, '/valid_url') assert response.content_disposition.type == 'attachment'