fluxlimit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fluxlimit/__init__.py +74 -0
- fluxlimit/algorithms/__init__.py +22 -0
- fluxlimit/algorithms/base.py +64 -0
- fluxlimit/algorithms/fixed_window.py +64 -0
- fluxlimit/algorithms/leaky_bucket.py +64 -0
- fluxlimit/algorithms/sliding_window.py +64 -0
- fluxlimit/algorithms/token_bucket.py +67 -0
- fluxlimit/backends/__init__.py +13 -0
- fluxlimit/backends/base.py +79 -0
- fluxlimit/backends/memory.py +207 -0
- fluxlimit/backends/postgres.py +305 -0
- fluxlimit/backends/redis.py +335 -0
- fluxlimit/core.py +225 -0
- fluxlimit/exceptions.py +33 -0
- fluxlimit/metrics.py +68 -0
- fluxlimit/middleware/__init__.py +27 -0
- fluxlimit/middleware/_helpers.py +40 -0
- fluxlimit/middleware/asgi.py +129 -0
- fluxlimit/middleware/flask.py +234 -0
- fluxlimit-0.1.0.dist-info/METADATA +339 -0
- fluxlimit-0.1.0.dist-info/RECORD +23 -0
- fluxlimit-0.1.0.dist-info/WHEEL +4 -0
- fluxlimit-0.1.0.dist-info/licenses/LICENSE +21 -0
fluxlimit/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""fluxlimit - Distributed rate limiting with fairness for Python.
|
|
2
|
+
|
|
3
|
+
A production-ready rate limiting library supporting:
|
|
4
|
+
- Token bucket, leaky bucket, fixed window, sliding window algorithms
|
|
5
|
+
- Distributed fairness via Redis (Lua scripts) and PostgreSQL (advisory locks)
|
|
6
|
+
- Cost-based limiting for different endpoint weights
|
|
7
|
+
- FastAPI/Starlette and Flask middleware integrations
|
|
8
|
+
- Prometheus metrics export
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
from .core import RateLimiter, LimitRule
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
FluxlimitError,
|
|
16
|
+
RateLimitExceeded,
|
|
17
|
+
BackendError,
|
|
18
|
+
ConfigurationError,
|
|
19
|
+
)
|
|
20
|
+
from .algorithms import (
|
|
21
|
+
TokenBucket,
|
|
22
|
+
TokenBucketConfig,
|
|
23
|
+
LeakyBucket,
|
|
24
|
+
LeakyBucketConfig,
|
|
25
|
+
FixedWindow,
|
|
26
|
+
FixedWindowConfig,
|
|
27
|
+
SlidingWindow,
|
|
28
|
+
SlidingWindowConfig,
|
|
29
|
+
LimitResult,
|
|
30
|
+
)
|
|
31
|
+
from .backends import (
|
|
32
|
+
MemoryBackend,
|
|
33
|
+
RedisBackend,
|
|
34
|
+
PostgresBackend,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __getattr__(name: str):
|
|
39
|
+
if name in ("RateLimitMiddleware", "rate_limit", "Fluxlimit", "init_fluxlimit"):
|
|
40
|
+
from . import middleware
|
|
41
|
+
|
|
42
|
+
return getattr(middleware, name)
|
|
43
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# Core
|
|
48
|
+
"RateLimiter",
|
|
49
|
+
"LimitRule",
|
|
50
|
+
# Exceptions
|
|
51
|
+
"FluxlimitError",
|
|
52
|
+
"RateLimitExceeded",
|
|
53
|
+
"BackendError",
|
|
54
|
+
"ConfigurationError",
|
|
55
|
+
# Algorithms
|
|
56
|
+
"TokenBucket",
|
|
57
|
+
"TokenBucketConfig",
|
|
58
|
+
"LeakyBucket",
|
|
59
|
+
"LeakyBucketConfig",
|
|
60
|
+
"FixedWindow",
|
|
61
|
+
"FixedWindowConfig",
|
|
62
|
+
"SlidingWindow",
|
|
63
|
+
"SlidingWindowConfig",
|
|
64
|
+
"LimitResult",
|
|
65
|
+
# Backends
|
|
66
|
+
"MemoryBackend",
|
|
67
|
+
"RedisBackend",
|
|
68
|
+
"PostgresBackend",
|
|
69
|
+
# Middleware
|
|
70
|
+
"RateLimitMiddleware",
|
|
71
|
+
"rate_limit",
|
|
72
|
+
"Fluxlimit",
|
|
73
|
+
"init_fluxlimit",
|
|
74
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Rate limiting algorithms."""
|
|
2
|
+
|
|
3
|
+
from .token_bucket import TokenBucket, TokenBucketConfig
|
|
4
|
+
from .leaky_bucket import LeakyBucket, LeakyBucketConfig
|
|
5
|
+
from .fixed_window import FixedWindow, FixedWindowConfig
|
|
6
|
+
from .sliding_window import SlidingWindow, SlidingWindowConfig
|
|
7
|
+
from .base import BaseAlgorithm, LimitResult, AlgorithmConfig
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"TokenBucket",
|
|
11
|
+
"TokenBucketConfig",
|
|
12
|
+
"LeakyBucket",
|
|
13
|
+
"LeakyBucketConfig",
|
|
14
|
+
"FixedWindow",
|
|
15
|
+
"FixedWindowConfig",
|
|
16
|
+
"SlidingWindow",
|
|
17
|
+
"SlidingWindowConfig",
|
|
18
|
+
|
|
19
|
+
"BaseAlgorithm",
|
|
20
|
+
"LimitResult",
|
|
21
|
+
"AlgorithmConfig",
|
|
22
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Base class for rate limiting algorithms."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class LimitResult:
|
|
10
|
+
"""Result of a rate limit check.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
allowed: Whether the request is allowed.
|
|
14
|
+
remaining: Number of requests remaining in the current window.
|
|
15
|
+
reset_after: Seconds until the limit resets.
|
|
16
|
+
retry_after: Seconds until the request can be retried (only if not allowed).
|
|
17
|
+
"""
|
|
18
|
+
allowed: bool
|
|
19
|
+
remaining: int
|
|
20
|
+
reset_after: float
|
|
21
|
+
retry_after: float = 0.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class AlgorithmConfig:
|
|
26
|
+
"""Configuration for a rate limiting algorithm."""
|
|
27
|
+
key: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseAlgorithm(ABC):
|
|
31
|
+
"""Abstract base class for rate limiting algorithms."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, key_prefix: str = "fluxlimit"):
|
|
34
|
+
self.key_prefix = key_prefix
|
|
35
|
+
|
|
36
|
+
def _make_key(self, identifier: str, config: AlgorithmConfig) -> str:
|
|
37
|
+
"""Generate a storage key for the given identifier and config."""
|
|
38
|
+
return f"{self.key_prefix}:{config.key}:{identifier}"
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
async def check(
|
|
42
|
+
self,
|
|
43
|
+
backend,
|
|
44
|
+
identifier: str,
|
|
45
|
+
config: AlgorithmConfig,
|
|
46
|
+
cost: int = 1
|
|
47
|
+
) -> LimitResult:
|
|
48
|
+
"""Check if a request is allowed under the rate limit.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
backend: The storage backend to use.
|
|
52
|
+
identifier: Unique identifier for the client (IP, API key, user ID).
|
|
53
|
+
config: Algorithm-specific configuration.
|
|
54
|
+
cost: How many tokens/credits this request consumes.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
LimitResult indicating whether the request is allowed.
|
|
58
|
+
"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_config_class(self) -> type:
|
|
63
|
+
"""Return the configuration dataclass for this algorithm."""
|
|
64
|
+
pass
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Fixed window rate limiting algorithm."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import time
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
from .base import BaseAlgorithm, LimitResult, AlgorithmConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class FixedWindowConfig(AlgorithmConfig):
|
|
12
|
+
"""Configuration for fixed window algorithm.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
key: Unique key for this limit configuration.
|
|
16
|
+
max_requests: Maximum requests allowed per window.
|
|
17
|
+
window_seconds: Duration of each window in seconds.
|
|
18
|
+
"""
|
|
19
|
+
max_requests: int = 100
|
|
20
|
+
window_seconds: int = 60
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FixedWindow(BaseAlgorithm):
|
|
24
|
+
"""Fixed window rate limiter.
|
|
25
|
+
|
|
26
|
+
Counts requests in fixed time windows. Simple but can allow
|
|
27
|
+
bursts at window boundaries (2x the limit in a short period).
|
|
28
|
+
|
|
29
|
+
Use for simple cases where boundary bursts are acceptable.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, key_prefix: str = "fluxlimit:fixed_window"):
|
|
33
|
+
super().__init__(key_prefix)
|
|
34
|
+
|
|
35
|
+
def get_config_class(self) -> type:
|
|
36
|
+
return FixedWindowConfig
|
|
37
|
+
|
|
38
|
+
async def check(
|
|
39
|
+
self,
|
|
40
|
+
backend,
|
|
41
|
+
identifier: str,
|
|
42
|
+
config: FixedWindowConfig,
|
|
43
|
+
cost: int = 1
|
|
44
|
+
) -> LimitResult:
|
|
45
|
+
"""Check if request is allowed using fixed window algorithm."""
|
|
46
|
+
key = self._make_key(identifier, config)
|
|
47
|
+
now = time.time()
|
|
48
|
+
window_start = math.floor(now / config.window_seconds) * config.window_seconds
|
|
49
|
+
window_key = f"{key}:{int(window_start)}"
|
|
50
|
+
|
|
51
|
+
result = await backend.fixed_window_increment(
|
|
52
|
+
key=window_key,
|
|
53
|
+
max_requests=config.max_requests,
|
|
54
|
+
window_seconds=config.window_seconds,
|
|
55
|
+
cost=cost,
|
|
56
|
+
now=now
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return LimitResult(
|
|
60
|
+
allowed=result["allowed"],
|
|
61
|
+
remaining=result["remaining"],
|
|
62
|
+
reset_after=result["reset_after"],
|
|
63
|
+
retry_after=result.get("retry_after", 0.0)
|
|
64
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Leaky bucket rate limiting algorithm."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .base import BaseAlgorithm, LimitResult, AlgorithmConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class LeakyBucketConfig(AlgorithmConfig):
|
|
11
|
+
"""Configuration for leaky bucket algorithm.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
key: Unique key for this limit configuration.
|
|
15
|
+
rate: Requests processed per second (leak rate).
|
|
16
|
+
capacity: Maximum queue size.
|
|
17
|
+
"""
|
|
18
|
+
rate: float = 10.0
|
|
19
|
+
capacity: int = 100
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LeakyBucket(BaseAlgorithm):
|
|
23
|
+
"""Leaky bucket rate limiter.
|
|
24
|
+
|
|
25
|
+
Requests are queued and processed at a constant rate.
|
|
26
|
+
If the queue is full, requests are rejected.
|
|
27
|
+
|
|
28
|
+
This provides:
|
|
29
|
+
- Strict rate enforcement (no bursts allowed)
|
|
30
|
+
- Smooth output traffic
|
|
31
|
+
- Protection against traffic spikes
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, key_prefix: str = "fluxlimit:leaky_bucket"):
|
|
35
|
+
super().__init__(key_prefix)
|
|
36
|
+
|
|
37
|
+
def get_config_class(self) -> type:
|
|
38
|
+
return LeakyBucketConfig
|
|
39
|
+
|
|
40
|
+
async def check(
|
|
41
|
+
self,
|
|
42
|
+
backend,
|
|
43
|
+
identifier: str,
|
|
44
|
+
config: LeakyBucketConfig,
|
|
45
|
+
cost: int = 1
|
|
46
|
+
) -> LimitResult:
|
|
47
|
+
"""Check if request is allowed using leaky bucket algorithm."""
|
|
48
|
+
key = self._make_key(identifier, config)
|
|
49
|
+
now = time.time()
|
|
50
|
+
|
|
51
|
+
result = await backend.leaky_bucket_request(
|
|
52
|
+
key=key,
|
|
53
|
+
rate=config.rate,
|
|
54
|
+
capacity=config.capacity,
|
|
55
|
+
cost=cost,
|
|
56
|
+
now=now
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return LimitResult(
|
|
60
|
+
allowed=result["allowed"],
|
|
61
|
+
remaining=result["remaining"],
|
|
62
|
+
reset_after=result["reset_after"],
|
|
63
|
+
retry_after=result.get("retry_after", 0.0)
|
|
64
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Sliding window rate limiting algorithm."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .base import BaseAlgorithm, LimitResult, AlgorithmConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class SlidingWindowConfig(AlgorithmConfig):
|
|
11
|
+
"""Configuration for sliding window algorithm.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
key: Unique key for this limit configuration.
|
|
15
|
+
max_requests: Maximum requests allowed in the window.
|
|
16
|
+
window_seconds: Duration of the sliding window in seconds.
|
|
17
|
+
"""
|
|
18
|
+
max_requests: int = 100
|
|
19
|
+
window_seconds: int = 60
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SlidingWindow(BaseAlgorithm):
|
|
23
|
+
"""Sliding window rate limiter.
|
|
24
|
+
|
|
25
|
+
Uses a sliding time window to count requests. More accurate
|
|
26
|
+
than fixed window but requires more storage (tracking timestamps).
|
|
27
|
+
|
|
28
|
+
This provides:
|
|
29
|
+
- No boundary burst issues
|
|
30
|
+
- Smooth rate limiting over time
|
|
31
|
+
- Higher accuracy at the cost of storage
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, key_prefix: str = "fluxlimit:sliding_window"):
|
|
35
|
+
super().__init__(key_prefix)
|
|
36
|
+
|
|
37
|
+
def get_config_class(self) -> type:
|
|
38
|
+
return SlidingWindowConfig
|
|
39
|
+
|
|
40
|
+
async def check(
|
|
41
|
+
self,
|
|
42
|
+
backend,
|
|
43
|
+
identifier: str,
|
|
44
|
+
config: SlidingWindowConfig,
|
|
45
|
+
cost: int = 1
|
|
46
|
+
) -> LimitResult:
|
|
47
|
+
"""Check if request is allowed using sliding window algorithm."""
|
|
48
|
+
key = self._make_key(identifier, config)
|
|
49
|
+
now = time.time()
|
|
50
|
+
|
|
51
|
+
result = await backend.sliding_window_check(
|
|
52
|
+
key=key,
|
|
53
|
+
max_requests=config.max_requests,
|
|
54
|
+
window_seconds=config.window_seconds,
|
|
55
|
+
cost=cost,
|
|
56
|
+
now=now
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return LimitResult(
|
|
60
|
+
allowed=result["allowed"],
|
|
61
|
+
remaining=result["remaining"],
|
|
62
|
+
reset_after=result["reset_after"],
|
|
63
|
+
retry_after=result.get("retry_after", 0.0)
|
|
64
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Token bucket rate limiting algorithm."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import time
|
|
6
|
+
import math
|
|
7
|
+
|
|
8
|
+
from .base import BaseAlgorithm, LimitResult, AlgorithmConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class TokenBucketConfig(AlgorithmConfig):
|
|
13
|
+
"""Configuration for token bucket algorithm.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
key: Unique key for this limit configuration.
|
|
17
|
+
rate: Tokens added per second (sustained rate).
|
|
18
|
+
burst: Maximum bucket size (burst capacity).
|
|
19
|
+
"""
|
|
20
|
+
rate: float = 10.0
|
|
21
|
+
burst: int = 20
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TokenBucket(BaseAlgorithm):
|
|
25
|
+
"""Token bucket rate limiter.
|
|
26
|
+
|
|
27
|
+
The bucket starts full. Each request consumes tokens.
|
|
28
|
+
Tokens are replenished at a constant rate up to the burst capacity.
|
|
29
|
+
|
|
30
|
+
This provides:
|
|
31
|
+
- Smooth traffic shaping (requests are spread out)
|
|
32
|
+
- Bursty traffic tolerance (up to burst capacity)
|
|
33
|
+
- Distributed fairness when used with a shared backend
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, key_prefix: str = "fluxlimit:token_bucket"):
|
|
37
|
+
super().__init__(key_prefix)
|
|
38
|
+
|
|
39
|
+
def get_config_class(self) -> type:
|
|
40
|
+
return TokenBucketConfig
|
|
41
|
+
|
|
42
|
+
async def check(
|
|
43
|
+
self,
|
|
44
|
+
backend,
|
|
45
|
+
identifier: str,
|
|
46
|
+
config: TokenBucketConfig,
|
|
47
|
+
cost: int = 1
|
|
48
|
+
) -> LimitResult:
|
|
49
|
+
"""Check if request is allowed using token bucket algorithm."""
|
|
50
|
+
key = self._make_key(identifier, config)
|
|
51
|
+
now = time.time()
|
|
52
|
+
|
|
53
|
+
# Use backend's atomic compare-and-swap or Lua script for distributed fairness
|
|
54
|
+
result = await backend.token_bucket_consume(
|
|
55
|
+
key=key,
|
|
56
|
+
rate=config.rate,
|
|
57
|
+
burst=config.burst,
|
|
58
|
+
cost=cost,
|
|
59
|
+
now=now
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return LimitResult(
|
|
63
|
+
allowed=result["allowed"],
|
|
64
|
+
remaining=result["remaining"],
|
|
65
|
+
reset_after=result["reset_after"],
|
|
66
|
+
retry_after=result.get("retry_after", 0.0)
|
|
67
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Storage backends for fluxlimit."""
|
|
2
|
+
|
|
3
|
+
from .base import BaseBackend
|
|
4
|
+
from .memory import MemoryBackend
|
|
5
|
+
from .redis import RedisBackend
|
|
6
|
+
from .postgres import PostgresBackend
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"BaseBackend",
|
|
10
|
+
"MemoryBackend",
|
|
11
|
+
"RedisBackend",
|
|
12
|
+
"PostgresBackend",
|
|
13
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Base class for rate limiter backends."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseBackend(ABC):
|
|
8
|
+
"""Abstract base class for rate limiter storage backends.
|
|
9
|
+
|
|
10
|
+
All backends must implement atomic operations for each algorithm
|
|
11
|
+
to ensure distributed fairness.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
async def connect(self) -> None:
|
|
16
|
+
"""Establish connection to the backend."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
async def disconnect(self) -> None:
|
|
21
|
+
"""Close connection to the backend."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def token_bucket_consume(
|
|
26
|
+
self,
|
|
27
|
+
key: str,
|
|
28
|
+
rate: float,
|
|
29
|
+
burst: int,
|
|
30
|
+
cost: int,
|
|
31
|
+
now: float
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""Atomically consume tokens from a bucket.
|
|
34
|
+
|
|
35
|
+
Returns dict with keys: allowed (bool), remaining (int),
|
|
36
|
+
reset_after (float), retry_after (float).
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
async def leaky_bucket_request(
|
|
42
|
+
self,
|
|
43
|
+
key: str,
|
|
44
|
+
rate: float,
|
|
45
|
+
capacity: int,
|
|
46
|
+
cost: int,
|
|
47
|
+
now: float
|
|
48
|
+
) -> Dict[str, Any]:
|
|
49
|
+
"""Atomically request space in leaky bucket."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def fixed_window_increment(
|
|
54
|
+
self,
|
|
55
|
+
key: str,
|
|
56
|
+
max_requests: int,
|
|
57
|
+
window_seconds: int,
|
|
58
|
+
cost: int,
|
|
59
|
+
now: float
|
|
60
|
+
) -> Dict[str, Any]:
|
|
61
|
+
"""Atomically increment counter for fixed window."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def sliding_window_check(
|
|
66
|
+
self,
|
|
67
|
+
key: str,
|
|
68
|
+
max_requests: int,
|
|
69
|
+
window_seconds: int,
|
|
70
|
+
cost: int,
|
|
71
|
+
now: float
|
|
72
|
+
) -> Dict[str, Any]:
|
|
73
|
+
"""Atomically check sliding window."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def health_check(self) -> bool:
|
|
78
|
+
"""Check if backend is healthy."""
|
|
79
|
+
pass
|