From 9fe67ac4e06df262b3aab4856fbdab526ced2ee1 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 9 Feb 2026 15:53:02 -0500 Subject: [PATCH] Enable flake8-type-checking --- docs/conf.py | 1 + ruff.toml | 12 ++++++--- setuptools/command/_requirestxt.py | 6 +++-- setuptools/command/bdist_rpm.py | 8 +++++- setuptools/command/bdist_wheel.py | 12 ++++++--- setuptools/command/build.py | 7 +++--- setuptools/command/build_clib.py | 8 +++++- setuptools/command/build_ext.py | 3 ++- setuptools/command/build_py.py | 8 +++--- setuptools/command/develop.py | 2 +- setuptools/command/dist_info.py | 8 +++--- setuptools/command/editable_wheel.py | 30 ++++++++++++----------- setuptools/command/install.py | 6 +---- setuptools/command/install_lib.py | 8 +++--- setuptools/command/install_scripts.py | 5 +++- setuptools/command/sdist.py | 6 +++-- setuptools/config/__init__.py | 2 +- setuptools/config/pyprojecttoml.py | 3 ++- setuptools/config/setupcfg.py | 5 ++-- setuptools/discovery.py | 4 +-- setuptools/dist.py | 5 ++-- setuptools/extension.py | 6 +++-- setuptools/glob.py | 4 ++- setuptools/monkey.py | 2 +- setuptools/msvc.py | 3 ++- setuptools/tests/test_config_discovery.py | 2 +- 26 files changed, 105 insertions(+), 61 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3aee1141be..693f03bf7f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -175,6 +175,7 @@ ('py:class', 'OptionDummy'), # undocumented ('py:class', 'setuptools.dist.Distribution'), # undocumented ('py:class', 'UnixCCompiler'), # undocumented + ('py:class', 'StrPath'), # type import ('py:exc', 'CompileError'), # undocumented ('py:exc', 'DistutilsExecError'), # undocumented ('py:exc', 'DistutilsFileError'), # undocumented diff --git a/ruff.toml b/ruff.toml index 567074d50a..bb7a2ac1c9 100644 --- a/ruff.toml +++ b/ruff.toml @@ -36,6 +36,9 @@ extend-select = [ "TRY", # tryceratops "UP", # pyupgrade "YTT", # flake8-2020 + # Helps prevent circular imports, reduce runtime cost of typing symbols, + # and prevent leaking implementations details into modules + "TC", # flake8-type-checking ] ignore = [ # upstream @@ -67,14 +70,14 @@ ignore = [ "UP015", # redundant-open-modes, explicit is preferred # Only enforcing return type annotations for public functions "ANN202", # missing-return-type-private-function + # stdlib is not at risk of circular import, is clearly not public API, + # and assume it's gonna be included in the import chain at some point anyway + "TC003", # typing-only-standard-library-import ] [lint.per-file-ignores] # Suppress nuisance warnings about module-import-not-at-top-of-file (E402) due to workaround for #4476 "setuptools/__init__.py" = ["E402"] -# pkg_resources is due for removal, not worth fixing existing errors -"pkg_resources/__init__.py" = ["E402", "ANN204"] -"pkg_resources/tests/test_resources.py" = ["PT031"] [lint.isort] combine-as-imports = true @@ -89,6 +92,9 @@ sections.delayed = ["distutils"] [lint.flake8-annotations] ignore-fully-untyped = true +[lint.flake8-type-checking] +quote-annotations = true + [format] # Enable preview to get hugged parenthesis unwrapping and other nice surprises # See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373 diff --git a/setuptools/command/_requirestxt.py b/setuptools/command/_requirestxt.py index 9029b12514..ec9f7e6f73 100644 --- a/setuptools/command/_requirestxt.py +++ b/setuptools/command/_requirestxt.py @@ -13,13 +13,15 @@ from collections import defaultdict from collections.abc import Mapping from itertools import filterfalse -from typing import TypeVar +from typing import TYPE_CHECKING, TypeVar from jaraco.text import yield_lines from packaging.requirements import Requirement from .. import _reqs -from .._reqs import _StrOrIter + +if TYPE_CHECKING: + from .._reqs import _StrOrIter # dict can work as an ordered set _T = TypeVar("_T") diff --git a/setuptools/command/bdist_rpm.py b/setuptools/command/bdist_rpm.py index 6dbb27002a..3cb99fab4d 100644 --- a/setuptools/command/bdist_rpm.py +++ b/setuptools/command/bdist_rpm.py @@ -1,8 +1,14 @@ -from ..dist import Distribution +from __future__ import annotations + +from typing import TYPE_CHECKING + from ..warnings import SetuptoolsDeprecationWarning import distutils.command.bdist_rpm as orig +if TYPE_CHECKING: + from ..dist import Distribution + class bdist_rpm(orig.bdist_rpm): """ diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index 3bdfa0b35a..89ac74c008 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -16,7 +16,7 @@ from collections.abc import Iterable, Sequence from email.generator import BytesGenerator from glob import iglob -from typing import Literal, cast +from typing import TYPE_CHECKING, Literal, cast from zipfile import ZIP_DEFLATED, ZIP_STORED from packaging import tags, version as _packaging_version @@ -26,10 +26,12 @@ from .._core_metadata import _safe_license_file from .._normalization import safer_name from ..warnings import SetuptoolsDeprecationWarning -from .egg_info import egg_info as egg_info_cls from distutils import log +if TYPE_CHECKING: + from .egg_info import egg_info as egg_info_cls + def safe_version(version: str) -> str: """ @@ -233,7 +235,9 @@ def finalize_options(self) -> None: self.bdist_dir = os.path.join(bdist_base, "wheel") if self.dist_info_dir is None: - egg_info = cast(egg_info_cls, 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" @@ -492,7 +496,7 @@ def license_paths(self) -> Iterable[str]: metadata = self.distribution.get_option_dict("metadata") if setuptools_major_version >= 42: # Setuptools recognizes the license_files option but does not do globbing - patterns = cast(Sequence[str], self.distribution.metadata.license_files) + patterns = cast("Sequence[str]", self.distribution.metadata.license_files) else: # Prior to those, wheel is entirely responsible for handling license files if "license_files" in metadata: diff --git a/setuptools/command/build.py b/setuptools/command/build.py index 54cbb8d2e7..2319036343 100644 --- a/setuptools/command/build.py +++ b/setuptools/command/build.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import Protocol - -from ..dist import Distribution +from typing import TYPE_CHECKING, Protocol from distutils.command.build import build as _build +if TYPE_CHECKING: + from ..dist import Distribution + _ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"} diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index f376f4ce4d..d910504690 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -1,10 +1,16 @@ -from ..dist import Distribution +from __future__ import annotations + +from typing import TYPE_CHECKING + from ..modified import newer_pairwise_group import distutils.command.build_clib as orig from distutils import log from distutils.errors import DistutilsSetupError +if TYPE_CHECKING: + from ..dist import Distribution + class build_clib(orig.build_clib): """ diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index dbb956db8d..39b8a917dc 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -11,7 +11,6 @@ from pathlib import Path from typing import TYPE_CHECKING -from setuptools.dist import Distribution from setuptools.errors import BaseError from setuptools.extension import Extension, Library @@ -20,6 +19,8 @@ from distutils.sysconfig import customize_compiler, get_config_var if TYPE_CHECKING: + from setuptools.dist import Distribution + # Cython not installed on CI tests, causing _build_ext to be `Any` from distutils.command.build_ext import build_ext as _build_ext else: diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 3c7c2d1bd6..42ee414fb6 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -10,18 +10,20 @@ from functools import partial from glob import glob from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any from more_itertools import unique_everseen -from .._path import StrPath, StrPathT -from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning import distutils.command.build_py as orig import distutils.errors from distutils.util import convert_path +if TYPE_CHECKING: + from .._path import StrPath, StrPathT + from ..dist import Distribution + _IMPLICIT_DATA_FILES = ('*.pyi', 'py.typed') diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index 2d468845e5..62df2850fb 100644 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -31,7 +31,7 @@ class develop(Command): def run(self) -> None: # Casting because mypy doesn't understand bool mult conditionals cmd = cast( - list[str], + "list[str]", [sys.executable, '-m', 'pip', 'install', '-e', '.', '--use-pep517'] + ['--target', self.install_dir] * bool(self.install_dir) + ['--no-deps'] * self.no_deps diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py index dca01ff0ce..faa37973fb 100644 --- a/setuptools/command/dist_info.py +++ b/setuptools/command/dist_info.py @@ -7,15 +7,17 @@ import shutil from contextlib import contextmanager from pathlib import Path -from typing import cast +from typing import TYPE_CHECKING, cast from .. import _normalization from .._shutil import rmdir as _rm -from .egg_info import egg_info as egg_info_cls from distutils import log from distutils.core import Command +if TYPE_CHECKING: + from .egg_info import egg_info as egg_info_cls + class dist_info(Command): """ @@ -54,7 +56,7 @@ def finalize_options(self) -> None: project_dir = dist.src_root or os.curdir self.output_dir = Path(self.output_dir or project_dir) - egg_info = cast(egg_info_cls, self.reinitialize_command("egg_info")) + egg_info = cast("egg_info_cls", self.reinitialize_command("egg_info")) egg_info.egg_base = str(self.output_dir) if self.tag_date: diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 6f44f13643..8cff703360 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -29,24 +29,25 @@ from typing import TYPE_CHECKING, Protocol, TypeVar, cast from .. import Command, _normalization, _path, _shutil, errors, namespaces -from .._path import StrPath from ..compat import py310, py312 from ..discovery import find_package_path -from ..dist import Distribution from ..warnings import InformationOnly, SetuptoolsDeprecationWarning -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 -from .egg_info import egg_info as egg_info_cls -from .install import install as install_cls -from .install_scripts import install_scripts as install_scripts_cls if TYPE_CHECKING: from typing_extensions import Self + from .._path import StrPath from .._vendor.wheel.wheelfile import WheelFile + from ..dist import Distribution + from .build import build as build_cls + from .dist_info import dist_info as dist_info_cls + from .egg_info import egg_info as egg_info_cls + from .install import install as install_cls + from .install_scripts import install_scripts as install_scripts_cls + + _P = TypeVar("_P", bound=StrPath) -_P = TypeVar("_P", bound=StrPath) _logger = logging.getLogger(__name__) @@ -150,7 +151,7 @@ def run(self) -> None: def _ensure_dist_info(self): if self.dist_info_dir is None: - dist_info = cast(dist_info_cls, self.reinitialize_command("dist_info")) + dist_info = cast("dist_info_cls", self.reinitialize_command("dist_info")) dist_info.output_dir = self.dist_dir dist_info.ensure_finalized() dist_info.run() @@ -198,16 +199,17 @@ def _configure_build( # egg-info may be generated again to create a manifest (used for package data) egg_info = cast( - egg_info_cls, dist.reinitialize_command("egg_info", reinit_subcommands=True) + "egg_info_cls", + dist.reinitialize_command("egg_info", reinit_subcommands=True), ) egg_info.egg_base = str(tmp_dir) egg_info.ignore_egg_info_in_manifest = True build = cast( - build_cls, dist.reinitialize_command("build", reinit_subcommands=True) + "build_cls", dist.reinitialize_command("build", reinit_subcommands=True) ) install = cast( - install_cls, dist.reinitialize_command("install", reinit_subcommands=True) + "install_cls", dist.reinitialize_command("install", reinit_subcommands=True) ) build.build_platlib = build.build_purelib = build.build_lib = build_lib @@ -222,13 +224,13 @@ def _configure_build( build_scripts.executable = 'python' install_scripts = cast( - install_scripts_cls, dist.get_command_obj("install_scripts") + "install_scripts_cls", dist.get_command_obj("install_scripts") ) install_scripts.no_ep = True build.build_temp = str(tmp_dir) - build_py = cast(build_py_cls, dist.get_command_obj("build_py")) + build_py = cast("build_py_cls", dist.get_command_obj("build_py")) build_py.compile = False build_py.existing_egg_info_dir = self._find_egg_info_dir() diff --git a/setuptools/command/install.py b/setuptools/command/install.py index 19ca601458..668e640f27 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -5,17 +5,13 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Any, ClassVar -from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning import distutils.command.install as orig from distutils.errors import DistutilsArgError if TYPE_CHECKING: - # This is only used for a type-cast, don't import at runtime or it'll cause deprecation warnings - from .easy_install import easy_install as easy_install_cls -else: - easy_install_cls = None + from ..dist import Distribution def __getattr__(name: str): # pragma: no cover diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index 8e1e072710..0d282023a1 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -3,12 +3,14 @@ import os import sys from itertools import product, starmap - -from .._path import StrPath -from ..dist import Distribution +from typing import TYPE_CHECKING import distutils.command.install_lib as orig +if TYPE_CHECKING: + 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""" diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index 5d7d3869ec..ddb75f716e 100644 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -2,13 +2,16 @@ import os import sys +from typing import TYPE_CHECKING from .._path import ensure_directory -from ..dist import Distribution import distutils.command.install_scripts as orig from distutils import log +if TYPE_CHECKING: + from ..dist import Distribution + class install_scripts(orig.install_scripts): """Do normal script install, plus any egg_info wrapper scripts""" diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 17279ac421..473ceb64c3 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -5,15 +5,17 @@ import re from collections.abc import Iterator from itertools import chain -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar from .._importlib import metadata -from ..dist import Distribution from .build import _ORIGINAL_SUBCOMMANDS import distutils.command.sdist as orig from distutils import log +if TYPE_CHECKING: + from ..dist import Distribution + _default_revctrl = list diff --git a/setuptools/config/__init__.py b/setuptools/config/__init__.py index fcc7d008d6..e118e34439 100644 --- a/setuptools/config/__init__.py +++ b/setuptools/config/__init__.py @@ -36,7 +36,7 @@ def _wrapper(*args, **kwargs): ) return fn(*args, **kwargs) - return cast(Fn, _wrapper) + return cast("Fn", _wrapper) read_configuration = _deprecation_notice(setupcfg.read_configuration) diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index fd6c5968c8..0a9d4e0d36 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -19,7 +19,6 @@ from types import TracebackType from typing import TYPE_CHECKING, Any, Callable -from .._path import StrPath from ..errors import FileError, InvalidConfigError from ..warnings import SetuptoolsWarning from . import expand as _expand @@ -30,6 +29,8 @@ from setuptools.dist import Distribution + from .._path import StrPath + _logger = logging.getLogger(__name__) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 121a0febda..ba60a46ab3 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -25,7 +25,6 @@ from packaging.version import InvalidVersion, Version from .. import _static -from .._path import StrPath from ..errors import FileError, OptionError from ..warnings import SetuptoolsDeprecationWarning from . import expand @@ -35,6 +34,8 @@ from setuptools.dist import Distribution + from .._path import StrPath + from distutils.dist import DistributionMetadata SingleCommandOptions: TypeAlias = dict[str, tuple[str, Any]] @@ -103,7 +104,7 @@ def _apply( try: # TODO: Temporary cast until mypy 1.12 is released with upstream fixes from typeshed - _Distribution.parse_config_files(dist, filenames=cast(list[str], filenames)) + _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/discovery.py b/setuptools/discovery.py index 296d3193ab..07e1ceda37 100644 --- a/setuptools/discovery.py +++ b/setuptools/discovery.py @@ -49,14 +49,14 @@ import _distutils_hack.override # noqa: F401 -from ._path import StrPath - from distutils import log from distutils.util import convert_path if TYPE_CHECKING: from setuptools import Distribution + from ._path import StrPath + chain_iter = itertools.chain.from_iterable diff --git a/setuptools/dist.py b/setuptools/dist.py index a224b3ee44..100353832d 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -25,8 +25,6 @@ ) from ._importlib import metadata from ._normalization import _canonicalize_license_expression -from ._path import StrPath -from ._reqs import _StrOrIter from .config import pyprojecttoml, setupcfg from .discovery import ConfigDiscovery from .errors import InvalidConfigError @@ -46,6 +44,9 @@ if TYPE_CHECKING: from typing_extensions import TypeAlias + from ._path import StrPath + from ._reqs import _StrOrIter + __all__ = ['Distribution'] diff --git a/setuptools/extension.py b/setuptools/extension.py index 3e63cbe12a..8c521c213d 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -5,14 +5,15 @@ from collections.abc import Iterable from typing import TYPE_CHECKING -from setuptools._path import StrPath - from .monkey import get_unpatched import distutils.core import distutils.errors import distutils.extension +if TYPE_CHECKING: + from setuptools._path import StrPath + def _have_cython() -> bool: """ @@ -31,6 +32,7 @@ def _have_cython() -> bool: have_pyrex = _have_cython if TYPE_CHECKING: # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962 + from distutils.core import Extension as _Extension else: _Extension = get_unpatched(distutils.core.Extension) diff --git a/setuptools/glob.py b/setuptools/glob.py index 1dfff2cd50..f9a8cb416c 100644 --- a/setuptools/glob.py +++ b/setuptools/glob.py @@ -15,7 +15,9 @@ from typing import TYPE_CHECKING, AnyStr, overload if TYPE_CHECKING: - from _typeshed import BytesPath, StrOrBytesPath, StrPath + from _typeshed import BytesPath, StrOrBytesPath + + from ._path import StrPath __all__ = ["glob", "iglob", "escape"] diff --git a/setuptools/monkey.py b/setuptools/monkey.py index 24bb8180f9..9b3cb94c36 100644 --- a/setuptools/monkey.py +++ b/setuptools/monkey.py @@ -58,7 +58,7 @@ def get_unpatched_class(cls: type[_T]) -> type[_T]: first. """ external_bases = ( - cast(type[_T], cls) + cast("type[_T]", cls) for cls in _get_mro(cls) if not cls.__module__.startswith('setuptools') ) diff --git a/setuptools/msvc.py b/setuptools/msvc.py index f506c8222d..6838351d2d 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -17,7 +17,6 @@ from more_itertools import unique_everseen -from ._path import StrPath from .compat import py310 import distutils.errors @@ -25,6 +24,8 @@ if TYPE_CHECKING: from typing_extensions import LiteralString, NotRequired + from ._path import StrPath + # https://github.com/python/mypy/issues/8166 if not TYPE_CHECKING and platform.system() == 'Windows': import winreg diff --git a/setuptools/tests/test_config_discovery.py b/setuptools/tests/test_config_discovery.py index b5df8203cd..cc2220d56c 100644 --- a/setuptools/tests/test_config_discovery.py +++ b/setuptools/tests/test_config_discovery.py @@ -620,7 +620,7 @@ def _get_dist(dist_path, attrs): if script.exists(): with Path(dist_path): dist = cast( - Distribution, + "Distribution", distutils.core.run_setup("setup.py", {}, stop_after="init"), ) else: