From ed2f3be83e8d6dd1b40ba8ea6bc6579496f25939 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 30 Apr 2023 15:43:54 -0400 Subject: [PATCH 1/4] Remove utcnow This removes the use of utcnow to avoid deprecation warnings, though ideally this would be fixed instead by switching over to using aware datetimes instead. --- cachecontrol/caches/redis_cache.py | 4 ++-- cachecontrol/heuristics.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cachecontrol/caches/redis_cache.py b/cachecontrol/caches/redis_cache.py index 98e9f3e..bd0d337 100644 --- a/cachecontrol/caches/redis_cache.py +++ b/cachecontrol/caches/redis_cache.py @@ -4,7 +4,7 @@ from __future__ import division -from datetime import datetime +from datetime import datetime, timezone from typing import TYPE_CHECKING, Optional, Union from cachecontrol.cache import BaseCache @@ -26,7 +26,7 @@ def set( if not expires: self.conn.set(key, value) elif isinstance(expires, datetime): - delta = expires - datetime.utcnow() + delta = expires - datetime.now(timezone.utc).replace(tzinfo=None) self.conn.setex(key, int(delta.total_seconds()), value) else: self.conn.setex(key, expires, value) diff --git a/cachecontrol/heuristics.py b/cachecontrol/heuristics.py index fdee762..cde142f 100644 --- a/cachecontrol/heuristics.py +++ b/cachecontrol/heuristics.py @@ -4,7 +4,7 @@ import calendar import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from email.utils import formatdate, parsedate, parsedate_tz from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional @@ -15,7 +15,7 @@ def expire_after(delta: timedelta, date: Optional[datetime] = None) -> datetime: - date = date or datetime.utcnow() + date = date or datetime.now(timezone.utc).replace(tzinfo=None) return date + delta From 3c8eb7ea0077359f7ad1d73a6026c29e57872b7e Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 30 Apr 2023 15:52:30 -0400 Subject: [PATCH 2/4] Use aware datetimes --- cachecontrol/caches/redis_cache.py | 5 ++++- cachecontrol/heuristics.py | 4 ++-- tests/test_expires_heuristics.py | 9 ++++++--- tests/test_storage_redis.py | 7 ++++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cachecontrol/caches/redis_cache.py b/cachecontrol/caches/redis_cache.py index bd0d337..ee03764 100644 --- a/cachecontrol/caches/redis_cache.py +++ b/cachecontrol/caches/redis_cache.py @@ -26,7 +26,10 @@ def set( if not expires: self.conn.set(key, value) elif isinstance(expires, datetime): - delta = expires - datetime.now(timezone.utc).replace(tzinfo=None) + now_utc = datetime.now(timezone.utc) + if expires.tzinfo is None: + now_utc = now_utc.replace(tzinfo=None) + delta = expires - now_utc self.conn.setex(key, int(delta.total_seconds()), value) else: self.conn.setex(key, expires, value) diff --git a/cachecontrol/heuristics.py b/cachecontrol/heuristics.py index cde142f..24356e0 100644 --- a/cachecontrol/heuristics.py +++ b/cachecontrol/heuristics.py @@ -15,7 +15,7 @@ def expire_after(delta: timedelta, date: Optional[datetime] = None) -> datetime: - date = date or datetime.now(timezone.utc).replace(tzinfo=None) + date = date or datetime.now(timezone.utc) return date + delta @@ -67,7 +67,7 @@ def update_headers(self, response: "HTTPResponse") -> Dict[str, str]: if "expires" not in response.headers: date = parsedate(response.headers["date"]) - expires = expire_after(timedelta(days=1), date=datetime(*date[:6])) + expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) headers["expires"] = datetime_to_header(expires) headers["cache-control"] = "public" return headers diff --git a/tests/test_expires_heuristics.py b/tests/test_expires_heuristics.py index 2dd1b5f..0e54df6 100644 --- a/tests/test_expires_heuristics.py +++ b/tests/test_expires_heuristics.py @@ -4,7 +4,7 @@ import calendar import time -from datetime import datetime +from datetime import datetime, timezone from email.utils import formatdate, parsedate from pprint import pprint from unittest.mock import Mock @@ -157,7 +157,9 @@ def test_last_modified_is_used(self): resp = DummyResponse(200, {"Date": self.now, "Last-Modified": self.week_ago}) modified = self.heuristic.update_headers(resp) assert ["expires"] == list(modified.keys()) - assert datetime(*parsedate(modified["expires"])[:6]) > datetime.now() + + expected = datetime(*parsedate(modified["expires"])[:6], tzinfo=timezone.utc) + assert expected > datetime.now(timezone.utc) def test_last_modified_is_not_used_when_cache_control_present(self): resp = DummyResponse( @@ -185,7 +187,8 @@ def test_last_modified_is_used_when_cache_control_public(self): ) modified = self.heuristic.update_headers(resp) assert ["expires"] == list(modified.keys()) - assert datetime(*parsedate(modified["expires"])[:6]) > datetime.now() + expected = datetime(*parsedate(modified["expires"][:6]), tzinfo=timezone.utc) + assert expected > datetime.now(timezone.utc) def test_warning_not_added_when_response_more_recent_than_24_hours(self): resp = DummyResponse(200, {"Date": self.now, "Last-Modified": self.week_ago}) diff --git a/tests/test_storage_redis.py b/tests/test_storage_redis.py index 6d6f07c..ac5b22c 100644 --- a/tests/test_storage_redis.py +++ b/tests/test_storage_redis.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import Mock from cachecontrol.caches import RedisCache @@ -17,6 +17,11 @@ def test_set_expiration_datetime(self): self.cache.set("foo", "bar", expires=datetime(2014, 2, 2)) assert self.conn.setex.called + def test_set_expiration_datetime_aware(self): + self.cache.set("foo", "bar", + expires=datetime(2014, 2, 2, tzinfo=timezone.utc)) + assert self.conn.setex.called + def test_set_expiration_int(self): self.cache.set("foo", "bar", expires=600) assert self.conn.setex.called From 9bf6dea20d2928c4fb77438666b4485037a986b1 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Jun 2023 08:04:55 +0800 Subject: [PATCH 3/4] fix a typo --- tests/test_expires_heuristics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_expires_heuristics.py b/tests/test_expires_heuristics.py index 0e54df6..c0895ef 100644 --- a/tests/test_expires_heuristics.py +++ b/tests/test_expires_heuristics.py @@ -187,7 +187,7 @@ def test_last_modified_is_used_when_cache_control_public(self): ) modified = self.heuristic.update_headers(resp) assert ["expires"] == list(modified.keys()) - expected = datetime(*parsedate(modified["expires"][:6]), tzinfo=timezone.utc) + expected = datetime(*parsedate(modified["expires"])[:6], tzinfo=timezone.utc) assert expected > datetime.now(timezone.utc) def test_warning_not_added_when_response_more_recent_than_24_hours(self): From dc1e34af418eff86d7ffe136c7cd8c73e8c5a8f1 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Jun 2023 08:09:03 +0800 Subject: [PATCH 4/4] ignore type checker error --- cachecontrol/heuristics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cachecontrol/heuristics.py b/cachecontrol/heuristics.py index 24356e0..5188c66 100644 --- a/cachecontrol/heuristics.py +++ b/cachecontrol/heuristics.py @@ -67,7 +67,7 @@ def update_headers(self, response: "HTTPResponse") -> Dict[str, str]: if "expires" not in response.headers: date = parsedate(response.headers["date"]) - expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) + expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) # type: ignore[misc] headers["expires"] = datetime_to_header(expires) headers["cache-control"] = "public" return headers