From bed3792b26c15ce6765c970864f522a1fa05b168 Mon Sep 17 00:00:00 2001 From: aaishmeen Date: Fri, 26 Jun 2026 14:41:45 +0530 Subject: [PATCH 1/2] feat: add FastAPI integration example --- examples/fastapi/README.md | 112 +++++++++++++++++++++++++++++ examples/fastapi/main.py | 115 ++++++++++++++++++++++++++++++ examples/fastapi/requirements.txt | 4 ++ 3 files changed, 231 insertions(+) create mode 100644 examples/fastapi/README.md create mode 100644 examples/fastapi/main.py create mode 100644 examples/fastapi/requirements.txt 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..5c43a21 --- /dev/null +++ b/examples/fastapi/main.py @@ -0,0 +1,115 @@ +from fastapi import FastAPI +import httpx + +from smooth_api import resilient_api, ResilientConfig +from smooth_api.config import BackoffConfig, CircuitBreakerConfig + +app = FastAPI( + title="SmoothAPI FastAPI Example", + description="Demonstrates retry handling, circuit breaker behavior, and fallback handling using SmoothAPI.", + version="1.0.0", +) + +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(): + 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(): + async with httpx.AsyncClient(timeout=10.0) as client: + response = await 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(): + 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(): + async with httpx.AsyncClient(timeout=10.0) as client: + response = await 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(): + 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 From fa0e7c4f4babb35c2b5f07cb73cc3f79b35517cb Mon Sep 17 00:00:00 2001 From: Aryan Sharma Date: Fri, 26 Jun 2026 18:49:11 +0530 Subject: [PATCH 2/2] refactor(fastapi-example): improve connection pooling, typing, and config Co-authored-by: aaishmeen --- examples/fastapi/main.py | 39 +++++++++++++++++---------- packages/smooth-api-py/pyproject.toml | 2 +- packages/smooth-api-ts/tsconfig.json | 1 + 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/examples/fastapi/main.py b/examples/fastapi/main.py index 5c43a21..e9b7e7d 100644 --- a/examples/fastapi/main.py +++ b/examples/fastapi/main.py @@ -1,16 +1,29 @@ +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 = "http://localhost:3001" +SANDBOX_URL = os.getenv("SANDBOX_URL", "http://localhost:3001") retry_config = ResilientConfig( backoff=BackoffConfig( @@ -43,7 +56,7 @@ summary="Project Overview", description="Returns information about the available SmoothAPI demonstration endpoints.", ) -async def root(): +async def root() -> Dict[str, Any]: return { "project": "SmoothAPI FastAPI Example", "description": "Demonstrates retries, circuit breakers, and fallback handling using SmoothAPI.", @@ -56,11 +69,10 @@ async def root(): @resilient_api(retry_config) -async def fetch_unstable_data(): - async with httpx.AsyncClient(timeout=10.0) as client: - response = await client.get(f"{SANDBOX_URL}/unstable-data") - response.raise_for_status() - return response.json() +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( @@ -68,7 +80,7 @@ async def fetch_unstable_data(): summary="Retry Handling Example", description="Demonstrates SmoothAPI automatically retrying transient failures.", ) -async def retry_demo(): +async def retry_demo() -> Dict[str, Any]: result = await fetch_unstable_data() return { @@ -81,11 +93,10 @@ async def retry_demo(): @resilient_api(circuit_config) -async def fetch_failing_data(): - async with httpx.AsyncClient(timeout=10.0) as client: - response = await client.get(f"{SANDBOX_URL}/always-fail") - response.raise_for_status() - return response +async def fetch_failing_data() -> Any: + response = await http_client.get(f"{SANDBOX_URL}/always-fail") + response.raise_for_status() + return response @app.get( @@ -93,7 +104,7 @@ async def fetch_failing_data(): summary="Circuit Breaker Example", description="Demonstrates circuit breaker behavior and fallback responses.", ) -async def circuit_demo(): +async def circuit_demo() -> Dict[str, Any]: result = await fetch_failing_data() if isinstance(result, httpx.Response): 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,