Skip to main content

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.dynamic is non-empty. If you're not using dynamic fields like dynamic = ["version"], add dynamic = ["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-updates generates my-core>=1.2.3. This is the most common choice for libraries.

  • allow-minor-updates generates my-core>=1.2.3,<2.0.0. Allows minor and patch updates but not major version bumps.

  • allow-patch-updates generates my-core>=1.2.3,<1.3.0. Allows patch updates only.

  • semver is version-aware: for 0.x versions it behaves like allow-patch-updates (since minor bumps can be breaking in 0.x), and for 1.x+ it behaves like allow-minor-updates.

  • pin generates my-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 environment
  • uv run poe test - Run tests
  • uv run poe lint - Run linter
  • uv run poe lint:fix - Run linter with auto-fix
  • uv run poe format - Format code
  • uv run poe format:check - Check code formatting
  • uv run poe typecheck - Run type checker
  • uv run poe lock:check - Check lockfile exists and is up to date
  • uv run poe check - Run all checks
  • uv 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

hatch_cada-0.1.0.tar.gz (177.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hatch_cada-0.1.0-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

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

Hashes for hatch_cada-0.1.0.tar.gz
Algorithm Hash digest
SHA256 240cffdb110cb4bd9ad38b4bb3d9e1f9849ad60e5377fd2bd9c14b6a26c43b20
MD5 2e1b42200d687e4f291296a70c17b33a
BLAKE2b-256 20b8e1c25a4561ea1cc6033b587a10181cd3706b5ac4d2358150ada18b2b3f14

See more details on using hashes here.

Provenance

The following attestation bundles were made for hatch_cada-0.1.0.tar.gz:

Publisher: release.yml on bilelomrani1/hatch-cada

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for hatch_cada-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d4038d87da033f48cb61e5a0c7a92fba33916bde4cee13de85055dd03c0d5d9f
MD5 61e8d0ac65d124c47d0a878bb9f73ac8
BLAKE2b-256 80b23a8333b3ce010f67b29f10899581a2179fd6eae5ec159dacaf306b06639f

See more details on using hashes here.

Provenance

The following attestation bundles were made for hatch_cada-0.1.0-py3-none-any.whl:

Publisher: release.yml on bilelomrani1/hatch-cada

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page