Claude Code for Python — Complete Setup Guide

Claude Code works extremely well with Python projects when configured correctly. This guide covers virtual environment setup, pytest automation, Django and FastAPI configuration, type annotation workflows, and CLAUDE.md patterns that make Claude productive from the first prompt.

Python CLAUDE.md Template

A good CLAUDE.md eliminates repetitive setup. Here is a production-ready template for a Python project:

# Project: [Your App Name]

## Stack
- Python 3.12, venv at `.venv/`
- Django 5.x / FastAPI 0.115 (choose one)
- Database: PostgreSQL (via psycopg3)
- Testing: pytest + pytest-django

## Commands
```bash
# Activate environment
source .venv/bin/activate  # Linux/Mac
.venv\Scripts\activate      # Windows

# Run tests
pytest --tb=short -q

# Start dev server (Django)
python manage.py runserver

# Start dev server (FastAPI)
uvicorn app.main:app --reload

# Database migrations
python manage.py makemigrations && python manage.py migrate

# Linting
ruff check . && mypy src/
```

## Conventions
- Always add full type annotations (PEP 484)
- Use dataclasses or Pydantic models for data structures
- Follow Ruff defaults for formatting
- Tests in tests/ directory, mirroring src/ structure
- Never import from __future__ annotations unless required

## Architecture
- src/[appname]/ — application code
- tests/ — pytest tests
- scripts/ — one-off maintenance scripts

Virtual Environment Configuration

Claude Code runs shell commands in your project directory. To ensure it uses your venv automatically, add a PreToolUse hook or tell Claude explicitly in CLAUDE.md.

Option A: CLAUDE.md instruction (simplest)

## Environment
All Python commands must be run with the venv active.
Prefix every Python/pip command with: source .venv/bin/activate &&
Example: source .venv/bin/activate && pytest
Claude reads this and will automatically prepend the activation. Works reliably across sessions.

Option B: Hook (automatic, no instruction needed)

// .claude/settings.json
{
  "hooks": [
    {
      "event": "PreToolUse",
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo 'source .venv/bin/activate' > /tmp/activate_prefix"
      }]
    }
  ]
}

uv projects

# For uv-managed projects, tell Claude:
# Use 'uv run' prefix instead of activating venv.
# Example: uv run pytest, uv run python manage.py runserver

## Commands
uv run pytest --tb=short -q
uv run python manage.py runserver
uv add <package>  # instead of pip install

Automated pytest with Hooks

Run tests automatically after every file edit — the fastest feedback loop for TDD:

// .claude/settings.json
{
  "allowedTools": ["Edit", "Write", "Bash", "Read"],
  "hooks": [
    {
      "event": "PostToolUse",
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "cd $PROJECT_ROOT && source .venv/bin/activate && pytest --tb=short -q --no-header 2>&1 | tail -20"
      }]
    }
  ]
}
The tail -20 keeps the output manageable. Remove it if you want full pytest output.

TDD workflow prompt

claude "write a failing test for [feature], then implement it until the test passes.
Run pytest after each change. Stop when all tests pass."

pytest configuration for Claude Code

# pytest.ini or pyproject.toml [tool.pytest.ini_options]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_functions = ["test_*"]
addopts = "--tb=short -q"

Type Annotations Workflow

Claude Code writes fully typed Python when instructed. Add this to CLAUDE.md:

## Type Annotations
- All functions and methods must have full type annotations
- Use typing.TypeAlias for complex types
- Prefer dataclasses for value objects
- Use Pydantic BaseModel for anything that crosses a boundary (API, DB, config)
- Run mypy after generating new modules
PatternClaude will generate
Simple functiondef fn(x: int, y: str) -> bool:
Optional argsdef fn(x: int, y: str | None = None) -> list[str]:
Data container@dataclass\nclass User:\n id: int\n email: str
API bodyclass CreateUserRequest(BaseModel):\n email: str\n name: str
Async functionasync def fetch(url: str) -> dict[str, Any]:

mypy hook

// Add to .claude/settings.json hooks array
{
  "event": "PostToolUse",
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "cd $PROJECT_ROOT && source .venv/bin/activate && mypy src/ --ignore-missing-imports --no-error-summary 2>&1 | tail -10"
  }]
}

