Claude Code Python CLAUDE.md AGENTS.md pytest uv ruff FastAPI

Claude Code for Python Projects: CLAUDE.md Templates and AGENTS.md Patterns (2026)

The Prompt Shelf ·

Python has a configuration problem that Claude Code exposes immediately. The language ships with one obvious way to do almost nothing: package managers (pip, poetry, uv, conda), formatters (black, ruff, autopep8), type checkers (mypy, pyright, pytype), test runners (pytest, unittest, nose), and virtual environment strategies all compete. When Claude Code opens your project without guidance, it guesses. Sometimes it guesses right. Often it installs a dependency to the system Python, runs python when your project requires python3.12, or formats with black when your team uses ruff.

CLAUDE.md and AGENTS.md fix this. A well-written CLAUDE.md locks Claude Code into your specific toolchain in the first session and every session after. This guide gives you production-ready templates for the most common Python project types, plus the reasoning behind each decision so you can adapt them.

Why Python Specifically Needs CLAUDE.md

Most compiled languages have one obvious path. Python does not. A Claude Code session on a TypeScript project can reasonably assume npm install and tsc. A Python project might use uv sync, poetry install, pip install -e ., or conda env create. Without CLAUDE.md, Claude Code either asks every time (slow) or assumes (wrong).

The virtual environment problem is particularly sharp. Claude Code needs to know not just that a venv exists, but where it is, how to activate it conceptually (for generated commands), and which Python binary to use. A missing venv instruction leads to the classic ModuleNotFoundError after Claude Code runs code with the wrong interpreter.

Type checking is another pain point. mypy and pyright have different configuration files, different strictness levels, and different behaviors on the same codebase. Telling Claude Code which one you use — and at what strictness — prevents it from generating code that satisfies one checker and fails the other.

The Minimal CLAUDE.md for Python

Start here if you want something you can drop into any Python project today:

# CLAUDE.md

## Python Environment

- Python version: 3.12
- Package manager: uv
- Virtual environment: `.venv/` (project root)

## Commands

```bash
# Setup
uv sync                    # Install dependencies from uv.lock
uv sync --extra dev        # Include dev dependencies

# Running
uv run python src/main.py  # Always use uv run, never python directly

# Testing
uv run pytest              # Run all tests
uv run pytest -x           # Stop on first failure
uv run pytest --cov=src    # With coverage

# Linting / Formatting
uv run ruff check .        # Lint
uv run ruff format .       # Format
uv run mypy src/           # Type check

# Build
uv build                   # Build distribution

Code Style

  • Formatter: ruff (not black, not autopep8)
  • Linter: ruff
  • Type checker: mypy
  • Line length: 88
  • Always add type annotations to function signatures
  • Use from __future__ import annotations for forward references

Project Structure

src/
  mypackage/
    __init__.py
    core.py
tests/
  test_core.py
pyproject.toml
uv.lock
CLAUDE.md

Rules

  • Never install packages with pip install. Use uv add <package> instead.
  • Never modify uv.lock directly.
  • Run uv run ruff format . before committing changes.
  • Run uv run mypy src/ before marking any task complete.
  • Tests live in tests/, not alongside source files.

This is functional but generic. The sections below add depth for specific project types.

## Full CLAUDE.md Template: Web API (FastAPI)

FastAPI projects have specific patterns Claude Code should know: async functions everywhere, Pydantic models for validation, dependency injection via `Depends`, and the separation between app factory and router modules.

```markdown
# CLAUDE.md

## Project: FastAPI REST API

## Environment

- Python: 3.12
- Package manager: uv
- Virtual environment: `.venv/`
- Framework: FastAPI 0.115+
- ASGI server: uvicorn (dev) / gunicorn + uvicorn workers (prod)

## Commands

```bash
# Setup
uv sync --extra dev

# Development server (hot reload)
uv run uvicorn app.main:app --reload --port 8000

# Testing
uv run pytest                            # All tests
uv run pytest tests/unit/               # Unit tests only
uv run pytest tests/integration/        # Integration tests only
uv run pytest -k "test_users"           # Filter by name
uv run pytest --cov=app --cov-report=html  # Coverage report

# Linting
uv run ruff check app/ tests/
uv run ruff format app/ tests/
uv run mypy app/

# Database migrations (Alembic)
uv run alembic upgrade head             # Apply pending migrations
uv run alembic revision --autogenerate -m "description"  # New migration
uv run alembic downgrade -1             # Roll back one migration

# Load local env
cp .env.example .env && uv run uvicorn app.main:app --reload

Project Structure

app/
  __init__.py
  main.py           # FastAPI app factory, lifespan, middleware
  config.py         # Settings via pydantic-settings
  dependencies.py   # Shared FastAPI dependencies
  routers/
    __init__.py
    users.py
    items.py
  models/
    __init__.py
    user.py         # SQLAlchemy models
  schemas/
    __init__.py
    user.py         # Pydantic request/response schemas
  services/
    __init__.py
    user_service.py # Business logic, no HTTP concerns
  repositories/
    __init__.py
    user_repo.py    # Database access
tests/
  conftest.py       # pytest fixtures (async client, test DB)
  unit/
    test_user_service.py
  integration/
    test_users_api.py
alembic/
migrations/
pyproject.toml
.env.example

Architecture Rules

  • Routers handle HTTP only: parse request, call service, return response.
  • Services handle business logic: no SQLAlchemy, no FastAPI imports.
  • Repositories handle database: return domain objects, not ORM rows.
  • Use pydantic-settings for config. Never os.environ.get() in app code.
  • All route functions must be async def.
  • Use Annotated for dependency injection: user: Annotated[User, Depends(get_current_user)]

Testing Patterns

# Always use async test client
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app

@pytest.fixture
async def client():
    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test"
    ) as ac:
        yield ac
  • Tests must not hit the real database. Use conftest.py to override get_db.
  • Integration tests use a separate test database (SQLite in-memory or Postgres with pytest-postgresql).
  • Mock external services with unittest.mock.AsyncMock.

Type Annotations

  • All function signatures require type annotations.
  • Use pydantic.BaseModel for all API schemas.
  • SQLAlchemy models use Mapped[T] syntax (SQLAlchemy 2.0 style).
  • Return types on route functions should match the response_model.

Do Not

  • Do not use app.include_router() in routers (only in main.py).
  • Do not put business logic in route functions.
  • Do not commit .env files.
  • Do not use synchronous database calls in async endpoints.

## Full CLAUDE.md Template: Django

Django projects have more opinions baked in, but Claude Code still needs guidance on your specific configuration — particularly around database migrations, the admin, and whether you're using DRF or something else.

```markdown
# CLAUDE.md

## Project: Django Web Application

## Environment

- Python: 3.12
- Package manager: uv
- Framework: Django 5.1
- API: Django REST Framework 3.15
- Database: PostgreSQL (local: SQLite for testing)

## Commands

