[bumpversion]
-current_version = 67.5.0
+current_version = 67.5.1
commit = True
tag = True
+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
-------
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
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
@property
def hashcmp(self):
return (
- self.parsed_version,
+ self._forgiving_parsed_version,
self.precedence,
self.key,
self.location,
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:
[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
# *** PLEASE DO NOT MODIFY DIRECTLY: Automatically generated code ***
-VERSION = "2.16.2"
+VERSION = "2.16.3"
import re
from .fastjsonschema_exceptions import JsonSchemaValueException
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:
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)
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"]
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"]
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)
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"]
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)
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
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.
def _apply(
- dist: "Distribution", filepath: _Path,
+ dist: "Distribution",
+ filepath: _Path,
other_files: Iterable[_Path] = (),
ignore_option_errors: bool = False,
) -> Tuple["ConfigHandler", ...]:
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.
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()
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."""
)
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)
: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
"""
for section_name, section_options in self.sections.items():
-
method_postfix = ''
if section_name: # [section.option] variant
method_postfix = '_%s' % section_name
class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
-
section_prefix = 'metadata'
aliases = {
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
class ConfigOptionsHandler(ConfigHandler["Distribution"]):
-
section_prefix = 'options'
def __init__(
"""
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
"benchmarks",
"exercise",
"exercises",
+ "htmlcov", # Coverage.py
# ---- Hidden directories/Private packages ----
"[._]*",
)
"benchmarks",
"exercise",
"exercises",
- "htmlcov",
# ---- Hidden files/Private modules ----
"[._]*",
)
),
"tool-specific": (
[
+ "htmlcov/index.html",
"pkg/__init__.py",
"tasks/__init__.py",
"tasks/subpackage/__init__.py",