You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider replacing Hatch with uv for project and dependency management #199
When I created this SDK in November 2023, the Python build and project management space was crowded. There was Hatch, Poetry, PDM, Flit, setuptools, and probably others I'm forgetting. There wasn't a clear winner. I picked Hatch because it was a PyPA project, it had a clean pyproject.toml-based configuration, and it covered everything I needed: environments, scripts, building, versioning, and linting (via its Ruff integration).
Today we use Hatch for:
Building wheels and sdists (hatch build), using hatchling as the PEP 517 build backend.
Environments with isolated dependencies — a generate env for protoc, a docs env for pdoc, and a hatch-static-analysis env for Ruff.
Scripts like hatch run generate:protoc and hatch run docs:generate.
Version management — reading the version from __version__.py at build time.
Linting via hatch fmt, which wraps Ruff.
Testing via hatch test, which wraps pytest.
function-template-python uses the same setup, and any new functions built from it will inherit it.
Hatch works. I don't have a concrete problem with it.
What I do have is a growing sense that the ecosystem has converged on uv in the ~18 months since I made this choice. uv didn't exist when I picked Hatch — its first release was February 2024, three months after this SDK. Since then it's grown to 82k GitHub stars (vs Hatch's 7.2k and Poetry's 34.3k) and 14,600 dependent repositories (vs Hatch's 1,800). It has a full-time team at Astral and ships releases at a pace that's hard to ignore.
We already use Astral's Ruff for linting and formatting, and I'm interested in their ty type checker too. Adopting uv would mean our Python build toolchain is entirely Astral: uv for project management, Ruff for linting, and potentially ty for type checking. That's less build plumbing for contributors to learn and install.
uv would also give us a lockfile (uv.lock) for pinning transitive dependencies, which Hatch doesn't support. We pin direct dependencies today but have no control over what versions of their transitive dependencies get installed.
How could this Function help solve your problem?
I think we should evaluate replacing Hatch with uv for project and dependency management in this SDK and in function-template-python. I'll follow up with a comment breaking down what we'd gain and what we'd lose.
Here's a breakdown of what we'd gain and lose by switching from Hatch to uv.
Approach
Use uv for everything — project management, building, and publishing. We can use uv's own build backend (uv_build) instead of hatchling. The two hatchling features we rely on today are replaceable:
Dynamic versioning from __version__.py. Today CI runs hatch version to write a version string to __version__.py, then hatchling reads it back during hatch build. Nothing in the SDK actually imports __version__ at runtime. With uv, CI could write the version directly into pyproject.toml as a static field before running uv build.
Package layout. We use tool.hatch.build.targets.wheel.packages to tell hatchling about our flat layout. uv_build supports the same thing via tool.uv.build-backend.module-root.
What we'd gain
A lockfile. uv produces a uv.lock that pins exact versions of all transitive dependencies, cross-platform. Today we pin direct deps but have no control over transitive ones.
Less build plumbing. The entire Python toolchain becomes Astral: uv for project management and building, Ruff for linting, and potentially ty for type checking. No hatchling, no separate build backend.
Better dependency management UX.uv add, uv remove, and uv sync are more ergonomic than hand-editing pyproject.toml.
Built-in publishing.uv publish supports trusted publishing, replacing the separate gh-action-pypi-publish step.
What we'd lose
Named scripts. This is the main regression. Hatch scripts like hatch run generate:protoc and hatch run docs:generate give us short, memorable commands defined declaratively in pyproject.toml. Hatch effectively acts as a task runner without needing a separate tool.
uv has no task runner. There's a heavily requested open issue (630+ upvotes) but no accepted design or timeline. Astral has said they're working on scheduling this, and are watching two Python packaging standardization proposals that could lead to a PEP-standard approach.
Without a task runner, the commands get longer:
Today (Hatch)
After (uv)
hatch fmt
uv run ruff check && uv run ruff format
hatch test
uv run pytest
hatch build
uv build
hatch run docs:generate
uv run --group docs pdoc -m google crossplane/function -o docs
hatch run generate:protoc
uv run scripts/generate.py
How other projects handle the task runner gap
Most major Python projects that use uv (Pydantic, Polars, LangChain) work around this with a Makefile — targets that call uv run <tool>.
There are also third-party task runners that integrate with pyproject.toml:
Poe the Poet — configure tasks in [tool.poe.tasks], run via uv run poe <task>.
taskipy — configure tasks in [tool.taskipy.tasks], run via uv run task <name>.
These keep task definitions in pyproject.toml (like Hatch scripts) without a separate file, but they add a dev dependency.
Now that we have support for embedded Python functions in crossplane/cli, which also follows this setup, I think this is more relevant than ever.
I'd add another item to the list of arguments for uv: environment management. uv makes it incredibly easy to recreate an environment with exact dependencies. This improves devex, enables better caching in build runners, and more.
Activity
negz commentedon Mar 25, 2026
Here's a breakdown of what we'd gain and lose by switching from Hatch to uv.
Approach
Use uv for everything — project management, building, and publishing. We can use uv's own build backend (
uv_build) instead of hatchling. The two hatchling features we rely on today are replaceable:__version__.py. Today CI runshatch versionto write a version string to__version__.py, then hatchling reads it back duringhatch build. Nothing in the SDK actually imports__version__at runtime. With uv, CI could write the version directly intopyproject.tomlas a static field before runninguv build.tool.hatch.build.targets.wheel.packagesto tell hatchling about our flat layout.uv_buildsupports the same thing viatool.uv.build-backend.module-root.What we'd gain
A lockfile. uv produces a
uv.lockthat pins exact versions of all transitive dependencies, cross-platform. Today we pin direct deps but have no control over transitive ones.Less build plumbing. The entire Python toolchain becomes Astral: uv for project management and building, Ruff for linting, and potentially ty for type checking. No hatchling, no separate build backend.
Better dependency management UX.
uv add,uv remove, anduv syncare more ergonomic than hand-editingpyproject.toml.Built-in publishing.
uv publishsupports trusted publishing, replacing the separategh-action-pypi-publishstep.What we'd lose
Named scripts. This is the main regression. Hatch scripts like
hatch run generate:protocandhatch run docs:generategive us short, memorable commands defined declaratively inpyproject.toml. Hatch effectively acts as a task runner without needing a separate tool.uv has no task runner. There's a heavily requested open issue (630+ upvotes) but no accepted design or timeline. Astral has said they're working on scheduling this, and are watching two Python packaging standardization proposals that could lead to a PEP-standard approach.
Without a task runner, the commands get longer:
hatch fmtuv run ruff check && uv run ruff formathatch testuv run pytesthatch builduv buildhatch run docs:generateuv run --group docs pdoc -m google crossplane/function -o docshatch run generate:protocuv run scripts/generate.pyHow other projects handle the task runner gap
Most major Python projects that use uv (Pydantic, Polars, LangChain) work around this with a Makefile — targets that call
uv run <tool>.There are also third-party task runners that integrate with pyproject.toml:
[tool.poe.tasks], run viauv run poe <task>.[tool.taskipy.tasks], run viauv run task <name>.These keep task definitions in
pyproject.toml(like Hatch scripts) without a separate file, but they add a dev dependency.bobh66 commentedon Mar 25, 2026
My preference would be the Makefile, but that's because I have
make fooin muscle memory.eric-carlsson commentedon Jun 8, 2026
Now that we have support for embedded Python functions in crossplane/cli, which also follows this setup, I think this is more relevant than ever.
I'd add another item to the list of arguments for uv: environment management. uv makes it incredibly easy to recreate an environment with exact dependencies. This improves devex, enables better caching in build runners, and more.
How do we move forward with this?
fernandezcuesta commentedon Sep 11, 2026
I migrated some time ago to
uvin function-naming-convention only for the build, considerably faster than hatch.