Home
Nefe Tech LTD
p y t

3 Things I Wish I Knew Before Setting Up a UV Workspace

Dennis Traub

Dennis Traub

@dennistraub

February 27, 2026 3 min read 21 1
3 Things I Wish I Knew Before Setting Up a UV Workspace

I love uv, it's so much better than pip, but I'm still learning the ins and outs. Today I was setting up a Python monorepo with uv workspaces and ran into a few issues, the fixes of which were trivial once I knew about them.

1. Give the Root a Distinct Name

First, a virtual root (package = false) still needs a [project] name - and it can't match any member package.

I had both the root and my core package using the same name, e.g. my-app:

my-app/                   # workspace root
  pyproject.toml          # name = "my-app" <- problem!
  packages/
    core/
      pyproject.toml      # name = "my-app"
      src/core/
    cli/
      pyproject.toml      # name = "my-app-cli"
      src/cli/
Enter fullscreen mode Exit fullscreen mode

When I ran uv sync, it refused outright:

$ uv sync
error: Two workspace members are both named `my-app`:
  `/path/to/my-app` and `/path/to/my-app/packages/core`
Enter fullscreen mode Exit fullscreen mode

Even though the root has package = false, uv still registers its name as a workspace member identity. Same name, two members, no way to disambiguate.

The fix - give the root a workspace-specific name:

# Root pyproject.toml
[project]
name = "my-app-workspace"  # NOT "my-app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

[tool.uv.workspace]
members = ["packages/*"]

[dependency-groups]
dev = [
    "pytest",
    "ruff",
]
Enter fullscreen mode Exit fullscreen mode

Two things to note: package = false means "don't install me", not "don't need a name". And dev dependencies go in [dependency-groups] (PEP 735), not [project.dependencies] - the root is virtual, so project dependencies are just metadata.

2. Use workspace = true for Inter-Package Deps

When one workspace package depends on another, you need two things: a normal dependency declaration and a [tool.uv.sources] entry telling uv to resolve it locally.

# packages/cli/pyproject.toml
[project]
name = "my-app-cli"
dependencies = [
    "my-app",
]

[tool.uv.sources]
my-app = { workspace = true }
Enter fullscreen mode Exit fullscreen mode

Without the [tool.uv.sources] entry, uv sync fails with a helpful but initially confusing error:

$ uv sync
  x Failed to build `my-app-cli @ file:///path/to/packages/cli`
  |-- Failed to parse entry: `my-app`
  \-- `my-app` is included as a workspace member, but is missing
      an entry in `tool.uv.sources`
      (e.g., `my-app = { workspace = true }`)
Enter fullscreen mode Exit fullscreen mode

At least uv tells you exactly what to add.

The [project.dependencies] list stays PEP 621 compliant, so any standard Python tool can read it. The [tool.uv.sources] table is uv-specific and only affects resolution. And uv sync installs the local package as editable automatically - changes are immediately visible without reinstalling.

3. Use importlib Mode for pytest

When running pytest across a workspace where multiple packages have tests/ directories with same-named test files (e.g. both have test_helpers.py), pytest's default import mode breaks:

$ uv run pytest packages/ -v
collected 1 item / 1 error

ERROR collecting packages/core/tests/test_helpers.py
import file mismatch:
imported module 'test_helpers' has this __file__ attribute:
  /path/to/packages/cli/tests/test_helpers.py
which is not the same as the test file we want to collect:
  /path/to/packages/core/tests/test_helpers.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename
Enter fullscreen mode Exit fullscreen mode

Pytest's default prepend import mode treats both test_helpers.py as the same module. It imports the first one, caches it, then errors when the second file doesn't match.

The fix - add importlib mode to your root pyproject.toml:

# Root pyproject.toml
[tool.pytest.ini_options]
addopts = "--import-mode=importlib"
Enter fullscreen mode Exit fullscreen mode
$ uv run pytest packages/ -v
packages/cli/tests/test_helpers.py::test_cli_helper PASSED    [ 50%]
packages/core/tests/test_helpers.py::test_core_helper PASSED  [100%]
Enter fullscreen mode Exit fullscreen mode

Note: Don't add __init__.py to your test directories as a workaround - with importlib mode, that can actually cause a silent bug where pytest resolves both files to the same cached module and runs the wrong tests without any error.

This isn't uv-specific - it's a Python monorepo thing. But uv workspaces make monorepos easy to set up, so you're likely to hit it early.


References

Share this article:
View on Dev.to