# How to manage cross-repository Python dependencies with uv


A [uv](https://pydevtools.com/handbook/reference/uv.md) 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](https://pydevtools.com/handbook/how-to/how-to-set-up-a-python-monorepo-with-uv-workspaces.md) instead. Workspaces share a single [lockfile](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) 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:

```bash
uv add --editable ../shared-lib
```

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

```toml
[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`):

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

This produces:

```toml
[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:

```console
$ 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](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) 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:

```toml
[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`:

```toml
[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:

```toml
# 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](https://peps.python.org/pep-0508/#environment-markers):

```toml
[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:

```toml
[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

- [uv: A Complete Guide](https://pydevtools.com/handbook/explanation/uv-complete-guide.md) covers what uv does, how fast it is, the core workflows, and recent releases.
- [uv: dependency sources](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources)
- [uv: git dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#git)
- [How to set up a Python monorepo with uv workspaces](https://pydevtools.com/handbook/how-to/how-to-set-up-a-python-monorepo-with-uv-workspaces.md)
- [How to use private package indexes with uv](https://pydevtools.com/handbook/how-to/how-to-use-private-package-indexes-with-uv.md)
