Skip to main content

modern-python-guidance

CI PyPI version Python License

Stop your AI from writing typing.List, @validator, and setup.py. 41 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — carried by a rules file that loads whenever you edit Python or project config, a check that runs on each .py file you save, and MCP/CLI lookup for the rest of the catalog.

Highlights

  • Evidence status: Historical V5 content-efficacy evidence is documented with its model, prompt, sample, metric, and delivery scope; it is not a product-wide effectiveness claim (V5 benchmark details)
  • 41 guides across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
  • Version- and dependency-aware: filters Python-version-incompatible guidance and qualifies framework/tool guidance from project evidence
  • Delivery, in the order it reaches you: a Rules file auto-loaded when you edit Python files or project config (pyproject.toml, requirements*.txt, setup.cfg, .python-version, Pipfile) — this is the path carrying the embedded patterns; a PostToolUse hook checking each saved .py; Agent Skills; and MCP/CLI lookup for the rest of the catalog
  • Not Ruff: Ruff auto-fixes syntax (Listlist). mpg guides design decisions that Ruff can't touch — TaskGroup over gather, Pydantic V2 migration, SQLAlchemy 2.0 style

Benchmark evidence status: historical V5 cells remain documented for audit, but no traceable numeric product claim is currently promoted. Default mpg setup end-to-end effectiveness has not yet been measured.

Note: The tool itself requires Python 3.11+ to run. Guides cover patterns from Python 3.9 onward, and --python-version filters guides for your target environment.

Quick start

Claude Code (recommended)

pip install modern-python-guidance
mpg setup

This registers the MCP server, links Agent Skills, creates a Rules file (.claude/rules/modern-python.md), and registers a PostToolUse hook in one command. The Rules file auto-injects modern Python guidance whenever you touch Python-related files, and the hook checks each edited .py file against the guides with a detector applicable to the target project (see PostToolUse hook below). Advisory-only gaps are disclosed through CLI/MCP metadata. Start a new Claude Code session afterwards — newly registered MCP servers, skills, rules, and hooks take effect on the next launch.

CLI

pip install modern-python-guidance
mpg search "pydantic validator"
mpg retrieve pydantic-v2-validators

mpg is the short alias for modern-python-guidance. Both work.

Manual setup / other agents

MCP registration (Claude Code):

# uv tool / pipx installs (mpg is on PATH):
claude mcp add mpg -- mpg mcp

# venv installs — Claude Code spawns MCP servers outside your venv, so register
# the interpreter's absolute path (`mpg setup` does this automatically):
claude mcp add mpg -- "$(python -c 'import sys; print(sys.executable)')" -m modern_python_guidance mcp

Other MCP-compatible agents (Cursor, Windsurf, etc.) — add to your MCP config:

{
  "mcpServers": {
    "mpg": {
      "command": "mpg",
      "args": ["mcp"]
    }
  }
}

For venv installs, set "command" to your interpreter's absolute path and "args" to ["-m", "modern_python_guidance", "mcp"] for the same reason as above.

Agent Skills + Rules only (Claude Code):

mpg setup --skills-only

mpg setup flags:

Flag Purpose
--mcp-only MCP registration only
--skills-only Project-local artifacts only (Skills + Rules)
--scope {user,local} MCP scope (default: user)
--project-dir PATH Target project for local MCP and project artifacts
--dry-run Show what would be done
--no-hook Don't register the PostToolUse hook (removes it if already present)
--with-hook Register the hook even if this project already has mpg artifacts

If this project already has mpg's Skills or Rules symlinks from a previous mpg setup, the hook is not silently enabled — you'll see a note instead (New: mpg can auto-check Python files after every edit. / Enable: mpg setup --with-hook). A fresh project gets the hook by default.

After registering, mpg setup checks whether a same-name registration in a higher-precedence scope (local > project > user) would shadow the one it just wrote, and prints a warning with the exact claude mcp remove command if so.

When mpg setup reports success, every symlink it created leads to a bundled source with readable content — a skills directory carrying a readable SKILL.md, and a non-empty rule file. An installation that is present but hollow (a packaging or install accident) fails setup with the hollow path named, instead of linking silently; mpg doctor keeps reporting an already-made link to such a source as degraded, exactly as described under Check the install below.

