Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions examples/fastapi/README.md
Original file line number Diff line number Diff line change
@@ -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
```
126 changes: 126 additions & 0 deletions examples/fastapi/main.py
Original file line number Diff line number Diff line change
@@ -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,
}
4 changes: 4 additions & 0 deletions examples/fastapi/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi
uvicorn
httpx
smoothapi-py
2 changes: 1 addition & 1 deletion packages/smooth-api-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions packages/smooth-api-ts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
Expand Down
Loading