Using uv workspaces
makes local development of Python packages much easier, but it requires some care to avoid missing dependencies.
Workspaces let you develop multiple Python packages from a single development environment. This is great for DX, since you can simply run commands on each package using uv run
instead of activating a new virtual environment for each package.
This convenience comes at a cost — missing dependencies can go unnoticed until late in development, or worse, after the packages have already been published.
In Python, this general problem is tricky to solve, as mentioned in the uv
documentation:
Since Python is interpreted and dynamic, you cannot get a complete ‘build-time’ guarantee about your dependencies. There are a few fundamental challenges for static validation of dependencies:
importlib
or __import__()
, which cannot be exhaustively detected in general without running the code.Even with these barriers, static validation can still help developers catch missing dependencies earlier in the development cycle.
Tach is a tool for Python codebases to manage their 1st-party and 3rd-party dependencies. Using tach check-external
or the VSCode extension, undeclared and unused dependencies can be detected immediately. NVIDIA uses Tach for this purpose on their bionemo-framework repo.
Consider this example project:
This project has 4 workspace members in total: workspace-root
, coreservice
, lib1
, and lib2
.
Each of these members declares its own dependencies in pyproject.toml
, but during development will have access to the full, combined set of dependencies.
However, the intention is that lib
packages are standalone libraries which can be published separately, while the coreservice
package depends on these libraries.
You can validate this relationship is preserved using Tach.
The only configuration necessary to validate 3rd-party dependencies is source_roots
, which can use globs to match the standard 'src-layout'. This means adding source_roots = ["**/src"]
to your tach.toml
config.
Then, running tach check-external
will automatically detect each project and validate that the declared dependencies match your actual import statements!
If it catches errors, Tach will print out location information and a short description of each issue:
In this case, we can see that Tach found undeclared dependencies in lib1
and lib2
. This would have worked fine locally, but caused a runtime error for an end user!
Returning to the barriers mentioned before, Tach makes some assumptions:
source_roots
. This means that import namespace
may not behave as expected (if multiple packages belong to the namespace), while import namespace.package
will be validated.import
statements, and explicitly does not cover runtime imports.By making these tradeoffs, Tach can run very quickly over large Python codebases, and catch low-hanging dependency issues immediately. It can be run as a pre-commit hook, in CI, and in your editor - there is an official VSCode extension, and a built-in LSP server (tach server
) for integration with other editors.
Using workspaces
in uv
can be a huge gain to developer quality-of-life, but a corresponding investment in code quality is needed. With Tach’s early detection of missing dependencies alongside isolated test suites, you can have confidence that your packages are ready to publish.