From: JinWang An Date: Mon, 27 Mar 2023 08:03:01 +0000 (+0900) Subject: Imported Upstream version 67.5.1 X-Git-Tag: upstream/67.5.1^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9403f228f0e7a1e2732a472db2646660ee0d14ea;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 67.5.1 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9b4a054..89976bc 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 67.5.0 +current_version = 67.5.1 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 44d0e35..7d5470a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ------- diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index e08d17f..a73a1df 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -121,6 +121,9 @@ _namespace_packages = None warnings.warn("pkg_resources is deprecated as an API", DeprecationWarning) +_PEP440_FALLBACK = re.compile(r"^v?(?P(?:[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')) + + >>> parse_version(_forgiving_version('0.23-')) + + >>> parse_version(_forgiving_version('0.-_')) + + >>> parse_version(_forgiving_version('42.+?1')) + + >>> parse_version(_forgiving_version('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: diff --git a/setup.cfg b/setup.cfg index 947f627..cb4d928 100644 --- 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 diff --git a/setuptools/config/_validate_pyproject/fastjsonschema_validations.py b/setuptools/config/_validate_pyproject/fastjsonschema_validations.py index 30d8d63..cd4e444 100644 --- a/setuptools/config/_validate_pyproject/fastjsonschema_validations.py +++ b/setuptools/config/_validate_pyproject/fastjsonschema_validations.py @@ -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', '`_.'], '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', '`_', 'and `setuptools docs', '`_', '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) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index f4a92bf..03a446f 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -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 diff --git a/setuptools/discovery.py b/setuptools/discovery.py index 6244a18..f053dba 100644 --- a/setuptools/discovery.py +++ b/setuptools/discovery.py @@ -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 ---- "[._]*", ) diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index efcce92..6c605e0 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -217,6 +217,7 @@ class TestFlatLayoutPackageFinder: ), "tool-specific": ( [ + "htmlcov/index.html", "pkg/__init__.py", "tasks/__init__.py", "tasks/subpackage/__init__.py",