Comprehensive learning path for building production-ready FastAPI services integrated with AI APIs.
- FastAPI Fundamentals
- Async Python Programming
- Pydantic & Data Validation
- Gemini AI API Integration
- Testing Async Applications
- Deployment Strategies
- Future Enhancements
Why FastAPI?
- Automatic API documentation (Swagger UI, ReDoc)
- Built-in data validation via Pydantic
- Async/await support for high concurrency
- Type hints for better IDE support
- High performance (comparable to Node.js, Go)
Basic Application Structure:
from fastapi import FastAPI
app = FastAPI(
title="My API",
description="API description",
version="1.0.0"
)
@app.get("/")
async def root():
return {"message": "Hello World"}
# Run with: uvicorn main:app --reloadGET Request:
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}POST Request with Request Body:
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
description: Optional[str] = None
@app.post("/items/")
async def create_item(item: Item):
return itemasync def get_db():
db = Database()
try:
yield db
finally:
db.close()
@app.get("/users/")
async def read_users(db: Database = Depends(get_db)):
return db.get_all_users()from fastapi import HTTPException
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found"
)
return items[item_id]Learning Exercise:
- Create a simple CRUD API with in-memory storage
- Add query parameters for filtering
- Implement proper error responses
- Test with Swagger UI at
/docs
Synchronous vs Asynchronous:
# Synchronous (blocks execution)
def fetch_data():
response = requests.get("https://api.example.com/data")
return response.json()
# Asynchronous (non-blocking)
async def fetch_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()import asyncio
async def task1():
await asyncio.sleep(1)
return "Task 1 done"
async def task2():
await asyncio.sleep(2)
return "Task 2 done"
# Run tasks concurrently
async def main():
results = await asyncio.gather(task1(), task2())
print(results)
# Output: ['Task 1 done', 'Task 2 done'] after 2 seconds (not 3)Use Async For:
- I/O-bound operations (API calls, database queries, file I/O)
- High concurrency requirements
- Real-time applications
Don't Use Async For:
- CPU-bound operations (heavy computations)
- Simple scripts with sequential logic
- When synchronous code is clearer
Learning Exercise:
- Convert synchronous API calls to async
- Measure performance difference with concurrent requests
- Practice error handling in async functions
from pydantic import BaseModel, Field, validator
from typing import Optional, List
class User(BaseModel):
id: int
username: str = Field(..., min_length=3, max_length=50)
email: str
age: Optional[int] = Field(None, ge=0, le=150)
tags: List[str] = []
@validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Invalid email format')
return v
model_config = {
"json_schema_extra": {
"examples": [{
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"age": 30,
"tags": ["developer", "python"]
}]
}
}class Address(BaseModel):
street: str
city: str
country: str
class UserWithAddress(BaseModel):
name: str
address: Address
# Usage
user = UserWithAddress(
name="John",
address={"street": "123 Main St", "city": "NYC", "country": "USA"}
)class UserCreate(BaseModel):
username: str
password: str
email: str
class UserResponse(BaseModel):
id: int
username: str
email: str
# password excluded from response
@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):
# password hashing logic here
return UserResponse(id=1, username=user.username, email=user.email)Learning Exercise:
- Create complex nested models
- Add custom validators
- Use Field() for constraints
- Test validation errors in Swagger UI
Installation:
pip install google-generativeaiBasic Configuration:
import google.generativeai as genai
import os
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel('gemini-1.5-pro')Simple Generation:
response = model.generate_content("Explain quantum computing")
print(response.text)Streaming Response:
response = model.generate_content("Write a story", stream=True)
for chunk in response:
print(chunk.text, end='')System Instructions:
model = genai.GenerativeModel(
'gemini-1.5-pro',
system_instruction="You are a Python expert. Answer concisely."
)Structured Prompts:
prompt = """Analyze this code:
```python
{code}Provide:
- Functionality explanation
- Time complexity
- Improvement suggestions """ response = model.generate_content(prompt.format(code=user_code))
### Error Handling & Retry Logic
```python
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def call_gemini_with_retry(prompt: str):
try:
response = await model.generate_content_async(prompt)
return response.text
except Exception as e:
logger.error(f"Gemini API error: {e}")
raise
Learning Exercise:
- Get Gemini API key from Google AI Studio
- Experiment with different prompts
- Implement streaming responses
- Add rate limiting and retry logic
- Compare response quality with different models
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_endpoint():
response = client.get("/health")
assert response.status_code == 200import pytest
from httpx import AsyncClient
from app.main import app
@pytest.mark.asyncio
async def test_async_endpoint():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.post("/api/v1/analyze", json={"code": "test"})
assert response.status_code == 200from unittest.mock import patch, AsyncMock
@pytest.mark.asyncio
async def test_gemini_service():
with patch('app.services.gemini_service.genai') as mock_genai:
mock_response = AsyncMock()
mock_response.text = "Mocked analysis"
mock_genai.GenerativeModel.return_value.generate_content_async.return_value = mock_response
result = await gemini_service.analyze_code("test code", "python")
assert "analysis" in result# Run with coverage
pytest --cov=app --cov-report=html
# View report
open htmlcov/index.htmlLearning Exercise:
- Write tests for all endpoints
- Mock Gemini API responses
- Achieve 80%+ code coverage
- Test error scenarios
Multi-stage Build:
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app/ ./app/
ENV PATH=/root/.local/bin:$PATH
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]from pydantic_settings import BaseSettings
class Settings(BaseSettings):
gemini_api_key: str
api_host: str = "0.0.0.0"
api_port: int = 8000
log_level: str = "INFO"
class Config:
env_file = ".env"
settings = Settings()1. AWS Lambda + API Gateway:
- Use Mangum for ASGI to Lambda adapter
- Configure API Gateway for HTTP endpoints
- Set environment variables in Lambda console
2. Google Cloud Run:
- Fully managed container platform
- Auto-scaling based on traffic
- Pay per request pricing
3. Railway / Render:
- Simple git push deployment
- Automatic HTTPS
- Environment variable management
Learning Exercise:
- Deploy to Railway or Render
- Set up environment variables
- Configure custom domain
- Monitor logs and metrics
from fastapi.responses import StreamingResponse
@app.post("/analyze/stream")
async def analyze_stream(request: CodeAnalysisRequest):
async def generate():
response = model.generate_content(prompt, stream=True)
for chunk in response:
yield f"data: {chunk.text}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")from functools import lru_cache
import hashlib
@lru_cache(maxsize=100)
def get_cached_analysis(code_hash: str):
# Redis or in-memory cache
passfrom slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.post("/analyze")
@limiter.limit("10/minute")
async def analyze_code(request: Request, data: CodeAnalysisRequest):
pass- Store analysis history
- User authentication
- Usage analytics
- Real-time code analysis
- Collaborative features
- Fallback to OpenAI if Gemini fails
- Model comparison
- Cost optimization
Official Documentation:
Books & Tutorials:
- "Python Concurrency with asyncio" by Matthew Fowler
- FastAPI course on Test-Driven.io
- Real Python FastAPI tutorials
Tools:
- Postman for API testing
- httpie for CLI testing
- Swagger Editor for API design
- Build basic FastAPI application
- Add Pydantic models with validation
- Implement async database queries
- Integrate Gemini AI API
- Write unit and integration tests
- Set up CI/CD pipeline
- Deploy to cloud platform
- Add caching layer
- Implement rate limiting
- Monitor production metrics