Skip to content

Commit d96b167

Browse files
committed
Adds compress-level to sdist and wheel configs
1 parent 4ebce0e commit d96b167

File tree

3 files changed

+71
-8
lines changed

3 files changed

+71
-8
lines changed

backend/src/hatchling/builders/sdist.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929

3030
class SdistArchive:
31-
def __init__(self, name: str, *, reproducible: bool) -> None:
31+
def __init__(self, name: str, *, reproducible: bool, compress_level: int) -> None:
3232
"""
3333
https://peps.python.org/pep-0517/#source-distributions
3434
"""
@@ -38,7 +38,8 @@ def __init__(self, name: str, *, reproducible: bool) -> None:
3838

3939
raw_fd, self.path = tempfile.mkstemp(suffix='.tar.gz')
4040
self.fd = os.fdopen(raw_fd, 'w+b')
41-
self.gz = gzip.GzipFile(fileobj=self.fd, mode='wb', mtime=self.timestamp)
41+
42+
self.gz = gzip.GzipFile(fileobj=self.fd, mode='wb', mtime=self.timestamp, compresslevel=compress_level)
4243
self.tf = tarfile.TarFile(fileobj=self.gz, mode='w', format=tarfile.PAX_FORMAT)
4344
self.gettarinfo = lambda *args, **kwargs: self.normalize_tar_metadata(self.tf.gettarinfo(*args, **kwargs))
4445

@@ -90,10 +91,32 @@ class SdistBuilderConfig(BuilderConfig):
9091
def __init__(self, *args: Any, **kwargs: Any) -> None:
9192
super().__init__(*args, **kwargs)
9293

94+
self.__compress_level: int | None = None
9395
self.__core_metadata_constructor: Callable[..., str] | None = None
9496
self.__strict_naming: bool | None = None
9597
self.__support_legacy: bool | None = None
9698