```bash
# Setup
uv sync --extra dev
uv run python manage.py migrate
uv run python manage.py createsuperuser

# Development
uv run python manage.py runserver

# Testing
uv run pytest                          # All tests (via pytest-django)
uv run pytest apps/users/              # Single app
uv run pytest --reuse-db               # Reuse test database (faster)
uv run pytest --create-db              # Force rebuild test database

# Database
uv run python manage.py makemigrations # Generate migrations
uv run python manage.py migrate        # Apply migrations
uv run python manage.py showmigrations # Check migration state

# Static files
uv run python manage.py collectstatic --noinput

# Quality
uv run ruff check .
uv run ruff format .
uv run mypy .

Project Structure

config/
  settings/
    base.py
    local.py
    production.py
    test.py
  urls.py
  wsgi.py
  asgi.py
apps/
  users/
    migrations/
    admin.py
    apps.py
    managers.py
    models.py
    serializers.py
    views.py
    urls.py
    tests/
      test_models.py
      test_views.py
  core/
    models.py        # Abstract base models (TimeStampedModel, etc.)
templates/
static/
manage.py
pyproject.toml

Django Conventions

  • Use DJANGO_SETTINGS_MODULE=config.settings.local for local dev.
  • Apps live in apps/, never at the project root.
  • All models inherit from TimeStampedModel (provides created_at, updated_at).
  • Use custom User model from project start. Never use django.contrib.auth.User directly.
  • URL namespaces required: app_name = "users" in every urls.py.

Testing Patterns

  • Use pytest-django. Configure in pyproject.toml:
    [tool.pytest.ini_options]
    DJANGO_SETTINGS_MODULE = "config.settings.test"
  • Use baker (model_bakery) for fixture creation, not hand-built instances.
  • Use @pytest.mark.django_db for any test that touches the database.
  • API tests use DRF’s APIClient, not Django’s TestClient.

Migration Rules

  • Never squash migrations without team agreement.
  • All migrations must be reversible (implement backwards or verify auto-reversal).
  • Run uv run python manage.py migrate --check in CI.

## AGENTS.md for Python Projects

AGENTS.md serves a different purpose than CLAUDE.md: where CLAUDE.md configures Claude Code specifically, AGENTS.md communicates with any AI agent tool. If your team uses multiple tools (Claude Code, Cursor, GitHub Copilot, OpenAI Codex), AGENTS.md is the shared contract.

Here is a production-ready AGENTS.md for a Python project:

```markdown
# AGENTS.md

## Project Overview

Python 3.12 REST API built with FastAPI. Uses uv for dependency management,
pytest for testing, ruff for formatting/linting, mypy for type checking.

## Quick Start

```bash
uv sync --extra dev
uv run uvicorn app.main:app --reload

Repository Layout

app/          Source code (FastAPI application)
tests/        Test suite (mirrors app/ structure)
alembic/      Database migrations
docs/         API documentation
scripts/      Utility scripts (not part of the app)

Important: All imports use the app package namespace. Do not use relative imports between top-level modules.

Required Commands Before Finishing Any Task

Run these in order. Fix any errors before marking work complete:

uv run ruff check app/ tests/   # Must pass with 0 errors
uv run ruff format app/ tests/  # Apply formatting
uv run mypy app/                # Must pass with 0 errors
uv run pytest                   # All tests must pass

Python Conventions

Type Annotations

Every function must have type annotations:

# Correct
def get_user(user_id: int, db: Session) -> User | None:
    ...

# Wrong — missing annotations
def get_user(user_id, db):
    ...

Use X | None (union syntax) over Optional[X]. Python 3.10+ syntax.

Error Handling

  • Use custom exception classes defined in app/exceptions.py.
  • FastAPI endpoints catch exceptions via exception handlers, not try/except in routes.
  • Never silently swallow exceptions.

Async

  • All route functions are async def.
  • Database operations use async SQLAlchemy (AsyncSession).
  • Use asyncio.gather() for concurrent independent operations.
  • Do not mix sync and async database calls.

Dependencies

  • Add new dependencies: uv add <package>
  • Add dev-only dependencies: uv add --dev <package>
  • Never edit pyproject.toml dependencies manually.
  • Never run pip install.

Testing Rules

  • New code requires tests. No exceptions.
  • Test files mirror source: app/services/user_service.pytests/unit/test_user_service.py
  • Minimum coverage: 80% on new code (--cov-fail-under=80).
  • Do not mock the database in integration tests — use the test database fixture.

What to Avoid

  • Do not use print() for debugging. Use logging.getLogger(__name__).
  • Do not hardcode configuration values. Use app/config.py (pydantic-settings).
  • Do not commit secrets. Check .gitignore before adding new file types.
  • Do not bypass mypy with # type: ignore without a comment explaining why.

## Python-Specific .claudeignore Patterns

Claude Code respects `.claudeignore` the same way git respects `.gitignore`. For Python projects, there are files you definitely do not want Claude Code reading or modifying:

```gitignore
# .claudeignore

# Python cache — never needs to be read
__pycache__/
*.pyc
*.pyo
*.pyd
.Python

