Skip to content

How to manage cross-repository Python dependencies with uv

uv

A uv workspace keeps sibling packages in sync when they live in the same repository. When the packages live in separate repositories, reach for [tool.uv.sources] instead. A source override lets each consumer point a dependency at a local checkout, a git ref, or the published version on PyPI without changing the dependency spec itself.

Note

If your packages live in the same repository, use uv workspaces instead. Workspaces share a single lockfile and virtual environment, which is what you want for tightly coupled packages.

Develop against a sibling checkout

When working on a consumer and a library at the same time, install the library as an editable path dependency so changes show up immediately:

uv add --editable ../shared-lib

The consumer’s pyproject.toml now has both a regular dependency entry and a source override:

[project]
dependencies = [
    "shared-lib",
]

[tool.uv.sources]
shared-lib = { path = "../shared-lib", editable = true }

The source override tells uv to install from the sibling checkout instead of resolving against PyPI. Anyone who clones both repos into the same parent directory gets the editable install on the next uv sync.

Drop --editable if you want uv to build the sibling once and install it as a regular package. Editable mode is almost always what you want during active development.

Tip

Check in the [tool.uv.sources] entry only if every developer on the team clones the repos side by side. Otherwise, keep it out of version control and document the override as a local-development step. Scopes can also be combined with markers (below) so the override only activates in specific environments.

Pin a dependency to a git branch or tag

When the library isn’t published to PyPI yet, or you need a fix that hasn’t shipped, depend directly on git. Use uv add with the package @ git+URL form and a ref flag (--branch, --tag, or --rev):

uv add "shared-lib @ git+https://github.com/acme/shared-lib" --branch main

This produces:

[project]
dependencies = [
    "shared-lib",
]

[tool.uv.sources]
shared-lib = { git = "https://github.com/acme/shared-lib", branch = "main" }

The same pattern works with --tag v1.2.0 or --rev abc123. A bare --branch without the git+ URL fails because uv has no repository to apply the branch to:

$ uv add shared-lib --branch main
error: `shared-lib` did not resolve to a Git repository, but a Git reference (`--branch main`) was provided.

The lockfile records the resolved commit SHA, so every uv sync pulls the same code until you run uv lock --upgrade-package shared-lib. Branch pins drift over time by design; use --tag or --rev when you need a fixed point.

For a library published from a subdirectory of a larger repository, add subdirectory to the source entry manually:

[tool.uv.sources]
langchain = { git = "https://github.com/langchain-ai/langchain", subdirectory = "libs/langchain" }

Toggle between git and the published version

Keep the dependency specifier pointed at the published package on PyPI and let [tool.uv.sources] override it. Removing the override reverts to PyPI without touching dependencies:

[project]
dependencies = [
    "shared-lib>=1.0",
]

# Comment out this block to use the PyPI release
[tool.uv.sources]
shared-lib = { git = "https://github.com/acme/shared-lib", branch = "main" }

With the source in place, uv lock records the git commit. Remove or comment out the [tool.uv.sources] block and re-run uv lock; the lockfile switches back to the registry:

# With source override
source = { git = "https://github.com/acme/shared-lib?branch=main#<commit-sha>" }

# After removing the source
source = { registry = "https://pypi.org/simple" }

This pattern is useful when a library is published to PyPI but you occasionally need to ride a fix before the next release. The dependency specifier stays stable; only the source changes.

Gate the override with a marker

To keep the override active only in certain environments, add a PEP 508 marker:

[tool.uv.sources]
shared-lib = { git = "https://github.com/acme/shared-lib", branch = "main", marker = "sys_platform == 'darwin'" }

Multiple sources can be listed for the same package when each has its own marker:

[tool.uv.sources]
shared-lib = [
    { git = "https://github.com/acme/shared-lib", tag = "1.2.0", marker = "sys_platform == 'darwin'" },
    { git = "https://github.com/acme/shared-lib", tag = "1.1.5", marker = "sys_platform == 'linux'" },
]

Markers also support extras (extra = "gpu") and dependency groups (group = "dev") for overrides that only apply to opt-in installs.

Learn More

Last updated on

Please submit corrections and feedback...