vcs-versioning 2.1.2__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/CHANGELOG.md +11 -0
  2. {vcs_versioning-2.1.2/src/vcs_versioning.egg-info → vcs_versioning-2.2.0}/PKG-INFO +6 -4
  3. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/pyproject.toml +7 -5
  4. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/__init__.py +9 -2
  5. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_compat.py +32 -4
  6. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_entrypoints.py +3 -2
  7. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/__init__.py +6 -1
  8. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_legacy_parse.py +11 -4
  9. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_overrides.py +14 -4
  10. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_pyproject_reading.py +7 -3
  11. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_run_cmd.py +7 -1
  12. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_scm_metadata.py +8 -7
  13. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_scm_version.py +7 -3
  14. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_toml.py +17 -7
  15. vcs_versioning-2.2.0/src/vcs_versioning/_types.py +40 -0
  16. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_cls.py +9 -3
  17. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_worktree_discovery.py +3 -3
  18. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0/src/vcs_versioning.egg-info}/PKG-INFO +6 -4
  19. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/requires.txt +1 -1
  20. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_overrides_api.py +1 -4
  21. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_workdir_discovery.py +0 -45
  22. vcs_versioning-2.1.2/src/vcs_versioning/_types.py +0 -29
  23. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/LICENSE.txt +0 -0
  24. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/MANIFEST.in +0 -0
  25. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/README.md +0 -0
  26. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/setup.cfg +0 -0
  27. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/setup.py +0 -0
  28. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/__init__.py +0 -0
  29. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/__main__.py +0 -0
  30. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/__init__.py +0 -0
  31. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_discover_vcs.py +0 -0
  32. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_git.py +0 -0
  33. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_hg.py +0 -0
  34. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_hg_git.py +0 -0
  35. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_jj.py +0 -0
  36. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_backends/_scm_workdir.py +0 -0
  37. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/_args.py +0 -0
  38. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/git_archival_full.txt +0 -0
  39. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_cli/git_archival_stable.txt +0 -0
  40. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_config.py +0 -0
  41. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_discover.py +0 -0
  42. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_dump_version.py +0 -0
  43. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_environment.py +0 -0
  44. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_exceptions.py +0 -0
  45. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_fallback_workdir.py +0 -0
  46. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_fallbacks.py +0 -0
  47. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_git.py +0 -0
  48. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_hg.py +0 -0
  49. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_file_finders/_jj.py +0 -0
  50. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_get_version_impl.py +0 -0
  51. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_integration.py +0 -0
  52. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_integrator_helpers.py +0 -0
  53. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_log.py +0 -0
  54. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_modify_version.py +0 -0
  55. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_node_utils.py +0 -0
  56. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_paths.py +0 -0
  57. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_project_overrides.py +0 -0
  58. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_protocols.py +0 -0
  59. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_requirement_cls.py +0 -0
  60. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_test_utils.py +0 -0
  61. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_fields.py +0 -0
  62. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_inference.py +0 -0
  63. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/__init__.py +0 -0
  64. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_common.py +0 -0
  65. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_standard.py +0 -0
  66. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/_version_schemes/_towncrier.py +0 -0
  67. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/overrides.py +0 -0
  68. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/py.typed +0 -0
  69. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning/test_api.py +0 -0
  70. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/SOURCES.txt +0 -0
  71. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/dependency_links.txt +0 -0
  72. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/entry_points.txt +0 -0
  73. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/src/vcs_versioning.egg-info/top_level.txt +0 -0
  74. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/__init__.py +0 -0
  75. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/conftest.py +0 -0
  76. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_better_root_errors.py +0 -0
  77. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_chain_api.py +0 -0
  78. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_compat.py +0 -0
  79. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_config.py +0 -0
  80. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_expect_parse.py +0 -0
  81. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_file_finders.py +0 -0
  82. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_git.py +0 -0
  83. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_hg_git.py +0 -0
  84. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_integrator_helpers.py +0 -0
  85. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_internal_log_level.py +0 -0
  86. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_jj.py +0 -0
  87. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_legacy_parse.py +0 -0
  88. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_mercurial.py +0 -0
  89. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_overrides_env_reader.py +0 -0
  90. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_project_overrides.py +0 -0
  91. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_project_path.py +0 -0
  92. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_regressions.py +0 -0
  93. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_scm_metadata.py +0 -0
  94. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_tag_config.py +0 -0
  95. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_version.py +0 -0
  96. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_version_scheme_towncrier.py +0 -0
  97. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_version_schemes.py +0 -0
  98. {vcs_versioning-2.1.2 → vcs_versioning-2.2.0}/testing_vcs/test_workdir_api.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  <!-- towncrier release notes start -->
