1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
# Migrating to PyrateLimiter 4.0
This guide covers the breaking changes when upgrading from PyrateLimiter 3.x to 4.0.
## Quick Summary
Version 4.0 simplifies the API by:
- Removing exception-based flow control in favor of blocking behavior
- Simplifying the decorator API
- Adding proper async support with `try_acquire_async`
- Moving clock responsibility from Limiter to Bucket
- Adding context manager support
## Breaking Changes
### 1. try_acquire is Now Blocking by Default
This is the most significant change. Previously, `try_acquire` would raise `BucketFullException` when the rate limit was exceeded. Now it blocks until a permit is available.
**v3.x:**
```python
from pyrate_limiter import BucketFullException
try:
limiter.try_acquire("item")
except BucketFullException as e:
print(f"Rate limited: {e.meta_info}")
```
**v4.0:**
```python
# Blocking (default) - waits until permit is available
limiter.try_acquire("item") # blocks until success when called without a timeout
# Non-blocking - returns immediately with False if bucket is full
success = limiter.try_acquire("item", blocking=False)
if not success:
print("Rate limited")
```
Base acquire signature (used by both sync `try_acquire` and async `try_acquire_async`):
```python
def try_acquire(
name: str = "pyrate",
weight: int = 1,
blocking: bool = True, # NEW: wait for permit
timeout: int = -1 # NEW: max wait time (primarily used by try_acquire_async)
) -> bool
```
### 2. Use try_acquire_async for Async Code
For async code, use the new `try_acquire_async` method which uses `asyncio.Lock` and `asyncio.sleep`:
**v3.x:**
```python
# v3.x used try_acquire for both sync and async
result = await limiter.try_acquire("item")
```
**v4.0:**
```python
# Use try_acquire_async for proper async behavior
success = await limiter.try_acquire_async("item")
# With timeout (in seconds)
success = await limiter.try_acquire_async("item", timeout=5)
if not success:
print("Timed out waiting for permit")
# Non-blocking async
success = await limiter.try_acquire_async("item", blocking=False)
```
### 3. Decorator API Simplified
The decorator no longer requires a mapping function. Pass `name` and `weight` directly.
**v3.x:**
```python
decorator = limiter.as_decorator()
def mapping(*args, **kwargs):
return ("item_name", 1) # (name, weight) tuple
@decorator(mapping)
def my_function():
pass
@decorator(mapping)
async def my_async_function():
pass
```
**v4.0:**
```python
@limiter.as_decorator(name="item_name", weight=1)
def my_function():
pass
@limiter.as_decorator(name="item_name", weight=1)
async def my_async_function():
pass
```
### 4. Exception Classes Removed
`BucketFullException` and `LimiterDelayException` have been removed entirely. Use `blocking=False` to get non-blocking behavior.
**v3.x:**
```python
from pyrate_limiter import BucketFullException, LimiterDelayException
try:
limiter.try_acquire("item")
except BucketFullException as e:
handle_rate_limit(e.meta_info)
except LimiterDelayException as e:
handle_delay_exceeded(e.meta_info)
```
**v4.0:**
```python
success = limiter.try_acquire("item", blocking=False)
if not success:
handle_rate_limit()
```
### 5. Limiter Constructor Simplified
**v3.x:**
```python
limiter = Limiter(
bucket,
clock=TimeClock(),
raise_when_fail=True,
max_delay=5000,
retry_until_max_delay=True,
)
```
**v4.0:**
```python
limiter = Limiter(
bucket,
buffer_ms=50, # optional, default 50ms
)
```
Removed parameters:
- `clock` - each bucket now manages its own clock via `bucket.now()`
- `raise_when_fail` - no exceptions are raised; use `blocking=False`
- `max_delay` - blocking is controlled per-call via `blocking` parameter
- `retry_until_max_delay` - blocking mode retries automatically
### 6. BucketFactory Changes
If you implement a custom `BucketFactory`, remove the clock parameter from `schedule_leak` and `create` calls.
**v3.x:**
```python
class MyFactory(BucketFactory):
def __init__(self, clock):
self.clock = clock
def wrap_item(self, name, weight=1):
return RateItem(name, self.clock.now(), weight=weight)
bucket = factory.create(clock, InMemoryBucket, rates)
factory.schedule_leak(bucket, clock)
```
**v4.0:**
```python
class MyFactory(BucketFactory):
def wrap_item(self, name, weight=1):
return RateItem(name, self.bucket.now(), weight=weight)
bucket = factory.create(InMemoryBucket, rates)
factory.schedule_leak(bucket)
```
### 7. Clock Changes
*Most users won't need to change anything here - clocks are now managed internally by buckets.*
If you explicitly used clock classes:
- `TimeClock` removed - buckets use `MonotonicClock` by default
- `SQLiteClock` removed - buckets manage their own time
- `TimeAsyncClock` renamed to `MonotonicAsyncClock`
## New Features
### Context Manager Support
```python
with Limiter(bucket) as limiter:
limiter.try_acquire("item")
# Resources automatically cleaned up
```
### limiter_factory Module
Convenience functions for common patterns:
```python
from pyrate_limiter import limiter_factory, Duration
limiter = limiter_factory.create_inmemory_limiter(
rate_per_duration=5,
duration=Duration.SECOND,
)
limiter = limiter_factory.create_sqlite_limiter(
rate_per_duration=100,
duration=Duration.MINUTE,
db_path="/path/to/db.sqlite",
)
```
### Web Request Helpers
```python
from pyrate_limiter.extras.aiohttp_limiter import RateLimitedSession
from pyrate_limiter.extras.httpx_limiter import RateLimiterTransport
from pyrate_limiter.extras.requests_limiter import RateLimitedRequestsSession
```
### MultiprocessBucket
```python
from pyrate_limiter import MultiprocessBucket
bucket = MultiprocessBucket(rates, manager)
```
|