Hatchling plugin for uv workspaces with independently publishable packages
Project description
Cada
uv workspaces are great for developing multiple Python packages together in a monorepo. But uv doesn't currently solve how to build and publish packages that depend on each other.
Cada is a simple hatchling build plugin for uv workspaces. It rewrites workspace dependencies with version constraints at build time, so each package can be published independently.
Table of contents
Why?
In a monorepo, you shouldn't have to manually track version constraints for internal dependencies. Each package defines its own version, either in project.version or derived from VCS tags. At build time, Cada reads these versions and generates constraints based on a compatibility strategy you choose. You focus on code, not version bookkeeping.
Suppose you have a monorepo managed by a uv workspace with internal libraries that depend on each other:
[project]
name = "my-client"
dependencies = ["my-core"]
[tool.uv.sources]
my-core = { workspace = true }
This works great for local development. But when you build my-client, the resulting wheel depends on my-core with no version constraint, which might not be desirable.
Cada solves this by generating version constraints at build time:
my-core-2.0.0.whl
my-client-1.0.0.whl # depends on my-core>=2.0.0
Each package keeps its own version and release cycle. Users only install what they need. Update my-core and consumers of my-client get it transitively ie. no new my-client release is required.
Quickstart
Add Cada to your build dependencies in each package that has workspace dependencies:
[build-system]
requires = ["hatchling", "hatch-cada"]
build-backend = "hatchling.build"
[project]
name = "my-client"
dependencies = ["my-core"]
[tool.uv.sources]
my-core = { workspace = true }
[tool.hatch.metadata.hooks.cada]
strategy = "allow-all-updates"
[!NOTE] Cada requires Hatchling as your build backend. The uv build backend does not support plugins yet.
[!WARNING] On current versions of hatchling, metadata hooks only run when
project.dynamicis non-empty. If you're not using dynamic fields likedynamic = ["version"], adddynamic = ["license-files"]as a workaround. See pypa/hatch#2153 for details.
Then build using your usual build frontend (uv, hatch, etc.):
uv build packages/my-client
The built wheel will have proper version constraints for all workspace dependencies.
Configuration
Add the following to your pyproject.toml:
[tool.hatch.metadata.hooks.cada]
strategy = "allow-all-updates"
The strategy option controls how version constraints are generated for workspace dependencies. If my-core is at version 1.2.3:
-
allow-all-updatesgeneratesmy-core>=1.2.3. This is the most common choice for libraries. -
allow-minor-updatesgeneratesmy-core>=1.2.3,<2.0.0. Allows minor and patch updates but not major version bumps. -
allow-patch-updatesgeneratesmy-core>=1.2.3,<1.3.0. Allows patch updates only. -
semveris version-aware: for0.xversions it behaves likeallow-patch-updates(since minor bumps can be breaking in0.x), and for1.x+it behaves likeallow-minor-updates. -
pingeneratesmy-core==1.2.3. Locks to the exact current version in your workspace.
You can override the strategy for specific dependencies:
[tool.hatch.metadata.hooks.cada]
strategy = "allow-all-updates"
[tool.hatch.metadata.hooks.cada.overrides]
my-core = "pin"
my-utils = "semver"
With this configuration, my-core will be pinned to its exact version, my-utils will use semver-aware constraints, and all other workspace dependencies will use the default allow-all-updates strategy.
Comparison with other plugins
Una
Una takes a different approach: instead of publishing workspace packages independently, it bundles all workspace dependencies into a single wheel:
my-client-1.0.0.whl
├── my_client/
└── my_core/ # vendored inside
Use Una when you're building an application you deploy yourself (Docker images, Lambda functions, CLI tools). You get a single artifact with everything included ie. no external dependencies to manage at install time.
Use Cada when you're building libraries for external consumers. Each package gets its own version and release cycle, and users only install what they need.
hatch-dependency-coversion
hatch-dependency-coversion rewrites dependency versions to match the current package's version. This is useful for lockstep versioning where all packages in a monorepo share the same version number.
Cada is different: it resolves each dependency's version through hatchling's plugin system. This supports independent versioning where each package has its own release cycle, and also works with dynamic versioning plugins like hatch-vcs.
uv-dynamic-versioning
uv-dynamic-versioning is another hatchling plugin that can handle workspace dependencies. The key difference is in how dependencies are declared.
uv-dynamic-versioning requires moving dependencies out of the standard project.dependencies field and uses non-standard templating in the version specifier:
[project]
name = "my-client"
dynamic = ["dependencies"]
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
dependencies = ["my-core=={{ version }}"]
Cada keeps standard dependencies syntax and location:
[project]
name = "my-client"
dependencies = ["my-core"]
[tool.uv.sources]
my-core = { workspace = true }
[tool.hatch.metadata.hooks.cada]
strategy = "allow-all-updates"
This matters because many tools in the Python ecosystem rely on the standard project.dependencies field:
- Monorepo build tools (Nx, Moon, etc.) auto-detect internal dependencies to build task graphs and determine what to rebuild when code changes. Non-standard dependency locations break this detection.
- Dependency scanners (pip-audit, safety, etc.) won't detect vulnerabilities and won't be able to rewrite your dependencies if they are in a non-standard location.
- Dependency update tools such as Dependabot only supports PEP 621 standard locations and has no workaround. Renovate can be configured with custom regex managers, but this requires manual setup.
- Import linters (deptry) can't verify imports match declared dependencies
Cada preserves compatibility with these tools out of the box by keeping your pyproject.toml structure standard.
Development
Set up the development environment:
uv sync --group dev
uv run poe setup
Available tasks via Poe the Poet:
uv run poe setup- Set up development environmentuv run poe test- Run testsuv run poe lint- Run linteruv run poe lint:fix- Run linter with auto-fixuv run poe format- Format codeuv run poe format:check- Check code formattinguv run poe typecheck- Run type checkeruv run poe lock:check- Check lockfile exists and is up to dateuv run poe check- Run all checksuv run poe release- Trigger release workflow on GitHub
License
Cada is distributed under the terms of the MIT license.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hatch_cada-0.1.0.tar.gz.
File metadata
- Download URL: hatch_cada-0.1.0.tar.gz
- Upload date:
- Size: 177.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
240cffdb110cb4bd9ad38b4bb3d9e1f9849ad60e5377fd2bd9c14b6a26c43b20
|
|
| MD5 |
2e1b42200d687e4f291296a70c17b33a
|
|
| BLAKE2b-256 |
20b8e1c25a4561ea1cc6033b587a10181cd3706b5ac4d2358150ada18b2b3f14
|
Provenance
The following attestation bundles were made for hatch_cada-0.1.0.tar.gz:
Publisher:
release.yml on bilelomrani1/hatch-cada
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hatch_cada-0.1.0.tar.gz -
Subject digest:
240cffdb110cb4bd9ad38b4bb3d9e1f9849ad60e5377fd2bd9c14b6a26c43b20 - Sigstore transparency entry: 782767243
- Sigstore integration time:
-
Permalink:
bilelomrani1/hatch-cada@a665b5044f43f3d7f84d03ad7f483c0501a44311 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bilelomrani1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a665b5044f43f3d7f84d03ad7f483c0501a44311 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file hatch_cada-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hatch_cada-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4038d87da033f48cb61e5a0c7a92fba33916bde4cee13de85055dd03c0d5d9f
|
|
| MD5 |
61e8d0ac65d124c47d0a878bb9f73ac8
|
|
| BLAKE2b-256 |
80b23a8333b3ce010f67b29f10899581a2179fd6eae5ec159dacaf306b06639f
|
Provenance
The following attestation bundles were made for hatch_cada-0.1.0-py3-none-any.whl:
Publisher:
release.yml on bilelomrani1/hatch-cada
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hatch_cada-0.1.0-py3-none-any.whl -
Subject digest:
d4038d87da033f48cb61e5a0c7a92fba33916bde4cee13de85055dd03c0d5d9f - Sigstore transparency entry: 782767248
- Sigstore integration time:
-
Permalink:
bilelomrani1/hatch-cada@a665b5044f43f3d7f84d03ad7f483c0501a44311 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bilelomrani1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a665b5044f43f3d7f84d03ad7f483c0501a44311 -
Trigger Event:
workflow_dispatch
-
Statement type: