Skip to content

feat(python): match the Python version of a project's .venv - #292

Merged
CalvinAllen merged 1 commit into
mainfrom
feat/python/venv-version
Oct 22, 2026
Merged

CalvinAllen merged 1 commit into
mainfrom
feat/python/venv-version

Conversation

@CalvinAllen

Copy link
Copy Markdown
Contributor

Description

In a project that has a .venv but no version file, such as agent_c_framework (a uv workspace whose .venv was built from dtvem's Python 3.12.12), a bare python ran the global version. That matched the venv only while the global happened to be the venv's version. dtvem now reads the venv's pyvenv.cfg and uses the version it was built from.

It picks the version only: dtvem runs its own install of that version, not the venv's interpreter. The project's packages are still available only through an activated venv or uv run, both of which bypass dtvem's shims as before. Running the venv itself was the other option and is left as a possible follow-up.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement
  • CI/CD or build process change

Related Issues

Resolves #289
Relates to #284, #287

Changes Made

  • Precedence: same upward walk; the nearest directory wins. Within one directory the order is .dtvem/runtimes.json, then .tool-versions, then .python-version, and only then .venv/pyvenv.cfg. A venv is inferred, so every declared pin in the same directory beats it. The global version stays the fallback.

  • Reading the version: the version key (python -m venv), else version_info (uv). dtvem keeps the leading major.minor[.patch], so 3.12.1.final.0 → 3.12.1 and 3.13.0rc1 → 3.13.0.

  • Matching: the exact version if installed, otherwise the highest installed version with the same major.minor. A venv's compiled packages depend on the minor version, so a venv built from a system or uv-managed 3.12.7 runs on dtvem's 3.12.12 rather than failing.

  • Not installed: if nothing installed shares the major.minor, the shim reports Python 3.11 is configured but not installed, names .venv\pyvenv.cfg as the source, and suggests dtvem install python 3.11.

  • Passed over:

    • a .venv without pyvenv.cfg (not a virtual environment)
    • a .venv that is a file (some tools use one to name a virtualenv)
    • a .venv in the home directory

    The home-directory rule isn't in the issue; I added it. The upward walk passes through $HOME, and a ~/.venv (e.g. python -m venv ~/.venv for personal tools) is a user-wide environment, not a project's. Without the rule it would silently set the Python version for everything under the home directory, unlike ~/.python-version, which a user writes on purpose as a default.

  • Reported: a pyvenv.cfg with no usable version, like an empty version file, and a .venv that exists but can't be read.

  • Code: internal/config/venv.go holds the venv handling. versionFileRules gains venv, set only for Python.

  • Docs: README feature line, dtvem current --help, and the CLAUDE.md "Version Resolution" section.

Testing

Test Environment

  • OS: Windows 11
  • Go Version: 1.25.4 (module targets 1.23)
  • dtvem Version: built from this branch; resolved against a real 0.6.5 install with Python 3.8.9, 3.9.9 and 3.12.12 installed, 3.12.12 global

Test Steps

  1. ./rnr check: gofmt, golangci-lint (0 issues), and all tests pass.
  2. New src/internal/config/venv_test.go:
    • pyvenv.cfg parsing: python -m venv and uv formats, version over version_info, release-level and pre-release suffixes, spacing, CRLF, major.minor only.
    • Unusable values: empty, no key, empty value, non-version, major only.
    • Resolution: exact, other patch → highest of the minor, missing minor → major.minor, from a subdirectory.
    • Precedence: each declared pin beats .venv in one directory, and a nearer .venv beats a parent .python-version.
    • Passed over: no pyvenv.cfg, .venv as a file, .venv in $HOME, other runtimes.
    • A pyvenv.cfg with no version is reported.
  3. End-to-end: built the CLI and shim into a temp directory, put the shim there as python.exe, and ran it with the venv not activated, against real venvs and the real install.

Test Results

Project python (venv not activated)
venv created by uv venv from 3.8.9, run from src/pkg 3.8.9 (global is 3.12.12)
venv created by uv venv from 3.9.9 (version_info = 3.9.9) 3.9.9
pyvenv.cfg version = 3.9.4 (not installed) 3.9.9, same minor
pyvenv.cfg version = 3.11.9 (no 3.11 installed) "Python 3.11 is configured but not installed", Set by: .venv\pyvenv.cfg, dtvem install python 3.11, exit 1
3.8.9 venv + .python-version 3.9 in the same folder 3.9.9
C:\code\agent_c_framework (read-only check; venv made by python -m venv from 3.12.12) 3.12.12; dtvem current python shows …\agent_c_framework\.venv\pyvenv.cfg as the Source

Checklist

  • My code follows the Google Go Style Guide
  • I have run golangci-lint run and fixed all issues (includes gofmt, govet, and 40+ linters)
  • I have run go mod tidy to clean up dependencies (no dependency changes in this PR)
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (go test ./...)
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation (README.md, CONTRIBUTING.md, etc.) if needed
  • My changes generate no new warnings or errors
  • I have checked my code works on multiple platforms (Windows, macOS, Linux) if applicable (tested on Windows; CI covers macOS and Linux)
  • I have added appropriate error handling
  • I have followed conventional commit format in my commit messages

Breaking Changes

None intended. In a project whose .venv was built from a Python minor version dtvem doesn't have, python now stops with an install hint instead of running the global version.

Additional Notes

  • Out of scope, as the issue lists: running the venv's own interpreter (auto-activation), other venv locations (venv/, env/, Poetry/pipenv central venvs), and VIRTUAL_ENV.
  • Unrelated issue found while testing: dtvem's Windows installs of Python 3.8.9 and 3.9.9 are the CPython embeddable package (python38.zip + python38._pth, no Lib\venv), so python -m venv fails on them with No module named venv. 3.12.12 is a full build. That's an install/mirror issue (possibly related to pip installation fails for older Python versions (3.8, 3.7) #113), not something this PR touches; the end-to-end test built those venvs with uv instead.

In a project with a .venv but no version file, a bare python ran the
global version, which matched the venv only while the global happened to
be the version the venv was built from. Version resolution now reads the
venv's pyvenv.cfg and uses that version.

- It is the last local source for Python in a directory: after
  .dtvem/runtimes.json, .tool-versions and .python-version, since a venv
  is inferred rather than a declared pin. The nearest directory still
  wins.
- The version comes from pyvenv.cfg's version key (python -m venv), else
  version_info (uv), keeping the leading major.minor[.patch].
- dtvem runs its own install of that version, not the venv's interpreter.
  It prefers the exact version, else the highest installed with the same
  major.minor, since compiled packages depend on the minor version. With
  none installed, the major.minor is reported as not installed.
- A .venv without pyvenv.cfg, a .venv that is a file, and a .venv in the
  home directory, which is a user-wide environment rather than a
  project's, are passed over. A pyvenv.cfg without a usable version is
  reported.
@CalvinAllen
CalvinAllen merged commit 9a30d25 into main Oct 22, 2026
11 checks passed
@CalvinAllen
CalvinAllen deleted the feat/python/venv-version branch October 22, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

feat(python): match the Python version of a project's .venv

1 participant