Skip to content

Commit 9cda625

Browse files
authored
3: Make time monotonic (#231)
1 parent fc664b3 commit 9cda625

File tree

7 files changed

+26
-34
lines changed

7 files changed

+26
-34
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ from pyrate_limiter import Limiter
193193

194194
# Limiter constructor accepts single bucket as the only parameter,
195195
# the rest are 3 optional parameters with default values as following
196-
# Limiter(bucket, clock=TimeClock(), raise_when_fail=True, max_delay=None)
196+
# Limiter(bucket, clock=MonotonicClock(), raise_when_fail=True, max_delay=None)
197197
limiter = Limiter(bucket)
198198

199199
# Limiter is now ready to work!
@@ -209,9 +209,9 @@ When multiple bucket types are needed and items must be routed based on certain
209209
First, define your clock (time source). Most use cases work with the built-in clocks:
210210

211211
```python
212-
from pyrate_limiter.clock import TimeClock, MonotonicClock, SQLiteClock
212+
from pyrate_limiter.clock import MonotonicClock, SQLiteClock
213213

214-
base_clock = TimeClock()
214+
base_clock = MonotonicClock()
215215
```
216216

217217
PyrateLimiter does not assume routing logic, so you implement a custom BucketFactory. At a minimum, these two methods must be defined:
@@ -439,7 +439,7 @@ Here's an example that will raise an exception on the 4th request:
439439
```python
440440
rate = Rate(3, Duration.SECOND)
441441
bucket = InMemoryBucket([rate])
442-
clock = TimeClock()
442+
clock = MonotonicClock()
443443

444444

445445
class MyBucketFactory(BucketFactory):
@@ -662,7 +662,7 @@ Multiprocessing: If using MultiprocessBucket, two locks are used in Limiter: a t
662662
Time source can be anything from anywhere: be it python's built-in time, or monotonic clock, sqliteclock, or crawling from world time server(well we don't have that, but you can!).
663663

664664
```python
665-
from pyrate_limiter import TimeClock # use python' time.time()
665+
from pyrate_limiter import MonotonicClock # use python time.monotonic()
666666
from pyrate_limiter import MonotonicClock # use python time.monotonic()
667667
```
668668

pyrate_limiter/clocks.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import sqlite3
66
from contextlib import nullcontext
7-
from time import monotonic, time
7+
from time import monotonic
88
from typing import TYPE_CHECKING, Optional, Union
99

1010
from .abstracts import AbstractClock
@@ -25,16 +25,11 @@ def now(self):
2525
return int(1000 * monotonic())
2626

2727

28-
class TimeClock(AbstractClock):
29-
def now(self):
30-
return int(1000 * time())
31-
32-
33-
class TimeAsyncClock(AbstractClock):
34-
"""Time Async Clock, meant for testing only"""
28+
class MonotonicAsyncClock(AbstractClock):
29+
"""Monotonic Async Clock, meant for testing only"""
3530

3631
async def now(self) -> int:
37-
return int(1000 * time())
32+
return int(1000 * monotonic())
3833

3934

4035
class SQLiteClock(AbstractClock):

pyrate_limiter/limiter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from .abstracts import AbstractBucket, AbstractClock, BucketFactory, Duration, Rate, RateItem
1313
from .buckets import InMemoryBucket
14-
from .clocks import TimeClock
14+
from .clocks import MonotonicClock
1515
from .exceptions import BucketFullException, LimiterDelayException
1616

1717
logger = logging.getLogger("pyrate_limiter")
@@ -94,15 +94,15 @@ def __init__(
9494
9595
Parameters:
9696
argument (Union[BucketFactory, AbstractBucket, Rate, List[Rate]]): The bucket or rate configuration.
97-
clock (AbstractClock, optional): The clock instance to use for rate limiting. Defaults to TimeClock().
97+
clock (AbstractClock, optional): The clock instance to use for rate limiting. Defaults to MonotonicClock().
9898
raise_when_fail (bool, optional): Whether to raise an exception when rate limiting fails. Defaults to True.
9999
max_delay (Optional[Union[int, Duration]], optional): The maximum delay allowed for rate limiting.
100100
Defaults to None.
101101
retry_until_max_delay (bool, optional): If True, retry operations until the maximum delay is reached.
102102
Useful for ensuring operations eventually succeed within the allowed delay window. Defaults to False.
103103
"""
104104
if clock is None:
105-
clock = TimeClock()
105+
clock = MonotonicClock()
106106
self.bucket_factory = self._init_bucket_factory(argument, clock=clock)
107107
self.raise_when_fail = raise_when_fail
108108
self.retry_until_max_delay = retry_until_max_delay

tests/conftest.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
from pyrate_limiter import PostgresBucket
1919
from pyrate_limiter import Rate
2020
from pyrate_limiter import RedisBucket
21-
from pyrate_limiter import TimeAsyncClock
22-
from pyrate_limiter import TimeClock
21+
from pyrate_limiter import MonotonicAsyncClock
2322

2423

2524
# Make log messages visible on test failure (or with pytest -s)
@@ -32,14 +31,12 @@
3231

3332
clocks = [
3433
pytest.param(MonotonicClock(), marks=pytest.mark.monotonic),
35-
pytest.param(TimeClock(), marks=pytest.mark.timeclock),
36-
pytest.param(TimeAsyncClock(), marks=pytest.mark.asyncclock),
34+
pytest.param(MonotonicAsyncClock(), marks=pytest.mark.asyncclock),
3735
]
3836

3937
ClockSet = Union[
4038
MonotonicClock,
41-
TimeClock,
42-
TimeAsyncClock,
39+
MonotonicAsyncClock,
4340
]
4441

4542

tests/test_bucket_all.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from pyrate_limiter import BucketAsyncWrapper
1414
from pyrate_limiter import Rate
1515
from pyrate_limiter import RateItem
16-
from pyrate_limiter import TimeClock
16+
from pyrate_limiter import MonotonicClock
1717

1818

1919
async def get_now(clock: AbstractClock) -> int:
@@ -218,7 +218,7 @@ async def test_bucket_flush(create_bucket):
218218
rates = [Rate(50, 1000)]
219219
bucket = BucketAsyncWrapper(await create_bucket(rates))
220220
assert isinstance(bucket.rates[0], Rate)
221-
clock = TimeClock()
221+
clock = MonotonicClock()
222222

223223
while await bucket.put(RateItem("item", clock.now())):
224224
pass
@@ -236,7 +236,7 @@ async def test_bucket_performance(create_bucket):
236236
Putting a very large number of item into bucket
237237
Only need to test with a single clock type
238238
"""
239-
clock = TimeClock()
239+
clock = MonotonicClock()
240240
rates = [Rate(30000, 50000)]
241241
bucket = BucketAsyncWrapper(await create_bucket(rates))
242242
before = time()

tests/test_limiter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from pyrate_limiter import LimiterDelayException
2525
from pyrate_limiter import Rate
2626
from pyrate_limiter import SingleBucketFactory
27-
from pyrate_limiter import TimeClock
27+
from pyrate_limiter import MonotonicClock
2828

2929

3030
@pytest.mark.asyncio
@@ -55,7 +55,7 @@ async def test_limiter_constructor_02(
5555

5656
limiter = Limiter(bucket)
5757
assert isinstance(limiter.bucket_factory, SingleBucketFactory)
58-
assert isinstance(limiter.bucket_factory.clock, TimeClock)
58+
assert isinstance(limiter.bucket_factory.clock, MonotonicClock)
5959
assert limiter.max_delay is None
6060
assert limiter.raise_when_fail is True
6161

@@ -356,7 +356,7 @@ def test_wait_too_long():
356356

357357
rate = Rate(requests_per_second, Duration.SECOND)
358358
bucket = InMemoryBucket([rate])
359-
limiter = Limiter(bucket, raise_when_fail=False, clock=TimeClock(),
359+
limiter = Limiter(bucket, raise_when_fail=False, clock=MonotonicClock(),
360360
max_delay=Duration.SECOND, retry_until_max_delay=True)
361361

362362
# raise_when_fail = False
@@ -370,7 +370,7 @@ def test_wait_too_long():
370370
time.sleep(1)
371371

372372
# raise_when_fail = True
373-
limiter = Limiter(bucket, raise_when_fail=True, clock=TimeClock(),
373+
limiter = Limiter(bucket, raise_when_fail=True, clock=MonotonicClock(),
374374
max_delay=Duration.SECOND, retry_until_max_delay=True)
375375

376376
with pytest.raises(LimiterDelayException):

tests/test_multiprocessing.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from pyrate_limiter import Rate
2222
from pyrate_limiter import SQLiteBucket
2323
from pyrate_limiter import SQLiteClock
24-
from pyrate_limiter import TimeClock
24+
from pyrate_limiter import MonotonicClock
2525
from pyrate_limiter.buckets.mp_bucket import MultiprocessBucket
2626

2727
MAX_DELAY = Duration.DAY
@@ -46,7 +46,7 @@ def init_process_mp(
4646
LIMITER = Limiter(
4747
bucket,
4848
raise_when_fail=raise_when_fail,
49-
clock=TimeClock(),
49+
clock=MonotonicClock(),
5050
max_delay=max_delay,
5151
retry_until_max_delay=not raise_when_fail,
5252
)
@@ -286,7 +286,7 @@ def test_limiter_delay():
286286
limiter = Limiter(
287287
bucket,
288288
raise_when_fail=True,
289-
clock=TimeClock(),
289+
clock=MonotonicClock(),
290290
max_delay=Duration.SECOND,
291291
retry_until_max_delay=False,
292292
)
@@ -318,7 +318,7 @@ def test_bucket_full():
318318
limiter = Limiter(
319319
bucket,
320320
raise_when_fail=True,
321-
clock=TimeClock(),
321+
clock=MonotonicClock(),
322322
max_delay=None,
323323
retry_until_max_delay=False,
324324
)

0 commit comments

Comments
 (0)