Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@
},
{
"name": "modern-python",
"version": "1.5.2",
"version": "1.5.3",
"description": "Modern Python best practices. Use when creating new Python projects, and writing Python scripts, or migrating existing projects from legacy tools.",
"author": {
"name": "William Tan",
Expand Down
2 changes: 1 addition & 1 deletion plugins/modern-python/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "modern-python",
"version": "1.5.2",
"version": "1.5.3",
"description": "Modern Python best practices. Use when creating new Python projects, and writing Python scripts, or migrating existing projects from legacy tools.",
"author": {
"name": "William Tan",
Expand Down
2 changes: 1 addition & 1 deletion plugins/modern-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Modern Python tooling and best practices using uv, ruff, ty, and pytest. Based o

## Hook: Legacy Command Interception

This plugin includes a `SessionStart` hook that prepends PATH shims for `python`, `pip`, `pipx`, and `uv`. When Claude runs a bare `python`, `pip`, or `pipx` command, the shell resolves to the shim, which prints an error with the correct `uv` alternative and exits non-zero. `uv run` is unaffected because it prepends its managed virtualenv's `bin/` to PATH, shadowing the shims.
This plugin includes a `SessionStart` hook that prepends PATH shims for `python`, `pip`, `pipx`, and `uv`. When Claude runs a bare `python`, `pip`, or `pipx` command, the shell resolves to the shim, which prints an error with the correct `uv` alternative and exits non-zero. The suggested alternative always uses the exact command name `python` (never `python3`) so it also works outside a project; see the header comment in [`hooks/shims/python`](hooks/shims/python) for the full rationale.

| Intercepted Command | Suggested Alternative |
|---------------------|----------------------|
Expand Down
4 changes: 2 additions & 2 deletions plugins/modern-python/hooks/setup-shims.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ set -euo pipefail
# SessionStart hook: prepend shims directory to PATH so that bare
# python/pip/pipx/uv-pip invocations are intercepted with uv suggestions.
#
# `uv run` is unaffected because it prepends its managed virtualenv's
# bin/ to PATH, shadowing the shims.
# The suggested `uv run python ...` commands are unaffected by the shims;
# see the header comment in shims/python for why.

# Guard: only activate when uv is available
command -v uv &>/dev/null || exit 0
Expand Down
23 changes: 21 additions & 2 deletions plugins/modern-python/hooks/shims/python
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ set -euo pipefail
# suggests the uv equivalent. Works for both names via $0.
cmd="$(basename "$0")"

# Canonical rationale for the suggestion's shape (the README,
# setup-shims.sh, and python-shim.bats point here):
#
# Suggestions always use the exact name `python`, never `python3`: uv
# special-cases the `python` command (uv >= 0.4.0) and executes its resolved
# interpreter directly instead of a PATH lookup, so the suggested command
# works even outside a project, where `uv run python3` would resolve back
# to this shim.
#
# Arguments are requoted with %q so the suggestion stays runnable when they
# contain spaces or shell metacharacters (e.g. -c 'print(1+1)').
args=""
if (($#)); then
args="$(printf ' %q' "$@")"
fi

case "${1:-}" in
-m)
case "${2:-}" in
Expand All @@ -14,12 +30,15 @@ case "${1:-}" in
echo " uv remove <package> # remove a dependency" >&2
;;
*)
echo "ERROR: Use \`uv run $cmd -m ${2:-<module>}\` instead of \`$cmd -m ${2:-<module>}\`" >&2
if (($# < 2)); then
args=" -m <module>"
fi
echo "ERROR: Use \`uv run python$args\` instead of \`$cmd$args\`" >&2
;;
esac
;;
*)
echo "ERROR: Use \`uv run $cmd ${*:-}\` instead of \`$cmd ${*:-}\`" >&2
echo "ERROR: Use \`uv run python$args\` instead of \`$cmd$args\`" >&2
;;
esac

Expand Down
40 changes: 39 additions & 1 deletion plugins/modern-python/hooks/shims/python-shim.bats
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,45 @@ SHIM="${BATS_TEST_DIRNAME}/python"
@test "works when invoked as python3 via symlink" {
run "${BATS_TEST_DIRNAME}/python3"
[[ $status -ne 0 ]]
[[ "$output" == *"uv run python3"* ]]
[[ "$output" == *'instead of `python3'* ]]
}

# The suggestion must use the exact name `python`, never `python3`; see
# the header comment in ./python for the full rationale.
@test "suggests exact 'uv run python', not python3, when invoked as python3" {
run "${BATS_TEST_DIRNAME}/python3" script.py
[[ $status -ne 0 ]]
[[ "$output" == *"Use \`uv run python script.py\`"* ]]
[[ "$output" != *"uv run python3"* ]]
}

@test "suggests exact 'uv run python -m', not python3, for modules" {
run "${BATS_TEST_DIRNAME}/python3" -m http.server
[[ $status -ne 0 ]]
[[ "$output" == *"Use \`uv run python -m http.server\`"* ]]
[[ "$output" != *"uv run python3"* ]]
}

@test "-m suggestion preserves arguments after the module" {
run "${BATS_TEST_DIRNAME}/python3" -m http.server 8000
[[ $status -ne 0 ]]
[[ "$output" == *"Use \`uv run python -m http.server 8000\` instead of \`python3 -m http.server 8000\`"* ]]
}

# %q output can differ across bash versions, so build the expectation with
# the same requoting the shim uses, after checking it actually escapes.
@test "suggestion requotes -c code so it stays copy-paste runnable" {
run "$SHIM" -c 'print(1+1)'
[[ $status -ne 0 ]]
quoted="$(printf '%q' 'print(1+1)')"
[[ "$quoted" != 'print(1+1)' ]]
[[ "$output" == *"Use \`uv run python -c $quoted\`"* ]]
}

@test "bare invocation suggests uv run python without trailing space" {
run "$SHIM"
[[ $status -ne 0 ]]
[[ "$output" == *"Use \`uv run python\` instead of \`python\`"* ]]
}

@test "python3 -m pip suggests uv add" {
Expand Down
Loading