Uninstall — reverse mpg setup (deregister the MCP server and unlink Agent Skills + Rules):

mpg uninstall            # remove all
mpg uninstall --dry-run  # preview what would be removed
Flag Purpose
--mcp-only MCP deregistration only
--skills-only Project-local artifacts only (Skills + Rules)
--project-dir PATH Target project for local MCP and project artifacts
--dry-run Show what would be done

mpg uninstall clears the MCP registration from every scope setup can write to (user and local), removes only the symlinks mpg created (never their targets or other files), removes mpg's PostToolUse hook entry from .claude/settings.local.json (leaving any other tools' hooks untouched), and is idempotent — running it on an already-clean state is a harmless no-op.

When --scope local is used, --project-dir is also the cwd for every MCP add, replacement, health-check, and removal operation. A missing target is created before local setup; uninstall fails closed for a missing explicit target instead of touching the caller's local registration. User-scope MCP operations remain global and do not use this path.

Check the installmpg doctor reports what each delivery channel is actually doing. It repairs nothing; mpg setup remains the repair tool.

mpg doctor                        # inspect the nearest project root
mpg doctor --project-dir ./app    # inspect a specific project

Each channel reads as present, degraded, absent, or unknown, with the fix printed beside anything broken. The exit status is the machine-readable half: 0 when every channel is present or absent, 1 when any is degraded, 2 when any could not be determined. absent is not a failure — --mcp-only, --skills-only, and --no-hook each make it a configuration someone chose. unknown is never folded into health, because a check that cannot tell "working" from "not measured" is not a check.

present means the delivery path is intact and carries something. For the Skills and Rules symlinks that means the target is opened and read, not just named — a link whose destination has the right name and nothing behind it is degraded. For the hook it means the registration is the shape Claude Code runs: the entry is a command entry, its arguments are the ones that invoke mpg, and exactly one entry matches each of the tools mpg hooks. A registration that only matches other tools never fires on an edit, and two that match the same tool are one more than mpg setup writes; both read as degraded. Every mpg entry in the file is examined, not the first one found.

A matcher written as a regular expression is answered only from a subset measured to mean the same thing in both engines. Claude Code evaluates that form in JavaScript and doctor evaluates it in Python, and the two languages differ at the edges — (?P<x>Edit)|Write fires in Python and is a syntax error in JavaScript, so the hook never runs while the old check called it present. The subset, and what was measured to draw it, are in matcher_fires_on's docstring; anything outside it, including working patterns like (Edit|Write) or \d, reads as unknown rather than being answered from the wrong engine. The simple form (Edit|Write, Edit, Write) is unaffected, and is not covered by that measurement either: doctor answers it by splitting on | and , and comparing names, following the documented rule rather than anything measured about how Claude Code reads it.

By default the hook channel never runs the registered command, and reports only the shape of the registration. The interpreter path in a settings file is an arbitrary string, so executing it turns a read-only diagnostic into a way to run whatever a settings file names — including one that arrived with a repository you cloned. The cost of not running it is that an interpreter which exists but no longer has mpg installed reads as present, since nothing has asked it.

mpg doctor --run-interpreter asks it. The registered interpreter is run the way Claude Code spawns the hook — <command> -m modern_python_guidance --version — and the channel is present only if that prints mpg's name and a version. An exit status of 0 is not enough: a program that ignores its arguments and succeeds satisfies one without loading anything. Any version is accepted, not this installation's, because a hook wired to an interpreter holding an older mpg is working. A non-zero exit, or output that is not a version line, is degraded; an interpreter that cannot be started at all (a missing shebang target, no execute permission) is degraded too, since Claude Code spawning the same hook meets the same wall. A timeout, a failure to start for reasons of this process's own, or more than four distinct interpreters to try, read as unknown.

The run is bounded: no stdin, at most four kilobytes of output read, five seconds per interpreter, its own process group killed on timeout, an environment holding only PATH and HOME, a scratch working directory, and at most four distinct spellings of a command per run — so about twenty-five seconds in the worst case, and ../python counts separately from python.

