Skip to content

Commit ff58491

Browse files
authored
Merge branch 'master' into upgrade_ruff
2 parents ffc3bf4 + f400be1 commit ff58491

File tree

14 files changed

+107
-66
lines changed

14 files changed

+107
-66
lines changed

backend/src/hatchling/builders/wheel.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ def __init__(self, project_id: str, *, reproducible: bool) -> None:
8888
def get_reproducible_time_tuple() -> TIME_TUPLE:
8989
from datetime import datetime, timezone
9090

91-
d = datetime.fromtimestamp(get_reproducible_timestamp(), timezone.utc)
91+
# `zipfile.ZipInfo` does not support timestamps before 1980
92+
min_ts = 315532800 # 1980-01-01T00:00:00Z
93+
d = datetime.fromtimestamp(max(get_reproducible_timestamp(), min_ts), timezone.utc)
9294
return d.year, d.month, d.day, d.hour, d.minute, d.second
9395

9496
def add_file(self, included_file: IncludedFile) -> tuple[str, str, str]:

backend/src/hatchling/version/scheme/standard.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any, cast
3+
from typing import TYPE_CHECKING, Any, Literal, cast
44

55
from hatchling.version.scheme.plugin.interface import VersionSchemeInterface
66

@@ -28,13 +28,15 @@ def update(
2828

2929
for version in versions:
3030
if version == "release":
31-
reset_version_parts(original, release=original.release)
31+
original = reset_version_parts(original, release=original.release)
3232
elif version == "major":
33-
reset_version_parts(original, release=update_release(original, [original.major + 1]))
33+
original = reset_version_parts(original, release=update_release(original, [original.major + 1]))
3434
elif version == "minor":
35-
reset_version_parts(original, release=update_release(original, [original.major, original.minor + 1]))
35+
original = reset_version_parts(
36+
original, release=update_release(original, [original.major, original.minor + 1])
37+
)
3638
elif version in {"micro", "patch", "fix"}:
37-
reset_version_parts(
39+
original = reset_version_parts(
3840
original, release=update_release(original, [original.major, original.minor, original.micro + 1])
3941
)
4042
elif version in {"a", "b", "c", "rc", "alpha", "beta", "pre", "preview"}:
@@ -44,13 +46,13 @@ def update(
4446
if phase == current_phase:
4547
number = current_number + 1
4648

47-
reset_version_parts(original, pre=(phase, number))
49+
original = reset_version_parts(original, pre=(phase, number))
4850
elif version in {"post", "rev", "r"}:
4951
number = 0 if original.post is None else original.post + 1
50-
reset_version_parts(original, post=parse_letter_version(version, number))
52+
original = reset_version_parts(original, post=number)
5153
elif version == "dev":
5254
number = 0 if original.dev is None else original.dev + 1
53-
reset_version_parts(original, dev=(version, number))
55+
original = reset_version_parts(original, dev=number)
5456
else:
5557
if len(versions) > 1:
5658
message = "Cannot specify multiple update operations with an explicit version"
@@ -66,9 +68,13 @@ def update(
6668
return str(original)
6769

6870

69-
def reset_version_parts(version: Version, **kwargs: Any) -> None:
70-
# https://github.com/pypa/packaging/blob/20.9/packaging/version.py#L301-L310
71-
internal_version = version._version # noqa: SLF001
71+
def reset_version_parts(version: Version, **kwargs: Any) -> Version:
72+
"""
73+
Update version parts and clear all subsequent parts in the sequence.
74+
75+
When __replace__ is available (packaging 26.0+), returns a new Version instance.
76+
Otherwise mutates version via private ._version and returns the same instance.
77+
"""
7278
parts: dict[str, Any] = {}
7379
ordered_part_names = ("epoch", "release", "pre", "post", "dev", "local")
7480

@@ -80,9 +86,16 @@ def reset_version_parts(version: Version, **kwargs: Any) -> None:
8086
parts[part_name] = kwargs[part_name]
8187
reset = True
8288
else:
83-
parts[part_name] = getattr(internal_version, part_name)
89+
parts[part_name] = getattr(version, part_name)
90+
91+
# Use __replace__ if available for efficiency
92+
if hasattr(version, "__replace__"):
93+
return version.__replace__(**parts)
8494

95+
# Reference: https://github.com/pypa/packaging/blob/20.9/packaging/version.py#L301-L310
96+
internal_version = version._version # noqa: SLF001
8597
version._version = type(internal_version)(**parts) # noqa: SLF001
98+
return version
8699

87100

88101
def update_release(original_version: Version, new_release_parts: list[int]) -> tuple[int, ...]:
@@ -92,7 +105,7 @@ def update_release(original_version: Version, new_release_parts: list[int]) -> t
92105
return tuple(new_release_parts)
93106

94107

95-
def parse_letter_version(*args: Any, **kwargs: Any) -> tuple[str, int]:
108+
def parse_letter_version(*args: Any, **kwargs: Any) -> tuple[Literal["a", "b", "rc"], int]:
96109
from packaging.version import _parse_letter_version # noqa: PLC2701
97110

98-
return cast(tuple[str, int], _parse_letter_version(*args, **kwargs))
111+
return cast(tuple[Literal["a", "b", "rc"], int], _parse_letter_version(*args, **kwargs))

docs/history/hatch.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
88

99
## Unreleased
1010

11+
## [1.16.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.3) - 2026-01-20 ## {: #hatch-v1.16.3 }
12+
1113
***Added:***
1214

1315
- Env var for keep-env when an exception occurs during environment creation to enable debugging.
@@ -16,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1618

1719
- Fix issue with self-referential dependencies not being recognized.
1820
- Fix incomplete environments created when an exception occurs during creation.
21+
- Fix dependency-groups not working with when environment is not marked as builder.
22+
- Change Keyring to take expect repository URL instead of repository name.
1923

2024
## [1.16.2](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.2) - 2025-12-06 ## {: #hatch-v1.16.2 }
2125

docs/plugins/builder/wheel.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ The builder plugin name is `wheel`.
2525
| `bypass-selection` | `false` | Whether or not to suppress the error when one has not defined any file selection options and all heuristics have failed to determine what to ship |
2626
| `sbom-files` | | A list of paths to [Software Bill of Materials](https://peps.python.org/pep-0770/) files that will be included in the `.dist-info/sboms/` directory of the wheel |
2727

28+
!!! note
29+
Many build frontends will build the wheel from a source distribution. This is the recommended approach, but it means you need to ensure the SBOM files passed to `sbom-files` are also [included in the source distribution](https://hatch.pypa.io/latest/config/build/#file-selection).
2830

2931
## Versions
3032

hatch.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ update-ruff = [
118118
]
119119

120120
[envs.release]
121+
workspace.members = ["backend/"]
121122
detached = true
122123
installer = "uv"
123124

src/hatch/cli/shell/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from __future__ import annotations
22

3+
import os
34
from typing import TYPE_CHECKING
45

56
import click
67

8+
from hatch.config.constants import AppEnvVars
9+
710
if TYPE_CHECKING:
811
from hatch.cli.application import Application
912

@@ -42,7 +45,7 @@ def shell(app: Application, env_name: str | None, name: str, path: str): # no c
4245

4346
with app.project.ensure_cwd():
4447
environment = app.project.get_environment(chosen_env)
45-
app.project.prepare_environment(environment)
48+
app.project.prepare_environment(environment, keep_env=bool(os.environ.get(AppEnvVars.KEEP_ENV)))
4649

4750
first_run_indicator = app.cache_dir / "shell" / "first_run"
4851
if not first_run_indicator.is_file():

tests/backend/builders/test_wheel.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,14 @@ def test_default_auto_detection(self, hatch, helpers, temp_dir, config_file):
786786
zip_info = zip_archive.getinfo(f"{metadata_directory}/WHEEL")
787787
assert zip_info.date_time == (2020, 2, 2, 0, 0, 0)
788788

789-
def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_file):
789+
@pytest.mark.parametrize(
790+
("epoch", "expected_date_time"),
791+
[
792+
("0", (1980, 1, 1, 0, 0, 0)),
793+
("1580601700", (2020, 2, 2, 0, 1, 40)),
794+
],
795+
)
796+
def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_file, epoch, expected_date_time):
790797
config_file.model.template.plugins["default"]["src-layout"] = False
791798
config_file.save()
792799

@@ -812,7 +819,7 @@ def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_f
812819
build_path = project_path / "dist"
813820
build_path.mkdir()
814821

815-
with project_path.as_cwd(env_vars={"SOURCE_DATE_EPOCH": "1580601700"}):
822+
with project_path.as_cwd(env_vars={"SOURCE_DATE_EPOCH": epoch}):
816823
artifacts = list(builder.build(directory=str(build_path)))
817824

818825
assert len(artifacts) == 1
@@ -837,7 +844,7 @@ def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_f
837844

838845
with zipfile.ZipFile(str(expected_artifact), "r") as zip_archive:
839846
zip_info = zip_archive.getinfo(f"{metadata_directory}/WHEEL")
840-
assert zip_info.date_time == (2020, 2, 2, 0, 1, 40)
847+
assert zip_info.date_time == expected_date_time
841848

842849
def test_default_no_reproducible(self, hatch, helpers, temp_dir, config_file):
843850
config_file.model.template.plugins["default"]["src-layout"] = False

tests/backend/metadata/test_core.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,7 +1163,7 @@ def test_direct_reference_allowed(self, isolation):
11631163
},
11641164
)
11651165

1166-
assert metadata.core.dependencies == ["proj@ git+https://github.com/org/proj.git@v1"]
1166+
assert metadata.core.dependencies == ["proj @ git+https://github.com/org/proj.git@v1"]
11671167

11681168
def test_context_formatting(self, isolation, uri_slash_prefix):
11691169
metadata = ProjectMetadata(
@@ -1176,7 +1176,7 @@ def test_context_formatting(self, isolation, uri_slash_prefix):
11761176
)
11771177

11781178
normalized_path = str(isolation).replace("\\", "/")
1179-
assert metadata.core.dependencies == [f"proj@ file:{uri_slash_prefix}{normalized_path}"]
1179+
assert metadata.core.dependencies == [f"proj @ file:{uri_slash_prefix}{normalized_path}"]
11801180

11811181
def test_correct(self, isolation):
11821182
metadata = ProjectMetadata(
@@ -1343,7 +1343,7 @@ def test_context_formatting(self, isolation, uri_slash_prefix):
13431343
)
13441344

13451345
normalized_path = str(isolation).replace("\\", "/")
1346-
assert metadata.core.optional_dependencies == {"foo": [f"proj@ file:{uri_slash_prefix}{normalized_path}"]}
1346+
assert metadata.core.optional_dependencies == {"foo": [f"proj @ file:{uri_slash_prefix}{normalized_path}"]}
13471347

13481348
def test_direct_reference_allowed(self, isolation):
13491349
metadata = ProjectMetadata(
@@ -1358,7 +1358,7 @@ def test_direct_reference_allowed(self, isolation):
13581358
},
13591359
)
13601360

1361-
assert metadata.core.optional_dependencies == {"foo": ["proj@ git+https://github.com/org/proj.git@v1"]}
1361+
assert metadata.core.optional_dependencies == {"foo": ["proj @ git+https://github.com/org/proj.git@v1"]}
13621362

13631363
def test_correct(self, isolation):
13641364
metadata = ProjectMetadata(

tests/backend/metadata/test_spec.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def test_dependencies(self):
216216
Requires-Dist: bar==5; extra == 'feature2'
217217
Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'
218218
Provides-Extra: feature3
219-
Requires-Dist: baz@ file:///path/to/project ; extra == 'feature3'
219+
Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'
220220
"""
221221
assert project_metadata_from_core_metadata(core_metadata) == {
222222
"name": "My.App",
@@ -225,7 +225,7 @@ def test_dependencies(self):
225225
"optional-dependencies": {
226226
"feature1": ['bar==5; python_version < "3"', "foo==1"],
227227
"feature2": ["bar==5", 'foo==1; python_version < "3"'],
228-
"feature3": ["baz@ file:///path/to/project"],
228+
"feature3": ["baz @ file:///path/to/project"],
229229
},
230230
}
231231

@@ -990,7 +990,7 @@ def test_all(self, constructor, helpers, temp_dir):
990990
Requires-Dist: bar==5; extra == 'feature2'
991991
Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'
992992
Provides-Extra: feature3
993-
Requires-Dist: baz@ file:///path/to/project ; extra == 'feature3'
993+
Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'
994994
Description-Content-Type: text/markdown
995995
996996
test content
@@ -1459,7 +1459,7 @@ def test_all(self, constructor, helpers, temp_dir):
14591459
Requires-Dist: bar==5; extra == 'feature2'
14601460
Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'
14611461
Provides-Extra: feature3
1462-
Requires-Dist: baz@ file:///path/to/project ; extra == 'feature3'
1462+
Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'
14631463
Description-Content-Type: text/markdown
14641464
14651465
test content
@@ -1898,7 +1898,7 @@ def test_all(self, constructor, temp_dir, helpers):
18981898
Requires-Dist: bar==5; extra == 'feature2'
18991899
Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'
19001900
Provides-Extra: feature3
1901-
Requires-Dist: baz@ file:///path/to/project ; extra == 'feature3'
1901+
Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'
19021902
Description-Content-Type: text/markdown
19031903
19041904
test content
@@ -2364,7 +2364,7 @@ def test_all(self, constructor, temp_dir, helpers):
23642364
Requires-Dist: bar==5; extra == 'feature2'
23652365
Requires-Dist: foo==1; (python_version < '3') and extra == 'feature2'
23662366
Provides-Extra: feature3
2367-
Requires-Dist: baz@ file:///path/to/project ; extra == 'feature3'
2367+
Requires-Dist: baz @ file:///path/to/project ; extra == 'feature3'
23682368
Description-Content-Type: text/markdown
23692369
23702370
test content

tests/cli/dep/show/test_table.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_project_only(hatch, helpers, temp_dir, config_file):
6969
assert result.exit_code == 0, result.output
7070
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(
7171
"""
72-
Project
72+
Project
7373
+-------------+
7474
| Name |
7575
+=============+
@@ -103,7 +103,7 @@ def test_environment_only(hatch, helpers, temp_dir, config_file):
103103
assert result.exit_code == 0, result.output
104104
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(
105105
"""
106-
Env: default
106+
Env: default
107107
+-------------+
108108
| Name |
109109
+=============+
@@ -140,13 +140,13 @@ def test_default_both(hatch, helpers, temp_dir, config_file):
140140
assert result.exit_code == 0, result.output
141141
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(
142142
"""
143-
Project
143+
Project
144144
+-------------+
145145
| Name |
146146
+=============+
147147
| foo-bar-baz |
148148
+-------------+
149-
Env: default
149+
Env: default
150150
+-------------+
151151
| Name |
152152
+=============+
@@ -196,15 +196,15 @@ def test_optional_columns(hatch, helpers, temp_dir, config_file):
196196
assert result.exit_code == 0, result.output
197197
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(
198198
"""
199-
Project
199+
Project
200200
+-----------------+----------+------------------------+------------+
201201
| Name | Versions | Markers | Features |
202202
+=================+==========+========================+============+
203203
| bar-baz | >=1.2rc5 | | eddsa, tls |
204204
| foo | | python_version < '3.8' | |
205205
| python-dateutil | | | |
206206
+-----------------+----------+------------------------+------------+
207-
Env: default
207+
Env: default
208208
+---------+----------------------------------------+----------+------------------------+------------+
209209
| Name | URL | Versions | Markers | Features |
210210
+=========+========================================+==========+========================+============+
@@ -252,7 +252,7 @@ def test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_p
252252
assert helpers.remove_trailing_spaces(result.output) == helpers.dedent(
253253
"""
254254
Syncing environment plugin requirements
255-
Project
255+
Project
256256
+-------------+
257257
| Name |
258258
+=============+

0 commit comments

Comments
 (0)