Skip to main content

pytest-uv-workspace

Collect tests across a uv workspace by import name, not by path.

The problem

In a Python monorepo, both of pytest's import modes break down:

Default (prepend/append). A test file that isn't inside an importable package is named after its own basename. Two members with tests/integration/conftest.py and no __init__.py both want to be the top-level module conftest:

import file mismatch:
  imported module 'conftest' has this __file__ attribute:
    /repo/libs/pkg-a/tests/integration/conftest.py
  which is not the same as the test file we want to collect:
    /repo/libs/pkg-b/tests/integration/conftest.py

--import-mode=importlib. When a test file isn't inside a package, pytest falls back to a rootdir-relative dotted name. Every intermediate directory becomes a module name component, so a directory named platform/ (or profile/, test/, types/) shadows the stdlib module of that name and collection blows up somewhere unrelated.

The fix

Keep tests inside the package, and address them by module name:

libs/my-package/
└── src/
    └── my_package/
        ├── __init__.py
        └── tests/
            ├── __init__.py
            └── test_thing.py

Now the module is my_package.tests.test_thing under both import modes — globally unique, and free of any intermediate directory name. Nothing in the path can collide or shadow.

Getting there means running pytest --pyargs my_package other_package ... and keeping that list in sync by hand. This plugin does it for you.

What it does

On a bare pytest invocation it runs uv workspace metadata, maps each member to its import name, checks that name actually imports from inside the member's directory, and appends the survivors as --pyargs targets:

$ uv run pytest
uv workspace: collecting 3 package(s): my_package, other_package, shared_utils
...

That is the whole feature. It does not change your import mode, add fixtures, or touch collection in any other way.

Install

uv add --dev pytest-uv-workspace

uv must be on PATH (set UV to point elsewhere). If you'd rather pin it as a dependency, install pytest-uv-workspace[uv].

Requires Python 3.11+ and pytest 8.1.1+. The pytest floor matters: 8.0's --import-mode=importlib is not package-aware and still names modules from their rootdir-relative path, so intermediate directories leak in even when your tests live inside a package.

When it stays out of the way

Injection happens only on a bare invocation. The plugin does nothing if:

  • you pass any path, node id, or --pyargs module yourself — pytest tests/test_x.py behaves exactly as it always did;
  • testpaths is set in your config — that's a deliberate target list, so it wins;
  • uv isn't on PATH, or the directory isn't a uv workspace;
  • you pass --no-uv-workspace, or set uv_workspace = false in your ini.

Which members get collected

A member is collected when its distribution name maps to an importable module by the usual convention — my-packagemy_packageand that module resolves to a location inside the member's own directory. The location check is what stops an unrelated PyPI package of the same name from being collected in its place.

Members that don't resolve are skipped and reported:

$ uv run pytest -v
uv workspace: collecting 2 package(s): my_package, shared_utils
uv workspace: skipped 1 member(s)
  odd-member -> odd_member: not importable (is the workspace synced?)

So a member whose import name doesn't follow the convention (vedana-solar shipping solar_etl), or one that exposes several top-level packages, opts out simply by not resolving. To include those, give them a package matching their distribution name, or list them explicitly in testpaths.

If a workspace is found but nothing resolves, the plugin raises a UsageError rather than letting pytest quietly fall back to collecting the entire monorepo — usually it means you need to run uv sync.

Configuration

Option Default Meaning
--no-uv-workspace Disable discovery for this run
uv_workspace (ini) true Disable discovery permanently
UV (env) Path to the uv binary

Migrating an existing monorepo

If your tests currently live in libs/my-package/tests/, move them under the package and add __init__.py files:

git mv libs/my-package/tests libs/my-package/src/my_package/tests
touch libs/my-package/src/my_package/tests/__init__.py

The __init__.py files are the important part — they are what let pytest derive a fully qualified module name instead of guessing from the path.

License

MIT

Release files for pytest-uv-workspace 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-uv-workspace 0.1.0
File Size Uploaded
pytest_uv_workspace-0.1.0.tar.gz 22.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-uv-workspace 0.1.0
File Interpreter ABI Platform
pytest_uv_workspace-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.3 kB

Release files / pytest_uv_workspace-0.1.0.tar.gz

Download URL pytest_uv_workspace-0.1.0.tar.gz
Size 22.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5466ab8882c1b407b75a80e03ba6813a2117e0dfdc21261b53cdea9da3cca929
BLAKE2b-256 checksum
How to use checksums
19a505f1e1882fc29c580dc76686b6e189c958a4e10cd090bac8f94d0bcd7d4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pytest_uv_workspace-0.1.0-py3-none-any.whl

Download URL pytest_uv_workspace-0.1.0-py3-none-any.whl
Size 9.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ee37e4ac0fd28beea72faaf9c428c80ff697d6297d166ad02174a96cc6b161ef
BLAKE2b-256 checksum
How to use checksums
15db23a60720add94c3f1965e8f028fbfcd2655b8d2bb5b12bbf376ae89c8ea6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page