In the current PEP 621 state, there is no way to specify an optional dependency that occurs in several groups, without repeating yourself.
Let’s consider an imaginary package, called example. This example package is having 3 sets of optional dependencies, specified in pyproject.toml
file as follows:
[project.optional-dependencies]
test = [
"pytest < 5.0.0",
"pytest-cov[all]"
]
lint = [
"black",
"flake8"
]
ci = [
"pytest < 5.0.0",
"pytest-cov[all]",
"black",
"flake8"
]
As you can see, there are some dependencies repeated between the groups, which violates the DRY principle and it cannot be resolved on the project level easily. The only current solution to this problem is to ask the user to specify multiple extras when installing the package (to assemble all the dependencies he needs), which is not optimal.
Poetry solves this problem partially, by introducing an optional
argument to the list of the main project dependencies that marks this dependency as one not to be installed by default, but only when used in optional dependencies (called extras in poetry configuration). Such dependency, when originally declared in the main list of dependencies, will be mentioned only by its name inside the extras and all constraints for this dependency will be copied over from the main list under the hood. This way, constraints for any optional dependency needs to be specified only once.
This solution solves the issue only partially, as the name of the dependency still has to be mentioned in each set of optional dependencies requiring it (which is a more obvious problem when having a bigger list of dependencies shared between optional sets). It also introduces another problem to handle: how to treat a dependency marked as optional, if it is not being used in any of the optional dependencies set?
My proposal to fix the solution would be to allow the inheritance of the optional dependencies, by introducing some additional dependency syntax, which will be valid only in this single context. For an example, I’m assuming an entry starting with >
character followed by a name of another optional dependencies set should be used. Given that, here is above example again, but with the use of my proposal:
[project.optional-dependencies]
test = [
"pytest < 5.0.0",
"pytest-cov[all]"
]
lint = [
"black",
"flake8"
]
ci = [
">test",
">lint"
]
Additionally, there may be an option to declare an optional dependencies set that can only be used in another sets. For example a name starting with an underscore (_
) will be treated as one used only in other sets and will not end up in the parsed requirements on its own. The choice of the underscore is very random and a delimiter that is currently not allowed at the beginning of the optional dependencies group name should be used instead (I haven’t checked if underscore is currently allowed).