Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove datetime.datetime.utcnow #3145

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions botocore/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimal change here is:

Suggested change
datetime_now = datetime.datetime.now(datetime.timezone.utc)
datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)

The code as it currently stands makes datetime_now timezone-aware. The change I'm suggesting above resolves the DeprecationWarning without fundamentally altering datetime_now in the process.

You can see how datetime_now was affected by looking in the test suite, where all of the existing tests had to be modified to suddenly be timezone-aware.

request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)
# This could be a retry. Make sure the previous
# authorization header is removed first.
Expand Down Expand Up @@ -554,7 +554,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)
request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)

fields = {}
Expand Down Expand Up @@ -813,7 +813,7 @@ class S3SigV4PostAuth(SigV4Auth):
"""

def add_auth(self, request):
datetime_now = datetime.datetime.utcnow()
datetime_now = datetime.datetime.now(datetime.timezone.utc)
request.context['timestamp'] = datetime_now.strftime(SIGV4_TIMESTAMP)

fields = {}
Expand Down
8 changes: 2 additions & 6 deletions botocore/crt/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,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(
datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(
tzinfo=datetime.timezone.utc
)
Comment on lines +57 to 59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call .replace() here anymore; it's already set.

Suggested change
datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(
tzinfo=datetime.timezone.utc
)
datetime_now = datetime.datetime.now(datetime.timezone.utc)


Expand Down Expand Up @@ -251,9 +249,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(
datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(
tzinfo=datetime.timezone.utc
)
Comment on lines +252 to 254

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Suggested change
datetime_now = datetime.datetime.now(datetime.timezone.utc).replace(
tzinfo=datetime.timezone.utc
)
datetime_now = datetime.datetime.now(datetime.timezone.utc)


Expand Down
14 changes: 8 additions & 6 deletions botocore/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ def prepare_request(self, request):
def _calculate_ttl(
self, response_received_timestamp, date_header, read_timeout
):
local_timestamp = datetime.datetime.utcnow()
local_timestamp = datetime.datetime.now(datetime.timezone.utc)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, this can be simplified to avoid fundamentally changing local_timestamp:

Suggested change
local_timestamp = datetime.datetime.now(datetime.timezone.utc)
local_timestamp = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)

date_conversion = datetime.datetime.strptime(
date_header, "%a, %d %b %Y %H:%M:%S %Z"
)
).replace(tzinfo=datetime.timezone.utc)
estimated_skew = date_conversion - response_received_timestamp
ttl = (
local_timestamp
Expand All @@ -169,7 +169,9 @@ def _set_ttl(self, retries_context, read_timeout, success_response):
has_streaming_input = retries_context.get('has_streaming_input')
if response_date_header and not has_streaming_input:
try:
response_received_timestamp = datetime.datetime.utcnow()
response_received_timestamp = datetime.datetime.now(
datetime.timezone.utc
)
retries_context['ttl'] = self._calculate_ttl(
response_received_timestamp,
response_date_header,
Expand Down Expand Up @@ -298,9 +300,9 @@ def _do_get_response(self, request, operation_model, context):
)

http_response_record_dict = response_dict.copy()
http_response_record_dict[
'streaming'
] = operation_model.has_streaming_output
http_response_record_dict['streaming'] = (
operation_model.has_streaming_output
)
history_recorder.record('HTTP_RESPONSE', http_response_record_dict)

protocol = operation_model.metadata['protocol']
Expand Down
2 changes: 1 addition & 1 deletion botocore/signers.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,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)
expire_date = datetime_now + datetime.timedelta(seconds=expires_in)
policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601)

Expand Down
4 changes: 2 additions & 2 deletions botocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,13 +667,13 @@ def _evaluate_expiration(self, credentials):
try:
expiration = datetime.datetime.strptime(
expiration, "%Y-%m-%dT%H:%M:%SZ"
)
).replace(tzinfo=datetime.timezone.utc)
refresh_interval = self._config.get(
"ec2_credential_refresh_window", 60 * 10
)
jitter = random.randint(120, 600) # Between 2 to 10 minutes
refresh_interval_with_jitter = refresh_interval + jitter
current_time = datetime.datetime.utcnow()
current_time = datetime.datetime.now(datetime.timezone.utc)
refresh_offset = datetime.timedelta(
seconds=refresh_interval_with_jitter
)
Expand Down
22 changes: 19 additions & 3 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,22 @@ def verify_stubs(self):
stub.assert_no_pending_responses()


def now_patcher_factory(date):
"""
Factory for creating a datetime.datetime.now() patcher.

