The Monorepo Dependency Nightmare
Python monorepos sound great until you have 50 packages, each with its own pyproject.toml, conflicting version pins, and a CI pipeline that installs half of PyPI for every PR. The tools have gotten better, but the fundamental challenge remains: how do you manage dependencies across dozens of packages without going insane?
Two years into the uv and Pants ecosystems, patterns have emerged. Here’s what works for Python monorepos in 2026.
uv Workspaces: The Lightweight Approach
uv introduced workspace support in 2024, and it’s matured into a solid foundation for monorepos with fewer than 20 packages. A workspace is defined in the root pyproject.toml using the [tool.uv.workspace] section. Package directories are listed as members. Each package under packages/ gets its own pyproject.toml with package-specific dependencies. The workspace handles cross-package references transparently.
The key advantage: uv resolves the entire workspace’s dependency graph together, ensuring there’s only one version of each transitive dependency. This eliminates the classic monorepo problem where Package A pins requests 2.31 and Package B pins requests 2.32, causing conflicts at install time.
Pants Build System: For Larger Monorepos
When you have 50-plus packages with complex build steps — compiled extensions, protobuf generation, Docker image builds — uv workspaces start showing their limits. Pants is the heavyweight alternative that scales to thousands of targets.
Pants doesn’t just manage dependencies. It understands your entire build graph. It knows which packages depend on which others, which files changed, and exactly which tests need to run. The command to run only affected tests is a single line. The command to build only affected Docker images is similarly concise.
The learning curve is real. Pants has its own BUILD file syntax, its own concepts of targets and goals. But for large teams, the build graph intelligence pays off in dramatically faster CI times.
Dependency Freshness: Automated Rot Prevention
The silent killer of monorepo health is dependency staleness. Package C still depends on numpy 1.24 from 2023, and nobody notices because it works fine — until a security vulnerability in numpy 1.24 forces an emergency upgrade that cascades through 12 packages.
Automated freshness checks solve this. A weekly GitHub Action that checks for outdated dependencies across all packages and opens issues when things get stale. The cost of running the check is negligible. The cost of not running it is a production incident.
The Shared Config Pattern
One of the best patterns to emerge: a shared dev-config package containing all build configuration. Instead of 50 pyproject.toml files each defining their own ruff and mypy settings, they all import from the shared config. Changes to linting rules propagate automatically.
The Tradeoffs
uv workspaces are the right choice for most Python monorepos. They’re simple, well-documented, and integrate with existing tooling. Switch to Pants when you need build caching, remote execution, or Docker image building integrated into the build graph. The most common failure mode is trying to do too much too early. Start simple, add automation, and only bring in heavy tooling when you actually need it.
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.