Those bounds cap the damage; they do not prevent it. A child that calls setsid() outlives the group kill and keeps running after doctor has answered. Nothing stops it reaching the network. It runs as you, so everything you can read it can read — ~/.aws/credentials included — and everything you can write it can write, which is the more durable problem: five seconds is enough to append to ~/.zshrc. PATH and HOME are there because a pyenv or conda shim cannot start without them, and dropping the rest is what keeps credentials held in the environment out of reach; it does nothing about credentials in files.

Opting in is the actual control, which is why this is a flag you type rather than a default. Do not put --run-interpreter in CI: a fork's pull request can carry both a binary and the settings file naming it, and a checkout path in CI is predictable.

Two things it does not establish. That the hook produces guidance — present here means something printed a version line, no more, and a program that only echoes one satisfies it. And that a slow machine will finish in five seconds: exceeding the timeout on a healthy interpreter reads as unknown, which exits 2.

Inspecting the MCP registration runs claude mcp get, which starts the server to answer, so mpg doctor takes roughly 1.5 seconds rather than being instant. That is also why it does not use claude mcp list — that connects to every server you have configured, and took 30 seconds on the machine this was measured on.

CLI usage

# Search guides by keyword
mpg search "pydantic validator"

# Retrieve a specific guide (full BAD/GOOD content)
mpg retrieve use-builtin-generics

# List all guides compatible with your Python version
mpg list --python-version 3.11

# Auto-detect project Python version from pyproject.toml / .python-version
mpg detect-version

# Filter by category
mpg search "timeout" --category async

# Filter by layer (1 stdlib, 2 frameworks, 3 toolchain) or by how often the pattern is missed
mpg list --layer 2
mpg list --frequency high

# Filters combine — this is the intersection, not the union
mpg list --layer 1 --frequency high

# Emit the selected guides with their full bodies, for building a system prompt
mpg list --layer 2 --frequency high --with-content --format json > guidance.json

# Scan a file for outdated patterns
mpg check app.py
mpg check app.py --format json | jq '.summary.guide_ids'
mpg check app.py --exit-zero  # always exit 0

# JSON output (default when piped, explicit with --format)
mpg search "typing" --format json | jq '.[0].id'

# Read dependency evidence from another project; hide proven-incompatible guides by default
mpg search "pydantic validator" --project-dir ../my-app

# Supply an exact target-environment override when files cannot prove it
mpg search "pydantic validator" --dependency-version package:pydantic=2.7.4

Supported platforms

Linux is what CI runs — ubuntu-latest across Python 3.11, 3.12, 3.13, and 3.14 — and macOS is where the project is developed. Both are exercised routinely.

Windows is untested, and one part of it is known to fail. mpg setup installs the Agent Skills directory and the rule file as symlinks, and Windows grants that privilege only with Developer Mode enabled or from an elevated prompt; without either, os.symlink raises WinError 1314 and those two steps report an error. The remaining steps are unaffected — MCP registration and hook registration run independently and complete — but mpg setup still exits non-zero, because one of its steps failed. mpg setup --mcp-only avoids the symlinks entirely, at the cost of skipping the hook as well. This is read off the code path, not observed: no Windows machine has run it.

Guide coverage

41 guides across 3 layers:

Layer Categories Count Examples
1 — stdlib typing, async, stdlib, data-structures 18 list over List, match/case, TaskGroup, deferred annotations, t-strings
2 — frameworks pydantic, fastapi, httpx, django, sqlalchemy, pytest 18 Pydantic V2 migration, SQLAlchemy 2.0 style, Annotated[Depends]
3 — toolchain toolchain 5 uv over pip, ruff over flake8, pickle avoidance

Run mpg list to see the 41-guide catalog, or browse it on GitHub.

Selecting a subset of the catalog

--category, --layer, and --frequency select on guide metadata and combine as an intersection: --layer 1 --frequency high returns the guides that are both, not either. search and list accept all three, as do the search_guides and list_guides MCP tools. mpg list --with-content adds each guide's full body to the output, which is the shape to pipe into a system prompt or a generated rules file.

