From 0bc324806ce62e93fcbcf9d3f1b018f85176da52 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 25 Oct 2024 13:41:19 -0400 Subject: [PATCH] Merge simple type annotations from typeshed (#4504) * Merge simple type annotations from typeshed * Incorrect TypeAlias usage * Wrap self.always_copy in bool * Update setuptools/command/editable_wheel.py * Add jaraco ignore_missing_imports * Fix type-errors post-merge * More distutils workarounds * Remove StrIter --------- Co-authored-by: Anderson Bravalheri --- mypy.ini | 14 ++++--- ruff.toml | 2 + setuptools/__init__.py | 8 ++-- setuptools/_path.py | 6 +-- setuptools/build_meta.py | 35 +++++++++-------- setuptools/command/bdist_egg.py | 16 +++++++- setuptools/command/bdist_wheel.py | 6 +-- setuptools/command/build_ext.py | 8 ++-- setuptools/command/build_py.py | 18 +++++---- setuptools/command/easy_install.py | 26 +++++++------ setuptools/command/editable_wheel.py | 24 +++++------- setuptools/command/egg_info.py | 6 +-- setuptools/command/install_lib.py | 9 +++-- setuptools/command/install_scripts.py | 2 +- setuptools/config/_apply_pyprojecttoml.py | 22 ++++++----- setuptools/config/expand.py | 16 ++++---- setuptools/config/pyprojecttoml.py | 6 +-- setuptools/config/setupcfg.py | 15 +++++--- setuptools/depends.py | 16 ++++++-- setuptools/discovery.py | 25 +++++++----- setuptools/dist.py | 37 ++++++++++-------- setuptools/errors.py | 47 ++++++++++------------- setuptools/extension.py | 24 +++++++++--- setuptools/glob.py | 4 +- setuptools/installer.py | 3 +- setuptools/logging.py | 2 +- setuptools/package_index.py | 28 +++++++------- setuptools/sandbox.py | 12 +++--- setuptools/warnings.py | 7 +++- 29 files changed, 253 insertions(+), 191 deletions(-) diff --git a/mypy.ini b/mypy.ini index cfd909ba93..cadfa6be59 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,7 +5,7 @@ strict = False # Early opt-in even when strict = False -warn_unused_ignores = True +# warn_unused_ignores = True # Disabled until we have distutils stubs for Python 3.12+ warn_redundant_casts = True enable_error_code = ignore-without-code @@ -34,12 +34,14 @@ exclude = (?x)( # Duplicate module name | ^pkg_resources/tests/data/my-test-package-source/setup.py$ ) - -# 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 +disable_error_code = + # DistributionMetadata.license_files and DistributionMetadata.license_file + # are dynamically patched in setuptools/_core_metadata.py + # and no DistributionMetadata subclass exists in setuptools + attr-defined, + # See issue described below about distutils across Python versions + has-type, # - pkg_resources tests create modules that won't exists statically before the test is run. # Let's ignore all "import-not-found" since, if an import really wasn't found, then the test would fail. diff --git a/ruff.toml b/ruff.toml index 484246aa1e..e154cdf70d 100644 --- a/ruff.toml +++ b/ruff.toml @@ -56,6 +56,8 @@ ignore = [ # Only enforcing return type annotations for public modules "**/tests/**" = ["ANN2"] "tools/**" = ["ANN2"] +# Temporarily disabling enforced return annotations for the setuptool package to progressively type from Typeshed +"setuptools/**" = ["ANN2"] # Suppress nuisance warnings about module-import-not-at-top-of-file (E402) due to workaround for #4476 "setuptools/__init__.py" = ["E402"] "pkg_resources/__init__.py" = ["E402"] diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 3a1cc58aa3..eba86c4f9c 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -89,7 +89,7 @@ def finalize_options(self): _fetch_build_eggs(dist) -def _fetch_build_eggs(dist): +def _fetch_build_eggs(dist: Distribution): try: dist.fetch_build_eggs(dist.setup_requires) except Exception as ex: @@ -120,10 +120,8 @@ 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: TypeAlias = distutils.core.Command + from distutils.core import Command as _Command else: _Command = monkey.get_unpatched(distutils.core.Command) @@ -188,7 +186,7 @@ def _ensure_stringlike(self, option, what, default=None): ) return val - def ensure_string_list(self, option): + def ensure_string_list(self, option: str): r"""Ensure that 'option' is a list of strings. If 'option' is currently a string, we split it either on /,\s*/ or /\s+/, so "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become diff --git a/setuptools/_path.py b/setuptools/_path.py index c7bef83365..5a2bdbd0d4 100644 --- a/setuptools/_path.py +++ b/setuptools/_path.py @@ -5,13 +5,11 @@ import sys from typing import TYPE_CHECKING, Union -if TYPE_CHECKING: - from typing_extensions import TypeAlias - - from more_itertools import unique_everseen if TYPE_CHECKING: + from typing_extensions import TypeAlias + StrPath: TypeAlias = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath else: # Python 3.8 support diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index a3c83c7002..afe0b57b3d 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -38,7 +38,7 @@ import tokenize import warnings from pathlib import Path -from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Union +from typing import TYPE_CHECKING, Iterable, Iterator, List, Mapping, Union import setuptools @@ -53,7 +53,6 @@ if TYPE_CHECKING: from typing_extensions import TypeAlias - __all__ = [ 'get_requires_for_build_sdist', 'get_requires_for_build_wheel', @@ -147,7 +146,7 @@ def suppress_known_deprecation(): yield -_ConfigSettings: TypeAlias = Union[Dict[str, Union[str, List[str], None]], None] +_ConfigSettings: TypeAlias = Union[Mapping[str, Union[str, List[str], None]], None] """ Currently the user can run:: @@ -291,7 +290,9 @@ def _arbitrary_args(self, config_settings: _ConfigSettings) -> Iterator[str]: class _BuildMetaBackend(_ConfigSettingsTranslator): - def _get_build_requires(self, config_settings, requirements): + def _get_build_requires( + self, config_settings: _ConfigSettings, requirements: list[str] + ): sys.argv = [ *sys.argv[:1], *self._global_args(config_settings), @@ -305,7 +306,7 @@ def _get_build_requires(self, config_settings, requirements): return requirements - def run_setup(self, setup_script='setup.py'): + def run_setup(self, setup_script: str = 'setup.py'): # Note that we can reuse our build directory between calls # Correctness comes first, then optimization later __file__ = os.path.abspath(setup_script) @@ -328,13 +329,15 @@ def run_setup(self, setup_script='setup.py'): "setup-py-deprecated.html", ) - def get_requires_for_build_wheel(self, config_settings=None): + def get_requires_for_build_wheel(self, config_settings: _ConfigSettings = None): return self._get_build_requires(config_settings, requirements=[]) - def get_requires_for_build_sdist(self, config_settings=None): + def get_requires_for_build_sdist(self, config_settings: _ConfigSettings = None): return self._get_build_requires(config_settings, requirements=[]) - def _bubble_up_info_directory(self, metadata_directory: str, suffix: str) -> str: + def _bubble_up_info_directory( + self, metadata_directory: StrPath, suffix: str + ) -> str: """ PEP 517 requires that the .dist-info directory be placed in the metadata_directory. To comply, we MUST copy the directory to the root. @@ -347,7 +350,7 @@ def _bubble_up_info_directory(self, metadata_directory: str, suffix: str) -> str # PEP 517 allow other files and dirs to exist in metadata_directory return info_dir.name - def _find_info_directory(self, metadata_directory: str, suffix: str) -> Path: + def _find_info_directory(self, metadata_directory: StrPath, suffix: str) -> Path: for parent, dirs, _ in os.walk(metadata_directory): candidates = [f for f in dirs if f.endswith(suffix)] @@ -359,14 +362,14 @@ def _find_info_directory(self, metadata_directory: str, suffix: str) -> Path: raise errors.InternalError(msg) def prepare_metadata_for_build_wheel( - self, metadata_directory, config_settings=None + self, metadata_directory: StrPath, config_settings: _ConfigSettings = None ): sys.argv = [ *sys.argv[:1], *self._global_args(config_settings), "dist_info", "--output-dir", - metadata_directory, + str(metadata_directory), "--keep-egg-info", ] with no_install_setup_requires(): @@ -462,7 +465,7 @@ def build_editable( self, wheel_directory: StrPath, config_settings: _ConfigSettings = None, - metadata_directory: str | None = None, + metadata_directory: StrPath | None = None, ): # XXX can or should we hide our editable_wheel command normally? info_dir = self._get_dist_info_dir(metadata_directory) @@ -473,11 +476,13 @@ def build_editable( cmd, ".whl", wheel_directory, config_settings ) - def get_requires_for_build_editable(self, config_settings=None): + def get_requires_for_build_editable( + self, config_settings: _ConfigSettings = None + ): return self.get_requires_for_build_wheel(config_settings) def prepare_metadata_for_build_editable( - self, metadata_directory, config_settings=None + self, metadata_directory: StrPath, config_settings: _ConfigSettings = None ): return self.prepare_metadata_for_build_wheel( metadata_directory, config_settings @@ -496,7 +501,7 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): and will eventually be removed. """ - def run_setup(self, setup_script='setup.py'): + def run_setup(self, setup_script: str = 'setup.py'): # In order to maintain compatibility with scripts assuming that # the setup.py script is in a directory on the PYTHONPATH, inject # '' into sys.path. (pypa/setuptools#1642) diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index f3b7150208..c9eee16a5d 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -2,6 +2,8 @@ Build .egg distributions""" +from __future__ import annotations + import marshal import os import re @@ -9,6 +11,7 @@ import textwrap from sysconfig import get_path, get_python_version from types import CodeType +from typing import TYPE_CHECKING, Literal from setuptools import Command from setuptools.extension import Library @@ -18,6 +21,12 @@ from distutils import log from distutils.dir_util import mkpath, remove_tree +if TYPE_CHECKING: + from typing_extensions import TypeAlias + +# Same as zipfile._ZipFileMode from typeshed +_ZipFileMode: TypeAlias = Literal["r", "w", "x", "a"] + def _get_purelib(): return get_path("purelib") @@ -431,7 +440,12 @@ def can_scan(): def make_zipfile( - zip_filename, base_dir, verbose=False, dry_run=False, compress=True, mode='w' + zip_filename, + base_dir, + verbose: bool = False, + dry_run: bool = False, + compress=True, + mode: _ZipFileMode = 'w', ): """Create a zip file from all the files under 'base_dir'. The output zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index f377cd5ee6..81a85e217f 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -257,9 +257,9 @@ def initialize_options(self) -> None: self.relative = False self.owner = None self.group = None - self.universal: bool = False - self.compression: int | str = "deflated" - self.python_tag: str = python_tag() + self.universal = False + self.compression: str | int = "deflated" + self.python_tag = python_tag() self.build_number: str | None = None self.py_limited_api: str | Literal[False] = False self.plat_name_supplied = False diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index e2a88ce218..1b9c313ff5 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -89,8 +89,8 @@ 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 + editable_mode = False + inplace = False def run(self): """Build extensions in build directory, then copy if --inplace""" @@ -410,7 +410,7 @@ def link_shared_object( library_dirs=None, runtime_library_dirs=None, export_symbols=None, - debug=False, + debug: bool = False, extra_preargs=None, extra_postargs=None, build_temp=None, @@ -445,7 +445,7 @@ def link_shared_object( library_dirs=None, runtime_library_dirs=None, export_symbols=None, - debug=False, + debug: bool = False, extra_preargs=None, extra_postargs=None, build_temp=None, diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 584d2c15ac..628a20b40b 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -12,6 +12,8 @@ from more_itertools import unique_everseen +from setuptools._path import StrPath + from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning @@ -48,14 +50,14 @@ def finalize_options(self): del self.__dict__['data_files'] self.__updated_files = [] - def copy_file( + def copy_file( # type: ignore[override] # No overload, str support only self, - infile, - outfile, - preserve_mode=True, - preserve_times=True, - link=None, - level=1, + infile: StrPath, + outfile: StrPath, + preserve_mode: bool = True, + preserve_times: bool = True, + link: str | None = None, + level: object = 1, ): # Overwrite base class to allow using links if link: @@ -141,7 +143,7 @@ def find_data_files(self, package, src_dir): ) return self.exclude_data_files(package, src_dir, files) - def get_outputs(self, include_bytecode=True) -> list[str]: + def get_outputs(self, include_bytecode: bool = True) -> list[str]: # type: ignore[override] # Using a real boolean instead of 0|1 """See :class:`setuptools.commands.build.SubCommand`""" if self.editable_mode: return list(self.get_output_mapping().keys()) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 2a59c74ccd..6c835db593 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -425,7 +425,7 @@ def expand_dirs(self): ] self._expand_attrs(dirs) - def run(self, show_deprecation=True): + def run(self, show_deprecation: bool = True): if show_deprecation: self.announce( "WARNING: The easy_install command is deprecated " @@ -674,7 +674,7 @@ def _tmpdir(self): finally: os.path.exists(tmpdir) and _rmtree(tmpdir) - def easy_install(self, spec, deps=False): + def easy_install(self, spec, deps: bool = False): with self._tmpdir() as tmpdir: if not isinstance(spec, Requirement): if URL_SCHEME(spec): @@ -711,9 +711,9 @@ def easy_install(self, spec, deps=False): else: return self.install_item(spec, dist.location, tmpdir, deps) - def install_item(self, spec, download, tmpdir, deps, install_needed=False): + def install_item(self, spec, download, tmpdir, deps, install_needed: bool = False): # Installation is also needed if file in tmpdir or is not an egg - install_needed = install_needed or self.always_copy + install_needed = install_needed or bool(self.always_copy) install_needed = install_needed or os.path.dirname(download) == tmpdir install_needed = install_needed or not download.endswith('.egg') install_needed = install_needed or ( @@ -759,7 +759,7 @@ def process_distribution( # noqa: C901 self, requirement, dist, - deps=True, + deps: bool = True, *info, ): self.update_pth(dist) @@ -860,7 +860,7 @@ def _load_template(dev_path): raw_bytes = resource_string('setuptools', name) return raw_bytes.decode('utf-8') - def write_script(self, script_name, contents, mode="t", blockers=()): + def write_script(self, script_name, contents, mode: str = "t", blockers=()): """Write an executable file to the scripts directory""" self.delete_blockers( # clean up old .py/.pyw w/o a script [os.path.join(self.script_dir, x) for x in blockers] @@ -1143,7 +1143,7 @@ def install_wheel(self, wheel_path, tmpdir): """ ) - def installation_report(self, req, dist, what="Installed"): + def installation_report(self, req, dist, what: str = "Installed"): """Helpful installation message for display to package users""" msg = "\n%(what)s %(eggloc)s%(extras)s" if self.multi_version and not self.no_report: @@ -2080,7 +2080,7 @@ def from_environment(cls): return cls([cls._sys_executable()]) @classmethod - def from_string(cls, string): + def from_string(cls, string: str): """ Construct a command spec from a simple string representing a command line parseable by shlex.split. @@ -2088,7 +2088,7 @@ def from_string(cls, string): items = shlex.split(string, **cls.split_args) return cls(items) - def install_options(self, script_text): + def install_options(self, script_text: str): self.options = shlex.split(self._extract_options(script_text)) cmdline = subprocess.list2cmdline(self) if not isascii(cmdline): @@ -2218,7 +2218,11 @@ def _get_script_args(cls, type_, name, header, script_text): yield (name, header + script_text) @classmethod - def get_header(cls, script_text="", executable=None): + def get_header( + cls, + script_text: str = "", + executable: str | CommandSpec | Iterable[str] | None = None, + ): """Create a #! line, getting options (if any) from script_text""" cmd = cls.command_spec_class.best().from_param(executable) cmd.install_options(script_text) @@ -2340,7 +2344,7 @@ def load_launcher_manifest(name): return manifest.decode('utf-8') % vars() -def _rmtree(path, ignore_errors=False, onexc=auto_chmod): +def _rmtree(path, ignore_errors: bool = False, onexc=auto_chmod): return py311.shutil_rmtree(path, ignore_errors, onexc) diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 2b21eacbad..c1641a7267 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -23,7 +23,6 @@ from itertools import chain, starmap from pathlib import Path from tempfile import TemporaryDirectory -from types import TracebackType from typing import TYPE_CHECKING, Iterable, Iterator, Mapping, Protocol, TypeVar, cast from .. import Command, _normalization, _path, errors, namespaces @@ -138,7 +137,6 @@ 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 @@ -231,7 +229,6 @@ def _set_editable_mode(self): """Set the ``editable_mode`` flag in the build sub-commands""" dist = self.distribution build = dist.get_command_obj("build") - # TODO: Update typeshed distutils stubs to overload non-None return type by default for cmd_name in build.get_sub_commands(): cmd = dist.get_command_obj(cmd_name) if hasattr(cmd, "editable_mode"): @@ -380,16 +377,15 @@ def _select_strategy( class EditableStrategy(Protocol): - def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): ... - + def __call__( + self, wheel: WheelFile, files: list[str], mapping: Mapping[str, str] + ): ... def __enter__(self) -> Self: ... - def __exit__( self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, - /, + _exc_type: object, + _exc_value: object, + _traceback: object, ) -> object: ... @@ -399,7 +395,7 @@ def __init__(self, dist: Distribution, name: str, path_entries: list[Path]): self.name = name self.path_entries = path_entries - def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: Mapping[str, str]): entries = "\n".join(str(p.resolve()) for p in self.path_entries) contents = _encode_pth(f"{entries}\n") wheel.writestr(f"__editable__.{self.name}.pth", contents) @@ -444,7 +440,7 @@ def __init__( self._file = dist.get_command_obj("build_py").copy_file super().__init__(dist, name, [self.auxiliary_dir]) - def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: Mapping[str, str]): self._create_links(files, mapping) super().__call__(wheel, files, mapping) @@ -461,7 +457,7 @@ def _create_file(self, relative_output: str, src_file: str, link=None): dest.parent.mkdir(parents=True) self._file(src_file, dest, link=link) - def _create_links(self, outputs, output_mapping): + def _create_links(self, outputs, output_mapping: Mapping[str, str]): self.auxiliary_dir.mkdir(parents=True, exist_ok=True) link_type = "sym" if _can_symlink_files(self.auxiliary_dir) else "hard" normalised = ((self._normalize_output(k), v) for k, v in output_mapping.items()) @@ -538,7 +534,7 @@ def get_implementation(self) -> Iterator[tuple[str, bytes]]: content = _encode_pth(f"import {finder}; {finder}.install()") yield (f"__editable__.{self.name}.pth", content) - def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: Mapping[str, str]): for file, content in self.get_implementation(): wheel.writestr(file, content) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index bc6c677878..7b9f8f0b72 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -252,7 +252,7 @@ def _get_egg_basename(self, py_version=PY_MAJOR, platform=None): """Compute filename of the output egg. Private API.""" return _egg_basename(self.egg_name, self.egg_version, py_version, platform) - def write_or_delete_file(self, what, filename, data, force=False): + def write_or_delete_file(self, what, filename, data, force: bool = False): """Write `data` to `filename` or delete if empty If `data` is non-empty, this routine is the same as ``write_file()``. @@ -324,7 +324,7 @@ def find_sources(self): class FileList(_FileList): # Implementations of the various MANIFEST.in commands - def __init__(self, warn=None, debug_print=None, ignore_egg_info_dir=False): + def __init__(self, warn=None, debug_print=None, ignore_egg_info_dir: bool = False): super().__init__(warn, debug_print) self.ignore_egg_info_dir = ignore_egg_info_dir @@ -690,7 +690,7 @@ def overwrite_arg(cmd, basename, filename): write_arg(cmd, basename, filename, True) -def write_arg(cmd, basename, filename, force=False): +def write_arg(cmd, basename, filename, force: bool = False): argname = os.path.splitext(basename)[0] value = getattr(cmd.distribution, argname, None) if value is not None: diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index 292f07ab6e..53b68f6363 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -95,10 +95,11 @@ def copy_tree( self, infile: StrPath, outfile: str, - preserve_mode=True, - preserve_times=True, - preserve_symlinks=False, - level=1, + # override: Using actual booleans + preserve_mode: bool = True, # type: ignore[override] + preserve_times: bool = True, # type: ignore[override] + preserve_symlinks: bool = False, # type: ignore[override] + level: object = 1, ) -> list[str]: assert preserve_mode and preserve_times and not preserve_symlinks exclude = self.get_exclusions() diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index 7b90611d1c..f1ccc2bbf8 100644 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -56,7 +56,7 @@ def _install_ep_scripts(self): for args in writer.get_args(dist, cmd.as_header()): self.write_script(*args) - def write_script(self, script_name, contents, mode="t", *ignored): + def write_script(self, script_name, contents, mode: str = "t", *ignored): """Write an executable file to the scripts directory""" from setuptools.command.easy_install import chmod, current_umask diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 16fe753b58..2b7fe7bd80 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -30,12 +30,12 @@ from setuptools._importlib import metadata from setuptools.dist import Distribution - from distutils.dist import _OptionsList + from distutils.dist import _OptionsList # Comes from typeshed + EMPTY: Mapping = MappingProxyType({}) # Immutable dict-like _ProjectReadmeValue: TypeAlias = Union[str, Dict[str, str]] -_CorrespFn: TypeAlias = Callable[["Distribution", Any, StrPath], None] -_Correspondence: TypeAlias = Union[str, _CorrespFn] +_Correspondence: TypeAlias = Callable[["Distribution", Any, Union[StrPath, None]], None] _T = TypeVar("_T") _logger = logging.getLogger(__name__) @@ -149,7 +149,9 @@ def _guess_content_type(file: str) -> str | None: raise ValueError(f"Undefined content type for {file}, {msg}") -def _long_description(dist: Distribution, val: _ProjectReadmeValue, root_dir: StrPath): +def _long_description( + dist: Distribution, val: _ProjectReadmeValue, root_dir: StrPath | None +): from setuptools.config import expand file: str | tuple[()] @@ -171,7 +173,7 @@ def _long_description(dist: Distribution, val: _ProjectReadmeValue, root_dir: St dist._referenced_files.add(file) -def _license(dist: Distribution, val: dict, root_dir: StrPath): +def _license(dist: Distribution, val: dict, root_dir: StrPath | None): from setuptools.config import expand if "file" in val: @@ -181,7 +183,7 @@ def _license(dist: Distribution, val: dict, root_dir: StrPath): _set_config(dist, "license", val["text"]) -def _people(dist: Distribution, val: list[dict], _root_dir: StrPath, kind: str): +def _people(dist: Distribution, val: list[dict], _root_dir: StrPath | None, kind: str): field = [] email_field = [] for person in val: @@ -199,24 +201,24 @@ def _people(dist: Distribution, val: list[dict], _root_dir: StrPath, kind: str): _set_config(dist, f"{kind}_email", ", ".join(email_field)) -def _project_urls(dist: Distribution, val: dict, _root_dir): +def _project_urls(dist: Distribution, val: dict, _root_dir: StrPath | None): _set_config(dist, "project_urls", val) -def _python_requires(dist: Distribution, val: str, _root_dir): +def _python_requires(dist: Distribution, val: str, _root_dir: StrPath | None): from packaging.specifiers import SpecifierSet _set_config(dist, "python_requires", SpecifierSet(val)) -def _dependencies(dist: Distribution, val: list, _root_dir): +def _dependencies(dist: Distribution, val: list, _root_dir: StrPath | None): if getattr(dist, "install_requires", []): msg = "`install_requires` overwritten in `pyproject.toml` (dependencies)" SetuptoolsWarning.emit(msg) dist.install_requires = val -def _optional_dependencies(dist: Distribution, val: dict, _root_dir): +def _optional_dependencies(dist: Distribution, val: dict, _root_dir: StrPath | None): if getattr(dist, "extras_require", None): msg = "`extras_require` overwritten in `pyproject.toml` (optional-dependencies)" SetuptoolsWarning.emit(msg) diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index 8f2040fefa..81caf1c35e 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -45,7 +45,7 @@ from setuptools.dist import Distribution _K = TypeVar("_K") -_V = TypeVar("_V", covariant=True) +_V_co = TypeVar("_V_co", covariant=True) class StaticModule: @@ -354,7 +354,7 @@ def canonic_data_files( ] -def entry_points(text: str, text_source="entry-points") -> dict[str, dict]: +def entry_points(text: str, text_source: str = "entry-points") -> dict[str, dict]: """Given the contents of entry-points file, process it into a 2-level dictionary (``dict[str, dict[str, str]]``). The first level keys are entry-point groups, the second level keys are @@ -398,7 +398,7 @@ def __exit__( exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, - ) -> None: + ): if self._called: self._dist.set_defaults.analyse_name() # Now we can set a default name @@ -413,7 +413,7 @@ def package_dir(self) -> Mapping[str, str]: return LazyMappingProxy(self._get_package_dir) -class LazyMappingProxy(Mapping[_K, _V]): +class LazyMappingProxy(Mapping[_K, _V_co]): """Mapping proxy that delays resolving the target object, until really needed. >>> def obtain_mapping(): @@ -427,16 +427,16 @@ class LazyMappingProxy(Mapping[_K, _V]): 'other value' """ - def __init__(self, obtain_mapping_value: Callable[[], Mapping[_K, _V]]): + def __init__(self, obtain_mapping_value: Callable[[], Mapping[_K, _V_co]]): self._obtain = obtain_mapping_value - self._value: Mapping[_K, _V] | None = None + self._value: Mapping[_K, _V_co] | None = None - def _target(self) -> Mapping[_K, _V]: + def _target(self) -> Mapping[_K, _V_co]: if self._value is None: self._value = self._obtain() return self._value - def __getitem__(self, key: _K) -> _V: + def __getitem__(self, key: _K) -> _V_co: return self._target()[key] def __len__(self) -> int: diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index e0040cefbd..f5bda2ce34 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -63,7 +63,7 @@ def validate(config: dict, filepath: StrPath) -> bool: def apply_configuration( dist: Distribution, filepath: StrPath, - ignore_option_errors=False, + ignore_option_errors: bool = False, ) -> Distribution: """Apply the configuration from a ``pyproject.toml`` file into an existing distribution object. @@ -74,8 +74,8 @@ def apply_configuration( def read_configuration( filepath: StrPath, - expand=True, - ignore_option_errors=False, + expand: bool = True, + ignore_option_errors: bool = False, dist: Distribution | None = None, ) -> dict[str, Any]: """Read given configuration file and returns options from it as a dict. diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 5b4e1e8d95..35fe4f9aa9 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -41,22 +41,25 @@ from . import expand if TYPE_CHECKING: + from typing_extensions import TypeAlias + from setuptools.dist import Distribution from distutils.dist import DistributionMetadata -SingleCommandOptions = Dict["str", Tuple["str", Any]] +SingleCommandOptions: TypeAlias = 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 (e.g. the name of the configuration file where it was read from), while the second element of the tuple is the option value itself """ -AllCommandOptions = Dict["str", SingleCommandOptions] # cmd name => its options +AllCommandOptions: TypeAlias = Dict[str, SingleCommandOptions] +"""cmd name => its options""" Target = TypeVar("Target", "Distribution", "DistributionMetadata") def read_configuration( - filepath: StrPath, find_others=False, ignore_option_errors=False + filepath: StrPath, find_others: bool = False, ignore_option_errors: bool = False ) -> dict: """Read given configuration file and returns options from it as a dict. @@ -158,7 +161,7 @@ def configuration_to_dict( def parse_configuration( distribution: Distribution, command_options: AllCommandOptions, - ignore_option_errors=False, + ignore_option_errors: bool = False, ) -> tuple[ConfigMetadataHandler, ConfigOptionsHandler]: """Performs additional parsing of configuration options for a distribution. @@ -379,7 +382,7 @@ def parser(value): return parser - def _parse_file(self, value, root_dir: StrPath): + def _parse_file(self, value, root_dir: StrPath | None): """Represents value as a string, allowing including text from nearest files using `file:` directive. @@ -547,7 +550,7 @@ def __init__( ignore_option_errors: bool, ensure_discovered: expand.EnsurePackagesDiscovered, package_dir: dict | None = None, - root_dir: StrPath = os.curdir, + root_dir: StrPath | None = os.curdir, ): super().__init__(target_obj, options, ignore_option_errors, ensure_discovered) self.package_dir = package_dir diff --git a/setuptools/depends.py b/setuptools/depends.py index 9398b95331..e73f06808e 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import dis import marshal @@ -15,7 +17,13 @@ class Require: """A prerequisite to building or installing a distribution""" def __init__( - self, name, requested_version, module, homepage='', attribute=None, format=None + self, + name, + requested_version, + module, + homepage: str = '', + attribute=None, + format=None, ): if format is None and requested_version is not None: format = Version @@ -43,7 +51,7 @@ def version_ok(self, version): and self.format(version) >= self.requested_version ) - def get_version(self, paths=None, default="unknown"): + def get_version(self, paths=None, default: str = "unknown"): """Get version number of installed module, 'None', or 'default' Search 'paths' for module. If not found, return 'None'. If found, @@ -98,7 +106,7 @@ def empty(): # XXX it'd be better to test assertions about bytecode instead. if not sys.platform.startswith('java') and sys.platform != 'cli': - def get_module_constant(module, symbol, default=-1, paths=None): + def get_module_constant(module, symbol, default: str | int = -1, paths=None): """Find 'module' by searching 'paths', and extract 'symbol' Return 'None' if 'module' does not exist on 'paths', or it does not define @@ -126,7 +134,7 @@ def get_module_constant(module, symbol, default=-1, paths=None): return extract_constant(code, symbol, default) - def extract_constant(code, symbol, default=-1): + def extract_constant(code, symbol, default: str | int = -1): """Extract the constant value of 'symbol' from 'code' If the name 'symbol' is bound to a constant value by the Python code diff --git a/setuptools/discovery.py b/setuptools/discovery.py index 577be2f16b..09d1e2f474 100644 --- a/setuptools/discovery.py +++ b/setuptools/discovery.py @@ -41,10 +41,11 @@ import itertools import os +from collections.abc import Iterator 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, Mapping import _distutils_hack.override # noqa: F401 @@ -53,13 +54,11 @@ from distutils import log from distutils.util import convert_path -StrIter = Iterator[str] - -chain_iter = itertools.chain.from_iterable - if TYPE_CHECKING: from setuptools import Distribution +chain_iter = itertools.chain.from_iterable + def _valid_name(path: StrPath) -> bool: # Ignore invalid names that cannot be imported directly @@ -124,7 +123,9 @@ def find( ) @classmethod - def _find_iter(cls, where: StrPath, exclude: _Filter, include: _Filter) -> StrIter: + def _find_iter( + cls, where: StrPath, exclude: _Filter, include: _Filter + ) -> Iterator[str]: raise NotImplementedError @@ -136,7 +137,9 @@ class PackageFinder(_Finder): ALWAYS_EXCLUDE = ("ez_setup", "*__pycache__") @classmethod - def _find_iter(cls, where: StrPath, exclude: _Filter, include: _Filter) -> StrIter: + def _find_iter( + cls, where: StrPath, exclude: _Filter, include: _Filter + ) -> Iterator[str]: """ All the packages found in 'where' that pass the 'include' filter, but not the 'exclude' filter. @@ -185,7 +188,9 @@ class ModuleFinder(_Finder): """ @classmethod - def _find_iter(cls, where: StrPath, exclude: _Filter, include: _Filter) -> StrIter: + def _find_iter( + cls, where: StrPath, exclude: _Filter, include: _Filter + ) -> Iterator[str]: for file in glob(os.path.join(where, "*.py")): module, _ext = os.path.splitext(os.path.basename(file)) @@ -328,7 +333,9 @@ def _package_dir(self) -> dict[str, str]: return {} return self.dist.package_dir - def __call__(self, force=False, name=True, ignore_ext_modules=False): + def __call__( + self, force: bool = False, name: bool = True, ignore_ext_modules: bool = False + ): """Automatically discover missing configuration fields and modifies the given ``distribution`` object in-place. diff --git a/setuptools/dist.py b/setuptools/dist.py index d6b8e08214..1348ca61c2 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -6,6 +6,7 @@ import os import re import sys +from collections.abc import Iterable from glob import iglob from pathlib import Path from typing import ( @@ -24,12 +25,15 @@ from packaging.specifiers import InvalidSpecifier, SpecifierSet from packaging.version import Version +from setuptools._path import StrPath + from . import ( _entry_points, _reqs, command as _, # noqa: F401 # imported for side-effects ) from ._importlib import metadata +from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery from .monkey import get_unpatched @@ -235,10 +239,8 @@ 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: TypeAlias = distutils.core.Distribution + from distutils.core import Distribution as _Distribution else: _Distribution = get_unpatched(distutils.core.Distribution) @@ -302,7 +304,8 @@ class Distribution(_Distribution): # Used by build_py, editable_wheel and install_lib commands for legacy namespaces namespace_packages: list[str] #: :meta private: DEPRECATED - def __init__(self, attrs: MutableMapping | None = None) -> None: + # Any: Dynamic assignment results in Incompatible types in assignment + def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None: have_package_data = hasattr(self, "package_data") if not have_package_data: self.package_data: dict[str, list[str]] = {} @@ -311,9 +314,9 @@ def __init__(self, attrs: MutableMapping | None = None) -> None: 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', []) - self.setup_requires = attrs.pop('setup_requires', []) + self.src_root: str | None = attrs.pop("src_root", None) + self.dependency_links: list[str] = attrs.pop('dependency_links', []) + self.setup_requires: list[str] = attrs.pop('setup_requires', []) for ep in metadata.entry_points(group='distutils.setup_keywords'): vars(self).setdefault(ep.name, None) @@ -515,7 +518,7 @@ def _parse_config_files(self, filenames=None): # noqa: C901 except ValueError as e: raise DistutilsOptionError(e) from e - def warn_dash_deprecation(self, opt, section): + def warn_dash_deprecation(self, opt: str, section: str): if section in ( 'options.extras_require', 'options.data_files', @@ -557,7 +560,7 @@ def _setuptools_commands(self): # during bootstrapping, distribution doesn't exist return [] - def make_option_lowercase(self, opt, section): + def make_option_lowercase(self, opt: str, section: str): if section != 'metadata' or opt.islower(): return opt @@ -621,7 +624,7 @@ def _set_command_options(self, command_obj, option_dict=None): # noqa: C901 except ValueError as e: raise DistutilsOptionError(e) from e - def _get_project_config_files(self, filenames): + def _get_project_config_files(self, filenames: Iterable[StrPath] | None): """Add default file and split between INI and TOML""" tomlfiles = [] standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml") @@ -633,7 +636,11 @@ def _get_project_config_files(self, filenames): tomlfiles = [standard_project_metadata] return filenames, tomlfiles - def parse_config_files(self, filenames=None, ignore_option_errors=False): + def parse_config_files( + self, + filenames: Iterable[StrPath] | None = None, + ignore_option_errors: bool = False, + ): """Parses configuration files from various levels and loads configuration. """ @@ -650,7 +657,7 @@ def parse_config_files(self, filenames=None, ignore_option_errors=False): self._finalize_requires() self._finalize_license_files() - def fetch_build_eggs(self, requires): + def fetch_build_eggs(self, requires: _StrOrIter): """Resolve pre-setup requirements""" from .installer import _fetch_build_eggs @@ -721,7 +728,7 @@ def fetch_build_egg(self, req): return fetch_build_egg(self, req) - def get_command_class(self, command): + def get_command_class(self, command: str): """Pluggable version of get_command_class()""" if command in self.cmdclass: return self.cmdclass[command] @@ -775,7 +782,7 @@ def include(self, **attrs): else: self._include_misc(k, v) - def exclude_package(self, package): + def exclude_package(self, package: str): """Remove packages, modules, and extensions in named package""" pfx = package + '.' @@ -796,7 +803,7 @@ def exclude_package(self, package): if p.name != package and not p.name.startswith(pfx) ] - def has_contents_for(self, package): + def has_contents_for(self, package: str): """Return true if 'exclude_package(package)' would do something""" pfx = package + '.' diff --git a/setuptools/errors.py b/setuptools/errors.py index 90fcf7170e..990ecbf4e2 100644 --- a/setuptools/errors.py +++ b/setuptools/errors.py @@ -5,45 +5,40 @@ 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: 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 +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 # The root error class in the hierarchy -BaseError: TypeAlias = _distutils_errors.DistutilsError +BaseError = _distutils_errors.DistutilsError -class InvalidConfigError(OptionError): +class InvalidConfigError(OptionError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+ """Error used for invalid configurations.""" -class RemovedConfigError(OptionError): +class RemovedConfigError(OptionError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+ """Error used for configurations that were deprecated and removed.""" -class RemovedCommandError(BaseError, RuntimeError): +class RemovedCommandError(BaseError, RuntimeError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+ """Error used for commands that have been removed in setuptools. Since ``setuptools`` is built on ``distutils``, simply removing a command @@ -53,7 +48,7 @@ class RemovedCommandError(BaseError, RuntimeError): """ -class PackageDiscoveryError(BaseError, RuntimeError): +class PackageDiscoveryError(BaseError, RuntimeError): # type: ignore[valid-type, misc] # distutils imports are `Any` on python 3.12+ """Impossible to perform automatic discovery of packages and/or modules. The current project layout or given discovery options can lead to problems when diff --git a/setuptools/extension.py b/setuptools/extension.py index cbefc72508..79bcc203e9 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -4,6 +4,8 @@ import re from typing import TYPE_CHECKING +from setuptools._path import StrPath + from .monkey import get_unpatched import distutils.core @@ -27,10 +29,8 @@ 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: TypeAlias = distutils.core.Extension + from distutils.core import Extension as _Extension else: _Extension = get_unpatched(distutils.core.Extension) @@ -52,7 +52,7 @@ class Extension(_Extension): the full name of the extension, including any packages -- ie. *not* a filename or pathname, but Python dotted name - :arg list[str] sources: + :arg list[str|os.PathLike[str]] sources: 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), @@ -140,11 +140,23 @@ class Extension(_Extension): _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): + def __init__( + self, + name: str, + sources: list[StrPath], + *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 = py_limited_api - super().__init__(name, sources, *args, **kw) + super().__init__( + name, + sources, # type: ignore[arg-type] # Vendored version of setuptools supports PathLike + *args, + **kw, + ) def _convert_pyx_sources_to_lang(self): """ diff --git a/setuptools/glob.py b/setuptools/glob.py index ffe0ae92cb..97aca44314 100644 --- a/setuptools/glob.py +++ b/setuptools/glob.py @@ -13,7 +13,7 @@ __all__ = ["glob", "iglob", "escape"] -def glob(pathname, recursive=False): +def glob(pathname, recursive: bool = False): """Return a list of paths matching a pathname pattern. The pattern may contain simple shell-style wildcards a la @@ -27,7 +27,7 @@ def glob(pathname, recursive=False): return list(iglob(pathname, recursive=recursive)) -def iglob(pathname, recursive=False): +def iglob(pathname, recursive: bool = False): """Return an iterator which yields the paths matching a pathname pattern. The pattern may contain simple shell-style wildcards a la diff --git a/setuptools/installer.py b/setuptools/installer.py index ce3559cd93..ba2d808d49 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -6,6 +6,7 @@ from functools import partial from . import _reqs +from ._reqs import _StrOrIter from .warnings import SetuptoolsDeprecationWarning from .wheel import Wheel @@ -30,7 +31,7 @@ def fetch_build_egg(dist, req): return _fetch_build_egg_no_warn(dist, req) -def _fetch_build_eggs(dist, requires): +def _fetch_build_eggs(dist, requires: _StrOrIter): import pkg_resources # Delay import to avoid unnecessary side-effects _DeprecatedInstaller.emit(stacklevel=3) diff --git a/setuptools/logging.py b/setuptools/logging.py index e9674c5a81..c6d25a6b1e 100644 --- a/setuptools/logging.py +++ b/setuptools/logging.py @@ -35,6 +35,6 @@ def configure(): distutils.dist.log = distutils.log -def set_threshold(level): +def set_threshold(level: int): logging.root.setLevel(level * 10) return set_threshold.unpatched(level) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 9b3769fac9..2a05b35c4f 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -303,20 +303,20 @@ class PackageIndex(Environment): def __init__( self, - index_url="https://pypi.org/simple/", + index_url: str = "https://pypi.org/simple/", hosts=('*',), ca_bundle=None, - verify_ssl=True, + verify_ssl: bool = True, *args, **kw, ): super().__init__(*args, **kw) self.index_url = index_url + "/"[: not index_url.endswith('/')] - self.scanned_urls = {} - self.fetched_urls = {} - self.package_pages = {} + self.scanned_urls: dict = {} + self.fetched_urls: dict = {} + self.package_pages: dict = {} self.allows = re.compile('|'.join(map(translate, hosts))).match - self.to_scan = [] + self.to_scan: list = [] self.opener = urllib.request.urlopen def add(self, dist): @@ -328,7 +328,7 @@ def add(self, dist): return super().add(dist) # FIXME: 'PackageIndex.process_url' is too complex (14) - def process_url(self, url, retrieve=False): # noqa: C901 + def process_url(self, url, retrieve: bool = False): # noqa: C901 """Evaluate a URL as a possible download, and maybe retrieve it""" if url in self.scanned_urls and not retrieve: return @@ -381,7 +381,7 @@ def process_url(self, url, retrieve=False): # noqa: C901 if url.startswith(self.index_url) and getattr(f, 'code', None) != 404: page = self.process_index(url, page) - def process_filename(self, fn, nested=False): + def process_filename(self, fn, nested: bool = False): # process filenames or directories if not os.path.exists(fn): self.warn("Not found: %s", fn) @@ -397,7 +397,7 @@ def process_filename(self, fn, nested=False): self.debug("Found: %s", fn) list(map(self.add, dists)) - def url_ok(self, url, fatal=False): + def url_ok(self, url, fatal: bool = False): s = URL_SCHEME(url) is_file = s and s.group(1).lower() == 'file' if is_file or self.allows(urllib.parse.urlparse(url)[1]): @@ -603,9 +603,9 @@ def fetch_distribution( # noqa: C901 # is too complex (14) # FIXME self, requirement, tmpdir, - force_scan=False, - source=False, - develop_ok=False, + force_scan: bool = False, + source: bool = False, + develop_ok: bool = False, local_index=None, ): """Obtain a distribution suitable for fulfilling `requirement` @@ -681,7 +681,9 @@ def find(req, env=None): self.info("Best match: %s", dist) return dist.clone(location=dist.download_location) - def fetch(self, requirement, tmpdir, force_scan=False, source=False): + def fetch( + self, requirement, tmpdir, force_scan: bool = False, source: bool = False + ): """Obtain a file suitable for fulfilling `requirement` DEPRECATED; use the ``fetch_distribution()`` method now instead. For diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 9c2c78a32c..ec5e79da05 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -295,10 +295,10 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: object, - exc_value: object, - traceback: object, - ) -> None: + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ): self._active = False builtins.open = _open self._copy(_os) @@ -416,7 +416,7 @@ def _remap_pair(self, operation, src, dst, *args, **kw): class DirectorySandbox(AbstractSandbox): """Restrict operations to a single subdirectory - pseudo-chroot""" - write_ops = dict.fromkeys([ + write_ops: dict[str, None] = dict.fromkeys([ "open", "chmod", "chown", @@ -491,7 +491,7 @@ def _remap_pair(self, operation, src, dst, *args, **kw): self._violation(operation, src, dst, *args, **kw) return (src, dst) - def open(self, file, flags, mode=0o777, *args, **kw): + def open(self, file, flags, mode: int = 0o777, *args, **kw): """Called for low-level os.open()""" if flags & WRITE_FLAGS and not self._ok(file): self._violation("os.open", file, flags, mode, *args, **kw) diff --git a/setuptools/warnings.py b/setuptools/warnings.py index 8c94bc96e6..f0ef616725 100644 --- a/setuptools/warnings.py +++ b/setuptools/warnings.py @@ -12,9 +12,12 @@ from datetime import date from inspect import cleandoc from textwrap import indent -from typing import Tuple +from typing import TYPE_CHECKING, Tuple -_DueDate = Tuple[int, int, int] # time tuple +if TYPE_CHECKING: + from typing_extensions import TypeAlias + +_DueDate: TypeAlias = Tuple[int, int, int] # time tuple _INDENT = 8 * " " _TEMPLATE = f"""{80 * '*'}\n{{details}}\n{80 * '*'}"""