Skip to content

Commit 467891f

Browse files
committed
fix(sdk): populate Author/Author-email via metadata hook
PEP 621 maps `authors` to PKG-INFO's `Author-email:` line but not to the legacy single `Author:` line that `pip show` renders, and pip does not display `Maintainer:` either. As a result every previous release shipped with an empty `Author:` and the maintainer's name never appeared in `pip show nullrun`. Hatchling compounds this: its authors parser only adds an entry to `authors_data["name"]` (which becomes `Author:`) when an inline-table has a `name` and NO `email`. When both are present the name is folded into `Author-email:`'s display_name and the legacy `Author:` line is suppressed entirely. Fix: declare `authors` and `maintainers` as dynamic fields and populate them from a custom hatchling metadata hook (`hatch_build.py`). The hook splits the primary author into a name-only + email-only inline-table pair so hatchling populates both `Author:` and `Author-email:`. Declaring at least one dynamic field is what actually wires `MetadataHookInterface.update()` — without it hatchling configures the hook but never invokes it.
1 parent 2a7886b commit 467891f

2 files changed

Lines changed: 76 additions & 12 deletions

File tree

hatch_build.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Hatchling build hooks for the nullrun SDK.
2+
3+
``authors`` / ``maintainers`` injection
4+
---------------------------------------
5+
PEP 621 maps the ``authors`` array to PKG-INFO's ``Author-email:``
6+
line but does NOT populate the legacy single ``Author:`` line, and
7+
``pip show`` only renders ``Author:`` (it does not render
8+
``Maintainer:`` at all). As a result a project whose ``authors`` is
9+
``[{name=..., email=...}]`` ships with an empty ``Author:`` field and
10+
the maintainer's name never appears in ``pip show``.
11+
12+
Hatchling makes this worse: in its ``authors`` property parser
13+
(``hatchling/metadata/core.py``), an inline-table only contributes to
14+
the legacy ``Author:`` field when it has a ``name`` and NO ``email``.
15+
If both are set, the name is folded into the ``Author-email:``
16+
display_name and the ``Author:`` line is suppressed entirely.
17+
18+
This hook splits the primary author into two inline-table entries so
19+
hatchling populates both ``authors_data["name"]`` (``Author:``) and
20+
``authors_data["email"]`` (``Author-email:``)::
21+
22+
Author: Anatolii Maltsev
23+
Author-email: support@nullrun.io
24+
25+
It also sets ``maintainers`` to the publishing org for the PyPI
26+
sidebar (pip does not display ``Maintainer:``).
27+
28+
Why ``authors`` / ``maintainers`` are listed in ``project.dynamic``:
29+
hatchling only invokes ``MetadataHookInterface.update()`` when at
30+
least one field is marked dynamic. Removing the static arrays and
31+
keeping the hook as the single source of truth is what actually wires
32+
the update call.
33+
"""
34+
35+
from __future__ import annotations
36+
37+
from hatchling.metadata.plugin.interface import MetadataHookInterface
38+
39+
40+
class CustomMetadataHook(MetadataHookInterface):
41+
PLUGIN_NAME = "custom"
42+
43+
def update(self, metadata: dict) -> None:
44+
# See module docstring for the full rationale.
45+
metadata["authors"] = [
46+
{"name": "Anatolii Maltsev"},
47+
{"email": "support@nullrun.io"},
48+
]
49+
metadata["maintainers"] = [
50+
{"name": "nullrun.io", "email": "support@nullrun.io"},
51+
]

pyproject.toml

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,22 @@ readme = "README.md"
1414
license = { text = "Apache-2.0" }
1515
requires-python = ">=3.10"
1616

17-
authors = [
18-
{ name = "nullrun.io", email = "support@nullrun.io" }
19-
]
20-
21-
# Maintainer populates the PKG-INFO `Maintainer:` / `Maintainer-email:`
22-
# fields. PEP 621 maps the `authors` array to `Author-email:` but NOT
23-
# to the legacy `Author:` field, which leaves `pip show` displaying an
24-
# empty `Author:` line. Adding `maintainers` populates `Maintainer:`
25-
# instead so every metadata viewer shows non-empty contact info.
26-
maintainers = [
27-
{ name = "Anatolii Maltsev", email = "support@nullrun.io" }
28-
]
17+
# Authors and maintainers are populated dynamically by the custom
18+
# metadata hook in ``hatch_build.py``. Declaring ``authors`` here as a
19+
# dynamic field is what triggers hatchling to call
20+
# ``MetadataHookInterface.update()`` at all — without at least one
21+
# field in ``dynamic``, the hook is configured but never invoked.
22+
#
23+
# Why dynamic in the first place: PEP 621 maps the ``authors`` array to
24+
# PKG-INFO's ``Author-email:`` line but NOT to the legacy single
25+
# ``Author:`` line that ``pip show`` renders. Worse, hatchling's
26+
# authors parser (core.py, ``authors`` property) only populates the
27+
# legacy ``Author:`` field when an inline-table has a ``name`` and NO
28+
# ``email``; if both are present the name is folded into the email's
29+
# display_name and ``Author:`` is suppressed. The hook splits the
30+
# author into two inline-tables (name-only + email-only) so both lines
31+
# appear in the wheel METADATA.
32+
dynamic = ["authors", "maintainers"]
2933

3034
keywords = [
3135
"circuit-breaker", "agent", "llm", "observability",
@@ -166,6 +170,15 @@ include = [
166170
"src/nullrun/py.typed",
167171
]
168172

173+
# Custom metadata hook: rewrites ``project.authors`` into name-only +
174+
# email-only inline tables so hatchling's authors parser populates both
175+
# the legacy ``Author:`` field and the ``Author-email:`` field. See
176+
# ``hatch_build.py`` for the full rationale. The hook lives at the repo
177+
# root because hatchling discovers it by import path, not via the wheel
178+
# ``packages`` list above (which only covers the runtime package).
179+
[tool.hatch.metadata.hooks.custom]
180+
path = "hatch_build.py"
181+
169182
[tool.hatch.build.targets.sdist]
170183
exclude = [
171184
"tests/",

0 commit comments

Comments
 (0)