# Virtual environments
.venv/
venv/
env/
ENV/
.env.bak

# Build artifacts
dist/
build/
*.egg-info/
.eggs/

# Testing artifacts
.pytest_cache/
htmlcov/
.coverage
coverage.xml
*.cover

# Type checking cache
.mypy_cache/
.dmypy.json
dmypy.json
.pyright/

# Jupyter
.ipynb_checkpoints/

# IDE
.idea/
.vscode/settings.json

# Local secrets (never read these)
.env
.env.local
.env.*.local
secrets.yml

# Large data files
data/raw/
data/processed/
*.csv
*.parquet
*.h5

The key insight here is __pycache__ and .venv. These directories can contain thousands of files. Letting Claude Code index them wastes context and can lead to it “reading” bytecode files that tell it nothing useful. List them in .claudeignore and Claude Code focuses only on your actual source.

Working with uv

uv has become the de-facto fast Python package manager in 2026. CLAUDE.md needs to communicate the uv workflow clearly because the commands differ from pip and poetry.

## uv Workflow

# Installing dependencies
uv sync               # Install from uv.lock (equivalent to npm ci)
uv sync --extra dev   # Include optional [dev] dependencies

# Adding packages
uv add requests                  # Add to [dependencies]
uv add --dev pytest ruff mypy    # Add to [dev-dependencies]
uv add "httpx>=0.27"             # Add with version constraint

# Running code
uv run python script.py          # Run with project's Python
uv run pytest                    # Run pytest via uv
uv run --no-sync python script.py # Skip sync check (faster in scripts)

# Updating
uv lock --upgrade-package requests  # Upgrade single package
uv lock --upgrade                   # Upgrade all packages

# Python version
uv python install 3.12           # Install specific Python version
uv python pin 3.12               # Pin project to 3.12

The most common mistake Claude Code makes without this guidance: running pip install <package> when it should run uv add <package>. pip install bypasses the lockfile and can create inconsistencies. The CLAUDE.md rule “Never use pip install. Use uv add.” is short but essential.

Testing Workflow

Here is a complete testing section for CLAUDE.md that covers the patterns Claude Code needs to know:

## Testing

Framework: pytest with pytest-cov

### Running Tests

```bash
# Full test suite
uv run pytest

# Single file
uv run pytest tests/unit/test_user_service.py

# Single test
uv run pytest tests/unit/test_user_service.py::test_create_user_success

# With coverage
uv run pytest --cov=app --cov-report=term-missing

# Stop on first failure
uv run pytest -x

# Verbose output
uv run pytest -v

# Run only tests marked as fast
uv run pytest -m "not slow"

Test Structure

# Standard test file layout
import pytest
from unittest.mock import MagicMock, patch

from app.services.user_service import UserService
from app.schemas.user import UserCreate


class TestUserService:
    """Tests for UserService."""

    @pytest.fixture
    def service(self, db_session: AsyncSession) -> UserService:
        return UserService(db=db_session)

    async def test_create_user_success(
        self,
        service: UserService,
        valid_user_data: UserCreate,
    ) -> None:
        """Creates a user with valid data."""
        user = await service.create_user(valid_user_data)

        assert user.email == valid_user_data.email
        assert user.id is not None

    async def test_create_user_duplicate_email(
        self,
        service: UserService,
        existing_user: User,
    ) -> None:
        """Raises ValueError on duplicate email."""
        with pytest.raises(ValueError, match="already exists"):
            await service.create_user(
                UserCreate(email=existing_user.email, password="test")
            )

Coverage Requirements

  • New code: minimum 80% line coverage
  • Critical paths (auth, payments, data mutations): 95%+
  • Run uv run pytest --cov-fail-under=80 to enforce in CI

## Data Science and Jupyter Notebook Patterns

Data science projects need CLAUDE.md to explain the notebook-to-module pipeline. Claude Code should understand when to use notebooks (exploration) versus modules (production).

```markdown
## Data Science Configuration

## Environment

