From 346dc8e2035d451154bbd3e107bf90d1e8879ea7 Mon Sep 17 00:00:00 2001 From: JinWang An Date: Mon, 27 Mar 2023 17:02:59 +0900 Subject: [PATCH] Imported Upstream version 66.1.0 --- .bumpversion.cfg | 2 +- CHANGES.rst | 17 +++++++++ docs/artwork.rst | 3 +- docs/build_meta.rst | 2 +- docs/deprecated/easy_install.rst | 4 +-- docs/userguide/declarative_config.rst | 15 ++++---- docs/userguide/package_discovery.rst | 2 +- docs/userguide/pyproject_config.rst | 13 ++++--- pkg_resources/__init__.py | 23 ++++++++----- setup.cfg | 2 +- setuptools/__init__.py | 21 ++++++++++++ setuptools/command/egg_info.py | 10 +++++- setuptools/config/_apply_pyprojecttoml.py | 13 +++++-- setuptools/config/pyprojecttoml.py | 9 +++-- setuptools/config/setupcfg.py | 15 +++++--- setuptools/dist.py | 7 +++- setuptools/tests/test_sdist.py | 42 +++++++++++++++++++++++ 17 files changed, 160 insertions(+), 40 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a7f4e50..d997ebc 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 66.0.0 +current_version = 66.1.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index a5d7a4a..7a9d817 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,20 @@ +v66.1.0 +------- + + +Changes +^^^^^^^ +* #3685: Fix improper usage of deprecated/removed ``pkgutil`` APIs in Python 3.12+. +* #3779: Files referenced by ``file:`` in ``setup.cfg`` and by ``project.readme.file``, + ``project.license.file`` or ``tool.setuptools.dynamic.*.file`` in + ``pyproject.toml`` are now automatically included in the generated sdists. + +Misc +^^^^ +* #3776: Added note about using the ``--pep-517`` flag with ``pip`` to workaround + ``InvalidVersion`` errors for packages that are already installed in the system. + + v66.0.0 ------- diff --git a/docs/artwork.rst b/docs/artwork.rst index 907e62a..d815561 100644 --- a/docs/artwork.rst +++ b/docs/artwork.rst @@ -65,7 +65,7 @@ Inspiration This design was inspired by :user:`cajhne`'s `original proposal`_ and the ancient symbol of the ouroboros_. It features a snake moving in a circular trajectory not only as a reference to -the Python programming language but also to the `wheel package format`_ as one +the Python programming language but also to the :pep:`wheel package format <427>` as one of the distribution formats supported by setuptools. The shape of the snake also resembles a cog, which together with the hammer is a nod to the two words that compose the name of the project. @@ -115,5 +115,4 @@ https://github.com/pypa/setuptools or https://setuptools.pypa.io. .. _setuptools repository: https://github.com/pypa/setuptools .. _install the correct fonts: https://wiki.inkscape.org/wiki/Installing_fonts .. _original proposal: https://github.com/pypa/setuptools/issues/2227#issuecomment-653628344 -.. _wheel package format: https://www.python.org/dev/peps/pep-0427/ .. _ouroboros: https://en.wikipedia.org/wiki/Ouroboros diff --git a/docs/build_meta.rst b/docs/build_meta.rst index 08fd8a7..197e591 100644 --- a/docs/build_meta.rst +++ b/docs/build_meta.rst @@ -16,7 +16,7 @@ overhaul. Because ``setup.py`` scripts allow for arbitrary execution, it is difficult to provide a reliable user experience across environments and history. -`PEP 517 `_ came to +:pep:`517` came to the rescue and specified a new standard for packaging and distributing Python modules. Under PEP 517: diff --git a/docs/deprecated/easy_install.rst b/docs/deprecated/easy_install.rst index 3cf3bea..ab3d38a 100644 --- a/docs/deprecated/easy_install.rst +++ b/docs/deprecated/easy_install.rst @@ -989,9 +989,7 @@ The following section lists only the easiest and most relevant approaches [1]_. `Use "virtualenv"`_ -.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_. - -.. _PEP-370: http://www.python.org/dev/peps/pep-0370/ +.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by :pep:`370`. Use the "--user" option diff --git a/docs/userguide/declarative_config.rst b/docs/userguide/declarative_config.rst index adedb0c..d573516 100644 --- a/docs/userguide/declarative_config.rst +++ b/docs/userguide/declarative_config.rst @@ -30,7 +30,6 @@ boilerplate code in some cases. description = My package description long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst keywords = one, two - python_requires = >=3.7 license = BSD-3-Clause classifiers = Framework :: Django @@ -40,6 +39,7 @@ boilerplate code in some cases. zip_safe = False include_package_data = True packages = find: + python_requires = >=3.7 install_requires = requests importlib-metadata; python_version<"3.8" @@ -169,11 +169,14 @@ Special directives: The ``file:`` directive is sandboxed and won't reach anything outside the project directory (i.e. the directory containing ``setup.cfg``/``pyproject.toml``). - .. attention:: - When using the ``file:`` directive, please make sure that all necessary - files are included in the ``sdist``. You can do that via ``MANIFEST.in`` - or using plugins such as ``setuptools-scm``. - Please have a look on :doc:`/userguide/miscellaneous` for more information. + .. note:: + If you are using an old version of ``setuptools``, you might need to ensure + that all files referenced by the ``file:`` directive are included in the ``sdist`` + (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, + please have a look on :doc:`/userguide/miscellaneous` for more information). + + .. versionchanged:: 66.1.0 + Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. Metadata diff --git a/docs/userguide/package_discovery.rst b/docs/userguide/package_discovery.rst index 7dda84a..9577a53 100644 --- a/docs/userguide/package_discovery.rst +++ b/docs/userguide/package_discovery.rst @@ -508,7 +508,7 @@ available to your interpreter. Legacy Namespace Packages ========================= The fact you can create namespace packages so effortlessly above is credited -to `PEP 420 `_. It used to be more +to :pep:`420`. It used to be more cumbersome to accomplish the same result. Historically, there were two methods to create namespace packages. One is the ``pkg_resources`` style supported by ``setuptools`` and the other one being ``pkgutils`` style offered by diff --git a/docs/userguide/pyproject_config.rst b/docs/userguide/pyproject_config.rst index 633f4de..c97984b 100644 --- a/docs/userguide/pyproject_config.rst +++ b/docs/userguide/pyproject_config.rst @@ -220,11 +220,14 @@ however please keep in mind that all non-comment lines must conform with :pep:`5 (``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported). -.. attention:: - When using the ``file`` directive, please make sure that all necessary - files are included in the ``sdist``. You can do that via ``MANIFEST.in`` - or using plugins such as ``setuptools-scm``. - Please have a look on :doc:`/userguide/miscellaneous` for more information. +.. note:: + If you are using an old version of ``setuptools``, you might need to ensure + that all files referenced by the ``file`` directive are included in the ``sdist`` + (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, + please have a look on :doc:`/userguide/miscellaneous` for more information). + + .. versionchanged:: 66.1.0 + Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. ---- diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index bba775b..0ae951b 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2187,10 +2187,10 @@ def resolve_egg_link(path): return next(dist_groups, ()) -register_finder(pkgutil.ImpImporter, find_on_path) +if hasattr(pkgutil, 'ImpImporter'): + register_finder(pkgutil.ImpImporter, find_on_path) -if hasattr(importlib_machinery, 'FileFinder'): - register_finder(importlib_machinery.FileFinder, find_on_path) +register_finder(importlib_machinery.FileFinder, find_on_path) _declare_state('dict', _namespace_handlers={}) _declare_state('dict', _namespace_packages={}) @@ -2344,11 +2344,11 @@ def file_ns_handler(importer, path_item, packageName, module): return subpath -register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) -register_namespace_handler(zipimport.zipimporter, file_ns_handler) +if hasattr(pkgutil, 'ImpImporter'): + register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) -if hasattr(importlib_machinery, 'FileFinder'): - register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) +register_namespace_handler(zipimport.zipimporter, file_ns_handler) +register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) def null_ns_handler(importer, path_item, packageName, module): @@ -2675,7 +2675,14 @@ class Distribution: @property def parsed_version(self): if not hasattr(self, "_parsed_version"): - self._parsed_version = parse_version(self.version) + try: + self._parsed_version = parse_version(self.version) + except packaging.version.InvalidVersion as ex: + info = f"(package: {self.project_name})" + if hasattr(ex, "add_note"): + ex.add_note(info) # PEP 678 + raise + raise packaging.version.InvalidVersion(f"{str(ex)} {info}") from None return self._parsed_version diff --git a/setup.cfg b/setup.cfg index cd0e6b0..04d88d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = setuptools -version = 66.0.0 +version = 66.1.0 author = Python Packaging Authority author_email = distutils-sig@python.org description = Easily download, build, install, upgrade, and uninstall Python packages diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 6c24cc2..89f6f06 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -77,7 +77,28 @@ def _install_setup_requires(attrs): # Honor setup.cfg's options. dist.parse_config_files(ignore_option_errors=True) if dist.setup_requires: + _fetch_build_eggs(dist) + + +def _fetch_build_eggs(dist): + try: dist.fetch_build_eggs(dist.setup_requires) + except Exception as ex: + msg = """ + It is possible a package already installed in your system + contains an version that is invalid according to PEP 440. + You can try `pip install --use-pep517` as a workaround for this problem, + or rely on a new virtual environment. + + If the problem refers to a package that is not installed yet, + please contact that package's maintainers or distributors. + """ + if "InvalidVersion" in ex.__class__.__name__: + if hasattr(ex, "add_note"): + ex.add_note(msg) # PEP 678 + else: + dist.announce(f"\n{msg}\n") + raise def setup(**attrs): diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 1885efb..86e99dd 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -565,6 +565,7 @@ class manifest_maker(sdist): if os.path.exists(self.template): self.read_template() self.add_license_files() + self._add_referenced_files() self.prune_file_list() self.filelist.sort() self.filelist.remove_duplicates() @@ -619,9 +620,16 @@ class manifest_maker(sdist): license_files = self.distribution.metadata.license_files or [] for lf in license_files: log.info("adding license file '%s'", lf) - pass self.filelist.extend(license_files) + def _add_referenced_files(self): + """Add files referenced by the config (e.g. `file:` directive) to filelist""" + referenced = getattr(self.distribution, '_referenced_files', []) + # ^-- fallback if dist comes from distutils or is a custom class + for rf in referenced: + log.debug("adding file referenced by config '%s'", rf) + self.filelist.extend(referenced) + def prune_file_list(self): build = self.get_finalized_command('build') base_dir = self.distribution.get_fullname() diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 8af5561..c805e63 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -16,7 +16,7 @@ from functools import partial, reduce from itertools import chain from types import MappingProxyType from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, - Type, Union) + Type, Union, cast) from setuptools._deprecation_warning import SetuptoolsDeprecationWarning @@ -142,22 +142,29 @@ def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path): from setuptools.config import expand if isinstance(val, str): - text = expand.read_files(val, root_dir) + file: Union[str, list] = val + text = expand.read_files(file, root_dir) ctype = _guess_content_type(val) else: - text = val.get("text") or expand.read_files(val.get("file", []), root_dir) + file = val.get("file") or [] + text = val.get("text") or expand.read_files(file, root_dir) ctype = val["content-type"] _set_config(dist, "long_description", text) + if ctype: _set_config(dist, "long_description_content_type", ctype) + if file: + dist._referenced_files.add(cast(str, file)) + def _license(dist: "Distribution", val: dict, root_dir: _Path): from setuptools.config import expand if "file" in val: _set_config(dist, "license", expand.read_files([val["file"]], root_dir)) + dist._referenced_files.add(val["file"]) else: _set_config(dist, "license", val["text"]) diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index fee6fac..cedf567 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -8,7 +8,7 @@ import os import warnings from contextlib import contextmanager from functools import partial -from typing import TYPE_CHECKING, Callable, Dict, Optional, Mapping, Union +from typing import TYPE_CHECKING, Callable, Dict, Optional, Mapping, Set, Union from setuptools.errors import FileError, OptionError @@ -84,8 +84,8 @@ def read_configuration( :param Distribution|None: Distribution object to which the configuration refers. If not given a dummy object will be created and discarded after the - configuration is read. This is used for auto-discovery of packages in the case - a dynamic configuration (e.g. ``attr`` or ``cmdclass``) is expanded. + configuration is read. This is used for auto-discovery of packages and in the + case a dynamic configuration (e.g. ``attr`` or ``cmdclass``) is expanded. When ``expand=False`` this object is simply ignored. :rtype: dict @@ -211,6 +211,7 @@ class _ConfigExpander: self.dynamic_cfg = self.setuptools_cfg.get("dynamic", {}) self.ignore_option_errors = ignore_option_errors self._dist = dist + self._referenced_files: Set[str] = set() def _ensure_dist(self) -> "Distribution": from setuptools.dist import Distribution @@ -241,6 +242,7 @@ class _ConfigExpander: self._expand_cmdclass(package_dir) self._expand_all_dynamic(dist, package_dir) + dist._referenced_files.update(self._referenced_files) return self.config def _expand_packages(self): @@ -310,6 +312,7 @@ class _ConfigExpander: with _ignore_errors(self.ignore_option_errors): root_dir = self.root_dir if "file" in directive: + self._referenced_files.update(directive["file"]) return _expand.read_files(directive["file"], root_dir) if "attr" in directive: return _expand.read_attr(directive["attr"], package_dir, root_dir) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index c2a974d..3df3b6e 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -12,7 +12,7 @@ from collections import defaultdict from functools import partial from functools import wraps from typing import (TYPE_CHECKING, Callable, Any, Dict, Generic, Iterable, List, - Optional, Tuple, TypeVar, Union) + Optional, Set, Tuple, TypeVar, Union) from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement @@ -172,6 +172,9 @@ def parse_configuration( distribution.src_root, ) meta.parse() + distribution._referenced_files.update( + options._referenced_files, meta._referenced_files + ) return meta, options @@ -247,6 +250,10 @@ class ConfigHandler(Generic[Target]): self.sections = sections self.set_options: List[str] = [] self.ensure_discovered = ensure_discovered + self._referenced_files: Set[str] = set() + """After parsing configurations, this property will enumerate + all files referenced by the "file:" directive. Private API for setuptools only. + """ @property def parsers(self): @@ -365,8 +372,7 @@ class ConfigHandler(Generic[Target]): return parser - @classmethod - def _parse_file(cls, value, root_dir: _Path): + def _parse_file(self, value, root_dir: _Path): """Represents value as a string, allowing including text from nearest files using `file:` directive. @@ -388,7 +394,8 @@ class ConfigHandler(Generic[Target]): return value spec = value[len(include_directive) :] - filepaths = (path.strip() for path in spec.split(',')) + filepaths = [path.strip() for path in spec.split(',')] + self._referenced_files.update(filepaths) return expand.read_files(filepaths, root_dir) def _parse_attr(self, value, package_dir, root_dir: _Path): diff --git a/setuptools/dist.py b/setuptools/dist.py index 1c71e5e..cd34d74 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -17,7 +17,7 @@ from distutils.fancy_getopt import translate_longopt from glob import iglob import itertools import textwrap -from typing import List, Optional, TYPE_CHECKING +from typing import List, Optional, Set, TYPE_CHECKING from pathlib import Path from collections import defaultdict @@ -481,6 +481,11 @@ class Distribution(_Distribution): }, ) + # Private API (setuptools-use only, not restricted to Distribution) + # Stores files that are referenced by the configuration and need to be in the + # sdist (e.g. `version = file: VERSION.txt`) + self._referenced_files: Set[str] = set() + # Save the original dependencies before they are processed into the egg format self._orig_extras_require = {} self._orig_install_requires = [] diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 30631c2..11de75c 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -498,6 +498,48 @@ class TestSdistTest: filename = filename.decode('latin-1') filename not in cmd.filelist.files + _EXAMPLE_DIRECTIVES = { + "setup.cfg - long_description and version": """ + [metadata] + name = testing + version = file: VERSION.txt + license_files = DOWHATYOUWANT + long_description = file: README.rst, USAGE.rst + """, + "pyproject.toml - static readme/license files and dynamic version": """ + [project] + name = "testing" + readme = "USAGE.rst" + license = {file = "DOWHATYOUWANT"} + dynamic = ["version"] + [tool.setuptools.dynamic] + version = {file = ["VERSION.txt"]} + """ + } + + @pytest.mark.parametrize("config", _EXAMPLE_DIRECTIVES.keys()) + def test_add_files_referenced_by_config_directives(self, tmp_path, config): + config_file, _, _ = config.partition(" - ") + config_text = self._EXAMPLE_DIRECTIVES[config] + (tmp_path / 'VERSION.txt').write_text("0.42", encoding="utf-8") + (tmp_path / 'README.rst').write_text("hello world!", encoding="utf-8") + (tmp_path / 'USAGE.rst').write_text("hello world!", encoding="utf-8") + (tmp_path / 'DOWHATYOUWANT').write_text("hello world!", encoding="utf-8") + (tmp_path / config_file).write_text(config_text, encoding="utf-8") + + dist = Distribution({"packages": []}) + dist.script_name = 'setup.py' + dist.parse_config_files() + + cmd = sdist(dist) + cmd.ensure_finalized() + with quiet(): + cmd.run() + + assert 'VERSION.txt' in cmd.filelist.files + assert 'USAGE.rst' in cmd.filelist.files + assert 'DOWHATYOUWANT' in cmd.filelist.files + def test_pyproject_toml_in_sdist(self, tmpdir): """ Check if pyproject.toml is included in source distribution if present -- 2.34.1