On April 26, 2025, we, the pip team released pip 25.1.

Compared to previous releases, this release is a large one. The changelog is quite long. Among the changes, there are numerous new features, including an install progress bar and support for Dependency Groups!

I won’t cover every single change here, so as always, please refer to the changelog for the full list of changes.

Key features ✨

Dependency groups (PEP 735)

Support for installing Dependency Groups (PEP 735) is now available with the --group option.

What are Dependency Groups?

Dependency Groups are the modern standardized replacement for requirements.txt files and extras used for development workflows. Dependency Groups are NOT published nor installable by the end user, unlike extras.

They are meant to replace files like test-requirements.txt or extras like [test], [dev], or [doc]. They are defined in a pyproject.toml file under the dependency-groups table.1

First, let’s assume there is a pyproject.toml file in the current directory that defines test and lint dependency groups:

# pyproject.toml
[dependency-groups]
test = ["pytest", "pytest-xdist"]
lint = ["mypy", "isort"]
# Dependency Groups can include other groups! ✨
dev = [ {include-group = "test"}, {include-group = "lint"} ]

To install the test group, one can simply pass --group test.

pip install --group test

You can repeat the option to install multiple groups. You can also use --group with -r, -e, and any other dependency specifier. Want to install the local project, two groups, and a requirements.txt? You bet you can.

pip install . --group lint --group test -r my-old-requirements.txt

To install a dependency group from a pyproject.toml file that exists in a different directory, state the path before the group separated with a colon.

pip install --group ./dev/vem/pyproject.toml:dev
Warning

pip will NOT look in parent directories for a pyproject.toml file to install dependency groups from. pip does not have the concept of a project workflow.2

Additionally, Dependency Groups are a standardized feature, thus pip-specific options are NOT supported. If you need to pass --no-deps or some other pip flag, you should continue to use a requirements.txt file.3

Please read the pip user guide for more information. You can also read the motivation and rationale sections of PEP 735 if you are curious about why this feature came about.

Thank you to Stephen Rosen for writing and shepherding PEP 735 and contributing the pip implementation. Congratulations! 🎉

Package installation progress bar

A progress bar has been added to pip install that tracks the installation step.

Previously, pip would provide no feedback on how many packages have been installed. Is pip slowly installing a large package (e.g., torch) or is pip simply stuck? Who knows! The only time you do know is when pip first needs to uninstall an existing package to install a new version, emitting Attempting uninstall: mypackage as it goes.

Now, you can install every single Home Assistant dependency and know much progress pip has made after dependency resolution and downloads.

Resumable downloads

Support for automatic download retrying is available as an experimental feature starting with pip 25.1. The download retry limit can be configured using the --resume-retries option.

Important

Download retrying (--resume-retries) is separate from connection retrying (--retries).

When a download terminates early, pip will attempt to resume the download from where it left off. If the remote server does not support download resumption, pip will fall back to restarting the download. If the download is still incomplete after pip has run out of retries, a diagnostic error will be raised.

screeenshot of pip diagnostic incomplete-download error

It has been a long-standing complaint that pip is unreliable on poor/flaky connections. Any downloads of a sufficiently large package would fail midway through, forcing the user to retry the entire pip command, only for it to fail again. For users on a slow or metered connection, pip is likely unusable. And if hash-checking was enabled, then a confusing and misleading hash mismatch error would be raised.

If you use the feature, please let us know how it goes! We want to make sure it works well. Assuming nothing is fundamentally broken, --resume-retries will be changed in the next release to allow a few download retries by default.

Thank you to George Margaritis for contributing this feature!

Experimental lockfile (PEP 751) generation: pip lock

pylock.toml (PEP 751) is the recently accepted standard for Python lockfiles. Lockfiles are alternatives to the classic requirements.txt format, designed to enable reproducible installation in a Python environment.

pip 25.1 makes the first step at supporting the new lockfile format. The pip lock command has been added to create a pylock.toml from a set of requirements.

Here’s an example locking the pip project in editable mode and six.

pip lock -e ./pip six --output - -qq
lock-version = "1.0"
created-by = "pip"

[[packages]]
name = "pip"

[packages.directory]
path = "pip"
editable = true

[[packages]]
name = "six"
version = "1.17.0"

[[packages.wheels]]
name = "six-1.17.0-py2.py3-none-any.whl"
url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl"

[packages.wheels.hashes]
sha256 = "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"

The produced lockfile is specific to the Python version and platform pip is invoked under. In other words, the lockfile is NOT a universal lockfile.

The command is experimental, meant to enable basic locked installation scenarios. It is expected to undergo further development once the lockfile standard sees more widespread adoption and matures.

Installing from a lockfile is unsupported, but it is on the roadmap.

Congratulations to Brett Cannon for getting a lockfile standardized after many years(!) of PEP drafting and several rounds of discussion. I look forward to seeing pylock.toml mature!

pip index versions is stable

The pip index versions command is no longer experimental. It is the modern replacement for the old pip install mypackage== hack which used to be an easy way to get the list of all available versions.

