Python CLAUDE.md cursor rules AI coding rules best practices developer tools Claude Code

AI Coding Rules for Python Projects 2026: CLAUDE.md and .cursorrules Templates

The Prompt Shelf ·

AI coding rules for Python projects solve a specific problem: Python is permissive enough that an AI assistant without project-specific guidance will produce technically correct code that still doesn’t fit your codebase. Different projects use different formatting tools, different type systems, different testing patterns, and different conventions for packaging and environments. Without explicit rules, AI tools make reasonable guesses — and those guesses are often wrong for your specific setup.

This guide covers practical CLAUDE.md and .cursorrules templates for Python projects, with rules organized by domain: type hints, testing, packaging, environment management, and async patterns. Browse Python-specific rules from real projects in our gallery.

Core Python Rules: What Belongs in Every File

Regardless of your Python project’s specific stack, these rules belong in any AI coding configuration:

## Python Version and Runtime
- Python 3.11+ required
- Use f-strings for string formatting (not .format() or %)
- Prefer pathlib.Path over os.path for file operations
- Use contextlib.suppress() over bare except blocks for expected exceptions

## Type Hints
- All function signatures must have type hints (parameters + return type)
- Use `from __future__ import annotations` at the top of every file
- Use `X | Y` union syntax (not Optional[X] or Union[X, Y])
- Use built-in generics: list[str] not List[str], dict[str, int] not Dict[str, int]
- TypedDict for dict shapes that are passed between functions

## Code Style
- Black for formatting (line length: 88)
- isort for import ordering (profile = black)
- ruff for linting
- Run: black . && isort . && ruff check .

The from __future__ import annotations import deserves a note: it enables PEP 563 deferred evaluation, which allows forward references in type hints and avoids runtime errors in Python 3.9. If your codebase targets 3.9, this is not optional.

Type Hints in Practice

Type hints are where AI-generated Python code most often needs correction. A well-written rules section prevents the common mistakes:

## Type Hint Rules

### Use X | Y not Optional
# Bad
from typing import Optional
def get_user(id: int) -> Optional[str]:

# Good
def get_user(id: int) -> str | None:

### Return types are required
# Bad
def process_data(items):
    return [x * 2 for x in items]

# Good
def process_data(items: list[int]) -> list[int]:
    return [x * 2 for x in items]

### TypedDict for complex dicts
from typing import TypedDict

class UserConfig(TypedDict):
    name: str
    role: str
    permissions: list[str]

def apply_config(config: UserConfig) -> None:
    ...

### Protocols over ABCs for structural typing
from typing import Protocol

class Closeable(Protocol):
    def close(self) -> None: ...

def safe_close(resource: Closeable) -> None:
    resource.close()

Project Structure Rules

AI tools need to know your project’s layout to put files in the right place:

## Project Structure
src/
  mypackage/          # main package
    __init__.py
    models/           # dataclasses, TypedDicts, Pydantic models
    services/         # business logic
    api/              # HTTP handlers (if applicable)
    utils/            # shared utilities
tests/
  unit/              # fast, no I/O
  integration/       # may use database/network
  conftest.py        # shared fixtures
scripts/             # one-off scripts, not part of the package

## Import conventions
- Absolute imports only (no relative imports like `from ..utils import foo`)
- Import the module, not the contents, unless the contents are constants:
  - Good: import mypackage.utils as utils
  - OK: from mypackage.constants import MAX_RETRIES
  - Bad: from mypackage.services import create_user

Testing Rules

Testing is where Python projects diverge most significantly. Spell out your exact setup:

## Testing Setup
- pytest (not unittest)
- pytest-asyncio for async tests
- pytest-mock or unittest.mock for mocking
- Test command: pytest tests/ -v --tb=short
- Coverage command: pytest --cov=src/mypackage --cov-report=term-missing

## Test Writing Rules
- File naming: test_{module_name}.py in the tests/ directory
- Function naming: test_{what}_{condition}_{expected_result}
- Use fixtures for shared setup, not setUp/tearDown
- Assert specific values, not just truthiness: `assert result == expected` not `assert result`
- Mock at the boundary of your system, not inside it
- Never use time.sleep() in tests — use freezegun or mock datetime

## Example test pattern
import pytest
from unittest.mock import AsyncMock, patch
from mypackage.services import UserService

@pytest.fixture
def user_service() -> UserService:
    return UserService(db=None)  # or use a fixture DB

async def test_get_user_returns_none_when_not_found(
    user_service: UserService,
) -> None:
    with patch.object(user_service, "db") as mock_db:
        mock_db.fetch_one = AsyncMock(return_value=None)
        result = await user_service.get_user(id=999)
    assert result is None

Environment and Dependency Management

This area causes the most friction when it is not specified:

