Python FastAPI Development
Expert patterns for building Python APIs with FastAPI, uv package manager, modular architecture, and SQLAlchemy database integration.
Technology Stack
- Runtime: Python 3.12+
- Package Manager: uv (fast, Rust-based)
- Framework: FastAPI
- ORM: SQLAlchemy 2.0 (async)
- Validation: Pydantic v2
- Database: PostgreSQL (or SQLite for dev)
- Migrations: Alembic
- Testing: pytest, pytest-asyncio
- Linting: ruff
Project Structure
Feature-based modular architecture - code organized by domain, not by layer:
my-project/
├── pyproject.toml # Project config with uv
├── uv.lock # Lock file
├── .python-version # Python version
├── .env # Environment variables
├── .env.example
├── alembic.ini # Alembic config
├── alembic/ # Migrations
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
├── src/
│ └── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app entry
│ ├── config.py # Settings
│ ├── database.py # DB session
│ ├── core/
│ │ ├── __init__.py
│ │ ├── dependencies.py # Shared dependencies
│ │ ├── exceptions.py # Custom exceptions
│ │ ├── middleware.py # Middleware
│ │ └── security.py # Auth utilities
│ ├── models/
│ │ ├── __init__.py
│ │ └── base.py # SQLAlchemy base & mixins
│ ├── features/
│ │ ├── __init__.py
│ │ ├── auth/
│ │ │ ├── __init__.py
│ │ │ ├── api.py # Auth endpoints
│ │ │ ├── schemas.py # Auth Pydantic schemas
│ │ │ ├── services.py # Auth business logic
│ │ │ ├── models.py # Auth SQLAlchemy models
│ │ │ └── utils.py # Auth helpers (JWT, etc.)
│ │ ├── users/
│ │ │ ├── __init__.py
│ │ │ ├── api.py # User endpoints
│ │ │ ├── schemas.py # User Pydantic schemas
│ │ │ ├── services.py # User business logic
│ │ │ ├── models.py # User SQLAlchemy models
│ │ │ └── repository.py # User data access
│ │ └── items/
│ │ ├── __init__.py
│ │ ├── api.py
│ │ ├── schemas.py
│ │ ├── services.py
│ │ └── models.py
│ └── api/
│ ├── __init__.py
│ └── router.py # Aggregates all feature routers
└── tests/
├── __init__.py
├── conftest.py
├── features/
│ ├── auth/
│ │ └── test_auth.py
│ └── users/
│ └── test_users.py
└── integration/
Quick Setup with uv
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create new project
uv init my-project
cd my-project
# Set Python version
uv python pin 3.12
# Add dependencies
uv add fastapi uvicorn[standard] sqlalchemy[asyncio] asyncpg
uv add pydantic pydantic-settings python-dotenv
uv add alembic
# Add dev dependencies
uv add --dev pytest pytest-asyncio pytest-cov httpx ruff mypy
# Create source structure
mkdir -p src/app/{api/v1/endpoints,core,models,schemas,services,repositories}
touch src/app/__init__.py
Core Patterns
pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
description = "FastAPI application"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.32.0",
"sqlalchemy[asyncio]>=2.0.0",
"asyncpg>=0.30.0",
"pydantic>=2.10.0",
"pydantic-settings>=2.6.0",
"python-dotenv>=1.0.0",
"alembic>=1.14.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-cov>=6.0.0",
"httpx>=0.28.0",
"ruff>=0.8.0",
"mypy>=1.13.0",
]
[tool.ruff]
target-version = "py312"
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
[tool.mypy]
python_version = "3.12"
strict = true
Configuration (src/app/config.py)
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
# App
app_name: str = "My API"
debug: bool = False
api_v1_prefix: str = "/api/v1"
# Database
database_url: str = "postgresql+asyncpg://user:pass@localhost:5432/db"
# Security
secret_key: str = "change-me-in-production"
access_token_expire_minutes: int = 30
@lru_cache
def get_settings() -> Settings:
return Settings()
settings = get_settings()
Database Setup (src/app/database.py)
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import (
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from app.config import settings
engine = create_async_engine(
settings.database_url,
echo=settings.debug,
pool_pre_ping=True,
)
async_session_maker = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False,
)
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
SQLAlchemy Base Model (src/app/models/base.py)
from datetime import datetime
from sqlalchemy import DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
)
Feature Module Pattern
Each feature is self-contained with its own api, schemas, services, models, and utils.
Feature: users/schemas.py
from pydantic import BaseModel, EmailStr, ConfigDict
class UserBase(BaseModel):
email: EmailStr
full_name: str | None = None
class UserCreate(UserBase):
password: str
class UserUpdate(BaseModel):
email: EmailStr | None = None
full_name: str | None = None
password: str | None = None
class UserResponse(UserBase):
model_config = ConfigDict(from_attributes=True)
id: int
is_active: bool
Feature: users/models.py
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class User(Base, TimestampMixin):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String(255))
full_name: Mapped[str | None] = mapped_column(String(255))
is_active: Mapped[bool] = mapped_column(default=True)
Feature: users/repository.py
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.features.users.models import User
class UserRepository:
def __init__(self, db: AsyncSession):
self.db = db
async def get(self, id: int) -> User | None:
result = await self.db.execute(select(User).where(User.id == id))
return result.scalar_one_or_none()
async def get_by_email(self, email: str) -> User | None:
result = await self.db.execute(select(User).where(User.email == email))
return result.scalar_one_or_none()
async def get_all(self, skip: int = 0, limit: int = 100) -> list[User]:
result = await self.db.execute(select(User).offset(skip).limit(limit))
return list(result.scalars().all())
asy