Python’s toolchain diversity is its strength and its most persistent AI-coding problem. There is no canonical setup. A project might use uv or poetry or bare pip. Formatting might be ruff, black, or autopep8. Type checking could be mypy or pyright. Without a CLAUDE.md, Claude Code makes guesses — and those guesses fail in specific, frustrating ways.
We’ve run Claude Code across Python projects: a FastAPI service, a data pipeline, a CLI tool, and a library. Without configuration, the failures follow a pattern:
pip installto the system Python instead of the project venvpython test.pyinstead ofpytest tests/blackformatting on aruff-only projectmypystrictness violations in generated code because the agent didn’t know the strictness level- Missing
__init__.pyin new packages, breaking imports
A CLAUDE.md with five sections eliminates these. Here’s exactly what to put in it.
Why Python + Claude Code Needs Explicit Configuration
Claude Code is good at Python. The problem is not comprehension — it’s the absence of project-specific defaults. Python’s ecosystem has evolved over decades of competing tools. The agent learned from all of them and defaults to none specifically.
Three areas cause the most friction:
Virtual environment handling. Python projects expect code to run inside a specific venv with specific packages. Claude Code doesn’t automatically know where your venv lives, which Python binary to use, or whether uv run or source .venv/bin/activate is the right way to invoke tools. Incorrect venv handling means ModuleNotFoundError on the first test run.
Tool selection. ruff replaces both flake8 and black in modern Python projects, but Claude Code defaults to pattern-matching from training data. Without explicit configuration, it writes import black; black.format_str(...) in a ruff-only project, or generates code that passes ruff but fails mypy --strict.
Test structure. The pytest ecosystem has strong conventions — fixtures, conftest.py placement, parametrize patterns — that differ from unittest style. Agents without explicit guidance mix the styles.
The Complete CLAUDE.md Template for Python
This template covers the most common Python project setup: uv for package management, ruff for formatting and linting, mypy for type checking, and pytest for testing. Adapt by deleting what doesn’t apply.
# Python Project: [ProjectName]
## Environment Setup
- Package manager: uv (not pip directly, not poetry)
- Python version: 3.12 (pinned in .python-version)
- Virtual environment: .venv/ at project root
- Run commands via: `uv run <command>` (do NOT activate venv manually)
- Install dependencies: `uv sync`
- Add dependency: `uv add <package>`
- Add dev dependency: `uv add --dev <package>`
## Build & Test Commands
- Install: `uv sync`
- Run tests: `uv run pytest`
- Run tests verbose: `uv run pytest -v`
- Run single test: `uv run pytest tests/test_module.py::test_name`
- Type check: `uv run mypy src/`
- Lint + format: `uv run ruff check . && uv run ruff format --check .`
- Format (write): `uv run ruff format .`
- Fix linting: `uv run ruff check --fix .`
## Code Style
- Formatter: ruff (replaces black) — do NOT use black
- Linter: ruff (replaces flake8, isort) — do NOT use flake8
- Line length: 100 characters (configured in pyproject.toml)
- Import sorting: ruff handles this — do NOT add isort
- No print() statements in non-CLI code — use logging
- String formatting: f-strings preferred over .format() or %
## Type Checking
- Type checker: mypy
- Strictness: --strict (or whatever pyproject.toml specifies)
- All new functions and methods must have type annotations
- Avoid `Any` — use proper types, Union, or TypeVar
- Use `from __future__ import annotations` in all files (Python 3.9 compat)
- TypedDict for structured dict return values
- Protocol for structural typing (prefer over ABC where duck typing fits)
## Testing Conventions
- Framework: pytest (not unittest)
- Test location: tests/ directory, mirroring src/ layout
- Test file naming: test_<module_name>.py
- Test function naming: test_<function_name>_<scenario>
- Fixtures: define in conftest.py; use autouse sparingly
- Use pytest.mark.parametrize for testing multiple inputs
- Avoid unittest.TestCase — use plain functions with pytest
- Mock via pytest-mock (mocker fixture), not unittest.mock directly
- Coverage target: 80% on src/; focus on edge cases and error paths
## Project Structure
- src/<package_name>/ — source code (src layout)
- tests/ — all test files
- tests/conftest.py — shared fixtures
- pyproject.toml — all project config (setup.cfg/setup.py deprecated)
- No requirements.txt (use pyproject.toml + uv.lock)
## Error Handling
- Raise specific exceptions, not bare Exception
- Define custom exceptions in src/<package>/exceptions.py
- Use contextlib.suppress() only when truly ignoring an error intentionally
- Never: `except Exception: pass` — always handle or reraise with context
## Logging
- Use the standard logging module (not print)
- Configure loggers per module: `logger = logging.getLogger(__name__)`
- Do not configure handlers in library code — leave that to the application
- Log levels: DEBUG for tracing, INFO for operations, WARNING for recoverable issues, ERROR for failures
## What NOT to Do
- Do not run `pip install` directly — use `uv add` or `uv sync`
- Do not modify requirements.txt (file may not exist; pyproject.toml is canonical)
- Do not use `from package import *`
- Do not add `__init__.py` to tests/ (pytest finds tests without it)
- Do not write type: ignore comments without explaining why in a comment
Poetry Alternative
If your project uses Poetry instead of uv, replace the environment section:
## Environment Setup
- Package manager: poetry
- Python version: 3.12 (specified in pyproject.toml)
- Virtual environment: managed by poetry (do NOT create manually)
- Run commands via: `poetry run <command>`
- Install dependencies: `poetry install`
- Add dependency: `poetry add <package>`
- Add dev dependency: `poetry add --dev <package>`
## Commands
- Run tests: `poetry run pytest`
- Type check: `poetry run mypy src/`
- Lint: `poetry run ruff check .`
- Format: `poetry run ruff format .`
The ruff Configuration
Modern Python projects consolidate formatting and linting in ruff. Your CLAUDE.md should reference the pyproject.toml configuration so Claude Code applies the same rules when generating code:
## Ruff Rules in Effect
- See pyproject.toml [tool.ruff] for enabled rules
- Key rules: E (pycodestyle), W (warnings), F (pyflakes), I (isort), UP (pyupgrade), B (flake8-bugbear)
- Auto-fix safe rules with `uv run ruff check --fix .`
- Do NOT use noqa comments without a specific rule code (e.g., `# noqa: E501`)
With this in CLAUDE.md, Claude Code writes code that passes ruff on the first check rather than requiring a cleanup pass.
mypy Strictness Rules
The mypy --strict flag enables a set of checks that the agent won’t apply by default. The most consequential:
## mypy Rules
- Run: `uv run mypy src/`
- Strictness: strict (--strict flag via pyproject.toml)
- No implicit Optional: function params with defaults do not become Optional[T] automatically
- disallow_untyped_defs: True — all functions need annotations
- warn_return_any: True — do not return Any from typed functions
- If mypy reports an error, fix the type, don't add type: ignore
The disallow_untyped_defs rule is the most practically useful: it forces type annotations on every function Claude Code generates, which makes the output immediately reviewable and IDE-friendly.
pytest Fixtures and Parametrize Patterns
The highest-value thing to specify in CLAUDE.md for test generation is the fixture pattern and the parametrize convention:
## pytest Patterns
Fixture pattern:
```python
# tests/conftest.py
import pytest
from mypackage import create_app, AppConfig
@pytest.fixture
def app():
config = AppConfig(env="test", db_url="sqlite:///:memory:")
return create_app(config)
@pytest.fixture
def client(app):
return app.test_client()
Parametrize pattern:
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("", ""),
(" spaces ", " SPACES "),
])
def test_to_upper(input, expected):
assert to_upper(input) == expected
Do NOT write test classes unless testing stateful objects that require setUp/tearDown logic.
Claude Code generates tests in the fixture pattern when this is explicit. Without it, it defaults to ad-hoc test functions with local setup — not wrong, but harder to maintain.
## PostToolUse Hook for Python
Claude Code supports `PostToolUse` hooks that run shell commands after file edits. For Python, a hook running `ruff check` and `mypy` after every write gives Claude Code immediate feedback on whether its changes are type-safe and lint-clean.
Add to `.claude/settings.json`:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "uv run ruff check . 2>&1 | tail -15 && uv run mypy src/ 2>&1 | tail -15"
}
]
}
]
}
}
With this hook, Claude Code sees lint and type errors after every file write. It will self-correct on the next tool call rather than handing back code with uncaught type violations. This is the difference between a review pass that takes 5 minutes and one that takes 30 seconds.
For faster feedback during exploration, use only ruff check:
{
"command": "uv run ruff check . 2>&1 | tail -10"
}
Run mypy separately when you want full type coverage — it’s slower and better suited to explicit verification than constant background checking.
CI/CD Integration in CLAUDE.md
If your project runs CI, document the exact CI commands so Claude Code generates changes that will pass:
## CI Checks (must pass before merge)
- `uv run ruff check .` — no lint errors
- `uv run ruff format --check .` — no formatting changes needed
- `uv run mypy src/` — no type errors
- `uv run pytest --cov=src --cov-fail-under=80` — 80% coverage minimum
Run locally in sequence:
`uv run ruff check . && uv run ruff format --check . && uv run mypy src/ && uv run pytest --cov=src`
AGENTS.md Version for Cross-Tool Projects
If your team uses multiple AI coding tools (Cursor, Codex, Claude Code), keep an AGENTS.md at the project root. It uses the same format and is read by all major agents natively:
# AGENTS.md — Python Project
## Commands
- install: `uv sync`
- test: `uv run pytest`
- lint: `uv run ruff check .`
- format: `uv run ruff format .`
- typecheck: `uv run mypy src/`
## Critical Rules
1. Use `uv run <cmd>` — never activate venv manually
2. `uv add <pkg>` to add dependencies (not pip install)
3. All functions must have type annotations
4. Test with pytest, not unittest
5. Use `from __future__ import annotations` in all source files
6. No print() in library code — use logging.getLogger(__name__)
## Style
- Formatter: ruff (not black)
- Linter: ruff (not flake8)
- Type checker: mypy --strict
## What Agents Should NOT Do
- pip install anything
- Write bare except: clauses
- Add type: ignore without a comment
- Write test classes when plain functions work
- Use Optional[T] as default param type — use T | None (Python 3.10+) or from __future__ import annotations
Common Mistakes Without CLAUDE.md
These are the patterns that appear most consistently in AI-generated Python code without explicit configuration:
Wrong Python binary. python script.py fails when the project requires python3.12 or uv run python. Specifying uv run as the execution prefix eliminates this.
Missing annotations. Generated functions frequently lack type annotations. The disallow_untyped_defs rule in CLAUDE.md forces Claude Code to include them.
Fixture anti-patterns. Without pytest guidance, agents write setUp/tearDown classes or put fixtures inside test files instead of conftest.py. Over time, this creates duplicated setup code across test files.
Broad exception handling. except Exception: pass is a code smell that agents reach for when propagation looks complex. The explicit policy in CLAUDE.md — “handle or reraise with context” — changes the generated pattern.
print() debugging. Agents add print() calls during iteration. Without an explicit “use logging” rule, these persist in committed code.
Direct dict return types. Agents return raw dict[str, Any] where a TypedDict or dataclass would be clearer. Noting “TypedDict for structured dict return values” in CLAUDE.md shifts the default.
Putting It Together
The template above is a starting point, not a ceiling. The most effective CLAUDE.md files grow from accumulated mistakes: every time Claude Code generates something you have to revert, that pattern belongs in CLAUDE.md.
Python’s toolchain flexibility is an asset once your configuration is explicit. A well-written CLAUDE.md makes that flexibility invisible to Claude Code — it sees one canonical way to run, test, format, and type-check your project, and produces code that fits it from the first suggestion.
Browse Python CLAUDE.md examples in our gallery for real project configurations across FastAPI, Django, data science, and CLI tools.
FAQ
Should I use uv or poetry in CLAUDE.md?
Use whichever your team has already adopted. The critical thing is to pick one explicitly and document the run prefix (uv run vs poetry run). Mixed signals — poetry in pyproject.toml, manual venv activation in CLAUDE.md — produce inconsistent behavior.
Does Claude Code understand Python virtual environments?
Yes, but imperfectly without guidance. It knows venvs exist and can often detect them. What it can’t reliably infer is whether to use uv run, poetry run, or a path-qualified Python. Make the execution convention explicit in CLAUDE.md.
Should I include mypy configuration in CLAUDE.md or just reference pyproject.toml? Reference pyproject.toml for the full rule set, but include the critical policy decisions (no Any, no type: ignore without comment) in CLAUDE.md directly. Those are behavioral rules that need to be in front of Claude Code, not buried in a config file it may not read.
How do I handle projects that still use setup.py?
Add a note in CLAUDE.md: “Legacy setup: use pip install -e . for install; do not add setup.cfg entries for new configuration — use pyproject.toml.” This tells Claude Code the project is in migration and what to do in the meantime.
Can this CLAUDE.md template work for data science projects?
The core template applies. Add a section for your specific toolchain: jupyter nbconvert --execute for notebook validation, pandas type stubs note (pandas-stubs for mypy), and any dataset loading conventions. Data science projects typically relax the no print() rule for exploration code — specify where that’s acceptable.
What is the difference between ruff check and ruff format?
ruff check catches code quality issues — unused imports, undefined names, anti-patterns. ruff format formats code style (equivalent to black). Both should be in your CI and both belong in CLAUDE.md. The agent will apply them correctly once it knows they are separate steps.
Related Articles
- AGENTS.md Best Practices 2026: What 60,000+ Repos Teach Us
- Claude Code for Rust Projects: CLAUDE.md Template and Workflow Guide
- Claude Code for Go Projects: CLAUDE.md Template and Go Module Workflows
- Writing Effective CLAUDE.md: The Complete Guide
- Claude Code Hooks: Complete Reference 2026
- CLAUDE.md Token Optimization: Deep Dive
- AI Coding Rules for Python 2026