Two things to expect. First, the counts you get back are what survives after the target Python and dependency filters, which apply on every command whether or not you asked for them — mpg list --layer 1 reports fewer guides than the layer table above when your project targets an older Python, and --python-version 3.14 shows the full catalog. Second, mpg search falls back to fuzzy suggestions whenever the exact query matches nothing, and a filter can leave it with nothing to match; a precise query that returns unexpected results marked fuzzy: true usually means the filter excluded the guide you were looking for, not that the query was wrong.

Note that --layer and --frequency make two abbreviations ambiguous that used to resolve: --f no longer selects --format, and --l no longer selects --limit. Spell those two out.

Version-aware filtering

Guides specify their minimum Python version. Every guidance command resolves the target Python from the nearest project context (and discloses the result as target_python: {version, source}) using this precedence:

  1. --python-version flag
  2. pyproject.toml requires-python
  3. pyproject.toml Poetry python constraint (^3.10, ~3.11, >=3.10,<3.14)
  4. .python-version file
  5. Default: 3.11

The same resolver is used by CLI search/retrieve/list/check, the MCP guidance tools, and the PostToolUse hook. An explicit CLI/MCP version always wins. mpg detect-version keeps its plain output for scripts; use mpg detect-version --format json to audit the resolved version and stable source label, including when a JSON search or list result is empty.

# Only shows guides compatible with Python 3.9 (explicit override)
mpg list --python-version 3.9
# Excludes: TaskGroup (3.11+), match/case (3.10+), etc.

Dependency-aware applicability

Framework and toolchain guides declare machine-readable applies-to-packages and/or applies-to-tools metadata. Every declared requirement must be proven (AND semantics). mpg search and mpg list hide guidance that is proven incompatible by default; use --include-incompatible to inspect it. retrieve always returns an explicitly requested guide so migration work can be reviewed.

Use --project-dir PATH to read the nearest project evidence, and repeat --dependency-version KIND:NAME=VERSION for an exact target-environment override. The MCP search_guides, retrieve_guides, and list_guides tools expose the same project_dir (relative to the MCP server), dependency_versions object, and include_incompatible option where filtering applies. search_guides and list_guides additionally accept layer and frequency; retrieve_guides takes neither, since it is addressed by explicit ID.

Results contain additive dependency_requirements and dependency_compatibility objects. Status is deliberately conservative:

  • confirmed: all requirements are proven compatible;
  • incompatible: at least one requirement is proven incompatible;
  • unknown: evidence is absent, ambiguous, conditional, or cannot safely prove a range.

Unknown is not confirmation. Do not infer the target environment from the interpreter running mpg, a [tool.uv]/[tool.ruff] table, optional or dependency-group entries, or an unrooted/ambiguous lockfile. The PostToolUse hook suppresses known-incompatible findings and asks for verification when status is unknown; mpg check follows the same rule and annotates remaining unknown findings in JSON/human output.

PostToolUse hook

mpg setup registers a PostToolUse hook by default (see Quick start above) — no manual .claude/settings.local.json editing needed. It checks every .py file Claude edits or writes against the detector-capable guides applicable to the target project and surfaces findings via hookSpecificOutput.additionalContext, so Claude receives them as part of its own context rather than as a raw stderr error.

mpg check and the PostToolUse hook automatically check only guides with a detector for the target Python/dependency context. The current catalog has 26 detectable guides out of 41; 15 are advisory-only and are not claimed as actively checked. Advisory-only guides: dataclass-modern, dict-merge-operator, django-check-constraints, exception-groups, fastapi-typed-state, httpx-streaming, match-case-patterns, override-decorator, pytest-parametrize, pytest-raises-match, removeprefix-removesuffix, ruff-over-flake8, sqlalchemy-async-session, template-strings, uv-over-pip. A clean automatic check does not certify advisory-only guidance. Use mpg list --format json or MCP metadata to inspect each guide's detection status.

This capability contract is distinct from #152: #181 describes which guides can produce automatic findings after activation, while #152 measures whether an organic development session reaches the catalog at all.

