From 1d57acb5b417facb7afcdd173e40eb9db2fc71e1 Mon Sep 17 00:00:00 2001 From: Kenneth Daily Date: Thu, 2 Jul 2026 11:48:50 -0700 Subject: [PATCH 1/5] Resolve Python 3.12 datetime.utcnow() DeprecationWarning Port of botocore commit b0015b7 (PR #3239) by @kurtmckee. Replace datetime.datetime.utcnow() with datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) to resolve the Python 3.12 DeprecationWarning while ensuring datetime instances remain timezone-unaware for backward compatibility. Update test mocks from .utcnow to .now to match the new code paths. Upstream: https://github.com/boto/botocore/commit/b0015b7 Upstream PR: https://github.com/boto/botocore/pull/3239 --- awscli/botocore/auth.py | 6 +++--- awscli/botocore/crt/auth.py | 12 ++---------- awscli/botocore/signers.py | 2 +- awscli/botocore/utils.py | 2 +- tests/functional/botocore/test_ec2.py | 2 +- tests/functional/botocore/test_lex.py | 2 +- tests/functional/botocore/test_s3express.py | 7 ------- tests/functional/botocore/test_sts.py | 2 +- tests/unit/botocore/auth/test_signers.py | 12 ++++++------ tests/unit/botocore/test_signers.py | 4 ++-- tests/unit/botocore/test_utils.py | 2 +- tests/utils/botocore/__init__.py | 6 +++--- 12 files changed, 22 insertions(+), 37 deletions(-) diff --git a/awscli/botocore/auth.py b/awscli/botocore/auth.py index 21a8033d3d2c..858993c4f97c 100644 --- a/awscli/botocore/auth.py +++ b/awscli/botocore/auth.py @@ -426,7 +426,7 @@ def signature(self, string_to_sign, request): def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - datetime_now = datetime.datetime.utcnow() + datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) # This could be a retry. Make sure the previous # authorization header is removed first. @@ -564,7 +564,7 @@ class S3ExpressPostAuth(S3ExpressAuth): REQUIRES_IDENTITY_CACHE = True def add_auth(self, request): - datetime_now = datetime.datetime.utcnow() + datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) fields = {} @@ -825,7 +825,7 @@ class S3SigV4PostAuth(SigV4Auth): """ def add_auth(self, request): - datetime_now = datetime.datetime.utcnow() + datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) fields = {} diff --git a/awscli/botocore/crt/auth.py b/awscli/botocore/crt/auth.py index 2b96c7e8c0d4..86104f3a808c 100644 --- a/awscli/botocore/crt/auth.py +++ b/awscli/botocore/crt/auth.py @@ -56,11 +56,7 @@ def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - # Use utcnow() because that's what gets mocked by tests, but set - # timezone because CRT assumes naive datetime is local time. - datetime_now = datetime.datetime.utcnow().replace( - tzinfo=datetime.timezone.utc - ) + datetime_now = datetime.datetime.now(datetime.timezone.utc) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) @@ -254,11 +250,7 @@ def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - # Use utcnow() because that's what gets mocked by tests, but set - # timezone because CRT assumes naive datetime is local time. - datetime_now = datetime.datetime.utcnow().replace( - tzinfo=datetime.timezone.utc - ) + datetime_now = datetime.datetime.now(datetime.timezone.utc) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) diff --git a/awscli/botocore/signers.py b/awscli/botocore/signers.py index 8cd7b37215fb..9f616db47650 100644 --- a/awscli/botocore/signers.py +++ b/awscli/botocore/signers.py @@ -717,7 +717,7 @@ def generate_presigned_post( policy = {} # Create an expiration date for the policy - datetime_now = datetime.datetime.utcnow() + datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) expire_date = datetime_now + datetime.timedelta(seconds=expires_in) policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601) diff --git a/awscli/botocore/utils.py b/awscli/botocore/utils.py index a3ac03e745e8..6e6be5c0e345 100644 --- a/awscli/botocore/utils.py +++ b/awscli/botocore/utils.py @@ -633,7 +633,7 @@ def _evaluate_expiration(self, credentials): refresh_interval_with_jitter = refresh_interval + random.randint( 120, 600 ) - current_time = datetime.datetime.utcnow() + current_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) refresh_offset = datetime.timedelta( seconds=refresh_interval_with_jitter ) diff --git a/tests/functional/botocore/test_ec2.py b/tests/functional/botocore/test_ec2.py index 81f4b764097a..e864a452824d 100644 --- a/tests/functional/botocore/test_ec2.py +++ b/tests/functional/botocore/test_ec2.py @@ -99,7 +99,7 @@ def setUp(self): mock.Mock(wraps=datetime.datetime), ) self.mocked_datetime = self.datetime_patch.start() - self.mocked_datetime.utcnow.return_value = self.now + self.mocked_datetime.now.return_value = self.now def tearDown(self): super().tearDown() diff --git a/tests/functional/botocore/test_lex.py b/tests/functional/botocore/test_lex.py index cded9c0920fe..5a27b7aeaaab 100644 --- a/tests/functional/botocore/test_lex.py +++ b/tests/functional/botocore/test_lex.py @@ -34,7 +34,7 @@ def test_unsigned_payload(self): timestamp = datetime(2017, 3, 22, 0, 0) with mock.patch('botocore.auth.datetime') as _datetime: - _datetime.datetime.utcnow.return_value = timestamp + _datetime.datetime.now.return_value = timestamp self.http_stubber.add_response(body=b'{}') with self.http_stubber: self.client.post_content(**params) diff --git a/tests/functional/botocore/test_s3express.py b/tests/functional/botocore/test_s3express.py index 91ec532fffcd..db40089cd611 100644 --- a/tests/functional/botocore/test_s3express.py +++ b/tests/functional/botocore/test_s3express.py @@ -108,7 +108,6 @@ def test_s3_express_auth_headers(self): class TestS3ExpressIdentityCache: def test_default_s3_express_cache(self, default_s3_client, mock_datetime): mock_datetime.now.return_value = DATE - mock_datetime.utcnow.return_value = DATE identity_cache = S3ExpressIdentityCache( default_s3_client, @@ -126,7 +125,6 @@ def test_s3_express_cache_one_network_call( self, default_s3_client, mock_datetime ): mock_datetime.now.return_value = DATE - mock_datetime.utcnow.return_value = DATE bucket = 'my_bucket' identity_cache = S3ExpressIdentityCache( @@ -151,7 +149,6 @@ def test_s3_express_cache_multiple_buckets( self, default_s3_client, mock_datetime ): mock_datetime.now.return_value = DATE - mock_datetime.utcnow.return_value = DATE bucket = 'my_bucket' other_bucket = 'other_bucket' @@ -204,7 +201,6 @@ def _call_get_object(self, client): ) def test_create_bucket(self, default_s3_client, mock_datetime): - mock_datetime.utcnow.return_value = DATE with ClientHTTPStubber(default_s3_client) as stubber: stubber.add_response() @@ -228,7 +224,6 @@ def test_create_bucket(self, default_s3_client, mock_datetime): self._assert_standard_sigv4_signature(stubber.requests[0].headers) def test_get_object(self, default_s3_client, mock_datetime): - mock_datetime.utcnow.return_value = DATE mock_datetime.now.return_value = DATE with ClientHTTPStubber(default_s3_client) as stubber: @@ -250,7 +245,6 @@ def test_get_object(self, default_s3_client, mock_datetime): def test_cache_with_multiple_requests( self, default_s3_client, mock_datetime ): - mock_datetime.utcnow.return_value = DATE mock_datetime.now.return_value = DATE with ClientHTTPStubber(default_s3_client) as stubber: @@ -275,7 +269,6 @@ def test_cache_with_multiple_requests( def test_delete_objects_injects_correct_checksum( self, default_s3_client, mock_datetime ): - mock_datetime.utcnow.return_value = DATE mock_datetime.now.return_value = DATE with ClientHTTPStubber(default_s3_client) as stubber: diff --git a/tests/functional/botocore/test_sts.py b/tests/functional/botocore/test_sts.py index 6bf343ea5830..02783cdaa2c4 100644 --- a/tests/functional/botocore/test_sts.py +++ b/tests/functional/botocore/test_sts.py @@ -34,7 +34,7 @@ def setUp(self): def test_presigned_url_contains_no_content_type(self): timestamp = datetime(2017, 3, 22, 0, 0) with mock.patch('botocore.auth.datetime') as _datetime: - _datetime.datetime.utcnow.return_value = timestamp + _datetime.datetime.now.return_value = timestamp url = self.client.generate_presigned_url('get_caller_identity', {}) # There should be no 'content-type' in x-amz-signedheaders diff --git a/tests/unit/botocore/auth/test_signers.py b/tests/unit/botocore/auth/test_signers.py index b1780d957dc8..0c04c7dcdc3f 100644 --- a/tests/unit/botocore/auth/test_signers.py +++ b/tests/unit/botocore/auth/test_signers.py @@ -31,7 +31,7 @@ def setUp(self): self.fixed_date = datetime.datetime(2014, 3, 10, 17, 2, 55, 0) self.datetime_patch = mock.patch('botocore.auth.datetime.datetime') self.datetime_mock = self.datetime_patch.start() - self.datetime_mock.utcnow.return_value = self.fixed_date + self.datetime_mock.now.return_value = self.fixed_date self.datetime_mock.strptime.return_value = self.fixed_date def tearDown(self): @@ -409,9 +409,9 @@ def test_thread_safe_timestamp(self): 'datetime', mock.Mock(wraps=datetime.datetime), ) as mock_datetime: - original_utcnow = datetime.datetime(2014, 1, 1, 0, 0) + original_now = datetime.datetime(2014, 1, 1, 0, 0) - mock_datetime.utcnow.return_value = original_utcnow + mock_datetime.now.return_value = original_now # Go through the add_auth process once. This will attach # a timestamp to the request at the beginning of auth. auth.add_auth(request) @@ -419,7 +419,7 @@ def test_thread_safe_timestamp(self): # Ensure the date is in the Authorization header self.assertIn('20140101', request.headers['Authorization']) # Now suppose the utc time becomes the next day all of a sudden - mock_datetime.utcnow.return_value = datetime.datetime( + mock_datetime.now.return_value = datetime.datetime( 2014, 1, 2, 0, 0 ) # Smaller methods like the canonical request and string_to_sign @@ -593,7 +593,7 @@ def setUp(self): mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime( + mocked_datetime.now.return_value = datetime.datetime( 2014, 1, 1, 0, 0 ) @@ -810,7 +810,7 @@ def setUp(self): mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime( + mocked_datetime.now.return_value = datetime.datetime( 2014, 1, 1, 0, 0 ) diff --git a/tests/unit/botocore/test_signers.py b/tests/unit/botocore/test_signers.py index b97bf585003f..fe37e81533a1 100644 --- a/tests/unit/botocore/test_signers.py +++ b/tests/unit/botocore/test_signers.py @@ -688,7 +688,7 @@ def setUp(self): self.datetime_mock = self.datetime_patch.start() self.fixed_date = datetime.datetime(2014, 3, 10, 17, 2, 55, 0) self.fixed_delta = datetime.timedelta(seconds=3600) - self.datetime_mock.datetime.utcnow.return_value = self.fixed_date + self.datetime_mock.datetime.now.return_value = self.fixed_date self.datetime_mock.timedelta.return_value = self.fixed_delta def tearDown(self): @@ -1141,7 +1141,7 @@ def test_generate_db_auth_token(self): clock = datetime.datetime(2016, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock result = generate_db_auth_token( self.client, hostname, port, username ) diff --git a/tests/unit/botocore/test_utils.py b/tests/unit/botocore/test_utils.py index ad6b9653c947..7cf4aef7a918 100644 --- a/tests/unit/botocore/test_utils.py +++ b/tests/unit/botocore/test_utils.py @@ -3238,7 +3238,7 @@ def test_v1_disabled_by_config(self): def _get_datetime(self, dt=None, offset=None, offset_func=operator.add): if dt is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) if offset is not None: dt = offset_func(dt, offset) diff --git a/tests/utils/botocore/__init__.py b/tests/utils/botocore/__init__.py index bbe6a59060da..1a201b7b67f4 100644 --- a/tests/utils/botocore/__init__.py +++ b/tests/utils/botocore/__init__.py @@ -579,12 +579,12 @@ class FreezeTime(contextlib.ContextDecorator): :param module: reference to imported module to patch (e.g. botocore.auth.datetime) :type date: datetime.datetime - :param date: datetime object specifying the output for utcnow() + :param date: datetime object specifying the output for now() """ def __init__(self, module, date=None): if date is None: - date = datetime.datetime.utcnow() + date = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) self.date = date self.datetime_patcher = mock.patch.object( module, 'datetime', mock.Mock(wraps=datetime.datetime) @@ -592,7 +592,7 @@ def __init__(self, module, date=None): def __enter__(self, *args, **kwargs): mock = self.datetime_patcher.start() - mock.utcnow.return_value = self.date + mock.now.return_value = self.date def __exit__(self, *args, **kwargs): self.datetime_patcher.stop() From 149ca4ddf2d96ca538a56b862382d8d254136166 Mon Sep 17 00:00:00 2001 From: Kenneth Daily Date: Thu, 2 Jul 2026 11:49:45 -0700 Subject: [PATCH 2/5] Consolidate datetime invocations to get_current_datetime() Port of botocore commit 7ebb8b8 (PR #3533) by @nateprewitt. Introduce get_current_datetime() utility in botocore.compat to centralize the datetime.now(timezone.utc) pattern. This makes it easier to mock in tests and provides a single point of maintenance for timezone-aware datetime retrieval. The CRT auth module uses get_current_datetime(remove_tzinfo=False) since the CRT library expects timezone-aware datetime objects. Upstream: https://github.com/boto/botocore/commit/7ebb8b8 Upstream PR: https://github.com/boto/botocore/pull/3533 --- awscli/botocore/auth.py | 7 ++++--- awscli/botocore/compat.py | 8 ++++++++ awscli/botocore/crt/auth.py | 13 +++++++++---- awscli/botocore/signers.py | 4 ++-- awscli/botocore/utils.py | 3 ++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/awscli/botocore/auth.py b/awscli/botocore/auth.py index 858993c4f97c..f17e1d1bfd49 100644 --- a/awscli/botocore/auth.py +++ b/awscli/botocore/auth.py @@ -26,6 +26,7 @@ HTTPHeaders, encodebytes, ensure_unicode, + get_current_datetime, json, parse_qs, quote, @@ -426,7 +427,7 @@ def signature(self, string_to_sign, request): def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + datetime_now = get_current_datetime() request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) # This could be a retry. Make sure the previous # authorization header is removed first. @@ -564,7 +565,7 @@ class S3ExpressPostAuth(S3ExpressAuth): REQUIRES_IDENTITY_CACHE = True def add_auth(self, request): - datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + datetime_now = get_current_datetime() request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) fields = {} @@ -825,7 +826,7 @@ class S3SigV4PostAuth(SigV4Auth): """ def add_auth(self, request): - datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + datetime_now = get_current_datetime() request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP) fields = {} diff --git a/awscli/botocore/compat.py b/awscli/botocore/compat.py index b9afd2a97ff8..fc5ad5518919 100644 --- a/awscli/botocore/compat.py +++ b/awscli/botocore/compat.py @@ -289,6 +289,14 @@ def get_tzinfo_options(): return (tzlocal,) +def get_current_datetime(remove_tzinfo=True): + """Retrieve the current timezone in UTC, with or without an explicit timezone.""" + datetime_now = datetime.datetime.now(datetime.timezone.utc) + if remove_tzinfo: + datetime_now = datetime_now.replace(tzinfo=None) + return datetime_now + + ######################################################## # urllib3 compat backports # ######################################################## diff --git a/awscli/botocore/crt/auth.py b/awscli/botocore/crt/auth.py index 86104f3a808c..50b26b7fb59b 100644 --- a/awscli/botocore/crt/auth.py +++ b/awscli/botocore/crt/auth.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import datetime from io import BytesIO import awscrt.auth @@ -23,7 +22,13 @@ _get_body_as_dict, _host_from_url, ) -from botocore.compat import HTTPHeaders, parse_qs, urlsplit, urlunsplit +from botocore.compat import ( + HTTPHeaders, + get_current_datetime, + parse_qs, + urlsplit, + urlunsplit, +) from botocore.exceptions import NoCredentialsError from botocore.useragent import register_feature_id from botocore.utils import percent_encode_sequence @@ -56,7 +61,7 @@ def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - datetime_now = datetime.datetime.now(datetime.timezone.utc) + datetime_now = get_current_datetime(remove_tzinfo=False) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) @@ -250,7 +255,7 @@ def add_auth(self, request): if self.credentials is None: raise NoCredentialsError() - datetime_now = datetime.datetime.now(datetime.timezone.utc) + datetime_now = get_current_datetime(remove_tzinfo=False) # Use existing 'X-Amz-Content-SHA256' header if able existing_sha256 = self._get_existing_sha256(request) diff --git a/awscli/botocore/signers.py b/awscli/botocore/signers.py index 9f616db47650..4636ffdf70b2 100644 --- a/awscli/botocore/signers.py +++ b/awscli/botocore/signers.py @@ -18,7 +18,7 @@ import botocore import botocore.auth from botocore.awsrequest import create_request_object, prepare_request_dict -from botocore.compat import OrderedDict +from botocore.compat import OrderedDict, get_current_datetime from botocore.exceptions import ( ParamValidationError, UnknownClientMethodError, @@ -717,7 +717,7 @@ def generate_presigned_post( policy = {} # Create an expiration date for the policy - datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + datetime_now = get_current_datetime() expire_date = datetime_now + datetime.timedelta(seconds=expires_in) policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601) diff --git a/awscli/botocore/utils.py b/awscli/botocore/utils.py index 6e6be5c0e345..2303edc1789f 100644 --- a/awscli/botocore/utils.py +++ b/awscli/botocore/utils.py @@ -42,6 +42,7 @@ from awscrt.crypto import EC from botocore.compat import ( MD5_AVAILABLE, + get_current_datetime, get_md5, get_tzinfo_options, json, @@ -633,7 +634,7 @@ def _evaluate_expiration(self, credentials): refresh_interval_with_jitter = refresh_interval + random.randint( 120, 600 ) - current_time = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + current_time = get_current_datetime() refresh_offset = datetime.timedelta( seconds=refresh_interval_with_jitter ) From 55b3e1201c337f8a5ea32de6261cdee879d08816 Mon Sep 17 00:00:00 2001 From: Kenneth Daily Date: Thu, 2 Jul 2026 11:51:07 -0700 Subject: [PATCH 3/5] Add timezones to test dates Port of botocore commit bbed2c0 (PR #3533) by @nateprewitt. Add tzinfo=datetime.timezone.utc to test datetime fixtures to match the real behavior of get_current_datetime() which internally calls datetime.datetime.now(datetime.timezone.utc). This ensures mocked return values are consistent with production code behavior, which is important for the CRT auth path that uses remove_tzinfo=False. Upstream: https://github.com/boto/botocore/commit/bbed2c0 Upstream PR: https://github.com/boto/botocore/pull/3533 --- tests/functional/botocore/test_ec2.py | 2 +- tests/functional/botocore/test_lex.py | 4 ++-- tests/functional/botocore/test_s3express.py | 2 +- tests/functional/botocore/test_sts.py | 4 ++-- tests/unit/botocore/auth/test_signers.py | 10 +++++----- tests/unit/botocore/test_utils.py | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/functional/botocore/test_ec2.py b/tests/functional/botocore/test_ec2.py index e864a452824d..39423aacfbd5 100644 --- a/tests/functional/botocore/test_ec2.py +++ b/tests/functional/botocore/test_ec2.py @@ -92,7 +92,7 @@ def setUp(self): '%s\n' '\n' ) - self.now = datetime.datetime(2011, 9, 9, 23, 36) + self.now = datetime.datetime(2011, 9, 9, 23, 36, tzinfo=datetime.timezone.utc) self.datetime_patch = mock.patch.object( botocore.auth.datetime, 'datetime', diff --git a/tests/functional/botocore/test_lex.py b/tests/functional/botocore/test_lex.py index 5a27b7aeaaab..1e9a3085fb05 100644 --- a/tests/functional/botocore/test_lex.py +++ b/tests/functional/botocore/test_lex.py @@ -10,7 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from datetime import datetime +import datetime from tests import BaseSessionTest, ClientHTTPStubber, mock @@ -31,7 +31,7 @@ def test_unsigned_payload(self): 'inputStream': b'', } - timestamp = datetime(2017, 3, 22, 0, 0) + timestamp = datetime.datetime(2017, 3, 22, 0, 0, tzinfo=datetime.timezone.utc) with mock.patch('botocore.auth.datetime') as _datetime: _datetime.datetime.now.return_value = timestamp diff --git a/tests/functional/botocore/test_s3express.py b/tests/functional/botocore/test_s3express.py index db40089cd611..7029bbac4029 100644 --- a/tests/functional/botocore/test_s3express.py +++ b/tests/functional/botocore/test_s3express.py @@ -36,7 +36,7 @@ S3EXPRESS_BUCKET = "mytestbucket--usw2-az5--x-s3" -DATE = datetime.datetime(2023, 11, 26, 0, 0, 0, tzinfo=tzutc()) +DATE = datetime.datetime(2023, 11, 26, 0, 0, 0, tzinfo=datetime.timezone.utc) CREATE_SESSION_RESPONSE = ( diff --git a/tests/functional/botocore/test_sts.py b/tests/functional/botocore/test_sts.py index 02783cdaa2c4..fa3a0b2f8c93 100644 --- a/tests/functional/botocore/test_sts.py +++ b/tests/functional/botocore/test_sts.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import re -from datetime import datetime +import datetime from botocore.config import Config from botocore.stub import Stubber @@ -32,7 +32,7 @@ def setUp(self): self.stubber.activate() def test_presigned_url_contains_no_content_type(self): - timestamp = datetime(2017, 3, 22, 0, 0) + timestamp = datetime.datetime(2017, 3, 22, 0, 0, tzinfo=datetime.timezone.utc) with mock.patch('botocore.auth.datetime') as _datetime: _datetime.datetime.now.return_value = timestamp url = self.client.generate_presigned_url('get_caller_identity', {}) diff --git a/tests/unit/botocore/auth/test_signers.py b/tests/unit/botocore/auth/test_signers.py index 0c04c7dcdc3f..e45712ca866b 100644 --- a/tests/unit/botocore/auth/test_signers.py +++ b/tests/unit/botocore/auth/test_signers.py @@ -28,7 +28,7 @@ class BaseTestWithFixedDate(unittest.TestCase): def setUp(self): - self.fixed_date = datetime.datetime(2014, 3, 10, 17, 2, 55, 0) + self.fixed_date = datetime.datetime(2014, 3, 10, 17, 2, 55, 0, tzinfo=datetime.timezone.utc) self.datetime_patch = mock.patch('botocore.auth.datetime.datetime') self.datetime_mock = self.datetime_patch.start() self.datetime_mock.now.return_value = self.fixed_date @@ -409,7 +409,7 @@ def test_thread_safe_timestamp(self): 'datetime', mock.Mock(wraps=datetime.datetime), ) as mock_datetime: - original_now = datetime.datetime(2014, 1, 1, 0, 0) + original_now = datetime.datetime(2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc) mock_datetime.now.return_value = original_now # Go through the add_auth process once. This will attach @@ -420,7 +420,7 @@ def test_thread_safe_timestamp(self): self.assertIn('20140101', request.headers['Authorization']) # Now suppose the utc time becomes the next day all of a sudden mock_datetime.now.return_value = datetime.datetime( - 2014, 1, 2, 0, 0 + 2014, 1, 2, 0, 0, tzinfo=datetime.timezone.utc ) # Smaller methods like the canonical request and string_to_sign # should have the timestamp attached to the request in their @@ -594,7 +594,7 @@ def setUp(self): ) mocked_datetime = self.datetime_patcher.start() mocked_datetime.now.return_value = datetime.datetime( - 2014, 1, 1, 0, 0 + 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc ) def tearDown(self): @@ -811,7 +811,7 @@ def setUp(self): ) mocked_datetime = self.datetime_patcher.start() mocked_datetime.now.return_value = datetime.datetime( - 2014, 1, 1, 0, 0 + 2014, 1, 1, 0, 0, tzinfo=datetime.timezone.utc ) def tearDown(self): diff --git a/tests/unit/botocore/test_utils.py b/tests/unit/botocore/test_utils.py index 7cf4aef7a918..f10638451ce1 100644 --- a/tests/unit/botocore/test_utils.py +++ b/tests/unit/botocore/test_utils.py @@ -111,7 +111,7 @@ from tests import FreezeTime, RawResponse, create_session, mock, unittest -DATE = datetime.datetime(2021, 12, 10, 00, 00, 00) +DATE = datetime.datetime(2021, 12, 10, 00, 00, 00, tzinfo=datetime.timezone.utc) DT_FORMAT = "%Y-%m-%dT%H:%M:%SZ" From 65c966b196da0eb864624b131b22006c74697fb8 Mon Sep 17 00:00:00 2001 From: Kenneth Daily Date: Thu, 2 Jul 2026 12:17:01 -0700 Subject: [PATCH 4/5] Update s3 presign test mock from utcnow to now Follow-up to the botocore utcnow deprecation port. The presign command test was still mocking datetime.datetime.utcnow instead of .now, causing 6 test failures. Upstream: https://github.com/boto/botocore/commit/b0015b7 Upstream PR: https://github.com/boto/botocore/pull/3239 --- tests/functional/s3/test_presign_command.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/s3/test_presign_command.py b/tests/functional/s3/test_presign_command.py index 43ede9a3d56b..27e8f584ddc3 100644 --- a/tests/functional/s3/test_presign_command.py +++ b/tests/functional/s3/test_presign_command.py @@ -22,7 +22,7 @@ temporary_file, ) -# Values used to fix time.time() and datetime.datetime.utcnow() +# Values used to fix time.time() and datetime.datetime.now() # so we know the exact values of the signatures generated. FROZEN_TIMESTAMP = 1471305652 DEFAULT_EXPIRES = 3600 @@ -76,7 +76,7 @@ def parse_query_string(self, query_string): def get_presigned_url_for_cmd(self, cmdline): with mock.patch('time.time', FROZEN_TIME): with mock.patch('datetime.datetime') as d: - d.utcnow = FROZEN_DATETIME + d.now = FROZEN_DATETIME stdout = self.assert_params_for_cmd(cmdline, None)[0].strip() return stdout From 3d46df6d98a1cdb0e8df36c0c0f18a22eb5c4cdd Mon Sep 17 00:00:00 2001 From: Kenneth Daily Date: Wed, 15 Jul 2026 11:27:43 -0700 Subject: [PATCH 5/5] Resolve remaining datetime.utcnow() usage Replace datetime.utcnow() with datetime.now(timezone.utc) in awscli customization modules that were not covered by the botocore port: - awscli/customizations/eks/get_token.py - awscli/customizations/logs/tail.py (via _utcnow helper) - awscli/customizations/ec2/bundleinstance.py Update test mocks from .utcnow to .now across all affected test files: - tests/functional/botocore/test_s3express.py - tests/functional/eks/test_get_token.py - tests/functional/logs/test_tail.py - tests/functional/dsql/test_generate_db_auth_token.py - tests/functional/rds/test_generate_db_auth_token.py - tests/functional/ec2instanceconnect/test_opentunnel.py - tests/functional/ec2/test_bundle_instance.py --- awscli/customizations/ec2/bundleinstance.py | 2 +- awscli/customizations/eks/get_token.py | 4 ++-- awscli/customizations/logs/tail.py | 9 +++++++-- tests/functional/botocore/test_s3express.py | 1 + .../functional/dsql/test_generate_db_auth_token.py | 6 +++--- tests/functional/ec2/test_bundle_instance.py | 2 +- .../ec2instanceconnect/test_opentunnel.py | 2 +- tests/functional/eks/test_get_token.py | 8 ++++---- tests/functional/logs/test_tail.py | 14 ++++---------- .../functional/rds/test_generate_db_auth_token.py | 2 +- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/awscli/customizations/ec2/bundleinstance.py b/awscli/customizations/ec2/bundleinstance.py index 240540ff8317..8ca62dd09597 100644 --- a/awscli/customizations/ec2/bundleinstance.py +++ b/awscli/customizations/ec2/bundleinstance.py @@ -129,7 +129,7 @@ def _generate_policy(params): # Called if there is no policy supplied by the user. # Creates a policy that provides access for 24 hours. delta = datetime.timedelta(hours=24) - expires = datetime.datetime.utcnow() + delta + expires = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) + delta expires_iso = expires.strftime("%Y-%m-%dT%H:%M:%S.%fZ") policy = POLICY.format( expires=expires_iso, bucket=params['Bucket'], prefix=params['Prefix'] diff --git a/awscli/customizations/eks/get_token.py b/awscli/customizations/eks/get_token.py index 00fc4d348b9f..853bdcd8c05a 100644 --- a/awscli/customizations/eks/get_token.py +++ b/awscli/customizations/eks/get_token.py @@ -14,7 +14,7 @@ import json import os import sys -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import botocore from botocore.model import ServiceId @@ -105,7 +105,7 @@ class GetTokenCommand(BasicCommand): ] def get_expiration_time(self): - token_expiration = datetime.utcnow() + timedelta( + token_expiration = datetime.now(timezone.utc).replace(tzinfo=None) + timedelta( minutes=TOKEN_EXPIRATION_MINS ) return token_expiration.strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/awscli/customizations/logs/tail.py b/awscli/customizations/logs/tail.py index 1afcdf363353..85de75920fb7 100644 --- a/awscli/customizations/logs/tail.py +++ b/awscli/customizations/logs/tail.py @@ -14,7 +14,7 @@ import re import time from collections import defaultdict -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import colorama from botocore.utils import datetime2timestamp, parse_timestamp @@ -251,6 +251,11 @@ def _should_use_color(self, parsed_globals): return is_a_tty() +def _utcnow(): + """Return the current UTC time as a timezone-aware datetime.""" + return datetime.now(timezone.utc) + + class TimestampUtils: _RELATIVE_TIMESTAMP_REGEX = re.compile( r"(?P\d+)(?Ps|m|h|d|w)$" @@ -266,7 +271,7 @@ class TimestampUtils: def __init__(self, now=None): self._now = now if now is None: - self._now = datetime.utcnow + self._now = _utcnow def to_epoch_millis(self, timestamp): re_match = self._RELATIVE_TIMESTAMP_REGEX.match(timestamp) diff --git a/tests/functional/botocore/test_s3express.py b/tests/functional/botocore/test_s3express.py index 7029bbac4029..2830697350cb 100644 --- a/tests/functional/botocore/test_s3express.py +++ b/tests/functional/botocore/test_s3express.py @@ -201,6 +201,7 @@ def _call_get_object(self, client): ) def test_create_bucket(self, default_s3_client, mock_datetime): + mock_datetime.now.return_value = DATE with ClientHTTPStubber(default_s3_client) as stubber: stubber.add_response() diff --git a/tests/functional/dsql/test_generate_db_auth_token.py b/tests/functional/dsql/test_generate_db_auth_token.py index 2b4845358480..39871ab2b01e 100644 --- a/tests/functional/dsql/test_generate_db_auth_token.py +++ b/tests/functional/dsql/test_generate_db_auth_token.py @@ -50,7 +50,7 @@ def test_generate_simple_token(self): clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock stdout, _, _ = self.run_cmd(command, expected_rc=0) # Expected hashes are always the same as session variables come from the BaseAwsCommandParamsTest class @@ -79,7 +79,7 @@ def test_missing_hostname_raises_exception(self): clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock stdout, _, _ = self.run_cmd(command, expected_rc=252) @@ -92,7 +92,7 @@ def test_generate_simple_token(self): clock = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock stdout, _, _ = self.run_cmd(command, expected_rc=0) # Expected hashes are always the same as session variables come from the BaseAwsCommandParamsTest class diff --git a/tests/functional/ec2/test_bundle_instance.py b/tests/functional/ec2/test_bundle_instance.py index 5442929c9f8a..3aba0c52f412 100644 --- a/tests/functional/ec2/test_bundle_instance.py +++ b/tests/functional/ec2/test_bundle_instance.py @@ -42,7 +42,7 @@ def setUp(self): mock.Mock(wraps=datetime.datetime), ) mocked_datetime = self.datetime_patcher.start() - mocked_datetime.utcnow.return_value = datetime.datetime(2013, 8, 9) + mocked_datetime.now.return_value = datetime.datetime(2013, 8, 9) def tearDown(self): super(TestBundleInstance, self).tearDown() diff --git a/tests/functional/ec2instanceconnect/test_opentunnel.py b/tests/functional/ec2instanceconnect/test_opentunnel.py index e1742665e97a..e66de7e78530 100644 --- a/tests/functional/ec2instanceconnect/test_opentunnel.py +++ b/tests/functional/ec2instanceconnect/test_opentunnel.py @@ -409,7 +409,7 @@ def request_params_for_describe_eice(): def datetime_utcnow_patch(): clock = datetime.datetime(2020, 1, 1, 1, 1, 1, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock yield dt diff --git a/tests/functional/eks/test_get_token.py b/tests/functional/eks/test_get_token.py index 0e78f9a02137..7d68e86f5749 100644 --- a/tests/functional/eks/test_get_token.py +++ b/tests/functional/eks/test_get_token.py @@ -78,7 +78,7 @@ def set_kubernetes_exec_info(self, api_version): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_get_token(self, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) + mock_datetime.now.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) cmd = 'eks get-token --cluster-name %s' % self.cluster_name response = self.run_get_token(cmd) self.assertEqual( @@ -96,7 +96,7 @@ def test_get_token(self, mock_datetime): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_query_nested_object(self, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) + mock_datetime.now.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) cmd = 'eks get-token --cluster-name %s' % self.cluster_name cmd += ' --query status' response = self.run_get_token(cmd) @@ -119,7 +119,7 @@ def test_query_value(self): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_output_text(self, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) + mock_datetime.now.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) cmd = 'eks get-token --cluster-name %s' % self.cluster_name cmd += ' --output text' stdout, _, _ = self.run_cmd(cmd) @@ -129,7 +129,7 @@ def test_output_text(self, mock_datetime): @mock.patch('awscli.customizations.eks.get_token.datetime') def test_output_table(self, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) + mock_datetime.now.return_value = datetime(2019, 10, 23, 23, 0, 0, 0) cmd = 'eks get-token --cluster-name %s' % self.cluster_name cmd += ' --output table' stdout, _, _ = self.run_cmd(cmd) diff --git a/tests/functional/logs/test_tail.py b/tests/functional/logs/test_tail.py index 0024bd48ca6f..b35613495711 100644 --- a/tests/functional/logs/test_tail.py +++ b/tests/functional/logs/test_tail.py @@ -154,12 +154,9 @@ def test_tail_with_follow(self): ) def test_tail_defaults_to_10m(self): - datetime_mock = mock.Mock(wraps=datetime) - datetime_mock.utcnow = mock.Mock( - return_value=datetime(1970, 1, 1, 0, 10, 1, tzinfo=tz.tzutc()) - ) with mock.patch( - 'awscli.customizations.logs.tail.datetime', new=datetime_mock + 'awscli.customizations.logs.tail._utcnow', + return_value=datetime(1970, 1, 1, 0, 10, 1, tzinfo=tz.tzutc()), ): self.assert_params_for_cmd( 'logs tail %s' % self.group_name, @@ -181,12 +178,9 @@ def test_tail_with_since(self): ) def test_tail_with_relative_since(self): - datetime_mock = mock.Mock(wraps=datetime) - datetime_mock.utcnow = mock.Mock( - return_value=datetime(1970, 1, 1, 0, 0, 2, tzinfo=tz.tzutc()) - ) with mock.patch( - 'awscli.customizations.logs.tail.datetime', new=datetime_mock + 'awscli.customizations.logs.tail._utcnow', + return_value=datetime(1970, 1, 1, 0, 0, 2, tzinfo=tz.tzutc()), ): self.assert_params_for_cmd( 'logs tail %s --since 1s' % self.group_name, diff --git a/tests/functional/rds/test_generate_db_auth_token.py b/tests/functional/rds/test_generate_db_auth_token.py index 4cc562af4eec..4ff83e17336a 100644 --- a/tests/functional/rds/test_generate_db_auth_token.py +++ b/tests/functional/rds/test_generate_db_auth_token.py @@ -50,7 +50,7 @@ def test_generate_simple_token(self): clock = datetime.datetime(2016, 11, 7, 17, 39, 33, tzinfo=tzutc()) with mock.patch('datetime.datetime') as dt: - dt.utcnow.return_value = clock + dt.now.return_value = clock stdout, _, _ = self.run_cmd(command, expected_rc=0) expected = (