Skip to content

Commit f729737

Browse files
authored
Merge pull request #47 from python-project-templates/preserve-split-sdist-metadata
Preserve split package metadata in sdists
2 parents 43f3660 + d8c8ba6 commit f729737

7 files changed

Lines changed: 110 additions & 6 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ dynamic = ["dependencies"]
2323
main = [...]
2424
other = [...]
2525

26-
[tool.hatch.metadata.hooks.multi]
26+
[tool.hatch.metadata.hooks.hatch-multi]
2727
primary = "main"
28+
29+
# Required for split sdists to retain their package name when installed.
30+
[tool.hatch.build.targets.sdist.hooks.hatch-multi]
2831
```
2932

3033
```bash

hatch_multi/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__version__ = "1.0.0"
22

3-
from .hooks import hatch_register_metadata_hook
4-
from .plugin import HatchMultiMetadataHook
3+
from .hooks import hatch_register_build_hook, hatch_register_metadata_hook
4+
from .plugin import HatchMultiBuildHook, HatchMultiMetadataHook
55
from .structs import *

hatch_multi/hooks.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from hatchling.plugin import hookimpl
22

3-
from .plugin import HatchMultiMetadataHook
3+
from .plugin import HatchMultiBuildHook, HatchMultiMetadataHook
4+
5+
6+
@hookimpl
7+
def hatch_register_build_hook() -> type[HatchMultiBuildHook]:
8+
return HatchMultiBuildHook
49

510

611
@hookimpl

hatch_multi/plugin.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,64 @@
11
from __future__ import annotations
22

3+
from json import dumps
34
from logging import getLogger
45
from os import getenv
6+
from pathlib import Path
7+
from tempfile import NamedTemporaryFile
58

9+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
610
from hatchling.metadata.plugin.interface import MetadataHookInterface
711

812
from .structs import HatchMultiConfig
913

10-
__all__ = ("HatchMultiMetadataHook",)
14+
__all__ = ("HatchMultiBuildHook", "HatchMultiMetadataHook")
1115

1216

13-
class HatchMultiMetadataHook(MetadataHookInterface):
17+
class HatchMultiBuildHook(BuildHookInterface):
1418
"""The hatch-multi build hook."""
1519

20+
PLUGIN_NAME = "hatch-multi"
21+
_temporary_project_file: Path | None = None
22+
23+
def initialize(self, version: str, build_data: dict) -> None:
24+
if self.target_name != "sdist":
25+
return
26+
27+
configured_name = self.metadata.config["project"]["name"]
28+
package_name = self.metadata.core.raw_name
29+
if package_name == configured_name:
30+
return
31+
32+
project_file = Path(self.root, "pyproject.toml")
33+
lines = project_file.read_text(encoding="utf-8").splitlines(keepends=True)
34+
in_project_table = False
35+
for index, line in enumerate(lines):
36+
stripped_line = line.strip()
37+
if stripped_line.startswith("[") and stripped_line.endswith("]"):
38+
in_project_table = stripped_line == "[project]"
39+
elif in_project_table:
40+
key, separator, _value = line.partition("=")
41+
if separator and key.strip() == "name":
42+
newline = "\n" if line.endswith("\n") else ""
43+
lines[index] = f"{key}= {dumps(package_name)}{newline}"
44+
break
45+
46+
with NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".toml", delete=False) as temporary_project_file:
47+
temporary_project_file.writelines(lines)
48+
self._temporary_project_file = Path(temporary_project_file.name)
49+
50+
build_data["force_include"].pop(str(project_file), None)
51+
build_data["force_include"][str(self._temporary_project_file)] = "pyproject.toml"
52+
53+
def finalize(self, version: str, build_data: dict, artifact_path: str) -> None:
54+
if self._temporary_project_file is not None:
55+
self._temporary_project_file.unlink()
56+
self._temporary_project_file = None
57+
58+
59+
class HatchMultiMetadataHook(MetadataHookInterface):
60+
"""The hatch-multi metadata hook."""
61+
1662
PLUGIN_NAME = "hatch-multi"
1763
_logger = getLogger(__name__)
1864

hatch_multi/tests/test_project_basic/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ packages = ["project"]
2424

2525
[tool.hatch.metadata.hooks.hatch-multi]
2626
primary = "main"
27+
28+
[tool.hatch.build.targets.sdist.hooks.hatch-multi]

hatch_multi/tests/test_project_multiple_primary/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ packages = ["project"]
2424

2525
[tool.hatch.metadata.hooks.hatch-multi]
2626
default = ["main", "other"]
27+
28+
[tool.hatch.build.targets.sdist.hooks.hatch-multi]

hatch_multi/tests/test_projects_basic.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from shutil import rmtree
44
from subprocess import check_call
55
from sys import executable
6+
from tarfile import TarFile
67
from zipfile import ZipFile
78

89

@@ -105,3 +106,48 @@ def test_basic():
105106
"""
106107
)
107108
rmtree(f"hatch_multi/tests/{project}/dist")
109+
110+
111+
def test_sdist_preserves_extra():
112+
project = "test_project_basic"
113+
project_root = Path(f"hatch_multi/tests/{project}")
114+
rmtree(project_root / "dist", ignore_errors=True)
115+
116+
check_call(
117+
[
118+
executable,
119+
"-m",
120+
"build",
121+
"-n",
122+
"-s",
123+
],
124+
cwd=project_root,
125+
env={"HATCH_MULTI_BUILD": "other"},
126+
)
127+
128+
sdist_name = "hatch_cpp_test_project_basic_other-0.1.0.tar.gz"
129+
assert listdir(project_root / "dist") == [sdist_name]
130+
with TarFile.open(project_root / "dist" / sdist_name, "r:gz") as tar_file:
131+
tar_file.extractall(project_root / "dist" / "extracted", filter="data")
132+
133+
source_root = project_root / "dist" / "extracted" / "hatch_cpp_test_project_basic_other-0.1.0"
134+
check_call(
135+
[
136+
executable,
137+
"-m",
138+
"build",
139+
"-n",
140+
"-w",
141+
],
142+
cwd=source_root,
143+
env={},
144+
)
145+
146+
wheel_name = "hatch_cpp_test_project_basic_other-0.1.0-py3-none-any.whl"
147+
assert listdir(source_root / "dist") == [wheel_name]
148+
with ZipFile(source_root / "dist" / wheel_name) as zip_file:
149+
metadata = zip_file.read("hatch_cpp_test_project_basic_other-0.1.0.dist-info/METADATA").decode()
150+
151+
assert "Name: hatch-cpp-test-project-basic-other\n" in metadata
152+
assert "Requires-Dist: organizeit2\n" in metadata
153+
rmtree(project_root / "dist")

0 commit comments

Comments
 (0)