- Python: 3.12
- Package manager: uv
- Notebook runner: jupyter lab
- Core stack: pandas, numpy, scikit-learn, matplotlib

## Commands

```bash
# Setup
uv sync --extra dev

# Jupyter
uv run jupyter lab              # Start Jupyter Lab
uv run jupyter nbconvert --execute notebooks/analysis.ipynb  # Run notebook headlessly

# Data pipeline
uv run python src/pipeline/ingest.py    # Run data ingestion
uv run python src/pipeline/transform.py # Run transformation
uv run python src/pipeline/train.py     # Run training

# Testing
uv run pytest tests/
uv run pytest tests/ -m "not slow"  # Skip slow tests that require full dataset

# Quality
uv run ruff check src/
uv run mypy src/

Project Structure

notebooks/
  01_exploration.ipynb    # Exploration only, never imported
  02_feature_analysis.ipynb
src/
  data/
    loader.py       # Data loading utilities
    validator.py    # Schema validation
  features/
    engineering.py  # Feature engineering functions
  models/
    trainer.py
    evaluator.py
  pipeline/
    ingest.py
    transform.py
    train.py
tests/
  test_features.py
  test_models.py
data/
  raw/              # Never commit, in .gitignore
  processed/        # Never commit, in .gitignore

Notebooks vs Modules

Notebooks: Exploration and visualization only. Never import from notebooks. When notebook code is worth keeping, refactor it into src/.

Modules in src/: Reusable functions, tested, type-annotated, linted. All production code lives here.

Data Rules

  • Never commit raw data files (added to .gitignore).
  • Use pathlib.Path everywhere. Never os.path.join().
  • DataFrames: always validate schema on load with pandera or manual checks.
  • Never modify raw data. Always work from copies.
  • Document DataFrame column names and dtypes in module docstrings.

Pandas Patterns Claude Code Should Follow

# Preferred: method chaining with type annotations
def process_sales(df: pd.DataFrame) -> pd.DataFrame:
    return (
        df
        .assign(revenue=lambda x: x["price"] * x["quantity"])
        .query("revenue > 0")
        .groupby("product_id", as_index=False)
        .agg(total_revenue=("revenue", "sum"))
        .sort_values("total_revenue", ascending=False)
    )

# Avoid: index-based access without explicit column reference
# df[0]  <- fragile, use df.iloc[0] or df["column_name"]

# Prefer explicit dtypes on read
df = pd.read_csv("data.csv", dtype={"id": int, "price": float})

## Type Checking: mypy vs pyright

Both mypy and pyright are common in Python projects. They have meaningfully different behaviors, so CLAUDE.md needs to specify which one you use and at what configuration level. Claude Code generating code that passes one but fails the other is a frustrating problem that a two-line CLAUDE.md entry prevents.

```markdown
## Type Checking

Tool: mypy
Config file: pyproject.toml

```toml
[tool.mypy]
python_version = "3.12"
strict = true
plugins = ["pydantic.mypy"]

[[tool.mypy.overrides]]
module = ["tests.*"]
disallow_untyped_defs = false

Run: uv run mypy src/

Strict mode means:

  • All function arguments and return types must be annotated
  • No implicit Any
  • No untyped function definitions
  • Warn on unused ignores

When mypy reports an error, fix the underlying type issue. Use # type: ignore[specific-error] only when the third-party library has incomplete stubs, and always add a comment explaining why.


If you use pyright instead:

```markdown
## Type Checking

Tool: pyright (via pylance in VS Code, standalone via CLI)
Config: `pyrightconfig.json`

```json
{
  "include": ["src"],
  "exclude": ["tests"],
  "pythonVersion": "3.12",
  "typeCheckingMode": "strict",
  "venvPath": ".",
  "venv": ".venv"
}

Run: uv run pyright src/

Note: pyright is stricter than mypy on some narrowing patterns. When pyright rejects code mypy accepts, prefer pyright’s interpretation.


## The Multi-Environment Problem

Python projects frequently run in multiple environments: local dev, CI, Docker, production. CLAUDE.md should tell Claude Code which environment it is operating in and what assumptions are safe.

```markdown
## Environments

