Skip to content

Commit 5317cfe

Browse files
committed
Replace uses of time.time with time.monotonic(_ns)
1 parent fde5f29 commit 5317cfe

File tree

5 files changed

+35
-29
lines changed

5 files changed

+35
-29
lines changed

prawcore/auth.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,23 @@ def __init__(self, authenticator: BaseAuthenticator):
142142
self._validate_authenticator()
143143

144144
def _clear_access_token(self):
145-
self._expiration_timestamp: float
145+
self._expiration_timestamp_ns: int
146146
self.access_token: str | None = None
147147
self.scopes: set[str] | None = None
148148

149149
def _request_token(self, **data: Any):
150150
url = self._authenticator._requestor.reddit_url + const.ACCESS_TOKEN_PATH
151-
pre_request_time = time.time()
151+
pre_request_timestamp_ns = time.monotonic_ns()
152152
response = self._authenticator._post(url=url, **data)
153153
payload = response.json()
154154
if "error" in payload: # Why are these OKAY responses?
155155
raise OAuthException(
156156
response, payload["error"], payload.get("error_description")
157157
)
158158

159-
self._expiration_timestamp = pre_request_time - 10 + payload["expires_in"]
159+
self._expiration_timestamp_ns = (
160+
pre_request_timestamp_ns + (payload["expires_in"] + 10) * const.NANOSECONDS
161+
)
160162
self.access_token = payload["access_token"]
161163
if "refresh_token" in payload:
162164
self.refresh_token = payload["refresh_token"]
@@ -181,7 +183,8 @@ def is_valid(self) -> bool:
181183
182184
"""
183185
return (
184-
self.access_token is not None and time.time() < self._expiration_timestamp
186+
self.access_token is not None
187+
and time.monotonic_ns() < self._expiration_timestamp_ns
185188
)
186189

187190
def revoke(self):
@@ -338,7 +341,9 @@ def __init__(
338341
339342
"""
340343
super().__init__(authenticator)
341-
self._expiration_timestamp = time.time() + expires_in
344+
self._expiration_timestamp_ns = (
345+
time.monotonic_ns() + expires_in * const.NANOSECONDS
346+
)
342347
self.access_token = access_token
343348
self.scopes = set(scope.split(" "))
344349

