Skip to content

Commit 29d396f

Browse files
refactor: Use tz-aware datetimes (#332)
1 parent ad5db0a commit 29d396f

File tree

2 files changed

+43
-27
lines changed

2 files changed

+43
-27
lines changed

tap_github/authenticator.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import time
77
from copy import deepcopy
8-
from datetime import datetime, timedelta
8+
from datetime import datetime, timedelta, timezone
99
from os import environ
1010
from random import choice, shuffle
1111
from typing import Any
@@ -49,8 +49,9 @@ def __init__(
4949
def update_rate_limit(self, response_headers: Any) -> None:
5050
self.rate_limit = int(response_headers["X-RateLimit-Limit"])
5151
self.rate_limit_remaining = int(response_headers["X-RateLimit-Remaining"])
52-
self.rate_limit_reset = datetime.utcfromtimestamp(
53-
int(response_headers["X-RateLimit-Reset"])
52+
self.rate_limit_reset = datetime.fromtimestamp(
53+
int(response_headers["X-RateLimit-Reset"]),
54+
tz=timezone.utc,
5455
)
5556
self.rate_limit_used = int(response_headers["X-RateLimit-Used"])
5657

@@ -86,10 +87,9 @@ def has_calls_remaining(self) -> bool:
8687
"""
8788
if self.rate_limit_reset is None:
8889
return True
89-
return (
90-
self.rate_limit_used <= (self.rate_limit - self.rate_limit_buffer)
91-
or self.rate_limit_reset <= datetime.now()
92-
)
90+
return self.rate_limit_used <= (
91+
self.rate_limit - self.rate_limit_buffer
92+
) or self.rate_limit_reset <= datetime.now(tz=timezone.utc)
9393

9494

9595
class PersonalTokenManager(TokenManager):
@@ -126,7 +126,7 @@ def generate_app_access_token(
126126
github_private_key: str,
127127
github_installation_id: str | None = None,
128128
) -> tuple[str, datetime]:
129-
produced_at = datetime.now()
129+
produced_at = datetime.now(tz=timezone.utc)
130130
jwt_token = generate_jwt_token(github_app_id, github_private_key)
131131

132132
headers = {"Authorization": f"Bearer {jwt_token}"}
@@ -219,9 +219,9 @@ def has_calls_remaining(self) -> bool:
219219
True if the token is valid and has enough api calls remaining.
220220
"""
221221
if self.token_expires_at is not None:
222-
close_to_expiry = datetime.now() > self.token_expires_at - timedelta(
223-
minutes=self.expiry_time_buffer
224-
)
222+
close_to_expiry = datetime.now(
223+
tz=timezone.utc
224+
) > self.token_expires_at - timedelta(minutes=self.expiry_time_buffer)
225225

226226
if close_to_expiry:
227227
self.claim_token()

tap_github/tests/test_authenticator.py

+32-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from datetime import datetime, timedelta
2+
from datetime import datetime, timedelta, timezone
33
from unittest.mock import MagicMock, call, patch
44

55
import pytest
@@ -14,6 +14,10 @@
1414
)
1515

1616

17+
def _now():
18+
return datetime.now(tz=timezone.utc)
19+
20+
1721
class TestTokenManager:
1822
def test_default_rate_limits(self):
1923
token_manager = TokenManager("mytoken", rate_limit_buffer=700)
@@ -40,7 +44,15 @@ def test_update_rate_limit(self):
4044

4145
assert token_manager.rate_limit == 5000
4246
assert token_manager.rate_limit_remaining == 4999
43-
assert token_manager.rate_limit_reset == datetime(2013, 7, 1, 17, 47, 53)
47+
assert token_manager.rate_limit_reset == datetime(
48+
2013,
49+
7,
50+
1,
51+
17,
52+
47,
53+
53,
54+
tzinfo=timezone.utc,
55+
)
4456
assert token_manager.rate_limit_used == 1
4557

4658
def test_is_valid_token_successful(self):
@@ -108,9 +120,7 @@ def test_has_calls_remaining_fails_if_few_calls_remaining_and_reset_time_not_rea
108120
mock_response_headers = {
109121
"X-RateLimit-Limit": "5000",
110122
"X-RateLimit-Remaining": "1",
111-
"X-RateLimit-Reset": str(
112-
int((datetime.now() + timedelta(days=100)).timestamp())
113-
),
123+
"X-RateLimit-Reset": str(int((_now() + timedelta(days=100)).timestamp())),
114124
"X-RateLimit-Used": "4999",
115125
}
116126

@@ -164,8 +174,8 @@ def test_successful_token_generation(self):
164174
assert token_manager.token_expires_at == token_time
165175

166176
def test_has_calls_remaining_regenerates_a_token_if_close_to_expiry(self):
167-
unexpired_time = datetime.now() + timedelta(days=1)
168-
expired_time = datetime.now() - timedelta(days=1)
177+
unexpired_time = _now() + timedelta(days=1)
178+
expired_time = _now() - timedelta(days=1)
169179
with patch.object(AppTokenManager, "is_valid_token", return_value=True), patch(
170180
"tap_github.authenticator.generate_app_access_token",
171181
return_value=("valid_token", unexpired_time),
@@ -193,8 +203,8 @@ def test_has_calls_remaining_regenerates_a_token_if_close_to_expiry(self):
193203
)
194204

195205
def test_has_calls_remaining_logs_warning_if_token_regeneration_fails(self):
196-
unexpired_time = datetime.now() + timedelta(days=1)
197-
expired_time = datetime.now() - timedelta(days=1)
206+
unexpired_time = _now() + timedelta(days=1)
207+
expired_time = _now() - timedelta(days=1)
198208
with patch.object(
199209
AppTokenManager, "is_valid_token", return_value=True
200210
) as mock_is_valid, patch(
@@ -222,7 +232,7 @@ def test_has_calls_remaining_logs_warning_if_token_regeneration_fails(self):
222232
)
223233

224234
def test_has_calls_remaining_succeeds_if_token_new_and_never_used(self):
225-
unexpired_time = datetime.now() + timedelta(days=1)
235+
unexpired_time = _now() + timedelta(days=1)
226236
with patch.object(AppTokenManager, "is_valid_token", return_value=True), patch(
227237
"tap_github.authenticator.generate_app_access_token",
228238
return_value=("valid_token", unexpired_time),
@@ -231,7 +241,7 @@ def test_has_calls_remaining_succeeds_if_token_new_and_never_used(self):
231241
assert token_manager.has_calls_remaining()
232242

233243
def test_has_calls_remaining_succeeds_if_time_and_requests_left(self):
234-
unexpired_time = datetime.now() + timedelta(days=1)
244+
unexpired_time = _now() + timedelta(days=1)
235245
with patch.object(AppTokenManager, "is_valid_token", return_value=True), patch(
236246
"tap_github.authenticator.generate_app_access_token",
237247
return_value=("valid_token", unexpired_time),
@@ -249,7 +259,7 @@ def test_has_calls_remaining_succeeds_if_time_and_requests_left(self):
249259
assert token_manager.has_calls_remaining()
250260

251261
def test_has_calls_remaining_succeeds_if_time_left_and_reset_time_reached(self):
252-
unexpired_time = datetime.now() + timedelta(days=1)
262+
unexpired_time = _now() + timedelta(days=1)
253263
with patch.object(AppTokenManager, "is_valid_token", return_value=True), patch(
254264
"tap_github.authenticator.generate_app_access_token",
255265
return_value=("valid_token", unexpired_time),
@@ -271,7 +281,7 @@ def test_has_calls_remaining_succeeds_if_time_left_and_reset_time_reached(self):
271281
def test_has_calls_remaining_fails_if_time_left_and_few_calls_remaining_and_reset_time_not_reached( # noqa: E501
272282
self,
273283
):
274-
unexpired_time = datetime.now() + timedelta(days=1)
284+
unexpired_time = _now() + timedelta(days=1)
275285
with patch.object(AppTokenManager, "is_valid_token", return_value=True), patch(
276286
"tap_github.authenticator.generate_app_access_token",
277287
return_value=("valid_token", unexpired_time),
@@ -280,7 +290,7 @@ def test_has_calls_remaining_fails_if_time_left_and_few_calls_remaining_and_rese
280290
"X-RateLimit-Limit": "5000",
281291
"X-RateLimit-Remaining": "1",
282292
"X-RateLimit-Reset": str(
283-
int((datetime.now() + timedelta(days=100)).timestamp())
293+
int((_now() + timedelta(days=100)).timestamp())
284294
),
285295
"X-RateLimit-Used": "4999",
286296
}
@@ -427,7 +437,10 @@ def test_all_token_types(self, mock_stream):
427437
):
428438
stream = mock_stream
429439
stream.config.update(
430-
{"auth_token": "gt5", "additional_auth_tokens": ["gt7", "gt8", "gt9"]}
440+
{
441+
"auth_token": "gt5",
442+
"additional_auth_tokens": ["gt7", "gt8", "gt9"],
443+
}
431444
)
432445
auth = GitHubTokenAuthenticator(stream=stream)
433446
token_managers = auth.prepare_tokens()
@@ -568,7 +581,10 @@ def test_prepare_tokens_returns_empty_if_all_tokens_invalid(self, mock_stream):
568581
):
569582
stream = mock_stream
570583
stream.config.update(
571-
{"auth_token": "gt5", "additional_auth_tokens": ["gt7", "gt8", "gt9"]}
584+
{
585+
"auth_token": "gt5",
586+
"additional_auth_tokens": ["gt7", "gt8", "gt9"],
587+
}
572588
)
573589
auth = GitHubTokenAuthenticator(stream=stream)
574590
token_managers = auth.prepare_tokens()

0 commit comments

Comments
 (0)