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 annotationsfor 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. Useuv add <package>instead. - Never modify
uv.lockdirectly. - 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-settingsfor config. Neveros.environ.get()in app code. - All route functions must be
async def. - Use
Annotatedfor 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.pyto overrideget_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.BaseModelfor 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 inmain.py). - Do not put business logic in route functions.
- Do not commit
.envfiles. - 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.localfor local dev. - Apps live in
apps/, never at the project root. - All models inherit from
TimeStampedModel(providescreated_at,updated_at). - Use custom User model from project start. Never use
django.contrib.auth.Userdirectly. - URL namespaces required:
app_name = "users"in everyurls.py.
Testing Patterns
- Use
pytest-django. Configure inpyproject.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_dbfor any test that touches the database. - API tests use DRF’s
APIClient, not Django’sTestClient.
Migration Rules
- Never squash migrations without team agreement.
- All migrations must be reversible (implement
backwardsor verify auto-reversal). - Run
uv run python manage.py migrate --checkin 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.tomldependencies manually. - Never run
pip install.
Testing Rules
- New code requires tests. No exceptions.
- Test files mirror source:
app/services/user_service.py→tests/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. Uselogging.getLogger(__name__). - Do not hardcode configuration values. Use
app/config.py(pydantic-settings). - Do not commit secrets. Check
.gitignorebefore adding new file types. - Do not bypass mypy with
# type: ignorewithout 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=80to 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.Patheverywhere. Neveros.path.join(). - DataFrames: always validate schema on load with
panderaor 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:
- Python version mismatch (pinned in
.python-version) - Missing environment variable
- Path separator differences (
/vs\) - 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.