Imported Upstream version 67.5.1 upstream/67.5.1
authorJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:03:01 +0000 (17:03 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:03:01 +0000 (17:03 +0900)
.bumpversion.cfg
CHANGES.rst
pkg_resources/__init__.py
setup.cfg
setuptools/config/_validate_pyproject/fastjsonschema_validations.py
setuptools/config/setupcfg.py
setuptools/discovery.py
setuptools/tests/test_find_packages.py

index 9b4a0540e43eabea00756b650e99e991a67b3f8b..89976bca50d34265c9afae5bbcd374a319a0885e 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 67.5.0
+current_version = 67.5.1
 commit = True
 tag = True
 
index 44d0e3519d9c1916e6b171e33d7cd062da4a81e2..7d5470a78f203c2e6027ea995c866af77bbe31ee 100644 (file)
@@ -1,3 +1,23 @@
+v67.5.1
+-------
+
+
+Misc
+^^^^
+* #3836: Fixed interaction between ``setuptools``' package auto-discovery and
+  auto-generated ``htmlcov`` files.
+
+  Previously, the ``htmlcov`` name was ignored when searching for single-file
+  modules, however the correct behaviour is to ignore it when searching for
+  packages (since it is supposed to be a directory, see `coverage config`_)
+  -- by :user:`yukihiko-shinoda`.
+
+  .. _coverage config: https://coverage.readthedocs.io/en/stable/config.html#html-directory
+* #3838: Improved error messages for ``pyproject.toml`` validations.
+* #3839: Fixed ``pkg_resources`` errors caused when parsing metadata of packages that
+  are already installed but do not conform with PEP 440.
+
+
 v67.5.0
 -------
 
index e08d17f42372683fececdfc10c935f217bc79968..a73a1df3b61075ef9b63ab8b835477a172244ba8 100644 (file)
@@ -121,6 +121,9 @@ _namespace_packages = None
 warnings.warn("pkg_resources is deprecated as an API", DeprecationWarning)
 
 
+_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)
+
+
 class PEP440Warning(RuntimeWarning):
     """
     Used when there is an issue with a version or specifier not complying with
@@ -1396,6 +1399,38 @@ def safe_version(version):
         return re.sub('[^A-Za-z0-9.]+', '-', version)
 
 
+def _forgiving_version(version):
+    """Fallback when ``safe_version`` is not safe enough
+    >>> parse_version(_forgiving_version('0.23ubuntu1'))
+    <Version('0.23.dev0+sanitized.ubuntu1')>
+    >>> parse_version(_forgiving_version('0.23-'))
+    <Version('0.23.dev0+sanitized')>
+    >>> parse_version(_forgiving_version('0.-_'))
+    <Version('0.dev0+sanitized')>
+    >>> parse_version(_forgiving_version('42.+?1'))
+    <Version('42.dev0+sanitized.1')>
+    >>> parse_version(_forgiving_version('hello world'))
+    <Version('0.dev0+sanitized.hello.world')>
+    """
+    version = version.replace(' ', '.')
+    match = _PEP440_FALLBACK.search(version)
+    if match:
+        safe = match["safe"]
+        rest = version[len(safe):]
+    else:
+        safe = "0"
+        rest = version
+    local = f"sanitized.{_safe_segment(rest)}".strip(".")
+    return f"{safe}.dev0+{local}"
+
+
+def _safe_segment(segment):
+    """Convert an arbitrary string into a safe segment"""
+    segment = re.sub('[^A-Za-z0-9.]+', '-', segment)
+    segment = re.sub('-[^A-Za-z0-9]+', '-', segment)
+    return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-")
+
+
 def safe_extra(extra):
     """Convert an arbitrary string to a standard 'extra' name
 
