astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
20.23k stars 601 forks source link

Support working on multiple projects in the same venv #7314

Open cdwilson opened 6 days ago

cdwilson commented 6 days ago

I'd like to be able to install multiple projects in "development mode" (editable install) into the same virtual environment.

For example, let's say I'm developing projects A, B, and C simultaneously. Each of these projects are stored in separate git repos, published separately to PyPI, etc. However, the following dependencies exist:

When working on A, I'd like to use a venv where A is installed in "development mode" (editable install).

When working on B, I'd like to use a venv where A and B are installed in "development mode" (editable install).

When working on C, I'd like to use a venv where A, B, and C are installed in "development mode" (editable install).

I tried adding the dependencies for each project to [tool.uv.sources] by specifying a relative path for each. While this appears to be the solution, I don't really want to commit this into git because it requires anybody else who clones the project to have the dependencies located in the exact same relative paths.

I think what I might be looking for is some way to specify a set of [tool.uv.sources] for each project that are only local to my machine (not checked into git).

Is there a recommended way to achieve this workflow using uv? (It's also totally possible my workflow is bad and I'm missing the obvious correct way to work on multiple packages simultaneously.)

BTW, I'm coming from pyenv where this was simple to do. I could just create a single virtualenv for each project (which was stored outside of the project dir) and do an editable install of all dependencies into the active virtualenv.

zanieb commented 6 days ago

I could just create a single virtualenv for each project (which was stored outside of the project dir) and do an editable install of all dependencies into the active virtualenv.

So, we still support this via uv venv and uv pip install -e but if you want to use a higher-level API then I think you want to declare a workspace.

I think the idea would roughly be to a create an empty project, clone the three projects as subdirectories (or link them?), and add them as dependencies / workspace members to the root workspace project. Does that make sense?

The workspace documentation is available at https://docs.astral.sh/uv/concepts/workspaces/

cdwilson commented 6 days ago

So, we still support this via uv venv and uv pip install -e

The issue I ran into with this is that when A is installed via pip install -e into B's virtual environment, running uv sync in B will remove the editable install of A (because it is not listed as a dependency in B's pyproject.toml).

I think you want to declare a workspace.

The issue with workspaces is that it seems to require that each project is a subdirectory of some top-level workspace project.

Imagine the example above, but extended with another project D, where D also depends on A, but is totally unrelated to B or C. In this case putting everything into the same workspace doesn't make much sense.

To work on D, I would have to create a second workspace project with a second copy of A in a subdirectory of D's workspace. Now, I have to maintain separate copies of all the same dependencies in each workspace. As you suggested, maybe symlinking the dependencies from a central location instead of copying them might work, but it starts to feel like a lot of overhead vs. what I can do very easily/simply with pyenv.

Ideally, I'd like to just have a single development copy of A on my system, but install it as an editable-install in multiple other project's virtualenvs. I suppose this would require some user-specific (i.e. not checked into git) mechanism outside of the pyproject.toml file to record these dependency sources.

I'm totally open to changing my workflow, but just trying to find a way that is similarly low overhead to what I'm doing now.

zanieb commented 6 days ago

Yeah you can't really intermix uv sync and uv pip install. You can use uv sync --inexact to retain the existing install if you want to mix them, but they're generally not designed for that.

I'm totally open to changing my workflow, but just trying to find a way that is similarly low overhead to what I'm doing now.

I feel that. We're happy to help! I'm not sure if we have great support for the situation you've described yet. @konstin might have some better ideas for you. We've also considered ways to make it easier to "patch" dependencies of projects with local versions, but I think in that case we'd still require modifying the pyproject.toml.

cdwilson commented 6 days ago

We're happy to help!

Thanks for the ideas and help so far! I'll try out the workspace/symlink idea you suggested and see how that works out.

cdwilson commented 6 days ago

We've also considered ways to make it easier to "patch" dependencies of projects with local versions, but I think in that case we'd still require modifying the pyproject.toml.

Now that you mention it, I think this workflow is similar (if not essentially the same) to what I'm looking for, i.e. the ability to "patch" a project with a local copy of a dependency (usually via an editable install). It seems like there are two use cases in both of these workflows:

  1. Dependency sources need to be recorded in pyproject.toml and checked into git (implying that Path dependency sources always have the same hard-coded paths on every system)
  2. Dependency sources need to be configured locally, but Path dependency sources may vary across systems (i.e. not checked into git).

I don't have a sense for how much of an edge case the 2nd one of these is or whether it's worth supporting in some way.

juan11iguel commented 1 day ago

I am also facing this issue, I would like to have a "development" environment where some (editable) dependencies are local paths, and a "production" environment where the sources are different (a git repo or any other really). All handled via the same pyproject.toml.

A possible interface might be:

[project]
...
dependencies = [
    "numpy>=1.26.4",
    "pandas>=2.2.2",
    ...,
    "project_A",
    "project_B"
]

[tool.uv]
dev-dependencies = [
    "project_A",
    "project_B",
]

[tool.uv.sources]
project_A = { git = "https://github.com/username/project_A", branch = "main", "dev": { path = "../project_A", editable = true} }
project_B = { git = "https://github.com/username/project_B", branch = "main", "dev": { path = "../project_B", editable = true} }

Here for the same dependency there is a default source and and a dev source that might be used when the project is run with a --dev argument