Python pyenv

ID

pyenv

Home page

https://github.com/pyenv/pyenv

Version requirement

>= 2.3.13

Platforms

🐧 Linux · 🍎 macOS

Operations

installed · search · install · remove

purl types

pkg:pyenv

CLI name

pyenv

Issues and PRs

📦 manager: pyenv

Source

meta_package_manager/managers/pyenv.toml

pyenv is the Python version manager (https://github.com/pyenv/pyenv).

A package is one installed interpreter, named by the exact token pyenv prints, which is also the token install and uninstall accept. The token is left whole rather than split into an implementation and a version, because the namespace is far too heterogeneous for one pattern to divide: 3.13.0, 3.13.0t, 3.13-dev, 3.14.0rc1, pypy3.11-7.3.20, graalpy-24.1.1, miniconda3-latest and anaconda3-2024.10-1 all coexist, and any mis-split would break the round-trip that makes the id useful.

Virtualenvs are deliberately excluded, which is what --skip-envs does, and the reason is a bug rather than a preference. pyenv uninstall reduces its argument to the part after the last slash, so handing back the very id the listing prints for an environment, 3.12.11/envs/mango, reports success while deleting the top-level mango compatibility symlink instead and orphaning the environment itself. An mpm remove would destroy the wrong object and report it as done. Environments are also created by a plugin’s pyenv virtualenv rather than by pyenv install, so they were never addressable by half the operations here.

Note

--bare is what makes the listing independent of the working directory. pyenv’s default listing marks the selected interpreter with * and names the .python-version file it was selected by, and that selection follows the cwd up the directory tree. Only the marker moves: the set of rows is always the contents of $PYENV_ROOT/versions. --bare drops the marker along with the annotation, the --> alias expansion, and the system pseudo-entry that is not an installable package. Upstream pins this with a test of its own, so no equivalent of haxelib’s forced --global is needed here.

Note

The default listing also writes a warning to stderr when a .python-version somewhere above the working directory names an interpreter that is not installed, which is an ordinary state on a developer’s machine. --bare skips that branch entirely, so nothing spurious reaches mpm’s diagnosis tail.

No outdated, and no upgrade of either kind: pyenv has no in-place upgrade, a newer patch release being a separate install into its own prefix. Its latest subcommand would have to be asked once per package, which a definition cannot do, and it would need a comparison depth pyenv itself never picks: given 3.12.11, nothing says whether the newer version is 3.12.15 or 3.14.7.

No sync: the catalog ships inside pyenv, so install --list is answered offline and refreshing it means upgrading pyenv itself. No cleanup either: rehash regenerates shims rather than reclaiming anything, and uninstall already runs it, while the download cache has no command to purge it.

No escalation: pyenv installs under $PYENV_ROOT, defaulting to ~/.pyenv, and its sources contain no privileged path. Its shell integration is not required either, existing to put shims on $PATH rather than to make these subcommands work.

Nothing here needs color suppression: the only code that emits escapes turns them off when its output is not a terminal, and it runs during an install.

What mpm adds to pyenv

Through mpm, pyenv gains --exact and --extended search, to narrow to exact names or match descriptions.

Bigger still, mpm reaches across every manager at once: mpm installed and mpm outdated cover pyenv alongside every other manager you run in one table, mpm upgrade --all updates them together, and mpm sbom exports the whole machine as one bill of materials.

Every mpm command also gains --dry-run and --plan previews, cross-scheme version comparison and purl identifiers. See manager augmentations for how each one is built.

Your pyenv commands, in mpm

You already know pyenv: each operation maps one-to-one onto mpm, in an interface shared by every manager.

To…

With pyenv

With mpm

List what’s installed

pyenv versions --bare --skip-aliases --skip-envs

mpm --pyenv installed

Search for a package

pyenv install --list

mpm --pyenv search install

Install a package

pyenv install --skip-existing <package_id>

mpm install pkg:pyenv/<package_id>

Remove a package

pyenv uninstall --force <package_id>

mpm remove pkg:pyenv/<package_id>

Prefix any command above with --dry-run to simulate the underlying manager calls without touching the system: the safe way to watch what mpm would do before trusting it.

Operations

Operation

Supported

Notes

installed

outdated

orphans

search

exact and extended search backfilled by mpm

install

upgrade

upgrade_all

remove

sync

cleanup

doctor

Selecting and configuring pyenv

Deselect pyenv for a single run with --no-pyenv, or persist the choice in your configuration:

[mpm]
pyenv = false

Keep it enabled but tune how mpm drives it with a per-manager override:

[mpm.managers.pyenv]
timeout = 900

mpm config-template pyenv prints every overridable attribute as a ready-to-paste block.

Recipes

A few jobs you would otherwise script around pyenv, one mpm command each:

  • Snapshot and clone a machine: mpm --pyenv dump pyenv.toml, then mpm restore pyenv.toml on the next one.

  • Export a compliance SBOM: mpm --pyenv sbom (CycloneDX by default, --spdx for SPDX).

Privilege escalation

mpm runs this manager as the current user and never prepends sudo by default. Flip the policy for its privileged operations with --sudo or the per-manager sudo override.

None of its operations needs root.

See privilege escalation for the full policy.

Cooldown

State of Python pyenv’s release-age gating, from the cooldown support table:

Status: ❌ None

A cooldown only pays off where a compromised release can be withdrawn while the clock runs, and can only be emulated where the registry dates its releases. From the retraction table:

  • Registry: Python interpreter source releases

  • Retraction: None, and no operator to ask: each version is a build recipe shipped inside pyenv naming an upstream tarball, so withdrawing one means either the implementation pulling its own release or a new pyenv dropping the recipe

  • Publish date: ❌ a recipe names a source archive and its checksum, carrying no publication date for a release-age gate to read

With --cooldown set, mpm skips this manager’s install and upgrade operations rather than run them unguarded (fail-closed); --allow-unsupported-managers opts back in.

Version probe

The version is probed by running:

$ pyenv --version
pyenv 2.8.4

and extracted with:

r"^pyenv[ \t]+(?P<version>\d+(?:\.\d+)+)"

Reference traces

Raw native outputs captured in the bundled definition: the reference mpm’s parsers were written against. If you know Python pyenv well and a transcript below looks wrong, or a newer release changed its output format, report it.

$ pyenv versions --bare --skip-aliases --skip-envs
3.11.9
3.12.11
3.13.7
3.14.0rc1
graalpy-24.1.1
miniconda3-latest
pypy3.11-7.3.20
$ pyenv install --list
Available versions:
  2.1.3
  2.2.3
  2.3.7

Feed any of these through mpm and the raw output becomes one uniform table, the same shape for every manager: filter it, project columns, or export it (mpm --pyenv installed --output json, or csv, toml, yaml), each package carrying a purl and a version comparable across managers.

Changelog

  • 7.7.0.dev0 (unreleased)

    • Add the Python pyenv version manager with installed, search, install and remove support, shipped as a bundled definition. Listings go through --bare, which is what makes them independent of the working directory. Virtualenvs are excluded: pyenv uninstall reduces its argument to the part after the last slash, so removing one by the id the listing prints deletes a compatibility symlink and orphans the environment. It declares no outdated or upgrade, pyenv having no in-place interpreter upgrade.