0
# Time Sources
1
2
Different clock implementations for various deployment scenarios including local system time, monotonic time, and remote database-backed time sources. Clocks provide the time foundation for rate limiting calculations and can be chosen based on accuracy, consistency, and deployment requirements.
3
4
## Capabilities
5
6
### System Time Clock
7
8
Standard clock using system time for basic rate limiting scenarios.
9
10
```python { .api }
11
class TimeClock(AbstractClock):
12
def now(self) -> int:
13
"""
14
Get current system time in milliseconds.
15
16
Returns:
17
- int: Current time as milliseconds since epoch
18
19
Characteristics:
20
- Uses system time (time.time())
21
- Subject to system clock adjustments
22
- Fast and simple
23
- Default clock for most scenarios
24
"""
25
```
26
27
Usage example:
28
29
```python
30
from pyrate_limiter import TimeClock, Limiter, Rate, Duration
31
32
# Default clock (same as not specifying clock)
33
clock = TimeClock()
34
limiter = Limiter(Rate(10, Duration.SECOND), clock=clock)
35
36
# TimeClock is the default, so this is equivalent:
37
limiter = Limiter(Rate(10, Duration.SECOND))
38
```
39
40
### Monotonic Clock
41
42
Clock using monotonic time that doesn't go backwards, ideal for measuring intervals.
43
44
```python { .api }
45
class MonotonicClock(AbstractClock):
46
def __init__(self):
47
"""Initialize monotonic clock."""
48
49
def now(self) -> int:
50
"""
51
Get current monotonic time in milliseconds.
52
53
Returns:
54
- int: Monotonic time in milliseconds
55
56
Characteristics:
57
- Uses monotonic time (time.monotonic())
58
- Never goes backwards
59
- Not affected by system clock adjustments
60
- Ideal for measuring time intervals
61
- Cannot be compared across system restarts
62
"""
63
```
64
65
Usage example:
66
67
```python
68
from pyrate_limiter import MonotonicClock, Limiter, Rate, Duration
69
70
# Use monotonic clock for interval stability
71
clock = MonotonicClock()
72
limiter = Limiter(Rate(5, Duration.SECOND), clock=clock)
73
74
# Good for scenarios where system clock might be adjusted
75
# but rate limiting should continue smoothly
76
```
77
78
### Async Time Clock
79
80
Asynchronous clock for testing and async environments.
81
82
```python { .api }
83
class TimeAsyncClock(AbstractClock):
84
async def now(self) -> int:
85
"""
86
Get current system time asynchronously.
87
88
Returns:
89
- int: Current time as milliseconds since epoch
90
91
Characteristics:
92
- Async version of TimeClock
93
- Mainly for testing purposes
94
- Returns awaitable time value
95
"""
96
```
97
98
Usage example:
99
100
```python
101
from pyrate_limiter import TimeAsyncClock, BucketAsyncWrapper, InMemoryBucket
102
from pyrate_limiter import Rate, Duration
103
import asyncio
104
105
async def async_clock_example():
106
# Use async clock with async bucket
107
clock = TimeAsyncClock()
108
bucket = BucketAsyncWrapper(InMemoryBucket([Rate(10, Duration.SECOND)]))
109
110
# Clock will be used internally by the bucket/limiter
111
# This is primarily for testing scenarios
112
```
113
114
### SQLite Clock
115
116
Remote clock using SQLite database for distributed time consistency.
117
118
```python { .api }
119
class SQLiteClock(AbstractClock):
120
def __init__(self, conn: Union[sqlite3.Connection, SQLiteBucket]):
121
"""
122
Initialize SQLite clock with database connection.
123
124
Parameters:
125
- conn: SQLite connection or SQLiteBucket instance
126
127
Note: In multiprocessing, use SQLiteBucket to share locks
128
"""
129
130
@classmethod
131
def default(cls) -> "SQLiteClock":
132
"""
133
Create default SQLite clock with temporary database.
134
135
Returns:
136
- SQLiteClock: Clock using temporary SQLite database
137
"""
138
139
def now(self) -> int:
140
"""
141
Get current time from SQLite database.
142
143
Returns:
144
- int: Database time in milliseconds since epoch
145
146
Characteristics:
147
- Uses SQLite's datetime functions
148
- Consistent across processes using same database
149
- Slightly slower than system clocks
150
- Good for distributed consistency
151
"""
152
```
153
154
Usage example:
155
156
```python
157
from pyrate_limiter import SQLiteClock, SQLiteBucket, Limiter
158
from pyrate_limiter import Rate, Duration
159
import sqlite3
160
161
# Method 1: Default SQLite clock
162
clock = SQLiteClock.default()
163
limiter = Limiter(Rate(10, Duration.SECOND), clock=clock)
164
165
# Method 2: SQLite clock with custom connection
166
conn = sqlite3.connect("timesync.db")
167
clock = SQLiteClock(conn)
168
169
# Method 3: SQLite clock sharing bucket's connection and lock
170
bucket = SQLiteBucket.init_from_file(
171
rates=[Rate(5, Duration.SECOND)],
172
db_path="shared.db"
173
)
174
clock = SQLiteClock(bucket) # Shares connection and lock
175
limiter = Limiter(bucket, clock=clock)
176
```
177
178
### PostgreSQL Clock
179
180
Enterprise-grade clock using PostgreSQL for high-accuracy distributed time.
181
182
```python { .api }
183
class PostgresClock(AbstractClock):
184
def __init__(self, pool: ConnectionPool):
185
"""
186
Initialize PostgreSQL clock with connection pool.
187
188
Parameters:
189
- pool: PostgreSQL connection pool
190
"""
191
192
def now(self) -> int:
193
"""
194
Get current time from PostgreSQL database.
195
196
Returns:
197
- int: Database time in milliseconds since epoch
198
199
Characteristics:
200
- Uses PostgreSQL's timestamp functions
201
- High precision and accuracy
202
- Consistent across distributed systems
203
- Requires connection pool management
204
- Best for enterprise applications
205
"""
206
```
207
208
Usage example:
209
210
```python
211
from pyrate_limiter import PostgresClock, PostgresBucket, Limiter
212
from pyrate_limiter import Rate, Duration
213
from psycopg_pool import ConnectionPool
214
215
# Create connection pool
216
pool = ConnectionPool(
217
"host=localhost dbname=mydb user=myuser password=mypass",
218
min_size=1,
219
max_size=10
220
)
221
222
# Use PostgreSQL clock with PostgreSQL bucket
223
clock = PostgresClock(pool)
224
bucket = PostgresBucket([Rate(100, Duration.MINUTE)], pool)
225
limiter = Limiter(bucket, clock=clock)
226
227
# Both bucket and clock use the same connection pool
228
# for consistency and efficiency
229
```
230
231
## Abstract Base Classes
232
233
### AbstractClock Interface
234
235
All clocks implement the same interface for consistent behavior.
236
237
```python { .api }
238
class AbstractClock(ABC):
239
@abstractmethod
240
def now(self) -> Union[int, Awaitable[int]]:
241
"""
242
Get current time in milliseconds.
243
244
Returns:
245
- int: Time in milliseconds since epoch
246
- Awaitable[int]: For async clocks
247
"""
248
```
249
250
### BucketFactory Interface
251
252
Abstract factory for creating and managing buckets with custom routing logic.
253
254
```python { .api }
255
class BucketFactory(ABC):
256
leak_interval: int
257
258
@abstractmethod
259
def wrap_item(self, name: str, weight: int = 1) -> Union[RateItem, Awaitable[RateItem]]:
260
"""
261
Add timestamp to item using clock backend and create RateItem.
262
263
Parameters:
264
- name: Item identifier
265
- weight: Item weight (default: 1)
266
267
Returns:
268
- RateItem: Timestamped rate item
269
- Awaitable[RateItem]: For async factories
270
"""
271
272
@abstractmethod
273
def get(self, item: RateItem) -> Union[AbstractBucket, Awaitable[AbstractBucket]]:
274
"""
275
Get the corresponding bucket for this item.
276
277
Parameters:
278
- item: Rate item to get bucket for
279
280
Returns:
281
- AbstractBucket: Bucket for the item
282
- Awaitable[AbstractBucket]: For async factories
283
"""
284
285
def create(
286
self,
287
clock: AbstractClock,
288
bucket_class: Type[AbstractBucket],
289
*args,
290
**kwargs
291
) -> AbstractBucket:
292
"""Create bucket dynamically and schedule leak operations."""
293
294
def schedule_leak(self, bucket: AbstractBucket, clock: AbstractClock) -> None:
295
"""Schedule leak operations for bucket with associated clock."""
296
297
def get_buckets(self) -> List[AbstractBucket]:
298
"""Get list of all buckets in the factory."""
299
300
def dispose(self, bucket: Union[int, AbstractBucket]) -> bool:
301
"""Remove bucket from factory."""
302
303
def close(self) -> None:
304
"""Close factory and cleanup resources."""
305
```
306
307
## Clock Selection Guidelines
308
309
### Local Development
310
- **TimeClock**: Default choice for simple applications
311
- **MonotonicClock**: When system clock adjustments are a concern
312
313
### Single Server Deployment
314
- **TimeClock**: For most web applications
315
- **MonotonicClock**: For long-running services with precise timing
316
317
### Multi-Process Applications
318
- **SQLiteClock**: For shared time consistency across processes
319
- **TimeClock**: If minor time differences are acceptable
320
321
### Distributed Systems
322
- **PostgresClock**: For enterprise applications requiring precise time sync
323
- **SQLiteClock**: For moderate-scale distributed applications
324
325
### Testing
326
- **TimeAsyncClock**: For async testing scenarios
327
- **TimeClock**: For most test scenarios
328
329
## Time Synchronization Patterns
330
331
### Shared Database Time
332
333
Using database time ensures consistency across distributed components.
334
335
```python
336
from pyrate_limiter import SQLiteClock, SQLiteBucket, Limiter, Rate, Duration
337
338
# All processes use the same database for time and state
339
bucket = SQLiteBucket.init_from_file(
340
rates=[Rate(10, Duration.SECOND)],
341
db_path="/shared/rate_limits.db"
342
)
343
clock = SQLiteClock(bucket)
344
limiter = Limiter(bucket, clock=clock)
345
346
# Time and rate limiting state are both synchronized
347
```
348
349
### Clock Consistency
350
351
Ensuring bucket and clock use compatible time sources.
352
353
```python
354
from pyrate_limiter import PostgresClock, PostgresBucket, Limiter
355
from pyrate_limiter import Rate, Duration
356
from psycopg_pool import ConnectionPool
357
358
pool = ConnectionPool("postgresql://...")
359
360
# Use same connection pool for both time and storage
361
bucket = PostgresBucket([Rate(50, Duration.SECOND)], pool)
362
clock = PostgresClock(pool)
363
limiter = Limiter(bucket, clock=clock)
364
365
# Time calculations and storage operations use same database time
366
```
367
368
### Mixed Clock Scenarios
369
370
Different clocks for different purposes.
371
372
```python
373
from pyrate_limiter import TimeClock, MonotonicClock, InMemoryBucket
374
from pyrate_limiter import Rate, Duration, Limiter
375
376
# Use monotonic clock for stable intervals
377
# but system time for logging/debugging
378
monotonic_clock = MonotonicClock()
379
limiter = Limiter(Rate(10, Duration.SECOND), clock=monotonic_clock)
380
381
# System time for external coordination
382
import time
383
system_time = int(time.time() * 1000)
384
print(f"Rate limited at system time: {system_time}")
385
```