diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2b055f23898..51d22156e43 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 72.1.0 +current_version = 73.0.1 commit = True tag = True diff --git a/.coveragerc b/.coveragerc index 5b7fdefd2ae..c8d1cbbd6ed 100644 --- a/.coveragerc +++ b/.coveragerc @@ -17,6 +17,8 @@ disable_warnings = [report] show_missing = True exclude_also = - # jaraco/skeleton#97 - @overload + # Exclude common false positives per + # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion + # Ref jaraco/skeleton#97 and jaraco/skeleton#135 + class .*\bProtocol\): if TYPE_CHECKING: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ffcfca062fb..aaefd5fbbbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,7 @@ repos: -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.9 - hooks: - - id: ruff - - id: ruff-format + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.7 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/MANIFEST.in b/MANIFEST.in index 092612cb213..0643e7ee2d5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,11 +10,12 @@ recursive-include newsfragments * include *.py include *.rst include MANIFEST.in -include LICENSE +global-include LICEN[CS]E* COPYING* NOTICE* AUTHORS* include launcher.c include msvc-build-launcher.cmd include mypy.ini include pytest.ini include tox.ini include setuptools/tests/config/setupcfg_examples.txt +include setuptools/config/*.schema.json global-exclude *.py[cod] __pycache__ diff --git a/NEWS.rst b/NEWS.rst index e89dfcba6d6..3c36fbfa5aa 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,55 @@ +v73.0.1 +======= + +Bugfixes +-------- + +- Remove `abc.ABCMeta` metaclass from abstract classes. `pypa/setuptools#4503 `_ had an unintended consequence of causing potential ``TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases`` -- by :user:`Avasam` (#4579) + + +v73.0.0 +======= + +Features +-------- + +- Mark abstract base classes and methods with `abc.ABC` and `abc.abstractmethod` -- by :user:`Avasam` (#4503) +- Changed the order of type checks in ``setuptools.command.easy_install.CommandSpec.from_param`` to support any `collections.abc.Iterable` of `str` param -- by :user:`Avasam` (#4505) + + +Bugfixes +-------- + +- Prevent an error in ``bdist_wheel`` if ``compression`` is set to a `str` (even if valid) after finalizing options but before running the command. -- by :user:`Avasam` (#4383) +- Raises an exception when ``py_limited_api`` is used in a build with + ``Py_GIL_DISABLED``. This is currently not supported (python/cpython#111506). (#4420) +- Synced with pypa/distutils@30b7331 including fix for modified check on empty sources (pypa/distutils#284). + + +Deprecations and Removals +------------------------- + +- ``setuptools`` is replacing the usages of :pypi:`ordered_set` with simple + instances of ``dict[Hashable, None]``. This is done to remove the extra + dependency and it is possible because since Python 3.7, ``dict`` maintain + insertion order. (#4574) + + +Misc +---- + +- #4534, #4546, #4554, #4559, #4565 + + +v72.2.0 +======= + +Features +-------- + +- Merged with pypa/distutils@b7ee725f3 including: Support for Pathlike objects in data files and extensions (pypa/distutils#272, pypa/distutils#237), native support for C++ compilers (pypa/distuils#228) and removed unused get_msvcr() (pypa/distutils#274). (#4538) + + v72.1.0 ======= diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py index 35ab5cad496..b05d04e98e9 100644 --- a/_distutils_hack/__init__.py +++ b/_distutils_hack/__init__.py @@ -1,7 +1,6 @@ # don't import any costly modules -import sys import os - +import sys report_url = ( "https://github.com/pypa/setuptools/issues/new?" diff --git a/conftest.py b/conftest.py index 96e015974f0..eb052d05ca4 100644 --- a/conftest.py +++ b/conftest.py @@ -6,7 +6,6 @@ import pytest from pytest_mypy import MypyFileItem, MypyResults - pytest_plugins = 'setuptools.tests.fixtures' diff --git a/docs/development/developer-guide.rst b/docs/development/developer-guide.rst index 8c9142fc308..4566fef8d0d 100644 --- a/docs/development/developer-guide.rst +++ b/docs/development/developer-guide.rst @@ -131,3 +131,35 @@ simple Python script ``tools/vendor.py``. To refresh the dependencies, run the following command:: $ tox -e vendor + +---------------- +Type annotations +---------------- + +Most standards and best practices are enforced by +`Ruff `_'s ``ANN2``, ``FA``, ``PYI``, ``UP`` +and ``YTT`` rules. + +Explicit return types have to be added for typed public functions whose +parameters are *all* annotated. This is enforced by ``ANN2``, but it's worth noting +that this is due to mypy inferring ``Any`` even for simple return types. Mypy also +doesn't count functions with missing parameter annotations as "typed". (see +`python/mypy#4409 `_, +`python/mypy#10149 `_ and +`python/mypy#6646 `_). +Otherwise, return annotations can be omitted to reduce verbosity, +especially for complex return types. + +Instead of typing an explicit return type annotation as +``Generator[..., None, None]``, we'll prefer using an ``Iterator`` as it is more +concise and conceptually easier to deal with. Returning a ``Generator`` with no +``yield`` type or ``send`` type can sometimes be considered as exposing +implementation details. See +`Y058 `_. + +Avoid importing private type-checking-only symbols. These are often +`typeshed `_ internal details and are not +guaranteed to be stable. +Importing from ``_typeshed`` or ``typing_extensions`` is fine, but if you find +yourself importing the same symbol in ``TYPE_CHECKING`` blocks a lot, consider +implementing an alias directly in ``setuptools``. diff --git a/docs/history.rst b/docs/history.rst index 4f302ca06b0..7a4bb4dbee5 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -5,6 +5,9 @@ History ******* +.. meta:: + :keywords: changelog + .. towncrier-draft-entries:: DRAFT, unreleased as on |today| .. include:: ../NEWS (links).rst diff --git a/docs/userguide/package_discovery.rst b/docs/userguide/package_discovery.rst index 6a7bdbf8dac..c33877e1f66 100644 --- a/docs/userguide/package_discovery.rst +++ b/docs/userguide/package_discovery.rst @@ -88,8 +88,9 @@ exactly to the directory structure, you also need to configure ``package_dir``: package_dir = { "mypkg": "lib", # mypkg.module corresponds to lib/module.py "mypkg.subpkg1": "lib1", # mypkg.subpkg1.module1 corresponds to lib1/module1.py - "mypkg.subpkg2": "lib2" # mypkg.subpkg2.module2 corresponds to lib2/module2.py + "mypkg.subpkg2": "lib2", # mypkg.subpkg2.module2 corresponds to lib2/module2.py # ... + } ) .. tab:: pyproject.toml diff --git a/mypy.ini b/mypy.ini index 10314dfd14d..8848bb9e629 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ [mypy] # CI should test for all versions, local development gets hints for oldest supported -# Some upstream typeshed distutils stubs fixes are necessary before we can start testing on Python 3.12 -python_version = 3.8 +# But our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually. +# python_version = 3.8 strict = False warn_unused_ignores = True warn_redundant_casts = True @@ -15,14 +15,13 @@ exclude = (?x)( | ^setuptools/_vendor/ # Vendored | ^setuptools/_distutils/ # Vendored | ^setuptools/config/_validate_pyproject/ # Auto-generated - | ^setuptools/tests/bdist_wheel_testdata/ # Duplicate module name ) # Too many false-positives disable_error_code = overload-overlap -# Ignoring attr-defined because setuptools wraps a lot of distutils classes, adding new attributes, -# w/o updating all the attributes and return types from the base classes for type-checkers to understand -# Especially with setuptools.dist.command vs distutils.dist.command vs setuptools._distutils.dist.command +# DistributionMetadata.license_files and DistributionMetadata.license_file +# are dynamically patched in setuptools/_core_metadata.py +# and no DistributionMetadata subclass exists in setuptools [mypy-setuptools.*] disable_error_code = attr-defined @@ -31,16 +30,20 @@ disable_error_code = attr-defined [mypy-pkg_resources.tests.*] disable_error_code = import-not-found -# - distutils._modified has different errors on Python 3.8 [import-untyped], on Python 3.9+ [import-not-found] +# - distutils doesn't exist on Python 3.12, unfortunately, this means typing +# will be missing for subclasses of distutils on Python 3.12 until either: +# - support for `SETUPTOOLS_USE_DISTUTILS=stdlib` is dropped (#3625) +# for setuptools to import `_distutils` directly +# - or non-stdlib distutils typings are exposed # - All jaraco modules are still untyped # - _validate_project sometimes complains about trove_classifiers (#4296) # - wheel appears to be untyped # - pytest_mypy isn't marked as py.typed -[mypy-distutils._modified,jaraco.*,trove_classifiers,wheel.*,pytest_mypy.*] +[mypy-distutils.*,jaraco.*,trove_classifiers,wheel.*,pytest_mypy.*] ignore_missing_imports = True # Even when excluding a module, import issues can show up due to following import # https://github.com/python/mypy/issues/11936#issuecomment-1466764006 -[mypy-setuptools.config._validate_pyproject.*] +[mypy-setuptools.config._validate_pyproject.*,setuptools._distutils.*] follow_imports = silent # silent => ignore errors when following imports diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 3c88d8d3d7d..76aa5e77bac 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -27,62 +27,60 @@ if sys.version_info < (3, 8): # noqa: UP036 # Check for unsupported versions raise RuntimeError("Python 3.8 or later is required") -import os +import _imp +import collections +import email.parser +import errno +import functools +import importlib +import importlib.abc +import importlib.machinery +import inspect import io -import time +import ntpath +import operator +import os +import pkgutil +import platform +import plistlib +import posixpath import re +import stat +import tempfile +import textwrap +import time import types +import warnings +import zipfile +import zipimport +from pkgutil import get_importer from typing import ( + TYPE_CHECKING, Any, BinaryIO, - Literal, + Callable, Dict, + Iterable, Iterator, + Literal, Mapping, MutableSequence, NamedTuple, NoReturn, - Tuple, - Union, - TYPE_CHECKING, Protocol, - Callable, - Iterable, + Tuple, TypeVar, + Union, overload, ) -import zipfile -import zipimport -import warnings -import stat -import functools -import pkgutil -import operator -import platform -import collections -import plistlib -import email.parser -import errno -import tempfile -import textwrap -import inspect -import ntpath -import posixpath -import importlib -import importlib.abc -import importlib.machinery -from pkgutil import get_importer - -import _imp sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip # workaround for #4476 sys.modules.pop('backports', None) # capture these to bypass sandboxing -from os import utime -from os import open as os_open -from os.path import isdir, split +from os import open as os_open, utime # isort: skip +from os.path import isdir, split # isort: skip try: from os import mkdir, rename, unlink @@ -92,21 +90,17 @@ # no write support, probably under GAE WRITE_SUPPORT = False +import packaging.markers +import packaging.requirements import packaging.specifiers -from jaraco.text import ( - yield_lines, - drop_comment, - join_continuation, -) -from packaging import markers as _packaging_markers -from packaging import requirements as _packaging_requirements -from packaging import utils as _packaging_utils -from packaging import version as _packaging_version +import packaging.utils +import packaging.version +from jaraco.text import drop_comment, join_continuation, yield_lines from platformdirs import user_cache_dir as _user_cache_dir if TYPE_CHECKING: - from _typeshed import BytesPath, StrPath, StrOrBytesPath - from typing_extensions import Self + from _typeshed import BytesPath, StrOrBytesPath, StrPath + from typing_extensions import Self, TypeAlias warnings.warn( "pkg_resources is deprecated as an API. " @@ -118,20 +112,20 @@ _T = TypeVar("_T") _DistributionT = TypeVar("_DistributionT", bound="Distribution") # Type aliases -_NestedStr = Union[str, Iterable[Union[str, Iterable["_NestedStr"]]]] -_InstallerTypeT = Callable[["Requirement"], "_DistributionT"] -_InstallerType = Callable[["Requirement"], Union["Distribution", None]] -_PkgReqType = Union[str, "Requirement"] -_EPDistType = Union["Distribution", _PkgReqType] -_MetadataType = Union["IResourceProvider", None] -_ResolvedEntryPoint = Any # Can be any attribute in the module -_ResourceStream = Any # TODO / Incomplete: A readable file-like object +_NestedStr: TypeAlias = Union[str, Iterable[Union[str, Iterable["_NestedStr"]]]] +_StrictInstallerType: TypeAlias = Callable[["Requirement"], "_DistributionT"] +_InstallerType: TypeAlias = Callable[["Requirement"], Union["Distribution", None]] +_PkgReqType: TypeAlias = Union[str, "Requirement"] +_EPDistType: TypeAlias = Union["Distribution", _PkgReqType] +_MetadataType: TypeAlias = Union["IResourceProvider", None] +_ResolvedEntryPoint: TypeAlias = Any # Can be any attribute in the module +_ResourceStream: TypeAlias = Any # TODO / Incomplete: A readable file-like object # Any object works, but let's indicate we expect something like a module (optionally has __loader__ or __file__) -_ModuleLike = Union[object, types.ModuleType] +_ModuleLike: TypeAlias = Union[object, types.ModuleType] # Any: Should be _ModuleLike but we end up with issues where _ModuleLike doesn't have _ZipLoaderModule's __loader__ -_ProviderFactoryType = Callable[[Any], "IResourceProvider"] -_DistFinderType = Callable[[_T, str, bool], Iterable["Distribution"]] -_NSHandlerType = Callable[[_T, str, str, types.ModuleType], Union[str, None]] +_ProviderFactoryType: TypeAlias = Callable[[Any], "IResourceProvider"] +_DistFinderType: TypeAlias = Callable[[_T, str, bool], Iterable["Distribution"]] +_NSHandlerType: TypeAlias = Callable[[_T, str, str, types.ModuleType], Union[str, None]] _AdapterT = TypeVar( "_AdapterT", _DistFinderType[Any], _ProviderFactoryType, _NSHandlerType[Any] ) @@ -156,7 +150,7 @@ class PEP440Warning(RuntimeWarning): """ -parse_version = _packaging_version.Version +parse_version = packaging.version.Version _state_vars: dict[str, str] = {} @@ -801,7 +795,7 @@ def add( return self.by_key[dist.key] = dist - normalized_name = _packaging_utils.canonicalize_name(dist.key) + normalized_name = packaging.utils.canonicalize_name(dist.key) self.normalized_to_canonical_keys[normalized_name] = dist.key if dist.key not in keys: keys.append(dist.key) @@ -814,7 +808,7 @@ def resolve( self, requirements: Iterable[Requirement], env: Environment | None, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], replace_conflicting: bool = False, extras: tuple[str, ...] | None = None, ) -> list[_DistributionT]: ... @@ -824,7 +818,7 @@ def resolve( requirements: Iterable[Requirement], env: Environment | None = None, *, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], replace_conflicting: bool = False, extras: tuple[str, ...] | None = None, ) -> list[_DistributionT]: ... @@ -841,7 +835,7 @@ def resolve( self, requirements: Iterable[Requirement], env: Environment | None = None, - installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + installer: _InstallerType | None | _StrictInstallerType[_DistributionT] = None, replace_conflicting: bool = False, extras: tuple[str, ...] | None = None, ) -> list[Distribution] | list[_DistributionT]: @@ -947,7 +941,7 @@ def find_plugins( self, plugin_env: Environment, full_env: Environment | None, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], fallback: bool = True, ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ... @overload @@ -956,7 +950,7 @@ def find_plugins( plugin_env: Environment, full_env: Environment | None = None, *, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], fallback: bool = True, ) -> tuple[list[_DistributionT], dict[Distribution, Exception]]: ... @overload @@ -971,7 +965,7 @@ def find_plugins( self, plugin_env: Environment, full_env: Environment | None = None, - installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + installer: _InstallerType | None | _StrictInstallerType[_DistributionT] = None, fallback: bool = True, ) -> tuple[ list[Distribution] | list[_DistributionT], @@ -1217,7 +1211,7 @@ def best_match( self, req: Requirement, working_set: WorkingSet, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], replace_conflicting: bool = False, ) -> _DistributionT: ... @overload @@ -1232,7 +1226,7 @@ def best_match( self, req: Requirement, working_set: WorkingSet, - installer: _InstallerType | None | _InstallerTypeT[_DistributionT] = None, + installer: _InstallerType | None | _StrictInstallerType[_DistributionT] = None, replace_conflicting: bool = False, ) -> Distribution | None: """Find distribution best matching `req` and usable on `working_set` @@ -1265,7 +1259,7 @@ def best_match( def obtain( self, requirement: Requirement, - installer: _InstallerTypeT[_DistributionT], + installer: _StrictInstallerType[_DistributionT], ) -> _DistributionT: ... @overload def obtain( @@ -1285,7 +1279,7 @@ def obtain( installer: Callable[[Requirement], None] | _InstallerType | None - | _InstallerTypeT[_DistributionT] = None, + | _StrictInstallerType[_DistributionT] = None, ) -> Distribution | None: """Obtain a distribution matching `requirement` (e.g. via download) @@ -1561,8 +1555,8 @@ def safe_version(version: str) -> str: """ try: # normalize the version - return str(_packaging_version.Version(version)) - except _packaging_version.InvalidVersion: + return str(packaging.version.Version(version)) + except packaging.version.InvalidVersion: version = version.replace(' ', '.') return re.sub('[^A-Za-z0-9.]+', '-', version) @@ -1639,9 +1633,9 @@ def evaluate_marker(text: str, extra: str | None = None) -> bool: This implementation uses the 'pyparsing' module. """ try: - marker = _packaging_markers.Marker(text) + marker = packaging.markers.Marker(text) return marker.evaluate() - except _packaging_markers.InvalidMarker as e: + except packaging.markers.InvalidMarker as e: raise SyntaxError(e) from e @@ -3001,12 +2995,12 @@ def parsed_version(self): if not hasattr(self, "_parsed_version"): try: self._parsed_version = parse_version(self.version) - except _packaging_version.InvalidVersion as ex: + 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 + raise packaging.version.InvalidVersion(f"{str(ex)} {info}") from None return self._parsed_version @@ -3014,7 +3008,7 @@ def parsed_version(self): def _forgiving_parsed_version(self): try: return self.parsed_version - except _packaging_version.InvalidVersion as ex: + except packaging.version.InvalidVersion as ex: self._parsed_version = parse_version(_forgiving_version(self.version)) notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678 @@ -3194,7 +3188,7 @@ def from_filename( def as_requirement(self): """Return a ``Requirement`` that matches this distribution exactly""" - if isinstance(self.parsed_version, _packaging_version.Version): + if isinstance(self.parsed_version, packaging.version.Version): spec = "%s==%s" % (self.project_name, self.parsed_version) else: spec = "%s===%s" % (self.project_name, self.parsed_version) @@ -3452,11 +3446,11 @@ def parse_requirements(strs: _NestedStr) -> map[Requirement]: return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs)))) -class RequirementParseError(_packaging_requirements.InvalidRequirement): +class RequirementParseError(packaging.requirements.InvalidRequirement): "Compatibility wrapper for InvalidRequirement" -class Requirement(_packaging_requirements.Requirement): +class Requirement(packaging.requirements.Requirement): # prefer variable length tuple to set (as found in # packaging.requirements.Requirement) extras: tuple[str, ...] # type: ignore[assignment] diff --git a/pkg_resources/tests/test_find_distributions.py b/pkg_resources/tests/test_find_distributions.py index 8263ca6c417..301b36d6cdb 100644 --- a/pkg_resources/tests/test_find_distributions.py +++ b/pkg_resources/tests/test_find_distributions.py @@ -1,8 +1,9 @@ -from pathlib import Path import shutil +from pathlib import Path + import pytest -import pkg_resources +import pkg_resources TESTS_DATA_DIR = Path(__file__).parent / 'data' diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 424d5ac44bd..023adf60b0c 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -1,28 +1,23 @@ from __future__ import annotations import builtins -import sys -import tempfile -import os -import zipfile import datetime +import os import plistlib -import subprocess import stat -import distutils.dist -import distutils.command.install_egg_info - +import subprocess +import sys +import tempfile +import zipfile from unittest import mock -from pkg_resources import ( - DistInfoDistribution, - Distribution, - EggInfoDistribution, -) - import pytest import pkg_resources +from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution + +import distutils.command.install_egg_info +import distutils.dist class EggRemover(str): diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py index 9837c2719d9..3b672969528 100644 --- a/pkg_resources/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -1,23 +1,23 @@ +import itertools import os -import sys -import string import platform -import itertools +import string +import sys import pytest from packaging.specifiers import SpecifierSet import pkg_resources from pkg_resources import ( - parse_requirements, - VersionConflict, - parse_version, Distribution, EntryPoint, Requirement, - safe_version, - safe_name, + VersionConflict, WorkingSet, + parse_requirements, + parse_version, + safe_name, + safe_version, ) @@ -862,8 +862,8 @@ def test_path_order(self, symlinked_tmpdir): (subpkg / '__init__.py').write_text(vers_str % number, encoding='utf-8') with pytest.warns(DeprecationWarning, match="pkg_resources.declare_namespace"): - import nspkg.subpkg import nspkg + import nspkg.subpkg expected = [str(site.realpath() / 'nspkg') for site in site_dirs] assert nspkg.__path__ == expected assert nspkg.subpkg.__version__ == 1 diff --git a/pkg_resources/tests/test_working_set.py b/pkg_resources/tests/test_working_set.py index 57f62b54929..0537bb69a49 100644 --- a/pkg_resources/tests/test_working_set.py +++ b/pkg_resources/tests/test_working_set.py @@ -1,7 +1,7 @@ +import functools import inspect import re import textwrap -import functools import pytest diff --git a/pyproject.toml b/pyproject.toml index 034de18f54d..dcb581b09e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ backend-path = ["."] [project] name = "setuptools" -version = "72.1.0" +version = "73.0.1" authors = [ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, ] @@ -36,15 +36,10 @@ Changelog = "https://setuptools.pypa.io/en/stable/history.html" test = [ # upstream "pytest >= 6, != 8.1.*", - "pytest-checkdocs >= 2.4", - "pytest-cov", - "pytest-mypy", - "pytest-enabler >= 2.2", - "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", # local "virtualenv>=13.0.0", - "wheel", + "wheel>=0.44.0", # Consistent requirement normalisation in METADATA (see #4547) "pip>=19.1", # For proper file:// URLs support. "packaging>=23.2", "jaraco.envs>=2.2", @@ -69,18 +64,12 @@ test = [ "importlib_metadata", "pytest-subprocess", - # require newer pytest-ruff than upstream for pypa/setuptools#4368 - # also exclude cygwin for pypa/setuptools#3921 - 'pytest-ruff >= 0.3.2; sys_platform != "cygwin"', - # workaround for pypa/setuptools#4333 "pyproject-hooks!=1.1", "jaraco.test", - - # workaround for businho/pytest-ruff#28 - 'pytest-ruff < 0.4; platform_system == "Windows"', ] + doc = [ # upstream "sphinx >= 3.5", @@ -102,12 +91,14 @@ doc = [ # workaround for pypa/setuptools#4333 "pyproject-hooks!=1.1", + + # workaround for sphinx-contrib/sphinxcontrib-towncrier#92 + "towncrier<24.7", ] ssl = [] certs = [] core = [ "packaging>=24", - "ordered-set>=3.1.1", "more_itertools>=8.8", "jaraco.text>=3.7", "importlib_resources>=5.10.2; python_version < '3.9'", @@ -119,6 +110,33 @@ core = [ "platformdirs >= 2.6.2", ] +check = [ + # upstream + "pytest-checkdocs >= 2.4", + "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", + + # local + + # workaround for businho/pytest-ruff#28 + "ruff >= 0.5.2; sys_platform != 'cygwin'", +] + +cover = [ + "pytest-cov", +] + +enabler = [ + "pytest-enabler >= 2.2", +] + +type = [ + # upstream + "pytest-mypy", + + # local +] + + [project.entry-points."distutils.commands"] alias = "setuptools.command.alias:alias" bdist_egg = "setuptools.command.bdist_egg:bdist_egg" diff --git a/ruff.toml b/ruff.toml index 09f256d5cb7..b55b4e80672 100644 --- a/ruff.toml +++ b/ruff.toml @@ -14,6 +14,7 @@ extend-select = [ "ANN2", # missing-return-type-* "FA", # flake8-future-annotations "F404", # late-future-import + "I", # isort "PYI", # flake8-pyi "UP", # pyupgrade "TRY", @@ -56,6 +57,16 @@ ignore = [ "setuptools/__init__.py" = ["E402"] "pkg_resources/__init__.py" = ["E402"] +[lint.isort] +combine-as-imports = true +split-on-trailing-comma = false +# Force Ruff/isort to always import setuptools before distutils in tests as long as distutils_hack is supported +# This also ensures _distutils_hack is imported before distutils +# https://github.com/pypa/setuptools/issues/4137 +section-order = ["future", "standard-library", "eager", "third-party", "first-party", "local-folder", "delayed"] +sections.eager = ["_distutils_hack"] +sections.delayed = ["distutils"] + [lint.flake8-annotations] ignore-fully-untyped = true diff --git a/setup.py b/setup.py index 542edaea681..1cd9e36c151 100755 --- a/setup.py +++ b/setup.py @@ -10,9 +10,10 @@ here = os.path.dirname(__file__) -package_data = dict( - setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], -) +package_data = { + "": ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"], + "setuptools": ['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], +} force_windows_specific_files = os.environ.get( "SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES", "1" diff --git a/setuptools/__init__.py b/setuptools/__init__.py index afca08be9c5..1c39fd9dabb 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,27 +1,36 @@ """Extensions to the 'distutils' for large or complex distributions""" +# mypy: disable_error_code=override +# Command.reinitialize_command has an extra **kw param that distutils doesn't have +# Can't disable on the exact line because distutils doesn't exists on Python 3.12 +# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any, +# and a [unused-ignore] to be raised on 3.12+ + +from __future__ import annotations import functools import os import re import sys -from typing import TYPE_CHECKING +from abc import abstractmethod +from typing import TYPE_CHECKING, TypeVar, overload sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip # workaround for #4476 sys.modules.pop('backports', None) import _distutils_hack.override # noqa: F401 -import distutils.core -from distutils.errors import DistutilsOptionError from . import logging, monkey -from . import version as _version_module from .depends import Require from .discovery import PackageFinder, PEP420PackageFinder from .dist import Distribution from .extension import Extension +from .version import __version__ as __version__ from .warnings import SetuptoolsDeprecationWarning +import distutils.core +from distutils.errors import DistutilsOptionError + __all__ = [ 'setup', 'Distribution', @@ -33,11 +42,10 @@ 'find_namespace_packages', ] -__version__ = _version_module.__version__ +_CommandT = TypeVar("_CommandT", bound="_Command") bootstrap_install_from = None - find_packages = PackageFinder.find find_namespace_packages = PEP420PackageFinder.find @@ -111,8 +119,10 @@ def setup(**attrs): setup.__doc__ = distutils.core.setup.__doc__ if TYPE_CHECKING: + from typing_extensions import TypeAlias + # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 - _Command = distutils.core.Command + _Command: TypeAlias = distutils.core.Command else: _Command = monkey.get_unpatched(distutils.core.Command) @@ -130,42 +140,25 @@ class Command(_Command): When creating a new command from scratch, custom defined classes **SHOULD** inherit from ``setuptools.Command`` and implement a few mandatory methods. Between these mandatory methods, are listed: - - .. method:: initialize_options(self) - - Set or (reset) all options/attributes/caches used by the command - to their default values. Note that these values may be overwritten during - the build. - - .. method:: finalize_options(self) - - Set final values for all options/attributes used by the command. - Most of the time, each option/attribute/cache should only be set if it does not - have any value yet (e.g. ``if self.attr is None: self.attr = val``). - - .. method:: run(self) - - Execute the actions intended by the command. - (Side effects **SHOULD** only take place when ``run`` is executed, - for example, creating new files or writing to the terminal output). + :meth:`initialize_options`, :meth:`finalize_options` and :meth:`run`. A useful analogy for command classes is to think of them as subroutines with local - variables called "options". The options are "declared" in ``initialize_options()`` - and "defined" (given their final values, aka "finalized") in ``finalize_options()``, + variables called "options". The options are "declared" in :meth:`initialize_options` + and "defined" (given their final values, aka "finalized") in :meth:`finalize_options`, both of which must be defined by every command class. The "body" of the subroutine, - (where it does all the work) is the ``run()`` method. - Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set + (where it does all the work) is the :meth:`run` method. + Between :meth:`initialize_options` and :meth:`finalize_options`, ``setuptools`` may set the values for options/attributes based on user's input (or circumstance), which means that the implementation should be careful to not overwrite values in - ``finalize_options`` unless necessary. + :meth:`finalize_options` unless necessary. Please note that other commands (or other parts of setuptools) may also overwrite the values of the command's options/attributes multiple times during the build process. - Therefore it is important to consistently implement ``initialize_options()`` and - ``finalize_options()``. For example, all derived attributes (or attributes that + Therefore it is important to consistently implement :meth:`initialize_options` and + :meth:`finalize_options`. For example, all derived attributes (or attributes that depend on the value of other attributes) **SHOULD** be recomputed in - ``finalize_options``. + :meth:`finalize_options`. When overwriting existing commands, custom defined classes **MUST** abide by the same APIs implemented by the original class. They also **SHOULD** inherit from the @@ -221,11 +214,48 @@ def ensure_string_list(self, option): "'%s' must be a list of strings (got %r)" % (option, val) ) - def reinitialize_command(self, command, reinit_subcommands=False, **kw): + @overload + def reinitialize_command( + self, command: str, reinit_subcommands: bool = False, **kw + ) -> _Command: ... + @overload + def reinitialize_command( + self, command: _CommandT, reinit_subcommands: bool = False, **kw + ) -> _CommandT: ... + def reinitialize_command( + self, command: str | _Command, reinit_subcommands: bool = False, **kw + ) -> _Command: cmd = _Command.reinitialize_command(self, command, reinit_subcommands) vars(cmd).update(kw) return cmd + @abstractmethod + def initialize_options(self) -> None: + """ + Set or (reset) all options/attributes/caches used by the command + to their default values. Note that these values may be overwritten during + the build. + """ + raise NotImplementedError + + @abstractmethod + def finalize_options(self) -> None: + """ + Set final values for all options/attributes used by the command. + Most of the time, each option/attribute/cache should only be set if it does not + have any value yet (e.g. ``if self.attr is None: self.attr = val``). + """ + raise NotImplementedError + + @abstractmethod + def run(self) -> None: + """ + Execute the actions intended by the command. + (Side effects **SHOULD** only take place when :meth:`run` is executed, + for example, creating new files or writing to the terminal output). + """ + raise NotImplementedError + def _find_all_simple(path): """ diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py index 82ec19fc755..2e9c48a77b6 100644 --- a/setuptools/_core_metadata.py +++ b/setuptools/_core_metadata.py @@ -13,15 +13,16 @@ from email.message import Message from tempfile import NamedTemporaryFile -from distutils.util import rfc822_escape - -from . import _normalization, _reqs from packaging.markers import Marker from packaging.requirements import Requirement from packaging.utils import canonicalize_name, canonicalize_version from packaging.version import Version + +from . import _normalization, _reqs from .warnings import SetuptoolsDeprecationWarning +from distutils.util import rfc822_escape + def get_metadata_version(self): mv = getattr(self, 'metadata_version', None) diff --git a/setuptools/_distutils/_collections.py b/setuptools/_distutils/_collections.py index d11a83467c2..863030b3cf4 100644 --- a/setuptools/_distutils/_collections.py +++ b/setuptools/_distutils/_collections.py @@ -1,11 +1,7 @@ from __future__ import annotations import collections -import functools import itertools -import operator -from collections.abc import Mapping -from typing import Any # from jaraco.collections 3.5.1 @@ -60,144 +56,3 @@ def __contains__(self, other): def __len__(self): return len(list(iter(self))) - - -# from jaraco.collections 5.0.1 -class RangeMap(dict): - """ - A dictionary-like object that uses the keys as bounds for a range. - Inclusion of the value for that range is determined by the - key_match_comparator, which defaults to less-than-or-equal. - A value is returned for a key if it is the first key that matches in - the sorted list of keys. - - One may supply keyword parameters to be passed to the sort function used - to sort keys (i.e. key, reverse) as sort_params. - - Create a map that maps 1-3 -> 'a', 4-6 -> 'b' - - >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - Even float values should work so long as the comparison operator - supports it. - - >>> r[4.5] - 'b' - - Notice that the way rangemap is defined, it must be open-ended - on one side. - - >>> r[0] - 'a' - >>> r[-1] - 'a' - - One can close the open-end of the RangeMap by using undefined_value - - >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'}) - >>> r[0] - Traceback (most recent call last): - ... - KeyError: 0 - - One can get the first or last elements in the range by using RangeMap.Item - - >>> last_item = RangeMap.Item(-1) - >>> r[last_item] - 'b' - - .last_item is a shortcut for Item(-1) - - >>> r[RangeMap.last_item] - 'b' - - Sometimes it's useful to find the bounds for a RangeMap - - >>> r.bounds() - (0, 6) - - RangeMap supports .get(key, default) - - >>> r.get(0, 'not found') - 'not found' - - >>> r.get(7, 'not found') - 'not found' - - One often wishes to define the ranges by their left-most values, - which requires use of sort params and a key_match_comparator. - - >>> r = RangeMap({1: 'a', 4: 'b'}, - ... sort_params=dict(reverse=True), - ... key_match_comparator=operator.ge) - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - That wasn't nearly as easy as before, so an alternate constructor - is provided: - - >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value}) - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - """ - - def __init__( - self, - source, - sort_params: Mapping[str, Any] = {}, - key_match_comparator=operator.le, - ): - dict.__init__(self, source) - self.sort_params = sort_params - self.match = key_match_comparator - - @classmethod - def left(cls, source): - return cls( - source, sort_params=dict(reverse=True), key_match_comparator=operator.ge - ) - - def __getitem__(self, item): - sorted_keys = sorted(self.keys(), **self.sort_params) - if isinstance(item, RangeMap.Item): - result = self.__getitem__(sorted_keys[item]) - else: - key = self._find_first_match_(sorted_keys, item) - result = dict.__getitem__(self, key) - if result is RangeMap.undefined_value: - raise KeyError(key) - return result - - def get(self, key, default=None): - """ - Return the value for key if key is in the dictionary, else default. - If default is not given, it defaults to None, so that this method - never raises a KeyError. - """ - try: - return self[key] - except KeyError: - return default - - def _find_first_match_(self, keys, item): - is_match = functools.partial(self.match, item) - matches = list(filter(is_match, keys)) - if matches: - return matches[0] - raise KeyError(item) - - def bounds(self): - sorted_keys = sorted(self.keys(), **self.sort_params) - return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item]) - - # some special values for the RangeMap - undefined_value = type('RangeValueUndefined', (), {})() - - class Item(int): - "RangeMap Item" - - first_item = Item(0) - last_item = Item(-1) diff --git a/setuptools/_distutils/_modified.py b/setuptools/_distutils/_modified.py index 6532aa10731..b7bdaa2943e 100644 --- a/setuptools/_distutils/_modified.py +++ b/setuptools/_distutils/_modified.py @@ -63,7 +63,7 @@ def missing_as_newer(source): return missing == 'newer' and not os.path.exists(source) ignored = os.path.exists if missing == 'ignore' else None - return any( + return not os.path.exists(target) or any( missing_as_newer(source) or _newer(source, target) for source in filter(ignored, sources) ) diff --git a/setuptools/_distutils/bcppcompiler.py b/setuptools/_distutils/bcppcompiler.py index e47dca5d099..9157b43328d 100644 --- a/setuptools/_distutils/bcppcompiler.py +++ b/setuptools/_distutils/bcppcompiler.py @@ -236,8 +236,7 @@ def link( # noqa: C901 temp_dir = os.path.dirname(objects[0]) # preserve tree structure def_file = os.path.join(temp_dir, f'{modname}.def') contents = ['EXPORTS'] - for sym in export_symbols or []: - contents.append(f' {sym}=_{sym}') + contents.extend(f' {sym}=_{sym}' for sym in export_symbols) self.execute(write_file, (def_file, contents), f"writing {def_file}") # Borland C++ has problems with '/' in paths diff --git a/setuptools/_distutils/ccompiler.py b/setuptools/_distutils/ccompiler.py index 9d5297b9444..bc4743bcbf0 100644 --- a/setuptools/_distutils/ccompiler.py +++ b/setuptools/_distutils/ccompiler.py @@ -22,7 +22,7 @@ ) from .file_util import move_file from .spawn import spawn -from .util import execute, split_quoted, is_mingw +from .util import execute, is_mingw, split_quoted class CCompiler: @@ -1124,10 +1124,10 @@ def show_compilers(): # commands that use it. from distutils.fancy_getopt import FancyGetopt - compilers = [] - for compiler in compiler_class.keys(): - compilers.append(("compiler=" + compiler, None, compiler_class[compiler][2])) - compilers.sort() + compilers = sorted( + ("compiler=" + compiler, None, compiler_class[compiler][2]) + for compiler in compiler_class.keys() + ) pretty_printer = FancyGetopt(compilers) pretty_printer.print_help("List of available compilers:") @@ -1218,8 +1218,7 @@ def gen_preprocess_options(macros, include_dirs): # shell at all costs when we spawn the command! pp_opts.append("-D{}={}".format(*macro)) - for dir in include_dirs: - pp_opts.append(f"-I{dir}") + pp_opts.extend(f"-I{dir}" for dir in include_dirs) return pp_opts @@ -1230,10 +1229,7 @@ def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries): directories. Returns a list of command-line options suitable for use with some compiler (depending on the two format strings passed in). """ - lib_opts = [] - - for dir in library_dirs: - lib_opts.append(compiler.library_dir_option(dir)) + lib_opts = [compiler.library_dir_option(dir) for dir in library_dirs] for dir in runtime_library_dirs: lib_opts.extend(always_iterable(compiler.runtime_library_dir_option(dir))) diff --git a/setuptools/_distutils/command/bdist.py b/setuptools/_distutils/command/bdist.py index 1738f4e56b8..f3340751594 100644 --- a/setuptools/_distutils/command/bdist.py +++ b/setuptools/_distutils/command/bdist.py @@ -15,9 +15,10 @@ def show_formats(): """Print list of available formats (arguments to "--format" option).""" from ..fancy_getopt import FancyGetopt - formats = [] - for format in bdist.format_commands: - formats.append(("formats=" + format, None, bdist.format_commands[format][1])) + formats = [ + ("formats=" + format, None, bdist.format_commands[format][1]) + for format in bdist.format_commands + ] pretty_printer = FancyGetopt(formats) pretty_printer.print_help("List of available distribution formats:") diff --git a/setuptools/_distutils/command/build_ext.py b/setuptools/_distutils/command/build_ext.py index 18e1601a288..cf475fe8249 100644 --- a/setuptools/_distutils/command/build_ext.py +++ b/setuptools/_distutils/command/build_ext.py @@ -465,10 +465,7 @@ def get_outputs(self): # And build the list of output (built) filenames. Note that this # ignores the 'inplace' flag, and assumes everything goes in the # "build" tree. - outputs = [] - for ext in self.extensions: - outputs.append(self.get_ext_fullpath(ext.name)) - return outputs + return [self.get_ext_fullpath(ext.name) for ext in self.extensions] def build_extensions(self): # First, sanity-check the 'extensions' list diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py index 58b3f949f9f..93d754e73da 100644 --- a/setuptools/_distutils/command/check.py +++ b/setuptools/_distutils/command/check.py @@ -100,10 +100,9 @@ def check_metadata(self): """ metadata = self.distribution.metadata - missing = [] - for attr in 'name', 'version': - if not getattr(metadata, attr, None): - missing.append(attr) + missing = [ + attr for attr in ('name', 'version') if not getattr(metadata, attr, None) + ] if missing: self.warn("missing required meta-data: {}".format(', '.join(missing))) diff --git a/setuptools/_distutils/command/install_data.py b/setuptools/_distutils/command/install_data.py index 624c0b901b7..a90ec3b4d07 100644 --- a/setuptools/_distutils/command/install_data.py +++ b/setuptools/_distutils/command/install_data.py @@ -5,7 +5,11 @@ # contributed by Bastian Kleineidam +from __future__ import annotations + +import functools import os +from typing import Iterable from ..core import Command from ..util import change_root, convert_path @@ -46,36 +50,42 @@ def finalize_options(self): def run(self): self.mkpath(self.install_dir) for f in self.data_files: - if isinstance(f, str): - # it's a simple file, so copy it - f = convert_path(f) - if self.warn_dir: - self.warn( - "setup script did not provide a directory for " - f"'{f}' -- installing right in '{self.install_dir}'" - ) - (out, _) = self.copy_file(f, self.install_dir) + self._copy(f) + + @functools.singledispatchmethod + def _copy(self, f: tuple[str | os.PathLike, Iterable[str | os.PathLike]]): + # it's a tuple with path to install to and a list of files + dir = convert_path(f[0]) + if not os.path.isabs(dir): + dir = os.path.join(self.install_dir, dir) + elif self.root: + dir = change_root(self.root, dir) + self.mkpath(dir) + + if f[1] == []: + # If there are no files listed, the user must be + # trying to create an empty directory, so add the + # directory to the list of output files. + self.outfiles.append(dir) + else: + # Copy files, adding them to the list of output files. + for data in f[1]: + data = convert_path(data) + (out, _) = self.copy_file(data, dir) self.outfiles.append(out) - else: - # it's a tuple with path to install to and a list of files - dir = convert_path(f[0]) - if not os.path.isabs(dir): - dir = os.path.join(self.install_dir, dir) - elif self.root: - dir = change_root(self.root, dir) - self.mkpath(dir) - - if f[1] == []: - # If there are no files listed, the user must be - # trying to create an empty directory, so add the - # directory to the list of output files. - self.outfiles.append(dir) - else: - # Copy files, adding them to the list of output files. - for data in f[1]: - data = convert_path(data) - (out, _) = self.copy_file(data, dir) - self.outfiles.append(out) + + @_copy.register(str) + @_copy.register(os.PathLike) + def _(self, f: str | os.PathLike): + # it's a simple file, so copy it + f = convert_path(f) + if self.warn_dir: + self.warn( + "setup script did not provide a directory for " + f"'{f}' -- installing right in '{self.install_dir}'" + ) + (out, _) = self.copy_file(f, self.install_dir) + self.outfiles.append(out) def get_inputs(self): return self.data_files or [] diff --git a/setuptools/_distutils/command/install_lib.py b/setuptools/_distutils/command/install_lib.py index 54a12d38a81..01579d46b42 100644 --- a/setuptools/_distutils/command/install_lib.py +++ b/setuptools/_distutils/command/install_lib.py @@ -161,9 +161,7 @@ def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): build_dir = getattr(build_cmd, cmd_option) prefix_len = len(build_dir) + len(os.sep) - outputs = [] - for file in build_files: - outputs.append(os.path.join(output_dir, file[prefix_len:])) + outputs = [os.path.join(output_dir, file[prefix_len:]) for file in build_files] return outputs diff --git a/setuptools/_distutils/command/sdist.py b/setuptools/_distutils/command/sdist.py index 04333dd2144..e8abb73920d 100644 --- a/setuptools/_distutils/command/sdist.py +++ b/setuptools/_distutils/command/sdist.py @@ -24,10 +24,10 @@ def show_formats(): from ..archive_util import ARCHIVE_FORMATS from ..fancy_getopt import FancyGetopt - formats = [] - for format in ARCHIVE_FORMATS.keys(): - formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2])) - formats.sort() + formats = sorted( + ("formats=" + format, None, ARCHIVE_FORMATS[format][2]) + for format in ARCHIVE_FORMATS.keys() + ) FancyGetopt(formats).print_help("List of available source distribution formats:") diff --git a/setuptools/_distutils/compat/py38.py b/setuptools/_distutils/compat/py38.py index 2d442111479..03ec73ef0e7 100644 --- a/setuptools/_distutils/compat/py38.py +++ b/setuptools/_distutils/compat/py38.py @@ -14,6 +14,7 @@ def removeprefix(self, prefix): return self[len(prefix) :] else: return self[:] + else: def removesuffix(self, suffix): diff --git a/setuptools/_distutils/cygwinccompiler.py b/setuptools/_distutils/cygwinccompiler.py index 7b812fd0555..ce412e8329d 100644 --- a/setuptools/_distutils/cygwinccompiler.py +++ b/setuptools/_distutils/cygwinccompiler.py @@ -9,13 +9,11 @@ import copy import os import pathlib -import re import shlex import sys import warnings from subprocess import check_output -from ._collections import RangeMap from .errors import ( CCompilerError, CompileError, @@ -26,42 +24,10 @@ from .unixccompiler import UnixCCompiler from .version import LooseVersion, suppress_known_deprecation -_msvcr_lookup = RangeMap.left( - { - # MSVC 7.0 - 1300: ['msvcr70'], - # MSVC 7.1 - 1310: ['msvcr71'], - # VS2005 / MSVC 8.0 - 1400: ['msvcr80'], - # VS2008 / MSVC 9.0 - 1500: ['msvcr90'], - # VS2010 / MSVC 10.0 - 1600: ['msvcr100'], - # VS2012 / MSVC 11.0 - 1700: ['msvcr110'], - # VS2013 / MSVC 12.0 - 1800: ['msvcr120'], - # VS2015 / MSVC 14.0 - 1900: ['vcruntime140'], - 2000: RangeMap.undefined_value, - }, -) - def get_msvcr(): - """Include the appropriate MSVC runtime library if Python was built - with MSVC 7.0 or later. - """ - match = re.search(r'MSC v\.(\d{4})', sys.version) - try: - msc_ver = int(match.group(1)) - except AttributeError: - return [] - try: - return _msvcr_lookup[msc_ver] - except KeyError: - raise ValueError(f"Unknown MS Compiler version {msc_ver} ") + """No longer needed, but kept for backward compatibility.""" + return [] _runtime_library_dirs_msg = ( @@ -99,18 +65,20 @@ def __init__(self, verbose=False, dry_run=False, force=False): self.cxx = os.environ.get('CXX', 'g++') self.linker_dll = self.cc + self.linker_dll_cxx = self.cxx shared_option = "-shared" self.set_executables( compiler=f'{self.cc} -mcygwin -O -Wall', compiler_so=f'{self.cc} -mcygwin -mdll -O -Wall', compiler_cxx=f'{self.cxx} -mcygwin -O -Wall', + compiler_so_cxx=f'{self.cxx} -mcygwin -mdll -O -Wall', linker_exe=f'{self.cc} -mcygwin', - linker_so=(f'{self.linker_dll} -mcygwin {shared_option}'), + linker_so=f'{self.linker_dll} -mcygwin {shared_option}', + linker_exe_cxx=f'{self.cxx} -mcygwin', + linker_so_cxx=f'{self.linker_dll_cxx} -mcygwin {shared_option}', ) - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. self.dll_libraries = get_msvcr() @property @@ -138,9 +106,17 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): raise CompileError(msg) else: # for other files use the C-compiler try: - self.spawn( - self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs - ) + if self.detect_language(src) == 'c++': + self.spawn( + self.compiler_so_cxx + + cc_args + + [src, '-o', obj] + + extra_postargs + ) + else: + self.spawn( + self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs + ) except DistutilsExecError as msg: raise CompileError(msg) @@ -276,9 +252,12 @@ def __init__(self, verbose=False, dry_run=False, force=False): self.set_executables( compiler=f'{self.cc} -O -Wall', compiler_so=f'{self.cc} -shared -O -Wall', + compiler_so_cxx=f'{self.cxx} -shared -O -Wall', compiler_cxx=f'{self.cxx} -O -Wall', linker_exe=f'{self.cc}', linker_so=f'{self.linker_dll} {shared_option}', + linker_exe_cxx=f'{self.cxx}', + linker_so_cxx=f'{self.linker_dll_cxx} {shared_option}', ) def runtime_library_dir_option(self, dir): diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index d7d4ca8fc8b..115302b3e74 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -658,7 +658,7 @@ def _show_help( ) print() - for command in self.commands: + for command in commands: if isinstance(command, type) and issubclass(command, Command): klass = command else: @@ -745,10 +745,7 @@ def print_commands(self): for cmd in std_commands: is_std.add(cmd) - extra_commands = [] - for cmd in self.cmdclass.keys(): - if cmd not in is_std: - extra_commands.append(cmd) + extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std] max_length = 0 for cmd in std_commands + extra_commands: @@ -776,10 +773,7 @@ def get_command_list(self): for cmd in std_commands: is_std.add(cmd) - extra_commands = [] - for cmd in self.cmdclass.keys(): - if cmd not in is_std: - extra_commands.append(cmd) + extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std] rv = [] for cmd in std_commands + extra_commands: @@ -1301,7 +1295,4 @@ def fix_help_options(options): """Convert a 4-tuple 'help_options' list as found in various command classes to the 3-tuple form required by FancyGetopt. """ - new_options = [] - for help_tuple in options: - new_options.append(help_tuple[0:3]) - return new_options + return [opt[0:3] for opt in options] diff --git a/setuptools/_distutils/extension.py b/setuptools/_distutils/extension.py index 04e871bcd65..b302082f7ac 100644 --- a/setuptools/_distutils/extension.py +++ b/setuptools/_distutils/extension.py @@ -26,7 +26,7 @@ class Extension: name : string the full name of the extension, including any packages -- ie. *not* a filename or pathname, but Python dotted name - sources : [string] + sources : [string | os.PathLike] list of source filenames, relative to the distribution root (where the setup script lives), in Unix form (slash-separated) for portability. Source files may be C, C++, SWIG (.i), @@ -106,11 +106,16 @@ def __init__( ): if not isinstance(name, str): raise AssertionError("'name' must be a string") - if not (isinstance(sources, list) and all(isinstance(v, str) for v in sources)): - raise AssertionError("'sources' must be a list of strings") + if not ( + isinstance(sources, list) + and all(isinstance(v, (str, os.PathLike)) for v in sources) + ): + raise AssertionError( + "'sources' must be a list of strings or PathLike objects." + ) self.name = name - self.sources = sources + self.sources = list(map(os.fspath, sources)) self.include_dirs = include_dirs or [] self.define_macros = define_macros or [] self.undef_macros = undef_macros or [] diff --git a/setuptools/_distutils/msvc9compiler.py b/setuptools/_distutils/msvc9compiler.py index f860a8d3830..4c70848730d 100644 --- a/setuptools/_distutils/msvc9compiler.py +++ b/setuptools/_distutils/msvc9compiler.py @@ -640,9 +640,7 @@ def link( # noqa: C901 else: ldflags = self.ldflags_shared - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) + export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []] ld_args = ( ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] diff --git a/setuptools/_distutils/msvccompiler.py b/setuptools/_distutils/msvccompiler.py index 2bf94e60c9a..2a5e61d78dd 100644 --- a/setuptools/_distutils/msvccompiler.py +++ b/setuptools/_distutils/msvccompiler.py @@ -534,9 +534,7 @@ def link( # noqa: C901 else: ldflags = self.ldflags_shared - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) + export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []] ld_args = ( ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] diff --git a/setuptools/_distutils/spawn.py b/setuptools/_distutils/spawn.py index 50d30a27612..107b0113979 100644 --- a/setuptools/_distutils/spawn.py +++ b/setuptools/_distutils/spawn.py @@ -12,7 +12,6 @@ import subprocess import sys import warnings - from typing import Mapping from ._log import log diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py index 7ebe67687e2..fbdd5d73ae7 100644 --- a/setuptools/_distutils/sysconfig.py +++ b/setuptools/_distutils/sysconfig.py @@ -287,7 +287,7 @@ def _customize_macos(): ) -def customize_compiler(compiler): # noqa: C901 +def customize_compiler(compiler): """Do any platform-specific customization of a CCompiler instance. Mainly needed on Unix, so we can plug in the information that @@ -304,6 +304,7 @@ def customize_compiler(compiler): # noqa: C901 cflags, ccshared, ldshared, + ldcxxshared, shlib_suffix, ar, ar_flags, @@ -313,11 +314,14 @@ def customize_compiler(compiler): # noqa: C901 'CFLAGS', 'CCSHARED', 'LDSHARED', + 'LDCXXSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS', ) + cxxflags = cflags + if 'CC' in os.environ: newcc = os.environ['CC'] if 'LDSHARED' not in os.environ and ldshared.startswith(cc): @@ -325,38 +329,42 @@ def customize_compiler(compiler): # noqa: C901 # command for LDSHARED as well ldshared = newcc + ldshared[len(cc) :] cc = newcc - if 'CXX' in os.environ: - cxx = os.environ['CXX'] - if 'LDSHARED' in os.environ: - ldshared = os.environ['LDSHARED'] - if 'CPP' in os.environ: - cpp = os.environ['CPP'] - else: - cpp = cc + " -E" # not always - if 'LDFLAGS' in os.environ: - ldshared = ldshared + ' ' + os.environ['LDFLAGS'] - if 'CFLAGS' in os.environ: - cflags = cflags + ' ' + os.environ['CFLAGS'] - ldshared = ldshared + ' ' + os.environ['CFLAGS'] - if 'CPPFLAGS' in os.environ: - cpp = cpp + ' ' + os.environ['CPPFLAGS'] - cflags = cflags + ' ' + os.environ['CPPFLAGS'] - ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] - if 'AR' in os.environ: - ar = os.environ['AR'] - if 'ARFLAGS' in os.environ: - archiver = ar + ' ' + os.environ['ARFLAGS'] - else: - archiver = ar + ' ' + ar_flags + cxx = os.environ.get('CXX', cxx) + ldshared = os.environ.get('LDSHARED', ldshared) + ldcxxshared = os.environ.get('LDCXXSHARED', ldcxxshared) + cpp = os.environ.get( + 'CPP', + cc + " -E", # not always + ) + ldshared = _add_flags(ldshared, 'LD') + ldcxxshared = _add_flags(ldcxxshared, 'LD') + cflags = _add_flags(cflags, 'C') + ldshared = _add_flags(ldshared, 'C') + cxxflags = os.environ.get('CXXFLAGS', cxxflags) + ldcxxshared = _add_flags(ldcxxshared, 'CXX') + cpp = _add_flags(cpp, 'CPP') + cflags = _add_flags(cflags, 'CPP') + cxxflags = _add_flags(cxxflags, 'CPP') + ldshared = _add_flags(ldshared, 'CPP') + ldcxxshared = _add_flags(ldcxxshared, 'CPP') + + ar = os.environ.get('AR', ar) + + archiver = ar + ' ' + os.environ.get('ARFLAGS', ar_flags) cc_cmd = cc + ' ' + cflags + cxx_cmd = cxx + ' ' + cxxflags + compiler.set_executables( preprocessor=cpp, compiler=cc_cmd, compiler_so=cc_cmd + ' ' + ccshared, - compiler_cxx=cxx, + compiler_cxx=cxx_cmd, + compiler_so_cxx=cxx_cmd + ' ' + ccshared, linker_so=ldshared, + linker_so_cxx=ldcxxshared, linker_exe=cc, + linker_exe_cxx=cxx, archiver=archiver, ) @@ -561,3 +569,14 @@ def get_config_var(name): warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) return get_config_vars().get(name) + + +@pass_none +def _add_flags(value: str, type: str) -> str: + """ + Add any flags from the environment for the given type. + + type is the prefix to FLAGS in the environment key (e.g. "C" for "CFLAGS"). + """ + flags = os.environ.get(f'{type}FLAGS') + return f'{value} {flags}' if flags else value diff --git a/setuptools/_distutils/tests/test_archive_util.py b/setuptools/_distutils/tests/test_archive_util.py index abbcd36cb01..389eba16e8b 100644 --- a/setuptools/_distutils/tests/test_archive_util.py +++ b/setuptools/_distutils/tests/test_archive_util.py @@ -18,10 +18,10 @@ from distutils.spawn import spawn from distutils.tests import support from os.path import splitdrive -from test.support import patch import path import pytest +from test.support import patch from .compat.py38 import check_warnings from .unix_compat import UID_0_SUPPORT, grp, pwd, require_uid_0, require_unix_id diff --git a/setuptools/_distutils/tests/test_build.py b/setuptools/_distutils/tests/test_build.py index 8fb1bc1b776..d379aca0bbf 100644 --- a/setuptools/_distutils/tests/test_build.py +++ b/setuptools/_distutils/tests/test_build.py @@ -4,8 +4,7 @@ import sys from distutils.command.build import build from distutils.tests import support -from sysconfig import get_config_var -from sysconfig import get_platform +from sysconfig import get_config_var, get_platform class TestBuild(support.TempdirManager): diff --git a/setuptools/_distutils/tests/test_build_ext.py b/setuptools/_distutils/tests/test_build_ext.py index 6c4c4ba8693..8bd3cef8554 100644 --- a/setuptools/_distutils/tests/test_build_ext.py +++ b/setuptools/_distutils/tests/test_build_ext.py @@ -25,11 +25,11 @@ fixup_build_ext, ) from io import StringIO -from test import support import jaraco.path import path import pytest +from test import support from .compat import py38 as import_helper diff --git a/setuptools/_distutils/tests/test_cygwinccompiler.py b/setuptools/_distutils/tests/test_cygwinccompiler.py index 2e1640b7571..677bc0ac99e 100644 --- a/setuptools/_distutils/tests/test_cygwinccompiler.py +++ b/setuptools/_distutils/tests/test_cygwinccompiler.py @@ -71,50 +71,8 @@ def test_check_config_h(self): assert check_config_h()[0] == CONFIG_H_OK def test_get_msvcr(self): - # [] - sys.version = ( - '2.6.1 (r261:67515, Dec 6 2008, 16:42:21) ' - '\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]' - ) assert get_msvcr() == [] - # MSVC 7.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1300 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr70'] - - # MSVC 7.1 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr71'] - - # VS2005 / MSVC 8.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1400 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr80'] - - # VS2008 / MSVC 9.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1500 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr90'] - - sys.version = ( - '3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 18:46:30) ' - '[MSC v.1929 32 bit (Intel)]' - ) - assert get_msvcr() == ['vcruntime140'] - - # unknown - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.2000 32 bits (Intel)]' - ) - with pytest.raises(ValueError): - get_msvcr() - @pytest.mark.skipif('sys.platform != "cygwin"') def test_dll_libraries_not_none(self): from distutils.cygwinccompiler import CygwinCCompiler diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index 5bd206fec1e..4d78a19803f 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -88,7 +88,7 @@ def test_command_packages_cmdline(self, clear_argv): 'distutils' not in Distribution.parse_config_files.__module__, reason='Cannot test when virtualenv has monkey-patched Distribution', ) - def test_venv_install_options(self, tmp_path): + def test_venv_install_options(self, tmp_path, clear_argv): sys.argv.append("install") file = str(tmp_path / 'file') diff --git a/setuptools/_distutils/tests/test_extension.py b/setuptools/_distutils/tests/test_extension.py index 527a1355069..41872e04e8b 100644 --- a/setuptools/_distutils/tests/test_extension.py +++ b/setuptools/_distutils/tests/test_extension.py @@ -1,6 +1,7 @@ """Tests for distutils.extension.""" import os +import pathlib import warnings from distutils.extension import Extension, read_setup_file @@ -68,13 +69,15 @@ def test_extension_init(self): assert ext.name == 'name' # the second argument, which is the list of files, must - # be a list of strings + # be a list of strings or PathLike objects with pytest.raises(AssertionError): Extension('name', 'file') with pytest.raises(AssertionError): Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) assert ext.sources == ['file1', 'file2'] + ext = Extension('name', [pathlib.Path('file1'), pathlib.Path('file2')]) + assert ext.sources == ['file1', 'file2'] # others arguments have defaults for attr in ( diff --git a/setuptools/_distutils/tests/test_install_data.py b/setuptools/_distutils/tests/test_install_data.py index f34070b10bd..c800f86c645 100644 --- a/setuptools/_distutils/tests/test_install_data.py +++ b/setuptools/_distutils/tests/test_install_data.py @@ -1,6 +1,7 @@ """Tests for distutils.command.install_data.""" import os +import pathlib from distutils.command.install_data import install_data from distutils.tests import support @@ -18,22 +19,27 @@ def test_simple_run(self): # data_files can contain # - simple files + # - a Path object # - a tuple with a path, and a list of file one = os.path.join(pkg_dir, 'one') self.write_file(one, 'xxx') inst2 = os.path.join(pkg_dir, 'inst2') two = os.path.join(pkg_dir, 'two') self.write_file(two, 'xxx') + three = pathlib.Path(pkg_dir) / 'three' + self.write_file(three, 'xxx') - cmd.data_files = [one, (inst2, [two])] - assert cmd.get_inputs() == [one, (inst2, [two])] + cmd.data_files = [one, (inst2, [two]), three] + assert cmd.get_inputs() == [one, (inst2, [two]), three] # let's run the command cmd.ensure_finalized() cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 2 + assert len(cmd.get_outputs()) == 3 + rthree = os.path.split(one)[-1] + assert os.path.exists(os.path.join(inst, rthree)) rtwo = os.path.split(two)[-1] assert os.path.exists(os.path.join(inst2, rtwo)) rone = os.path.split(one)[-1] @@ -46,21 +52,23 @@ def test_simple_run(self): cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 2 + assert len(cmd.get_outputs()) == 3 + assert os.path.exists(os.path.join(inst, rthree)) assert os.path.exists(os.path.join(inst2, rtwo)) assert os.path.exists(os.path.join(inst, rone)) cmd.outfiles = [] # now using root and empty dir cmd.root = os.path.join(pkg_dir, 'root') - inst4 = os.path.join(pkg_dir, 'inst4') - three = os.path.join(cmd.install_dir, 'three') - self.write_file(three, 'xx') - cmd.data_files = [one, (inst2, [two]), ('inst3', [three]), (inst4, [])] + inst5 = os.path.join(pkg_dir, 'inst5') + four = os.path.join(cmd.install_dir, 'four') + self.write_file(four, 'xx') + cmd.data_files = [one, (inst2, [two]), three, ('inst5', [four]), (inst5, [])] cmd.ensure_finalized() cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 4 + assert len(cmd.get_outputs()) == 5 + assert os.path.exists(os.path.join(inst, rthree)) assert os.path.exists(os.path.join(inst2, rtwo)) assert os.path.exists(os.path.join(inst, rone)) diff --git a/setuptools/_distutils/tests/test_mingwccompiler.py b/setuptools/_distutils/tests/test_mingwccompiler.py index fd201cd7734..3e3ad5058c3 100644 --- a/setuptools/_distutils/tests/test_mingwccompiler.py +++ b/setuptools/_distutils/tests/test_mingwccompiler.py @@ -1,8 +1,8 @@ -import pytest - -from distutils.util import split_quoted, is_mingw -from distutils.errors import DistutilsPlatformError, CCompilerError from distutils import sysconfig +from distutils.errors import CCompilerError, DistutilsPlatformError +from distutils.util import is_mingw, split_quoted + +import pytest class TestMingw32CCompiler: @@ -45,6 +45,7 @@ def test_cygwincc_error(self, monkeypatch): with pytest.raises(CCompilerError): distutils.cygwinccompiler.Mingw32CCompiler() + @pytest.mark.skipif('sys.platform == "cygwin"') def test_customize_compiler_with_msvc_python(self): from distutils.cygwinccompiler import Mingw32CCompiler diff --git a/setuptools/_distutils/tests/test_modified.py b/setuptools/_distutils/tests/test_modified.py index 2bd82346cf0..e35cec2d6f1 100644 --- a/setuptools/_distutils/tests/test_modified.py +++ b/setuptools/_distutils/tests/test_modified.py @@ -117,3 +117,10 @@ def test_newer_pairwise_group(groups_target): newer = newer_pairwise_group([groups_target.newer], [groups_target.target]) assert older == ([], []) assert newer == ([groups_target.newer], [groups_target.target]) + + +def test_newer_group_no_sources_no_target(tmp_path): + """ + Consider no sources and no target "newer". + """ + assert newer_group([], str(tmp_path / 'does-not-exist')) diff --git a/setuptools/_distutils/tests/test_spawn.py b/setuptools/_distutils/tests/test_spawn.py index 2576bdd53da..fd7b669cbf5 100644 --- a/setuptools/_distutils/tests/test_spawn.py +++ b/setuptools/_distutils/tests/test_spawn.py @@ -7,10 +7,10 @@ from distutils.errors import DistutilsExecError from distutils.spawn import find_executable, spawn from distutils.tests import support -from test.support import unix_shell import path import pytest +from test.support import unix_shell from .compat import py38 as os_helper diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py index 889a398c22c..49274a36aea 100644 --- a/setuptools/_distutils/tests/test_sysconfig.py +++ b/setuptools/_distutils/tests/test_sysconfig.py @@ -9,12 +9,12 @@ from distutils import sysconfig from distutils.ccompiler import new_compiler # noqa: F401 from distutils.unixccompiler import UnixCCompiler -from test.support import swap_item import jaraco.envs import path import pytest from jaraco.text import trim +from test.support import swap_item def _gen_makefile(root, contents): @@ -134,7 +134,10 @@ def test_customize_compiler(self): assert comp.exes['compiler_so'] == ( 'env_cc --sc-cflags --env-cflags --env-cppflags --sc-ccshared' ) - assert comp.exes['compiler_cxx'] == 'env_cxx --env-cxx-flags' + assert ( + comp.exes['compiler_cxx'] + == 'env_cxx --env-cxx-flags --sc-cflags --env-cppflags' + ) assert comp.exes['linker_exe'] == 'env_cc' assert comp.exes['linker_so'] == ( 'env_ldshared --env-ldflags --env-cflags --env-cppflags' @@ -162,7 +165,7 @@ def test_customize_compiler(self): assert comp.exes['preprocessor'] == 'sc_cc -E' assert comp.exes['compiler'] == 'sc_cc --sc-cflags' assert comp.exes['compiler_so'] == 'sc_cc --sc-cflags --sc-ccshared' - assert comp.exes['compiler_cxx'] == 'sc_cxx' + assert comp.exes['compiler_cxx'] == 'sc_cxx --sc-cflags' assert comp.exes['linker_exe'] == 'sc_cc' assert comp.exes['linker_so'] == 'sc_ldshared' assert comp.shared_lib_extension == 'sc_shutil_suffix' diff --git a/setuptools/_distutils/tests/test_unixccompiler.py b/setuptools/_distutils/tests/test_unixccompiler.py index d2c88e91169..50b66544a8d 100644 --- a/setuptools/_distutils/tests/test_unixccompiler.py +++ b/setuptools/_distutils/tests/test_unixccompiler.py @@ -257,9 +257,13 @@ def test_cc_overrides_ldshared_for_cxx_correctly(self): def gcv(v): if v == 'LDSHARED': return 'gcc-4.2 -bundle -undefined dynamic_lookup ' + elif v == 'LDCXXSHARED': + return 'g++-4.2 -bundle -undefined dynamic_lookup ' elif v == 'CXX': return 'g++-4.2' - return 'gcc-4.2' + elif v == 'CC': + return 'gcc-4.2' + return '' def gcvs(*args, _orig=sysconfig.get_config_vars): if args: @@ -315,3 +319,33 @@ def test_has_function(self): self.cc.output_dir = 'scratch' os.chdir(self.mkdtemp()) self.cc.has_function('abort') + + def test_find_library_file(self, monkeypatch): + compiler = UnixCCompiler() + compiler._library_root = lambda dir: dir + monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) + + libname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll' + dirs = ('/foo/bar/missing', '/foo/bar/existing') + assert ( + compiler.find_library_file(dirs, 'abc').replace('\\', '/') + == f'/foo/bar/existing/{libname}' + ) + assert ( + compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + == f'/foo/bar/existing/{libname}' + ) + + monkeypatch.setattr( + os.path, + 'exists', + lambda d: 'existing' in d and '.a' in d and '.dll.a' not in d, + ) + assert ( + compiler.find_library_file(dirs, 'abc').replace('\\', '/') + == '/foo/bar/existing/libabc.a' + ) + assert ( + compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + == '/foo/bar/existing/libabc.a' + ) diff --git a/setuptools/_distutils/tests/test_util.py b/setuptools/_distutils/tests/test_util.py index 0de4e1a59c3..00c9743ed0a 100644 --- a/setuptools/_distutils/tests/test_util.py +++ b/setuptools/_distutils/tests/test_util.py @@ -5,6 +5,7 @@ import email.policy import io import os +import pathlib import sys import sysconfig as stdlib_sysconfig import unittest.mock as mock @@ -63,30 +64,9 @@ def test_get_platform(self): assert get_platform() == 'win-arm64' def test_convert_path(self): - # linux/mac - os.sep = '/' - - def _join(path): - return '/'.join(path) - - os.path.join = _join - - assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' - - # win - os.sep = '\\' - - def _join(*path): - return '\\'.join(path) - - os.path.join = _join - - with pytest.raises(ValueError): - convert_path('/home/to/my/stuff') - with pytest.raises(ValueError): - convert_path('home/to/my/stuff/') - - assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' + expected = os.sep.join(('', 'home', 'to', 'my', 'stuff')) + assert convert_path('/home/to/my/stuff') == expected + assert convert_path(pathlib.Path('/home/to/my/stuff')) == expected assert convert_path('.') == os.curdir def test_change_root(self): diff --git a/setuptools/_distutils/unixccompiler.py b/setuptools/_distutils/unixccompiler.py index 7e68596b26c..6c1116ae8f7 100644 --- a/setuptools/_distutils/unixccompiler.py +++ b/setuptools/_distutils/unixccompiler.py @@ -118,9 +118,12 @@ class UnixCCompiler(CCompiler): 'preprocessor': None, 'compiler': ["cc"], 'compiler_so': ["cc"], - 'compiler_cxx': ["cc"], + 'compiler_cxx': ["c++"], + 'compiler_so_cxx': ["c++"], 'linker_so': ["cc", "-shared"], + 'linker_so_cxx': ["c++", "-shared"], 'linker_exe': ["cc"], + 'linker_exe_cxx': ["c++", "-shared"], 'archiver': ["ar", "-cr"], 'ranlib': None, } @@ -187,8 +190,14 @@ def preprocess( def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs) + compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs) try: - self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) + if self.detect_language(src) == 'c++': + self.spawn( + compiler_so_cxx + cc_args + [src, '-o', obj] + extra_postargs + ) + else: + self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) except DistutilsExecError as msg: raise CompileError(msg) @@ -256,7 +265,13 @@ def link( # building an executable or linker_so (with shared options) # when building a shared library. building_exe = target_desc == CCompiler.EXECUTABLE - linker = (self.linker_exe if building_exe else self.linker_so)[:] + linker = ( + self.linker_exe + if building_exe + else ( + self.linker_so_cxx if target_lang == "c++" else self.linker_so + ) + )[:] if target_lang == "c++" and self.compiler_cxx: env, linker_ne = _split_env(linker) @@ -366,27 +381,11 @@ def _library_root(dir): return os.path.join(match.group(1), dir[1:]) if apply_root else dir def find_library_file(self, dirs, lib, debug=False): - r""" + """ Second-guess the linker with not much hard data to go on: GCC seems to prefer the shared library, so assume that *all* Unix C compilers do, ignoring even GCC's "-static" option. - - >>> compiler = UnixCCompiler() - >>> compiler._library_root = lambda dir: dir - >>> monkeypatch = getfixture('monkeypatch') - >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) - >>> dirs = ('/foo/bar/missing', '/foo/bar/existing') - >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.dylib' - >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.dylib' - >>> monkeypatch.setattr(os.path, 'exists', - ... lambda d: 'existing' in d and '.a' in d) - >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.a' - >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.a' """ lib_names = ( self.library_filename(lib, lib_type=type) diff --git a/setuptools/_distutils/util.py b/setuptools/_distutils/util.py index 9db89b09791..4cc6bd283c5 100644 --- a/setuptools/_distutils/util.py +++ b/setuptools/_distutils/util.py @@ -4,9 +4,12 @@ one of the other *util.py modules. """ +from __future__ import annotations + import functools import importlib.util import os +import pathlib import re import string import subprocess @@ -14,6 +17,7 @@ import sysconfig import tempfile +from ._functools import pass_none from ._log import log from ._modified import newer from .errors import DistutilsByteCompileError, DistutilsPlatformError @@ -116,33 +120,23 @@ def split_version(s): return [int(n) for n in s.split('.')] -def convert_path(pathname): - """Return 'pathname' as a name that will work on the native filesystem, - i.e. split it on '/' and put it back together again using the current - directory separator. Needed because filenames in the setup script are - always supplied in Unix style, and have to be converted to the local - convention before we can actually use them in the filesystem. Raises - ValueError on non-Unix-ish systems if 'pathname' either starts or - ends with a slash. +@pass_none +def convert_path(pathname: str | os.PathLike) -> str: + r""" + Allow for pathlib.Path inputs, coax to a native path string. + + If None is passed, will just pass it through as + Setuptools relies on this behavior. + + >>> convert_path(None) is None + True + + Removes empty paths. + + >>> convert_path('foo/./bar').replace('\\', '/') + 'foo/bar' """ - if os.sep == '/': - return pathname - if not pathname: - return pathname - if pathname[0] == '/': - raise ValueError(f"path '{pathname}' cannot be absolute") - if pathname[-1] == '/': - raise ValueError(f"path '{pathname}' cannot end with '/'") - - paths = pathname.split('/') - while '.' in paths: - paths.remove('.') - if not paths: - return os.curdir - return os.path.join(*paths) - - -# convert_path () + return os.fspath(pathlib.PurePath(pathname)) def change_root(new_root, pathname): diff --git a/setuptools/_entry_points.py b/setuptools/_entry_points.py index 5de12582beb..e785fc7df8d 100644 --- a/setuptools/_entry_points.py +++ b/setuptools/_entry_points.py @@ -1,13 +1,14 @@ import functools -import operator import itertools +import operator -from .errors import OptionError -from jaraco.text import yield_lines from jaraco.functools import pass_none +from jaraco.text import yield_lines +from more_itertools import consume + from ._importlib import metadata from ._itertools import ensure_unique -from more_itertools import consume +from .errors import OptionError def ensure_valid(ep): diff --git a/setuptools/_imp.py b/setuptools/_imp.py index 38b146fc4d5..bddbf6a6838 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -3,14 +3,12 @@ from the deprecated imp module. """ -import os -import importlib.util import importlib.machinery +import importlib.util +import os import tokenize - from importlib.util import module_from_spec - PY_SOURCE = 1 PY_COMPILED = 2 C_EXTENSION = 3 diff --git a/setuptools/_importlib.py b/setuptools/_importlib.py index b2d5b5b84af..5317be0fa0d 100644 --- a/setuptools/_importlib.py +++ b/setuptools/_importlib.py @@ -1,6 +1,5 @@ import sys - if sys.version_info < (3, 10): import importlib_metadata as metadata # pragma: no cover else: diff --git a/setuptools/_path.py b/setuptools/_path.py index d00db254647..dd4a9db8cbd 100644 --- a/setuptools/_path.py +++ b/setuptools/_path.py @@ -1,15 +1,20 @@ +from __future__ import annotations + import contextlib import os import sys -from typing import Union +from typing import TYPE_CHECKING, Union -from more_itertools import unique_everseen +if TYPE_CHECKING: + from typing_extensions import TypeAlias +from more_itertools import unique_everseen + if sys.version_info >= (3, 9): - StrPath = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath + StrPath: TypeAlias = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath else: - StrPath = Union[str, os.PathLike] + StrPath: TypeAlias = Union[str, os.PathLike] def ensure_directory(path): diff --git a/setuptools/_reqs.py b/setuptools/_reqs.py index 1b64d9df792..2d09244b430 100644 --- a/setuptools/_reqs.py +++ b/setuptools/_reqs.py @@ -1,11 +1,16 @@ +from __future__ import annotations + from functools import lru_cache -from typing import Callable, Iterable, Iterator, TypeVar, Union, overload +from typing import TYPE_CHECKING, Callable, Iterable, Iterator, TypeVar, Union, overload import jaraco.text as text from packaging.requirements import Requirement +if TYPE_CHECKING: + from typing_extensions import TypeAlias + _T = TypeVar("_T") -_StrOrIter = Union[str, Iterable[str]] +_StrOrIter: TypeAlias = Union[str, Iterable[str]] parse_req: Callable[[str], Requirement] = lru_cache()(Requirement) diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER b/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e38a3..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA b/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA deleted file mode 100644 index 7aea136818b..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA +++ /dev/null @@ -1,158 +0,0 @@ -Metadata-Version: 2.1 -Name: ordered-set -Version: 4.1.0 -Summary: An OrderedSet is a custom MutableSet that remembers its order, so that every -Author-email: Elia Robyn Lake -Requires-Python: >=3.7 -Description-Content-Type: text/markdown -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Dist: pytest ; extra == "dev" -Requires-Dist: black ; extra == "dev" -Requires-Dist: mypy ; extra == "dev" -Project-URL: Home, https://github.com/rspeer/ordered-set -Provides-Extra: dev - -[![Pypi](https://img.shields.io/pypi/v/ordered-set.svg)](https://pypi.python.org/pypi/ordered-set) - -An OrderedSet is a mutable data structure that is a hybrid of a list and a set. -It remembers the order of its entries, and every entry has an index number that -can be looked up. - -## Installation - -`ordered_set` is available on PyPI and packaged as a wheel. You can list it -as a dependency of your project, in whatever form that takes. - -To install it into your current Python environment: - - pip install ordered-set - -To install the code for development, after checking out the repository: - - pip install flit - flit install - -## Usage examples - -An OrderedSet is created and used like a set: - - >>> from ordered_set import OrderedSet - - >>> letters = OrderedSet('abracadabra') - - >>> letters - OrderedSet(['a', 'b', 'r', 'c', 'd']) - - >>> 'r' in letters - True - -It is efficient to find the index of an entry in an OrderedSet, or find an -entry by its index. To help with this use case, the `.add()` method returns -the index of the added item, whether it was already in the set or not. - - >>> letters.index('r') - 2 - - >>> letters[2] - 'r' - - >>> letters.add('r') - 2 - - >>> letters.add('x') - 5 - -OrderedSets implement the union (`|`), intersection (`&`), and difference (`-`) -operators like sets do. - - >>> letters |= OrderedSet('shazam') - - >>> letters - OrderedSet(['a', 'b', 'r', 'c', 'd', 'x', 's', 'h', 'z', 'm']) - - >>> letters & set('aeiou') - OrderedSet(['a']) - - >>> letters -= 'abcd' - - >>> letters - OrderedSet(['r', 'x', 's', 'h', 'z', 'm']) - -The `__getitem__()` and `index()` methods have been extended to accept any -iterable except a string, returning a list, to perform NumPy-like "fancy -indexing". - - >>> letters = OrderedSet('abracadabra') - - >>> letters[[0, 2, 3]] - ['a', 'r', 'c'] - - >>> letters.index(['a', 'r', 'c']) - [0, 2, 3] - -OrderedSet implements `__getstate__` and `__setstate__` so it can be pickled, -and implements the abstract base classes `collections.MutableSet` and -`collections.Sequence`. - -OrderedSet can be used as a generic collection type, similar to the collections -in the `typing` module like List, Dict, and Set. For example, you can annotate -a variable as having the type `OrderedSet[str]` or `OrderedSet[Tuple[int, -str]]`. - - -## OrderedSet in data science applications - -An OrderedSet can be used as a bi-directional mapping between a sparse -vocabulary and dense index numbers. As of version 3.1, it accepts NumPy arrays -of index numbers as well as lists. - -This combination of features makes OrderedSet a simple implementation of many -of the things that `pandas.Index` is used for, and many of its operations are -faster than the equivalent pandas operations. - -For further compatibility with pandas.Index, `get_loc` (the pandas method for -looking up a single index) and `get_indexer` (the pandas method for fancy -indexing in reverse) are both aliases for `index` (which handles both cases -in OrderedSet). - - -## Authors - -OrderedSet was implemented by Elia Robyn Lake (maiden name: Robyn Speer). -Jon Crall contributed changes and tests to make it fit the Python set API. -Roman Inflianskas added the original type annotations. - - -## Comparisons - -The original implementation of OrderedSet was a [recipe posted to ActiveState -Recipes][recipe] by Raymond Hettiger, released under the MIT license. - -[recipe]: https://code.activestate.com/recipes/576694-orderedset/ - -Hettiger's implementation kept its content in a doubly-linked list referenced by a -dict. As a result, looking up an item by its index was an O(N) operation, while -deletion was O(1). - -This version makes different trade-offs for the sake of efficient lookups. Its -content is a standard Python list instead of a doubly-linked list. This -provides O(1) lookups by index at the expense of O(N) deletion, as well as -slightly faster iteration. - -In Python 3.6 and later, the built-in `dict` type is inherently ordered. If you -ignore the dictionary values, that also gives you a simple ordered set, with -fast O(1) insertion, deletion, iteration and membership testing. However, `dict` -does not provide the list-like random access features of OrderedSet. You -would have to convert it to a list in O(N) to look up the index of an entry or -look up an entry by its index. - diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD b/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD deleted file mode 100644 index a9875cde4e9..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD +++ /dev/null @@ -1,8 +0,0 @@ -ordered_set-4.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -ordered_set-4.1.0.dist-info/METADATA,sha256=FqVN_VUTJTCDQ-vtnmXrbgapDjciET-54gSNJ47sro8,5340 -ordered_set-4.1.0.dist-info/RECORD,, -ordered_set-4.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -ordered_set-4.1.0.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81 -ordered_set/__init__.py,sha256=ytazgKsyBKi9uFtBt938yXxQtdat1VCC681s9s0CMqg,17146 -ordered_set/__pycache__/__init__.cpython-312.pyc,, -ordered_set/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/REQUESTED b/setuptools/_vendor/ordered_set-4.1.0.dist-info/REQUESTED deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL b/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL deleted file mode 100644 index c727d148239..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: flit 3.6.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/setuptools/_vendor/ordered_set/__init__.py b/setuptools/_vendor/ordered_set/__init__.py deleted file mode 100644 index e86c70ed80e..00000000000 --- a/setuptools/_vendor/ordered_set/__init__.py +++ /dev/null @@ -1,536 +0,0 @@ -""" -An OrderedSet is a custom MutableSet that remembers its order, so that every -entry has an index that can be looked up. It can also act like a Sequence. - -Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, -and released under the MIT license. -""" -import itertools as it -from typing import ( - Any, - Dict, - Iterable, - Iterator, - List, - MutableSet, - AbstractSet, - Sequence, - Set, - TypeVar, - Union, - overload, -) - -SLICE_ALL = slice(None) -__version__ = "4.1.0" - - -T = TypeVar("T") - -# SetLike[T] is either a set of elements of type T, or a sequence, which -# we will convert to an OrderedSet by adding its elements in order. -SetLike = Union[AbstractSet[T], Sequence[T]] -OrderedSetInitializer = Union[AbstractSet[T], Sequence[T], Iterable[T]] - - -def _is_atomic(obj: Any) -> bool: - """ - Returns True for objects which are iterable but should not be iterated in - the context of indexing an OrderedSet. - - When we index by an iterable, usually that means we're being asked to look - up a list of things. - - However, in the case of the .index() method, we shouldn't handle strings - and tuples like other iterables. They're not sequences of things to look - up, they're the single, atomic thing we're trying to find. - - As an example, oset.index('hello') should give the index of 'hello' in an - OrderedSet of strings. It shouldn't give the indexes of each individual - character. - """ - return isinstance(obj, str) or isinstance(obj, tuple) - - -class OrderedSet(MutableSet[T], Sequence[T]): - """ - An OrderedSet is a custom MutableSet that remembers its order, so that - every entry has an index that can be looked up. - - Example: - >>> OrderedSet([1, 1, 2, 3, 2]) - OrderedSet([1, 2, 3]) - """ - - def __init__(self, initial: OrderedSetInitializer[T] = None): - self.items: List[T] = [] - self.map: Dict[T, int] = {} - if initial is not None: - # In terms of duck-typing, the default __ior__ is compatible with - # the types we use, but it doesn't expect all the types we - # support as values for `initial`. - self |= initial # type: ignore - - def __len__(self): - """ - Returns the number of unique elements in the ordered set - - Example: - >>> len(OrderedSet([])) - 0 - >>> len(OrderedSet([1, 2])) - 2 - """ - return len(self.items) - - @overload - def __getitem__(self, index: slice) -> "OrderedSet[T]": - ... - - @overload - def __getitem__(self, index: Sequence[int]) -> List[T]: - ... - - @overload - def __getitem__(self, index: int) -> T: - ... - - # concrete implementation - def __getitem__(self, index): - """ - Get the item at a given index. - - If `index` is a slice, you will get back that slice of items, as a - new OrderedSet. - - If `index` is a list or a similar iterable, you'll get a list of - items corresponding to those indices. This is similar to NumPy's - "fancy indexing". The result is not an OrderedSet because you may ask - for duplicate indices, and the number of elements returned should be - the number of elements asked for. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset[1] - 2 - """ - if isinstance(index, slice) and index == SLICE_ALL: - return self.copy() - elif isinstance(index, Iterable): - return [self.items[i] for i in index] - elif isinstance(index, slice) or hasattr(index, "__index__"): - result = self.items[index] - if isinstance(result, list): - return self.__class__(result) - else: - return result - else: - raise TypeError("Don't know how to index an OrderedSet by %r" % index) - - def copy(self) -> "OrderedSet[T]": - """ - Return a shallow copy of this object. - - Example: - >>> this = OrderedSet([1, 2, 3]) - >>> other = this.copy() - >>> this == other - True - >>> this is other - False - """ - return self.__class__(self) - - # Define the gritty details of how an OrderedSet is serialized as a pickle. - # We leave off type annotations, because the only code that should interact - # with these is a generalized tool such as pickle. - def __getstate__(self): - if len(self) == 0: - # In pickle, the state can't be an empty list. - # We need to return a truthy value, or else __setstate__ won't be run. - # - # This could have been done more gracefully by always putting the state - # in a tuple, but this way is backwards- and forwards- compatible with - # previous versions of OrderedSet. - return (None,) - else: - return list(self) - - def __setstate__(self, state): - if state == (None,): - self.__init__([]) - else: - self.__init__(state) - - def __contains__(self, key: Any) -> bool: - """ - Test if the item is in this ordered set. - - Example: - >>> 1 in OrderedSet([1, 3, 2]) - True - >>> 5 in OrderedSet([1, 3, 2]) - False - """ - return key in self.map - - # Technically type-incompatible with MutableSet, because we return an - # int instead of nothing. This is also one of the things that makes - # OrderedSet convenient to use. - def add(self, key: T) -> int: - """ - Add `key` as an item to this OrderedSet, then return its index. - - If `key` is already in the OrderedSet, return the index it already - had. - - Example: - >>> oset = OrderedSet() - >>> oset.append(3) - 0 - >>> print(oset) - OrderedSet([3]) - """ - if key not in self.map: - self.map[key] = len(self.items) - self.items.append(key) - return self.map[key] - - append = add - - def update(self, sequence: SetLike[T]) -> int: - """ - Update the set with the given iterable sequence, then return the index - of the last element inserted. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset.update([3, 1, 5, 1, 4]) - 4 - >>> print(oset) - OrderedSet([1, 2, 3, 5, 4]) - """ - item_index = 0 - try: - for item in sequence: - item_index = self.add(item) - except TypeError: - raise ValueError( - "Argument needs to be an iterable, got %s" % type(sequence) - ) - return item_index - - @overload - def index(self, key: Sequence[T]) -> List[int]: - ... - - @overload - def index(self, key: T) -> int: - ... - - # concrete implementation - def index(self, key): - """ - Get the index of a given entry, raising an IndexError if it's not - present. - - `key` can be an iterable of entries that is not a string, in which case - this returns a list of indices. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset.index(2) - 1 - """ - if isinstance(key, Iterable) and not _is_atomic(key): - return [self.index(subkey) for subkey in key] - return self.map[key] - - # Provide some compatibility with pd.Index - get_loc = index - get_indexer = index - - def pop(self, index=-1) -> T: - """ - Remove and return item at index (default last). - - Raises KeyError if the set is empty. - Raises IndexError if index is out of range. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset.pop() - 3 - """ - if not self.items: - raise KeyError("Set is empty") - - elem = self.items[index] - del self.items[index] - del self.map[elem] - return elem - - def discard(self, key: T) -> None: - """ - Remove an element. Do not raise an exception if absent. - - The MutableSet mixin uses this to implement the .remove() method, which - *does* raise an error when asked to remove a non-existent item. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset.discard(2) - >>> print(oset) - OrderedSet([1, 3]) - >>> oset.discard(2) - >>> print(oset) - OrderedSet([1, 3]) - """ - if key in self: - i = self.map[key] - del self.items[i] - del self.map[key] - for k, v in self.map.items(): - if v >= i: - self.map[k] = v - 1 - - def clear(self) -> None: - """ - Remove all items from this OrderedSet. - """ - del self.items[:] - self.map.clear() - - def __iter__(self) -> Iterator[T]: - """ - Example: - >>> list(iter(OrderedSet([1, 2, 3]))) - [1, 2, 3] - """ - return iter(self.items) - - def __reversed__(self) -> Iterator[T]: - """ - Example: - >>> list(reversed(OrderedSet([1, 2, 3]))) - [3, 2, 1] - """ - return reversed(self.items) - - def __repr__(self) -> str: - if not self: - return "%s()" % (self.__class__.__name__,) - return "%s(%r)" % (self.__class__.__name__, list(self)) - - def __eq__(self, other: Any) -> bool: - """ - Returns true if the containers have the same items. If `other` is a - Sequence, then order is checked, otherwise it is ignored. - - Example: - >>> oset = OrderedSet([1, 3, 2]) - >>> oset == [1, 3, 2] - True - >>> oset == [1, 2, 3] - False - >>> oset == [2, 3] - False - >>> oset == OrderedSet([3, 2, 1]) - False - """ - if isinstance(other, Sequence): - # Check that this OrderedSet contains the same elements, in the - # same order, as the other object. - return list(self) == list(other) - try: - other_as_set = set(other) - except TypeError: - # If `other` can't be converted into a set, it's not equal. - return False - else: - return set(self) == other_as_set - - def union(self, *sets: SetLike[T]) -> "OrderedSet[T]": - """ - Combines all unique items. - Each items order is defined by its first appearance. - - Example: - >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) - >>> print(oset) - OrderedSet([3, 1, 4, 5, 2, 0]) - >>> oset.union([8, 9]) - OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) - >>> oset | {10} - OrderedSet([3, 1, 4, 5, 2, 0, 10]) - """ - cls: type = OrderedSet - if isinstance(self, OrderedSet): - cls = self.__class__ - containers = map(list, it.chain([self], sets)) - items = it.chain.from_iterable(containers) - return cls(items) - - def __and__(self, other: SetLike[T]) -> "OrderedSet[T]": - # the parent implementation of this is backwards - return self.intersection(other) - - def intersection(self, *sets: SetLike[T]) -> "OrderedSet[T]": - """ - Returns elements in common between all sets. Order is defined only - by the first set. - - Example: - >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) - >>> print(oset) - OrderedSet([1, 2, 3]) - >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) - OrderedSet([2]) - >>> oset.intersection() - OrderedSet([1, 2, 3]) - """ - cls: type = OrderedSet - items: OrderedSetInitializer[T] = self - if isinstance(self, OrderedSet): - cls = self.__class__ - if sets: - common = set.intersection(*map(set, sets)) - items = (item for item in self if item in common) - return cls(items) - - def difference(self, *sets: SetLike[T]) -> "OrderedSet[T]": - """ - Returns all elements that are in this set but not the others. - - Example: - >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) - OrderedSet([1, 3]) - >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) - OrderedSet([1]) - >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) - OrderedSet([1, 3]) - >>> OrderedSet([1, 2, 3]).difference() - OrderedSet([1, 2, 3]) - """ - cls = self.__class__ - items: OrderedSetInitializer[T] = self - if sets: - other = set.union(*map(set, sets)) - items = (item for item in self if item not in other) - return cls(items) - - def issubset(self, other: SetLike[T]) -> bool: - """ - Report whether another set contains this set. - - Example: - >>> OrderedSet([1, 2, 3]).issubset({1, 2}) - False - >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) - True - >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) - False - """ - if len(self) > len(other): # Fast check for obvious cases - return False - return all(item in other for item in self) - - def issuperset(self, other: SetLike[T]) -> bool: - """ - Report whether this set contains another set. - - Example: - >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) - False - >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) - True - >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) - False - """ - if len(self) < len(other): # Fast check for obvious cases - return False - return all(item in self for item in other) - - def symmetric_difference(self, other: SetLike[T]) -> "OrderedSet[T]": - """ - Return the symmetric difference of two OrderedSets as a new set. - That is, the new set will contain all elements that are in exactly - one of the sets. - - Their order will be preserved, with elements from `self` preceding - elements from `other`. - - Example: - >>> this = OrderedSet([1, 4, 3, 5, 7]) - >>> other = OrderedSet([9, 7, 1, 3, 2]) - >>> this.symmetric_difference(other) - OrderedSet([4, 5, 9, 2]) - """ - cls: type = OrderedSet - if isinstance(self, OrderedSet): - cls = self.__class__ - diff1 = cls(self).difference(other) - diff2 = cls(other).difference(self) - return diff1.union(diff2) - - def _update_items(self, items: list) -> None: - """ - Replace the 'items' list of this OrderedSet with a new one, updating - self.map accordingly. - """ - self.items = items - self.map = {item: idx for (idx, item) in enumerate(items)} - - def difference_update(self, *sets: SetLike[T]) -> None: - """ - Update this OrderedSet to remove items from one or more other sets. - - Example: - >>> this = OrderedSet([1, 2, 3]) - >>> this.difference_update(OrderedSet([2, 4])) - >>> print(this) - OrderedSet([1, 3]) - - >>> this = OrderedSet([1, 2, 3, 4, 5]) - >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) - >>> print(this) - OrderedSet([3, 5]) - """ - items_to_remove = set() # type: Set[T] - for other in sets: - items_as_set = set(other) # type: Set[T] - items_to_remove |= items_as_set - self._update_items([item for item in self.items if item not in items_to_remove]) - - def intersection_update(self, other: SetLike[T]) -> None: - """ - Update this OrderedSet to keep only items in another set, preserving - their order in this set. - - Example: - >>> this = OrderedSet([1, 4, 3, 5, 7]) - >>> other = OrderedSet([9, 7, 1, 3, 2]) - >>> this.intersection_update(other) - >>> print(this) - OrderedSet([1, 3, 7]) - """ - other = set(other) - self._update_items([item for item in self.items if item in other]) - - def symmetric_difference_update(self, other: SetLike[T]) -> None: - """ - Update this OrderedSet to remove items from another set, then - add items from the other set that were not present in this set. - - Example: - >>> this = OrderedSet([1, 4, 3, 5, 7]) - >>> other = OrderedSet([9, 7, 1, 3, 2]) - >>> this.symmetric_difference_update(other) - >>> print(this) - OrderedSet([4, 5, 9, 2]) - """ - items_to_add = [item for item in other if item not in self] - items_to_remove = set(other) - self._update_items( - [item for item in self.items if item not in items_to_remove] + items_to_add - ) diff --git a/setuptools/_vendor/ordered_set/py.typed b/setuptools/_vendor/ordered_set/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index 6b8460bd92c..e4acd75f9b9 100644 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -1,15 +1,16 @@ """Utilities for extracting common archive formats""" -import zipfile -import tarfile +import contextlib import os -import shutil import posixpath -import contextlib -from distutils.errors import DistutilsError +import shutil +import tarfile +import zipfile from ._path import ensure_directory +from distutils.errors import DistutilsError + __all__ = [ "unpack_archive", "unpack_zipfile", diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index c52c872fd0f..a6b85afc421 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -28,26 +28,31 @@ from __future__ import annotations +import contextlib import io import os import shlex -import sys -import tokenize import shutil -import contextlib +import sys import tempfile +import tokenize import warnings from pathlib import Path -from typing import Dict, Iterator, List, Optional, Union, Iterable +from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Union import setuptools -import distutils + from . import errors -from ._path import same_path, StrPath +from ._path import StrPath, same_path from ._reqs import parse_strings from .warnings import SetuptoolsDeprecationWarning + +import distutils from distutils.util import strtobool +if TYPE_CHECKING: + from typing_extensions import TypeAlias + __all__ = [ 'get_requires_for_build_sdist', @@ -142,7 +147,7 @@ def suppress_known_deprecation(): yield -_ConfigSettings = Optional[Dict[str, Union[str, List[str], None]]] +_ConfigSettings: TypeAlias = Union[Dict[str, Union[str, List[str], None]], None] """ Currently the user can run:: @@ -382,9 +387,10 @@ def _build_with_temp_dir( # Build in a temporary directory, then copy to the target. os.makedirs(result_directory, exist_ok=True) - temp_opts = {"prefix": ".tmp-", "dir": result_directory} - with tempfile.TemporaryDirectory(**temp_opts) as tmp_dist_dir: + with tempfile.TemporaryDirectory( + prefix=".tmp-", dir=result_directory + ) as tmp_dist_dir: sys.argv = [ *sys.argv[:1], *self._global_args(config_settings), diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index 5acd7687d64..bf011e896d9 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -1,6 +1,7 @@ -from distutils.command.bdist import bdist import sys +from distutils.command.bdist import bdist + if 'egg' not in bdist.format_commands: try: bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file") diff --git a/setuptools/command/_requirestxt.py b/setuptools/command/_requirestxt.py index ef35d183e86..b87476d6f4e 100644 --- a/setuptools/command/_requirestxt.py +++ b/setuptools/command/_requirestxt.py @@ -14,10 +14,10 @@ from itertools import filterfalse from typing import Dict, Mapping, TypeVar -from .. import _reqs from jaraco.text import yield_lines from packaging.requirements import Requirement +from .. import _reqs # dict can work as an ordered set _T = TypeVar("_T") diff --git a/setuptools/command/alias.py b/setuptools/command/alias.py index e7b4d5456a7..4eed6523810 100644 --- a/setuptools/command/alias.py +++ b/setuptools/command/alias.py @@ -1,6 +1,6 @@ -from distutils.errors import DistutilsOptionError +from setuptools.command.setopt import config_file, edit_config, option_base -from setuptools.command.setopt import edit_config, option_base, config_file +from distutils.errors import DistutilsOptionError def shquote(arg): diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 559f7d6032a..f3b7150208f 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -2,20 +2,21 @@ Build .egg distributions""" -from distutils.dir_util import remove_tree, mkpath -from distutils import log -from types import CodeType -import sys +import marshal import os import re +import sys import textwrap -import marshal +from sysconfig import get_path, get_python_version +from types import CodeType -from setuptools.extension import Library from setuptools import Command +from setuptools.extension import Library + from .._path import ensure_directory -from sysconfig import get_path, get_python_version +from distutils import log +from distutils.dir_util import mkpath, remove_tree def _get_purelib(): diff --git a/setuptools/command/bdist_rpm.py b/setuptools/command/bdist_rpm.py index 70ed6b6097f..e0d4caf2e97 100644 --- a/setuptools/command/bdist_rpm.py +++ b/setuptools/command/bdist_rpm.py @@ -1,7 +1,8 @@ -import distutils.command.bdist_rpm as orig - +from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning +import distutils.command.bdist_rpm as orig + class bdist_rpm(orig.bdist_rpm): """ @@ -12,6 +13,8 @@ class bdist_rpm(orig.bdist_rpm): disable eggs in RPM distributions. """ + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): SetuptoolsDeprecationWarning.emit( "Deprecated command", diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 5b9bcec60cf..8f067866594 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -16,20 +16,22 @@ import warnings from email.generator import BytesGenerator, Generator from email.policy import EmailPolicy -from distutils import log from glob import iglob from shutil import rmtree from typing import TYPE_CHECKING, Callable, Iterable, Literal, Sequence, cast from zipfile import ZIP_DEFLATED, ZIP_STORED -from .. import Command, __version__ +from packaging import tags, version as _packaging_version from wheel.metadata import pkginfo_to_metadata -from packaging import tags -from packaging import version as _packaging_version from wheel.wheelfile import WheelFile +from .. import Command, __version__ +from .egg_info import egg_info as egg_info_cls + +from distutils import log + if TYPE_CHECKING: - import types + from _typeshed import ExcInfo def safe_name(name: str) -> str: @@ -152,12 +154,14 @@ def safer_version(version: str) -> str: def remove_readonly( func: Callable[..., object], path: str, - excinfo: tuple[type[Exception], Exception, types.TracebackType], + excinfo: ExcInfo, ) -> None: remove_readonly_exc(func, path, excinfo[1]) -def remove_readonly_exc(func: Callable[..., object], path: str, exc: Exception) -> None: +def remove_readonly_exc( + func: Callable[..., object], path: str, exc: BaseException +) -> None: os.chmod(path, stat.S_IWRITE) func(path) @@ -232,40 +236,35 @@ class bdist_wheel(Command): def initialize_options(self) -> None: self.bdist_dir: str | None = None - self.data_dir = None + self.data_dir: str | None = None self.plat_name: str | None = None - self.plat_tag = None + self.plat_tag: str | None = None self.format = "zip" self.keep_temp = False self.dist_dir: str | None = None - self.egginfo_dir = None + self.egginfo_dir: str | None = None self.root_is_pure: bool | None = None - self.skip_build = None + self.skip_build = False self.relative = False self.owner = None self.group = None self.universal: bool = False - self.compression: str | int = "deflated" + self.compression: int | str = "deflated" self.python_tag: str = python_tag() self.build_number: str | None = None self.py_limited_api: str | Literal[False] = False self.plat_name_supplied = False - def finalize_options(self): - if self.bdist_dir is None: + def finalize_options(self) -> None: + if not self.bdist_dir: bdist_base = self.get_finalized_command("bdist").bdist_base self.bdist_dir = os.path.join(bdist_base, "wheel") - egg_info = self.distribution.get_command_obj("egg_info") + egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info")) egg_info.ensure_finalized() # needed for correct `wheel_dist_name` self.data_dir = self.wheel_dist_name + ".data" - self.plat_name_supplied = self.plat_name is not None - - try: - self.compression = self.supported_compressions[self.compression] - except KeyError: - raise ValueError(f"Unsupported compression: {self.compression}") from None + self.plat_name_supplied = bool(self.plat_name) need_options = ("dist_dir", "plat_name", "skip_build") @@ -275,10 +274,7 @@ def finalize_options(self): self.distribution.has_ext_modules() or self.distribution.has_c_libraries() ) - if self.py_limited_api and not re.match( - PY_LIMITED_API_PATTERN, self.py_limited_api - ): - raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'") + self._validate_py_limited_api() # Support legacy [wheel] section for setting universal wheel = self.distribution.get_option_dict("wheel") @@ -292,22 +288,37 @@ def finalize_options(self): if self.build_number is not None and not self.build_number[:1].isdigit(): raise ValueError("Build tag (build-number) must start with a digit.") + def _validate_py_limited_api(self) -> None: + if not self.py_limited_api: + return + + if not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): + raise ValueError(f"py-limited-api must match '{PY_LIMITED_API_PATTERN}'") + + if sysconfig.get_config_var("Py_GIL_DISABLED"): + raise ValueError( + f"`py_limited_api={self.py_limited_api!r}` not supported. " + "`Py_LIMITED_API` is currently incompatible with " + f"`Py_GIL_DISABLED` ({sys.abiflags=!r}). " + "See https://github.com/python/cpython/issues/111506." + ) + @property - def wheel_dist_name(self): + def wheel_dist_name(self) -> str: """Return distribution full name with - replaced with _""" - components = ( + components = [ safer_name(self.distribution.get_name()), safer_version(self.distribution.get_version()), - ) + ] if self.build_number: - components += (self.build_number,) + components.append(self.build_number) return "-".join(components) def get_tag(self) -> tuple[str, str, str]: # bdist sets self.plat_name if unset, we should only use it for purepy # wheels if the user supplied it. - if self.plat_name_supplied: - plat_name = cast(str, self.plat_name) + if self.plat_name_supplied and self.plat_name: + plat_name = self.plat_name elif self.root_is_pure: plat_name = "any" else: @@ -431,7 +442,7 @@ def run(self): os.makedirs(self.dist_dir) wheel_path = os.path.join(self.dist_dir, archive_basename + ".whl") - with WheelFile(wheel_path, "w", self.compression) as wf: + with WheelFile(wheel_path, "w", self._zip_compression()) as wf: wf.write_files(archive_root) # Add to 'Distribution.dist_files' so that the "upload" command works @@ -595,3 +606,16 @@ def adios(p: str) -> None: shutil.copy(license_path, os.path.join(distinfo_path, filename)) adios(egginfo_path) + + def _zip_compression(self) -> int: + if ( + isinstance(self.compression, int) + and self.compression in self.supported_compressions.values() + ): + return self.compression + + compression = self.supported_compressions.get(str(self.compression)) + if compression is not None: + return compression + + raise ValueError(f"Unsupported compression: {self.compression!r}") diff --git a/setuptools/command/build.py b/setuptools/command/build.py index bc765a17aec..0c5e544804f 100644 --- a/setuptools/command/build.py +++ b/setuptools/command/build.py @@ -1,12 +1,17 @@ from __future__ import annotations from typing import Protocol + +from ..dist import Distribution + from distutils.command.build import build as _build _ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"} class build(_build): + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + # copy to avoid sharing the object with parent class sub_commands = _build.sub_commands[:] diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index acd4d1d3bae..9db57ac8a20 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -1,6 +1,8 @@ +from ..dist import Distribution + import distutils.command.build_clib as orig -from distutils.errors import DistutilsSetupError from distutils import log +from distutils.errors import DistutilsSetupError try: from distutils._modified import newer_pairwise_group @@ -25,6 +27,8 @@ class build_clib(orig.build_clib): the compiler. """ + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def build_libraries(self, libraries): for lib_name, build_info in libraries: sources = build_info.get('sources') diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index 508704f3c04..d44d7b8ae1f 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -1,36 +1,38 @@ from __future__ import annotations +import itertools import os import sys -import itertools from importlib.machinery import EXTENSION_SUFFIXES from importlib.util import cache_from_source as _compiled_file_name -from typing import Iterator from pathlib import Path +from typing import Iterator -from distutils.command.build_ext import build_ext as _du_build_ext -from distutils.ccompiler import new_compiler -from distutils.sysconfig import customize_compiler, get_config_var -from distutils import log - +from setuptools.dist import Distribution from setuptools.errors import BaseError from setuptools.extension import Extension, Library +from distutils import log +from distutils.ccompiler import new_compiler +from distutils.sysconfig import customize_compiler, get_config_var + try: # Attempt to use Cython for building extensions, if available - from Cython.Distutils.build_ext import build_ext as _build_ext # type: ignore[import-not-found] # Cython not installed on CI tests + from Cython.Distutils.build_ext import ( # type: ignore[import-not-found] # Cython not installed on CI tests + build_ext as _build_ext, + ) # Additionally, assert that the compiler module will load # also. Ref #1229. __import__('Cython.Compiler.Main') except ImportError: - _build_ext = _du_build_ext + from distutils.command.build_ext import build_ext as _build_ext # make sure _config_vars is initialized get_config_var("LDSHARED") # Not publicly exposed in typeshed distutils stubs, but this is done on purpose # See https://github.com/pypa/setuptools/pull/4228#issuecomment-1959856400 -from distutils.sysconfig import _config_vars as _CONFIG_VARS # type: ignore # noqa +from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa: E402 def _customize_compiler_for_shlib(compiler): @@ -84,6 +86,7 @@ def get_abi3_suffix(): class build_ext(_build_ext): + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution editable_mode: bool = False inplace: bool = False diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 15a4f63fdd4..e6d9656f101 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -1,21 +1,23 @@ from __future__ import annotations -from functools import partial -from glob import glob -from distutils.util import convert_path -import distutils.command.build_py as orig -import os import fnmatch -import textwrap -import distutils.errors import itertools +import os import stat +import textwrap +from functools import partial +from glob import glob from pathlib import Path from typing import Iterable, Iterator from more_itertools import unique_everseen + +from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning +import distutils.command.build_py as orig +import distutils.errors +from distutils.util import convert_path _IMPLICIT_DATA_FILES = ('*.pyi', 'py.typed') @@ -34,6 +36,7 @@ class build_py(orig.build_py): 'py_modules' and 'packages' in the same setup operation. """ + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution editable_mode: bool = False existing_egg_info_dir: str | None = None #: Private API, internal use only. diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index 55f24f396cc..1938434b3a8 100644 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -1,17 +1,16 @@ -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsOptionError -import os import glob +import os -from setuptools.command.easy_install import easy_install -from setuptools import _normalization -from setuptools import _path -from setuptools import namespaces import setuptools +from setuptools import _normalization, _path, namespaces +from setuptools.command.easy_install import easy_install from ..unicode_utils import _read_utf8_with_fallback +from distutils import log +from distutils.errors import DistutilsOptionError +from distutils.util import convert_path + class develop(namespaces.DevelopInstaller, easy_install): """Set up package for development""" diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py index 2adc1c46f33..1db3fbf6bd6 100644 --- a/setuptools/command/dist_info.py +++ b/setuptools/command/dist_info.py @@ -6,14 +6,15 @@ import os import shutil from contextlib import contextmanager -from distutils import log -from distutils.core import Command from pathlib import Path from typing import cast from .. import _normalization from .egg_info import egg_info as egg_info_cls +from distutils import log +from distutils.core import Command + class dist_info(Command): """ diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 36114d40ed9..54d1e48449e 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -12,72 +12,72 @@ from __future__ import annotations -from glob import glob -from distutils.util import get_platform -from distutils.util import convert_path, subst_vars -from distutils.errors import ( - DistutilsArgError, - DistutilsOptionError, - DistutilsError, - DistutilsPlatformError, -) -from distutils import log, dir_util -from distutils.command.build_scripts import first_line_re -from distutils.command import install -import sys +import configparser +import contextlib +import io import os -import zipimport -import shutil -import tempfile -import zipfile -import re -import stat import random -import textwrap -import warnings +import re +import shlex +import shutil import site +import stat import struct -import contextlib import subprocess -import shlex -import io -import configparser +import sys import sysconfig - +import tempfile +import textwrap +import warnings +import zipfile +import zipimport +from collections.abc import Iterable +from glob import glob from sysconfig import get_path +from typing import TYPE_CHECKING -from setuptools import Command -from setuptools.sandbox import run_setup -from setuptools.command import setopt -from setuptools.archive_util import unpack_archive -from setuptools.package_index import ( - PackageIndex, - parse_requirement_arg, - URL_SCHEME, -) -from setuptools.command import bdist_egg, egg_info -from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning -from setuptools.wheel import Wheel +from jaraco.text import yield_lines + +import pkg_resources from pkg_resources import ( - normalize_path, - resource_string, - get_distribution, - find_distributions, - Environment, - Requirement, + DEVELOP_DIST, Distribution, - PathMetadata, - EggMetadata, - WorkingSet, DistributionNotFound, + EggMetadata, + Environment, + PathMetadata, + Requirement, VersionConflict, - DEVELOP_DIST, + WorkingSet, + find_distributions, + get_distribution, + normalize_path, + resource_string, ) -import pkg_resources -from ..compat import py39, py311 +from setuptools import Command +from setuptools.archive_util import unpack_archive +from setuptools.command import bdist_egg, egg_info, setopt +from setuptools.package_index import URL_SCHEME, PackageIndex, parse_requirement_arg +from setuptools.sandbox import run_setup +from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning +from setuptools.wheel import Wheel + from .._path import ensure_directory -from jaraco.text import yield_lines +from ..compat import py39, py311 + +from distutils import dir_util, log +from distutils.command import install +from distutils.command.build_scripts import first_line_re +from distutils.errors import ( + DistutilsArgError, + DistutilsError, + DistutilsOptionError, + DistutilsPlatformError, +) +from distutils.util import convert_path, get_platform, subst_vars +if TYPE_CHECKING: + from typing_extensions import Self # Turn on PEP440Warnings warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) @@ -2055,19 +2055,21 @@ def _sys_executable(cls): return os.environ.get('__PYVENV_LAUNCHER__', _default) @classmethod - def from_param(cls, param): + def from_param(cls, param: Self | str | Iterable[str] | None) -> Self: """ Construct a CommandSpec from a parameter to build_scripts, which may be None. """ if isinstance(param, cls): return param - if isinstance(param, list): + if isinstance(param, str): + return cls.from_string(param) + if isinstance(param, Iterable): return cls(param) if param is None: return cls.from_environment() - # otherwise, assume it's a string. - return cls.from_string(param) + # AttributeError to keep backwards compatibility, this should really be a TypeError though + raise AttributeError(f"Argument has an unsupported type {type(param)}") @classmethod def from_environment(cls): diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 49fd609b151..46852c1a948 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -12,8 +12,8 @@ from __future__ import annotations -import logging import io +import logging import os import shutil import traceback @@ -23,32 +23,14 @@ from itertools import chain, starmap from pathlib import Path from tempfile import TemporaryDirectory -from typing import ( - TYPE_CHECKING, - Iterable, - Iterator, - Mapping, - Protocol, - TypeVar, - cast, -) - -from .. import ( - Command, - _normalization, - _path, - errors, - namespaces, -) +from typing import TYPE_CHECKING, Iterable, Iterator, Mapping, Protocol, TypeVar, cast + +from .. import Command, _normalization, _path, errors, namespaces from .._path import StrPath from ..compat import py39 from ..discovery import find_package_path from ..dist import Distribution -from ..warnings import ( - InformationOnly, - SetuptoolsDeprecationWarning, - SetuptoolsWarning, -) +from ..warnings import InformationOnly, SetuptoolsDeprecationWarning, SetuptoolsWarning from .build import build as build_cls from .build_py import build_py as build_py_cls from .dist_info import dist_info as dist_info_cls @@ -153,6 +135,7 @@ def run(self): self._create_wheel_file(bdist_wheel) except Exception: traceback.print_exc() + # TODO: Fix false-positive [attr-defined] in typeshed project = self.distribution.name or self.distribution.get_name() _DebuggingTips.emit(project=project) raise @@ -466,8 +449,9 @@ def _create_file(self, relative_output: str, src_file: str, link=None): def _create_links(self, outputs, output_mapping): self.auxiliary_dir.mkdir(parents=True, exist_ok=True) link_type = "sym" if _can_symlink_files(self.auxiliary_dir) else "hard" - mappings = {self._normalize_output(k): v for k, v in output_mapping.items()} - mappings.pop(None, None) # remove files that are not relative to build_lib + normalised = ((self._normalize_output(k), v) for k, v in output_mapping.items()) + # remove files that are not relative to build_lib + mappings = {k: v for k, v in normalised if k is not None} for output in outputs: relative = self._normalize_output(output) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 30b62f5f2ef..09255a32400 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -2,34 +2,35 @@ Create a distribution's .egg-info directory and contents""" -from distutils.filelist import FileList as _FileList -from distutils.errors import DistutilsInternalError -from distutils.util import convert_path -from distutils import log -import distutils.errors -import distutils.filelist +import collections import functools import os import re import sys import time -import collections -from .._importlib import metadata -from .. import _entry_points, _normalization -from . import _requirestxt +import packaging +import packaging.requirements +import packaging.version +import setuptools.unicode_utils as unicode_utils from setuptools import Command -from setuptools.command.sdist import sdist -from setuptools.command.sdist import walk_revctrl -from setuptools.command.setopt import edit_config from setuptools.command import bdist_egg -import setuptools.unicode_utils as unicode_utils +from setuptools.command.sdist import sdist, walk_revctrl +from setuptools.command.setopt import edit_config from setuptools.glob import glob -import packaging +from .. import _entry_points, _normalization +from .._importlib import metadata from ..warnings import SetuptoolsDeprecationWarning +from . import _requirestxt +import distutils.errors +import distutils.filelist +from distutils import log +from distutils.errors import DistutilsInternalError +from distutils.filelist import FileList as _FileList +from distutils.util import convert_path PY_MAJOR = '{}.{}'.format(*sys.version_info) diff --git a/setuptools/command/install.py b/setuptools/command/install.py index f1ea2adf1d0..e2ec1abddec 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -1,17 +1,20 @@ from __future__ import annotations -from collections.abc import Callable -from distutils.errors import DistutilsArgError -import inspect import glob +import inspect import platform -import distutils.command.install as orig +from collections.abc import Callable from typing import Any, ClassVar, cast import setuptools + +from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning from .bdist_egg import bdist_egg as bdist_egg_cls +import distutils.command.install as orig +from distutils.errors import DistutilsArgError + # Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for # now. See https://github.com/pypa/setuptools/issues/199/ _install = orig.install @@ -20,6 +23,8 @@ class install(orig.install): """Use easy_install to install the package, w/dependencies""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + user_options = orig.install.user_options + [ ('old-and-unmanageable', None, "Try not to use this!"), ( diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py index a1d2e8184fc..be47254f008 100644 --- a/setuptools/command/install_egg_info.py +++ b/setuptools/command/install_egg_info.py @@ -1,11 +1,12 @@ -from distutils import log, dir_util import os -from setuptools import Command -from setuptools import namespaces +from setuptools import Command, namespaces from setuptools.archive_util import unpack_archive + from .._path import ensure_directory +from distutils import dir_util, log + class install_egg_info(namespaces.Installer, Command): """Install an .egg-info directory for the package""" diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index 3c77c6ebc66..292f07ab6e6 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -1,14 +1,20 @@ from __future__ import annotations + import os import sys from itertools import product, starmap -import distutils.command.install_lib as orig + from .._path import StrPath +from ..dist import Distribution + +import distutils.command.install_lib as orig class install_lib(orig.install_lib): """Don't add compiled flags to filenames of non-Python files""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): self.build() outfiles = self.install() @@ -103,6 +109,7 @@ def copy_tree( # Exclude namespace package __init__.py* files from the output from setuptools.archive_util import unpack_directory + from distutils import log outfiles: list[str] = [] diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index f44281b49b6..7b90611d1c8 100644 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -1,16 +1,20 @@ from __future__ import annotations -from distutils import log -import distutils.command.install_scripts as orig import os import sys from .._path import ensure_directory +from ..dist import Distribution + +import distutils.command.install_scripts as orig +from distutils import log class install_scripts(orig.install_scripts): """Do normal script install, plus any egg_info wrapper scripts""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def initialize_options(self): orig.install_scripts.initialize_options(self) self.no_ep = False @@ -29,6 +33,7 @@ def run(self) -> None: def _install_ep_scripts(self): # Delay import side-effects from pkg_resources import Distribution, PathMetadata + from . import easy_install as ei ei_cmd = self.get_finalized_command("egg_info") diff --git a/setuptools/command/register.py b/setuptools/command/register.py index beee9782e7b..93ef04aa0e2 100644 --- a/setuptools/command/register.py +++ b/setuptools/command/register.py @@ -1,12 +1,16 @@ -from distutils import log -import distutils.command.register as orig - from setuptools.errors import RemovedCommandError +from ..dist import Distribution + +import distutils.command.register as orig +from distutils import log + class register(orig.register): """Formerly used to register packages on PyPI.""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): msg = ( "The register command has been removed, use twine to upload " diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py index 064d7959ff6..dcdfafbcf73 100644 --- a/setuptools/command/rotate.py +++ b/setuptools/command/rotate.py @@ -1,13 +1,14 @@ from __future__ import annotations -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsOptionError import os import shutil from setuptools import Command +from distutils import log +from distutils.errors import DistutilsOptionError +from distutils.util import convert_path + class rotate(Command): """Delete older distributions""" diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index a834ba4a783..68afab89b4b 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -1,12 +1,16 @@ -from distutils import log -import distutils.command.sdist as orig -import os +from __future__ import annotations + import contextlib +import os from itertools import chain from .._importlib import metadata +from ..dist import Distribution from .build import _ORIGINAL_SUBCOMMANDS +import distutils.command.sdist as orig +from distutils import log + _default_revctrl = list @@ -43,7 +47,8 @@ class sdist(orig.sdist): ), ] - negative_opt = {} + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + negative_opt: dict[str, str] = {} README_EXTENSIONS = ['', '.rst', '.txt', '.md'] READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py index b78d845e60a..e351af22f0b 100644 --- a/setuptools/command/setopt.py +++ b/setuptools/command/setopt.py @@ -1,13 +1,14 @@ -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsOptionError -import distutils -import os import configparser +import os from .. import Command from ..unicode_utils import _cfg_read_utf8_with_fallback +import distutils +from distutils import log +from distutils.errors import DistutilsOptionError +from distutils.util import convert_path + __all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 1cca47cea9f..649b41fa30f 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,12 +1,15 @@ +from setuptools.dist import Distribution +from setuptools.errors import RemovedCommandError + from distutils import log from distutils.command import upload as orig -from setuptools.errors import RemovedCommandError - class upload(orig.upload): """Formerly used to upload packages to PyPI.""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): msg = ( "The upload command has been removed, use twine to upload " diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 32c9abd796f..3c2946cfc88 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -4,23 +4,23 @@ sites other than PyPi such as devpi). """ -from base64 import standard_b64encode -from distutils import log -from distutils.errors import DistutilsOptionError -import os -import zipfile -import tempfile -import shutil -import itertools import functools import http.client +import itertools +import os +import shutil +import tempfile import urllib.parse +import zipfile +from base64 import standard_b64encode from .._importlib import metadata from ..warnings import SetuptoolsDeprecationWarning - from .upload import upload +from distutils import log +from distutils.errors import DistutilsOptionError + def _encode(s): return s.encode('utf-8', 'surrogateescape') diff --git a/setuptools/compat/py310.py b/setuptools/compat/py310.py index cc875c004b6..b3912f8e02a 100644 --- a/setuptools/compat/py310.py +++ b/setuptools/compat/py310.py @@ -1,6 +1,5 @@ import sys - __all__ = ['tomllib'] diff --git a/setuptools/compat/py311.py b/setuptools/compat/py311.py index 5069c441c46..52b58af32a2 100644 --- a/setuptools/compat/py311.py +++ b/setuptools/compat/py311.py @@ -2,13 +2,14 @@ import shutil import sys -from typing import Any, Callable, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable if TYPE_CHECKING: - from _typeshed import StrOrBytesPath, ExcInfo + from _typeshed import ExcInfo, StrOrBytesPath + from typing_extensions import TypeAlias # Same as shutil._OnExcCallback from typeshed -_OnExcCallback = Callable[[Callable[..., Any], str, BaseException], object] +_OnExcCallback: TypeAlias = Callable[[Callable[..., Any], str, BaseException], object] def shutil_rmtree( diff --git a/setuptools/config/NOTICE b/setuptools/config/NOTICE new file mode 100644 index 00000000000..01864511b0f --- /dev/null +++ b/setuptools/config/NOTICE @@ -0,0 +1,10 @@ +The following files include code from opensource projects +(either as direct copies or modified versions): + +- `setuptools.schema.json`, `distutils.schema.json`: + - project: `validate-pyproject` - licensed under MPL-2.0 + (https://github.com/abravalheri/validate-pyproject): + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this file, + You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 6cc59d2b951..7b9c0b1a594 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -17,27 +17,24 @@ from inspect import cleandoc from itertools import chain from types import MappingProxyType -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Mapping, - Union, -) +from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Union + from .._path import StrPath from ..errors import RemovedConfigError from ..warnings import SetuptoolsWarning if TYPE_CHECKING: - from distutils.dist import _OptionsList + from typing_extensions import TypeAlias + from setuptools._importlib import metadata from setuptools.dist import Distribution + from distutils.dist import _OptionsList + EMPTY: Mapping = MappingProxyType({}) # Immutable dict-like -_ProjectReadmeValue = Union[str, Dict[str, str]] -_CorrespFn = Callable[["Distribution", Any, StrPath], None] -_Correspondence = Union[str, _CorrespFn] +_ProjectReadmeValue: TypeAlias = Union[str, Dict[str, str]] +_CorrespFn: TypeAlias = Callable[["Distribution", Any, StrPath], None] +_Correspondence: TypeAlias = Union[str, _CorrespFn] _logger = logging.getLogger(__name__) @@ -261,9 +258,10 @@ def _copy_command_options(pyproject: dict, dist: Distribution, filename: StrPath def _valid_command_options(cmdclass: Mapping = EMPTY) -> dict[str, set[str]]: - from .._importlib import metadata from setuptools.dist import Distribution + from .._importlib import metadata + valid_options = {"global": _normalise_cmd_options(Distribution.global_options)} unloaded_entry_points = metadata.entry_points(group='distutils.commands') @@ -278,6 +276,11 @@ def _valid_command_options(cmdclass: Mapping = EMPTY) -> dict[str, set[str]]: def _load_ep(ep: metadata.EntryPoint) -> tuple[str, type] | None: + if ep.value.startswith("wheel.bdist_wheel"): + # Ignore deprecated entrypoint from wheel and avoid warning pypa/wheel#631 + # TODO: remove check when `bdist_wheel` has been fully removed from pypa/wheel + return None + # Ignore all the errors try: return (ep.name, ep.load()) diff --git a/setuptools/config/_validate_pyproject/NOTICE b/setuptools/config/_validate_pyproject/NOTICE index 121ba5fd22a..74e8821fc84 100644 --- a/setuptools/config/_validate_pyproject/NOTICE +++ b/setuptools/config/_validate_pyproject/NOTICE @@ -1,7 +1,7 @@ The code contained in this directory was automatically generated using the following command: - python -m validate_pyproject.pre_compile --output-dir=setuptools/config/_validate_pyproject --enable-plugins setuptools distutils --very-verbose + python -m validate_pyproject.pre_compile --output-dir=setuptools/config/_validate_pyproject --enable-plugins setuptools distutils --very-verbose -t distutils=setuptools/config/distutils.schema.json -t setuptools=setuptools/config/setuptools.schema.json Please avoid changing it manually. diff --git a/setuptools/config/_validate_pyproject/error_reporting.py b/setuptools/config/_validate_pyproject/error_reporting.py index a6753604f5c..3591231c09f 100644 --- a/setuptools/config/_validate_pyproject/error_reporting.py +++ b/setuptools/config/_validate_pyproject/error_reporting.py @@ -6,7 +6,7 @@ import typing from contextlib import contextmanager from textwrap import indent, wrap -from typing import Any, Dict, Generator, Iterator, List, Optional, Sequence, Union, cast +from typing import Any, Dict, Generator, Iterator, List, Optional, Sequence, Union from .fastjsonschema_exceptions import JsonSchemaValueException @@ -316,9 +316,7 @@ def _label(self, path: Sequence[str]) -> str: def _value(self, value: Any, path: Sequence[str]) -> str: if path[-1] == "type" and not self._is_property(path): type_ = self._jargon(value) - return ( - f"[{', '.join(type_)}]" if isinstance(value, list) else cast(str, type_) - ) + return f"[{', '.join(type_)}]" if isinstance(type_, list) else type_ return repr(value) def _inline_attrs(self, schema: dict, path: Sequence[str]) -> Iterator[str]: diff --git a/setuptools/config/_validate_pyproject/fastjsonschema_validations.py b/setuptools/config/_validate_pyproject/fastjsonschema_validations.py index 1c58a55ea84..7e403af4b77 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.19.1" +VERSION = "2.20.0" from decimal import Decimal import re from .fastjsonschema_exceptions import JsonSchemaValueException @@ -31,7 +31,7 @@ def validate(data, custom_formats={}, name_prefix=None): def validate_https___packaging_python_org_en_latest_specifications_declaring_build_dependencies(data, custom_formats={}, name_prefix=None): if not isinstance(data, (dict)): - raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/', 'title': 'Data structure for ``pyproject.toml`` files', '$$description': ['File format containing build-time configurations for the Python ecosystem. ', ':pep:`517` initially defined a build-system independent format for source trees', 'which was complemented by :pep:`518` to provide a way of specifying dependencies ', 'for building Python projects.', 'Please notice the ``project`` table (as initially defined in :pep:`621`) is not included', 'in this schema and should be considered separately.'], 'type': 'object', 'additionalProperties': False, 'properties': {'build-system': {'type': 'object', 'description': 'Table used to store build-related data', 'additionalProperties': False, 'properties': {'requires': {'type': 'array', '$$description': ['List of dependencies in the :pep:`508` format required to execute the build', 'system. Please notice that the resulting dependency graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}}, 'build-backend': {'type': 'string', 'description': 'Python object that will be used to perform the build according to :pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type': 'array', '$$description': ['List of directories to be prepended to ``sys.path`` when loading the', 'back-end, and running its hooks'], 'items': {'type': 'string', '$comment': 'Should be a path (TODO: enforce it with format?)'}}}, 'required': ['requires']}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/', 'title': 'Data structure for ``pyproject.toml`` files', '$$description': ['File format containing build-time configurations for the Python ecosystem. ', ':pep:`517` initially defined a build-system independent format for source trees', 'which was complemented by :pep:`518` to provide a way of specifying dependencies ', 'for building Python projects.', 'Please notice the ``project`` table (as initially defined in :pep:`621`) is not included', 'in this schema and should be considered separately.'], 'type': 'object', 'additionalProperties': False, 'properties': {'build-system': {'type': 'object', 'description': 'Table used to store build-related data', 'additionalProperties': False, 'properties': {'requires': {'type': 'array', '$$description': ['List of dependencies in the :pep:`508` format required to execute the build', 'system. Please notice that the resulting dependency graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}}, 'build-backend': {'type': 'string', 'description': 'Python object that will be used to perform the build according to :pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type': 'array', '$$description': ['List of directories to be prepended to ``sys.path`` when loading the', 'back-end, and running its hooks'], 'items': {'type': 'string', '$comment': 'Should be a path (TODO: enforce it with format?)'}}}, 'required': ['requires']}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}}, rule='type') data_is_dict = isinstance(data, dict) if data_is_dict: data_keys = set(data.keys()) @@ -86,7 +86,7 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_bui data_keys.remove("tool") data__tool = data["tool"] if not isinstance(data__tool, (dict)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".tool must be object", value=data__tool, name="" + (name_prefix or "data") + ".tool", definition={'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".tool must be object", value=data__tool, name="" + (name_prefix or "data") + ".tool", definition={'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}, rule='type') data__tool_is_dict = isinstance(data__tool, dict) if data__tool_is_dict: data__tool_keys = set(data__tool.keys()) @@ -99,12 +99,12 @@ def validate_https___packaging_python_org_en_latest_specifications_declaring_bui data__tool__setuptools = data__tool["setuptools"] validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_html(data__tool__setuptools, custom_formats, (name_prefix or "data") + ".tool.setuptools") if data_keys: - raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/', 'title': 'Data structure for ``pyproject.toml`` files', '$$description': ['File format containing build-time configurations for the Python ecosystem. ', ':pep:`517` initially defined a build-system independent format for source trees', 'which was complemented by :pep:`518` to provide a way of specifying dependencies ', 'for building Python projects.', 'Please notice the ``project`` table (as initially defined in :pep:`621`) is not included', 'in this schema and should be considered separately.'], 'type': 'object', 'additionalProperties': False, 'properties': {'build-system': {'type': 'object', 'description': 'Table used to store build-related data', 'additionalProperties': False, 'properties': {'requires': {'type': 'array', '$$description': ['List of dependencies in the :pep:`508` format required to execute the build', 'system. Please notice that the resulting dependency graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}}, 'build-backend': {'type': 'string', 'description': 'Python object that will be used to perform the build according to :pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type': 'array', '$$description': ['List of directories to be prepended to ``sys.path`` when loading the', 'back-end, and running its hooks'], 'items': {'type': 'string', '$comment': 'Should be a path (TODO: enforce it with format?)'}}}, 'required': ['requires']}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}}, rule='additionalProperties') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/', 'title': 'Data structure for ``pyproject.toml`` files', '$$description': ['File format containing build-time configurations for the Python ecosystem. ', ':pep:`517` initially defined a build-system independent format for source trees', 'which was complemented by :pep:`518` to provide a way of specifying dependencies ', 'for building Python projects.', 'Please notice the ``project`` table (as initially defined in :pep:`621`) is not included', 'in this schema and should be considered separately.'], 'type': 'object', 'additionalProperties': False, 'properties': {'build-system': {'type': 'object', 'description': 'Table used to store build-related data', 'additionalProperties': False, 'properties': {'requires': {'type': 'array', '$$description': ['List of dependencies in the :pep:`508` format required to execute the build', 'system. Please notice that the resulting dependency graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}}, 'build-backend': {'type': 'string', 'description': 'Python object that will be used to perform the build according to :pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type': 'array', '$$description': ['List of directories to be prepended to ``sys.path`` when loading the', 'back-end, and running its hooks'], 'items': {'type': 'string', '$comment': 'Should be a path (TODO: enforce it with format?)'}}}, 'required': ['requires']}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html', 'title': '``tool.distutils`` table', '$$description': ['**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``', 'subtables to configure arguments for ``distutils`` commands.', 'Originally, ``distutils`` allowed developers to configure arguments for', '``setup.py`` commands via `distutils configuration files', '`_.', 'See also `the old Python docs _`.'], 'type': 'object', 'properties': {'global': {'type': 'object', 'description': 'Global options applied to all ``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}}, '$comment': 'TODO: Is there a practical way of making this schema more specific?'}, 'setuptools': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$ref': '#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'$ref': '#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]}, 'classifiers': {'$ref': '#/definitions/file-directive'}, 'description': {'$ref': '#/definitions/file-directive'}, 'entry-points': {'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref': '#/definitions/file-directive-for-dependencies'}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'$ref': '#/definitions/file-directive-for-dependencies'}}}, 'readme': {'type': 'object', 'anyOf': [{'$ref': '#/definitions/file-directive'}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'$ref': '#/definitions/file-directive/properties/file'}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://packaging.python.org/en/latest/specifications/pyproject-toml/', 'title': 'Package metadata stored in the ``project`` table', '$$description': ['Data structure for the **project** table inside ``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The name (primary identifier) of the project. MUST be statically defined.', 'format': 'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The version of the project as supported by :pep:`440`.', 'format': 'pep440'}, 'description': {'type': 'string', '$$description': ['The `summary description of the project', '`_']}, 'readme': {'$$description': ['`Full/detailed description of the project in the form of a README', '`_', "with meaning similar to the one defined in `core metadata's Description", '`_'], 'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file (UTF-8) containing the full description', 'of the project. If the file path ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the content-type is respectively', '``text/markdown`` or ``text/x-rst``']}, {'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to a text file containing the full description', 'of the project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', 'description': 'Full text describing the project.'}}, 'required': ['text']}]}, {'properties': {'content-type': {'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full description', '(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type': 'string', 'format': 'pep508-versionspec', '$$description': ['`The Python version requirements of the project', '`_.']}, 'license': {'description': '`Project license `_.', 'oneOf': [{'properties': {'file': {'type': 'string', '$$description': ['Relative path to the file (UTF-8) which contains the license for the', 'project.']}}, 'required': ['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The license of the project whose meaning is that of the', '`License field from the core metadata', '`_.']}}, 'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'authors' of the project.", 'The exact meaning is open to interpretation (e.g. original or primary authors,', 'current maintainers, or owners of the package).']}, 'maintainers': {'type': 'array', 'items': {'$ref': '#/definitions/author'}, '$$description': ["The people or organizations considered to be the 'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning is open to interpretation.']}, 'keywords': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of keywords to assist searching for the distribution in a larger catalog.'}, 'classifiers': {'type': 'array', 'items': {'type': 'string', 'format': 'trove-classifier', 'description': '`PyPI classifier `_.'}, '$$description': ['`Trove classifiers `_', 'which apply to the project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with the project in the form ``label => value``.', 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}}, 'scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create command-line wrappers for the given', '`entry points `_.']}, 'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description': ['Instruct the installer to create GUI wrappers for the given', '`entry points `_.', 'The difference between ``scripts`` and ``gui-scripts`` is only relevant in', 'Windows.']}, 'entry-points': {'$$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': {'^.+$': {'$ref': '#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array', 'description': 'Project (mandatory) dependencies.', 'items': {'$ref': '#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object', 'description': 'Optional dependency for the project', 'propertyNames': {'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref': '#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description': ['Specifies which fields are intentionally unspecified and expected to be', 'dynamically provided by build tools'], 'items': {'enum': ['version', 'description', 'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties': False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core metadata specification lists a field as "Required", then', ' the metadata MUST specify the field statically or list it in dynamic', 'In turn, `core metadata`_ defines:', ' The required fields are: Metadata-Version, Name, Version.', ' All the other fields are optional.', 'Since ``Metadata-Version`` is defined by the build back-end, ``name`` and', '``version`` are the only mandatory information in ``pyproject.toml``.', '.. _core metadata: https://packaging.python.org/specifications/core-metadata/']}, 'then': {'required': ['version'], '$$description': ['version should be statically defined in the ``version`` field']}, 'definitions': {'author': {'$id': '#/definitions/author', 'title': 'Author or Maintainer', '$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object', 'additionalProperties': False, 'properties': {'name': {'type': 'string', '$$description': ['MUST be a valid email name, i.e. whatever can be put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string', 'format': 'idn-email', 'description': 'MUST be a valid email address'}}}, 'entry-point-group': {'$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/'}}}, 'dependency': {'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string', 'description': 'Project dependency specification according to PEP 508', 'format': 'pep508'}}}}, rule='additionalProperties') return data def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_html(data, custom_formats={}, name_prefix=None): if not isinstance(data, (dict)): - raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, {'$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']}]}, 'classifiers': {'$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']}, 'description': {'$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']}, 'entry-points': {'$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']}, 'dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}}}, 'readme': {'type': 'object', 'anyOf': [{'$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']}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$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']}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must be object", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, {'$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']}]}, 'classifiers': {'$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']}, 'description': {'$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']}, 'entry-points': {'$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']}, 'dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}}}, 'readme': {'type': 'object', 'anyOf': [{'$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']}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$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']}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}, rule='type') data_is_dict = isinstance(data, dict) if data_is_dict: data_keys = set(data.keys()) @@ -181,7 +181,7 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm if data__packages_one_of_count1 < 2: try: if not isinstance(data__packages, (list, tuple)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".packages must be array", value=data__packages, name="" + (name_prefix or "data") + ".packages", definition={'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".packages must be array", value=data__packages, name="" + (name_prefix or "data") + ".packages", definition={'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, rule='type') data__packages_is_list = isinstance(data__packages, (list, tuple)) if data__packages_is_list: data__packages_len = len(data__packages) @@ -195,12 +195,12 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm data__packages_one_of_count1 += 1 except JsonSchemaValueException: pass if data__packages_one_of_count1 != 1: - raise JsonSchemaValueException("" + (name_prefix or "data") + ".packages must be valid exactly by one definition" + (" (" + str(data__packages_one_of_count1) + " matches found)"), value=data__packages, name="" + (name_prefix or "data") + ".packages", definition={'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, rule='oneOf') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".packages must be valid exactly by one definition" + (" (" + str(data__packages_one_of_count1) + " matches found)"), value=data__packages, name="" + (name_prefix or "data") + ".packages", definition={'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, rule='oneOf') if "package-dir" in data_keys: data_keys.remove("package-dir") data__packagedir = data["package-dir"] if not isinstance(data__packagedir, (dict)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must be object", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must be object", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='type') data__packagedir_is_dict = isinstance(data__packagedir, dict) if data__packagedir_is_dict: data__packagedir_keys = set(data__packagedir.keys()) @@ -211,7 +211,7 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm if not isinstance(data__packagedir_val, (str)): raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir.{data__packagedir_key}".format(**locals()) + " must be string", value=data__packagedir_val, name="" + (name_prefix or "data") + ".package-dir.{data__packagedir_key}".format(**locals()) + "", definition={'type': 'string'}, rule='type') if data__packagedir_keys: - raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must not contain "+str(data__packagedir_keys)+" properties", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='additionalProperties') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must not contain "+str(data__packagedir_keys)+" properties", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='additionalProperties') data__packagedir_len = len(data__packagedir) if data__packagedir_len != 0: data__packagedir_property_names = True @@ -230,11 +230,11 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm data__packagedir_key_any_of_count2 += 1 except JsonSchemaValueException: pass if not data__packagedir_key_any_of_count2: - raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir cannot be validated by any definition", value=data__packagedir_key, name="" + (name_prefix or "data") + ".package-dir", definition={'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, rule='anyOf') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir cannot be validated by any definition", value=data__packagedir_key, name="" + (name_prefix or "data") + ".package-dir", definition={'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, rule='anyOf') except JsonSchemaValueException: data__packagedir_property_names = False if not data__packagedir_property_names: - raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must be named by propertyName definition", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='propertyNames') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".package-dir must be named by propertyName definition", value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir", definition={'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, rule='propertyNames') if "package-data" in data_keys: data_keys.remove("package-data") data__packagedata = data["package-data"] @@ -342,30 +342,30 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm data_keys.remove("namespace-packages") data__namespacepackages = data["namespace-packages"] if not isinstance(data__namespacepackages, (list, tuple)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages must be array", value=data__namespacepackages, name="" + (name_prefix or "data") + ".namespace-packages", definition={'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages must be array", value=data__namespacepackages, name="" + (name_prefix or "data") + ".namespace-packages", definition={'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, rule='type') data__namespacepackages_is_list = isinstance(data__namespacepackages, (list, tuple)) if data__namespacepackages_is_list: data__namespacepackages_len = len(data__namespacepackages) for data__namespacepackages_x, data__namespacepackages_item in enumerate(data__namespacepackages): if not isinstance(data__namespacepackages_item, (str)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + " must be string", value=data__namespacepackages_item, name="" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + " must be string", value=data__namespacepackages_item, name="" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='type') if isinstance(data__namespacepackages_item, str): - if not custom_formats["python-module-name"](data__namespacepackages_item): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + " must be python-module-name", value=data__namespacepackages_item, name="" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='format') + if not custom_formats["python-module-name-relaxed"](data__namespacepackages_item): + raise JsonSchemaValueException("" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + " must be python-module-name-relaxed", value=data__namespacepackages_item, name="" + (name_prefix or "data") + ".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='format') if "py-modules" in data_keys: data_keys.remove("py-modules") data__pymodules = data["py-modules"] if not isinstance(data__pymodules, (list, tuple)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules must be array", value=data__pymodules, name="" + (name_prefix or "data") + ".py-modules", definition={'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules must be array", value=data__pymodules, name="" + (name_prefix or "data") + ".py-modules", definition={'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, rule='type') data__pymodules_is_list = isinstance(data__pymodules, (list, tuple)) if data__pymodules_is_list: data__pymodules_len = len(data__pymodules) for data__pymodules_x, data__pymodules_item in enumerate(data__pymodules): if not isinstance(data__pymodules_item, (str)): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + " must be string", value=data__pymodules_item, name="" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + " must be string", value=data__pymodules_item, name="" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='type') if isinstance(data__pymodules_item, str): - if not custom_formats["python-module-name"](data__pymodules_item): - raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + " must be python-module-name", value=data__pymodules_item, name="" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='format') + if not custom_formats["python-module-name-relaxed"](data__pymodules_item): + raise JsonSchemaValueException("" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + " must be python-module-name-relaxed", value=data__pymodules_item, name="" + (name_prefix or "data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='format') if "data-files" in data_keys: data_keys.remove("data-files") data__datafiles = data["data-files"] @@ -524,7 +524,7 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm if data__dynamic_keys: raise JsonSchemaValueException("" + (name_prefix or "data") + ".dynamic must not contain "+str(data__dynamic_keys)+" properties", value=data__dynamic, name="" + (name_prefix or "data") + ".dynamic", definition={'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, {'$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']}]}, 'classifiers': {'$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']}, 'description': {'$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']}, 'entry-points': {'$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']}, 'dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}}}, 'readme': {'type': 'object', 'anyOf': [{'$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']}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'additionalProperties': False}], 'required': ['file']}}}, rule='additionalProperties') if data_keys: - raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, {'$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']}]}, 'classifiers': {'$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']}, 'description': {'$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']}, 'entry-points': {'$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']}, 'dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}}}, 'readme': {'type': 'object', 'anyOf': [{'$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']}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$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']}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}, rule='additionalProperties') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must not contain "+str(data_keys)+" properties", value=data, name="" + (name_prefix or "data") + "", definition={'$schema': 'http://json-schema.org/draft-07/schema#', '$id': 'https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html', 'title': '``tool.setuptools`` table', '$$description': ['``setuptools``-specific configurations that can be set by users that require', 'customization.', 'These configurations are completely optional and probably can be skipped when', 'creating simple packages. They are equivalent to some of the `Keywords', '`_', 'used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.', 'It considers only ``setuptools`` `parameters', '`_', 'that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``', 'and ``setup_requires`` (incompatible with modern workflows/standards).'], 'type': 'object', 'additionalProperties': False, 'properties': {'platforms': {'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description': ['Package and virtual package names contained within this package', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this package renders obsolete', '**(not supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe': {'$$description': ['Whether the project can be safely installed and run from a zip file.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'boolean'}, 'script-files': {'$$description': ['Legacy way of defining scripts (entry-points are preferred).', 'Equivalent to the ``script`` keyword in ``setup.py``', '(it was renamed to avoid confusion with entry-point based ``project.scripts``', 'defined in :pep:`621`).', '**DISCOURAGED**: generic script wrappers are tricky and may not work properly.', 'Whenever possible, please use ``project.scripts`` instead.'], 'type': 'array', 'items': {'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be removed?'}, 'eager-resources': {'$$description': ['Resources that should be extracted together, if any of them is needed,', 'or if any C extensions included in the project are imported.', '**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and', '``setup.py install`` in the context of ``eggs`` (**DEPRECATED**).'], 'type': 'array', 'items': {'type': 'string'}}, 'packages': {'$$description': ['Packages that should be included in the distribution.', 'It can be given either as a list of package identifiers', 'or as a ``dict``-like structure with a single key ``find``', 'which corresponds to a dynamic call to', '``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is associated with a nested ``dict``-like structure that can', 'contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array of Python package identifiers', 'type': 'array', 'items': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}}, {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir': {'$$description': [':class:`dict`-like structure mapping from package names to directories where their', 'code can be found.', 'The empty string (as key) means that all packages are contained inside', 'the given directory will be included in the distribution.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from package names to lists of glob patterns.', 'Usually this option is not needed when using ``include-package-data = true``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'include-package-data': {'$$description': ['Automatically include any data files inside the package directories', 'that are specified by ``MANIFEST.in``', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from package names to lists of glob patterns that should be excluded', 'For more information on how to include data files, check ``setuptools`` `docs', '`_.'], 'type': 'object', 'additionalProperties': False, 'propertyNames': {'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html', 'description': '**DEPRECATED**: use implicit namespaces instead (:pep:`420`).'}, 'py-modules': {'description': 'Modules that setuptools will manipulate', 'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name-relaxed'}, '$comment': 'TODO: clarify the relationship with ``packages``'}, 'data-files': {'$$description': ['``dict``-like structure where each key represents a directory and', 'the value is a list of glob patterns that should be installed in them.', '**DISCOURAGED**: please notice this might not work as expected with wheels.', 'Whenever possible, consider using data files inside the package directories', '(or create a new namespace package that only contains data files).', 'See `data files support', '`_.'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style command names to ``setuptools.Command`` subclasses', 'which in turn should be represented by strings with a qualified class name', '(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd = "pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a directly defined at the top-level of the', 'containing module (no class nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type': 'string', 'format': 'python-qualified-identifier'}}}, 'license-files': {'type': 'array', 'items': {'type': 'string'}, '$$description': ['**PROVISIONAL**: list of glob patterns for all license files being distributed.', '(likely to become standard with :pep:`639`).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?'}, 'dynamic': {'type': 'object', 'description': 'Instructions for loading :pep:`621`-related metadata dynamically', 'additionalProperties': False, 'properties': {'version': {'$$description': ['A version dynamically loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure the given file or attribute respects :pep:`440`.', 'Also ensure to set ``project.dynamic`` accordingly.'], 'oneOf': [{'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, {'$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']}]}, 'classifiers': {'$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']}, 'description': {'$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']}, 'entry-points': {'$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']}, 'dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}, 'optional-dependencies': {'type': 'object', 'propertyNames': {'type': 'string', 'format': 'pep508-identifier'}, 'additionalProperties': False, 'patternProperties': {'.+': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$ref': '#/definitions/file-directive'}]}}}, 'readme': {'type': 'object', 'anyOf': [{'$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']}, {'type': 'object', 'properties': {'content-type': {'type': 'string'}, 'file': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'additionalProperties': False}], 'required': ['file']}}}}, 'definitions': {'package-name': {'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, 'file-directive': {'$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']}, 'file-directive-for-dependencies': {'title': "'file:' directive for dependencies", 'allOf': [{'$$description': ['**BETA**: subset of the ``requirements.txt`` format', 'without ``pip`` flags and options', '(one :pep:`508`-compliant string per line,', 'lines that are blank or start with ``#`` are excluded).', 'See `dynamic metadata', '`_.']}, {'$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']}]}, 'attr-directive': {'title': "'attr:' directive", '$id': '#/definitions/attr-directive', '$$description': ['Value is read from a module attribute. Supports callables and iterables;', 'unsupported types are cast via ``str()``'], 'type': 'object', 'additionalProperties': False, 'properties': {'attr': {'type': 'string', 'format': 'python-qualified-identifier'}}, 'required': ['attr']}, 'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:' directive", 'type': 'object', 'additionalProperties': False, 'properties': {'find': {'type': 'object', '$$description': ['Dynamic `package discovery', '`_.'], 'additionalProperties': False, 'properties': {'where': {'description': 'Directories to be searched for packages (Unix-style relative path)', 'type': 'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array', '$$description': ['Exclude packages that match the values listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict the found packages to just the ones listed in this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}}, 'namespaces': {'type': 'boolean', '$$description': ['When ``True``, directories without a ``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit namespaces']}}}}}}}, rule='additionalProperties') return data def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_html__definitions_file_directive_properties_file(data, custom_formats={}, name_prefix=None): @@ -673,15 +673,15 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_html__definitions_package_name(data, custom_formats={}, name_prefix=None): if not isinstance(data, (str)): - raise JsonSchemaValueException("" + (name_prefix or "data") + " must be string", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must be string", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, rule='type') data_any_of_count9 = 0 if not data_any_of_count9: try: if not isinstance(data, (str)): - raise JsonSchemaValueException("" + (name_prefix or "data") + " must be string", value=data, name="" + (name_prefix or "data") + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='type') + raise JsonSchemaValueException("" + (name_prefix or "data") + " must be string", value=data, name="" + (name_prefix or "data") + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='type') if isinstance(data, str): - if not custom_formats["python-module-name"](data): - raise JsonSchemaValueException("" + (name_prefix or "data") + " must be python-module-name", value=data, name="" + (name_prefix or "data") + "", definition={'type': 'string', 'format': 'python-module-name'}, rule='format') + if not custom_formats["python-module-name-relaxed"](data): + raise JsonSchemaValueException("" + (name_prefix or "data") + " must be python-module-name-relaxed", value=data, name="" + (name_prefix or "data") + "", definition={'type': 'string', 'format': 'python-module-name-relaxed'}, rule='format') data_any_of_count9 += 1 except JsonSchemaValueException: pass if not data_any_of_count9: @@ -694,7 +694,7 @@ def validate_https___setuptools_pypa_io_en_latest_userguide_pyproject_config_htm data_any_of_count9 += 1 except JsonSchemaValueException: pass if not data_any_of_count9: - raise JsonSchemaValueException("" + (name_prefix or "data") + " cannot be validated by any definition", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, rule='anyOf') + raise JsonSchemaValueException("" + (name_prefix or "data") + " cannot be validated by any definition", value=data, name="" + (name_prefix or "data") + "", definition={'$id': '#/definitions/package-name', 'title': 'Valid package name', 'description': 'Valid package name (importable or :pep:`561`).', 'type': 'string', 'anyOf': [{'type': 'string', 'format': 'python-module-name-relaxed'}, {'type': 'string', 'format': 'pep561-stub-name'}]}, rule='anyOf') return data def validate_https___setuptools_pypa_io_en_latest_deprecated_distutils_configfile_html(data, custom_formats={}, name_prefix=None): diff --git a/setuptools/config/_validate_pyproject/formats.py b/setuptools/config/_validate_pyproject/formats.py index aacf4092b02..153b1f0b27f 100644 --- a/setuptools/config/_validate_pyproject/formats.py +++ b/setuptools/config/_validate_pyproject/formats.py @@ -83,7 +83,9 @@ def pep508_identifier(name: str) -> bool: from packaging import requirements as _req except ImportError: # pragma: no cover # let's try setuptools vendored version - from setuptools._vendor.packaging import requirements as _req # type: ignore + from setuptools._vendor.packaging import ( # type: ignore[no-redef] + requirements as _req, + ) def pep508(value: str) -> bool: """See :ref:`PyPA's dependency specifiers ` @@ -91,9 +93,9 @@ def pep508(value: str) -> bool: """ try: _req.Requirement(value) + return True except _req.InvalidRequirement: return False - return True except ImportError: # pragma: no cover _logger.warning( @@ -289,6 +291,25 @@ def python_module_name(value: str) -> bool: return python_qualified_identifier(value) +def python_module_name_relaxed(value: str) -> bool: + """Similar to :obj:`python_module_name`, but relaxed to also accept + dash characters (``-``) and cover special cases like ``pip-run``. + + It is recommended, however, that beginners avoid dash characters, + as they require advanced knowledge about Python internals. + + The following are disallowed: + + * names starting/ending in dashes, + * names ending in ``-stubs`` (potentially collide with :obj:`pep561_stub_name`). + """ + if value.startswith("-") or value.endswith("-"): + return False + if value.endswith("-stubs"): + return False # Avoid collision with PEP 561 + return python_module_name(value.replace("-", "_")) + + def python_entrypoint_group(value: str) -> bool: """See ``Data model > group`` in the :ref:`PyPA's entry-points specification `. diff --git a/setuptools/config/distutils.schema.json b/setuptools/config/distutils.schema.json new file mode 100644 index 00000000000..93cd2e868ac --- /dev/null +++ b/setuptools/config/distutils.schema.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "$id": "https://setuptools.pypa.io/en/latest/deprecated/distutils/configfile.html", + "title": "``tool.distutils`` table", + "$$description": [ + "**EXPERIMENTAL** (NOT OFFICIALLY SUPPORTED): Use ``tool.distutils``", + "subtables to configure arguments for ``distutils`` commands.", + "Originally, ``distutils`` allowed developers to configure arguments for", + "``setup.py`` commands via `distutils configuration files", + "`_.", + "See also `the old Python docs _`." + ], + + "type": "object", + "properties": { + "global": { + "type": "object", + "description": "Global options applied to all ``distutils`` commands" + } + }, + "patternProperties": { + ".+": {"type": "object"} + }, + "$comment": "TODO: Is there a practical way of making this schema more specific?" +} diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index de6339fa424..dfee1c5d375 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -25,28 +25,20 @@ import os import pathlib import sys -from glob import iglob from configparser import ConfigParser +from glob import iglob from importlib.machinery import ModuleSpec, all_suffixes from itertools import chain -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Iterable, - Iterator, - Mapping, - TypeVar, -) from pathlib import Path from types import ModuleType +from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Mapping, TypeVar -from distutils.errors import DistutilsOptionError - -from .._path import same_path as _same_path, StrPath +from .._path import StrPath, same_path as _same_path from ..discovery import find_package_path from ..warnings import SetuptoolsWarning +from distutils.errors import DistutilsOptionError + if TYPE_CHECKING: from setuptools.dist import Distribution @@ -287,8 +279,9 @@ def find_packages( :rtype: list """ + from more_itertools import always_iterable, unique_everseen + from setuptools.discovery import construct_package_dir - from more_itertools import unique_everseen, always_iterable if namespaces: from setuptools.discovery import PEP420PackageFinder as PackageFinder diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index a83e43bb356..943b9f5a00a 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -21,13 +21,13 @@ from ..errors import FileError, InvalidConfigError from ..warnings import SetuptoolsWarning from . import expand as _expand -from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _MissingDynamic -from ._apply_pyprojecttoml import apply as _apply +from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _MissingDynamic, apply as _apply if TYPE_CHECKING: - from setuptools.dist import Distribution from typing_extensions import Self + from setuptools.dist import Distribution + _logger = logging.getLogger(__name__) @@ -121,7 +121,7 @@ def read_configuration( # the default would be an improvement. # `ini2toml` backfills include_package_data=False when nothing is explicitly given, # therefore setting a default here is backwards compatible. - if dist and getattr(dist, "include_package_data", None) is not None: + if dist and dist.include_package_data is not None: setuptools_table.setdefault("include-package-data", dist.include_package_data) else: setuptools_table.setdefault("include-package-data", True) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 5a3b7478a08..072b7870629 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -15,35 +15,37 @@ import functools import os from collections import defaultdict -from functools import partial -from functools import wraps +from functools import partial, wraps from typing import ( TYPE_CHECKING, - Callable, Any, + Callable, Dict, Generic, Iterable, Iterator, + List, Tuple, TypeVar, Union, + cast, ) -from .._path import StrPath -from ..errors import FileError, OptionError from packaging.markers import default_environment as marker_env from packaging.requirements import InvalidRequirement, Requirement from packaging.specifiers import SpecifierSet from packaging.version import InvalidVersion, Version + +from .._path import StrPath +from ..errors import FileError, OptionError from ..warnings import SetuptoolsDeprecationWarning from . import expand if TYPE_CHECKING: - from distutils.dist import DistributionMetadata - from setuptools.dist import Distribution + from distutils.dist import DistributionMetadata + SingleCommandOptions = Dict["str", Tuple["str", Any]] """Dict that associate the name of the options of a particular command to a tuple. The first element of the tuple indicates the origin of the option value @@ -108,7 +110,8 @@ def _apply( filenames = [*other_files, filepath] try: - _Distribution.parse_config_files(dist, filenames=filenames) # type: ignore[arg-type] # TODO: fix in distutils stubs + # TODO: Temporary cast until mypy 1.12 is released with upstream fixes from typeshed + _Distribution.parse_config_files(dist, filenames=cast(List[str], filenames)) handlers = parse_configuration( dist, dist.command_options, ignore_option_errors=ignore_option_errors ) diff --git a/setuptools/config/setuptools.schema.json b/setuptools/config/setuptools.schema.json new file mode 100644 index 00000000000..50ee6217eea --- /dev/null +++ b/setuptools/config/setuptools.schema.json @@ -0,0 +1,352 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "$id": "https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html", + "title": "``tool.setuptools`` table", + "$$description": [ + "``setuptools``-specific configurations that can be set by users that require", + "customization.", + "These configurations are completely optional and probably can be skipped when", + "creating simple packages. They are equivalent to some of the `Keywords", + "`_", + "used by the ``setup.py`` file, and can be set via the ``tool.setuptools`` table.", + "It considers only ``setuptools`` `parameters", + "`_", + "that are not covered by :pep:`621`; and intentionally excludes ``dependency_links``", + "and ``setup_requires`` (incompatible with modern workflows/standards)." + ], + + "type": "object", + "additionalProperties": false, + "properties": { + "platforms": { + "type": "array", + "items": {"type": "string"} + }, + "provides": { + "$$description": [ + "Package and virtual package names contained within this package", + "**(not supported by pip)**" + ], + "type": "array", + "items": {"type": "string", "format": "pep508-identifier"} + }, + "obsoletes": { + "$$description": [ + "Packages which this package renders obsolete", + "**(not supported by pip)**" + ], + "type": "array", + "items": {"type": "string", "format": "pep508-identifier"} + }, + "zip-safe": { + "$$description": [ + "Whether the project can be safely installed and run from a zip file.", + "**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and", + "``setup.py install`` in the context of ``eggs`` (**DEPRECATED**)." + ], + "type": "boolean" + }, + "script-files": { + "$$description": [ + "Legacy way of defining scripts (entry-points are preferred).", + "Equivalent to the ``script`` keyword in ``setup.py``", + "(it was renamed to avoid confusion with entry-point based ``project.scripts``", + "defined in :pep:`621`).", + "**DISCOURAGED**: generic script wrappers are tricky and may not work properly.", + "Whenever possible, please use ``project.scripts`` instead." + ], + "type": "array", + "items": {"type": "string"}, + "$comment": "TODO: is this field deprecated/should be removed?" + }, + "eager-resources": { + "$$description": [ + "Resources that should be extracted together, if any of them is needed,", + "or if any C extensions included in the project are imported.", + "**OBSOLETE**: only relevant for ``pkg_resources``, ``easy_install`` and", + "``setup.py install`` in the context of ``eggs`` (**DEPRECATED**)." + ], + "type": "array", + "items": {"type": "string"} + }, + "packages": { + "$$description": [ + "Packages that should be included in the distribution.", + "It can be given either as a list of package identifiers", + "or as a ``dict``-like structure with a single key ``find``", + "which corresponds to a dynamic call to", + "``setuptools.config.expand.find_packages`` function.", + "The ``find`` key is associated with a nested ``dict``-like structure that can", + "contain ``where``, ``include``, ``exclude`` and ``namespaces`` keys,", + "mimicking the keyword arguments of the associated function." + ], + "oneOf": [ + { + "title": "Array of Python package identifiers", + "type": "array", + "items": {"$ref": "#/definitions/package-name"} + }, + {"$ref": "#/definitions/find-directive"} + ] + }, + "package-dir": { + "$$description": [ + ":class:`dict`-like structure mapping from package names to directories where their", + "code can be found.", + "The empty string (as key) means that all packages are contained inside", + "the given directory will be included in the distribution." + ], + "type": "object", + "additionalProperties": false, + "propertyNames": { + "anyOf": [{"const": ""}, {"$ref": "#/definitions/package-name"}] + }, + "patternProperties": { + "^.*$": {"type": "string" } + } + }, + "package-data": { + "$$description": [ + "Mapping from package names to lists of glob patterns.", + "Usually this option is not needed when using ``include-package-data = true``", + "For more information on how to include data files, check ``setuptools`` `docs", + "`_." + ], + "type": "object", + "additionalProperties": false, + "propertyNames": { + "anyOf": [{"type": "string", "format": "python-module-name"}, {"const": "*"}] + }, + "patternProperties": { + "^.*$": {"type": "array", "items": {"type": "string"}} + } + }, + "include-package-data": { + "$$description": [ + "Automatically include any data files inside the package directories", + "that are specified by ``MANIFEST.in``", + "For more information on how to include data files, check ``setuptools`` `docs", + "`_." + ], + "type": "boolean" + }, + "exclude-package-data": { + "$$description": [ + "Mapping from package names to lists of glob patterns that should be excluded", + "For more information on how to include data files, check ``setuptools`` `docs", + "`_." + ], + "type": "object", + "additionalProperties": false, + "propertyNames": { + "anyOf": [{"type": "string", "format": "python-module-name"}, {"const": "*"}] + }, + "patternProperties": { + "^.*$": {"type": "array", "items": {"type": "string"}} + } + }, + "namespace-packages": { + "type": "array", + "items": {"type": "string", "format": "python-module-name-relaxed"}, + "$comment": "https://setuptools.pypa.io/en/latest/userguide/package_discovery.html", + "description": "**DEPRECATED**: use implicit namespaces instead (:pep:`420`)." + }, + "py-modules": { + "description": "Modules that setuptools will manipulate", + "type": "array", + "items": {"type": "string", "format": "python-module-name-relaxed"}, + "$comment": "TODO: clarify the relationship with ``packages``" + }, + "data-files": { + "$$description": [ + "``dict``-like structure where each key represents a directory and", + "the value is a list of glob patterns that should be installed in them.", + "**DISCOURAGED**: please notice this might not work as expected with wheels.", + "Whenever possible, consider using data files inside the package directories", + "(or create a new namespace package that only contains data files).", + "See `data files support", + "`_." + ], + "type": "object", + "patternProperties": { + "^.*$": {"type": "array", "items": {"type": "string"}} + } + }, + "cmdclass": { + "$$description": [ + "Mapping of distutils-style command names to ``setuptools.Command`` subclasses", + "which in turn should be represented by strings with a qualified class name", + "(i.e., \"dotted\" form with module), e.g.::\n\n", + " cmdclass = {mycmd = \"pkg.subpkg.module.CommandClass\"}\n\n", + "The command class should be a directly defined at the top-level of the", + "containing module (no class nesting)." + ], + "type": "object", + "patternProperties": { + "^.*$": {"type": "string", "format": "python-qualified-identifier"} + } + }, + "license-files": { + "type": "array", + "items": {"type": "string"}, + "$$description": [ + "**PROVISIONAL**: list of glob patterns for all license files being distributed.", + "(likely to become standard with :pep:`639`).", + "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``" + ], + "$comment": "TODO: revise if PEP 639 is accepted. Probably ``project.license-files``?" + }, + "dynamic": { + "type": "object", + "description": "Instructions for loading :pep:`621`-related metadata dynamically", + "additionalProperties": false, + "properties": { + "version": { + "$$description": [ + "A version dynamically loaded via either the ``attr:`` or ``file:``", + "directives. Please make sure the given file or attribute respects :pep:`440`.", + "Also ensure to set ``project.dynamic`` accordingly." + ], + "oneOf": [ + {"$ref": "#/definitions/attr-directive"}, + {"$ref": "#/definitions/file-directive"} + ] + }, + "classifiers": {"$ref": "#/definitions/file-directive"}, + "description": {"$ref": "#/definitions/file-directive"}, + "entry-points": {"$ref": "#/definitions/file-directive"}, + "dependencies": {"$ref": "#/definitions/file-directive-for-dependencies"}, + "optional-dependencies": { + "type": "object", + "propertyNames": {"type": "string", "format": "pep508-identifier"}, + "additionalProperties": false, + "patternProperties": { + ".+": {"$ref": "#/definitions/file-directive-for-dependencies"} + } + }, + "readme": { + "type": "object", + "anyOf": [ + {"$ref": "#/definitions/file-directive"}, + { + "type": "object", + "properties": { + "content-type": {"type": "string"}, + "file": { "$ref": "#/definitions/file-directive/properties/file" } + }, + "additionalProperties": false} + ], + "required": ["file"] + } + } + } + }, + + "definitions": { + "package-name": { + "$id": "#/definitions/package-name", + "title": "Valid package name", + "description": "Valid package name (importable or :pep:`561`).", + "type": "string", + "anyOf": [ + {"type": "string", "format": "python-module-name-relaxed"}, + {"type": "string", "format": "pep561-stub-name"} + ] + }, + "file-directive": { + "$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"] + }, + "file-directive-for-dependencies": { + "title": "'file:' directive for dependencies", + "allOf": [ + { + "$$description": [ + "**BETA**: subset of the ``requirements.txt`` format", + "without ``pip`` flags and options", + "(one :pep:`508`-compliant string per line,", + "lines that are blank or start with ``#`` are excluded).", + "See `dynamic metadata", + "`_." + ] + }, + {"$ref": "#/definitions/file-directive"} + ] + }, + "attr-directive": { + "title": "'attr:' directive", + "$id": "#/definitions/attr-directive", + "$$description": [ + "Value is read from a module attribute. Supports callables and iterables;", + "unsupported types are cast via ``str()``" + ], + "type": "object", + "additionalProperties": false, + "properties": { + "attr": {"type": "string", "format": "python-qualified-identifier"} + }, + "required": ["attr"] + }, + "find-directive": { + "$id": "#/definitions/find-directive", + "title": "'find:' directive", + "type": "object", + "additionalProperties": false, + "properties": { + "find": { + "type": "object", + "$$description": [ + "Dynamic `package discovery", + "`_." + ], + "additionalProperties": false, + "properties": { + "where": { + "description": + "Directories to be searched for packages (Unix-style relative path)", + "type": "array", + "items": {"type": "string"} + }, + "exclude": { + "type": "array", + "$$description": [ + "Exclude packages that match the values listed in this field.", + "Can container shell-style wildcards (e.g. ``'pkg.*'``)" + ], + "items": {"type": "string"} + }, + "include": { + "type": "array", + "$$description": [ + "Restrict the found packages to just the ones listed in this field.", + "Can container shell-style wildcards (e.g. ``'pkg.*'``)" + ], + "items": {"type": "string"} + }, + "namespaces": { + "type": "boolean", + "$$description": [ + "When ``True``, directories without a ``__init__.py`` file will also", + "be scanned for :pep:`420`-style implicit namespaces" + ] + } + } + } + } + } + } +} diff --git a/setuptools/depends.py b/setuptools/depends.py index 871a0925ef7..9398b95331c 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,13 +1,12 @@ -import sys -import marshal import contextlib import dis +import marshal +import sys - -from . import _imp -from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE from packaging.version import Version +from . import _imp +from ._imp import PY_COMPILED, PY_FROZEN, PY_SOURCE, find_module __all__ = ['Require', 'find_module'] diff --git a/setuptools/discovery.py b/setuptools/discovery.py index 3179852c694..577be2f16b5 100644 --- a/setuptools/discovery.py +++ b/setuptools/discovery.py @@ -44,16 +44,12 @@ from fnmatch import fnmatchcase from glob import glob from pathlib import Path -from typing import ( - TYPE_CHECKING, - Iterable, - Iterator, - Mapping, -) +from typing import TYPE_CHECKING, Iterable, Iterator, Mapping import _distutils_hack.override # noqa: F401 from ._path import StrPath + from distutils import log from distutils.util import convert_path diff --git a/setuptools/dist.py b/setuptools/dist.py index 6a29b2b2b7f..68f877decd0 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -10,31 +10,32 @@ from pathlib import Path from typing import TYPE_CHECKING, MutableMapping -import distutils.cmd -import distutils.command -import distutils.core -import distutils.dist -import distutils.log -from distutils.debug import DEBUG -from distutils.errors import DistutilsOptionError, DistutilsSetupError -from distutils.fancy_getopt import translate_longopt -from distutils.util import strtobool - from more_itertools import partition, unique_everseen -from ordered_set import OrderedSet from packaging.markers import InvalidMarker, Marker from packaging.specifiers import InvalidSpecifier, SpecifierSet from packaging.version import Version -from . import _entry_points -from . import _reqs -from . import command as _ # noqa -- imported for side-effects +from . import ( + _entry_points, + _reqs, + command as _, # noqa: F401 # imported for side-effects +) from ._importlib import metadata -from .config import setupcfg, pyprojecttoml +from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery from .monkey import get_unpatched from .warnings import InformationOnly, SetuptoolsDeprecationWarning +import distutils.cmd +import distutils.command +import distutils.core +import distutils.dist +import distutils.log +from distutils.debug import DEBUG +from distutils.errors import DistutilsOptionError, DistutilsSetupError +from distutils.fancy_getopt import translate_longopt +from distutils.util import strtobool + __all__ = ['Distribution'] sequence = tuple, list @@ -55,7 +56,7 @@ def assert_string_list(dist, attr, value): try: # verify that value is a list or tuple to exclude unordered # or single-use iterables - assert isinstance(value, (list, tuple)) + assert isinstance(value, sequence) # verify that elements of value are strings assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError) as e: @@ -194,8 +195,10 @@ def check_packages(dist, attr, value): if TYPE_CHECKING: + from typing_extensions import TypeAlias + # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 - _Distribution = distutils.core.Distribution + _Distribution: TypeAlias = distutils.core.Distribution else: _Distribution = get_unpatched(distutils.core.Distribution) @@ -249,7 +252,7 @@ class Distribution(_Distribution): _DISTUTILS_UNSUPPORTED_METADATA = { 'long_description_content_type': lambda: None, 'project_urls': dict, - 'provides_extras': OrderedSet, + 'provides_extras': dict, # behaves like an ordered set 'license_file': lambda: None, 'license_files': lambda: None, 'install_requires': list, @@ -265,6 +268,8 @@ def __init__(self, attrs: MutableMapping | None = None) -> None: self.package_data: dict[str, list[str]] = {} attrs = attrs or {} self.dist_files: list[tuple[str, str, str]] = [] + self.include_package_data: bool | None = None + self.exclude_package_data: dict[str, list[str]] | None = None # Filter-out setuptools' specific options. self.src_root = attrs.pop("src_root", None) self.dependency_links = attrs.pop('dependency_links', []) @@ -345,7 +350,7 @@ def _finalize_requires(self): # Setuptools allows a weird ": syntax for extras extra = extra.split(':')[0] if extra: - self.metadata.provides_extras.add(extra) + self.metadata.provides_extras.setdefault(extra) def _normalize_requires(self): """Make sure requirement-related attributes exist and are normalized""" diff --git a/setuptools/errors.py b/setuptools/errors.py index 67a5a1df10c..90fcf7170e8 100644 --- a/setuptools/errors.py +++ b/setuptools/errors.py @@ -3,30 +3,36 @@ Provides exceptions used by setuptools modules. """ +from __future__ import annotations + +from typing import TYPE_CHECKING + from distutils import errors as _distutils_errors +if TYPE_CHECKING: + from typing_extensions import TypeAlias # Re-export errors from distutils to facilitate the migration to PEP632 -ByteCompileError = _distutils_errors.DistutilsByteCompileError -CCompilerError = _distutils_errors.CCompilerError -ClassError = _distutils_errors.DistutilsClassError -CompileError = _distutils_errors.CompileError -ExecError = _distutils_errors.DistutilsExecError -FileError = _distutils_errors.DistutilsFileError -InternalError = _distutils_errors.DistutilsInternalError -LibError = _distutils_errors.LibError -LinkError = _distutils_errors.LinkError -ModuleError = _distutils_errors.DistutilsModuleError -OptionError = _distutils_errors.DistutilsOptionError -PlatformError = _distutils_errors.DistutilsPlatformError -PreprocessError = _distutils_errors.PreprocessError -SetupError = _distutils_errors.DistutilsSetupError -TemplateError = _distutils_errors.DistutilsTemplateError -UnknownFileError = _distutils_errors.UnknownFileError +ByteCompileError: TypeAlias = _distutils_errors.DistutilsByteCompileError +CCompilerError: TypeAlias = _distutils_errors.CCompilerError +ClassError: TypeAlias = _distutils_errors.DistutilsClassError +CompileError: TypeAlias = _distutils_errors.CompileError +ExecError: TypeAlias = _distutils_errors.DistutilsExecError +FileError: TypeAlias = _distutils_errors.DistutilsFileError +InternalError: TypeAlias = _distutils_errors.DistutilsInternalError +LibError: TypeAlias = _distutils_errors.LibError +LinkError: TypeAlias = _distutils_errors.LinkError +ModuleError: TypeAlias = _distutils_errors.DistutilsModuleError +OptionError: TypeAlias = _distutils_errors.DistutilsOptionError +PlatformError: TypeAlias = _distutils_errors.DistutilsPlatformError +PreprocessError: TypeAlias = _distutils_errors.PreprocessError +SetupError: TypeAlias = _distutils_errors.DistutilsSetupError +TemplateError: TypeAlias = _distutils_errors.DistutilsTemplateError +UnknownFileError: TypeAlias = _distutils_errors.UnknownFileError # The root error class in the hierarchy -BaseError = _distutils_errors.DistutilsError +BaseError: TypeAlias = _distutils_errors.DistutilsError class InvalidConfigError(OptionError): diff --git a/setuptools/extension.py b/setuptools/extension.py index 25420f42de5..dcc7709982e 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -1,12 +1,15 @@ -import re +from __future__ import annotations + import functools -import distutils.core -import distutils.errors -import distutils.extension +import re from typing import TYPE_CHECKING from .monkey import get_unpatched +import distutils.core +import distutils.errors +import distutils.extension + def _have_cython(): """ @@ -24,8 +27,10 @@ def _have_cython(): # for compatibility have_pyrex = _have_cython if TYPE_CHECKING: + from typing_extensions import TypeAlias + # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 - _Extension = distutils.core.Extension + _Extension: TypeAlias = distutils.core.Extension else: _Extension = get_unpatched(distutils.core.Extension) @@ -126,10 +131,19 @@ class Extension(_Extension): specified on Windows. (since v63) """ - def __init__(self, name, sources, *args, **kw): + # These 4 are set and used in setuptools/command/build_ext.py + # The lack of a default value and risk of `AttributeError` is purposeful + # to avoid people forgetting to call finalize_options if they modify the extension list. + # See example/rationale in https://github.com/pypa/setuptools/issues/4529. + _full_name: str #: Private API, internal use only. + _links_to_dynamic: bool #: Private API, internal use only. + _needs_stub: bool #: Private API, internal use only. + _file_name: str #: Private API, internal use only. + + def __init__(self, name: str, sources, *args, py_limited_api: bool = False, **kw): # The *args is needed for compatibility as calls may use positional # arguments. py_limited_api may be set only via keyword. - self.py_limited_api = kw.pop("py_limited_api", False) + self.py_limited_api = py_limited_api super().__init__(name, sources, *args, **kw) def _convert_pyx_sources_to_lang(self): diff --git a/setuptools/glob.py b/setuptools/glob.py index a184c0b643b..ffe0ae92cb1 100644 --- a/setuptools/glob.py +++ b/setuptools/glob.py @@ -6,9 +6,9 @@ * Hidden files are not ignored. """ +import fnmatch import os import re -import fnmatch __all__ = ["glob", "iglob", "escape"] diff --git a/setuptools/installer.py b/setuptools/installer.py index a6aff723c2d..ce3559cd934 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -3,13 +3,14 @@ import subprocess import sys import tempfile -from distutils import log -from distutils.errors import DistutilsError from functools import partial from . import _reqs -from .wheel import Wheel from .warnings import SetuptoolsDeprecationWarning +from .wheel import Wheel + +from distutils import log +from distutils.errors import DistutilsError def _fixup_find_links(find_links): diff --git a/setuptools/launch.py b/setuptools/launch.py index 0208fdf33b6..56c7d035f10 100644 --- a/setuptools/launch.py +++ b/setuptools/launch.py @@ -6,8 +6,8 @@ # Note that setuptools gets imported implicitly by the # invocation of this script using python -m setuptools.launch -import tokenize import sys +import tokenize def run(): diff --git a/setuptools/logging.py b/setuptools/logging.py index ceca99ca762..e9674c5a817 100644 --- a/setuptools/logging.py +++ b/setuptools/logging.py @@ -1,9 +1,11 @@ -import sys import inspect import logging -import distutils.log +import sys + from . import monkey +import distutils.log + def _not_warning(record): return record.levelno < logging.WARNING diff --git a/setuptools/modified.py b/setuptools/modified.py index af6ceeac893..245a61580b8 100644 --- a/setuptools/modified.py +++ b/setuptools/modified.py @@ -1,7 +1,7 @@ from ._distutils._modified import ( newer, - newer_pairwise, newer_group, + newer_pairwise, newer_pairwise_group, ) diff --git a/setuptools/monkey.py b/setuptools/monkey.py index e513f952457..abcc2755be0 100644 --- a/setuptools/monkey.py +++ b/setuptools/monkey.py @@ -14,7 +14,6 @@ import distutils.filelist - _T = TypeVar("_T") __all__: list[str] = [] diff --git a/setuptools/msvc.py b/setuptools/msvc.py index 2768059213f..ca332d59aa4 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -13,19 +13,20 @@ from __future__ import annotations -import json -from os import listdir, pathsep -from os.path import join, isfile, isdir, dirname -from subprocess import CalledProcessError import contextlib -import platform import itertools +import json +import platform import subprocess -import distutils.errors +from os import listdir, pathsep +from os.path import dirname, isdir, isfile, join +from subprocess import CalledProcessError from typing import TYPE_CHECKING from more_itertools import unique_everseen +import distutils.errors + # https://github.com/python/mypy/issues/8166 if not TYPE_CHECKING and platform.system() == 'Windows': import winreg diff --git a/setuptools/namespaces.py b/setuptools/namespaces.py index 2f2c1cfbe11..e82439e3efd 100644 --- a/setuptools/namespaces.py +++ b/setuptools/namespaces.py @@ -1,9 +1,9 @@ -import os -from distutils import log import itertools +import os from .compat import py39 +from distutils import log flatten = itertools.chain.from_iterable diff --git a/setuptools/package_index.py b/setuptools/package_index.py index c24c783762b..a66cbb2e613 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -1,49 +1,49 @@ """PyPI and direct package downloading.""" -import sys -import subprocess -import os -import re -import io -import shutil -import socket import base64 -import hashlib -import itertools import configparser +import hashlib import html import http.client +import io +import itertools +import os +import re +import shutil +import socket +import subprocess +import sys +import urllib.error import urllib.parse import urllib.request -import urllib.error +from fnmatch import translate from functools import wraps +from more_itertools import unique_everseen + import setuptools from pkg_resources import ( - CHECKOUT_DIST, - Distribution, BINARY_DIST, - normalize_path, + CHECKOUT_DIST, + DEVELOP_DIST, + EGG_DIST, SOURCE_DIST, + Distribution, Environment, + Requirement, find_distributions, + normalize_path, + parse_version, safe_name, safe_version, to_filename, - Requirement, - DEVELOP_DIST, - EGG_DIST, - parse_version, ) -from distutils import log -from distutils.errors import DistutilsError -from fnmatch import translate from setuptools.wheel import Wheel -from more_itertools import unique_everseen - -from .unicode_utils import _read_utf8_with_fallback, _cfg_read_utf8_with_fallback +from .unicode_utils import _cfg_read_utf8_with_fallback, _read_utf8_with_fallback +from distutils import log +from distutils.errors import DistutilsError EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 147b26749e8..7d545f1004c 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -1,29 +1,26 @@ from __future__ import annotations -import os -import sys -import tempfile -import operator +import builtins +import contextlib import functools import itertools -import re -import contextlib +import operator +import os import pickle +import re +import sys +import tempfile import textwrap -import builtins import pkg_resources -from distutils.errors import DistutilsError from pkg_resources import working_set +from distutils.errors import DistutilsError + if sys.platform.startswith('java'): import org.python.modules.posix.PosixModule as _os else: _os = sys.modules[os.name] -try: - _file = file # type: ignore[name-defined] # Check for global variable -except NameError: - _file = None _open = open @@ -283,15 +280,11 @@ def _copy(self, source): def __enter__(self): self._copy(self) - if _file: - builtins.file = self._file builtins.open = self._open self._active = True def __exit__(self, exc_type, exc_value, traceback): self._active = False - if _file: - builtins.file = _file builtins.open = _open self._copy(_os) @@ -324,8 +317,6 @@ def wrap(self, path, *args, **kw): return wrap - if _file: - _file = _mk_single_path_wrapper('file', _file) _open = _mk_single_path_wrapper('open', _open) for __name in [ "stat", @@ -442,13 +433,6 @@ def _violation(self, operation, *args, **kw): raise SandboxViolation(operation, args, kw) - if _file: - - def _file(self, path, mode='r', *args, **kw): - if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): - self._violation("file", path, mode, *args, **kw) - return _file(path, mode, *args, **kw) - def _open(self, path, mode='r', *args, **kw): if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): self._violation("open", path, mode, *args, **kw) diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 738ebf43bef..eb70bfb7115 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -3,13 +3,11 @@ import pytest - __all__ = ['fail_on_ascii'] -locale_encoding = ( - locale.getencoding() - if sys.version_info >= (3, 11) - else locale.getpreferredencoding(False) -) +if sys.version_info >= (3, 11): + locale_encoding = locale.getencoding() +else: + locale_encoding = locale.getpreferredencoding(False) is_ascii = locale_encoding == 'ANSI_X3.4-1968' fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale") diff --git a/setuptools/tests/compat/py39.py b/setuptools/tests/compat/py39.py index 9c86065cd27..1fdb9dac1fa 100644 --- a/setuptools/tests/compat/py39.py +++ b/setuptools/tests/compat/py39.py @@ -1,4 +1,3 @@ from jaraco.test.cpython import from_test_support, try_import - os_helper = try_import('os_helper') or from_test_support('can_symlink') diff --git a/setuptools/tests/config/downloads/preload.py b/setuptools/tests/config/downloads/preload.py index d559beff335..8eeb5dd75d3 100644 --- a/setuptools/tests/config/downloads/preload.py +++ b/setuptools/tests/config/downloads/preload.py @@ -13,7 +13,6 @@ from . import retrieve_file, urls_from_file - if __name__ == "__main__": urls = urls_from_file(Path(sys.argv[1])) list(map(retrieve_file, urls)) diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index 6650a657d30..78959b6454f 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -15,20 +15,17 @@ import pytest from ini2toml.api import LiteTranslator - from packaging.metadata import Metadata import setuptools # noqa ensure monkey patch to metadata -from setuptools.dist import Distribution -from setuptools.config import setupcfg, pyprojecttoml -from setuptools.config import expand -from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter from setuptools.command.egg_info import write_requirements +from setuptools.config import expand, pyprojecttoml, setupcfg +from setuptools.config._apply_pyprojecttoml import _MissingDynamic, _some_attrgetter +from setuptools.dist import Distribution from setuptools.errors import RemovedConfigError from .downloads import retrieve_file, urls_from_file - HERE = Path(__file__).parent EXAMPLES_FILE = "setupcfg_examples.txt" @@ -300,6 +297,33 @@ def test_default_patterns(self, tmp_path): assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"} +class TestPyModules: + # https://github.com/pypa/setuptools/issues/4316 + + def dist(self, name): + toml_config = f""" + [project] + name = "test" + version = "42.0" + [tool.setuptools] + py-modules = [{name!r}] + """ + pyproject = Path("pyproject.toml") + pyproject.write_text(cleandoc(toml_config), encoding="utf-8") + return pyprojecttoml.apply_configuration(Distribution({}), pyproject) + + @pytest.mark.parametrize("module", ["pip-run", "abc-d.λ-xyz-e"]) + def test_valid_module_name(self, tmp_path, monkeypatch, module): + monkeypatch.chdir(tmp_path) + assert module in self.dist(module).py_modules + + @pytest.mark.parametrize("module", ["pip run", "-pip-run", "pip-run-stubs"]) + def test_invalid_module_name(self, tmp_path, monkeypatch, module): + monkeypatch.chdir(tmp_path) + with pytest.raises(ValueError, match="py-modules"): + self.dist(module).py_modules + + class TestDeprecatedFields: def test_namespace_packages(self, tmp_path): pyproject = tmp_path / "pyproject.toml" diff --git a/setuptools/tests/config/test_expand.py b/setuptools/tests/config/test_expand.py index b309a1ce7c8..f51d2008d09 100644 --- a/setuptools/tests/config/test_expand.py +++ b/setuptools/tests/config/test_expand.py @@ -4,10 +4,11 @@ import pytest -from distutils.errors import DistutilsOptionError from setuptools.config import expand from setuptools.discovery import find_package_path +from distutils.errors import DistutilsOptionError + def write_files(files, root_dir): for file, content in files.items(): diff --git a/setuptools/tests/config/test_pyprojecttoml.py b/setuptools/tests/config/test_pyprojecttoml.py index bf8cae5a242..bb15ce10ded 100644 --- a/setuptools/tests/config/test_pyprojecttoml.py +++ b/setuptools/tests/config/test_pyprojecttoml.py @@ -7,18 +7,17 @@ import tomli_w from path import Path +import setuptools # noqa: F401 # force distutils.core to be patched from setuptools.config.pyprojecttoml import ( _ToolsTypoInMetadata, - read_configuration, - expand_configuration, apply_configuration, + expand_configuration, + read_configuration, validate, ) from setuptools.dist import Distribution from setuptools.errors import OptionError - -import setuptools # noqa -- force distutils.core to be patched import distutils.core EXAMPLE = """ diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py index 0074fe23088..4f0a7349f55 100644 --- a/setuptools/tests/config/test_setupcfg.py +++ b/setuptools/tests/config/test_setupcfg.py @@ -5,14 +5,16 @@ from unittest.mock import Mock, patch import pytest +from packaging.requirements import InvalidRequirement -from distutils.errors import DistutilsOptionError, DistutilsFileError -from setuptools.dist import Distribution, _Distribution from setuptools.config.setupcfg import ConfigHandler, read_configuration -from packaging.requirements import InvalidRequirement +from setuptools.dist import Distribution, _Distribution from setuptools.warnings import SetuptoolsDeprecationWarning + from ..textwrap import DALS +from distutils.errors import DistutilsFileError, DistutilsOptionError + class ErrConfigHandler(ConfigHandler): """Erroneous handler. Fails to implement required methods.""" @@ -673,7 +675,7 @@ def test_extras_require(self, tmpdir): 'pdf': ['ReportLab>=1.2', 'RXP'], 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'], } - assert dist.metadata.provides_extras == set(['pdf', 'rest']) + assert set(dist.metadata.provides_extras) == {'pdf', 'rest'} @pytest.mark.parametrize( "config", diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py index 112cdf4b288..97cceea0e70 100644 --- a/setuptools/tests/contexts.py +++ b/setuptools/tests/contexts.py @@ -1,10 +1,10 @@ -import tempfile +import contextlib +import io import os import shutil -import sys -import contextlib import site -import io +import sys +import tempfile from filelock import FileLock diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py index b9de4fda6b2..ed5499ef7d7 100644 --- a/setuptools/tests/environment.py +++ b/setuptools/tests/environment.py @@ -1,8 +1,8 @@ import os -import sys import subprocess +import sys import unicodedata -from subprocess import Popen as _Popen, PIPE as _PIPE +from subprocess import PIPE as _PIPE, Popen as _Popen import jaraco.envs diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py index a2870f11e12..a5472984b56 100644 --- a/setuptools/tests/fixtures.py +++ b/setuptools/tests/fixtures.py @@ -1,11 +1,11 @@ -import os import contextlib -import sys +import os import subprocess +import sys from pathlib import Path -import pytest import path +import pytest from . import contexts, environment diff --git a/setuptools/tests/integration/helpers.py b/setuptools/tests/integration/helpers.py index 615c43b2e0a..77b196e029b 100644 --- a/setuptools/tests/integration/helpers.py +++ b/setuptools/tests/integration/helpers.py @@ -8,8 +8,8 @@ import os import subprocess import tarfile -from zipfile import ZipFile from pathlib import Path +from zipfile import ZipFile def run(cmd, env=None): diff --git a/setuptools/tests/integration/test_pip_install_sdist.py b/setuptools/tests/integration/test_pip_install_sdist.py index ee70b1c286b..2d59337aff1 100644 --- a/setuptools/tests/integration/test_pip_install_sdist.py +++ b/setuptools/tests/integration/test_pip_install_sdist.py @@ -137,7 +137,7 @@ def test_install_sdist(package, version, tmp_path, venv_python, setuptools_wheel # Use a virtualenv to simulate PEP 517 isolation # but install fresh setuptools wheel to ensure the version under development env = EXTRA_ENV_VARS.get(package, {}) - run([*venv_pip, "install", "wheel", "-I", setuptools_wheel]) + run([*venv_pip, "install", "--force-reinstall", setuptools_wheel]) run([*venv_pip, "install", *INSTALL_OPTIONS, sdist], env) # Execute a simple script to make sure the package was installed correctly diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index 8cb735f1e63..15bbc3b1f0d 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -1,9 +1,9 @@ """Basic http server for tests to simulate PyPI or custom indexes""" +import http.server import os -import time import threading -import http.server +import time import urllib.parse import urllib.request diff --git a/setuptools/tests/test_archive_util.py b/setuptools/tests/test_archive_util.py index 06d7f05aa0b..e3efc628899 100644 --- a/setuptools/tests/test_archive_util.py +++ b/setuptools/tests/test_archive_util.py @@ -1,5 +1,5 @@ -import tarfile import io +import tarfile import pytest diff --git a/setuptools/tests/test_bdist_deprecations.py b/setuptools/tests/test_bdist_deprecations.py index f2b99b053be..d9d67b06161 100644 --- a/setuptools/tests/test_bdist_deprecations.py +++ b/setuptools/tests/test_bdist_deprecations.py @@ -5,8 +5,8 @@ import pytest -from setuptools.dist import Distribution from setuptools import SetuptoolsDeprecationWarning +from setuptools.dist import Distribution @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only') diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index 3ca277ba153..a1e3d9a73ec 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -16,9 +16,9 @@ import jaraco.path import pytest +from packaging import tags import setuptools -from distutils.core import run_setup from setuptools.command.bdist_wheel import ( bdist_wheel, get_abi_tag, @@ -26,7 +26,8 @@ remove_readonly_exc, ) from setuptools.dist import Distribution -from packaging import tags + +from distutils.core import run_setup DEFAULT_FILES = { "dummy_dist-1.0.dist-info/top_level.txt", diff --git a/setuptools/tests/test_build.py b/setuptools/tests/test_build.py index 141522efd49..f0f1d9dcf21 100644 --- a/setuptools/tests/test_build.py +++ b/setuptools/tests/test_build.py @@ -1,6 +1,6 @@ from setuptools import Command -from setuptools.dist import Distribution from setuptools.command.build import build +from setuptools.dist import Distribution def test_distribution_gives_setuptools_build_obj(tmpdir_cwd): diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py index 2c5b956c7c6..b5315df4f65 100644 --- a/setuptools/tests/test_build_clib.py +++ b/setuptools/tests/test_build_clib.py @@ -1,12 +1,13 @@ +import random from unittest import mock import pytest -import random -from distutils.errors import DistutilsSetupError from setuptools.command.build_clib import build_clib from setuptools.dist import Distribution +from distutils.errors import DistutilsSetupError + class TestBuildCLib: @mock.patch('setuptools.command.build_clib.newer_pairwise_group') diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index 0482fb5d5c6..814fbd86aa4 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -1,21 +1,20 @@ import os import sys -import distutils.command.build_ext as orig -from distutils.sysconfig import get_config_var from importlib.util import cache_from_source as _compiled_file_name +import pytest from jaraco import path from setuptools.command.build_ext import build_ext, get_abi3_suffix from setuptools.dist import Distribution -from setuptools.extension import Extension from setuptools.errors import CompileError +from setuptools.extension import Extension from . import environment from .textwrap import DALS -import pytest - +import distutils.command.build_ext as orig +from distutils.sysconfig import get_config_var IS_PYPY = '__pypy__' in sys.builtin_module_names @@ -189,6 +188,7 @@ def get_build_ext_cmd(self, optional: bool, **opts): dist = Distribution(dict(ext_modules=[extension])) dist.script_name = 'setup.py' cmd = build_ext(dist) + # TODO: False-positive [attr-defined], raise upstream vars(cmd).update(build_lib=".build/lib", build_temp=".build/tmp", **opts) cmd.ensure_finalized() return cmd diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index ecb1dcfd87b..c34f7116623 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -1,17 +1,18 @@ +import contextlib +import importlib import os -import sys +import re import shutil import signal +import sys import tarfile -import importlib -import contextlib from concurrent import futures -import re -from zipfile import ZipFile from pathlib import Path +from zipfile import ZipFile import pytest from jaraco import path +from packaging.requirements import Requirement from .textwrap import DALS @@ -436,18 +437,16 @@ def test_build_with_pyproject_config(self, tmpdir, setup_script): } assert license == "---- placeholder MIT license ----" - metadata = metadata.replace("(", "").replace(")", "") - # ^-- compatibility hack for pypa/wheel#552 - for line in ( "Summary: This is a Python package", "License: MIT", "Classifier: Intended Audience :: Developers", "Requires-Dist: appdirs", - "Requires-Dist: tomli >=1 ; extra == 'all'", - "Requires-Dist: importlib ; python_version == \"2.6\" and extra == 'all'", + "Requires-Dist: " + str(Requirement('tomli>=1 ; extra == "all"')), + "Requires-Dist: " + + str(Requirement('importlib; python_version=="2.6" and extra =="all"')), ): - assert line in metadata + assert line in metadata, (line, metadata) assert metadata.strip().endswith("This is a ``README``") assert epoints.strip() == "[console_scripts]\nfoo = foo.cli:main" diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index 6900fdefbd7..b8cd77dc0bd 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -1,12 +1,12 @@ import os -import stat import shutil +import stat import warnings from pathlib import Path from unittest.mock import Mock -import pytest import jaraco.path +import pytest from setuptools import SetuptoolsDeprecationWarning from setuptools.dist import Distribution diff --git a/setuptools/tests/test_config_discovery.py b/setuptools/tests/test_config_discovery.py index ff9e672b689..af172953e33 100644 --- a/setuptools/tests/test_config_discovery.py +++ b/setuptools/tests/test_config_discovery.py @@ -3,22 +3,22 @@ from configparser import ConfigParser from itertools import product +import jaraco.path +import pytest +from path import Path + +import setuptools # noqa: F401 # force distutils.core to be patched from setuptools.command.sdist import sdist -from setuptools.dist import Distribution from setuptools.discovery import find_package_path, find_parent_package +from setuptools.dist import Distribution from setuptools.errors import PackageDiscoveryError -import setuptools # noqa -- force distutils.core to be patched -import distutils.core - -import pytest -import jaraco.path -from path import Path - from .contexts import quiet from .integration.helpers import get_sdist_members, get_wheel_members, run from .textwrap import DALS +import distutils.core + class TestFindParentPackage: def test_single_package(self, tmp_path): diff --git a/setuptools/tests/test_core_metadata.py b/setuptools/tests/test_core_metadata.py index eaabf20a9d3..34828ac750d 100644 --- a/setuptools/tests/test_core_metadata.py +++ b/setuptools/tests/test_core_metadata.py @@ -1,17 +1,15 @@ import functools -import io import importlib +import io from email import message_from_string import pytest - from packaging.metadata import Metadata -from setuptools import sic, _reqs -from setuptools.dist import Distribution +from setuptools import _reqs, sic from setuptools._core_metadata import rfc822_escape, rfc822_unescape from setuptools.command.egg_info import egg_info, write_requirements - +from setuptools.dist import Distribution EXAMPLE_BASE_INFO = dict( name="package", @@ -190,7 +188,7 @@ def test_read_metadata(name, attrs): ('requires', dist_class.get_requires), ('classifiers', dist_class.get_classifiers), ('project_urls', lambda s: getattr(s, 'project_urls', {})), - ('provides_extras', lambda s: getattr(s, 'provides_extras', set())), + ('provides_extras', lambda s: getattr(s, 'provides_extras', {})), ] for attr, getter in tested_attrs: diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py index 6f17beb7039..929fa9c285e 100644 --- a/setuptools/tests/test_develop.py +++ b/setuptools/tests/test_develop.py @@ -1,18 +1,18 @@ """develop tests""" import os -import sys -import subprocess import pathlib import platform +import subprocess +import sys import pytest +from setuptools._path import paths_on_pythonpath from setuptools.command.develop import develop from setuptools.dist import Distribution -from setuptools._path import paths_on_pythonpath -from . import contexts -from . import namespaces + +from . import contexts, namespaces SETUP_PY = """\ from setuptools import setup diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 99cd5825019..597785b519e 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -1,20 +1,19 @@ import collections -import re import os -import urllib.request +import re import urllib.parse -from distutils.errors import DistutilsSetupError -from setuptools.dist import ( - check_package_data, - check_specifier, -) +import urllib.request + +import pytest + from setuptools import Distribution +from setuptools.dist import check_package_data, check_specifier -from .textwrap import DALS from .test_easy_install import make_nspkg_sdist from .test_find_packages import ensure_files +from .textwrap import DALS -import pytest +from distutils.errors import DistutilsSetupError def test_dist_fetch_build_egg(tmpdir): @@ -78,12 +77,12 @@ def test_provides_extras_deterministic_order(): extras['b'] = ['bar'] attrs = dict(extras_require=extras) dist = Distribution(attrs) - assert dist.metadata.provides_extras == ['a', 'b'] + assert list(dist.metadata.provides_extras) == ['a', 'b'] attrs['extras_require'] = collections.OrderedDict( reversed(list(attrs['extras_require'].items())) ) dist = Distribution(attrs) - assert dist.metadata.provides_extras == ['b', 'a'] + assert list(dist.metadata.provides_extras) == ['b', 'a'] CHECK_PACKAGE_DATA_TESTS = ( diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py index 44be6c3284f..6e109c9db27 100644 --- a/setuptools/tests/test_dist_info.py +++ b/setuptools/tests/test_dist_info.py @@ -11,8 +11,8 @@ import pkg_resources from setuptools.archive_util import unpack_archive -from .textwrap import DALS +from .textwrap import DALS read = partial(pathlib.Path.read_text, encoding="utf-8") diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index 74883d21998..0b020ba9fcc 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -1,11 +1,10 @@ import os -import sys import platform +import sys import textwrap import pytest - IS_PYPY = '__pypy__' in sys.builtin_module_names _TEXT_KWARGS = {"text": True, "encoding": "utf-8"} # For subprocess.run diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 7346a07929c..ca6af9667ed 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -1,42 +1,42 @@ """Easy install Tests""" -import sys -import os -import tempfile -import site import contextlib -import tarfile -import logging -import itertools -import distutils.errors import io -from typing import NamedTuple -import zipfile -import time +import itertools +import logging +import os +import pathlib import re +import site import subprocess -import pathlib +import sys +import tarfile +import tempfile +import time import warnings +import zipfile from pathlib import Path +from typing import NamedTuple from unittest import mock import pytest from jaraco import path -from setuptools import sandbox -from setuptools.sandbox import run_setup +import pkg_resources import setuptools.command.easy_install as ei +from pkg_resources import Distribution as PRDistribution, normalize_path, working_set +from setuptools import sandbox from setuptools.command.easy_install import PthDistributions from setuptools.dist import Distribution -from pkg_resources import normalize_path, working_set -from pkg_resources import Distribution as PRDistribution -from setuptools.tests.server import MockServer, path_to_url +from setuptools.sandbox import run_setup from setuptools.tests import fail_on_ascii -import pkg_resources +from setuptools.tests.server import MockServer, path_to_url from . import contexts from .textwrap import DALS +import distutils.errors + @pytest.fixture(autouse=True) def pip_disable_index(monkeypatch): diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index 24c10a50547..287367ac181 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -1,8 +1,8 @@ import os +import platform import stat -import sys import subprocess -import platform +import sys from copy import deepcopy from importlib import import_module from importlib.machinery import EXTENSION_SUFFIXES @@ -11,31 +11,31 @@ from unittest.mock import Mock from uuid import uuid4 -from distutils.core import run_setup - import jaraco.envs import jaraco.path import pytest from path import Path as _Path -from . import contexts, namespaces - from setuptools._importlib import resources as importlib_resources from setuptools.command.editable_wheel import ( _DebuggingTips, - _LinkTree, - _TopLevelFinder, _encode_pth, - _find_virtual_namespaces, _find_namespaces, _find_package_roots, + _find_virtual_namespaces, _finder_template, + _LinkTree, + _TopLevelFinder, editable_wheel, ) from setuptools.dist import Distribution from setuptools.extension import Extension from setuptools.warnings import SetuptoolsDeprecationWarning +from . import contexts, namespaces + +from distutils.core import run_setup + @pytest.fixture(params=["strict", "lenient"]) def editable_opts(request): diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 856dd127bcf..6e8d0c68c3a 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,11 +1,11 @@ from __future__ import annotations -import sys import ast -import os import glob +import os import re import stat +import sys import time from pathlib import Path from unittest import mock @@ -14,16 +14,11 @@ from jaraco import path from setuptools import errors -from setuptools.command.egg_info import ( - egg_info, - manifest_maker, - write_entries, -) +from setuptools.command.egg_info import egg_info, manifest_maker, write_entries from setuptools.dist import Distribution -from . import environment +from . import contexts, environment from .textwrap import DALS -from . import contexts class Environment(str): diff --git a/setuptools/tests/test_extern.py b/setuptools/tests/test_extern.py index da01b25b981..d7eb3c62c19 100644 --- a/setuptools/tests/test_extern.py +++ b/setuptools/tests/test_extern.py @@ -1,19 +1,14 @@ import importlib import pickle +import packaging + from setuptools import Distribution -import ordered_set def test_reimport_extern(): - ordered_set2 = importlib.import_module(ordered_set.__name__) - assert ordered_set is ordered_set2 - - -def test_orderedset_pickle_roundtrip(): - o1 = ordered_set.OrderedSet([1, 2, 5]) - o2 = pickle.loads(pickle.dumps(o1)) - assert o1 == o2 + packaging2 = importlib.import_module(packaging.__name__) + assert packaging is packaging2 def test_distribution_picklable(): diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index 0f4e2bef898..9fd9f8f6637 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -6,8 +6,7 @@ import pytest -from setuptools import find_packages -from setuptools import find_namespace_packages +from setuptools import find_namespace_packages, find_packages from setuptools.discovery import FlatLayoutPackageFinder from .compat.py39 import os_helper diff --git a/setuptools/tests/test_find_py_modules.py b/setuptools/tests/test_find_py_modules.py index 2459c98fa31..8034b544294 100644 --- a/setuptools/tests/test_find_py_modules.py +++ b/setuptools/tests/test_find_py_modules.py @@ -6,8 +6,8 @@ from setuptools.discovery import FlatLayoutModuleFinder, ModuleFinder -from .test_find_packages import ensure_files from .compat.py39 import os_helper +from .test_find_packages import ensure_files class TestModuleFinder: diff --git a/setuptools/tests/test_install_scripts.py b/setuptools/tests/test_install_scripts.py index 595b7ade676..2ae54965256 100644 --- a/setuptools/tests/test_install_scripts.py +++ b/setuptools/tests/test_install_scripts.py @@ -6,6 +6,7 @@ from setuptools.command.install_scripts import install_scripts from setuptools.dist import Distribution + from . import contexts diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 78f459d018f..b0e7d67b5e4 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -10,11 +10,10 @@ import pytest -from setuptools.command.easy_install import easy_install from setuptools.command import easy_install as easy_install_pkg +from setuptools.command.easy_install import easy_install from setuptools.dist import Distribution - pytestmark = pytest.mark.skipif( 'platform.python_implementation() == "PyPy" and platform.system() == "Windows"', reason="pypa/setuptools#2496", diff --git a/setuptools/tests/test_logging.py b/setuptools/tests/test_logging.py index cf89b3bd004..e01df7277c2 100644 --- a/setuptools/tests/test_logging.py +++ b/setuptools/tests/test_logging.py @@ -5,7 +5,6 @@ import pytest - IS_PYPY = '__pypy__' in sys.builtin_module_names @@ -25,6 +24,7 @@ def test_verbosity_level(tmp_path, monkeypatch, flag, expected_level): """Make sure the correct verbosity level is set (issue #3038)""" import setuptools # noqa: F401 # import setuptools to monkeypatch distutils + import distutils # <- load distutils after all the patches take place logger = logging.Logger(__name__) @@ -61,7 +61,9 @@ def test_patching_does_not_cause_problems(): # Ensure `dist.log` is only patched if necessary import _distutils_hack + import setuptools.logging + from distutils import dist setuptools.logging.configure() diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py index f3eba733d9b..ae5572b83c1 100644 --- a/setuptools/tests/test_manifest.py +++ b/setuptools/tests/test_manifest.py @@ -3,22 +3,22 @@ from __future__ import annotations import contextlib +import io +import itertools +import logging import os import shutil import sys import tempfile -import itertools -import io -import logging -from distutils import log -from distutils.errors import DistutilsTemplateError + +import pytest from setuptools.command.egg_info import FileList, egg_info, translate_pattern from setuptools.dist import Distribution from setuptools.tests.textwrap import DALS -import pytest - +from distutils import log +from distutils.errors import DistutilsTemplateError IS_PYPY = '__pypy__' in sys.builtin_module_names diff --git a/setuptools/tests/test_msvc14.py b/setuptools/tests/test_msvc14.py index 4b8344539f8..57d3cc38e88 100644 --- a/setuptools/tests/test_msvc14.py +++ b/setuptools/tests/test_msvc14.py @@ -3,10 +3,12 @@ """ import os -from distutils.errors import DistutilsPlatformError -import pytest import sys +import pytest + +from distutils.errors import DistutilsPlatformError + @pytest.mark.skipif(sys.platform != "win32", reason="These tests are only for win32") class TestMSVC14: diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index 56689301dad..a0f4120bf79 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -1,5 +1,5 @@ -import sys import subprocess +import sys from setuptools._path import paths_on_pythonpath diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index f5f37e0563a..e1f84586742 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -1,13 +1,14 @@ -import distutils.errors -import urllib.request -import urllib.error import http.client +import urllib.error +import urllib.request from inspect import cleandoc import pytest import setuptools.package_index +import distutils.errors + class TestPackageIndex: def test_regex(self): diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py index ed85e9bbd3d..0c7d109d31f 100644 --- a/setuptools/tests/test_register.py +++ b/setuptools/tests/test_register.py @@ -1,11 +1,11 @@ -from setuptools.command.register import register -from setuptools.dist import Distribution -from setuptools.errors import RemovedCommandError - from unittest import mock import pytest +from setuptools.command.register import register +from setuptools.dist import Distribution +from setuptools.errors import RemovedCommandError + class TestRegister: def test_register_exception(self): diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 096cdc39244..2df202fd182 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -1,32 +1,31 @@ """sdist tests""" +import contextlib +import io +import logging import os import sys +import tarfile import tempfile import unicodedata -import contextlib -import io -import tarfile -import logging -import distutils from inspect import cleandoc from unittest import mock +import jaraco.path import pytest -from distutils.core import run_setup -from setuptools import Command +from setuptools import Command, SetuptoolsDeprecationWarning from setuptools._importlib import metadata -from setuptools import SetuptoolsDeprecationWarning -from setuptools.command.sdist import sdist from setuptools.command.egg_info import manifest_maker +from setuptools.command.sdist import sdist from setuptools.dist import Distribution from setuptools.extension import Extension from setuptools.tests import fail_on_ascii -from .text import Filenames -import jaraco.path +from .text import Filenames +import distutils +from distutils.core import run_setup SETUP_ATTRS = { 'name': 'sdist_test', diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 9865ee847c8..6af1d98c6b6 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -1,22 +1,22 @@ """Tests for the 'setuptools' package""" +import os import re import sys -import os -import distutils.core -import distutils.cmd -from distutils.errors import DistutilsSetupError -from distutils.core import Extension from zipfile import ZipFile import pytest +from packaging.version import Version import setuptools -import setuptools.dist import setuptools.depends as dep +import setuptools.dist from setuptools.depends import Require -from packaging.version import Version +import distutils.cmd +import distutils.core +from distutils.core import Extension +from distutils.errors import DistutilsSetupError @pytest.fixture(autouse=True) diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py index 4ed59bc24d4..cbcd455c41f 100644 --- a/setuptools/tests/test_upload.py +++ b/setuptools/tests/test_upload.py @@ -1,11 +1,11 @@ -from setuptools.command.upload import upload -from setuptools.dist import Distribution -from setuptools.errors import RemovedCommandError - from unittest import mock import pytest +from setuptools.command.upload import upload +from setuptools.dist import Distribution +from setuptools.errors import RemovedCommandError + class TestUpload: def test_upload_exception(self): diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 4554581ed0d..cdc10f50044 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -1,9 +1,8 @@ import os -import sys import subprocess -from urllib.request import urlopen +import sys from urllib.error import URLError - +from urllib.request import urlopen import pytest diff --git a/setuptools/tests/test_warnings.py b/setuptools/tests/test_warnings.py index ac6d07795e2..41193d4f717 100644 --- a/setuptools/tests/test_warnings.py +++ b/setuptools/tests/test_warnings.py @@ -4,7 +4,6 @@ from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning - _EXAMPLES = { "default": dict( args=("Hello {x}", "\n\t{target} {v:.1f}"), diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index cc5d54b6d97..ee46f664e46 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -2,31 +2,31 @@ from __future__ import annotations -from distutils.sysconfig import get_config_var -from distutils.util import get_platform import contextlib -import pathlib -import stat import glob import inspect import os +import pathlib import shutil +import stat import subprocess import sys -from typing import Any import zipfile +from typing import Any import pytest from jaraco import path - -from pkg_resources import Distribution, PathMetadata, PY_MAJOR -from packaging.utils import canonicalize_name from packaging.tags import parse_tag +from packaging.utils import canonicalize_name + +from pkg_resources import PY_MAJOR, Distribution, PathMetadata from setuptools.wheel import Wheel from .contexts import tempdir from .textwrap import DALS +from distutils.sysconfig import get_config_var +from distutils.util import get_platform WHEEL_INFO_TESTS = ( ('invalid.whl', ValueError), diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py index b2726893514..4a112baf75d 100644 --- a/setuptools/tests/test_windows_wrappers.py +++ b/setuptools/tests/test_windows_wrappers.py @@ -13,15 +13,15 @@ """ import pathlib -import sys import platform -import textwrap import subprocess +import sys +import textwrap import pytest -from setuptools.command.easy_install import nt_quote_arg import pkg_resources +from setuptools.command.easy_install import nt_quote_arg pytestmark = pytest.mark.skipif(sys.platform != 'win32', reason="Windows only") diff --git a/setuptools/unicode_utils.py b/setuptools/unicode_utils.py index 696b34c46a1..862d79e8985 100644 --- a/setuptools/unicode_utils.py +++ b/setuptools/unicode_utils.py @@ -1,5 +1,5 @@ -import unicodedata import sys +import unicodedata from configparser import ConfigParser from .compat import py39 diff --git a/setuptools/wheel.py b/setuptools/wheel.py index a05cd98d1f5..69a73df2442 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -1,26 +1,25 @@ """Wheels support.""" +import contextlib import email -import itertools import functools +import itertools import os import posixpath import re import zipfile -import contextlib -from packaging.version import Version as parse_version from packaging.tags import sys_tags from packaging.utils import canonicalize_name - -from distutils.util import get_platform +from packaging.version import Version as parse_version import setuptools -from setuptools.command.egg_info import write_requirements, _egg_basename from setuptools.archive_util import _unpack_zipfile_obj +from setuptools.command.egg_info import _egg_basename, write_requirements from .unicode_utils import _read_utf8_with_fallback +from distutils.util import get_platform WHEEL_NAME = re.compile( r"""^(?P.+?)-(?P\d.*?) diff --git a/setuptools/windows_support.py b/setuptools/windows_support.py index 8299ac1cdfc..7a2b53a2914 100644 --- a/setuptools/windows_support.py +++ b/setuptools/windows_support.py @@ -8,7 +8,7 @@ def windows_only(func): @windows_only -def hide_file(path): +def hide_file(path: str) -> None: """ Set the hidden attribute on a file or directory. @@ -17,8 +17,8 @@ def hide_file(path): `path` must be text. """ import ctypes + import ctypes.wintypes - __import__('ctypes.wintypes') SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD SetFileAttributes.restype = ctypes.wintypes.BOOL diff --git a/tools/build_launchers.py b/tools/build_launchers.py index c6734453654..a8b85c5f556 100644 --- a/tools/build_launchers.py +++ b/tools/build_launchers.py @@ -17,15 +17,14 @@ - C++ ATL for latest v143 build tools (ARM64) """ -import os import functools import itertools +import os import pathlib import shutil import subprocess import tempfile - BUILD_TARGETS = ["cli", "gui"] GUI = {"cli": 0, "gui": 1} BUILD_PLATFORMS = ["Win32", "x64", "arm64"] diff --git a/tools/finalize.py b/tools/finalize.py index 3ba5d16ac7d..d646e67cd0a 100644 --- a/tools/finalize.py +++ b/tools/finalize.py @@ -5,14 +5,13 @@ __requires__ = ['bump2version', 'towncrier', 'jaraco.develop>=7.21'] -import subprocess import pathlib import re +import subprocess import sys from jaraco.develop import towncrier - bump_version_command = [ sys.executable, '-m', diff --git a/tools/generate_validation_code.py b/tools/generate_validation_code.py index ca933e8f3bc..82c7f011d95 100644 --- a/tools/generate_validation_code.py +++ b/tools/generate_validation_code.py @@ -1,17 +1,18 @@ from __future__ import annotations -from os import PathLike +import itertools import subprocess import sys - from pathlib import Path +from typing import Iterable -def generate_pyproject_validation(dest: str | PathLike[str]): +def generate_pyproject_validation(dest: Path, schemas: Iterable[Path]) -> bool: """ Generates validation code for ``pyproject.toml`` based on JSON schemas and the ``validate-pyproject`` library. """ + schema_args = (("-t", f"{f.name.partition('.')[0]}={f}") for f in schemas) cmd = [ sys.executable, "-m", @@ -21,13 +22,18 @@ def generate_pyproject_validation(dest: str | PathLike[str]): "setuptools", "distutils", "--very-verbose", + *itertools.chain.from_iterable(schema_args), ] subprocess.check_call(cmd) print(f"Validation code generated at: {dest}") + return True -def main(): - generate_pyproject_validation(Path("setuptools/config/_validate_pyproject")) +def main() -> bool: + return generate_pyproject_validation( + Path("setuptools/config/_validate_pyproject"), + schemas=Path("setuptools/config").glob("*.schema.json"), + ) __name__ == '__main__' and main() diff --git a/tools/ppc64le-patch.py b/tools/ppc64le-patch.py index 2a8ff8e0a0c..a0b04ce502f 100644 --- a/tools/ppc64le-patch.py +++ b/tools/ppc64le-patch.py @@ -6,9 +6,9 @@ TODO: is someone tracking this issue? Maybe just move to bionic? """ -import subprocess import collections import os +import subprocess def patch(): diff --git a/tox.ini b/tox.ini index f457ff1feed..cbade4ff462 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,10 @@ commands = usedevelop = True extras = test + check + cover + enabler + type core pass_env = SETUPTOOLS_USE_DISTUTILS @@ -84,7 +88,7 @@ commands = [testenv:generate-validation-code] skip_install = True deps = - validate-pyproject[all]==0.17 + validate-pyproject[all]==0.19 commands = python -m tools.generate_validation_code