- Refactor Redis backend connection handling and pool management - Update algorithm implementations with improved type annotations - Enhance config loader validation with stricter Pydantic schemas - Improve decorator and middleware error handling - Expand example scripts with better docstrings and usage patterns - Add new 00_basic_usage.py example for quick start - Reorganize examples directory structure - Fix type annotation inconsistencies across core modules - Update dependencies in pyproject.toml
267 lines
6.2 KiB
ReStructuredText
267 lines
6.2 KiB
ReStructuredText
Backends API
|
|
============
|
|
|
|
Storage backends for rate limit state.
|
|
|
|
Backend (Base Class)
|
|
--------------------
|
|
|
|
.. py:class:: Backend
|
|
|
|
Abstract base class for rate limit storage backends.
|
|
|
|
All backends must implement these methods:
|
|
|
|
.. py:method:: get(key)
|
|
:async:
|
|
|
|
Get the current state for a key.
|
|
|
|
:param key: The rate limit key.
|
|
:type key: str
|
|
:returns: The stored state dictionary or None if not found.
|
|
:rtype: dict[str, Any] | None
|
|
|
|
.. py:method:: set(key, value, *, ttl)
|
|
:async:
|
|
|
|
Set the state for a key with TTL.
|
|
|
|
:param key: The rate limit key.
|
|
:type key: str
|
|
:param value: The state dictionary to store.
|
|
:type value: dict[str, Any]
|
|
:param ttl: Time-to-live in seconds.
|
|
:type ttl: float
|
|
|
|
.. py:method:: delete(key)
|
|
:async:
|
|
|
|
Delete the state for a key.
|
|
|
|
:param key: The rate limit key.
|
|
:type key: str
|
|
|
|
.. py:method:: exists(key)
|
|
:async:
|
|
|
|
Check if a key exists.
|
|
|
|
:param key: The rate limit key.
|
|
:type key: str
|
|
:returns: True if the key exists.
|
|
:rtype: bool
|
|
|
|
.. py:method:: increment(key, amount=1)
|
|
:async:
|
|
|
|
Atomically increment a counter.
|
|
|
|
:param key: The rate limit key.
|
|
:type key: str
|
|
:param amount: The amount to increment by.
|
|
:type amount: int
|
|
:returns: The new value after incrementing.
|
|
:rtype: int
|
|
|
|
.. py:method:: clear()
|
|
:async:
|
|
|
|
Clear all rate limit data.
|
|
|
|
.. py:method:: close()
|
|
:async:
|
|
|
|
Close the backend connection.
|
|
|
|
Backends support async context manager protocol:
|
|
|
|
.. code-block:: python
|
|
|
|
async with MemoryBackend() as backend:
|
|
await backend.set("key", {"count": 1}, ttl=60)
|
|
|
|
MemoryBackend
|
|
-------------
|
|
|
|
.. py:class:: MemoryBackend(max_size=10000, cleanup_interval=60)
|
|
|
|
In-memory storage backend with LRU eviction and TTL cleanup.
|
|
|
|
:param max_size: Maximum number of keys to store.
|
|
:type max_size: int
|
|
:param cleanup_interval: How often to clean expired entries (seconds).
|
|
:type cleanup_interval: float
|
|
|
|
**Usage:**
|
|
|
|
.. code-block:: python
|
|
|
|
from fastapi_traffic import MemoryBackend, RateLimiter
|
|
|
|
backend = MemoryBackend(max_size=10000)
|
|
limiter = RateLimiter(backend)
|
|
|
|
.. py:method:: get_stats()
|
|
|
|
Get statistics about the backend.
|
|
|
|
:returns: Dictionary with stats like key count, memory usage.
|
|
:rtype: dict[str, Any]
|
|
|
|
.. py:method:: start_cleanup()
|
|
:async:
|
|
|
|
Start the background cleanup task.
|
|
|
|
.. py:method:: stop_cleanup()
|
|
:async:
|
|
|
|
Stop the background cleanup task.
|
|
|
|
SQLiteBackend
|
|
-------------
|
|
|
|
.. py:class:: SQLiteBackend(db_path, cleanup_interval=300)
|
|
|
|
SQLite storage backend for persistent rate limiting.
|
|
|
|
:param db_path: Path to the SQLite database file.
|
|
:type db_path: str | Path
|
|
:param cleanup_interval: How often to clean expired entries (seconds).
|
|
:type cleanup_interval: float
|
|
|
|
**Usage:**
|
|
|
|
.. code-block:: python
|
|
|
|
from fastapi_traffic import SQLiteBackend, RateLimiter
|
|
|
|
backend = SQLiteBackend("rate_limits.db")
|
|
limiter = RateLimiter(backend)
|
|
|
|
@app.on_event("startup")
|
|
async def startup():
|
|
await limiter.initialize()
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown():
|
|
await limiter.close()
|
|
|
|
.. py:method:: initialize()
|
|
:async:
|
|
|
|
Initialize the database schema.
|
|
|
|
Features:
|
|
|
|
- WAL mode for better concurrent performance
|
|
- Automatic schema creation
|
|
- Connection pooling
|
|
- Background cleanup of expired entries
|
|
|
|
RedisBackend
|
|
------------
|
|
|
|
.. py:class:: RedisBackend
|
|
|
|
Redis storage backend for distributed rate limiting.
|
|
|
|
.. py:method:: from_url(url, *, key_prefix="", **kwargs)
|
|
:classmethod:
|
|
|
|
Create a RedisBackend from a Redis URL. This is an async classmethod.
|
|
|
|
:param url: Redis connection URL.
|
|
:type url: str
|
|
:param key_prefix: Prefix for all keys.
|
|
:type key_prefix: str
|
|
:returns: Configured RedisBackend instance.
|
|
:rtype: RedisBackend
|
|
|
|
**Usage:**
|
|
|
|
.. code-block:: python
|
|
|
|
from fastapi_traffic.backends.redis import RedisBackend
|
|
from fastapi_traffic import RateLimiter
|
|
|
|
@app.on_event("startup")
|
|
async def startup():
|
|
backend = await RedisBackend.from_url("redis://localhost:6379/0")
|
|
limiter = RateLimiter(backend)
|
|
|
|
**Connection examples:**
|
|
|
|
.. code-block:: python
|
|
|
|
# Simple connection
|
|
backend = await RedisBackend.from_url("redis://localhost:6379/0")
|
|
|
|
# With password
|
|
backend = await RedisBackend.from_url("redis://:password@localhost:6379/0")
|
|
|
|
# With key prefix
|
|
backend = await RedisBackend.from_url(
|
|
"redis://localhost:6379/0",
|
|
key_prefix="myapp:ratelimit:",
|
|
)
|
|
|
|
.. py:method:: get_stats()
|
|
:async:
|
|
|
|
Get statistics about the Redis backend.
|
|
|
|
:returns: Dictionary with stats like key count, memory usage.
|
|
:rtype: dict[str, Any]
|
|
|
|
Features:
|
|
|
|
- Atomic operations via Lua scripts
|
|
- Automatic key expiration
|
|
- Connection pooling
|
|
- Support for Redis Sentinel and Cluster
|
|
|
|
Implementing Custom Backends
|
|
----------------------------
|
|
|
|
To create a custom backend, inherit from ``Backend`` and implement all abstract
|
|
methods:
|
|
|
|
.. code-block:: python
|
|
|
|
from fastapi_traffic.backends.base import Backend
|
|
from typing import Any
|
|
|
|
class MyBackend(Backend):
|
|
async def get(self, key: str) -> dict[str, Any] | None:
|
|
# Retrieve state from your storage
|
|
pass
|
|
|
|
async def set(self, key: str, value: dict[str, Any], *, ttl: float) -> None:
|
|
# Store state with expiration
|
|
pass
|
|
|
|
async def delete(self, key: str) -> None:
|
|
# Remove a key
|
|
pass
|
|
|
|
async def exists(self, key: str) -> bool:
|
|
# Check if key exists
|
|
pass
|
|
|
|
async def increment(self, key: str, amount: int = 1) -> int:
|
|
# Atomically increment (important for accuracy)
|
|
pass
|
|
|
|
async def clear(self) -> None:
|
|
# Clear all data
|
|
pass
|
|
|
|
async def close(self) -> None:
|
|
# Clean up connections
|
|
pass
|
|
|
|
The ``value`` dictionary contains algorithm-specific state. Your backend should
|
|
serialize it appropriately (JSON works well for most cases).
|