:type date: datetime.datetime
:param date: datetime object specifying the default output for now()
"""

def _now_patcher(tz=None):
if tz is None:
return date
return date.astimezone(tz)

return _now_patcher


class FreezeTime(ContextDecorator):
"""
Context manager for mocking out datetime in arbitrary modules when creating
Expand All @@ -568,20 +584,20 @@ class FreezeTime(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(datetime.timezone.utc)
"""

def __init__(self, module, date=None):
if date is None:
date = datetime.datetime.utcnow()
date = datetime.datetime.now(datetime.timezone.utc)
self.date = date
self.datetime_patcher = mock.patch.object(
module, 'datetime', mock.Mock(wraps=datetime.datetime)
)

def __enter__(self, *args, **kwargs):
mock = self.datetime_patcher.start()
mock.utcnow.return_value = self.date
mock.now.side_effect = now_patcher_factory(self.date)

def __exit__(self, *args, **kwargs):
self.datetime_patcher.stop()
Expand Down
14 changes: 11 additions & 3 deletions tests/functional/test_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
import botocore.session
from botocore.compat import parse_qs, urlparse
from botocore.stub import ANY, Stubber
from tests import BaseSessionTest, ClientHTTPStubber, mock, unittest
from tests import (
BaseSessionTest,
ClientHTTPStubber,
mock,
unittest,
now_patcher_factory,
)


class TestIdempotencyToken(unittest.TestCase):
Expand Down Expand Up @@ -91,14 +97,16 @@ def setUp(self):
'<snapshotId>%s</snapshotId>\n'
'</CopySnapshotResponse>\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',
mock.Mock(wraps=datetime.datetime),
)
self.mocked_datetime = self.datetime_patch.start()
self.mocked_datetime.utcnow.return_value = self.now
self.mocked_datetime.now.side_effect = now_patcher_factory(self.now)

def tearDown(self):
super().tearDown()
Expand Down
8 changes: 4 additions & 4 deletions tests/functional/test_lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
# 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
from datetime import datetime, UTC

from tests import BaseSessionTest, ClientHTTPStubber, mock
from tests import BaseSessionTest, ClientHTTPStubber, mock, now_patcher_factory


class TestLex(BaseSessionTest):
Expand All @@ -31,10 +31,10 @@ def test_unsigned_payload(self):
'inputStream': b'',
}

timestamp = datetime(2017, 3, 22, 0, 0)
timestamp = datetime(2017, 3, 22, 0, 0, tzinfo=UTC)

with mock.patch('botocore.auth.datetime.datetime') as _datetime:
_datetime.utcnow.return_value = timestamp
_datetime.now.side_effect = now_patcher_factory(timestamp)
self.http_stubber.add_response(body=b'{}')
with self.http_stubber:
self.client.post_content(**params)
Expand Down
73 changes: 50 additions & 23 deletions tests/functional/test_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import botocore.endpoint
from botocore.config import Config
from botocore.exceptions import ClientError
from tests import BaseSessionTest, ClientHTTPStubber, mock
from tests import BaseSessionTest, ClientHTTPStubber, mock, now_patcher_factory

RETRY_MODES = ('legacy', 'standard', 'adaptive')

Expand Down Expand Up @@ -66,26 +66,54 @@ def _retry_headers_test_cases(self):
]

# The first, third and seventh datetime values of each
# utcnow_side_effects list are side_effect values for when
# utcnow is called in SigV4 signing.
utcnow_side_effects = [
# now_side_effects list are side_effect values for when
# now is called in SigV4 signing.
now_side_effects = [
[
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(2019, 6, 1, 0, 0, 1, 0),
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(2019, 6, 1, 0, 0, 1, 0),
datetime.datetime(2019, 6, 1, 0, 0, 2, 0),
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 1, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 1, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 2, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
],
[
datetime.datetime(2020, 6, 1, 0, 0, 0, 0),
datetime.datetime(2019, 6, 1, 0, 0, 5, 0),
datetime.datetime(2019, 6, 1, 0, 0, 6, 0),
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(2019, 6, 1, 0, 0, 11, 0),
datetime.datetime(2019, 6, 1, 0, 0, 12, 0),
datetime.datetime(2019, 6, 1, 0, 0, 0, 0),
datetime.datetime(
2020, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 5, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 6, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 11, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 12, 0, tzinfo=datetime.timezone.utc
),
datetime.datetime(
2019, 6, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc
),
],
]
expected_headers = [
Expand All @@ -100,21 +128,20 @@ def _retry_headers_test_cases(self):
b'ttl=20190601T001020Z; attempt=3; max=3',
],
]
test_cases = list(
zip(responses, utcnow_side_effects, expected_headers)
)
test_cases = list(zip(responses, now_side_effects, expected_headers))

return test_cases

def _test_amz_sdk_request_header_with_test_case(
self, responses, utcnow_side_effects, expected_headers, client_config
self, responses, now_side_effects, expected_headers, client_config
):
datetime_patcher = mock.patch.object(
botocore.endpoint.datetime,
'datetime',
mock.Mock(wraps=datetime.datetime),
)
mocked_datetime = datetime_patcher.start()
mocked_datetime.utcnow.side_effect = utcnow_side_effects
mocked_datetime.now.side_effect = now_side_effects

client = self.session.create_client(
'dynamodb', self.region, config=client_config
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
unittest,
)

DATE = datetime.datetime(2021, 8, 27, 0, 0, 0)
DATE = datetime.datetime(2021, 8, 27, 0, 0, 0, tzinfo=datetime.timezone.utc)


class TestS3BucketValidation(unittest.TestCase):
Expand Down
Loading