4
4
 
5
+ ## 2.2.0 (2026-06-24)
6
+
7
+ ### Added
8
+
9
+ - Restore Python 3.8 and 3.9 support, re-enabling use as a build dependency for projects like scikit-build that still support these versions. ([#1445](https://github.com/pypa/setuptools-scm/issues/1445))
10
+
11
+
12
+ ### Miscellaneous
13
+
14
+ - Move PKG-INFO discovery tests to setuptools-scm where the entry points are registered. ([#1446](https://github.com/pypa/setuptools-scm/issues/1446))
15
+
5
16
  ## 2.1.2 (2026-06-23)
6
17
 
7
18
  ### Fixed
@@ -1,26 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vcs-versioning
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: the blessed package to manage your versions by vcs metadata
5
5
  Author-email: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
6
- License-Expression: MIT
6
+ License: MIT
7
7
  Project-URL: Documentation, https://github.com/pypa/setuptools-scm#readme
8
8
  Project-URL: Issues, https://github.com/pypa/setuptools-scm/issues
9
9
  Project-URL: Source, https://github.com/pypa/setuptools-scm
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
13
15
  Classifier: Programming Language :: Python :: 3.10
14
16
  Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: Programming Language :: Python :: 3.12
16
18
  Classifier: Programming Language :: Python :: 3.13
17
19
  Classifier: Programming Language :: Python :: 3.14
18
- Requires-Python: >=3.10
20
+ Requires-Python: >=3.8
19
21
  Description-Content-Type: text/markdown
20
22
  License-File: LICENSE.txt
21
23
  Requires-Dist: packaging>=20
22
24
  Requires-Dist: tomli>=1; python_version < "3.11"
23
- Requires-Dist: typing-extensions; python_version < "3.11"
25
+ Requires-Dist: typing-extensions>=4.1; python_version < "3.11"
24
26
  Dynamic: license-file
25
27
 
26
28
  # vcs-versioning
@@ -6,9 +6,9 @@
6
6
  [build-system]
7
7
  build-backend = "setuptools.build_meta"
8
8
  requires = [
9
- "setuptools>=77.0.3",
9
+ "setuptools>=45",
10
10
  "packaging>=20",
11
- 'typing-extensions; python_version < "3.11"',
11
+ 'typing-extensions>=4.1; python_version < "3.11"',
12
12
  ]
13
13
 
14
14
  [project]
@@ -17,15 +17,17 @@ description = "the blessed package to manage your versions by vcs metadata"
17
17
  readme = "README.md"
18
18
  keywords = [
19
19
  ]
20
- license = "MIT"
20
+ license = {text = "MIT"}
21
21
  authors = [
22
22
  { name = "Ronny Pfannschmidt", email = "opensource@ronnypfannschmidt.de" },
23
23
  ]
24
- requires-python = ">=3.10"
24
+ requires-python = ">=3.8"
25
25
  classifiers = [
26
26
  "Development Status :: 4 - Beta",
27
27
  "Programming Language :: Python",
28
28
  "Programming Language :: Python :: 3 :: Only",
29
+ "Programming Language :: Python :: 3.8",
30
+ "Programming Language :: Python :: 3.9",
29
31
  "Programming Language :: Python :: 3.10",
30
32
  "Programming Language :: Python :: 3.11",
31
33
  "Programming Language :: Python :: 3.12",
@@ -38,7 +40,7 @@ dynamic = [
38
40
  dependencies = [
39
41
  "packaging>=20",
40
42
  'tomli>=1; python_version < "3.11"',
41
- 'typing-extensions; python_version < "3.11"',
43
+ 'typing-extensions>=4.1; python_version < "3.11"',
42
44
  ]
43
45
 
44
46
  [dependency-groups]
@@ -4,7 +4,11 @@ import json
4
4
  import os
5
5
  import sys
6
6
  from collections.abc import Iterable
7
- from importlib.resources import files
7
+
8
+ if sys.version_info >= (3, 9):
9
+ from importlib.resources import files
10
+ else:
11
+ from importlib.resources import read_text as _read_text
8
12
  from pathlib import Path
9
13
  from typing import TypedDict
10
14
 
@@ -182,7 +186,10 @@ def _create_archival_file(opts: CliNamespace, config: Configuration) -> int:
182
186
  assert opts.archival_template is not None
183
187
 
184
188
  # Load template content from package resources
185
- content = files(__package__).joinpath(opts.archival_template).read_text("utf-8")
189
+ if sys.version_info >= (3, 9):
190
+ content = files(__package__).joinpath(opts.archival_template).read_text("utf-8")
191
+ else:
192
+ content = _read_text(__package__, opts.archival_template, encoding="utf-8")
186
193
 
187
194
  # Print appropriate message based on template
188
195
  if opts.archival_template == "git_archival_stable.txt":
@@ -3,10 +3,38 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from typing import TypeAlias
7
-
8
- # Path type for accepting both strings and PathLike objects
9
- PathT: TypeAlias = os.PathLike[str] | str
6
+ import sys
7
+ from importlib.metadata import entry_points as _stdlib_entry_points
8
+ from typing import TYPE_CHECKING, Union
9
+
10
+ if sys.version_info >= (3, 10):
11
+ from typing import TypeAlias
12
+ else:
13
+ from typing_extensions import TypeAlias
14
+
15
+ if TYPE_CHECKING:
16
+ from importlib.metadata import EntryPoint
17
+
18
+ PathT: TypeAlias = os.PathLike[str] | str
19
+ else:
20
+ PathT: TypeAlias = Union[os.PathLike, str]
21
+
22
+
23
+ if sys.version_info >= (3, 10):
24
+ from importlib.metadata import entry_points as entry_points
25
+ else:
26
+
27
+ def entry_points(
28
+ **params: str,
29
+ ) -> list[EntryPoint]:
30
+ """Backport of entry_points(group=...) for Python 3.8/3.9."""
31
+ groups = _stdlib_entry_points()
32
+ group = params.get("group", "")
33
+ name = params.get("name")
34
+ eps = list(groups.get(group, [])) # type: ignore[call-overload]
35
+ if name is not None:
36
+ eps = [ep for ep in eps if ep.name == name]
37
+ return eps
10
38
 
11
39
 
12
40
  def normalize_path_for_assertion(path: str) -> str:
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
  import logging
4
4
  from collections.abc import Callable, Iterator
5
5
  from importlib import metadata as im
6
- from importlib.metadata import entry_points
7
6
  from typing import TYPE_CHECKING, Any, cast
8
7
 
8
+ from ._compat import entry_points
9
+
9
10
  __all__ = [
10
11
  "entry_points",
11
12
  "im",
@@ -65,7 +66,7 @@ def _iter_version_schemes(
65
66
  or _get_from_object_reference_str(scheme_value, entrypoint),
66
67
  )
67
68
 
68
- if isinstance(scheme_value, list | tuple):
69
+ if isinstance(scheme_value, (list, tuple)):
69
70
  for variant in scheme_value:
70
71
  if variant not in _memo:
71
72
  _memo.add(variant)
@@ -2,8 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import os
5
+ import sys
5
6
  from collections.abc import Callable, Mapping
6
- from typing import TypeGuard
7
+
8
+ if sys.version_info >= (3, 10):
9
+ from typing import TypeGuard
10
+ else:
11
+ from typing_extensions import TypeGuard
7
12
 
8
13
  from .. import _types as _t
9
14
  from .._compat import norm_real
@@ -13,8 +13,6 @@ from __future__ import annotations
13
13
 
14
14
  import logging
15
15
  import warnings
16
- from dataclasses import dataclass
17
- from dataclasses import field as dc_field
18
16
  from pathlib import Path
19
17
  from typing import TYPE_CHECKING
20
18
 
@@ -29,7 +27,6 @@ if TYPE_CHECKING:
29
27
  log = logging.getLogger(__name__)
30
28
 
31
29
 
32
- @dataclass()
33
30
  class LegacyParseWorkdir(ScmWorkdir):
34
31
  """Wraps a legacy ``config.parse`` callable as a workdir.
35
32
 
@@ -38,7 +35,17 @@ class LegacyParseWorkdir(ScmWorkdir):
38
35
  entry-point group.
39
36
  """
40
37
 
41
- parse_fn: ParseFunction = dc_field(repr=False, kw_only=True)
38
+ parse_fn: ParseFunction
39
+
40
+ def __init__(
41
+ self,
42
+ path: Path,
43
+ *,
44
+ parse_fn: ParseFunction,
45
+ _config: Configuration | None = None,
46
+ ) -> None:
47
+ super().__init__(path=path, _config=_config)
48
+ self.parse_fn = parse_fn
42
49
 
43
50
  def get_scm_version(self) -> ScmVersion | None:
44
51
  warnings.warn(
@@ -207,15 +207,25 @@ def _apply_metadata_overrides(
207
207
 
208
208
  log.info("Applying metadata overrides: %s", metadata_overrides)
209
209
 
210
- # Get type hints from PretendMetadataDict for validation
211
- field_types = get_type_hints(PretendMetadataDict)
210
+ # Get valid field names from PretendMetadataDict for validation.
211
+ # Use __annotations__ keys directly instead of get_type_hints() to avoid
212
+ # evaluating PEP 604 forward references on Python <3.10.
213
+ valid_fields = frozenset(PretendMetadataDict.__annotations__)
214
+
215
+ # Try to resolve actual types for runtime validation (best-effort)
216
+ try:
217
+ field_types: dict[str, Any] | None = get_type_hints(PretendMetadataDict)
218
+ except TypeError:
219
+ field_types = None
212
220
 
213
221
  # Apply each override individually using dataclasses.replace
214
222
  result = scm_version
215
223
 
216
224
  for field, value in metadata_overrides.items():
217
- # Validate field types using the TypedDict annotations
218
- if field in field_types:
225
+ if field not in valid_fields:
226
+ continue
227
+ # Runtime type validation (only when type hints are resolvable)
228
+ if field_types is not None and field in field_types:
219
229
  expected_type = field_types[field]
220
230
  # Handle Optional/Union types (e.g., str | None)
221
231
  if hasattr(expected_type, "__args__"):
@@ -9,7 +9,11 @@ import warnings
9
9
  from collections.abc import Sequence
10
10
  from dataclasses import dataclass
11
11
  from pathlib import Path
12
- from typing import TypeAlias
12
+
13
+ if sys.version_info >= (3, 10):
14
+ from typing import TypeAlias
15
+ else:
16
+ from typing_extensions import TypeAlias
13
17
 
14
18
  if sys.version_info >= (3, 11):
15
19
  from typing import Self
@@ -158,7 +162,7 @@ class PyProjectData:
158
162
 
159
163
  # Testing injection type for configuration reading
160
164
  GivenPyProjectResult: TypeAlias = (
161
- PyProjectData | InvalidTomlError | FileNotFoundError | None
165
+ "PyProjectData | InvalidTomlError | FileNotFoundError | None"
162
166
  )
163
167
 
164
168
 
@@ -201,7 +205,7 @@ def read_pyproject(
201
205
  if _given_result is not None:
202
206
  if isinstance(_given_result, PyProjectData):
203
207
  return _given_result
204
- if isinstance(_given_result, InvalidTomlError | FileNotFoundError):
208
+ if isinstance(_given_result, (InvalidTomlError, FileNotFoundError)):
205
209
  raise _given_result
206
210
 
207
211
  if _given_definition is not None:
@@ -4,6 +4,7 @@ import logging
4
4
  import os
5
5
  import shlex
6
6
  import subprocess
7
+ import sys
7
8
  import textwrap
8
9
  import warnings
9
10
  from collections.abc import Callable, Mapping, Sequence
@@ -35,8 +36,13 @@ def _get_timeout(env: Mapping[str, str]) -> int:
35
36
  PARSE_RESULT = TypeVar("PARSE_RESULT")
36
37
  T = TypeVar("T")
37
38
 
39
+ if sys.version_info >= (3, 9):
40
+ _CompletedProcessBase = subprocess.CompletedProcess[str]
41
+ else:
42
+ _CompletedProcessBase = subprocess.CompletedProcess
38
43
 
39
- class CompletedProcess(subprocess.CompletedProcess[str]):
44
+
45
+ class CompletedProcess(_CompletedProcessBase):
40
46
  @classmethod
41
47
  def from_raw(
42
48
  cls, input: subprocess.CompletedProcess[str], strip: bool = True
@@ -99,13 +99,14 @@ def scm_version_data_from_scm_version(
99
99
  scm_version: ScmVersion | VersionFields,
100
100
  ) -> ScmVersionData:
101
101
  """Build ``ScmVersionData`` from a live ``ScmVersion`` object."""
102
- match scm_version.node_date:
103
- case date() as d:
104
- node_date_str: str | None = d.isoformat()
105
- case str() as s:
106
- node_date_str = s
107
- case _:
108
- node_date_str = None
102
+ node_date = scm_version.node_date
103
+ node_date_str: str | None
104
+ if isinstance(node_date, date):
105
+ node_date_str = node_date.isoformat()
106
+ elif isinstance(node_date, str):
107
+ node_date_str = node_date
108
+ else:
109
+ node_date_str = None
109
110
  return ScmVersionData(
110
111
  tag=str(scm_version.tag),
111
112
  distance=scm_version.distance,
@@ -9,10 +9,16 @@ from __future__ import annotations
9
9
 
10
10
  import dataclasses
11
11
  import logging
12
+ import sys
12
13
  import warnings
13
14
  from collections.abc import Callable
14
15
  from datetime import date, datetime
15
- from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypedDict
16
+ from typing import TYPE_CHECKING, Any, TypedDict
17
+
18
+ if sys.version_info >= (3, 10):
19
+ from typing import Concatenate, ParamSpec
20
+ else:
21
+ from typing_extensions import Concatenate, ParamSpec
16
22
 
17
23
  from . import _config
18
24
  from . import _version_cls as _v
@@ -20,8 +26,6 @@ from ._node_utils import _format_node_for_output
20
26
  from ._version_cls import _Version
21
27
 
22
28
  if TYPE_CHECKING:
23
- import sys
24
-
25
29
  if sys.version_info >= (3, 11):
26
30
  from typing import Unpack
27
31
  else:
@@ -4,7 +4,12 @@ import logging
4
4
  import sys
5
5
  from collections.abc import Callable
6
6
  from pathlib import Path
7
- from typing import Any, TypeAlias, TypedDict, TypeVar, cast, get_type_hints
7
+ from typing import Any, TypedDict, TypeVar, cast, get_type_hints
8
+
9
+ if sys.version_info >= (3, 10):
10
+ from typing import TypeAlias
11
+ else:
12
+ from typing_extensions import TypeAlias
8
13
 
9
14
  if sys.version_info >= (3, 11):
10
15
  from tomllib import loads as load_toml
@@ -14,8 +19,8 @@ else:
14
19
 
15
20
  log = logging.getLogger(__name__)
16
21
 
17
- TOML_RESULT: TypeAlias = dict[str, Any]
18
- TOML_LOADER: TypeAlias = Callable[[str], TOML_RESULT]
22
+ TOML_RESULT: TypeAlias = "dict[str, Any]"
23
+ TOML_LOADER: TypeAlias = "Callable[[str], TOML_RESULT]"
19
24
 
20
25
  # TypeVar for generic TypedDict support - the schema defines the return type
21
26
  TSchema = TypeVar("TSchema", bound=TypedDict) # type: ignore[valid-type]
@@ -71,10 +76,15 @@ def _validate_against_schema(
71
76
  # Extract valid field names from the TypedDict
72
77
  try:
73
78
  valid_fields = frozenset(get_type_hints(schema).keys())
74
- except NameError as e:
75
- # If type hints can't be resolved, log warning and skip validation
76
- log.warning("Could not resolve type hints for schema validation: %s", e)
77
- return data
79
+ except (NameError, TypeError) as e:
80
+ # If type hints can't be resolved (e.g. PEP 604 unions on Python <3.10),
81
+ # fall back to __annotations__ keys directly
82
+ annotations = getattr(schema, "__annotations__", None)
83
+ if annotations:
84
+ valid_fields = frozenset(annotations.keys())
85
+ else:
86
+ log.warning("Could not resolve type hints for schema validation: %s", e)
87
+ return data
78
88
 
79
89
  # If the schema has no fields (empty TypedDict), skip validation
80
90
  if not valid_fields:
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from collections.abc import Callable, Sequence
5
+ from typing import TYPE_CHECKING, Union
6
+
7
+ if sys.version_info >= (3, 10):
8
+ from typing import TypeAlias
9
+ else:
10
+ from typing_extensions import TypeAlias
11
+
12
+ if TYPE_CHECKING:
13
+ from ._scm_version import ScmVersion
14
+
15
+ # Re-export from _compat for backward compatibility
16
+ from ._compat import PathT as PathT # noqa: PLC0414
17
+
18
+ __all__ = [
19
+ "PathT",
20
+ "CMD_TYPE",
21
+ "VERSION_SCHEME",
22
+ "VERSION_SCHEMES",
23
+ "SCMVERSION",
24
+ "GIT_PRE_PARSE",
25
+ ]
26
+
27
+ if TYPE_CHECKING:
28
+ CMD_TYPE: TypeAlias = Sequence[PathT] | str
29
+ VERSION_SCHEME_CALLABLE: TypeAlias = Callable[["ScmVersion"], str | None]
30
+ VERSION_SCHEME: TypeAlias = str | VERSION_SCHEME_CALLABLE
31
+ VERSION_SCHEMES: TypeAlias = Sequence[VERSION_SCHEME] | VERSION_SCHEME
32
+ GIT_PRE_PARSE: TypeAlias = str | None
33
+ else:
34
+ CMD_TYPE = Union[Sequence, str]
35
+ VERSION_SCHEME_CALLABLE = Callable
36
+ VERSION_SCHEME = Union[str, Callable]
37
+ VERSION_SCHEMES = Union[Sequence, str, Callable]
38
+ GIT_PRE_PARSE = Union[str, None]
39
+
40
+ SCMVERSION: TypeAlias = "ScmVersion"
@@ -1,7 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from typing import TypeAlias, cast
4
+ import sys
5
+ from typing import cast
6
+
7
+ if sys.version_info >= (3, 10):
8
+ from typing import TypeAlias
9
+ else:
10
+ from typing_extensions import TypeAlias
5
11
 
6
12
  try:
7
13
  from packaging.version import InvalidVersion
@@ -67,7 +73,7 @@ def _version_as_tuple(version_str: str) -> tuple[int | str, ...]:
67
73
  return version_fields
68
74
 
69
75
 
70
- _Version: TypeAlias = Version | NonNormalizedVersion
76
+ _Version: TypeAlias = "Version | NonNormalizedVersion"
71
77
 
72
78
 
73
79
  def import_name(name: str) -> object:
@@ -93,7 +99,7 @@ def _validate_version_cls(
93
99
  return Version
94
100
  elif isinstance(version_cls, str):
95
101
  try:
96
- return cast(type[_Version], import_name(version_cls))
102
+ return cast("type[_Version]", import_name(version_cls))
97
103
  except Exception:
98
104
  raise ValueError(f"Unable to import version_cls='{version_cls}'") from None
99
105
  else:
@@ -15,17 +15,17 @@ SCM results are preferred over fallback results.
15
15
  from __future__ import annotations
16
16
 
17
17
  import logging
18
- from importlib.metadata import entry_points
19
18
  from pathlib import Path
20
- from typing import Protocol
19
+ from typing import Protocol, Union
21
20
 
22
21
  from ._backends._scm_workdir import ScmWorkdir
22
+ from ._compat import entry_points
23
23
  from ._config import Configuration
24
24
  from ._fallback_workdir import FallbackWorkdir, StaticWorkdir
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
28
- AnyWorkdir = ScmWorkdir | FallbackWorkdir
28
+ AnyWorkdir = Union[ScmWorkdir, FallbackWorkdir]
29
29
 
30
30
 
31
31
  class DiscoveryFactory(Protocol):
@@ -1,26 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vcs-versioning
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: the blessed package to manage your versions by vcs metadata
5
5
  Author-email: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
6
- License-Expression: MIT
6
+ License: MIT
7
7
  Project-URL: Documentation, https://github.com/pypa/setuptools-scm#readme
8
8
  Project-URL: Issues, https://github.com/pypa/setuptools-scm/issues
9
9
  Project-URL: Source, https://github.com/pypa/setuptools-scm
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
13
15
  Classifier: Programming Language :: Python :: 3.10
14
16
  Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: Programming Language :: Python :: 3.12
16
18
  Classifier: Programming Language :: Python :: 3.13
17
19
  Classifier: Programming Language :: Python :: 3.14
18
- Requires-Python: >=3.10
20
+ Requires-Python: >=3.8
19
21
  Description-Content-Type: text/markdown
20
22
  License-File: LICENSE.txt
21
23
  Requires-Dist: packaging>=20
22
24
  Requires-Dist: tomli>=1; python_version < "3.11"
23
- Requires-Dist: typing-extensions; python_version < "3.11"
25
+ Requires-Dist: typing-extensions>=4.1; python_version < "3.11"
24
26
  Dynamic: license-file
25
27
 
26
28
  # vcs-versioning
@@ -2,4 +2,4 @@ packaging>=20
2
2
 
3
3
  [:python_version < "3.11"]
4
4
  tomli>=1
5
- typing-extensions
5
+ typing-extensions>=4.1
@@ -698,10 +698,7 @@ def test_read_toml_overrides_with_schema(
698
698
  "SETUPTOOLS_SCM_OVERRIDES": '{version_scheme = "guess-next-dev", local_scheme = "no-local-version", invalid_field = "bad"}'
699
699
  }
700
700
 
701
- with (
702
- patch.dict(os.environ, mock_env, clear=True),
703
- caplog.at_level(logging.WARNING),
704
- ):
701
+ with patch.dict(os.environ, mock_env, clear=True), caplog.at_level(logging.WARNING):
705
702
  result = read_toml_overrides(dist_name=None)
706
703
 
707
704
  # Valid fields should be present
@@ -11,7 +11,6 @@ from vcs_versioning._config import Configuration
11
11
  from vcs_versioning._fallback_workdir import (
12
12
  ArchivedWorkdir,
13
13
  MetadataWorkdir,
14
- PkgInfoWorkdir,
15
14
  StaticWorkdir,
16
15
  )
17
16
  from vcs_versioning._scm_metadata import (
@@ -130,16 +129,6 @@ class TestDiscoverWorkdirGit:
130
129
 
131
130
 
132
131
  class TestDiscoverWorkdirFallback:
133
- def test_discovers_pkginfo(self, tmp_path: Path) -> None:
134
- (tmp_path / "PKG-INFO").write_text(
135
- "Metadata-Version: 2.1\nName: pkg\nVersion: 3.0.0\n",
136
- encoding="utf-8",
137
- )
138
- config = Configuration(relative_to=str(tmp_path / "pyproject.toml"))
139
- result = discover_workdir(config)
140
- assert result is not None
141
- assert isinstance(result, PkgInfoWorkdir)
142
-
143
132
  def test_discovers_git_archival(self, tmp_path: Path) -> None:
144
133
  (tmp_path / ".git_archival.txt").write_text(
145
134
  "node: abc1234\nref-names: HEAD\n",
@@ -219,40 +208,6 @@ class TestProjectPathVerificationInDiscovery:
219
208
  discover_workdir(config)
220
209
 
221
210
 
222
- class TestFallbackPriority:
223
- @pytest.mark.issue(1431)
224
- def test_unprocessed_archival_falls_through_to_pkginfo(
225
- self, tmp_path: Path
226
- ) -> None:
227
- """Unprocessed .git_archival.txt must not shadow a valid PKG-INFO.
228
-
229
- PyPI sdists contain both files: a .git_archival.txt with raw
230
- ``$Format:...`` placeholders (never substituted because the sdist
231
- was built by setuptools, not ``git archive``) and a PKG-INFO with
232
- the correct version. Before the fix, the archival fallback was
233
- stashed as the sole candidate and its ``get_scm_version()`` returned
234
- None, causing a LookupError.
235
- """
236
- (tmp_path / ".git_archival.txt").write_text(
237
- "node: $Format:%H$\n"
238
- "node-date: $Format:%cI$\n"
239
- "describe-name: $Format:%(describe:tags=true)$\n"
240
- "ref-names: $Format:%D$\n",
241
- encoding="utf-8",
242
- )
243
- (tmp_path / "PKG-INFO").write_text(
244
- "Metadata-Version: 2.1\nName: my-pkg\nVersion: 1.2.3\n",
245
- encoding="utf-8",
246
- )
247
- config = Configuration(relative_to=str(tmp_path / "pyproject.toml"))
248
- result = discover_workdir(config)
249
- assert result is not None
250
- assert isinstance(result, PkgInfoWorkdir)
251
- version = result.get_scm_version()
252
- assert version is not None
253
- assert str(version.tag) == "1.2.3"
254
-
255
-
256
211
  class TestFallbackWorkdirDiscoveryFactories:
257
212
  def test_discover_archival_git(self, tmp_path: Path) -> None:
258
213
  from vcs_versioning._fallback_workdir import discover_archival
@@ -1,29 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import Callable, Sequence
4
- from typing import TYPE_CHECKING, TypeAlias
5
-
6
- if TYPE_CHECKING:
7
- from ._scm_version import ScmVersion
8
-
9
- # Re-export from _compat for backward compatibility
10
- from ._compat import PathT as PathT # noqa: PLC0414
11
-
12
- __all__ = [
13
- "PathT",
14
- "CMD_TYPE",
15
- "VERSION_SCHEME",
16
- "VERSION_SCHEMES",
17
- "SCMVERSION",
18
- "GIT_PRE_PARSE",
19
- ]
20
-
21
- CMD_TYPE: TypeAlias = Sequence[PathT] | str
22
-
23
- VERSION_SCHEME_CALLABLE: TypeAlias = Callable[["ScmVersion"], str | None]
24
- VERSION_SCHEME: TypeAlias = str | VERSION_SCHEME_CALLABLE
25
- VERSION_SCHEMES: TypeAlias = Sequence[VERSION_SCHEME] | VERSION_SCHEME
26
- SCMVERSION: TypeAlias = "ScmVersion"
27
-
28
- # Git pre-parse function types
29
- GIT_PRE_PARSE: TypeAlias = str | None
File without changes
File without changes
File without changes