diff --git a/mypy.ini b/mypy.ini index 4fba13c286..19b4de03a3 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,9 +20,9 @@ exclude = (?x)( # 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 diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index da8d56fac2..0a2af0dd53 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -12,6 +12,7 @@ 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 @@ -83,6 +84,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 15a4f63fdd..0cdb64f424 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -14,8 +14,9 @@ from typing import Iterable, Iterator from more_itertools import unique_everseen -from ..warnings import SetuptoolsDeprecationWarning +from ..dist import Distribution +from ..warnings import SetuptoolsDeprecationWarning _IMPLICIT_DATA_FILES = ('*.pyi', 'py.typed') @@ -34,6 +35,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/editable_wheel.py b/setuptools/command/editable_wheel.py index 5ad8ecdd6a..abc7b9f2c8 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -153,6 +153,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 diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 30b62f5f2e..8a2d42ea04 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -24,13 +24,14 @@ from setuptools.command.sdist import walk_revctrl from setuptools.command.setopt import edit_config from setuptools.command import bdist_egg +from setuptools.dist import Distribution import setuptools.unicode_utils as unicode_utils from setuptools.glob import glob -import packaging +import packaging.requirements +import packaging.version from ..warnings import SetuptoolsDeprecationWarning - PY_MAJOR = '{}.{}'.format(*sys.version_info) @@ -520,6 +521,7 @@ def _safe_path(self, path): class manifest_maker(sdist): + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution template = "MANIFEST.in" def initialize_options(self): diff --git a/setuptools/command/install.py b/setuptools/command/install.py index f1ea2adf1d..12ef06f542 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -9,6 +9,7 @@ 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 @@ -20,6 +21,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_lib.py b/setuptools/command/install_lib.py index 3c77c6ebc6..d1577384df 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -3,12 +3,16 @@ import sys from itertools import product, starmap import distutils.command.install_lib as orig + from .._path import StrPath +from ..dist import Distribution 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() diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index f44281b49b..fa5de91f46 100644 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -6,11 +6,14 @@ import sys from .._path import ensure_directory +from ..dist import Distribution 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 diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index a834ba4a78..ca0f712792 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -5,6 +5,7 @@ from itertools import chain from .._importlib import metadata +from ..dist import Distribution from .build import _ORIGINAL_SUBCOMMANDS _default_revctrl = list @@ -43,6 +44,7 @@ class sdist(orig.sdist): ), ] + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution negative_opt = {} README_EXTENSIONS = ['', '.rst', '.txt', '.md'] diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 1cca47cea9..e05c82ae0f 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,12 +1,15 @@ from distutils import log from distutils.command import upload as orig +from setuptools.dist import Distribution 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/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index a83e43bb35..b7d521b7ad 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -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/dist.py b/setuptools/dist.py index 6c73ae792f..03017e56e1 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -265,6 +265,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', []) diff --git a/setuptools/extension.py b/setuptools/extension.py index 8a8a9206fe..96d392ef2b 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -127,6 +127,15 @@ class Extension(_Extension): specified on Windows. (since v63) """ + # 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. diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 738ebf43be..415ece4234 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -6,10 +6,9 @@ __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/test_build_ext.py b/setuptools/tests/test_build_ext.py index 0482fb5d5c..943fc6df2f 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -189,6 +189,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