diff --git a/examples/fastapi/README.md b/examples/fastapi/README.md new file mode 100644 index 0000000..bdf87df --- /dev/null +++ b/examples/fastapi/README.md @@ -0,0 +1,112 @@ +# SmoothAPI FastAPI Example + +This example demonstrates how to integrate **SmoothAPI** into a FastAPI application to make outbound API requests more resilient using retries, circuit breakers, and fallback responses. + +## Features + +- Automatic retries for transient failures +- Circuit breaker protection +- Graceful fallback responses +- Async FastAPI integration using `httpx` + +--- + +## Prerequisites + +- Python 3.10+ +- Node.js (for the sandbox server) + +--- + +## Install Dependencies + +```bash +pip install -r requirements.txt +``` + +--- + +## Start the Sandbox Server + +From the repository root: + +```bash +cd sandbox +npm install +node server.js +``` + +The sandbox will start on: + +``` +http://localhost:3001 +``` + +--- + +## Run the FastAPI Example + +From the `examples/fastapi` directory: + +```bash +uvicorn main:app --reload +``` + +The API will be available at: + +``` +http://127.0.0.1:8000 +``` + +Interactive API documentation: + +``` +http://127.0.0.1:8000/docs +``` + +--- + +## Available Endpoints + +### `GET /` + +Returns basic information about the example application. + +--- + +### `GET /retry-demo` + +Demonstrates SmoothAPI's retry mechanism by calling the sandbox's unstable endpoint. + +Target endpoint: + +``` +GET http://localhost:3001/unstable-data +``` + +SmoothAPI automatically retries transient failures before returning a successful response. + +--- + +### `GET /circuit-demo` + +Demonstrates SmoothAPI's circuit breaker and fallback behavior. + +Target endpoint: + +``` +GET http://localhost:3001/always-fail +``` + +After repeated failures, the circuit breaker opens and returns the configured fallback response immediately without making another network request. + +--- + +## Project Structure + +``` +fastapi/ +├── main.py +├── requirements.txt +└── README.md +``` \ No newline at end of file diff --git a/examples/fastapi/main.py b/examples/fastapi/main.py new file mode 100644 index 0000000..e9b7e7d --- /dev/null +++ b/examples/fastapi/main.py @@ -0,0 +1,126 @@ +import os +from contextlib import asynccontextmanager +from typing import Any, Dict + +from fastapi import FastAPI +import httpx + +from smooth_api import resilient_api, ResilientConfig +from smooth_api.config import BackoffConfig, CircuitBreakerConfig + +# Shared HTTP client for connection pooling +http_client = httpx.AsyncClient(timeout=10.0) + +@asynccontextmanager +async def lifespan(app: FastAPI): + yield + await http_client.aclose() + +app = FastAPI( + title="SmoothAPI FastAPI Example", + description="Demonstrates retry handling, circuit breaker behavior, and fallback handling using SmoothAPI.", + version="1.0.0", + lifespan=lifespan, +) + +SANDBOX_URL = os.getenv("SANDBOX_URL", "http://localhost:3001") + +retry_config = ResilientConfig( + backoff=BackoffConfig( + base_delay=0.1, + max_delay=5.0, + max_retries=3, + ), + retry_on=[429, 500, 502, 503, 504], +) + +circuit_config = ResilientConfig( + backoff=BackoffConfig( + base_delay=0.1, + max_delay=5.0, + max_retries=3, + ), + circuit_breaker=CircuitBreakerConfig( + failure_threshold=3, + cooldown_ms=5000, + ), + retry_on=[429, 500, 502, 503, 504], + fallback={ + "message": "Circuit breaker is open. Returning fallback response." + }, +) + + +@app.get( + "/", + summary="Project Overview", + description="Returns information about the available SmoothAPI demonstration endpoints.", +) +async def root() -> Dict[str, Any]: + return { + "project": "SmoothAPI FastAPI Example", + "description": "Demonstrates retries, circuit breakers, and fallback handling using SmoothAPI.", + "documentation": "/docs", + "endpoints": { + "retry_demo": "/retry-demo", + "circuit_demo": "/circuit-demo", + }, + } + + +@resilient_api(retry_config) +async def fetch_unstable_data() -> Any: + response = await http_client.get(f"{SANDBOX_URL}/unstable-data") + response.raise_for_status() + return response.json() + + +@app.get( + "/retry-demo", + summary="Retry Handling Example", + description="Demonstrates SmoothAPI automatically retrying transient failures.", +) +async def retry_demo() -> Dict[str, Any]: + result = await fetch_unstable_data() + + return { + "feature": "Retry Handling", + "source": "upstream-response", + "endpoint": "/unstable-data", + "description": "Demonstrates automatic retries for transient failures.", + "result": result, + } + + +@resilient_api(circuit_config) +async def fetch_failing_data() -> Any: + response = await http_client.get(f"{SANDBOX_URL}/always-fail") + response.raise_for_status() + return response + + +@app.get( + "/circuit-demo", + summary="Circuit Breaker Example", + description="Demonstrates circuit breaker behavior and fallback responses.", +) +async def circuit_demo() -> Dict[str, Any]: + result = await fetch_failing_data() + + if isinstance(result, httpx.Response): + return { + "feature": "Circuit Breaker", + "source": "upstream-response", + "endpoint": "/always-fail", + "status_code": result.status_code, + "description": "Retries were exhausted before the circuit opened.", + "result": result.json(), + } + + return { + "feature": "Circuit Breaker", + "source": "fallback", + "endpoint": "/always-fail", + "description": "The circuit breaker is open, so the configured fallback response was returned.", + "result": result, + } \ No newline at end of file diff --git a/examples/fastapi/requirements.txt b/examples/fastapi/requirements.txt new file mode 100644 index 0000000..0201ce3 --- /dev/null +++ b/examples/fastapi/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +httpx +smoothapi-py \ No newline at end of file diff --git a/packages/smooth-api-py/pyproject.toml b/packages/smooth-api-py/pyproject.toml index ac02f59..5e302d5 100644 --- a/packages/smooth-api-py/pyproject.toml +++ b/packages/smooth-api-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "smoothapi-py" -version = "1.0.0" +version = "1.1.0" description = "API resilience library — exponential backoff and circuit breaker" readme = "README.md" requires-python = ">=3.10" diff --git a/packages/smooth-api-ts/tsconfig.json b/packages/smooth-api-ts/tsconfig.json index 187a585..95799c6 100644 --- a/packages/smooth-api-ts/tsconfig.json +++ b/packages/smooth-api-ts/tsconfig.json @@ -7,6 +7,7 @@ "outDir": "dist", "declaration": true, "declarationMap": true, + "resolveJsonModule": true, "sourceMap": true, "strict": true, "esModuleInterop": true,