First of all, we (the uv team) are very excited about the addition of dependency groups. We’ve been holding off on exposing development dependency groups (there is just one dev-dependencies
section for uv right now) while we wait for a decision on this PEP. This is one of our most requested features, and I think it addresses a valuable need in the ecosystem. I’ve compiled some feedback from @charliermarsh and @konstin. Sorry for anything that’s repetitive; I did my best to look through the existing discussion, but it’s a 2+ hour thread and it feels impossible to capture all the context. Thank you Stephen for keeping track of everything!
I’m a bit worried about the lack of discussion (in the PEP) around locking and requirements.txt
files. It’s very common for requirements.txt
files to be used for locked versions of dependencies but throughout the PEP they are discussed as though they were requirement declaration files (more commonly, requirements.in
). The lack of distinction here isn’t a big deal, but I think the text could be improved in this regard to prevent confusion; especially with regards to teaching the feature. However, you’ll see much of my commentary comes from a lack of focus on locking the declared dependencies.
Because extras are package metadata, they are not usable when a project does not build a distribution (i.e., is not a package).
These can be, and are, statically extracted by various tools including uv — even if the project is not built into a distribution. I see context on this in the thread (1), but it’d be nice to avoid stating this in the PEP.
To support users in this case, this PEP defines and recommends validation behaviors in which tools only examine Dependency Groups which they are using. This allows multiple tools, using different versions of Dependency Groups data, to share a single table in pyproject.toml
.
uv will need to read all the groups for locking.
Use of a Dependency Group like test to test a package requires that the user’s configuration or toolchain also installs .
. For example,
It makes sense to require opt-in here, but two operations feels like a non-starter for us. We need to know if the group is intended to be compatible with the package for locking. I have a strong preference for declaring this statically. I believe this has been discussed quite a bit (e.g., in 2) and it sounds like it will need to be tackled separately? I’m not sure what the expectation is for tools in the meantime as this is a critical part of the workflow and we will be forced to write a bespoke implementation.
Note that this specification does not forbid having an extra whose name matches a Dependency Group. In such cases, tools must define their own semantics for precedence order or disambiguation. Users are advised to avoid creating Dependency Groups whose names match extras. Tools SHOULD NOT treat such matching as an error.
These two statements seem contrary and prone to confusion. Why let this be ambiguous?
I see a couple arguments in the thread:
- Tools should decide the user-experience of choosing a name (3)
- The user experience could differ based on the ability of tools to recognize the conflict (4)
Tools choosing their own user-experience when a name conflict occurs seems much more problematic than some tools not being able to recognize the conflict at all. If tools choose a different precedence, doesn’t that defeat the purpose of the standard here? It seems safer to be restrictive and have a consistent behavior in the tooling.
I think uv would error (e.g., on uv add
), or warn (e.g., on uv sync
), if this was encountered.
Eager validation is discouraged for tools which primarily install or resolve Dependency Groups.
As previously noted, uv must do this for locking.
The mutual compatibility of Dependency Groups is not guaranteed. For example, the Data Science example above shows conflicting versions of scikit-learn. Therefore, installing multiple locked dependency groups in tandem may require that tools apply additional constraints or generate additional lockfile data.
I believe that uv, Poetry, and PDM will all encounter this problem. This problem exists today, with project.optional-dependencies
, so I don’t consider it to be critical for this PEP — but I think these use-cases are important to consider.
There are a variety of use-cases in which package developers would like to install only the dependencies of a package, without the package itself.
This motivation for adding include
syntax to the project
table feels like a stretch. Installing the project alongside its dependencies is not something installers must do — it’s just common. Tools are figuring out a consistent way to expose this feature, but, for example, both Poetry and uv let you skip installation of the project. Support for excluding the project feels like an installer feature, not something that should be encoded in the metadata itself.
Using include
syntax to solve project installation creates a complicated user experience — each project will need to then move their dependencies from the project.dependency
table into a new dependency group. Then, at install time, consumers will need to determine the name of the group that houses the project’s dependencies and just install that. The group name can differ per project so, unlike when implemented at the installer level, the invocation will differ per project. Furthermore, it seems like a loss if the best practice for Python projects is to move dependencies out of the project.dependencies
table into a dependency-group
section. This example from the discussion (5) is particularly concerning as an ideal outcome:
[project]
dependencies = [{include = "runtime"}]
[optional-dependencies]
foo = [{include = "foo"}]
[dependency-groups]
runtime = ["a", "b"]
foo = ["c", "d"]
This change also feels dangerous from an adoption perspective. If a package uses this syntax, it will not be installable by older versions of package managers and will break static metadata readers. For example, when project metadata is static, uv will read it directly instead of invoking the build backend — but if the new syntax is used it will fail. New versions of uv can support this syntax, but the old versions will always be broken. This is also discussed for things like Dependabot and IDEs above (6).
I worry this change will create more fragmentation in the ecosystem. If there was a clear, strong benefit to this feature — I think the fragmentation could be acceptable but I don’t see enough justification for this additional functionality. From the discussion, it sounds this outcome was driven by not wanting to increase the scope of the PEP to handle declaration of a dependency on the project itself, I’d rather see this addressed directly in another PEP instead of adopting an awkward solution now. This is a core part of the user experience and it deserves some focused attention.
I know you previously said that you don’t have much more to say on the topic of this specific change (7), so I don’t expect you to address these points — all I can do is voice our shared concern.
As a final note, thanks again for all your work on this. I appreciate that you’re trying to improve the experience for users that aren’t building distributions.