Hatchling Auto Extras Hook (Custom Metadata Hook Pattern)
This entry describes a common pattern for creating a custom metadata hook within Hatchling (version 1.x), enabling automatic generation or modification of project extras. While 'hatchling-autoextras-hook' exists as a PyPI package, its low version and lack of dedicated documentation suggest it's more of an illustrative or niche implementation rather than a widely used standalone plugin. The typical approach involves implementing a custom metadata hook using `hatchling` itself, which allows dynamic modification of project metadata during the build process, including `optional-dependencies` (extras). Hatchling is a modern, extensible build backend for Python projects, with a rapid release cadence.
Warnings
- breaking Hatchling 1.16.0 changed how environment dependency resolution interacts with metadata hooks. Previously, metadata hooks might be inadvertently triggered during dependency resolution. This was rectified, meaning dynamic dependencies set via a metadata hook are not available for the build environment's own dependency resolution unless explicitly handled.
- gotcha For `hatchling` to respect dynamically set metadata fields (like `optional-dependencies`), they must be explicitly listed in the `[project] dynamic = [...]` array in your `pyproject.toml`.
- gotcha When creating a custom metadata hook by specifying `path = "your_hook.py"` under `[tool.hatch.metadata.hooks.custom]`, the `PLUGIN_NAME` attribute within your `MetadataHookInterface` subclass is ignored. The plugin name will always be `custom`.
- gotcha Importing your own package's code within a metadata hook can lead to circular dependency issues or incorrect behavior because the package is not yet fully built or installed in the isolated build environment.
Install
-
pip install hatchling -
pip install hatchling-autoextras-hook
Imports
- MetadataHookInterface
from hatchling.metadata.plugin.interface import MetadataHookInterface
- CustomMetadataHook
from hatchling_autoextras_hook import AutoExtrasHook
from hatch_build import CustomMetadataHook
Quickstart
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-project"
version = "0.1.0"
dynamic = ["optional-dependencies"]
[tool.hatch.metadata.hooks.custom]
path = "hatch_build.py"
# hatch_build.py
from hatchling.metadata.plugin.interface import MetadataHookInterface
class CustomMetadataHook(MetadataHookInterface):
PLUGIN_NAME = 'custom' # This name is fixed for custom hooks defined via `path`
def update(self, metadata: dict) -> None:
# Example: dynamically generate 'docs' extra
if 'docs' not in metadata.get('optional-dependencies', {}):
metadata.setdefault('optional-dependencies', {})
metadata['optional-dependencies']['docs'] = [
"sphinx>=5.0",
"myst-parser",
]
# Example: dynamically generate 'testing' extra based on some logic
if 'TEST_ENV' in self.config: # Access hook configuration
metadata.setdefault('optional-dependencies', {})
metadata['optional-dependencies']['testing'] = [
"pytest>=7.0",
"pytest-cov",
]
# To see effect, you would typically run:
# hatch build