-
-
Notifications
You must be signed in to change notification settings - Fork 360
Description
Problem
Metadata hooks only run when project.dynamic is non-empty. This is a problem for plugins that need to modify existing static metadata rather than populate dynamic fields.
In hatchling/metadata/core.py:
if metadata.dynamic:
for metadata_hook in metadata_hooks.values():
metadata_hook.update(self.core_raw_metadata)Even if [tool.hatch.metadata.hooks.my-plugin] is configured, the hook never runs unless project.dynamic has at least one entry. This came up in #2038 where users found this requirement counter-intuitive.
Use case
Consider a plugin that rewrites workspace dependency versions at build time:
[project]
name = "my-client"
dependencies = ["my-core"] # workspace dependency, no version
[tool.uv.sources]
my-core = { workspace = true }
[tool.hatch.metadata.hooks.my-plugin]
...The plugin reads dependencies from [project].dependencies, looks up my-core's version from the lockfile, and outputs my-core>=1.2.3 in the built wheel.
Keeping dependencies in [project].dependencies is essential. This is the standard location per PEP 621, and the entire Python tooling ecosystem expects dependencies there. Moving them elsewhere breaks interoperability with many tools, for instance:
- Dependabot can't detect dependencies outside
[project].dependencies(no custom location support, also here) - Renovate needs custom regex managers for non-standard locations
- pip-audit and safety won't scan dependencies in non-standard locations
- deptry can't verify imports match declared dependencies
- Monorepo tools like Moon, Nx, etc. use static analysis to build internal dependency graphs, non-standard locations break this
Current workarounds
Dummy PEP 621 field:
[project]
dynamic = ["license-files"] # not actually dynamic, just triggers hooksMisleading, but passes schema validation.
Arbitrary string (used by hatch-dependency-coversion), from their official docs:
[project]
# the dynamic entry must be present and the array must not be empty, otherwise hatch
# will not invoke the plugin. however, something in dynamic cannot be in the rest of
# the metadata, and only top-level keys can appear here - so if you did
# dynamic = ['dependencies'], then it (a) would not be true since it's not all the
# dependencies, just the versions of some of them and (b) you couldn't have a
# dependencies entry in the project table. so put an arbitrary string here, and the
# name of the plugin is as good an arbitrary string as any.
dynamic = ['dependency-coversioning']This works but fails schema validation in editors like Even Better TOML, and looks like a hack.
Declare dependencies as dynamic (used by uv-dynamic-versioning):
[project]
dynamic = ["dependencies"]
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
dependencies = ["my-core=={{ version }}"]This moves dependencies out of [project].dependencies entirely, which breaks all the tooling mentioned above.
Proposal
Run metadata hooks whenever [tool.hatch.metadata.hooks.*] is configured, regardless of project.dynamic. The existing validation that prevents hooks from adding undeclared fields would still apply, this just changes whether hooks run.