## Environment Management
- Use uv for package management (not pip/poetry/pipenv)
- Virtual environment: .venv/ in project root (created with: uv venv)
- Install command: uv pip install -e ".[dev]"
- Never add packages to requirements.txt directly — use pyproject.toml

## pyproject.toml conventions
[project]
name = "mypackage"
requires-python = ">=3.11"
dependencies = [
    "httpx>=0.27",
    "pydantic>=2.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-asyncio",
    "pytest-mock",
    "black",
    "ruff",
    "isort",
    "mypy",
]

## Never pin exact versions in pyproject.toml (use >= or ~=)
## Pin in requirements.lock (generated by: uv pip compile pyproject.toml)

Async Python Rules

Async code in Python has more footguns than synchronous code. AI tools without explicit rules will produce patterns that look reasonable but have subtle bugs:

## Async Rules
- Use asyncio.gather() for parallel I/O (not sequential await)
- Use asyncio.timeout() (Python 3.11+) for timeouts, not asyncio.wait_for()
- Never call .run() from inside an async function — use await
- Never block in an async function: no time.sleep(), no blocking I/O
- Use anyio.to_thread.run_sync() for CPU-bound work in async context
- httpx.AsyncClient not requests (requests is blocking)

## Common pattern: parallel requests
# Bad
result_a = await fetch(url_a)
result_b = await fetch(url_b)  # Sequential, slow

# Good
result_a, result_b = await asyncio.gather(
    fetch(url_a),
    fetch(url_b),
)

## Common pattern: timeout
# Bad (deprecated)
async with asyncio.timeout_at(deadline):  # use asyncio.timeout instead

# Good
async with asyncio.timeout(30.0):
    result = await slow_operation()

Dataclasses and Pydantic

Specify which to use and when:

## Data Models
- Pure data containers with no validation: use dataclasses
- Data that crosses API/network boundaries: use Pydantic v2 BaseModel
- Configuration: use Pydantic BaseSettings
- Never use plain dicts for domain objects — use TypedDict or dataclass

## Pydantic v2 patterns
from pydantic import BaseModel, Field, field_validator

class CreateUserRequest(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    email: str
    role: str = "viewer"

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email format")
        return v.lower()

## model_dump() not .dict() (Pydantic v2)
data = user.model_dump()          # correct
data = user.model_dump(mode="json")  # for JSON serialization

CLAUDE.md Full Template for Python Projects

Here is a complete, production-ready template you can adapt:

# Python Project — Claude Code Instructions

## Environment
- Python 3.11+
- Package manager: uv
- Virtual env: .venv/
- Run tests: pytest tests/ -v
- Lint: ruff check . && black --check .
- Type check: mypy src/

## Code Conventions
- from __future__ import annotations at top of every file
- All function signatures require type hints
- f-strings for formatting
- pathlib.Path not os.path
- Black formatting (line length 88)

## Type Hints
- X | None not Optional[X]
- list[str] not List[str]
- TypedDict for complex dict shapes
- Protocol for structural typing

## Testing
- pytest
- test_{what}_{condition}_{expected} naming
- Assert specific values
- Mock at system boundaries

## Project Structure
- Business logic in src/mypackage/services/
- Models in src/mypackage/models/
- Tests mirror src/ structure

## What to Avoid
- Relative imports
- print() for logging (use logging module)
- Mutable default arguments
- Bare except clauses
- time.sleep() in tests

.cursorrules Template for Python

If you use Cursor alongside or instead of Claude Code:

You are working on a Python 3.11+ project. Follow these rules strictly:

FORMATTING
- Use Black (line length 88) and isort (profile=black) for formatting
- Run ruff for linting
- All files start with: from __future__ import annotations

TYPE HINTS
- Every function parameter and return type must have hints
- Use X | None instead of Optional[X]
- Use built-in generics: list[str] not List[str]
- Use TypedDict for complex dict shapes

TESTING
- pytest only (no unittest)
- Name tests: test_{what}_{condition}_{expected}
- Use fixtures, not setUp
- Mock at system boundaries

ASYNC
- asyncio.gather() for parallel I/O
- asyncio.timeout() for timeouts (Python 3.11+)
- No blocking calls inside async functions
- httpx.AsyncClient for HTTP

PACKAGES
- uv for package management
- pyproject.toml only (not requirements.txt)
- Pydantic v2 for API boundary models
- dataclasses for internal data containers

NEVER
- Relative imports
- Mutable default arguments (def f(x=[]) pattern)
- Bare except
- print() for logging
- time.sleep() in tests

For more Python-specific rules from production codebases, see the Prompt Shelf gallery. For how to write effective CLAUDE.md files in general, see our CLAUDE.md guide.

More from the blog

Explore the collection

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

Browse Rules