The main drawback of the proposed sources approach is that we split the dependency declaration into two parts responsible for different parts of the declaration, and in the process, create two different formats for declaring dependencies (PEP 508 and TOML sources table, not counting requirements.txt which extend PEP 508 with some of those features such as -e), requiring users to use both formats at different locations in their pyproject.toml. Instead of declaring your dependencies once per section, you declare one half in an unrelated section and then have to use tricks like extra and group to reference backs. This syntactically also allow a number of strange states such as both project.dependencies and develop.sources declaring a path.
Before introducing uv’s tool.uv.sources, we considered various other approaches that would avoid splitting uv’s dependency declaration, but ultimately settled on the sources table as the only feasible way to be compliant with PEP 621. If we’re doing a PEP fixing the big limitations of PEP 621 (relative paths, editables, index pinning), I’d strongly recommend exploding the PEP 508 strings from PEP 621 into inline tables and - if required - having build backends to rewrite [project]. We can take inspiration from Poetry and Cargo with their inline table formats and Cargo’s rewriting features.
The motivating example on the document is multi-package repositories. For this case, wouldn’t it be simpler to have a single foo/<TBD>.toml that declares which packages belong to a workspace and automatically sets up the right paths? This avoids editing every single pyproject.toml, allows moving projects and only changing at most one path and avoids accidentally installing a package from PyPI by forgetting a sources declaration.
On the PEP text:
Optional, boolean, default false. When true, the project at path MUST be installed as an editable install per PEP 660, and path MUST refer to a directory containing a project. Tools MUST report an error if it refers to a wheel or source archive.
The MUST formulation would break --no-editable in uv, and the editable controls in uv’s workspaces. I’d follow Dependency specifiers - Python Packaging User Guide which only mandates format and meaning, but does not mandate behavior.
This PEP defines one key in [develop], sources. Other keys are reserved for future specifications. Tools MUST NOT define keys of their own in [develop], and MUST report an error for keys they do not recognize.
What are tools supposed to do for features not covered in develop? Would we have project.dependencies, develop.sources and then also still tool.uv.sources for features not in the standard?
In URL sources:
Required, string, non-empty. The URL of a wheel or source archive. Tools MAY refuse schemes other than https.
Are there requirements on the filename in the URL, to distinguish what file type to expect? Is it intentional that it say wheel and source archive, but not source distribution?
Outside Python, Cargo’s [patch] table redirects dependencies in a similar way. Cargo reads [patch] only from the manifest at the root of a workspace, applies it to every member, and ignores it in the manifests of dependencies.
Cargo’s patch and replace table work more like uv’s override-dependencies. For declaring source such as workspace dependencies, Cargo instead uses an inline table that is rewritten as the crate is published.
PDM expresses local and VCS dependencies through direct references together with a separate index configuration.
There’s also the extensions to PEP 621 by evaluating environment variables inside PEP 508 strings, working
uv names indexes rather than taking URLs, does not accept the string shorthand, applies sources to build-system.requires, and applies tool.uv.sources from a workspace root to every member of the workspace.
Those are important features in uv! It would be bad to regress this support with develop.sources.
Sources MUST NOT be applied to build-system.requires. Build requirements decide which backend builds a distribution, and which version of it. If sources could redirect them, the same source tree could produce a different distribution depending on whether [develop.sources] was honored, which Sources and built distributions does not allow. uv currently applies sources to build requirements by default; see Existing implementations.
I’m not following this limitation, if you can override the runtime dependencies, why can’t you override the build dependencies too? For the motivating use case, multi-package repositories, it is common to have in-tree build backends, either vendored, patched or entirely custom, that you’d similarly want to target.
Tools MUST NOT read [develop.sources] from any other file. In particular:
- A [develop.sources] table in a source tree or source distribution obtained as a dependency MUST NOT be honored, even when that dependency is itself redirected to a path below the invocation root.
- Tools MUST NOT search ancestor directories, or directories between the invocation root and the target, for further tables.
What happens if a project has a git dependency where the git repo uses relative dependencies internally, i.e. a dependency on a remote multi-package repository?