PEP: 838
Title: Adding python-version to pyvenv.cfg
Author: Konstantin Schütze <konstin@mailbox.org>
Sponsor: Alex Waygood <alex.waygood@gmail.com>
Discussions-To: Pending
Status: Draft
Type: Standards Track
Created: 15-Jul-2026
Python-Version: 3.16
Post-History: Pending


Abstract
========

This PEP proposes adding a ``python-version`` field to ``pyvenv.cfg``
which records the Python interpreter used by a virtual environment.
It records only the major and minor version to be resilient to patch
version updates.


Motivation
==========

``pyvenv.cfg``, as defined by :pep:`405`, only specifies the ``home``
and ``include-system-site-packages`` keys. Different tools record different
information about the Python version of a virtual environment. For a virtual
environment created with Python 3.14.4 final:

- CPython's :mod:`venv` module adds a ``version`` field as
  ``'%d.%d.%d' % sys.version_info[:3]``
  (`CPython source <https://github.com/python/cpython/blob/bc235304dfe5f018a87e1d73ca1ad2912f4ab999/Lib/venv/__init__.py#L234>`__).
  Example: ``version = 3.14.4``.
- The ``virtualenv`` package adds ``version_info`` and ``version`` fields as
  ``".".join(str(i) for i in sys.version_info)`` and
  ``".".join(str(i) for i in sys.version_info[:3])``
  (`virtualenv source <https://github.com/pypa/virtualenv/blob/21.5.1/src/virtualenv/create/creator.py#L222-L227>`__).
  Example: ``version_info = 3.14.4.final.0`` and ``version = 3.14.4``.
- uv adds a ``version_info`` field based on
  ``platform.python_version()``
  (`uv source <https://github.com/astral-sh/uv/blob/7bbc01af54e8fd1d3c61d05b65f233574f7466da/crates/uv-virtualenv/src/virtualenv.rs#L531>`__).
  Example: ``version_info = 3.14.4``.

This information is used by a variety of tools, see :ref:`pep838-appendix`.
Some of them want to present the full Python version to the user for
identification, while others only need the major and minor version of Python.

One problem is the different definitions of ``version`` and
``version_info``, and the lack of guarantees for downstream tools. There is
no guarantee that either field is present, nor is either field's granularity
defined.

Another problem is that the Python interpreter underneath the virtual
environment may change. Linux distributions routinely update CPython patch
versions as part of regular updates within a single distribution version. uv
supports `upgrading existing Python installations <https://docs.astral.sh/uv/guides/install-python/#upgrading-python-versions>`__,
upgrading virtual environments using the interpreter in the process. A
``version_info`` key in ``pyvenv.cfg`` with a patch version and a potential
prerelease component goes stale this way.

Tools interacting with virtual environments can `fail after a Python
patch-version upgrade <https://github.com/astral-sh/uv/issues/19920>`__ when
their assumptions about version granularity are violated. A standardized field
with only major and minor version avoids this problem.


Specification
=============

A new ``python-version`` field is added to ``pyvenv.cfg``. It contains a
string value with the major and minor version of the Python interpreter, such
as ``3.16``. The value can be obtained with
``f"{sys.version_info[0]}.{sys.version_info[1]}"``. Tools creating virtual
environments MUST write ``python-version`` to ``pyvenv.cfg``. Reading
``version`` or ``version_info`` is discouraged.

A Python interpreter MAY refuse to run from a virtual environment with a
mismatching ``python-version``.


Rationale
=========

The ``python-version`` key is not yet used by any known tool (`GitHub code
search <https://github.com/search?q=path%3A**%2Fpyvenv.cfg%20python-version&type=code>`__),
avoiding breakage in tools that read any of the existing keys. By
specifying only the major and minor version, the value remains fresh even if
the underlying Python interpreter is updated from one patch release to
another.

This PEP does not handle the case of ``python-version`` going stale due to
the underlying Python interpreter being updated to another minor version.
This may happen when upgrading from one Linux distribution version to another.
Such an update breaks any packages using CPython's unstable C API and cannot
be fixed through a different way of recording values in ``pyvenv.cfg``; it can
only be fixed by regenerating the virtual environment, resolving with the new
Python version and installing the appropriate packages. By performing a check
during interpreter startup, we avoid inscrutable errors at import time or at
runtime, and can inform the user about the problem.

