Python rules for AI coding agents

General Python is where agents look most fluent and still make old mistakes: mutable default arguments, bare except:, subprocess.run with shell=True, floats for money, or requests inside an async def. The Python rules apply to any Python 3 code and the FastAPI rules are meant to be used on top of them.

They start by making the agent read requires-python in pyproject.toml and write for the lowest version it allows, so it does not reach for syntax the project cannot run. From there they cover typing every public function, catching the narrowest exception you can handle, using pathlib instead of string paths, a src/ layout with import-time side effects kept out, secrets from the environment, and pytest with plain assert and fixtures.

Folder: rules/python/ · python.mdc, CLAUDE.md, AGENTS.md

What the Python rules tell your agent

First rule of each section in python.mdc, quoted as shipped.

Mistakes these rules are written to prevent

Source: “Pitfalls to avoid”.

Checks the agent runs before it says done

Example commands from the Python file (swap in your repo's own):

Run ruff check, ruff format --check, the type checker and pytest (or the project's task runner equivalents) and report the results. If you could not run a command, say so plainly instead of implying it passed.

Before and after: Python: exception handling review

Prompt card 08 · Stack: Python 3 · Type: Review and refactor · Best model: a frontier reasoning model

What the card is for

Replace bare and over-broad except blocks, lost tracebacks and silent return None failures with narrow handling, chained exceptions and domain errors that callers can act on.

Worked example from the card

Before

def get_rate(currency):
    try:
        resp = requests.get(f"{RATES_URL}/{currency}")
        data = resp.json()
        rate = float(data["rate"])
        cache[currency] = rate
        return rate
    except:
        print("rate lookup failed")
        return None

Expected AI output (abridged)

| 2-9 | bare except | narrow handling | catches KeyboardInterrupt and real bugs (KeyError on a
|     |             |                 | typo) and hides them as "None".
Exceptions possible: requests.RequestException (network, timeout), ValueError from .json()
or float(), KeyError for a missing "rate".
- Network errors: expected; translate to RateUnavailable so callers can fall back.
- Bad payload: the provider broke its contract; translate to RateUnavailable with the cause.
- No timeout was set; requests waits indefinitely. Added timeout=5.
- HTTP errors (500) were treated as success. Added raise_for_status().
Caller change: checkout.py tested `if rate is None`; it now catches RateUnavailable.

After

log = logging.getLogger(__name__)

class RateUnavailable(Exception):
    """The exchange rate could not be fetched or parsed."""

def get_rate(currency: str) -> Decimal:
    try:
        resp = requests.get(f"{RATES_URL}/{currency}", timeout=5)
        resp.raise_for_status()
        payload = resp.json()
    except requests.RequestException as err:
        raise RateUnavailable(f"rate service unreachable for {currency}") from err

    try:
        rate = Decimal(str(payload["rate"]))
    except (KeyError, TypeError, InvalidOperation) as err:
        log.warning("bad rate payload for %s: %r", currency, payload)
        raise RateUnavailable(f"malformed rate for {currency}") from err

    cache[currency] = rate
    return rate

Excerpt from card 08; the prompt itself is in the pack.

More Python prompt cards in the pack

Using these rules in Cursor, Claude Code and AGENTS.md tools

Cursor

Put _base.mdc and python.mdc in .cursor/rules/. Globs: pyproject.toml, **/*.py.

Claude Code

Root CLAUDE.md, base first. Path-scoped variant .claude/rules/python.md for **/*.py, pyproject.toml; PostToolUse hook: ruff format <file>.

AGENTS.md tools (Codex, Gemini CLI, Aider)

Base then stack in one AGENTS.md. Gemini CLI needs context.fileName; Aider needs --read AGENTS.md. Put this file in the repo root, or each Python package directory. Agents use the AGENTS.md nearest to the file being edited, so a nested copy overrides a root one for that subtree.

Related stacks

← All stacks