@@ -2642,7 +2677,7 @@ class Distribution:
     @property
     def hashcmp(self):
         return (
-            self.parsed_version,
+            self._forgiving_parsed_version,
             self.precedence,
             self.key,
             self.location,
@@ -2700,6 +2735,32 @@ class Distribution:
 
         return self._parsed_version
 
+    @property
+    def _forgiving_parsed_version(self):
+        try:
+            return self.parsed_version
+        except packaging.version.InvalidVersion as ex:
+            self._parsed_version = parse_version(_forgiving_version(self.version))
+
+            notes = "\n".join(getattr(ex, "__notes__", []))  # PEP 678
+            msg = f"""!!\n\n
+            *************************************************************************
+            {str(ex)}\n{notes}
+
+            This is a long overdue deprecation.
+            For the time being, `pkg_resources` will use `{self._parsed_version}`
+            as a replacement to avoid breaking existing environments,
+            but no future compatibility is guaranteed.
+
+            If you maintain package {self.project_name} you should implement
+            the relevant changes to adequate the project to PEP 440 immediately.
+            *************************************************************************
+            \n\n!!
+            """
+            warnings.warn(msg, DeprecationWarning)
+
+            return self._parsed_version
+
     @property
     def version(self):
         try:
index 947f627b400756022f78c35bc5787a5670c1787a..cb4d928dfc44dfb63058fdc6530793caea6a92f0 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 67.5.0
+version = 67.5.1
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
index 30d8d63e92ee6e9c5fe9042de293b299a0a508d5..cd4e4440739fe148fc8eae83c74310986355d4cc 100644 (file)
@@ -10,7 +10,7 @@
 # *** PLEASE DO NOT MODIFY DIRECTLY: Automatically generated code *** 
 
 
-VERSION = "2.16.2"
+VERSION = "2.16.3"
 import re
 from .fastjsonschema_exceptions import JsonSchemaValueException
 
@@ -185,7 +185,7 @@ def validate_https___setuptools_pypa_io_en_latest_references_keywords_html(data,
                     if data__packages_is_list:
                         data__packages_len = len(data__packages)
                         for data__packages_x, data__packages_item in enumerate(data__packages):
-                            validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_package_name(data__packages_item, custom_formats, (name_prefix or "data") + ".packages[{data__packages_x}]")
+                            validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_package_name(data__packages_item, custom_formats, (name_prefix or "data") + ".packages[{data__packages_x}]".format(**locals()))
                     data__packages_one_of_count1 += 1
                 except JsonSchemaValueException: pass
             if data__packages_one_of_count1 < 2:
@@ -462,7 +462,7 @@ def validate_https___setuptools_pypa_io_en_latest_references_keywords_html(data,
                             if REGEX_PATTERNS['.+'].search(data__dynamic__optionaldependencies_key):
                                 if data__dynamic__optionaldependencies_key in data__dynamic__optionaldependencies_keys:
                                     data__dynamic__optionaldependencies_keys.remove(data__dynamic__optionaldependencies_key)
-                                validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__optionaldependencies_val, custom_formats, (name_prefix or "data") + ".dynamic.optional-dependencies.{data__dynamic__optionaldependencies_key}")
+                                validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__optionaldependencies_val, custom_formats, (name_prefix or "data") + ".dynamic.optional-dependencies.{data__dynamic__optionaldependencies_key}".format(**locals()))
                         if data__dynamic__optionaldependencies_keys:
                             raise JsonSchemaValueException("" + (name_prefix or "data") + ".dynamic.optional-dependencies must not contain "+str(data__dynamic__optionaldependencies_keys)+" properties", value=data__dynamic__optionaldependencies, name="" + (name_prefix or "data") + ".dynamic.optional-dependencies", definition={'type': 'object', 'propertyNames': {'format': 'python-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive", 'description': 'Value is read from a file (or list of files and then concatenated)', 'type': 'object', 'additionalProperties': False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'required': ['file']}}}, rule='additionalProperties')
                         data__dynamic__optionaldependencies_len = len(data__dynamic__optionaldependencies)
@@ -810,7 +810,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_pro
             if data__authors_is_list:
                 data__authors_len = len(data__authors)
                 for data__authors_x, data__authors_item in enumerate(data__authors):
-                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__authors_item, custom_formats, (name_prefix or "data") + ".authors[{data__authors_x}]")
+                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__authors_item, custom_formats, (name_prefix or "data") + ".authors[{data__authors_x}]".format(**locals()))
         if "maintainers" in data_keys:
             data_keys.remove("maintainers")
             data__maintainers = data["maintainers"]
@@ -820,7 +820,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_pro
             if data__maintainers_is_list:
                 data__maintainers_len = len(data__maintainers)
                 for data__maintainers_x, data__maintainers_item in enumerate(data__maintainers):
-                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__maintainers_item, custom_formats, (name_prefix or "data") + ".maintainers[{data__maintainers_x}]")
+                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__maintainers_item, custom_formats, (name_prefix or "data") + ".maintainers[{data__maintainers_x}]".format(**locals()))
         if "keywords" in data_keys:
             data_keys.remove("keywords")
             data__keywords = data["keywords"]