prawcore/const.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
ACCESS_TOKEN_PATH = "/api/v1/access_token" # noqa: S105
66
AUTHORIZATION_PATH = "/api/v1/authorize" # noqa: S105
7+
NANOSECONDS = 1_000_000_000 # noqa: S105
78
REVOKE_TOKEN_PATH = "/api/v1/revoke_token" # noqa: S105
89
TIMEOUT = float(
910
os.environ.get(

prawcore/rate_limit.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from requests.models import Response
1313

14+
from prawcore.const import NANOSECONDS
15+
1416
log = logging.getLogger(__package__)
1517

1618

@@ -21,8 +23,6 @@ class RateLimiter:
2123
2224
"""
2325

24-
NANOSECONDS = 1_000_000_000
25-
2626
def __init__(self, *, window_size: int):
2727
"""Create an instance of the RateLimit class."""
2828
self.remaining: int | None = None
@@ -57,8 +57,7 @@ def delay(self):
5757
if self.next_request_timestamp_ns is None:
5858
return
5959
sleep_seconds = (
60-
float(self.next_request_timestamp_ns - time.monotonic_ns())
61-
/ self.NANOSECONDS
60+
float(self.next_request_timestamp_ns - time.monotonic_ns()) / NANOSECONDS
6261
)
6362
if sleep_seconds <= 0:
6463
return
@@ -90,7 +89,7 @@ def update(self, response_headers: Mapping[str, str]):
9089

9190
if self.remaining <= 0:
9291
self.next_request_timestamp_ns = now_ns + max(
93-
self.NANOSECONDS / 2, seconds_to_reset * self.NANOSECONDS
92+
NANOSECONDS / 2, seconds_to_reset * NANOSECONDS
9493
)
9594
return
9695

@@ -110,5 +109,5 @@ def update(self, response_headers: Mapping[str, str]):
110109
),
111110
10,
112111
)
113-
* self.NANOSECONDS
112+
* NANOSECONDS
114113
)

prawcore/sessions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def _log_request(
109109
params: dict[str, int],
110110
url: str,
111111
):
112-
log.debug("Fetching: %s %s at %s", method, url, time.time())
112+
log.debug("Fetching: %s %s at %s", method, url, time.monotonic())
113113
log.debug("Data: %s", pformat(data))
114114
log.debug("Params: %s", pformat(params))
115115

@@ -201,7 +201,7 @@ def _make_request(
201201
response.headers.get("x-ratelimit-reset"),
202202
response.headers.get("x-ratelimit-remaining"),
203203
response.headers.get("x-ratelimit-used"),
204-
time.time(),
204+
time.monotonic(),
205205
)
206206
return response, None
207207
except RequestException as exception:

tests/unit/test_rate_limit.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77

8+
from prawcore.const import NANOSECONDS
89
from prawcore.rate_limit import RateLimiter
910

1011
from . import UnitTest
@@ -14,7 +15,7 @@ class TestRateLimiter(UnitTest):
1415
@pytest.fixture
1516
def rate_limiter(self):
1617
rate_limiter = RateLimiter(window_size=600)
17-
rate_limiter.next_request_timestamp_ns = 100 * RateLimiter.NANOSECONDS
18+
rate_limiter.next_request_timestamp_ns = 100 * NANOSECONDS
1819
return rate_limiter
1920

2021
@staticmethod
@@ -28,7 +29,7 @@ def _headers(remaining, used, reset):
2829
@patch("time.monotonic_ns")
2930
@patch("time.sleep")
3031
def test_delay(self, mock_sleep, mock_monotonic_ns, rate_limiter):
31-
mock_monotonic_ns.return_value = 1 * RateLimiter.NANOSECONDS
32+
mock_monotonic_ns.return_value = 1 * NANOSECONDS
3233
rate_limiter.delay()
3334
assert mock_monotonic_ns.called
3435
mock_sleep.assert_called_with(99)
@@ -38,7 +39,7 @@ def test_delay(self, mock_sleep, mock_monotonic_ns, rate_limiter):
3839
def test_delay__no_sleep_when_time_in_past(
3940
self, mock_sleep, mock_monotonic_ns, rate_limiter
4041
):
41-
mock_monotonic_ns.return_value = 101 * RateLimiter.NANOSECONDS
42+
mock_monotonic_ns.return_value = 101 * NANOSECONDS
4243
rate_limiter.delay()
4344
assert mock_monotonic_ns.called
4445
assert not mock_sleep.called
@@ -54,7 +55,7 @@ def test_delay__no_sleep_when_time_is_not_set(self, mock_sleep, rate_limiter):
5455
def test_delay__no_sleep_when_times_match(
5556
self, mock_sleep, mock_monotonic_ns, rate_limiter
5657
):
57-
mock_monotonic_ns.return_value = 100 * RateLimiter.NANOSECONDS
58+
mock_monotonic_ns.return_value = 100 * NANOSECONDS
5859
rate_limiter.delay()
5960
assert mock_monotonic_ns.called
6061
assert not mock_sleep.called
@@ -63,64 +64,64 @@ def test_delay__no_sleep_when_times_match(
6364
def test_update__compute_delay_with_no_previous_info(
6465
self, mock_monotonic_ns, rate_limiter
6566
):
66-
mock_monotonic_ns.return_value = 100 * RateLimiter.NANOSECONDS
67+
mock_monotonic_ns.return_value = 100 * NANOSECONDS
6768
rate_limiter.update(self._headers(60, 100, 60))
6869
assert rate_limiter.remaining == 60
6970
assert rate_limiter.used == 100
70-
assert rate_limiter.next_request_timestamp_ns == 100 * RateLimiter.NANOSECONDS
71+
assert rate_limiter.next_request_timestamp_ns == 100 * NANOSECONDS
7172

7273
@patch("time.monotonic_ns")
7374
def test_update__compute_delay_with_single_client(
7475
self, mock_monotonic_ns, rate_limiter
7576
):
7677
rate_limiter.window_size = 150
77-
mock_monotonic_ns.return_value = 100 * RateLimiter.NANOSECONDS
78+
mock_monotonic_ns.return_value = 100 * NANOSECONDS
7879
rate_limiter.update(self._headers(50, 100, 60))
7980
assert rate_limiter.remaining == 50
8081
assert rate_limiter.used == 100
81-
assert rate_limiter.next_request_timestamp_ns == 110 * RateLimiter.NANOSECONDS
82+
assert rate_limiter.next_request_timestamp_ns == 110 * NANOSECONDS
8283

8384
@patch("time.monotonic_ns")
8485
def test_update__compute_delay_with_six_clients(
8586
self, mock_monotonic_ns, rate_limiter
8687
):
8788
rate_limiter.remaining = 66
8889
rate_limiter.window_size = 180
89-
mock_monotonic_ns.return_value = 100 * RateLimiter.NANOSECONDS
90+
mock_monotonic_ns.return_value = 100 * NANOSECONDS
9091
rate_limiter.update(self._headers(60, 100, 72))
9192
assert rate_limiter.remaining == 60
9293
assert rate_limiter.used == 100
93-
assert rate_limiter.next_request_timestamp_ns == 104.5 * RateLimiter.NANOSECONDS
94+
assert rate_limiter.next_request_timestamp_ns == 104.5 * NANOSECONDS
9495

9596
@patch("time.monotonic_ns")
9697
def test_update__delay_full_time_with_negative_remaining(
9798
self, mock_monotonic_ns, rate_limiter
9899
):
99-
mock_monotonic_ns.return_value = 37 * RateLimiter.NANOSECONDS
100+
mock_monotonic_ns.return_value = 37 * NANOSECONDS
100101
rate_limiter.update(self._headers(0, 100, 13))
101102
assert rate_limiter.remaining == 0
102103
assert rate_limiter.used == 100
103-
assert rate_limiter.next_request_timestamp_ns == 50 * RateLimiter.NANOSECONDS
104+
assert rate_limiter.next_request_timestamp_ns == 50 * NANOSECONDS
104105

105106
@patch("time.monotonic_ns")
106107
def test_update__delay_full_time_with_zero_remaining(
107108
self, mock_monotonic_ns, rate_limiter
108109
):
109-
mock_monotonic_ns.return_value = 37 * RateLimiter.NANOSECONDS
110+
mock_monotonic_ns.return_value = 37 * NANOSECONDS
110111
rate_limiter.update(self._headers(0, 100, 13))
111112
assert rate_limiter.remaining == 0
112113
assert rate_limiter.used == 100
113-
assert rate_limiter.next_request_timestamp_ns == 50 * RateLimiter.NANOSECONDS
114+
assert rate_limiter.next_request_timestamp_ns == 50 * NANOSECONDS
114115

115116
@patch("time.monotonic_ns")
116117
def test_update__delay_full_time_with_zero_remaining_and_no_sleep_time(
117118
self, mock_monotonic_ns, rate_limiter
118119
):
119-
mock_monotonic_ns.return_value = 37 * RateLimiter.NANOSECONDS
120+
mock_monotonic_ns.return_value = 37 * NANOSECONDS
120121
rate_limiter.update(self._headers(0, 100, 0))
121122
assert rate_limiter.remaining == 0
122123
assert rate_limiter.used == 100
123-
assert rate_limiter.next_request_timestamp_ns == 37.5 * RateLimiter.NANOSECONDS
124+
assert rate_limiter.next_request_timestamp_ns == 37.5 * NANOSECONDS
124125

125126
def test_update__no_change_without_headers(self, rate_limiter):
126127
prev = copy(rate_limiter)

0 commit comments

Comments
 (0)