22
33import warnings
44
5+ from dataclasses import dataclass
56from pathlib import Path
6- from typing import NamedTuple
77from typing import Sequence
88
99from ..import _log
10- from .setuptools import read_dist_name_from_setup_cfg
10+ from .. _requirement_cls import extract_package_name
1111from .toml import TOML_RESULT
1212from .toml import read_toml_content
1313
1616_ROOT = "root"
1717
1818
19- class PyProjectData (NamedTuple ):
19+ @dataclass
20+ class PyProjectData :
2021path :Path
2122tool_name :str
2223project :TOML_RESULT
2324section :TOML_RESULT
2425is_required :bool
2526section_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
2854def project_name (self )-> str | None :
@@ -33,6 +59,10 @@ def verify_dynamic_version_when_required(self) -> None:
3359if 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
3666dynamic = self .project .get ("dynamic" , [])
3767if "version" not in dynamic :
3868raise ValueError (
@@ -43,29 +73,41 @@ def verify_dynamic_version_when_required(self) -> None:
4373
4474
4575def has_build_package (
46- requires :Sequence [str ],build_package_names : Sequence [ str ]
76+ requires :Sequence [str ],canonical_build_package_name : str
4777)-> bool :
4878for 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 :
5681return True
5782return False
5883
5984
6085def read_pyproject (
6186path :Path = Path ("pyproject.toml" ),
6287tool_name :str = "setuptools_scm" ,
63- build_package_names : Sequence [ str ] = ( "setuptools_scm" , " setuptools-scm") ,
88+ canonical_build_package_name : str = " setuptools-scm" ,
6489missing_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+
67109requires :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
70112try :
71113section = defn .get ("tool" , {})[tool_name ]
@@ -87,8 +129,9 @@ def read_pyproject(
87129section_present = False
88130
89131project = defn .get ("project" , {})
132+ project_present = "project" in defn
90133pyproject_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(
121164if dist_name is None :
122165# minimal pep 621 support for figuring the pretend keys
123166dist_name = pyproject .project_name
124- if dist_name is None :
125- dist_name = read_dist_name_from_setup_cfg ()
126167if _ROOT in kwargs :
127168if kwargs [_ROOT ]is None :
128169kwargs .pop (_ROOT ,None )