From f31cf8e3a6156c947dbdd5383e26f631914b7c5f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 31 Jul 2025 11:48:21 +0200 Subject: [PATCH] Add optional Distribution parameter to walk_revctrl file finder entry points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File finder entry points for SCM-based files can now optionally receive the Distribution object to make more informed decisions about which files to include. This maintains full backward compatibility with existing entry points that only accept dirname. Changes: - Add optional distribution parameter to walk_revctrl function - Create _call_finder_with_distribution_support helper function with signature inspection to detect entry point capabilities - Update manifest_maker.add_defaults() to pass distribution object - Add comprehensive unit tests covering all scenarios including backward compatibility and error handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- setuptools/command/egg_info.py | 2 +- setuptools/command/sdist.py | 42 ++++++++++++- setuptools/tests/test_sdist.py | 108 ++++++++++++++++++++++++++++++++- 3 files changed, 148 insertions(+), 4 deletions(-) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index d9de297ecf..f5910e9f38 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -582,7 +582,7 @@ def add_defaults(self) -> None: sdist.add_defaults(self) self.filelist.append(self.template) self.filelist.append(self.manifest) - rcfiles = list(walk_revctrl()) + rcfiles = list(walk_revctrl(distribution=self.distribution)) if rcfiles: self.filelist.extend(rcfiles) elif os.path.exists(self.manifest): diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 17279ac421..7ffbeebedf 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +import inspect import os import re from collections.abc import Iterator @@ -17,10 +18,47 @@ _default_revctrl = list -def walk_revctrl(dirname='') -> Iterator: +def _call_finder_with_distribution_support(finder_func, dirname='', distribution=None): + """ + Call a file finder function with distribution support if available. + + This helper function inspects the finder function's signature to determine + if it accepts a distribution parameter. If it does, the distribution is passed; + otherwise, only the dirname is passed for backward compatibility. + + Args: + finder_func: The file finder function to call + dirname: Directory name to search (default: '') + distribution: Distribution object to pass if supported (default: None) + + Returns: + Iterator of file paths from the finder function + + Raises: + Any exception raised by the finder function itself + """ + try: + sig = inspect.signature(finder_func) + params = list(sig.parameters.keys()) + + # If function accepts distribution parameter, pass it + if len(params) > 1 and 'distribution' in params: + return finder_func(dirname, distribution=distribution) + else: + # Fall back to dirname-only for backward compatibility + return finder_func(dirname) + except (ValueError, TypeError): + # If signature inspection fails, fall back to dirname-only + return finder_func(dirname) + + +def walk_revctrl(dirname='', distribution=None) -> Iterator: """Find all files under revision control""" for ep in metadata.entry_points(group='setuptools.file_finders'): - yield from ep.load()(dirname) + finder_func = ep.load() + yield from _call_finder_with_distribution_support( + finder_func, dirname, distribution + ) class sdist(orig.sdist): diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 19d8ddf6da..20fc438fb1 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -19,7 +19,7 @@ from setuptools import Command, SetuptoolsDeprecationWarning from setuptools._importlib import metadata from setuptools.command.egg_info import manifest_maker -from setuptools.command.sdist import sdist +from setuptools.command.sdist import _call_finder_with_distribution_support, sdist from setuptools.dist import Distribution from setuptools.extension import Extension from setuptools.tests import fail_on_ascii @@ -982,3 +982,109 @@ def test_sanity_check_setuptools_own_sdist(setuptools_sdist): # setuptools sdist should not include the .tox folder tox_files = [name for name in files if ".tox" in name] assert len(tox_files) == 0, f"not empty {tox_files}" + + +class TestCallFinderWithDistributionSupport: + """Tests for the _call_finder_with_distribution_support helper function""" + + def test_finder_with_distribution_parameter(self): + """Test that finder functions accepting distribution parameter receive it""" + + def finder_with_distribution(dirname, distribution=None): + return [f"file_in_{dirname}_with_dist_{distribution}"] + + result = list( + _call_finder_with_distribution_support( + finder_with_distribution, dirname="testdir", distribution="test_dist" + ) + ) + + assert result == ["file_in_testdir_with_dist_test_dist"] + + def test_finder_without_distribution_parameter(self): + """Test that finder functions not accepting distribution parameter work normally""" + + def finder_without_distribution(dirname): + return [f"file_in_{dirname}"] + + result = list( + _call_finder_with_distribution_support( + finder_without_distribution, dirname="testdir", distribution="test_dist" + ) + ) + + assert result == ["file_in_testdir"] + + def test_finder_with_single_parameter_only(self): + """Test that finder functions with only dirname parameter work""" + + def finder_single_param(dirname): + return [f"single_{dirname}"] + + result = list( + _call_finder_with_distribution_support(finder_single_param, dirname="test") + ) + + assert result == ["single_test"] + + def test_finder_with_distribution_named_differently(self): + """Test that only functions with 'distribution' parameter name get it""" + + def finder_with_other_param(dirname, other_param=None): + return [f"other_{dirname}_{other_param}"] + + # Should not pass distribution even though function has 2+ params + result = list( + _call_finder_with_distribution_support( + finder_with_other_param, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["other_test_None"] + + def test_finder_with_signature_inspection_failure(self): + """Test fallback behavior when signature inspection fails""" + + # Create a mock function that raises an error during signature inspection + def problematic_finder(dirname): + return [f"fallback_{dirname}"] + + # Mock inspect.signature to raise ValueError + with mock.patch( + 'setuptools.command.sdist.inspect.signature', + side_effect=ValueError("test error"), + ): + result = list( + _call_finder_with_distribution_support( + problematic_finder, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["fallback_test"] + + def test_finder_with_multiple_parameters_including_distribution(self): + """Test finder with multiple parameters where one is distribution""" + + def complex_finder(dirname, extra_param=None, distribution=None): + return [f"complex_{dirname}_{extra_param}_{distribution}"] + + result = list( + _call_finder_with_distribution_support( + complex_finder, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["complex_test_None_dist_obj"] + + def test_finder_function_raises_exception(self): + """Test that exceptions from finder functions are properly propagated""" + + def failing_finder(dirname, distribution=None): + raise RuntimeError("Finder failed") + + with pytest.raises(RuntimeError, match="Finder failed"): + list( + _call_finder_with_distribution_support( + failing_finder, dirname="test", distribution="dist_obj" + ) + )