A few things worth knowing about how it behaves:

  • It scans the whole edited file, not just your diff. An edit anywhere in a file can resurface pre-existing outdated patterns you didn't touch this time, not only the lines you just wrote.
  • Findings are capped at 5 per edit, plus a +N more summary line, to bound how much context gets injected.
  • Only the guide ID and line number are surfaced — never the matching source line itself. Echoing arbitrary file content back as an authoritative-looking hook message would be an indirect prompt-injection channel; guide_id + line number is enough for Claude to look up the modern form.
  • The project's target Python version is auto-detected the same way as mpg detect-version (nearest pyproject.toml requires-python / Poetry python constraint, or .python-version, walking up from the edited file), so patterns that require a newer Python than your project targets are not flagged. The resolved target and source are shown in the summary ([target: py3.X; source: SOURCE]).
  • Non-Python files and clean files produce no output.
  • An existing project (Skills/Rules already linked from a previous mpg setup) is never silently opted in — see the --with-hook/--no-hook flags above.

Verify with /hooks in Claude Code to confirm it's active.

For manual CLI use, mpg check --quiet <file> uses the same automatic resolver. Pass --python-version X.Y when you intentionally want an explicit override; the JSON output contains the same top-level target_python object.

Manual hook setup (advanced)

If you'd rather manage the hook entry yourself instead of mpg setup, add it to .claude/settings.local.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/python",
            "args": ["-m", "modern_python_guidance", "hook", "claude-post-tool-use"]
          }
        ]
      }
    ]
  }
}

Use the interpreter's absolute path (command), not a bare mpg/python — Claude Code spawns hooks from its own environment, where a venv-only interpreter is not on PATH. mpg setup resolves and pins this for you automatically.

Development

git clone https://github.com/yottayoshida/modern-python-guidance.git
cd modern-python-guidance
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest

See CONTRIBUTING.md for project structure and guide authoring details.

Versioning

Semantic Versioning. From 1.0 onward the CLI surface, the MCP tool schemas, the JSON output fields, the guide frontmatter schema, the PostToolUse hook contract, and the documented exit-code rows are frozen. docs/VERSIONING.md says what each of those covers, what holds it in place, and what is deliberately left outside the freeze — including which side of each JSON surface is actually compared, and why exit codes are frozen row by row rather than as semantics in general.

License

Apache-2.0 OR MIT — see LICENSE and LICENSE-MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modern_python_guidance-1.2.0.tar.gz (374.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

modern_python_guidance-1.2.0-py3-none-any.whl (147.3 kB view details)

Uploaded Python 3

File details

Details for the file modern_python_guidance-1.2.0.tar.gz.

File metadata

  • Download URL: modern_python_guidance-1.2.0.tar.gz
  • Upload date:
  • Size: 374.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for modern_python_guidance-1.2.0.tar.gz
Algorithm Hash digest
SHA256 6ffa54336455dd2d9a78625322688a06af7b7ea252a20ce6353cc99c3b6a33da
MD5 3ebf62341e545a2ab81ef9cd95f0138e
BLAKE2b-256 5e0a68d76d914b3f56a38f3195ab7e6f976923a6d571db36f3aa02c7805921b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for modern_python_guidance-1.2.0.tar.gz:

Publisher: ci.yml on yottayoshida/modern-python-guidance

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file modern_python_guidance-1.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for modern_python_guidance-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd00c92c8a5b63692eebf21f4ac17d5584c63a610a054b138cbda31aaea6f440
MD5 5a41c62d30098a8032a08ada5c075080
BLAKE2b-256 9198bd2f1a22d6130004ccb8e842cc90740ba1bc0a3b816219e484181cf9d421

See more details on using hashes here.

Provenance

The following attestation bundles were made for modern_python_guidance-1.2.0-py3-none-any.whl:

Publisher: ci.yml on yottayoshida/modern-python-guidance

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.6.0

2 files

0.5.12

2 files

0.5.11

2 files

0.5.10

2 files

0.5.9

2 files

0.5.8

2 files

0.5.7

2 files

0.5.6

2 files

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.8

2 files

0.3.7

2 files

0.3.6

2 files

0.3.5

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page