From bb9a6347b9a089d96f050064057935c4c04f2f3c Mon Sep 17 00:00:00 2001 From: jakelodwick <25925+jakelodwick@users.noreply.github.com> Date: Sat, 14 Mar 2026 05:37:47 -0600 Subject: [PATCH 1/2] Cache prompt hook state to skip redundant pyenv sh-activate calls Track five values between prompts ($PWD, $PYENV_VERSION, .python-version content, $PYENV_ROOT/version content, $VIRTUAL_ENV) using shell builtins. When all match, return immediately without forking. Covers cd, pyenv shell/local/global, and manual venv activate/deactivate. Implemented for bash/zsh/ksh (shared POSIX path) and fish. --- bin/pyenv-virtualenv-init | 40 ++++++++++++++++++++++++++ test/init.bats | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/bin/pyenv-virtualenv-init b/bin/pyenv-virtualenv-init index e12769a8..6061fe72 100755 --- a/bin/pyenv-virtualenv-init +++ b/bin/pyenv-virtualenv-init @@ -106,11 +106,31 @@ fish ) cat </dev/null; or true + end + set -l pvh_global "" + if test -f "\$PYENV_ROOT/version" + read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true + end + if test "\$PWD" = "\$_PYENV_VH_PWD" \\ + -a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\ + -a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\ + -a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\ + -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" + return \$ret + end if [ -n "\$VIRTUAL_ENV" ] pyenv activate --quiet; or pyenv deactivate --quiet; or true else pyenv activate --quiet; or true end + set -g _PYENV_VH_PWD "\$PWD" + set -g _PYENV_VH_VERSION "\$PYENV_VERSION" + set -g _PYENV_VH_LOCAL "\$pvh_local" + set -g _PYENV_VH_GLOBAL "\$pvh_global" + set -g _PYENV_VH_VENV "\$VIRTUAL_ENV" return \$ret end EOS @@ -130,11 +150,31 @@ esac if [[ "$shell" != "fish" ]]; then cat </dev/null || true + fi + local pvh_global="" + if [ -f "\${PYENV_ROOT}/version" ]; then + pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true + fi + if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ + && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ + && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ + && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then + return \$ret + fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true else eval "\$(pyenv sh-activate --quiet || true)" || true fi + _PYENV_VH_PWD="\${PWD}" + _PYENV_VH_VERSION="\${PYENV_VERSION-}" + _PYENV_VH_LOCAL="\${pvh_local}" + _PYENV_VH_GLOBAL="\${pvh_global}" + _PYENV_VH_VENV="\${VIRTUAL_ENV-}" return \$ret }; EOS diff --git a/test/init.bats b/test/init.bats index 85f2b423..7c0e6100 100644 --- a/test/init.bats +++ b/test/init.bats @@ -54,11 +54,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}"; export PYENV_VIRTUALENV_INIT=1; _pyenv_virtualenv_hook() { local ret=\$? + local pvh_local="" + if [ -f "\${PWD}/.python-version" ]; then + pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true + fi + local pvh_global="" + if [ -f "\${PYENV_ROOT}/version" ]; then + pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true + fi + if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ + && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ + && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ + && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then + return \$ret + fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true else eval "\$(pyenv sh-activate --quiet || true)" || true fi + _PYENV_VH_PWD="\${PWD}" + _PYENV_VH_VERSION="\${PYENV_VERSION-}" + _PYENV_VH_LOCAL="\${pvh_local}" + _PYENV_VH_GLOBAL="\${pvh_global}" + _PYENV_VH_VENV="\${VIRTUAL_ENV-}" return \$ret }; if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then @@ -78,11 +98,31 @@ set -gx PATH '${TMP}/pyenv/plugins/pyenv-virtualenv/shims' \$PATH; set -gx PYENV_VIRTUALENV_INIT 1; function _pyenv_virtualenv_hook --on-event fish_prompt; set -l ret \$status + set -l pvh_local "" + if test -f "\$PWD/.python-version" + read -z pvh_local < "\$PWD/.python-version" 2>/dev/null; or true + end + set -l pvh_global "" + if test -f "\$PYENV_ROOT/version" + read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true + end + if test "\$PWD" = "\$_PYENV_VH_PWD" \\ + -a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\ + -a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\ + -a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\ + -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" + return \$ret + end if [ -n "\$VIRTUAL_ENV" ] pyenv activate --quiet; or pyenv deactivate --quiet; or true else pyenv activate --quiet; or true end + set -g _PYENV_VH_PWD "\$PWD" + set -g _PYENV_VH_VERSION "\$PYENV_VERSION" + set -g _PYENV_VH_LOCAL "\$pvh_local" + set -g _PYENV_VH_GLOBAL "\$pvh_global" + set -g _PYENV_VH_VENV "\$VIRTUAL_ENV" return \$ret end EOS @@ -97,11 +137,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}"; export PYENV_VIRTUALENV_INIT=1; _pyenv_virtualenv_hook() { local ret=\$? + local pvh_local="" + if [ -f "\${PWD}/.python-version" ]; then + pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true + fi + local pvh_global="" + if [ -f "\${PYENV_ROOT}/version" ]; then + pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true + fi + if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ + && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ + && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ + && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then + return \$ret + fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true else eval "\$(pyenv sh-activate --quiet || true)" || true fi + _PYENV_VH_PWD="\${PWD}" + _PYENV_VH_VERSION="\${PYENV_VERSION-}" + _PYENV_VH_LOCAL="\${pvh_local}" + _PYENV_VH_GLOBAL="\${pvh_global}" + _PYENV_VH_VENV="\${VIRTUAL_ENV-}" return \$ret }; typeset -g -a precmd_functions From 7ca917c015a6ca67153e19b7d654f75abda918fe Mon Sep 17 00:00:00 2001 From: jakelodwick <25925+jakelodwick@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:41:31 -0600 Subject: [PATCH 2/2] Revise hook cache: mtime-based directory walk Replace content reads ($(< .python-version), which fork a subshell in bash) with test -nt against a per-session marker file (shell builtin, zero forks). Walk the full directory tree from $PWD to / checking .python-version at each level, matching pyenv's "closest wins" resolution order. Check directory mtimes to detect file creation/deletion. Check $PYENV_ROOT/version only when no local .python-version exists anywhere in the tree. Skip all disk checks when $PYENV_VERSION is set (shell priority). --- bin/pyenv-virtualenv-init | 78 ++++++++++++++++--------- test/init.bats | 117 ++++++++++++++++++++++++-------------- 2 files changed, 125 insertions(+), 70 deletions(-) diff --git a/bin/pyenv-virtualenv-init b/bin/pyenv-virtualenv-init index 6061fe72..2685f03c 100755 --- a/bin/pyenv-virtualenv-init +++ b/bin/pyenv-virtualenv-init @@ -106,20 +106,31 @@ fish ) cat </dev/null; or true - end - set -l pvh_global "" - if test -f "\$PYENV_ROOT/version" - read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true - end if test "\$PWD" = "\$_PYENV_VH_PWD" \\ -a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\ - -a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\ - -a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\ - -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" - return \$ret + -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" \\ + -a -f "\$_PYENV_VH_MARKER" + if test -n "\$PYENV_VERSION" + return \$ret + end + set -l d "\$PWD" + set -l stale 0 + set -l found 0 + while true + if test -f "\$d/.python-version" + set found 1 + test "\$d/.python-version" -nt "\$_PYENV_VH_MARKER"; and set stale 1 + break + end + test "\$d" -nt "\$_PYENV_VH_MARKER"; and begin; set stale 1; break; end + test "\$d" = "/"; and break + set d (string replace -r '/[^/]*\$' '' -- "\$d") + test -z "\$d"; and set d "/" + end + if test \$stale = 0 -a \$found = 0 + test -f "\$PYENV_ROOT/version"; and test "\$PYENV_ROOT/version" -nt "\$_PYENV_VH_MARKER"; and set stale 1 + end + test \$stale = 0; and return \$ret end if [ -n "\$VIRTUAL_ENV" ] pyenv activate --quiet; or pyenv deactivate --quiet; or true @@ -128,9 +139,9 @@ function _pyenv_virtualenv_hook --on-event fish_prompt; end set -g _PYENV_VH_PWD "\$PWD" set -g _PYENV_VH_VERSION "\$PYENV_VERSION" - set -g _PYENV_VH_LOCAL "\$pvh_local" - set -g _PYENV_VH_GLOBAL "\$pvh_global" set -g _PYENV_VH_VENV "\$VIRTUAL_ENV" + set -g _PYENV_VH_MARKER "\$PYENV_ROOT/.pyenv-vh-marker-\$fish_pid" + command touch "\$_PYENV_VH_MARKER" return \$ret end EOS @@ -150,20 +161,31 @@ esac if [[ "$shell" != "fish" ]]; then cat </dev/null || true - fi - local pvh_global="" - if [ -f "\${PYENV_ROOT}/version" ]; then - pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true - fi if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ - && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ - && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ - && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then - return \$ret + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\ + && [ -f "\${_PYENV_VH_MARKER-}" ]; then + if [ -n "\${PYENV_VERSION-}" ]; then + return \$ret + fi + local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0 + while :; do + if [ -f "\${_pvh_d}/.python-version" ]; then + _pvh_found=1 + [ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1 + break + fi + [ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; } + [ "\${_pvh_d}" = "/" ] && break + _pvh_d="\${_pvh_d%/*}" + [ -z "\${_pvh_d}" ] && _pvh_d="/" + done + if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then + [ -f "\${PYENV_ROOT}/version" ] \\ + && [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\ + && _pvh_stale=1 + fi + [ "\${_pvh_stale}" = 0 ] && return \$ret fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true @@ -172,9 +194,9 @@ if [[ "$shell" != "fish" ]]; then fi _PYENV_VH_PWD="\${PWD}" _PYENV_VH_VERSION="\${PYENV_VERSION-}" - _PYENV_VH_LOCAL="\${pvh_local}" - _PYENV_VH_GLOBAL="\${pvh_global}" _PYENV_VH_VENV="\${VIRTUAL_ENV-}" + _PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$" + : > "\${_PYENV_VH_MARKER}" return \$ret }; EOS diff --git a/test/init.bats b/test/init.bats index 7c0e6100..9446c6ae 100644 --- a/test/init.bats +++ b/test/init.bats @@ -54,20 +54,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}"; export PYENV_VIRTUALENV_INIT=1; _pyenv_virtualenv_hook() { local ret=\$? - local pvh_local="" - if [ -f "\${PWD}/.python-version" ]; then - pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true - fi - local pvh_global="" - if [ -f "\${PYENV_ROOT}/version" ]; then - pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true - fi if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ - && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ - && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ - && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then - return \$ret + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\ + && [ -f "\${_PYENV_VH_MARKER-}" ]; then + if [ -n "\${PYENV_VERSION-}" ]; then + return \$ret + fi + local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0 + while :; do + if [ -f "\${_pvh_d}/.python-version" ]; then + _pvh_found=1 + [ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1 + break + fi + [ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; } + [ "\${_pvh_d}" = "/" ] && break + _pvh_d="\${_pvh_d%/*}" + [ -z "\${_pvh_d}" ] && _pvh_d="/" + done + if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then + [ -f "\${PYENV_ROOT}/version" ] \\ + && [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\ + && _pvh_stale=1 + fi + [ "\${_pvh_stale}" = 0 ] && return \$ret fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true @@ -76,9 +87,9 @@ _pyenv_virtualenv_hook() { fi _PYENV_VH_PWD="\${PWD}" _PYENV_VH_VERSION="\${PYENV_VERSION-}" - _PYENV_VH_LOCAL="\${pvh_local}" - _PYENV_VH_GLOBAL="\${pvh_global}" _PYENV_VH_VENV="\${VIRTUAL_ENV-}" + _PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$" + : > "\${_PYENV_VH_MARKER}" return \$ret }; if ! [[ "\${PROMPT_COMMAND-}" =~ _pyenv_virtualenv_hook ]]; then @@ -98,20 +109,31 @@ set -gx PATH '${TMP}/pyenv/plugins/pyenv-virtualenv/shims' \$PATH; set -gx PYENV_VIRTUALENV_INIT 1; function _pyenv_virtualenv_hook --on-event fish_prompt; set -l ret \$status - set -l pvh_local "" - if test -f "\$PWD/.python-version" - read -z pvh_local < "\$PWD/.python-version" 2>/dev/null; or true - end - set -l pvh_global "" - if test -f "\$PYENV_ROOT/version" - read -z pvh_global < "\$PYENV_ROOT/version" 2>/dev/null; or true - end if test "\$PWD" = "\$_PYENV_VH_PWD" \\ -a "\$PYENV_VERSION" = "\$_PYENV_VH_VERSION" \\ - -a "\$pvh_local" = "\$_PYENV_VH_LOCAL" \\ - -a "\$pvh_global" = "\$_PYENV_VH_GLOBAL" \\ - -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" - return \$ret + -a "\$VIRTUAL_ENV" = "\$_PYENV_VH_VENV" \\ + -a -f "\$_PYENV_VH_MARKER" + if test -n "\$PYENV_VERSION" + return \$ret + end + set -l d "\$PWD" + set -l stale 0 + set -l found 0 + while true + if test -f "\$d/.python-version" + set found 1 + test "\$d/.python-version" -nt "\$_PYENV_VH_MARKER"; and set stale 1 + break + end + test "\$d" -nt "\$_PYENV_VH_MARKER"; and begin; set stale 1; break; end + test "\$d" = "/"; and break + set d (string replace -r '/[^/]*\$' '' -- "\$d") + test -z "\$d"; and set d "/" + end + if test \$stale = 0 -a \$found = 0 + test -f "\$PYENV_ROOT/version"; and test "\$PYENV_ROOT/version" -nt "\$_PYENV_VH_MARKER"; and set stale 1 + end + test \$stale = 0; and return \$ret end if [ -n "\$VIRTUAL_ENV" ] pyenv activate --quiet; or pyenv deactivate --quiet; or true @@ -120,9 +142,9 @@ function _pyenv_virtualenv_hook --on-event fish_prompt; end set -g _PYENV_VH_PWD "\$PWD" set -g _PYENV_VH_VERSION "\$PYENV_VERSION" - set -g _PYENV_VH_LOCAL "\$pvh_local" - set -g _PYENV_VH_GLOBAL "\$pvh_global" set -g _PYENV_VH_VENV "\$VIRTUAL_ENV" + set -g _PYENV_VH_MARKER "\$PYENV_ROOT/.pyenv-vh-marker-\$fish_pid" + command touch "\$_PYENV_VH_MARKER" return \$ret end EOS @@ -137,20 +159,31 @@ export PATH="${TMP}/pyenv/plugins/pyenv-virtualenv/shims:\${PATH}"; export PYENV_VIRTUALENV_INIT=1; _pyenv_virtualenv_hook() { local ret=\$? - local pvh_local="" - if [ -f "\${PWD}/.python-version" ]; then - pvh_local=\$(< "\${PWD}/.python-version") 2>/dev/null || true - fi - local pvh_global="" - if [ -f "\${PYENV_ROOT}/version" ]; then - pvh_global=\$(< "\${PYENV_ROOT}/version") 2>/dev/null || true - fi if [ "\${PWD}" = "\${_PYENV_VH_PWD-}" ] \\ && [ "\${PYENV_VERSION-}" = "\${_PYENV_VH_VERSION-}" ] \\ - && [ "\${pvh_local}" = "\${_PYENV_VH_LOCAL-}" ] \\ - && [ "\${pvh_global}" = "\${_PYENV_VH_GLOBAL-}" ] \\ - && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ]; then - return \$ret + && [ "\${VIRTUAL_ENV-}" = "\${_PYENV_VH_VENV-}" ] \\ + && [ -f "\${_PYENV_VH_MARKER-}" ]; then + if [ -n "\${PYENV_VERSION-}" ]; then + return \$ret + fi + local _pvh_d="\${PWD}" _pvh_stale=0 _pvh_found=0 + while :; do + if [ -f "\${_pvh_d}/.python-version" ]; then + _pvh_found=1 + [ "\${_pvh_d}/.python-version" -nt "\${_PYENV_VH_MARKER}" ] && _pvh_stale=1 + break + fi + [ "\${_pvh_d}" -nt "\${_PYENV_VH_MARKER}" ] && { _pvh_stale=1; break; } + [ "\${_pvh_d}" = "/" ] && break + _pvh_d="\${_pvh_d%/*}" + [ -z "\${_pvh_d}" ] && _pvh_d="/" + done + if [ "\${_pvh_stale}" = 0 ] && [ "\${_pvh_found}" = 0 ]; then + [ -f "\${PYENV_ROOT}/version" ] \\ + && [ "\${PYENV_ROOT}/version" -nt "\${_PYENV_VH_MARKER}" ] \\ + && _pvh_stale=1 + fi + [ "\${_pvh_stale}" = 0 ] && return \$ret fi if [ -n "\${VIRTUAL_ENV-}" ]; then eval "\$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true @@ -159,9 +192,9 @@ _pyenv_virtualenv_hook() { fi _PYENV_VH_PWD="\${PWD}" _PYENV_VH_VERSION="\${PYENV_VERSION-}" - _PYENV_VH_LOCAL="\${pvh_local}" - _PYENV_VH_GLOBAL="\${pvh_global}" _PYENV_VH_VENV="\${VIRTUAL_ENV-}" + _PYENV_VH_MARKER="\${PYENV_ROOT}/.pyenv-vh-marker-\$\$" + : > "\${_PYENV_VH_MARKER}" return \$ret }; typeset -g -a precmd_functions