pip index versions six
six (1.17.0)
Available versions: 1.17.0, 1.16.0, 1.15.0, 1.14.0, 1.13.0, 1.12.0, 1.11.0, 1.10.0, 1.9.0, 1.8.0, 1.7.3, 1.7.2, 1.7.1, 1.7.0, 1.6.1, 1.6.0, 1.5.2, 1.5.1, 1.5.0, 1.4.1, 1.4.0, 1.3.0, 1.2.0, 1.1.0, 1.0.0, 0.9.2, 0.9.1, 0.9.0
  INSTALLED: 1.17.0
  LATEST:    1.17.0

In addition, you can now ask for JSON output using the --json flag.

pip index versions six --json
{"name": "six", "versions": ["1.17.0", "1.16.0", "1.15.0", "1.14.0", "1.13.0", "1.12.0", "1.11.0", "1.10.0", "1.9.0", "1.8.0", "1.7.3", "1.7.2", "1.7.1", "1.7.0", "1.6.1", "1.6.0", "1.5.2", "1.5.1", "1.5.0", "1.4.1", "1.4.0", "1.3.0", "1.2.0", "1.1.0", "1.0.0", "0.9.2", "0.9.1", "0.9.0"], "latest": "1.17.0", "installed_version": "1.17.0"}

If you use pip index versions programmatically, you are encouraged to switch to the --json output. Not only is it easier to parse, but you will avoid breakage if the human-friendly format ever changes later.

Thank you to Krishan Bhasin for stabilizing pip index versions and adding JSON support!

Key bugfixes

Dependency resolution improvements

Dependency resolution is not my area of expertise, so I’m going to shamelessly steal the changelog entries that Damian Shaw wrote:

  • Speed up resolution by first only considering the “priorities” of candidates that must be required to complete the resolution.

  • Improved heuristics for determining the order of dependency resolution by preferring direct URL candidates and requirements with upper bounds.4

  • The upgrade to ResolveLib 1.1.0 fixes a known issue where pip would report a ResolutionImpossible error even though there is a valid solution. However, some very complex scenarios that previously resolved may resolve slower or fail with an ResolutionTooDeep error.

  • The ResolutionTooDeep error has been converted into a more user-friendly diagnostic error. If you have any real-world examples that result in a ResolutionTooDeep error, we would like to hear them!

Legacy .egg distributions are only detected once

There are several formats that an installed package can be recorded in. Today, virtually every package is installed using the .dist-info format. However, legacy .egg distributions are still kicking around.

Support for .egg distributions has been deprecated since pip 23.2. In pip 25.1, this deprecation was finalized… but wait, pip still supports .egg distributions! It turns out that importlib.metadata, the library used to discover installed packages on Python 3.11+, supports .egg distributions just fine.

This meant that .egg distributions were being discovered several times. The importlib.metadata backend would find it, and then the pkg_resources backend, leading to this confusing pip list output.

pip list
Package                       Version
----------------------------- ---------------------
mypackage                     0.7.16
mypackage                     0.7.16

In pip 25.1, the pkg_resources backend is no longer used to discover .egg distributions and this duplication will not occur. Warnings about the .egg deprecation will no longer be printed.

Deprecations & upcoming removals

Starting with this release, the pip project no longer supports Python 3.8.

In addition, here is a list of current deprecations and the release they will be removed. As always, any given removal may be pushed to a future release as needed.

Non-bare project name in egg fragment To be removed in pip 25.2
See the pip 25.0 post for more details.
Legacy setup.py editable installs To be removed in pip 25.3
Please read the deprecation issue for more details and advice. This was scheduled for removal in 25.0 and then 25.1, but it got pushed back (again) to coincide with the deprecation of setup.py bdist_wheel installs.
Legacy setup.py bdist_wheel installs To be removed in pip 25.3
Please read the deprecation issue for more details and advice, however, the summary is that pip will stop running setup.py bdist_wheel directly to build a wheel for installation. This is a compatibility fallback for old environments that do not support the modern PEP 517 interface.
Non-standard wheel filenames To be removed in pip 25.3
This is a continuation of the old deprecation as the original deprecation did not catch all non-standard wheel filenames. See the pip 25.0 post for more details.

Acknowledgements

Thank you to Damian Shaw, Emma Smith, Hugo van Kemenade, and Stéphane Bidoul reviewing the draft of this post. Any typos or glaring mistakes are my own.


  1. They are not defined under the project table because Dependency Groups are meant to be a general feature that work regardless of whether you have an actual Python package to install or not. ↩︎

  2. There has been extensive discussion about pip’s fundamental design and whether it should change. There is too much to easily summarize, but broadly, pip has always been designed as a package manager, and not as a project workflow tool. There are many other tools that compete in the project management space. The consensus during review is that pip is valuable as a foundational tool that simply manages your installed Python packages and does what you want without too much fuss. More practically, shifting to a project-based model would constitute a major redesign of the pip UI and UX. ↩︎

  3. This is a limitation of the specification. However, if sufficient consensus builds around certain tool-specific flags, they can be standardized so they can be included in Dependency Groups in the future. ↩︎

  4. Please don’t add upper bounds unless you know for a fact your codebase is incompatible with the newer versions. Pre-emptive upper-bounds will not help dependency resolution and are generally considered harmful↩︎