@@ -883,7 +883,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_pro
                     if REGEX_PATTERNS['^.+$'].search(data__entrypoints_key):
                         if data__entrypoints_key in data__entrypoints_keys:
                             data__entrypoints_keys.remove(data__entrypoints_key)
-                        validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data__entrypoints_val, custom_formats, (name_prefix or "data") + ".entry-points.{data__entrypoints_key}")
+                        validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data__entrypoints_val, custom_formats, (name_prefix or "data") + ".entry-points.{data__entrypoints_key}".format(**locals()))
                 if data__entrypoints_keys:
                     raise JsonSchemaValueException("" + (name_prefix or "data") + ".entry-points must not contain "+str(data__entrypoints_keys)+" properties", value=data__entrypoints, name="" + (name_prefix or "data") + ".entry-points", definition={'$$description': ['Instruct the installer to expose the given modules/functions via', '``entry-point`` discovery mechanism (useful for plugins).', 'More information available in the `Python packaging guide', '<https://packaging.python.org/specifications/entry-points/>`_.'], 'propertyNames': {'format': 'python-entrypoint-group'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'$id': '#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped together to indicate what sort of capabilities they', 'provide.', 'See the `packaging guides', '<https://packaging.python.org/specifications/entry-points/>`_', 'and `setuptools docs', '<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for more information.'], 'propertyNames': {'format': 'python-entrypoint-name'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', '$$description': ['Reference to a Python object. It is either in the form', '``importable.module``, or ``importable.module:object.attr``.'], 'format': 'python-entrypoint-reference', '$comment': 'https://packaging.python.org/specifications/entry-points/'}}}}}, rule='additionalProperties')
                 data__entrypoints_len = len(data__entrypoints)
@@ -907,7 +907,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_pro
             if data__dependencies_is_list:
                 data__dependencies_len = len(data__dependencies)
                 for data__dependencies_x, data__dependencies_item in enumerate(data__dependencies):
-                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__dependencies_item, custom_formats, (name_prefix or "data") + ".dependencies[{data__dependencies_x}]")
+                    validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__dependencies_item, custom_formats, (name_prefix or "data") + ".dependencies[{data__dependencies_x}]".format(**locals()))
         if "optional-dependencies" in data_keys:
             data_keys.remove("optional-dependencies")
             data__optionaldependencies = data["optional-dependencies"]
@@ -926,7 +926,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_pro
                         if data__optionaldependencies_val_is_list:
                             data__optionaldependencies_val_len = len(data__optionaldependencies_val)
                             for data__optionaldependencies_val_x, data__optionaldependencies_val_item in enumerate(data__optionaldependencies_val):
-                                validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__optionaldependencies_val_item, custom_formats, (name_prefix or "data") + ".optional-dependencies.{data__optionaldependencies_key}[{data__optionaldependencies_val_x}]")
+                                validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__optionaldependencies_val_item, custom_formats, (name_prefix or "data") + ".optional-dependencies.{data__optionaldependencies_key}[{data__optionaldependencies_val_x}]".format(**locals()))
                 if data__optionaldependencies_keys:
                     raise JsonSchemaValueException("" + (name_prefix or "data") + ".optional-dependencies must not contain "+str(data__optionaldependencies_keys)+" properties", value=data__optionaldependencies, name="" + (name_prefix or "data") + ".optional-dependencies", definition={'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}}, rule='additionalProperties')
                 data__optionaldependencies_len = len(data__optionaldependencies)
index f4a92bf44a2598a683b6b38fea72d43fc3b0726b..03a446fd2ab36405a2a9a2e2ed52fabc9d264c56 100644 (file)
@@ -11,8 +11,20 @@ import warnings
 from collections import defaultdict
 from functools import partial
 from functools import wraps
-from typing import (TYPE_CHECKING, Callable, Any, Dict, Generic, Iterable, List,
-                    Optional, Set, Tuple, TypeVar, Union)
+from typing import (
+    TYPE_CHECKING,
+    Callable,
+    Any,
+    Dict,
+    Generic,
+    Iterable,
+    List,
+    Optional,
+    Set,
+    Tuple,
+    TypeVar,
+    Union,
+)
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
 from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement
@@ -39,9 +51,7 @@ Target = TypeVar("Target", bound=Union["Distribution", "DistributionMetadata"])
 
 
 def read_configuration(
-    filepath: _Path,
-    find_others=False,
-    ignore_option_errors=False
+    filepath: _Path, find_others=False, ignore_option_errors=False
 ) -> dict:
     """Read given configuration file and returns options from it as a dict.
 
@@ -76,7 +86,8 @@ def apply_configuration(dist: "Distribution", filepath: _Path) -> "Distribution"
 
 
 def _apply(
-    dist: "Distribution", filepath: _Path,
+    dist: "Distribution",
+    filepath: _Path,
     other_files: Iterable[_Path] = (),
     ignore_option_errors: bool = False,
 ) -> Tuple["ConfigHandler", ...]:
@@ -137,7 +148,7 @@ def configuration_to_dict(handlers: Tuple["ConfigHandler", ...]) -> dict:
 def parse_configuration(
     distribution: "Distribution",
     command_options: AllCommandOptions,
-    ignore_option_errors=False
+    ignore_option_errors=False,
 ) -> Tuple["ConfigMetadataHandler", "ConfigOptionsHandler"]:
     """Performs additional parsing of configuration options
     for a distribution.
@@ -240,19 +251,9 @@ class ConfigHandler(Generic[Target]):
         ignore_option_errors,
         ensure_discovered: expand.EnsurePackagesDiscovered,
     ):
-        sections: AllCommandOptions = {}
-
-        section_prefix = self.section_prefix
-        for section_name, section_options in options.items():
-            if not section_name.startswith(section_prefix):
-                continue
-
-            section_name = section_name.replace(section_prefix, '').strip('.')
-            sections[section_name] = section_options
-
         self.ignore_option_errors = ignore_option_errors
         self.target_obj = target_obj
-        self.sections = sections
+        self.sections = dict(self._section_options(options))
         self.set_options: List[str] = []
         self.ensure_discovered = ensure_discovered
         self._referenced_files: Set[str] = set()
@@ -260,6 +261,14 @@ class ConfigHandler(Generic[Target]):
         all files referenced by the "file:" directive. Private API for setuptools only.
         """
 
+    @classmethod
+    def _section_options(cls, options: AllCommandOptions):
+        for full_name, value in options.items():
+            pre, sep, name = full_name.partition(cls.section_prefix)
+            if pre:
+                continue
+            yield name.lstrip('.'), value
+
     @property
     def parsers(self):
         """Metadata item name to parser function mapping."""
@@ -268,40 +277,28 @@ class ConfigHandler(Generic[Target]):
         )
 
     def __setitem__(self, option_name, value):
-        unknown = tuple()
         target_obj = self.target_obj
 
         # Translate alias into real name.
         option_name = self.aliases.get(option_name, option_name)
 
-        current_value = getattr(target_obj, option_name, unknown)
-
-        if current_value is unknown:
+        try:
+            current_value = getattr(target_obj, option_name)
+        except AttributeError:
             raise KeyError(option_name)
 
         if current_value:
             # Already inhabited. Skipping.
             return
 
-        skip_option = False
-        parser = self.parsers.get(option_name)
-        if parser:
-            try:
-                value = parser(value)
-
-            except Exception:
-                skip_option = True
-                if not self.ignore_option_errors:
-                    raise
-
-        if skip_option:
+        try:
+            parsed = self.parsers.get(option_name, lambda x: x)(value)
+        except (Exception,) * self.ignore_option_errors:
             return
 
-        setter = getattr(target_obj, 'set_%s' % option_name, None)
-        if setter is None:
-            setattr(target_obj, option_name, value)
-        else:
-            setter(value)
+        simple_setter = functools.partial(target_obj.__setattr__, option_name)
+        setter = getattr(target_obj, 'set_%s' % option_name, simple_setter)
+        setter(parsed)
 
         self.set_options.append(option_name)
 
@@ -476,7 +473,7 @@ class ConfigHandler(Generic[Target]):
 
         :param dict section_options:
         """
-        for (name, (_, value)) in section_options.items():
+        for name, (_, value) in section_options.items():
             with contextlib.suppress(KeyError):
                 # Keep silent for a new option may appear anytime.
                 self[name] = value
@@ -487,7 +484,6 @@ class ConfigHandler(Generic[Target]):
 
         """
         for section_name, section_options in self.sections.items():
-
             method_postfix = ''
             if section_name:  # [section.option] variant
                 method_postfix = '_%s' % section_name
@@ -524,7 +520,6 @@ class ConfigHandler(Generic[Target]):
 
 
 class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
-
     section_prefix = 'metadata'
 
     aliases = {
@@ -547,7 +542,7 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
         ignore_option_errors: bool,
         ensure_discovered: expand.EnsurePackagesDiscovered,
         package_dir: Optional[dict] = None,
-        root_dir: _Path = os.curdir
+        root_dir: _Path = os.curdir,
     ):
         super().__init__(target_obj, options, ignore_option_errors, ensure_discovered)
         self.package_dir = package_dir
@@ -615,7 +610,6 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
 
 
 class ConfigOptionsHandler(ConfigHandler["Distribution"]):
-
     section_prefix = 'options'
 
     def __init__(
@@ -760,7 +754,7 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
         """
         parsed = self._parse_section_to_dict_with_key(
             section_options,
-            lambda k, v: self._parse_requirements_list(f"extras_require[{k}]", v)
+            lambda k, v: self._parse_requirements_list(f"extras_require[{k}]", v),
         )
 
         self['extras_require'] = parsed
index 6244a185588c0fae1aad7ffabe692635ddfb8295..f053dba8375975104dfb05b92ea909aa91b9620b 100644 (file)
@@ -234,6 +234,7 @@ class FlatLayoutPackageFinder(PEP420PackageFinder):
         "benchmarks",
         "exercise",
         "exercises",
+        "htmlcov",  # Coverage.py
         # ---- Hidden directories/Private packages ----
         "[._]*",
     )
@@ -273,7 +274,6 @@ class FlatLayoutModuleFinder(ModuleFinder):
         "benchmarks",
         "exercise",
         "exercises",
-        "htmlcov",
         # ---- Hidden files/Private modules ----
         "[._]*",
     )
index efcce924e5ab783f5eff33f7b7f9b836c1c0cf57..6c605e02b42696bd221e4ee7acb6ff88a863b7fe 100644 (file)
@@ -217,6 +217,7 @@ class TestFlatLayoutPackageFinder:
         ),
         "tool-specific": (
             [
+                "htmlcov/index.html",
                 "pkg/__init__.py",
                 "tasks/__init__.py",
                 "tasks/subpackage/__init__.py",