release: bump version to 0.3.0
- 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
This commit is contained in:
315
docs/user-guide/configuration.rst
Normal file
315
docs/user-guide/configuration.rst
Normal file
@@ -0,0 +1,315 @@
|
||||
Configuration
|
||||
=============
|
||||
|
||||
FastAPI Traffic supports loading configuration from environment variables and files.
|
||||
This makes it easy to manage settings across different environments without changing code.
|
||||
|
||||
Configuration Loader
|
||||
--------------------
|
||||
|
||||
The ``ConfigLoader`` class handles loading configuration from various sources:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import ConfigLoader, RateLimitConfig
|
||||
|
||||
loader = ConfigLoader()
|
||||
|
||||
# Load from environment variables
|
||||
config = loader.load_rate_limit_config_from_env()
|
||||
|
||||
# Load from a JSON file
|
||||
config = loader.load_rate_limit_config_from_json("config/rate_limits.json")
|
||||
|
||||
# Load from a .env file
|
||||
config = loader.load_rate_limit_config_from_env_file(".env")
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
Set rate limit configuration using environment variables with the ``FASTAPI_TRAFFIC_``
|
||||
prefix:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Basic settings
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_LIMIT=100
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_WINDOW_SIZE=60
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_ALGORITHM=sliding_window_counter
|
||||
|
||||
# Optional settings
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_KEY_PREFIX=myapp
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_BURST_SIZE=20
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_INCLUDE_HEADERS=true
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_ERROR_MESSAGE="Too many requests"
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_STATUS_CODE=429
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_SKIP_ON_ERROR=false
|
||||
export FASTAPI_TRAFFIC_RATE_LIMIT_COST=1
|
||||
|
||||
Then load them in your app:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import load_rate_limit_config_from_env, rate_limit
|
||||
|
||||
# Load config from environment
|
||||
config = load_rate_limit_config_from_env()
|
||||
|
||||
# Use it with the decorator
|
||||
@app.get("/api/data")
|
||||
@rate_limit(config.limit, config.window_size, algorithm=config.algorithm)
|
||||
async def get_data(request: Request):
|
||||
return {"data": "here"}
|
||||
|
||||
Custom Prefix
|
||||
-------------
|
||||
|
||||
If ``FASTAPI_TRAFFIC_`` conflicts with something else, use a custom prefix:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = ConfigLoader(prefix="MYAPP_RATELIMIT")
|
||||
config = loader.load_rate_limit_config_from_env()
|
||||
|
||||
# Now reads from:
|
||||
# MYAPP_RATELIMIT_RATE_LIMIT_LIMIT=100
|
||||
# MYAPP_RATELIMIT_RATE_LIMIT_WINDOW_SIZE=60
|
||||
# etc.
|
||||
|
||||
JSON Configuration
|
||||
------------------
|
||||
|
||||
For more complex setups, use a JSON file:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"limit": 100,
|
||||
"window_size": 60,
|
||||
"algorithm": "token_bucket",
|
||||
"burst_size": 25,
|
||||
"key_prefix": "api",
|
||||
"include_headers": true,
|
||||
"error_message": "Rate limit exceeded. Please slow down.",
|
||||
"status_code": 429,
|
||||
"skip_on_error": false,
|
||||
"cost": 1
|
||||
}
|
||||
|
||||
Load it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import ConfigLoader
|
||||
|
||||
loader = ConfigLoader()
|
||||
config = loader.load_rate_limit_config_from_json("config/rate_limits.json")
|
||||
|
||||
.env Files
|
||||
----------
|
||||
|
||||
You can also use ``.env`` files, which is handy for local development:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# .env
|
||||
FASTAPI_TRAFFIC_RATE_LIMIT_LIMIT=100
|
||||
FASTAPI_TRAFFIC_RATE_LIMIT_WINDOW_SIZE=60
|
||||
FASTAPI_TRAFFIC_RATE_LIMIT_ALGORITHM=sliding_window
|
||||
|
||||
Load it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = ConfigLoader()
|
||||
config = loader.load_rate_limit_config_from_env_file(".env")
|
||||
|
||||
Global Configuration
|
||||
--------------------
|
||||
|
||||
Besides per-endpoint configuration, you can set global defaults:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Global settings
|
||||
export FASTAPI_TRAFFIC_GLOBAL_ENABLED=true
|
||||
export FASTAPI_TRAFFIC_GLOBAL_DEFAULT_LIMIT=100
|
||||
export FASTAPI_TRAFFIC_GLOBAL_DEFAULT_WINDOW_SIZE=60
|
||||
export FASTAPI_TRAFFIC_GLOBAL_DEFAULT_ALGORITHM=sliding_window_counter
|
||||
export FASTAPI_TRAFFIC_GLOBAL_KEY_PREFIX=fastapi_traffic
|
||||
export FASTAPI_TRAFFIC_GLOBAL_INCLUDE_HEADERS=true
|
||||
export FASTAPI_TRAFFIC_GLOBAL_ERROR_MESSAGE="Rate limit exceeded"
|
||||
export FASTAPI_TRAFFIC_GLOBAL_STATUS_CODE=429
|
||||
export FASTAPI_TRAFFIC_GLOBAL_SKIP_ON_ERROR=false
|
||||
export FASTAPI_TRAFFIC_GLOBAL_HEADERS_PREFIX=X-RateLimit
|
||||
|
||||
Load global config:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import load_global_config_from_env, RateLimiter
|
||||
from fastapi_traffic.core.limiter import set_limiter
|
||||
|
||||
global_config = load_global_config_from_env()
|
||||
limiter = RateLimiter(config=global_config)
|
||||
set_limiter(limiter)
|
||||
|
||||
Auto-Detection
|
||||
--------------
|
||||
|
||||
The convenience functions automatically detect file format:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import load_rate_limit_config, load_global_config
|
||||
|
||||
# Detects JSON by extension
|
||||
config = load_rate_limit_config("config/limits.json")
|
||||
|
||||
# Detects .env file
|
||||
config = load_rate_limit_config("config/.env")
|
||||
|
||||
# Works for global config too
|
||||
global_config = load_global_config("config/global.json")
|
||||
|
||||
Overriding Values
|
||||
-----------------
|
||||
|
||||
You can override loaded values programmatically:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = ConfigLoader()
|
||||
|
||||
# Load base config from file
|
||||
config = loader.load_rate_limit_config_from_json(
|
||||
"config/base.json",
|
||||
limit=200, # Override the limit
|
||||
key_prefix="custom", # Override the prefix
|
||||
)
|
||||
|
||||
This is useful for environment-specific overrides:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
base_config = loader.load_rate_limit_config_from_json("config/base.json")
|
||||
|
||||
# Apply environment-specific overrides
|
||||
if os.getenv("ENVIRONMENT") == "production":
|
||||
config = loader.load_rate_limit_config_from_json(
|
||||
"config/base.json",
|
||||
limit=base_config.limit * 2, # Double the limit in production
|
||||
)
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
Configuration is validated when loaded. Invalid values raise ``ConfigurationError``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from fastapi_traffic import ConfigLoader, ConfigurationError
|
||||
|
||||
loader = ConfigLoader()
|
||||
|
||||
try:
|
||||
config = loader.load_rate_limit_config_from_env()
|
||||
except ConfigurationError as e:
|
||||
print(f"Invalid configuration: {e}")
|
||||
# Handle the error appropriately
|
||||
|
||||
Common validation errors:
|
||||
|
||||
- ``limit`` must be a positive integer
|
||||
- ``window_size`` must be a positive number
|
||||
- ``algorithm`` must be one of the valid algorithm names
|
||||
- ``status_code`` must be a valid HTTP status code
|
||||
|
||||
Algorithm Names
|
||||
---------------
|
||||
|
||||
When specifying algorithms in configuration, use these names:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Config Value
|
||||
- Algorithm
|
||||
* - ``token_bucket``
|
||||
- Token Bucket
|
||||
* - ``sliding_window``
|
||||
- Sliding Window
|
||||
* - ``fixed_window``
|
||||
- Fixed Window
|
||||
* - ``leaky_bucket``
|
||||
- Leaky Bucket
|
||||
* - ``sliding_window_counter``
|
||||
- Sliding Window Counter (default)
|
||||
|
||||
Boolean Values
|
||||
--------------
|
||||
|
||||
Boolean settings accept various formats:
|
||||
|
||||
- **True:** ``true``, ``1``, ``yes``, ``on``
|
||||
- **False:** ``false``, ``0``, ``no``, ``off``
|
||||
|
||||
Case doesn't matter.
|
||||
|
||||
Complete Example
|
||||
----------------
|
||||
|
||||
Here's a full example showing configuration loading in a real app:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi_traffic import (
|
||||
ConfigLoader,
|
||||
ConfigurationError,
|
||||
RateLimiter,
|
||||
rate_limit,
|
||||
)
|
||||
from fastapi_traffic.core.limiter import set_limiter
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
loader = ConfigLoader()
|
||||
|
||||
try:
|
||||
# Try to load from environment first
|
||||
global_config = loader.load_global_config_from_env()
|
||||
except ConfigurationError:
|
||||
# Fall back to defaults
|
||||
global_config = None
|
||||
|
||||
limiter = RateLimiter(config=global_config)
|
||||
set_limiter(limiter)
|
||||
await limiter.initialize()
|
||||
|
||||
@app.get("/api/data")
|
||||
@rate_limit(100, 60)
|
||||
async def get_data(request: Request):
|
||||
return {"data": "here"}
|
||||
|
||||
# Or load endpoint-specific config
|
||||
loader = ConfigLoader()
|
||||
try:
|
||||
api_config = loader.load_rate_limit_config_from_json("config/api_limits.json")
|
||||
except (FileNotFoundError, ConfigurationError):
|
||||
api_config = None
|
||||
|
||||
if api_config:
|
||||
@app.get("/api/special")
|
||||
@rate_limit(
|
||||
api_config.limit,
|
||||
api_config.window_size,
|
||||
algorithm=api_config.algorithm,
|
||||
)
|
||||
async def special_endpoint(request: Request):
|
||||
return {"special": "data"}
|
||||
Reference in New Issue
Block a user