From b9a3255ac5fe2a3a37829d24685f14fcc90889ef Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Thu, 17 Aug 2023 18:57:51 +0100 Subject: [PATCH 01/16] Include type information by default --- setuptools/command/build_py.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 5709eb6d8c..de9762da78 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -302,6 +302,19 @@ def _get_platform_patterns(spec, package, src_dir): for pattern in raw_patterns ) + def get_source_files(self): + py_files = [module[-1] for module in self.find_all_modules()] + + possible_stub_files = set(os.path.splitext(f)[0] + ".pyi" for f in py_files) + stub_files = [f for f in possible_stub_files if os.path.isfile(f)] + + possible_py_typed_files = set( + os.path.join(os.path.dirname(f), "py.typed") for f in py_files + ) + py_typed_files = [f for f in possible_py_typed_files if os.path.isfile(f)] + + return py_files + stub_files + py_typed_files + def assert_relative(path): if not os.path.isabs(path): From 88556f9f07012fe9c434f54d3e3e50d2db755ce2 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Thu, 17 Aug 2023 19:10:35 +0100 Subject: [PATCH 02/16] Add newsfragment --- newsfragments/3136.feat.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3136.feat.rst diff --git a/newsfragments/3136.feat.rst b/newsfragments/3136.feat.rst new file mode 100644 index 0000000000..d87124cc0f --- /dev/null +++ b/newsfragments/3136.feat.rst @@ -0,0 +1 @@ +Include type information (py.typed, *.pyi) by default (#3136) -- by :user:`Danie-1` From 9e13c09cc59f4ec6c9281365354637257c2100d5 Mon Sep 17 00:00:00 2001 From: Danie-1 <63882624+Danie-1@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:37:42 +0100 Subject: [PATCH 03/16] Use generators See https://github.com/pypa/setuptools/pull/4021#discussion_r1298221625 --- setuptools/command/build_py.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index de9762da78..2c05790568 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -305,15 +305,13 @@ def _get_platform_patterns(spec, package, src_dir): def get_source_files(self): py_files = [module[-1] for module in self.find_all_modules()] - possible_stub_files = set(os.path.splitext(f)[0] + ".pyi" for f in py_files) + possible_stub_files = (os.path.splitext(f)[0] + ".pyi" for f in py_files) stub_files = [f for f in possible_stub_files if os.path.isfile(f)] - possible_py_typed_files = set( - os.path.join(os.path.dirname(f), "py.typed") for f in py_files - ) - py_typed_files = [f for f in possible_py_typed_files if os.path.isfile(f)] + possible_py_typed_files = (os.path.join(os.path.dirname(f), "py.typed") for f in py_files) + py_typed_files = set(f for f in possible_py_typed_files if os.path.isfile(f)) - return py_files + stub_files + py_typed_files + return py_files + stub_files + list(py_typed_files) def assert_relative(path): From 909a2bcb6d7b2f0c305b3ce22831579e7cacaea4 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Sun, 3 Sep 2023 11:10:27 +0100 Subject: [PATCH 04/16] Revert "Use generators" This reverts commit fa06b37e9621e00827e2febf8452078ce0aa0345. --- setuptools/command/build_py.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 2c05790568..de9762da78 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -305,13 +305,15 @@ def _get_platform_patterns(spec, package, src_dir): def get_source_files(self): py_files = [module[-1] for module in self.find_all_modules()] - possible_stub_files = (os.path.splitext(f)[0] + ".pyi" for f in py_files) + possible_stub_files = set(os.path.splitext(f)[0] + ".pyi" for f in py_files) stub_files = [f for f in possible_stub_files if os.path.isfile(f)] - possible_py_typed_files = (os.path.join(os.path.dirname(f), "py.typed") for f in py_files) - py_typed_files = set(f for f in possible_py_typed_files if os.path.isfile(f)) + possible_py_typed_files = set( + os.path.join(os.path.dirname(f), "py.typed") for f in py_files + ) + py_typed_files = [f for f in possible_py_typed_files if os.path.isfile(f)] - return py_files + stub_files + list(py_typed_files) + return py_files + stub_files + py_typed_files def assert_relative(path): From c272dcf618858d0ba40539751eb0c18f54ead218 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Sun, 3 Sep 2023 11:11:02 +0100 Subject: [PATCH 05/16] Revert "Include type information by default" This reverts commit 2684ba24ce2eaf9d9ab5543b32fd9b33acf911d2. --- setuptools/command/build_py.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index de9762da78..5709eb6d8c 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -302,19 +302,6 @@ def _get_platform_patterns(spec, package, src_dir): for pattern in raw_patterns ) - def get_source_files(self): - py_files = [module[-1] for module in self.find_all_modules()] - - possible_stub_files = set(os.path.splitext(f)[0] + ".pyi" for f in py_files) - stub_files = [f for f in possible_stub_files if os.path.isfile(f)] - - possible_py_typed_files = set( - os.path.join(os.path.dirname(f), "py.typed") for f in py_files - ) - py_typed_files = [f for f in possible_py_typed_files if os.path.isfile(f)] - - return py_files + stub_files + py_typed_files - def assert_relative(path): if not os.path.isabs(path): From 66a36ae55f848ba8d6040b153ef6fe761ccb0f6b Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Sun, 3 Sep 2023 11:41:44 +0100 Subject: [PATCH 06/16] Rename newsfragment file --- newsfragments/{3136.feat.rst => 3136.feature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename newsfragments/{3136.feat.rst => 3136.feature.rst} (100%) diff --git a/newsfragments/3136.feat.rst b/newsfragments/3136.feature.rst similarity index 100% rename from newsfragments/3136.feat.rst rename to newsfragments/3136.feature.rst From e59ce9402ce59d3c5b2295db676bc8d049b2d884 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Mon, 4 Sep 2023 18:16:27 +0100 Subject: [PATCH 07/16] Add tests to check type information is included by default --- setuptools/tests/test_build_meta.py | 91 +++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index fd7cf168ce..cf6f3b62b5 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -4,6 +4,7 @@ import signal import tarfile import importlib +import itertools import contextlib from concurrent import futures import re @@ -981,3 +982,93 @@ def test_system_exit_in_setuppy(monkeypatch, tmp_path): with pytest.raises(SystemExit, match="some error"): backend = BuildBackend(backend_name="setuptools.build_meta") backend.get_requires_for_build_wheel() + + +class TestTypeInformationIncludedByDefault(): + dont_include_package_data = """ + [project] + name = "foo" + version = "1" + [tools.setuptools] + include-package-data = false + """ + + exclude_type_info = """ + [tool.setuptools.exclude-package-data] + "*" = ["py.typed", "*.pyi"] + """ + + package_1 = { + "foo": { + "bar.pyi": "", + "py.typed": "", + } + } + + package_2 = { + "foo": { + "bar": { + "py.typed": "", + "mod.pyi": "", + } + } + } + + package_3 = { + "foo": { + "namespace": { + "foo.pyi": "", + }, + "__init__.pyi": "", + "py.typed": "" + } + } + + packages_to_test = [package_1, package_2, package_3] + + def is_type_information_file(self, filename): + basename = os.path.basename(filename) + return basename.endswith(".pyi") or basename == "py.typed" + + def get_type_files(self, file_spec): + output = set() + for key in file_spec.keys(): + if isinstance(file_spec[key], str): + if self.is_type_information_file(key): + output.add(key) + else: + output.update(key + "/" + f for f in self.get_type_files(file_spec[key])) + return output + + @pytest.fixture(params=itertools.product(packages_to_test, [True, False])) + def file_spec_and_expected(self, request): + file_spec, exclude_type_information = request.param + pyproject = self.dont_include_package_data + if exclude_type_information: + pyproject += self.exclude_type_info + file_spec["pyproject.toml"] = pyproject + + if exclude_type_information: + expected = set() + else: + expected = self.get_type_files(file_spec) + + yield file_spec, expected + + def test_type_information_always_included(self, monkeypatch, tmp_path, file_spec_and_expected): + """Setuptools should include type information in the wheel (py.typed, *.pyi).""" + file_spec, expected = file_spec_and_expected + monkeypatch.chdir(tmp_path) + dist_dir = os.path.abspath('pip-wheel') + os.makedirs(dist_dir) + path.build(file_spec) + build_backend = BuildBackend(backend_name="setuptools.build_meta") + wheel_name = build_backend.build_wheel(dist_dir) + + wheel_file = os.path.join(dist_dir, wheel_name) + assert os.path.isfile(wheel_file) + + with ZipFile(wheel_file) as zipfile: + wheel_contents = set(filter(self.is_type_information_file, zipfile.namelist())) + + assert wheel_contents == expected From 418eb189032486f742b99d2e8591b2c8cc624f97 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Wed, 6 Sep 2023 22:56:20 +0100 Subject: [PATCH 08/16] Format test with black --- setuptools/tests/test_build_meta.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index cf6f3b62b5..43f3100a83 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -984,7 +984,7 @@ def test_system_exit_in_setuppy(monkeypatch, tmp_path): backend.get_requires_for_build_wheel() -class TestTypeInformationIncludedByDefault(): +class TestTypeInformationIncludedByDefault: dont_include_package_data = """ [project] name = "foo" @@ -1020,7 +1020,7 @@ class TestTypeInformationIncludedByDefault(): "foo.pyi": "", }, "__init__.pyi": "", - "py.typed": "" + "py.typed": "", } } @@ -1037,7 +1037,9 @@ def get_type_files(self, file_spec): if self.is_type_information_file(key): output.add(key) else: - output.update(key + "/" + f for f in self.get_type_files(file_spec[key])) + output.update( + key + "/" + f for f in self.get_type_files(file_spec[key]) + ) return output @pytest.fixture(params=itertools.product(packages_to_test, [True, False])) @@ -1055,7 +1057,9 @@ def file_spec_and_expected(self, request): yield file_spec, expected - def test_type_information_always_included(self, monkeypatch, tmp_path, file_spec_and_expected): + def test_type_information_always_included( + self, monkeypatch, tmp_path, file_spec_and_expected + ): """Setuptools should include type information in the wheel (py.typed, *.pyi).""" file_spec, expected = file_spec_and_expected monkeypatch.chdir(tmp_path) @@ -1069,6 +1073,8 @@ def test_type_information_always_included(self, monkeypatch, tmp_path, file_spec assert os.path.isfile(wheel_file) with ZipFile(wheel_file) as zipfile: - wheel_contents = set(filter(self.is_type_information_file, zipfile.namelist())) + wheel_contents = set( + filter(self.is_type_information_file, zipfile.namelist()) + ) assert wheel_contents == expected From df6b3b676a6ce7b4edf6e12a338ced358843ab7d Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Wed, 6 Sep 2023 22:59:42 +0100 Subject: [PATCH 09/16] Include type information by default --- setuptools/command/build_py.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 5709eb6d8c..8a201a270c 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -116,6 +116,7 @@ def find_data_files(self, package, src_dir): self.package_data, package, src_dir, + extra_patterns=['*.pyi', 'py.typed'], ) globs_expanded = map(partial(glob, recursive=True), patterns) # flatten the expanded globs into an iterable of matches @@ -285,7 +286,7 @@ def exclude_data_files(self, package, src_dir, files): return list(unique_everseen(keepers)) @staticmethod - def _get_platform_patterns(spec, package, src_dir): + def _get_platform_patterns(spec, package, src_dir, extra_patterns=[]): """ yield platform-specific path patterns (suitable for glob or fn_match) from a glob-based spec (such as @@ -293,6 +294,7 @@ def _get_platform_patterns(spec, package, src_dir): matching package in src_dir. """ raw_patterns = itertools.chain( + extra_patterns, spec.get('', []), spec.get(package, []), ) From 35b9fa0d8ccd6394b1f2e2a5be51e2a068768839 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Sun, 17 Sep 2023 16:05:42 +0100 Subject: [PATCH 10/16] Move tests from test_build_meta to test_build_py --- setuptools/command/build_py.py | 5 +- setuptools/tests/test_build_meta.py | 103 ++----------------- setuptools/tests/test_build_py.py | 152 ++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 98 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 8a201a270c..242d60f011 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -16,6 +16,9 @@ from ..warnings import SetuptoolsDeprecationWarning +_IMPLICIT_DATA_FILES = ('*.pyi', 'py.typed') + + def make_writable(target): os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE) @@ -116,7 +119,7 @@ def find_data_files(self, package, src_dir): self.package_data, package, src_dir, - extra_patterns=['*.pyi', 'py.typed'], + extra_patterns=_IMPLICIT_DATA_FILES, ) globs_expanded = map(partial(glob, recursive=True), patterns) # flatten the expanded globs into an iterable of matches diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 43f3100a83..5ce4714393 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -4,7 +4,6 @@ import signal import tarfile import importlib -import itertools import contextlib from concurrent import futures import re @@ -373,8 +372,10 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script): "src": { "foo": { "__init__.py": "__version__ = '0.1'", + "__init__.pyi": "__version__: str", "cli.py": "def main(): print('hello world')", "data.txt": "def main(): print('hello world')", + "py.typed": "", } }, } @@ -407,8 +408,10 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script): 'foo-0.1/src', 'foo-0.1/src/foo', 'foo-0.1/src/foo/__init__.py', + 'foo-0.1/src/foo/__init__.pyi', 'foo-0.1/src/foo/cli.py', 'foo-0.1/src/foo/data.txt', + 'foo-0.1/src/foo/py.typed', 'foo-0.1/src/foo.egg-info', 'foo-0.1/src/foo.egg-info/PKG-INFO', 'foo-0.1/src/foo.egg-info/SOURCES.txt', @@ -420,8 +423,10 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script): } assert wheel_contents == { "foo/__init__.py", + "foo/__init__.pyi", # include type information by default "foo/cli.py", "foo/data.txt", # include_package_data defaults to True + "foo/py.typed", # include type information by default "foo-0.1.dist-info/LICENSE.txt", "foo-0.1.dist-info/METADATA", "foo-0.1.dist-info/WHEEL", @@ -982,99 +987,3 @@ def test_system_exit_in_setuppy(monkeypatch, tmp_path): with pytest.raises(SystemExit, match="some error"): backend = BuildBackend(backend_name="setuptools.build_meta") backend.get_requires_for_build_wheel() - - -class TestTypeInformationIncludedByDefault: - dont_include_package_data = """ - [project] - name = "foo" - version = "1" - [tools.setuptools] - include-package-data = false - """ - - exclude_type_info = """ - [tool.setuptools.exclude-package-data] - "*" = ["py.typed", "*.pyi"] - """ - - package_1 = { - "foo": { - "bar.pyi": "", - "py.typed": "", - } - } - - package_2 = { - "foo": { - "bar": { - "py.typed": "", - "mod.pyi": "", - } - } - } - - package_3 = { - "foo": { - "namespace": { - "foo.pyi": "", - }, - "__init__.pyi": "", - "py.typed": "", - } - } - - packages_to_test = [package_1, package_2, package_3] - - def is_type_information_file(self, filename): - basename = os.path.basename(filename) - return basename.endswith(".pyi") or basename == "py.typed" - - def get_type_files(self, file_spec): - output = set() - for key in file_spec.keys(): - if isinstance(file_spec[key], str): - if self.is_type_information_file(key): - output.add(key) - else: - output.update( - key + "/" + f for f in self.get_type_files(file_spec[key]) - ) - return output - - @pytest.fixture(params=itertools.product(packages_to_test, [True, False])) - def file_spec_and_expected(self, request): - file_spec, exclude_type_information = request.param - pyproject = self.dont_include_package_data - if exclude_type_information: - pyproject += self.exclude_type_info - file_spec["pyproject.toml"] = pyproject - - if exclude_type_information: - expected = set() - else: - expected = self.get_type_files(file_spec) - - yield file_spec, expected - - def test_type_information_always_included( - self, monkeypatch, tmp_path, file_spec_and_expected - ): - """Setuptools should include type information in the wheel (py.typed, *.pyi).""" - file_spec, expected = file_spec_and_expected - monkeypatch.chdir(tmp_path) - dist_dir = os.path.abspath('pip-wheel') - os.makedirs(dist_dir) - path.build(file_spec) - build_backend = BuildBackend(backend_name="setuptools.build_meta") - wheel_name = build_backend.build_wheel(dist_dir) - - wheel_file = os.path.join(dist_dir, wheel_name) - assert os.path.isfile(wheel_file) - - with ZipFile(wheel_file) as zipfile: - wheel_contents = set( - filter(self.is_type_information_file, zipfile.namelist()) - ) - - assert wheel_contents == expected diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index ca50ce634a..ca10645b0c 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -319,3 +319,155 @@ def test_get_outputs(tmpdir_cwd): f"{build_lib}/mypkg/sub2/nested/__init__.py": "other/__init__.py", f"{build_lib}/mypkg/sub2/nested/mod3.py": "other/mod3.py", } + + +PYPROJECTS_FOR_TYPE_INFO_TEST = { + "default_pyproject": DALS( + """ + [project] + name = "foo" + version = "1" + """ + ), + "dont_include_package_data": DALS( + """ + [project] + name = "foo" + version = "1" + + [tools.setuptools] + include-package-data = false + """ + ), + "exclude_type_info": DALS( + """ + [project] + name = "foo" + version = "1" + + [tools.setuptools] + include-package-data = false + + [tool.setuptools.exclude-package-data] + "*" = ["py.typed", "*.pyi"] + """ + ), +} + +EXAMPLES_FOR_TYPE_INFO_TEST = { + "simple_namespace": { + "directory_structure": { + "foo": { + "bar.pyi": "", + "py.typed": "", + "__init__.py": "", + } + }, + "expected_type_files": {"foo/bar.pyi", "foo/py.typed"}, + }, + "nested_inside_namespace": { + "directory_structure": { + "foo": { + "bar": { + "py.typed": "", + "mod.pyi": "", + } + } + }, + "expected_type_files": {"foo/bar/mod.pyi", "foo/bar/py.typed"}, + }, + "namespace_nested_inside_regular": { + "directory_structure": { + "foo": { + "namespace": { + "foo.pyi": "", + }, + "__init__.pyi": "", + "py.typed": "", + } + }, + "expected_type_files": { + "foo/namespace/foo.pyi", + "foo/__init__.pyi", + "foo/py.typed", + }, + }, +} + + +@pytest.mark.parametrize( + "pyproject", ["default_pyproject", "dont_include_package_data"] +) +@pytest.mark.parametrize("example", EXAMPLES_FOR_TYPE_INFO_TEST.keys()) +def test_type_files_included_by_default(tmpdir_cwd, pyproject, example): + structure = EXAMPLES_FOR_TYPE_INFO_TEST[example]["directory_structure"] + expected_type_files = EXAMPLES_FOR_TYPE_INFO_TEST[example]["expected_type_files"] + jaraco.path.build(structure) + pyproject_contents = PYPROJECTS_FOR_TYPE_INFO_TEST[pyproject] + with open("pyproject.toml", "w") as pyproject_file: + pyproject_file.write(pyproject_contents) + + dist = Distribution({"script_name": "%PEP 517%"}) + dist.parse_config_files() + build_py = dist.get_command_obj("build_py") + build_py.finalize_options() + build_py.run() + + build_dir = Path(dist.get_command_obj("build_py").build_lib) + outputs = { + os.path.relpath(x.replace(os.sep, "/"), build_dir) + for x in build_py.get_outputs() + } + assert expected_type_files <= outputs + + +@pytest.mark.parametrize("pyproject", ["exclude_type_info"]) +@pytest.mark.parametrize("example", EXAMPLES_FOR_TYPE_INFO_TEST.keys()) +def test_type_files_can_be_excluded(tmpdir_cwd, pyproject, example): + structure = EXAMPLES_FOR_TYPE_INFO_TEST[example]["directory_structure"] + expected_type_files = EXAMPLES_FOR_TYPE_INFO_TEST[example]["expected_type_files"] + jaraco.path.build(structure) + pyproject_contents = PYPROJECTS_FOR_TYPE_INFO_TEST[pyproject] + with open("pyproject.toml", "w") as pyproject_file: + pyproject_file.write(pyproject_contents) + + dist = Distribution({"script_name": "%PEP 517%"}) + dist.parse_config_files() + build_py = dist.get_command_obj("build_py") + build_py.finalize_options() + build_py.run() + + build_dir = Path(dist.get_command_obj("build_py").build_lib) + outputs = { + os.path.relpath(x.replace(os.sep, "/"), build_dir) + for x in build_py.get_outputs() + } + assert expected_type_files.isdisjoint(outputs) + + +def test_stub_only_package(tmpdir_cwd): + structure = {"foo-stubs": {"__init__.pyi": "", "bar.pyi": ""}} + expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"} + jaraco.path.build(structure) + pyproject_contents = DALS( + """ + [project] + name = "foo-stubs" + version = "1" + """ + ) + with open("pyproject.toml", "w") as pyproject_file: + pyproject_file.write(pyproject_contents) + + dist = Distribution({"script_name": "%PEP 517%"}) + dist.parse_config_files() + build_py = dist.get_command_obj("build_py") + build_py.finalize_options() + build_py.run() + + build_dir = Path(dist.get_command_obj("build_py").build_lib) + outputs = { + os.path.relpath(x.replace(os.sep, "/"), build_dir) + for x in build_py.get_outputs() + } + assert expected_type_files <= outputs From 6857643df8ec201c308eb32f6d1467f7417377a7 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Tue, 19 Sep 2023 20:28:11 +0100 Subject: [PATCH 11/16] Fix test on windows by replacing os.sep --- setuptools/tests/test_build_py.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index ca10645b0c..d86beab486 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -415,7 +415,7 @@ def test_type_files_included_by_default(tmpdir_cwd, pyproject, example): build_dir = Path(dist.get_command_obj("build_py").build_lib) outputs = { - os.path.relpath(x.replace(os.sep, "/"), build_dir) + os.path.relpath(x, build_dir).replace(os.sep, "/") for x in build_py.get_outputs() } assert expected_type_files <= outputs @@ -439,7 +439,7 @@ def test_type_files_can_be_excluded(tmpdir_cwd, pyproject, example): build_dir = Path(dist.get_command_obj("build_py").build_lib) outputs = { - os.path.relpath(x.replace(os.sep, "/"), build_dir) + os.path.relpath(x, build_dir).replace(os.sep, "/") for x in build_py.get_outputs() } assert expected_type_files.isdisjoint(outputs) @@ -467,7 +467,7 @@ def test_stub_only_package(tmpdir_cwd): build_dir = Path(dist.get_command_obj("build_py").build_lib) outputs = { - os.path.relpath(x.replace(os.sep, "/"), build_dir) + os.path.relpath(x, build_dir).replace(os.sep, "/") for x in build_py.get_outputs() } assert expected_type_files <= outputs From 67c071c409f393982efcc88e1e8134118bb11465 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Fri, 13 Oct 2023 14:53:05 +0100 Subject: [PATCH 12/16] Refactor type files tests in test_build_py --- setuptools/tests/test_build_py.py | 250 ++++++++++++++---------------- 1 file changed, 117 insertions(+), 133 deletions(-) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index d86beab486..c6ddc09dd8 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -321,153 +321,137 @@ def test_get_outputs(tmpdir_cwd): } -PYPROJECTS_FOR_TYPE_INFO_TEST = { - "default_pyproject": DALS( - """ - [project] - name = "foo" - version = "1" - """ - ), - "dont_include_package_data": DALS( - """ - [project] - name = "foo" - version = "1" - - [tools.setuptools] - include-package-data = false - """ - ), - "exclude_type_info": DALS( - """ - [project] - name = "foo" - version = "1" - - [tools.setuptools] - include-package-data = false - - [tool.setuptools.exclude-package-data] - "*" = ["py.typed", "*.pyi"] - """ - ), -} +class TestTypeInfoFiles: + PYPROJECTS = { + "default_pyproject": DALS( + """ + [project] + name = "foo" + version = "1" + """ + ), + "dont_include_package_data": DALS( + """ + [project] + name = "foo" + version = "1" + + [tools.setuptools] + include-package-data = false + """ + ), + "exclude_type_info": DALS( + """ + [project] + name = "foo" + version = "1" + + [tools.setuptools] + include-package-data = false + + [tool.setuptools.exclude-package-data] + "*" = ["py.typed", "*.pyi"] + """ + ), + } -EXAMPLES_FOR_TYPE_INFO_TEST = { - "simple_namespace": { - "directory_structure": { - "foo": { - "bar.pyi": "", - "py.typed": "", - "__init__.py": "", - } - }, - "expected_type_files": {"foo/bar.pyi", "foo/py.typed"}, - }, - "nested_inside_namespace": { - "directory_structure": { - "foo": { - "bar": { + EXAMPLES = { + "simple_namespace": { + "directory_structure": { + "foo": { + "bar.pyi": "", "py.typed": "", - "mod.pyi": "", + "__init__.py": "", } - } + }, + "expected_type_files": {"foo/bar.pyi", "foo/py.typed"}, }, - "expected_type_files": {"foo/bar/mod.pyi", "foo/bar/py.typed"}, - }, - "namespace_nested_inside_regular": { - "directory_structure": { - "foo": { - "namespace": { - "foo.pyi": "", - }, - "__init__.pyi": "", - "py.typed": "", - } + "nested_inside_namespace": { + "directory_structure": { + "foo": { + "bar": { + "py.typed": "", + "mod.pyi": "", + } + } + }, + "expected_type_files": {"foo/bar/mod.pyi", "foo/bar/py.typed"}, }, - "expected_type_files": { - "foo/namespace/foo.pyi", - "foo/__init__.pyi", - "foo/py.typed", + "namespace_nested_inside_regular": { + "directory_structure": { + "foo": { + "namespace": { + "foo.pyi": "", + }, + "__init__.pyi": "", + "py.typed": "", + } + }, + "expected_type_files": { + "foo/namespace/foo.pyi", + "foo/__init__.pyi", + "foo/py.typed", + }, }, - }, -} - - -@pytest.mark.parametrize( - "pyproject", ["default_pyproject", "dont_include_package_data"] -) -@pytest.mark.parametrize("example", EXAMPLES_FOR_TYPE_INFO_TEST.keys()) -def test_type_files_included_by_default(tmpdir_cwd, pyproject, example): - structure = EXAMPLES_FOR_TYPE_INFO_TEST[example]["directory_structure"] - expected_type_files = EXAMPLES_FOR_TYPE_INFO_TEST[example]["expected_type_files"] - jaraco.path.build(structure) - pyproject_contents = PYPROJECTS_FOR_TYPE_INFO_TEST[pyproject] - with open("pyproject.toml", "w") as pyproject_file: - pyproject_file.write(pyproject_contents) - - dist = Distribution({"script_name": "%PEP 517%"}) - dist.parse_config_files() - build_py = dist.get_command_obj("build_py") - build_py.finalize_options() - build_py.run() - - build_dir = Path(dist.get_command_obj("build_py").build_lib) - outputs = { - os.path.relpath(x, build_dir).replace(os.sep, "/") - for x in build_py.get_outputs() - } - assert expected_type_files <= outputs - - -@pytest.mark.parametrize("pyproject", ["exclude_type_info"]) -@pytest.mark.parametrize("example", EXAMPLES_FOR_TYPE_INFO_TEST.keys()) -def test_type_files_can_be_excluded(tmpdir_cwd, pyproject, example): - structure = EXAMPLES_FOR_TYPE_INFO_TEST[example]["directory_structure"] - expected_type_files = EXAMPLES_FOR_TYPE_INFO_TEST[example]["expected_type_files"] - jaraco.path.build(structure) - pyproject_contents = PYPROJECTS_FOR_TYPE_INFO_TEST[pyproject] - with open("pyproject.toml", "w") as pyproject_file: - pyproject_file.write(pyproject_contents) - - dist = Distribution({"script_name": "%PEP 517%"}) - dist.parse_config_files() - build_py = dist.get_command_obj("build_py") - build_py.finalize_options() - build_py.run() - - build_dir = Path(dist.get_command_obj("build_py").build_lib) - outputs = { - os.path.relpath(x, build_dir).replace(os.sep, "/") - for x in build_py.get_outputs() } - assert expected_type_files.isdisjoint(outputs) - -def test_stub_only_package(tmpdir_cwd): - structure = {"foo-stubs": {"__init__.pyi": "", "bar.pyi": ""}} - expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"} - jaraco.path.build(structure) - pyproject_contents = DALS( - """ - [project] - name = "foo-stubs" - version = "1" - """ + @pytest.mark.parametrize( + "pyproject", ["default_pyproject", "dont_include_package_data"] ) - with open("pyproject.toml", "w") as pyproject_file: - pyproject_file.write(pyproject_contents) - - dist = Distribution({"script_name": "%PEP 517%"}) + @pytest.mark.parametrize("example", EXAMPLES.keys()) + def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example): + structure = self.EXAMPLES[example]["directory_structure"] + expected_type_files = self.EXAMPLES[example]["expected_type_files"] + structure["pyproject.toml"] = self.PYPROJECTS[pyproject] + jaraco.path.build(structure) + + build_py = run_build_py() + outputs = get_outputs(build_py) + assert expected_type_files <= outputs + + @pytest.mark.parametrize("pyproject", ["exclude_type_info"]) + @pytest.mark.parametrize("example", EXAMPLES.keys()) + def test_type_files_can_be_excluded(self, tmpdir_cwd, pyproject, example): + structure = self.EXAMPLES[example]["directory_structure"] + expected_type_files = self.EXAMPLES[example]["expected_type_files"] + structure["pyproject.toml"] = self.PYPROJECTS[pyproject] + jaraco.path.build(structure) + + build_py = run_build_py() + outputs = get_outputs(build_py) + assert expected_type_files.isdisjoint(outputs) + + def test_stub_only_package(self, tmpdir_cwd): + structure = { + "pyproject.toml": DALS( + """ + [project] + name = "foo-stubs" + version = "1" + """ + ), + "foo-stubs": {"__init__.pyi": "", "bar.pyi": ""}, + } + expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"} + jaraco.path.build(structure) + + build_py = run_build_py() + outputs = get_outputs(build_py) + assert expected_type_files <= outputs + + +def run_build_py(script_name="%build_meta%"): + dist = Distribution({"script_name": script_name}) dist.parse_config_files() build_py = dist.get_command_obj("build_py") build_py.finalize_options() build_py.run() + return build_py - build_dir = Path(dist.get_command_obj("build_py").build_lib) - outputs = { + +def get_outputs(build_py): + build_dir = Path(build_py.build_lib) + return { os.path.relpath(x, build_dir).replace(os.sep, "/") for x in build_py.get_outputs() } - assert expected_type_files <= outputs From cbd4f51d5a2136a262c8b84ffdf413f57c2ccdd3 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Fri, 13 Oct 2023 15:01:19 +0100 Subject: [PATCH 13/16] Avoid running build_py in tests to speed up process --- setuptools/tests/test_build_py.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index c6ddc09dd8..39a044dd54 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -405,7 +405,7 @@ def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example): structure["pyproject.toml"] = self.PYPROJECTS[pyproject] jaraco.path.build(structure) - build_py = run_build_py() + build_py = get_finalized_build_py() outputs = get_outputs(build_py) assert expected_type_files <= outputs @@ -417,7 +417,7 @@ def test_type_files_can_be_excluded(self, tmpdir_cwd, pyproject, example): structure["pyproject.toml"] = self.PYPROJECTS[pyproject] jaraco.path.build(structure) - build_py = run_build_py() + build_py = get_finalized_build_py() outputs = get_outputs(build_py) assert expected_type_files.isdisjoint(outputs) @@ -435,17 +435,16 @@ def test_stub_only_package(self, tmpdir_cwd): expected_type_files = {"foo-stubs/__init__.pyi", "foo-stubs/bar.pyi"} jaraco.path.build(structure) - build_py = run_build_py() + build_py = get_finalized_build_py() outputs = get_outputs(build_py) assert expected_type_files <= outputs -def run_build_py(script_name="%build_meta%"): +def get_finalized_build_py(script_name="%build_py-test%"): dist = Distribution({"script_name": script_name}) dist.parse_config_files() build_py = dist.get_command_obj("build_py") build_py.finalize_options() - build_py.run() return build_py From 111b05f4e63011ebb3a5425eba19c397cc7dcfc5 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Fri, 13 Oct 2023 15:22:22 +0100 Subject: [PATCH 14/16] Mark feature as experimental in docs --- docs/userguide/miscellaneous.rst | 13 +++++++++++++ newsfragments/3136.feature.rst | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/userguide/miscellaneous.rst b/docs/userguide/miscellaneous.rst index 19908e05ad..bba3928aa5 100644 --- a/docs/userguide/miscellaneous.rst +++ b/docs/userguide/miscellaneous.rst @@ -10,6 +10,19 @@ These include all :term:`pure Python modules ` in the headers) listed as part of extensions when creating a :term:`source distribution (or "sdist")`. +.. note:: + .. versionadded:: v68.3.0 + ``setuptools`` will attempt to include type information files + by default in the distribution + (``.pyi`` and ``py.typed``, as specified in :pep:`561`). + + *Please note however that this feature is* **EXPERIMENTAL** *and my change in + the future.* + + If you have ``.pyi`` and ``py.typed`` files in your project, but do not + wish to distribute them, you can opt out by setting + :doc:`exclude-package-data ` to remove them. + However, when building more complex packages (e.g. packages that include non-Python files, or that need to use custom C headers), you might find that not all files present in your project folder are included in package diff --git a/newsfragments/3136.feature.rst b/newsfragments/3136.feature.rst index d87124cc0f..a57a8f4e57 100644 --- a/newsfragments/3136.feature.rst +++ b/newsfragments/3136.feature.rst @@ -1 +1,2 @@ -Include type information (py.typed, *.pyi) by default (#3136) -- by :user:`Danie-1` +Include type information (``py.typed``, ``*.pyi``) by default (#3136) -- by :user:`Danie-1`, +**EXPERIMENTAL**. From 63f82edcb5365a6734c3e1c0e1c95302ca876f73 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Fri, 13 Oct 2023 15:34:28 +0100 Subject: [PATCH 15/16] Avoid modifying dict in test_build_py --- setuptools/tests/test_build_py.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index 39a044dd54..500a9ab6f3 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -400,9 +400,11 @@ class TestTypeInfoFiles: ) @pytest.mark.parametrize("example", EXAMPLES.keys()) def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example): - structure = self.EXAMPLES[example]["directory_structure"] + structure = { + **self.EXAMPLES[example]["directory_structure"], + "pyproject.toml": self.PYPROJECTS[pyproject], + } expected_type_files = self.EXAMPLES[example]["expected_type_files"] - structure["pyproject.toml"] = self.PYPROJECTS[pyproject] jaraco.path.build(structure) build_py = get_finalized_build_py() @@ -412,9 +414,11 @@ def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example): @pytest.mark.parametrize("pyproject", ["exclude_type_info"]) @pytest.mark.parametrize("example", EXAMPLES.keys()) def test_type_files_can_be_excluded(self, tmpdir_cwd, pyproject, example): - structure = self.EXAMPLES[example]["directory_structure"] + structure = { + **self.EXAMPLES[example]["directory_structure"], + "pyproject.toml": self.PYPROJECTS[pyproject], + } expected_type_files = self.EXAMPLES[example]["expected_type_files"] - structure["pyproject.toml"] = self.PYPROJECTS[pyproject] jaraco.path.build(structure) build_py = get_finalized_build_py() From 42fc47eb5714b8d34cae669a772792b42ca05cb1 Mon Sep 17 00:00:00 2001 From: Daniel Naylor Date: Sat, 14 Oct 2023 22:54:19 +0100 Subject: [PATCH 16/16] Fix typo in docs --- docs/userguide/miscellaneous.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/miscellaneous.rst b/docs/userguide/miscellaneous.rst index bba3928aa5..ea0a58845e 100644 --- a/docs/userguide/miscellaneous.rst +++ b/docs/userguide/miscellaneous.rst @@ -16,7 +16,7 @@ distribution (or "sdist")`. by default in the distribution (``.pyi`` and ``py.typed``, as specified in :pep:`561`). - *Please note however that this feature is* **EXPERIMENTAL** *and my change in + *Please note however that this feature is* **EXPERIMENTAL** *and may change in the future.* If you have ``.pyi`` and ``py.typed`` files in your project, but do not