99+
@property
100+
def compress_level(self) -> int:
101+
if self.__compress_level is None:
102+
try:
103+
compress_level = int(self.target_config.get('compress-level', 9))
104+
except ValueError as e:
105+
message = f'Field `tool.hatch.build.{self.plugin_name}.compress-level` must be an integer'
106+
raise TypeError(message) from e
107+
108+
if not (0 <= compress_level <= 9): # noqa: PLR2004
109+
message = (
110+
'Value field '
111+
f'`tool.hatch.build.targets.{self.plugin_name}.compress-level` '
112+
'must be an integer from 0 to 9'
113+
)
114+
raise ValueError(message)
115+
116+
self.__compress_level = compress_level
117+
118+
return self.__compress_level
119+
97120
@property
98121
def core_metadata_constructor(self) -> Callable[..., str]:
99122
if self.__core_metadata_constructor is None:
@@ -166,7 +189,9 @@ def clean( # noqa: PLR6301
166189
def build_standard(self, directory: str, **build_data: Any) -> str:
167190
found_packages = set()
168191

169-
with SdistArchive(self.artifact_project_id, reproducible=self.config.reproducible) as archive:
192+
with SdistArchive(
193+
self.artifact_project_id, reproducible=self.config.reproducible, compress_level=self.config.compress_level
194+
) as archive:
170195
for included_file in self.recurse_included_files():
171196
if self.config.support_legacy:
172197
possible_package, file_name = os.path.split(included_file.relative_path)

backend/src/hatchling/builders/wheel.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def __exit__(
6565

6666

6767
class WheelArchive:
68-
def __init__(self, project_id: str, *, reproducible: bool) -> None:
68+
def __init__(self, project_id: str, *, reproducible: bool, compress_level: int) -> None:
6969
"""
7070
https://peps.python.org/pep-0427/#abstract
7171
"""
@@ -81,7 +81,7 @@ def __init__(self, project_id: str, *, reproducible: bool) -> None:
8181

8282
raw_fd, self.path = tempfile.mkstemp(suffix='.whl')
8383
self.fd = os.fdopen(raw_fd, 'w+b')
84-
self.zf = zipfile.ZipFile(self.fd, 'w', compression=zipfile.ZIP_DEFLATED)
84+
self.zf = zipfile.ZipFile(self.fd, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=compress_level)
8585

8686
@staticmethod
8787
def get_reproducible_time_tuple() -> TIME_TUPLE:
@@ -187,13 +187,35 @@ class WheelBuilderConfig(BuilderConfig):
187187
def __init__(self, *args: Any, **kwargs: Any) -> None:
188188
super().__init__(*args, **kwargs)
189189

190+
self.__compress_level: int | None = None
190191
self.__core_metadata_constructor: Callable[..., str] | None = None
191192
self.__shared_data: dict[str, str] | None = None
192193
self.__shared_scripts: dict[str, str] | None = None
193194
self.__extra_metadata: dict[str, str] | None = None
194195
self.__strict_naming: bool | None = None
195196
self.__macos_max_compat: bool | None = None
196197

198+
@property
199+
def compress_level(self) -> int:
200+
if self.__compress_level is None:
201+
try:
202+
compress_level = int(self.target_config.get('compress-level', 9))
203+
except ValueError as e:
204+
message = f'Field `tool.hatch.build.{self.plugin_name}.compress-level` must be an integer'
205+
raise TypeError(message) from e
206+
207+
if not (0 <= compress_level <= 9): # noqa: PLR2004
208+
message = (
209+
'Value field '
210+
f'`tool.hatch.build.targets.{self.plugin_name}.compress-level` '
211+
'must be an integer from 0 to 9'
212+
)
213+
raise ValueError(message)
214+
215+
self.__compress_level = compress_level
216+
217+
return self.__compress_level
218+
197219
@cached_property
198220
def default_file_selection_options(self) -> FileSelectionOptions:
199221
include = self.target_config.get('include', self.build_config.get('include', []))
@@ -472,7 +494,7 @@ def build_standard(self, directory: str, **build_data: Any) -> str:
472494
build_data['tag'] = self.get_default_tag()
473495

474496
with WheelArchive(
475-
self.artifact_project_id, reproducible=self.config.reproducible
497+
self.artifact_project_id, reproducible=self.config.reproducible, compress_level=self.config.compress_level
476498
) as archive, RecordFile() as records:
477499
for included_file in self.recurse_included_files():
478500
record = archive.add_file(included_file)
@@ -501,7 +523,7 @@ def build_editable_detection(self, directory: str, **build_data: Any) -> str:
501523
build_data['tag'] = self.get_default_tag()
502524

503525
with WheelArchive(
504-
self.artifact_project_id, reproducible=self.config.reproducible
526+
self.artifact_project_id, reproducible=self.config.reproducible, compress_level=self.config.compress_level
505527
) as archive, RecordFile() as records:
506528
exposed_packages = {}
507529
for included_file in self.recurse_selected_project_files():
@@ -582,7 +604,7 @@ def build_editable_explicit(self, directory: str, **build_data: Any) -> str:
582604
build_data['tag'] = self.get_default_tag()
583605

584606
with WheelArchive(
585-
self.artifact_project_id, reproducible=self.config.reproducible
607+
self.artifact_project_id, reproducible=self.config.reproducible, compress_level=self.config.compress_level
586608
) as archive, RecordFile() as records:
587609
directories = sorted(
588610
os.path.normpath(os.path.join(self.root, relative_directory))

docs/config/build.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,22 @@ skip-excluded-dirs = true
180180
!!! warning
181181
This may result in not shipping desired files. For example, if you want to include the file `a/b/c.txt` but your [VCS ignores](#vcs) `a/b`, the file `c.txt` will not be seen because its parent directory will not be entered. In such cases you can use the [`force-include`](#forced-inclusion) option.
182182

183+
#### Compression level
184+
185+
You can change the level used for compressing the sdist tarballs and wheels. In some circumstances, lowering it from the default of 9 can massively reduce build times without affecting the output size too much.
186+
187+
Note that for widely distributed packages it probably makes the most sense to use the default, highest compression level to conserve the amount of bytes transferred over the network.
188+
189+
```toml config-example
190+
[tool.hatch.build.targets.sdist]
191+
compress-level = 1
192+
193+
[tool.hatch.build.targets.wheel]
194+
compress-level = 1
195+
```
196+
197+
Accepted values are 0 (no compression) to 9 (highest compression).
198+
183199
## Reproducible builds
184200

185201
By default, [build targets](#build-targets) will build in a reproducible manner provided that they support that behavior. To disable this, set `reproducible` to `false`:

0 commit comments

Comments
 (0)