From a921c02aa8be7545527ac1528f7fc6bd5f94aecb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 4 Aug 2025 14:03:11 +0200 Subject: [PATCH 1/2] correctly handle scm_version=None in version dumping closes #1180 --- CHANGELOG.md | 8 +++ .../_integration/dump_version.py | 31 +++++++--- testing/test_functions.py | 58 +++++++++++++++++++ 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b28221d..1b952ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog + +## v9.0.1 + +### Fixed + +- fix #1180: ensure version dumping works when no scm_version is given (problems in downstreams) + + ## v9.0.0 ### Breaking diff --git a/src/setuptools_scm/_integration/dump_version.py b/src/setuptools_scm/_integration/dump_version.py index 62827a1b..06081c9f 100644 --- a/src/setuptools_scm/_integration/dump_version.py +++ b/src/setuptools_scm/_integration/dump_version.py @@ -95,19 +95,34 @@ def _validate_template(target: Path, template: str | None) -> str: return template +class DummyScmVersion: + @property + def short_node(self) -> str | None: + return None + + def write_version_to_path( - target: Path, template: str | None, version: str, scm_version: ScmVersion | None + target: Path, + template: str | None, + version: str, + scm_version: ScmVersion | None = None, ) -> None: final_template = _validate_template(target, template) log.debug("dump %s into %s", version, target) version_tuple = _version_as_tuple(version) - if scm_version is not None: - content = final_template.format( - version=version, - version_tuple=version_tuple, - scm_version=scm_version, + if scm_version is None: + warnings.warn( + "write_version_to_path called without scm_version parameter. " + "This will be required in a future version. " + "Pass scm_version=None explicitly to suppress this warning.", + DeprecationWarning, + stacklevel=2, ) - else: - content = final_template.format(version=version, version_tuple=version_tuple) + + content = final_template.format( + version=version, + version_tuple=version_tuple, + scm_version=scm_version or DummyScmVersion(), + ) target.write_text(content, encoding="utf-8") diff --git a/testing/test_functions.py b/testing/test_functions.py index c0cb5166..b6b8a59e 100644 --- a/testing/test_functions.py +++ b/testing/test_functions.py @@ -293,3 +293,61 @@ def test_has_command_logs_stderr(caplog: pytest.LogCaptureFixture) -> None: def test_tag_to_version(tag: str, expected_version: str) -> None: version = str(tag_to_version(tag, c)) assert version == expected_version + + +def test_write_version_to_path_deprecation_warning_none(tmp_path: Path) -> None: + """Test that write_version_to_path warns when scm_version=None is passed.""" + from setuptools_scm._integration.dump_version import write_version_to_path + + target_file = tmp_path / "version.py" + + # This should raise a deprecation warning when scm_version=None is explicitly passed + with pytest.warns( + DeprecationWarning, match="write_version_to_path called without scm_version" + ): + write_version_to_path( + target=target_file, + template=None, # Use default template + version="1.2.3", + scm_version=None, # Explicitly passing None should warn + ) + + # Verify the file was created and contains the expected content + assert target_file.exists() + content = target_file.read_text(encoding="utf-8") + + # Check that the version is correctly formatted + assert "__version__ = version = '1.2.3'" in content + assert "__version_tuple__ = version_tuple = (1, 2, 3)" in content + + # Check that commit_id is set to None when scm_version is None + assert "__commit_id__ = commit_id = None" in content + + +def test_write_version_to_path_deprecation_warning_missing(tmp_path: Path) -> None: + """Test that write_version_to_path warns when scm_version parameter is not provided.""" + from setuptools_scm._integration.dump_version import write_version_to_path + + target_file = tmp_path / "version.py" + + # This should raise a deprecation warning when scm_version is not provided + with pytest.warns( + DeprecationWarning, match="write_version_to_path called without scm_version" + ): + write_version_to_path( + target=target_file, + template=None, # Use default template + version="1.2.3", + # scm_version not provided - should warn + ) + + # Verify the file was created and contains the expected content + assert target_file.exists() + content = target_file.read_text(encoding="utf-8") + + # Check that the version is correctly formatted + assert "__version__ = version = '1.2.3'" in content + assert "__version_tuple__ = version_tuple = (1, 2, 3)" in content + + # Check that commit_id is set to None when scm_version is None + assert "__commit_id__ = commit_id = None" in content From 4ffcbfc725ad768899c55c0cb3e0e87b9d767749 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 4 Aug 2025 16:47:03 +0200 Subject: [PATCH 2/2] introduce a workaround for not walking the dependency graph whne figuring if we should enable closes #1181 --- CHANGELOG.md | 3 +- src/setuptools_scm/_config.py | 13 +++++++- .../_integration/pyproject_reading.py | 3 +- src/setuptools_scm/_integration/setuptools.py | 1 + testing/test_integration.py | 32 +++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b952ade..ed1f30c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ ### Fixed - fix #1180: ensure version dumping works when no scm_version is given (problems in downstreams) - +- fix #1181: config - reintroduce controll over when we expect a section to be present + as it turns out theres valid use cases where setuptools_scm is not direct part of the dependencies ## v9.0.0 diff --git a/src/setuptools_scm/_config.py b/src/setuptools_scm/_config.py index 0387de9d..c4ebc773 100644 --- a/src/setuptools_scm/_config.py +++ b/src/setuptools_scm/_config.py @@ -271,6 +271,7 @@ def from_file( name: str | os.PathLike[str] = "pyproject.toml", dist_name: str | None = None, missing_file_ok: bool = False, + missing_section_ok: bool = False, **kwargs: Any, ) -> Configuration: """ @@ -279,10 +280,20 @@ def from_file( not installed or the file has invalid format or does not contain setuptools_scm configuration (either via _ [tool.setuptools_scm] section or build-system.requires). + + Parameters: + - name: path to pyproject.toml + - dist_name: name of the distribution + - missing_file_ok: if True, do not raise an error if the file is not found + - missing_section_ok: if True, do not raise an error if the section is not found + (workaround for not walking the dependency graph when figuring out if setuptools_scm is a dependency) + - **kwargs: additional keyword arguments to pass to the Configuration constructor """ try: - pyproject_data = _read_pyproject(Path(name)) + pyproject_data = _read_pyproject( + Path(name), missing_section_ok=missing_section_ok + ) except FileNotFoundError: if missing_file_ok: log.warning("File %s not found, using empty configuration", name) diff --git a/src/setuptools_scm/_integration/pyproject_reading.py b/src/setuptools_scm/_integration/pyproject_reading.py index baf850d1..8e59ce4e 100644 --- a/src/setuptools_scm/_integration/pyproject_reading.py +++ b/src/setuptools_scm/_integration/pyproject_reading.py @@ -47,6 +47,7 @@ def read_pyproject( path: Path = Path("pyproject.toml"), tool_name: str = "setuptools_scm", build_package_names: Sequence[str] = ("setuptools_scm", "setuptools-scm"), + missing_section_ok: bool = False, ) -> PyProjectData: defn = read_toml_content(path) requires: list[str] = defn.get("build-system", {}).get("requires", []) @@ -55,7 +56,7 @@ def read_pyproject( try: section = defn.get("tool", {})[tool_name] except LookupError as e: - if not is_required: + if not is_required and not missing_section_ok: # Enhanced error message that mentions both configuration options error = ( f"{path} does not contain a tool.{tool_name} section. " diff --git a/src/setuptools_scm/_integration/setuptools.py b/src/setuptools_scm/_integration/setuptools.py index e96807e4..e5ff73e5 100644 --- a/src/setuptools_scm/_integration/setuptools.py +++ b/src/setuptools_scm/_integration/setuptools.py @@ -130,6 +130,7 @@ def version_keyword( config = _config.Configuration.from_file( dist_name=dist_name, missing_file_ok=True, + missing_section_ok=True, **overrides, ) _assign_version(dist, config) diff --git a/testing/test_integration.py b/testing/test_integration.py index 074785f9..c381998c 100644 --- a/testing/test_integration.py +++ b/testing/test_integration.py @@ -921,3 +921,35 @@ def test_integration_function_call_order( assert final_version == expected_final_version, ( f"Expected version '{expected_final_version}' but got '{final_version}'" ) + + +def test_version_keyword_no_scm_dependency_works( + wd: WorkDir, monkeypatch: pytest.MonkeyPatch +) -> None: + # Set up a git repository with a tag + wd.commit_testfile("test") + wd("git tag 1.0.0") + monkeypatch.chdir(wd.cwd) + + # Create a pyproject.toml file WITHOUT setuptools_scm in build-system.requires + # and WITHOUT [tool.setuptools_scm] section + pyproject_content = """ +[build-system] +requires = ["setuptools>=80"] +build-backend = "setuptools.build_meta" + +[project] +name = "test-package-no-scm" +dynamic = ["version"] +""" + wd.write("pyproject.toml", pyproject_content) + + import setuptools + + from setuptools_scm._integration.setuptools import version_keyword + + # Create distribution + dist = setuptools.Distribution({"name": "test-package-no-scm"}) + + version_keyword(dist, "use_scm_version", True) + assert dist.metadata.version == "1.0.0"