### Local Development (your current context)

- OS: macOS / Linux
- Python binary: managed by uv (`.venv/bin/python`)
- Database: local PostgreSQL on port 5432
- Environment variables: `.env` file at project root
- File paths: always use `pathlib.Path`, never hardcoded `/home/...` paths

### CI (GitHub Actions)

- Python: installed via `actions/setup-python`
- Database: PostgreSQL service container
- Environment variables: set via GitHub Actions secrets
- No `.env` file — all config via environment variables

### Docker (production-like testing)

```bash
docker compose up -d          # Start all services
docker compose exec api pytest # Run tests inside container
docker compose down           # Stop services

When debugging an issue that “works locally but fails in CI”, check:

  1. Python version mismatch (pinned in .python-version)
  2. Missing environment variable
  3. Path separator differences (/ vs \)
  4. Database migration state

## Quick Reference: Copy-Paste Templates

We maintain a collection of CLAUDE.md and AGENTS.md templates for common Python stacks in [our rules collection](/rules). The templates there are kept up to date with current tooling recommendations and include community-contributed patterns for less common configurations.

Here is a minimal template matrix to choose from:

| Project Type | Package Manager | Test Runner | Formatter | Type Checker |
|---|---|---|---|---|
| FastAPI + async | uv | pytest-asyncio | ruff | mypy strict |
| Django + DRF | uv | pytest-django | ruff | mypy |
| CLI tool | uv | pytest | ruff | mypy strict |
| Data science | uv | pytest | ruff | mypy (relaxed) |
| Library | uv | pytest | ruff | mypy strict |
| Legacy / pip | pip | pytest | ruff | pyright |

## Common Mistakes and How CLAUDE.md Prevents Them

**Mistake: Claude Code runs the wrong Python**

Without `uv run` prefix, Claude Code may use system Python or the wrong venv. Fix: CLAUDE.md rule that all commands use `uv run <command>`.

**Mistake: Claude Code installs packages system-wide**

`pip install` without an active venv installs to system Python. Fix: "Never use pip install. Use uv add." in CLAUDE.md.

**Mistake: Claude Code skips type checking**

Type errors accumulate when type checking is not part of the definition-of-done. Fix: add mypy/pyright to the "required before finishing" list in AGENTS.md.

**Mistake: Claude Code generates `Optional[X]` instead of `X | None`**

Pre-3.10 syntax still appears in generated code. Fix: add "Use `X | None` not `Optional[X]`" to the type annotation section.

**Mistake: Claude Code uses `os.path` instead of `pathlib.Path`**

`pathlib.Path` is the modern standard and handles cross-platform paths correctly. Fix: explicit rule in CLAUDE.md.

**Mistake: Claude Code adds print statements for debugging**

Common in generated code. Fix: "Use `logging.getLogger(__name__)` not `print()`" in AGENTS.md.

## Putting It Together

The CLAUDE.md and AGENTS.md templates above are starting points. The ones that work best in practice are the ones teams actually maintain. A few principles for keeping them useful:

**Keep commands runnable.** Every command in CLAUDE.md should be something you can paste into a terminal and run successfully. Commands that require environment setup you have not documented are useless.

**Rules should be checkable.** "Write good code" is not a rule Claude Code can verify. "Run mypy with zero errors before marking work complete" is. Write rules that have clear pass/fail states.

**Update when conventions change.** When your team switches from black to ruff, or from poetry to uv, update CLAUDE.md in the same commit. A stale CLAUDE.md is worse than no CLAUDE.md because it actively misdirects.

**Start minimal, add as problems appear.** A 20-line CLAUDE.md that is accurate is more valuable than a 200-line one that is 30% wrong. Add sections when Claude Code makes a specific mistake you want to prevent.

The Python ecosystem will keep changing. uv was not the obvious choice two years ago. Whatever comes next, the CLAUDE.md pattern stays the same: tell Claude Code exactly what your project uses, and it will use it.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules