Skip to content
native-api edited this page Jun 19, 2025 · 2 revisions

pyenv plugins provide new commands and/or hook into existing functionality of pyenv. The following file naming scheme should be followed in a plugin project:

  1. bin/pyenv-COMMAND for commands
  2. etc/pyenv.d/HOOK_NAME/*.bash for hooks

pyenv commands

An pyenv command is an executable named like pyenv-COMMAND. It will get executed when a user runs pyenv COMMAND. Its help will be displayed when a user runs pyenv help COMMAND. It can be written in any interpreted language, but bash script is recommended for portability.

A plugin command can't override any of the pyenv's built-in commands.

Environment

Each pyenv command runs with the following environment:

  • $PYENV_ROOT - where pyenv versions & user data is placed, typically ~/.pyenv
  • $PYENV_DIR - the current directory of the caller
  • $PATH - constructed to contain:
    1. pyenv's libexec dir with core commands
    2. $PYENV_ROOT/plugins/*/bin for plugin commands
    3. $PATH (external value)

Calling other commands

When calling other commands from a command, use the pyenv-COMMAND form (with dash) instead of pyenv COMMAND (with space).

Use pyenv's core low-level commands to inspect the environment instead of doing it manually. For example, read the result of pyenv-prefix instead of constructing it like $PYENV_ROOT/versions/$version.

A plugin command shouldn't have too much knowledge of pyenv's internals.

Help text

An pyenv command should provide help text in the topmost comment of its source code. The help format is described in pyenv help help.

Here is a template for an executable called pyenv-COMMAND:

#!/usr/bin/env bash
#
# Summary: One line, short description of a command
#
# Usage: pyenv COMMAND [--optional-flag] <required-argument>
#
# More thorough help text wrapped at 70 characters that spans
# multiple lines until the end of the comment block.

set -e
[ -n "$PYENV_DEBUG" ] && set -x

# Optional: Abort with usage line when called with invalid arguments
# (replace COMMAND with the name of this command)
if [ -z "$1" ]; then
  pyenv-help --usage COMMAND >&2
  exit 1
fi

Completions

A command can optionally provide tab-completions in the shell by outputting completion values when invoked with the --complete flag.

# Provide pyenv completions
if [ "$1" = "--complete" ]; then
  echo hello
  exit
fi

Note: it's important to keep the above comment intact. This is how pyenv detects if a command is capable of providing completion values.

In-shell commands

Executables named pyenv-sh-COMMAND are invoked by the pyenv shell function when the user runs pyenv COMMAND with Pyenv shell integration enabled.

Unlike regular commands, in-shell commands need to print the code that would be executed in the user's shell to the standard output. This code will most likely depend on which type of shell the user has (the $shell variable).

pyenv hooks

Hooks are bash scripts named like HOOK_NAME/*.bash, where "HOOK_NAME" is the Pyenv subcommand that you are hooking into (more specifically, the argument that the subcommand passes to pyenv-hooks in its source code). E.g. as of this writing, Pyenv core commands that support hooks are:

  • exec
  • rehash
  • which

And the Python-Build plugin supports hooks for:

  • `install
  • uninstall

Generally, you can search subcommands for pyenv-hooks calls to quickly locate those that support hooks, e.g.: find "$(pyenv root)" -type f -name 'pyenv-*' -exec grep -HEe "pyenv-hooks [[:alnum:]]" {} +

Hooks are looked for in $PYENV_HOOK_PATH, which is composed of:

  1. $PYENV_HOOK_PATH (external value)
  2. $PYENV_ROOT/pyenv.d
  3. /usr/local/etc/pyenv.d
  4. /etc/pyenv.d
  5. /usr/lib/pyenv/hooks
  6. $PYENV_ROOT/plugins/*/etc/pyenv.d

Hook scripts are executed at specific points during pyenv operation. They provide a low-level entry point for integration with pyenv's functionality. To get a better understanding of the possibilities with hooks, read the source code of pyenv's hook-enabled commands listed above.

Many subcommands that support hooks define specific shell functions that hook scripts can call to install additional functions to be called at certain points during the subcommand's execution. This is usually done with boilerplate code so e.g. in the core and official plugins, you can generally locate such code by searching for ""$hook"" and looking at the surrounding code, e.g.: find "$(pyenv root)" -type f -name 'pyenv-*' -exec grep -C3 -HEe '"\$hook"' {} +

Example:

$(pyenv root)/plugins/my_cool_plugin/etc/pyenv.d/install.bash:

echo "My cool plugin hook running!"

my_cool_plugin_before_install() {
  echo "About to install: $DEFINITION"
}

my_cool_plugin_after_install() {
  echo "Installed: $DEFINITION"
}

echo "My cool plugin hook setting up additional functions to be called later"

before_install my_cool_plugin_before_install
after_install my_cool_plugin_after_install

echo "My cool plugin hook completed!"

Hooking in-shell subcommands

Like in-shell subcommands themselves (see above), hooks for them also need to print the code that would be executed in the user's shell.

Clone this wiki locally