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:
2026-03-17 20:55:38 +00:00
parent 492410614f
commit f3453cb0fc
51 changed files with 6507 additions and 166 deletions

View File

@@ -7,6 +7,7 @@ making it easy to manage settings across different environments (dev, staging, p
from __future__ import annotations
import json
import logging
import os
import tempfile
from contextlib import asynccontextmanager
@@ -44,7 +45,6 @@ def example_env_variables() -> RateLimitConfig:
export FASTAPI_TRAFFIC_RATE_LIMIT_ALGORITHM=sliding_window_counter
export FASTAPI_TRAFFIC_RATE_LIMIT_KEY_PREFIX=myapi
"""
# Using the convenience function
config = load_rate_limit_config_from_env(
# You can provide overrides for values not in env vars
limit=50, # Default if FASTAPI_TRAFFIC_RATE_LIMIT_LIMIT not set
@@ -93,7 +93,7 @@ def example_dotenv_file() -> RateLimitConfig:
FASTAPI_TRAFFIC_RATE_LIMIT_INCLUDE_HEADERS=true
FASTAPI_TRAFFIC_RATE_LIMIT_ERROR_MESSAGE="Too many requests, please slow down"
"""
# Create a sample .env file for demonstration
with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as f:
f.write("# Rate limit configuration\n")
f.write("FASTAPI_TRAFFIC_RATE_LIMIT_LIMIT=100\n")
@@ -104,7 +104,7 @@ def example_dotenv_file() -> RateLimitConfig:
env_path = f.name
try:
# Load using auto-detection (detects .env suffix)
config = load_rate_limit_config(env_path)
print(f"From .env: limit={config.limit}, algorithm={config.algorithm}")
print(f"Burst size: {config.burst_size}")
@@ -132,7 +132,6 @@ def example_json_file() -> RateLimitConfig:
"cost": 1
}
"""
# Create a sample JSON file for demonstration
config_data = {
"limit": 500,
"window_size": 300.0,
@@ -148,7 +147,7 @@ def example_json_file() -> RateLimitConfig:
json_path = f.name
try:
# Load using auto-detection (detects .json suffix)
config = load_rate_limit_config(json_path)
print(f"From JSON: limit={config.limit}, window={config.window_size}s")
print(f"Algorithm: {config.algorithm.value}")
@@ -346,7 +345,9 @@ def create_app_with_config() -> FastAPI:
)
@app.exception_handler(RateLimitExceeded)
async def _rate_limit_handler(_: Request, exc: RateLimitExceeded) -> JSONResponse:
async def _rate_limit_handler( # pyright: ignore[reportUnusedFunction]
_: Request, exc: RateLimitExceeded
) -> JSONResponse:
return JSONResponse(
status_code=429,
content={
@@ -358,23 +359,26 @@ def create_app_with_config() -> FastAPI:
@app.get("/")
@rate_limit(limit=10, window_size=60)
async def _root(_: Request) -> dict[str, str]:
async def _root( # pyright: ignore[reportUnusedFunction]
_: Request,
) -> dict[str, str]:
return {"message": "Hello from config-loaded app!"}
@app.get("/health")
async def _health() -> dict[str, str]:
async def _health() -> dict[str, str]: # pyright: ignore[reportUnusedFunction]
"""Health check - exempt from rate limiting."""
return {"status": "healthy"}
@app.get("/api/data")
@rate_limit(limit=50, window_size=60)
async def _get_data(_: Request) -> dict[str, str]:
async def _get_data( # pyright: ignore[reportUnusedFunction]
_: Request,
) -> dict[str, str]:
return {"data": "Some API data"}
return app
# Create the app instance
app = create_app_with_config()
@@ -383,59 +387,77 @@ app = create_app_with_config()
# =============================================================================
logger = logging.getLogger(__name__)
def run_examples() -> None:
"""Run all configuration loading examples."""
print("=" * 60)
print("FastAPI Traffic - Configuration Loader Examples")
print("=" * 60)
logging.basicConfig(level=logging.INFO, format="%(message)s")
print("\n1. Loading from environment variables:")
print("-" * 40)
logger.info("=" * 60)
logger.info("FastAPI Traffic - Configuration Loader Examples")
logger.info("=" * 60)
logger.info("\n1. Loading from environment variables:")
logger.info("-" * 40)
example_env_variables()
print("\n2. Loading GlobalConfig from environment:")
print("-" * 40)
logger.info("\n2. Loading GlobalConfig from environment:")
logger.info("-" * 40)
example_global_config_env()
print("\n3. Loading from .env file:")
print("-" * 40)
logger.info("\n3. Loading from .env file:")
logger.info("-" * 40)
example_dotenv_file()
print("\n4. Loading from JSON file:")
print("-" * 40)
logger.info("\n4. Loading from JSON file:")
logger.info("-" * 40)
example_json_file()
print("\n5. Loading GlobalConfig from JSON:")
print("-" * 40)
logger.info("\n5. Loading GlobalConfig from JSON:")
logger.info("-" * 40)
example_global_config_json()
print("\n6. Using custom environment prefix:")
print("-" * 40)
logger.info("\n6. Using custom environment prefix:")
logger.info("-" * 40)
example_custom_prefix()
print("\n7. Validation and error handling:")
print("-" * 40)
logger.info("\n7. Validation and error handling:")
logger.info("-" * 40)
example_validation()
print("\n8. Environment-based configuration:")
print("-" * 40)
logger.info("\n8. Environment-based configuration:")
logger.info("-" * 40)
example_environment_based_config()
print("\n" + "=" * 60)
print("All examples completed!")
print("=" * 60)
logger.info("\n" + "=" * 60)
logger.info("All examples completed!")
logger.info("=" * 60)
if __name__ == "__main__":
import sys
import argparse
if len(sys.argv) > 1 and sys.argv[1] == "--demo":
# Run the demo examples
import uvicorn
parser = argparse.ArgumentParser(description="Config loader example")
parser.add_argument(
"--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)"
)
parser.add_argument(
"--port", type=int, default=8011, help="Port to bind to (default: 8011)"
)
parser.add_argument(
"--demo",
action="store_true",
help="Run configuration examples instead of server",
)
args = parser.parse_args()
if args.demo:
run_examples()
else:
# Run the FastAPI app
import uvicorn
print("Starting FastAPI app with config loader...")
print("Run with --demo flag to see configuration examples")
uvicorn.run(app, host="127.0.0.1", port=8011)
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger.info("Starting FastAPI app with config loader...")
logger.info("Run with --demo flag to see configuration examples")
uvicorn.run(app, host=args.host, port=args.port)