Django Setup

# CLAUDE.md additions for Django
## Django
- Django 5.x, apps in apps/
- Settings split: config/settings/base.py, local.py, production.py
- Use 'python manage.py' for all Django commands
- Never use 'django.conf.settings' in tests — use pytest-django @pytest.mark.django_db

## Django commands
python manage.py makemigrations [app]
python manage.py migrate
python manage.py createsuperuser
python manage.py shell_plus  # django-extensions
python manage.py test [app] --keepdb

Django CLAUDE.md prompts that work well

# Add a new model
claude "add a Post model to apps/blog/models.py with fields:
title (CharField 200), body (TextField), author (FK to User),
published_at (DateTimeField null/blank), created_at (auto_now_add).
Add __str__, ordering, and admin registration.
Run makemigrations and migrate after."

# Add an API endpoint
claude "add a REST endpoint GET /api/posts/ returning paginated posts
as {id, title, author_name, published_at}. Use DRF.
Add serializer, view, URL, and tests."

FastAPI Setup

# CLAUDE.md additions for FastAPI
## FastAPI
- FastAPI 0.115, app in app/
- Routers in app/routers/, models in app/models/, schemas in app/schemas/
- Use Pydantic v2 for all request/response models
- Database via SQLAlchemy 2.x async with asyncpg
- Start server: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

FastAPI prompts

# Add a new route
claude "add POST /users/ endpoint that:
- accepts CreateUserRequest(email: str, name: str)
- creates a User in the DB
- returns UserResponse(id: int, email: str, name: str, created_at: datetime)
Add the route to app/routers/users.py, register in app/main.py,
add pytest-asyncio test. Use async def throughout."

Linting with Ruff

// Auto-lint hook
{
  "event": "PostToolUse",
  "matcher": "Edit|Write",
  "hooks": [{
    "type": "command",
    "command": "cd $PROJECT_ROOT && source .venv/bin/activate && ruff check --fix $(git diff --name-only HEAD) 2>&1 | tail -5"
  }]
}
# pyproject.toml
[tool.ruff]
target-version = "py312"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]

Common Python Workflows

TaskPrompt
Add dependencyclaude "add [package] to the project. Install it in the venv, add to requirements.txt/pyproject.toml, and import it in [file]."
Write testsclaude "write pytest tests for [module]. Aim for 90% coverage. Use fixtures for DB/HTTP mocking."
Refactor to asyncclaude "convert [function] to async using asyncio. Keep the sync wrapper for CLI use."
Add Pydantic validationclaude "add Pydantic v2 validation to [class]. Validators for: [constraints]."
Profile slow codeclaude "profile [function] with cProfile, identify the slowest call, propose a fix."
DB migrationclaude "add [column] to [model]. Write the migration, run it, update tests."

Frequently Asked Questions

Does Claude Code work with Python 2?

Claude Code has no version restriction, but Claude's knowledge is strongest for Python 3.10+. If you are on Python 2, specify this explicitly in CLAUDE.md — Claude will avoid Python 3-only syntax. That said, migrating to Python 3 is strongly recommended and Claude can assist with the migration.

How do I get Claude to always use my venv Python?

In CLAUDE.md add: Always use python from .venv/bin/python (never system python). All pip commands must target the venv. Claude will follow this consistently. Alternatively, rely on your shell's venv activation if you have it set in your terminal profile.

Can Claude Code generate full test files?

Yes. Prompt: claude "write a comprehensive pytest test file for src/mymodule.py. Cover all public functions. Use pytest fixtures for any I/O. Include edge cases and error paths." Claude will create the test file and run it. Iterate from there.

How do I stop Claude from breaking my imports?

Add your import conventions to CLAUDE.md: which packages are installed (requirements.txt / pyproject.toml dependencies), your package name, and any aliased imports (e.g., import numpy as np). Claude reads your existing code and follows the patterns it sees, but explicit instruction in CLAUDE.md eliminates ambiguity.

→ Testing Workflow — full pytest + coverage guide

→ Hooks — automate lint, tests, and formatting

→ Project Setup — CLAUDE.md and settings.json

→ Claude Code for TypeScript — Node.js & TS guide

← Back to Claude Code Workflows Home