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
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"
}]
}
]
}
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
| Pattern | Claude will generate |
|---|---|
| Simple function | def fn(x: int, y: str) -> bool: |
| Optional args | def fn(x: int, y: str | None = None) -> list[str]: |
| Data container | @dataclass\nclass User:\n id: int\n email: str |
| API body | class CreateUserRequest(BaseModel):\n email: str\n name: str |
| Async function | async 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
| Task | Prompt |
|---|---|
| Add dependency | claude "add [package] to the project. Install it in the venv, add to requirements.txt/pyproject.toml, and import it in [file]." |
| Write tests | claude "write pytest tests for [module]. Aim for 90% coverage. Use fixtures for DB/HTTP mocking." |
| Refactor to async | claude "convert [function] to async using asyncio. Keep the sync wrapper for CLI use." |
| Add Pydantic validation | claude "add Pydantic v2 validation to [class]. Validators for: [constraints]." |
| Profile slow code | claude "profile [function] with cProfile, identify the slowest call, propose a fix." |
| DB migration | claude "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