-
-
Notifications
You must be signed in to change notification settings - Fork 549
Comparing changes
Open a pull request
base repository: tox-dev/tox
base: 4.41.0
head repository: tox-dev/tox
compare: 4.42.0
- 19 commits
- 48 files changed
- 2 contributors
Commits on Feb 19, 2026
-
🐛 fix(interrupt): suppress BrokenPipeError during teardown (#3778)
When users interrupt tox with Ctrl-C, excessive tracebacks clutter the output and obscure the actual interrupt handling. The packaging backend process may already be terminated when teardown attempts to send the "_exit" shutdown message, causing BrokenPipeError exceptions that make it appear something went wrong when the interrupt is actually being handled correctly. The fix catches BrokenPipeError alongside SystemExit during backend teardown in the pyproject packaging code. 🐛 Both exceptions indicate the process is being terminated cleanly and don't require error reporting. This approach keeps the existing interrupt flow intact while eliminating noise during cleanup. Users will now see clean interrupt output focused on the actual teardown progress rather than pipe errors from attempting to communicate with already-dead processes.
Configuration menu - View commit details
-
Copy full SHA for 903a4c5 - Browse repository at this point
Copy the full SHA 903a4c5View commit details -
✨ feat(cli): add --no-capture flag for interactive programs (#3777)
Interactive programs like Python REPL, debuggers, and TUI applications need direct terminal access to work correctly. They query console dimensions, set raw mode for keystroke input, and send VT100 escape sequences for cursor control. 🐛 Tox normally pipes stdout/stderr to capture output for logging and result reporting, but this breaks terminal APIs that require real console handles instead of pipe handles. Python 3.13+ REPL crashes on Windows with `WinError 123` when `_pyrepl/windows_console.py` tries to call Win32 APIs that only work on console buffers, and similar failures occur on other platforms when programs try to query terminal properties. The new `--no-capture` (`-i`) flag disables output capture and makes subprocesses inherit parent console handles directly. ✨ This is opt-in because it's mutually exclusive with `--result-json` where output must be captured, and parallel mode where output from multiple environments would interleave. The implementation validates incompatible flag combinations early with clear error messages. Additionally, `tox exec` now always runs with `no_capture` enabled since it's designed for one-off interactive commands, and the flag is hidden from its help output to avoid confusion. The flag works on all platforms since terminal APIs universally require console handles, not just on Windows. Documentation has been added to both the how-to guide for practical usage and the explanation section to clarify why interactive terminal programs need special handling.
Configuration menu - View commit details
-
Copy full SHA for cf64528 - Browse repository at this point
Copy the full SHA cf64528View commit details -
✨ feat(config): add platform-dependent factor support (#3779)
Managing cross-platform projects required creating separate test environments for each platform (like `task-linux`, `task-darwin`, `task-win32`) or using the `platform` configuration option which skips entire environments when the regex doesn't match. Neither approach allowed a single environment to run different commands based on the execution platform. 🔧 This change injects `sys.platform` as an implicit factor available to all environments. Platform values like `linux`, `darwin`, and `win32` become automatically available alongside factors from the environment name, enabling natural conditional configuration that adapts at runtime. Users can now write `linux: pytest` or `win32: mypy` in a single environment without encoding the platform in the environment name. The implementation adds one line to `filter_for_env()` in `src/tox/config/loader/ini/factor.py` to include `sys.platform` in the factor set. This leverages the existing factor filtering infrastructure without requiring changes to how factors are parsed or matched. ✨ Resolves #2092
Configuration menu - View commit details
-
Copy full SHA for 97cf6e6 - Browse repository at this point
Copy the full SHA 97cf6e6View commit details
Commits on Feb 20, 2026
-
✨ feat(env): gracefully skip environments with unavailable runners (#…
…3781) When environments reference unavailable runners (from missing plugins), tox now gracefully skips them instead of failing catastrophically. 🔧 If a user explicitly requests an unavailable environment with `-e`, they get a clear error message indicating which runner is missing and that the plugin may not be installed. Environments discovered from config but not explicitly selected are shown with "NOT AVAILABLE" status without failing the overall run. This enables better plugin dependency management, allowing users to depend on optional plugins without breaking tox for users who haven't installed them. The implementation introduces a `RunnerUnavailable` exception to distinguish missing runners from other skip scenarios, extends `_ToxEnvInfo` to track unavailable environments, and updates the reporting layer to show "NOT AVAILABLE" in yellow (matching the SKIP status level). Success calculations treat unavailable environments like skipped—they don't cause the run to fail. Comprehensive TOML-based tests verify all scenarios: unavailable runners in discovered env_list, explicitly requested environments, mixed available/unavailable environments, and multiple unavailable runners. Fixes #3504
Configuration menu - View commit details
-
Copy full SHA for c82f253 - Browse repository at this point
Copy the full SHA c82f253View commit details -
🐛 fix(env): restore compound factor conditionals (#3782)
PR #3753 fixed section header names from being decomposed into freely combinable factors, preventing `tox -e functional-py312` from silently falling back to `[testenv]` when only `[testenv:functional{-py310}]` was defined. However, it also broke compound factor conditionals like `np-cov: coverage` — running `tox -e py310-np-cov` would fail with "provided environments not found" because the individual factor `cov` (from the compound conditional `np-cov`) was no longer recognized as combinable. 🔍 The fix distinguishes between env names from section headers and env names discovered from factor conditionals. Section header names remain valid only as whole identifiers (preserving the #3753 fix), while factor-conditional env names have their individual factors added to the combinable set. This is done by collecting section-derived env names via `Config.sections()` and treating everything else in `known_envs` (minus `env_list`) as factor-conditional — splitting those into individual factors. Fixes #3780
Configuration menu - View commit details
-
Copy full SHA for e652010 - Browse repository at this point
Copy the full SHA e652010View commit details -
✨ feat(config): add default_base_python config key (#3783)
Environments without a Python factor in their name (e.g. `lint`, `type`, `docs`) currently fall back to `sys.executable` when `base_python` isn't explicitly set. This means the interpreter varies across machines — Ubuntu 22.04 defaults to 3.10, Fedora 37 to 3.11 — harming reproducibility and causing unexpected failures when dependencies don't support the host's Python version. 🔧 Setting `base_python` globally in `[env_run_base]` works around this but conflicts with `pyXY` factor-named environments. The new `default_base_python` key sits at the environment config level and acts as a fallback only when no Python factor is detected and no explicit `base_python` is set. This gives it clear precedence semantics: Python factor > explicit `base_python` > `default_base_python` > `sys.executable`. Placing it in `[env_run_base]` rather than `[tox]` allows per-environment overrides when needed. ```toml [env_run_base] default_base_python = ["python3.10", "python3.9"] ``` ✨ Existing configurations are unaffected since the default remains `sys.executable`. This closes #2846.
Configuration menu - View commit details
-
Copy full SHA for 294a995 - Browse repository at this point
Copy the full SHA 294a995View commit details -
✨ feat(config): add env_site_packages_dir_plat substitution (#3784)
Since virtualenv 20.x, the \`lib64\` directory is no longer symlinked to \`lib\` on Linux distributions like Fedora and RHEL (see [virtualenv#1751](pypa/virtualenv#1751)). This means \`{env_site_packages_dir}\` (which returns purelib) doesn't cover the platform-specific \`lib64/pythonX.Y/site-packages\` path where compiled extensions live. 🔧 The new \`{env_site_packages_dir_plat}\` / \`{envsitepackagesdir_plat}\` substitution exposes the platlib path from virtualenv's \`Describe\` interface. On most platforms (macOS, Windows) this is identical to purelib, but on affected Linux distributions it correctly resolves to the \`lib64\` directory. Closes #2302.
Configuration menu - View commit details
-
Copy full SHA for a8cc07f - Browse repository at this point
Copy the full SHA a8cc07fView commit details -
🐛 fix(parallel): show --list-dependencies output (#3786)
Running `tox run-parallel --list-dependencies` silently discards the dependency output on successful runs. This happens because parallel mode suspends all stdout/stderr into in-memory buffers, and those buffers are only flushed when a test fails or when `parallel_show_output` is explicitly enabled. Since `--list-dependencies` writes via `logging.warning()`, the output gets captured and never reaches the user. The fix adds `list_dependencies` as an additional condition for flushing the suspended output buffers after each environment completes. This sits alongside the existing `parallel_show_output` and failure checks in `_handle_one_run_done`, preserving the same flush-or-discard pattern while ensuring dependency output is always visible when requested. Fixes #3322
Configuration menu - View commit details
-
Copy full SHA for 6885b5c - Browse repository at this point
Copy the full SHA 6885b5cView commit details -
✨ feat(config): add open-ended generative ranges (#3788)
Projects that test across multiple Python versions must manually update `env_list` whenever a new CPython release comes out. This is easy to forget and creates unnecessary churn in configuration files. The existing closed-range syntax `py3{10-13}` helps, but still requires bumping the upper bound each release cycle. Open-ended ranges solve this by letting users write `py3{10-}` or `py3{-13}`, where the missing bound is filled in from two constants — `LATEST_PYTHON_MINOR_MIN` (oldest non-EOL minor, currently 10) and `LATEST_PYTHON_MINOR_MAX` (latest stable release, currently 14) — that are updated with each tox release. The expansion is purely syntactic and happens at configuration load time with no I/O or interpreter probing, keeping it deterministic and fast. Environments for interpreters not installed on the system are naturally handled by `skip_missing_interpreters`. This is a **breaking change** for the previously-documented behavior where `{N-}` and `{-N}` were treated as literals. Non-numerical open ranges like `{a-}` and `{-b}` remain unexpanded. Fixes #3583Configuration menu - View commit details
-
Copy full SHA for 4573c04 - Browse repository at this point
Copy the full SHA 4573c04View commit details -
🐛 fix(config): collapse continuation lines before factor filtering (#…
…3787) When a factor-specific command uses backslash continuation (`\`) to span multiple lines, the continuation lines leak into environments that don't match the factor. For example with `foo: python -c "\⏎ print('foo')"`, the `print('foo')"` line gets executed as a standalone command in the `bar` environment because `expand_factors()` sees it as an unfactored line. 🔧 The root cause is the ordering of operations in `process_raw()` — factor filtering via `filter_for_env()` splits by newlines and evaluates each line independently, but backslash continuation collapsing (`\\\n` removal) happened *after* filtering. Moving the continuation collapse to happen *before* factor filtering ensures multi-line commands are joined into a single line before the factor prefix check runs. This is an INI-only issue since TOML configs don't use `filter_for_env()`. Fixes #2912
Configuration menu - View commit details
-
Copy full SHA for 78eb394 - Browse repository at this point
Copy the full SHA 78eb394View commit details -
✨ feat(pkg): cache external packaging env build across envs (#3790)
When using `package = external` with a shared `package_env`, the build commands run once per test environment that depends on it, rebuilding an identical package each time. For projects compiling C extensions or running complex build pipelines, this wastes significant time — often several minutes per redundant build. 🚀 The built package path is now cached after the first successful build within a session. Subsequent test environments that share the same external packaging env skip the build entirely and reuse the already-built artifact. Per-environment extras and dependencies are still resolved individually, so environments with different `extras` configurations continue to get the correct dependency set. This mirrors the caching behavior that `wheel_build_env` already provides for PEP-517 wheel builds, extending the same optimization to external packaging workflows. Fixes #2729
Configuration menu - View commit details
-
Copy full SHA for 326e14a - Browse repository at this point
Copy the full SHA 326e14aView commit details -
✨ feat(changelog): add granular towncrier categories (#3785)
The previous 5 changelog categories (feature, bugfix, doc, removal, misc) lacked granularity for communicating the nature of changes to users. Deprecations and breaking changes were scattered across unrelated sections, making it harder for users to assess upgrade impact at a glance. This adopts 8 categories inspired by [yarl's changelog](https://yarl.aio-libs.org/en/latest/changes/): `breaking`, `deprecation`, `feature`, `bugfix`, `doc`, `packaging`, `contrib`, and `misc`. Each category now has a more descriptive display name (e.g. "Bug fixes" instead of "Bugfixes", "Miscellaneous internal changes" instead of "Miscellaneous"). The unused `removal` directory is replaced by `breaking` for backward incompatible changes, while `deprecation` gets its own dedicated section for upcoming removals. Fixes #3200
Configuration menu - View commit details
-
Copy full SHA for 80d2931 - Browse repository at this point
Copy the full SHA 80d2931View commit details -
✨ feat(cli): accept plugin CLI options during provisioning (#3791)
Plugins that add CLI options via `tox_add_option` couldn't be used with `requires`-based provisioning. Running `tox --demo-plugin` where `--demo-plugin` is defined by a plugin in `requires` would fail with "unrecognized arguments" because CLI argument validation happened before provisioning had a chance to install the plugin and re-execute tox. 🔧 The fix defers unknown argument validation until after the provisioning check. During initial CLI parsing, `parse_known_args` is used instead of `parse_args`, allowing unrecognized flags through. If provisioning is needed, tox re-executes in the provisioned environment where the plugin is installed and the flag is recognized. If provisioning is *not* needed, any remaining unknown arguments still raise the familiar error message — so typos and invalid flags are still caught. The demo plugin used in integration tests now also registers a `--demo-plugin` CLI flag via `tox_add_option` to exercise this path. Fixes #2935
Configuration menu - View commit details
-
Copy full SHA for 0c08af3 - Browse repository at this point
Copy the full SHA 0c08af3View commit details -
✨ feat(env): add recreate_commands config key (#3793)
When `tox run -r` recreates an environment, external caches managed by tools inside that environment (e.g. pre-commit) survive the directory wipe. Users had no mechanism to clean those caches as part of the recreation lifecycle, leading to stale state that defeats the purpose of recreation. 🧹 `recreate_commands` is a new `list[Command]` config key that runs inside the still-existing environment before its directory is removed. This placement is deliberate -- the old virtualenv and its installed tools remain available, so commands like `{env_python} -Im pre_commit clean` work naturally. The env's bin directory is temporarily added to the allowlist so command resolution works correctly even though `_setup_env` hasn't run yet in the current session. Failures in `recreate_commands` are logged as warnings but never block recreation -- the environment is being destroyed anyway, so a cleanup failure shouldn't prevent a fresh start. The commands are skipped entirely on first creation (no env directory exists yet) and on normal re-runs without `-r`. Closes #3423Configuration menu - View commit details
-
Copy full SHA for ae36881 - Browse repository at this point
Copy the full SHA ae36881View commit details -
📝 docs(faq): document Debian python3-venv limitation (#3789)
On Debian and Ubuntu the system Python splits `venv` and `ensurepip` into a separate `python3-venv` package. tox itself is unaffected — it uses `virtualenv` which bundles its own bootstrap — but tools that tox runs as commands inside environments (most commonly `pyproject-build`) may use stdlib `venv` internally and fail with a confusing "ensurepip is not available" error. This adds a "Known limitations" entry in the concepts documentation explaining the root cause and providing two solutions: installing `python3-venv`, or using `tox-uv` to bypass stdlib `venv` entirely. Closes #3195
Configuration menu - View commit details
-
Copy full SHA for 5ee23bf - Browse repository at this point
Copy the full SHA 5ee23bfView commit details -
✨ feat(config): warn on unused config keys with -v (#3795)
Config directives placed in the wrong section (e.g. `ignore_base_python_conflict` in `[testenv]` instead of `[tox]`) are silently ignored. The only way to discover this was via `tox config` and inspecting the `# !!! unused:` markers — something new users are unlikely to know about. This surfaces unused config key warnings during `tox run -v`, printing them in yellow before the final report. The warnings are gated behind `-v` (one level above default verbosity) so existing workflows are unaffected. This also cannot be an error because unused keys can be legitimate — for example, plugin-defined keys that only exist in a provisioned environment. Documentation updated across all four Diataxis dimensions: - **Reference** (`config.rst`): added notes to Core and tox environment section headers clarifying section boundaries - **Explanation** (`explanation.rst`): new "Misplaced configuration keys" section in Known limitations - **How-to** (`usage.rst`): added step 6 to troubleshooting guide - **Tutorial** (`getting-started.rst`): added tip after first section explanation Fixes #3188
Configuration menu - View commit details
-
Copy full SHA for ee25113 - Browse repository at this point
Copy the full SHA ee25113View commit details -
✨ feat(cli): add --skip-env-install flag for offline env reuse (#3792)
When working offline or in CI environments where dependencies are already installed, users had no way to skip the dependency installation step without also losing package installation control. The existing `--skip-pkg-install` flag only skips package installation but still attempts to install dependencies, which fails without network access. This addresses #3310. The new `--skip-env-install` flag skips both dependency installation (`deps`, dependency groups) and package installation in a single opt-in flag. When set, `_setup_env()` returns early before calling `_install_deps()` and `_install_dependency_groups()`, and `_setup_with_env()` treats it as implying `--skip-pkg-install` to also skip package building. This makes it straightforward to reuse a previously populated environment without any network calls. The flag is available on all run commands (`run`, `run-parallel`, `legacy`) alongside the existing `--skip-pkg-install` flag. Existing behavior is completely unchanged when the flag is not provided. Fixes #3310
Configuration menu - View commit details
-
Copy full SHA for 7ea5bc1 - Browse repository at this point
Copy the full SHA 7ea5bc1View commit details -
✨ feat(env): add virtualenv_spec for per-env version pinning (#3794)
No single virtualenv release covers the full range of Python versions that projects need to test against — older versions like `virtualenv<20.22.0` are required for Python 3.6, while the latest virtualenv is needed for Python 3.15+. Since tox imports virtualenv as a library, environments targeting unsupported Python versions simply fail with no workaround. ✨ This becomes a real blocker for projects that must maintain compatibility across a wide Python version range in a single `tox.toml`. The new `virtualenv_spec` per-environment config key (e.g. `virtualenv_spec = "virtualenv<20.22.0"`) solves this by bootstrapping the specified virtualenv version into a cached venv under `.tox/.virtualenv-bootstrap/` and driving it via subprocess instead of the imported library. The bootstrap is content-addressed by spec hash, protected by file locks for concurrent safety, and reused across runs. When `virtualenv_spec` is empty (the default), tox continues using the imported virtualenv with zero overhead — the subprocess path only activates when explicitly configured. The `VirtualEnv` class now operates in dual mode: its `session` property routes to either the imported `session_via_cli` or a new `SubprocessSession` that mimics the same `Session`/`Creator`/`Describe` interface. Path accessors (`bin_dir`, `purelib`, `exe`, etc.) dispatch through `isinstance` checks so both modes integrate transparently with the rest of the environment lifecycle. The `python_cache()` dict includes the spec string when set, ensuring environments are automatically recreated when the pinned version changes. Closes #3656
Configuration menu - View commit details
-
Copy full SHA for c80c62f - Browse repository at this point
Copy the full SHA c80c62fView commit details -
Configuration menu - View commit details
-
Copy full SHA for 09b4f60 - Browse repository at this point
Copy the full SHA 09b4f60View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff 4.41.0...4.42.0