To show accurate Python version information to the user, tools can present the
value of ``python-version``, or if they need the full Python version, they can
query the interpreter for ``sys.version_info``, invalidating this information
when the virtual environment interpreter or the underlying base executable
change. This PEP does not require any specific tool behavior, nor does it
disallow existing patterns. Its goal is to provide the required information
for correct, resilient implementations.


Backwards Compatibility
=======================

If ``python-version`` is not available, tools can fall back to the
existing unspecified fields or inspect the Python interpreter. There is no
known existing usage of ``python-version`` in ``pyvenv.cfg``.


How to Teach This
=================

A new specification page for ``pyvenv.cfg`` will be added to the `Python
Packaging User Guide <https://packaging.python.org/>`__, including
documentation for this field. The field is not user-facing.


Reference Implementation
========================

Reference implementations are available for uv, virtualenv and CPython:

- `uv <https://github.com/astral-sh/uv/pull/20569>`__
- `virtualenv <https://github.com/pypa/virtualenv/pull/3193>`__
- :mod:`venv` in :cpython-pr:`154378`
- An optional CPython startup check in :cpython-pr:`154381`


.. _pep838-appendix:

Appendix: Existing Tool Behavior
================================

A non-exhaustive list of how tools parse version values:

- **ty** splits the dotted value and parses only the major and minor
  components, ignoring any remaining components
  (`ty source <https://github.com/astral-sh/ruff/blob/1b011de418f1007a37fbe33f989a47bf99e2aa9f/crates/ty_site_packages/src/lib.rs#L789-L803>`__).
  Ty only needs the major and minor version of Python.
- The **VS Code Python extension** parses both fields and
  separately handles virtualenv-style values such as ``3.9.0.final.0``.
  When both fields are present, it selects the most specific version
  (`VS Code source <https://github.com/microsoft/vscode-python/blob/34348154b5eac877eef60b5b838699c6cbaf58b6/src/client/pythonEnvironments/common/environmentManagers/simplevirtualenvs.ts#L123-L211>`__).
  VS Code presents the full Python version to the user for identification.
- **Microsoft Python Environment Tools** requires at least three numeric
  components for both fields, then parses the first two components
  (`Python Environment Tools source <https://github.com/microsoft/python-environment-tools/blob/89bf1c02290e09413e745cbbb0194bc50491bf0d/crates/pet-core/src/pyvenv_cfg.rs#L10-L162>`__).
  Its uv-specific parser stores only ``version_info``
  (`Python Environment Tools uv source <https://github.com/microsoft/python-environment-tools/blob/89bf1c02290e09413e745cbbb0194bc50491bf0d/crates/pet-uv/src/lib.rs#L27-L60>`__).
  VS Code presents the full Python version to the user for identification.
- **pre-commit** compares ``version_info`` with the ``sys.version_info``
  components joined by dots.
  (`pre-commit source <https://github.com/pre-commit/pre-commit/blob/1553b465fd7ea42321ae0d04d1b41e706b89ae45/pre_commit/languages/python.py#L27-L33>`__,
  `health-check source <https://github.com/pre-commit/pre-commit/blob/1553b465fd7ea42321ae0d04d1b41e706b89ae45/pre_commit/languages/python.py#L175-L211>`__).
  `This caused a failure <https://github.com/astral-sh/uv/issues/19920>`__ where uv and pre-commit disagreed about virtual environment freshness checks.
- **Jute** reads ``version_info``
  (`Jute source <https://github.com/ekzhang/jute/blob/18723a036b843d9efc1d07b326bda5614b2020e7/src-tauri/src/commands/venv.rs#L125-L165>`__).
  Jute presents the full Python version to the user for identification.
- **MediaHarbor** parses the first two components of ``version``
  (`MediaHarbor source <https://github.com/MediaHarbor/mediaharbor/blob/0fe6810d3a348e238e7d5ed87f6c2a9b16510b85/src/core/src/venv_manager.rs#L174-L184>`__).
- **Jac** parses the first two components of ``version``
  (`Jac source <https://github.com/jaseci-labs/jaseci/blob/e7afd1d8253a940bca989b946a1569970f99f719/jac/jaclang/project/impl/dependencies.impl.jac#L437-L453>`__).


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
