PEP 735: Dependency Groups in pyproject.toml

In 100% agreement with your footnote, I think this is the same as the --only-deps case. It’s not a case that I’ve experienced myself, so my understanding of this case is weak. I may need to reread some of the threads on this topic.

I think any such need could be solved by having dependency group inclusion interact with [project.dependencies] in either direction, since any desire for --only-deps can be satisfied by making a dependency group which is synonymous with [project.dependencies].

As we discussed the path dependencies, one of the things which I grew uncomfortable with was the realization that specifying . would mean that a build happens. Even if behavior is newly defined for this, it’s not necessarily the same as whatever the project’s preferred build frontend does. If a project has any need to configure the build environment, this could start to break down.

Having thought more about this, I’m not ready to reintroduce the idea that a dependency group can refer to the current package as a package. I could be convinced that it would be okay to do so, but right now I would need convincing.

I’m not sure we have a clear use case we’re satisfying by including . as a dependency. It “feels natural” and we know that users will want environments with a dependency group (like “test”) + the current package. But are we making things significantly better for users by including it?

For a tool like hatch or tox which can already install . in addition to some extended set of dependencies or extras, I don’t think there’s any particular benefit in having a dependency group include .. For example, for tox, we’re really talking about replacing

[testenv]
deps = -r test-requirements.txt
commands = pytest

with

[testenv]
dependendency-groups = test
commands = pytest

In both cases the installation of . is managed separately. I think tox would actually find this harder if test included a reference to ., since . is being requested twice via two different paths: once in the dependency group and once implicitly as part of a tox testenv without skip_install=true.

For direct pip usage, we’re comparing pip install --dependency-group test against pip install . --dependency-group test. (Or maybe two invocations of pip, but still something like that.)
So it’s really similar in simplicity, although you could argue that it’s subtle.

Before we reintroduce . of “self” or “current package”, I want to have a clearer handle on what we’re gaining by including it and what we’re losing by excluding it.

This is consistent with @EpicWink’s comment:

I’m not sure that there is a clear use case for it. A lot of this thread assumes that there is such a case, but as far as I know we haven’t clearly articulated cases. I will have one such case detailed below, at the end of this comment.

Right now, I’m reading and trying to get a better understanding of what kinds of includes between [project] and [dependency-groups] are important or useful.

I appreciate that you’re playing around with syntaxes for this, and I think the “relative path” trick is cute/clever (I mean that in a positive way) but too subtle to be a good interface.

We want it to be relatively obvious at a glance what each “thing” in the [dependency-groups] and [project] is. Currently {include = ...} means a Dependency Group Include. If we need multiple types of includes, maybe include is a bad keyword to use as a bare name, and it should be a vocabulary of things like

{include-group = "foo"}  # Dependency Group
{include-optional-dependencies = "bar"}  # Extra
{include-dependencies = true }  # [project.dependencies] list

Thanks for these! I’ll get the PEP updated with some fixes.

Use Cases for [project] Includes

I only have one user story which I can articulate clearly, and that’s related specifically to static analysis.
This applies mostly to type checkers, but other analyzers like pylint sometimes have similar requirements.

Basically, type checking a codebase requires that all of the runtime dependencies of that project are present. It also often requires that some or all extras are installed. (Theoretically, there could be conflicting/mutually exclusive extras, which could all also be needed over multiple runs of the analysis, but I’ve never seen this in practice.)

So, for a simple case, imagine a project with some library requirements and one extra:

[project]
dependencies = ["a", "b"]
[optional-dependencies]
foo = ["c", "d"]

In order to type check this project, assuming the use of mypy, the following packages need to be installed:

mypy, a, b, c, d

How should this project write the requirements?

Well, here’s how we can write it in one of the proposed forms from this thread:

[project]
dependencies = [{include = "runtime"}]
[optional-dependencies]
foo = [{include = "foo"}]
[dependency-groups]
runtime = ["a", "b"]
foo = ["c", "d"]
typing = ["mypy", {include = "runtime"}, {include = "foo"}]

Name the group pylint and swap mypy for pylint and you have the same case, but for a different analyzer.

Could this be satisfied by installing .[foo]? Yes, but two things have been lost:

  • installing . is wasted work – the analyzer doesn’t need it, but it is being built and installed anyway
  • if the typing dependency group cannot express the need for these other dependencies, it is now incomplete

There’s another element of the way that a test dependency group would typically interact with extras which I’m thinking about but not yet sure how it impacts things:

test requirements are often needed as part of a matrix of build configurations over multiple extras, which may be dependent on the python runtime version.
For example, the following test configurations may be desired for a package where a toml extra refers to tomli and a yaml extra refers to pyyaml:

dependency_groups extras pythons
test (none) 3.9, 3.10, 3.11, 3.12
test toml 3.9, 3.10
test yaml 3.9, 3.10, 3.11, 3.12
test toml, yaml 3.9, 3.10

etc.

tox, nox, etc already let you build these kinds of matrices. Do we benefit from allowing dependency groups to include extras in these cases, or vice-versa? I tend to think not, but maybe I’m missing a potential interaction here.

2 Likes