Skip to content

Commit d7f1688

Browse files
Merge pull request #1193 from RonnyPfannschmidt/infer-version-logic
reiterate version inference logic
2 parents 66e16e2 + f3c9a30 commit d7f1688

16 files changed

+842
-237
lines changed

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ include CHANGELOG.md
1818

1919
recursive-include testing *.bash
2020
prune nextgen
21+
prune .cursor
2122

2223
recursive-include docs *.md
2324
include docs/examples/version_scheme_code/*.py
2425
include docs/examples/version_scheme_code/*.toml
2526
include mkdocs.yml
27+
include uv.lock

pyproject.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ dependencies = [
4848
'typing-extensions; python_version < "3.10"',
4949
]
5050
[project.optional-dependencies]
51-
rich = [
52-
"rich",
53-
]
54-
toml = [
55-
]
51+
rich = ["rich"]
52+
toml = []
5653

5754
[dependency-groups]
5855
docs = [

src/setuptools_scm/_config.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -291,24 +291,12 @@ def from_file(
291291
- **kwargs: additional keyword arguments to pass to the Configuration constructor
292292
"""
293293

294-
try:
295-
if pyproject_data is None:
296-
pyproject_data = _read_pyproject(
297-
Path(name), missing_section_ok=missing_section_ok
298-
)
299-
except FileNotFoundError:
300-
if missing_file_ok:
301-
log.warning("File %s not found, using empty configuration", name)
302-
pyproject_data = PyProjectData(
303-
path=Path(name),
304-
tool_name="setuptools_scm",
305-
project={},
306-
section={},
307-
is_required=False,
308-
section_present=False,
309-
)
310-
else:
311-
raise
294+
if pyproject_data is None:
295+
pyproject_data = _read_pyproject(
296+
Path(name),
297+
missing_section_ok=missing_section_ok,
298+
missing_file_ok=missing_file_ok,
299+
)
312300
args = _get_args_for_pyproject(pyproject_data, dist_name, kwargs)
313301

314302
args.update(read_toml_overrides(args["dist_name"]))

src/setuptools_scm/_integration/pyproject_reading.py

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import warnings
44

5+
from dataclasses import dataclass
56
from pathlib import Path
6-
from typing import NamedTuple
77
from typing import Sequence
88

99
from .. import _log
10-
from .setuptools import read_dist_name_from_setup_cfg
10+
from .._requirement_cls import extract_package_name
1111
from .toml import TOML_RESULT
1212
from .toml import read_toml_content
1313

@@ -16,13 +16,39 @@
1616
_ROOT = "root"
1717

1818

19-
class PyProjectData(NamedTuple):
19+
@dataclass
20+
class PyProjectData:
2021
path: Path
2122
tool_name: str
2223
project: TOML_RESULT
2324
section: TOML_RESULT
2425
is_required: bool
2526
section_present: bool
27+
project_present: bool
28+
29+
@classmethod
30+
def for_testing(
31+
cls,
32+
is_required: bool = False,
33+
section_present: bool = False,
34+
project_present: bool = False,
35+
project_name: str | None = None,
36+
) -> PyProjectData:
37+
"""Create a PyProjectData instance for testing purposes."""
38+
if project_name is not None:
39+
project = {"name": project_name}
40+
assert project_present
41+
else:
42+
project = {}
43+
return cls(
44+
path=Path("pyproject.toml"),
45+
tool_name="setuptools_scm",
46+
project=project,
47+
section={},
48+
is_required=is_required,
49+
section_present=section_present,
50+
project_present=project_present,
51+
)
2652

2753
@property
2854
def project_name(self) -> str | None:
@@ -33,6 +59,10 @@ def verify_dynamic_version_when_required(self) -> None:
3359
if self.is_required and not self.section_present:
3460
# When setuptools-scm is in build-system.requires but no tool section exists,
3561
# we need to verify that dynamic=['version'] is set in the project section
62+
# But only if there's actually a project section
63+
if not self.project_present:
64+
# No project section, so don't auto-activate setuptools_scm
65+
return
3666
dynamic = self.project.get("dynamic", [])
3767
if "version" not in dynamic:
3868
raise ValueError(
@@ -43,29 +73,41 @@ def verify_dynamic_version_when_required(self) -> None:
4373

4474

4575
def has_build_package(
46-
requires: Sequence[str], build_package_names: Sequence[str]
76+
requires: Sequence[str], canonical_build_package_name: str
4777
) -> bool:
4878
for requirement in requires:
49-
import re
50-
51-
# Remove extras like [toml] first
52-
clean_req = re.sub(r"\[.*?\]", "", requirement)
53-
# Split on version operators and take first part
54-
package_name = re.split(r"[><=!~]", clean_req)[0].strip().lower()
55-
if package_name in build_package_names:
79+
package_name = extract_package_name(requirement)
80+
if package_name == canonical_build_package_name:
5681
return True
5782
return False
5883

5984

6085
def read_pyproject(
6186
path: Path = Path("pyproject.toml"),
6287
tool_name: str = "setuptools_scm",
63-
build_package_names: Sequence[str] = ("setuptools_scm", "setuptools-scm"),
88+
canonical_build_package_name: str = "setuptools-scm",
6489
missing_section_ok: bool = False,
90+
missing_file_ok: bool = False,
6591
) -> PyProjectData:
66-
defn = read_toml_content(path)
92+
try:
93+
defn = read_toml_content(path)
94+
except FileNotFoundError:
95+
if missing_file_ok:
96+
log.warning("File %s not found, using empty configuration", path)
97+
return PyProjectData(
98+
path=path,
99+
tool_name=tool_name,
100+
project={},
101+
section={},
102+
is_required=False,
103+
section_present=False,
104+
project_present=False,
105+
)
106+
else:
107+
raise
108+
67109
requires: list[str] = defn.get("build-system", {}).get("requires", [])
68-
is_required = has_build_package(requires, build_package_names)
110+
is_required = has_build_package(requires, canonical_build_package_name)
69111

70112
try:
71113
section = defn.get("tool", {})[tool_name]
@@ -87,8 +129,9 @@ def read_pyproject(
87129
section_present = False
88130

89131
project = defn.get("project", {})
132+
project_present = "project" in defn
90133
pyproject_data = PyProjectData(
91-
path, tool_name, project, section, is_required, section_present
134+
path, tool_name, project, section, is_required, section_present, project_present
92135
)
93136

94137
# Verify dynamic version when setuptools-scm is used as build dependency indicator
@@ -121,8 +164,6 @@ def get_args_for_pyproject(
121164
if dist_name is None:
122165
# minimal pep 621 support for figuring the pretend keys
123166
dist_name = pyproject.project_name
124-
if dist_name is None:
125-
dist_name = read_dist_name_from_setup_cfg()
126167
if _ROOT in kwargs:
127168
if kwargs[_ROOT] is None:
128169
kwargs.pop(_ROOT, None)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
import os
4+
5+
import setuptools
6+
7+
8+
def read_dist_name_from_setup_cfg(
9+
input: str | os.PathLike[str] = "setup.cfg",
10+
) -> str | None:
11+
# minimal effort to read dist_name off setup.cfg metadata
12+
import configparser
13+
14+
parser = configparser.ConfigParser()
15+
parser.read([input], encoding="utf-8")
16+
dist_name = parser.get("metadata", "name", fallback=None)
17+
return dist_name
18+
19+
20+
def _dist_name_from_legacy(dist: setuptools.Distribution) -> str | None:
21+
return dist.metadata.name or read_dist_name_from_setup_cfg()

0 commit comments

Comments
 (0)