From ecde60bfa50de155aa88c3410bd00b7dbaa0afd4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 10:54:32 -0400 Subject: [PATCH 1/3] Update mypy.ini from skeleton --- mypy.ini | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index 7de7e5a508..a9d0fdd7df 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,17 +1,27 @@ [mypy] -# CI should test for all versions, local development gets hints for oldest supported -# But our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually. -# python_version = 3.8 +## upstream + +# Is the project well-typed? strict = False + +# Early opt-in even when strict = False warn_unused_ignores = True warn_redundant_casts = True -# required to support namespace packages: https://github.com/python/mypy/issues/14057 +enable_error_code = ignore-without-code + +# Support namespace packages per https://github.com/python/mypy/issues/14057 explicit_package_bases = True disable_error_code = # Disable due to many false positives overload-overlap, +## local + +# CI should test for all versions, local development gets hints for oldest supported +# But our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually. +# python_version = 3.8 + exclude = (?x)( # Avoid scanning Python files in generated folders ^build/ From 3403ffd553a8781f07cadd94ecc1680d8e2003c2 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 11:59:12 -0400 Subject: [PATCH 2/3] Re-enable mypy & Resolve all [ignore-without-code] --- mypy.ini | 2 +- pkg_resources/__init__.py | 2 +- pyproject.toml | 4 ---- setuptools/build_meta.py | 2 +- setuptools/config/expand.py | 13 ++++++++----- setuptools/config/pyprojecttoml.py | 4 ++-- setuptools/msvc.py | 2 +- setuptools/tests/test_build_ext.py | 2 +- setuptools/tests/test_editable_install.py | 14 +++++++++----- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/mypy.ini b/mypy.ini index a9d0fdd7df..26692755ea 100644 --- a/mypy.ini +++ b/mypy.ini @@ -64,6 +64,6 @@ 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.*,setuptools._distutils.*] +[mypy-setuptools.config._validate_pyproject.*,setuptools._vendor.*,setuptools._distutils.*] follow_imports = silent # silent => ignore errors when following imports diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 4e9b83d83d..f1f0ef2535 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2777,7 +2777,7 @@ def load( if require: # We could pass `env` and `installer` directly, # but keeping `*args` and `**kwargs` for backwards compatibility - self.require(*args, **kwargs) # type: ignore + self.require(*args, **kwargs) # type: ignore[arg-type] return self.resolve() def resolve(self) -> _ResolvedEntryPoint: diff --git a/pyproject.toml b/pyproject.toml index bfa4d154a7..eb6ec11041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,7 +216,3 @@ formats = "zip" [tool.setuptools_scm] - - -[tool.pytest-enabler.mypy] -# Disabled due to jaraco/skeleton#143 diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index e730a27f25..a3c83c7002 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -431,7 +431,7 @@ def _build(cmd: list[str]): return _build(['bdist_wheel']) try: - return _build(['bdist_wheel', '--dist-info-dir', metadata_directory]) + return _build(['bdist_wheel', '--dist-info-dir', str(metadata_directory)]) except SystemExit as ex: # pragma: nocover # pypa/setuptools#4683 if "--dist-info-dir not recognized" not in str(ex): diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index e11bcf9b42..8f2040fefa 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -203,7 +203,8 @@ def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType: return sys.modules[name] module = importlib.util.module_from_spec(spec) sys.modules[name] = module # cache (it also ensures `==` works on loaded items) - spec.loader.exec_module(module) # type: ignore + assert spec.loader is not None + spec.loader.exec_module(module) return module @@ -285,10 +286,11 @@ def find_packages( from setuptools.discovery import construct_package_dir - if namespaces: - from setuptools.discovery import PEP420PackageFinder as PackageFinder + # check "not namespaces" first due to python/mypy#6232 + if not namespaces: + from setuptools.discovery import PackageFinder else: - from setuptools.discovery import PackageFinder # type: ignore + from setuptools.discovery import PEP420PackageFinder as PackageFinder root_dir = root_dir or os.curdir where = kwargs.pop('where', ['.']) @@ -359,7 +361,8 @@ def entry_points(text: str, text_source="entry-points") -> dict[str, dict]: entry-point names, and the second level values are references to objects (that correspond to the entry-point value). """ - parser = ConfigParser(default_section=None, delimiters=("=",)) # type: ignore + # Using undocumented behaviour, see python/typeshed#12700 + parser = ConfigParser(default_section=None, delimiters=("=",)) # type: ignore[call-overload] parser.optionxform = str # case sensitive parser.read_string(text, text_source) groups = {k: dict(v.items()) for k, v in parser.items()} diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index 5d95e18b83..e0040cefbd 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -44,8 +44,8 @@ def validate(config: dict, filepath: StrPath) -> bool: trove_classifier = validator.FORMAT_FUNCTIONS.get("trove-classifier") if hasattr(trove_classifier, "_disable_download"): - # Improve reproducibility by default. See issue 31 for validate-pyproject. - trove_classifier._disable_download() # type: ignore + # Improve reproducibility by default. See abravalheri/validate-pyproject#31 + trove_classifier._disable_download() # type: ignore[union-attr] try: return validator.validate(config) diff --git a/setuptools/msvc.py b/setuptools/msvc.py index de4b05f928..7ee685e023 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -1418,7 +1418,7 @@ def VCRuntimeRedist(self) -> str | None: os.path.join(prefix, arch_subdir, crt_dir, vcruntime) for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs) ) - return next(filter(os.path.isfile, candidate_paths), None) + return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682 def return_env(self, exists=True): """ diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py index dab8b41cc9..f3e4ccd364 100644 --- a/setuptools/tests/test_build_ext.py +++ b/setuptools/tests/test_build_ext.py @@ -183,7 +183,7 @@ def get_build_ext_cmd(self, optional: bool, **opts): "eggs.c": "#include missingheader.h\n", ".build": {"lib": {}, "tmp": {}}, } - path.build(files) # type: ignore[arg-type] # jaraco/path#232 + path.build(files) # jaraco/path#232 extension = Extension('spam.eggs', ['eggs.c'], optional=optional) dist = Distribution(dict(ext_modules=[extension])) dist.script_name = 'setup.py' diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index 287367ac18..bdbaa3c7e7 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import stat @@ -8,6 +10,7 @@ from importlib.machinery import EXTENSION_SUFFIXES from pathlib import Path from textwrap import dedent +from typing import Any from unittest.mock import Mock from uuid import uuid4 @@ -840,7 +843,8 @@ class TestOverallBehaviour: version = "3.14159" """ - FLAT_LAYOUT = { + # Any: Would need a TypedDict. Keep it simple for tests + FLAT_LAYOUT: dict[str, Any] = { "pyproject.toml": dedent(PYPROJECT), "MANIFEST.in": EXAMPLE["MANIFEST.in"], "otherfile.py": "", @@ -878,9 +882,9 @@ class TestOverallBehaviour: "otherfile.py": "", "mypkg": { "__init__.py": "", - "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], # type: ignore + "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], }, - "other": FLAT_LAYOUT["mypkg"]["subpackage"], # type: ignore + "other": FLAT_LAYOUT["mypkg"]["subpackage"], }, "namespace": { "pyproject.toml": dedent(PYPROJECT), @@ -888,8 +892,8 @@ class TestOverallBehaviour: "otherfile.py": "", "src": { "mypkg": { - "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], # type: ignore - "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"], # type: ignore + "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"], + "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"], }, }, }, From 1429bf595df92ce2cf901a54d4e17d5d8e44f4f4 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Aug 2024 12:33:34 -0400 Subject: [PATCH 3/3] Fix ConfigHandler generic --- setuptools/config/setupcfg.py | 17 ++++++++++------- setuptools/tests/config/test_setupcfg.py | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 54469f74a3..4fee109e26 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -27,7 +27,6 @@ List, Tuple, TypeVar, - Union, cast, ) @@ -53,7 +52,7 @@ while the second element of the tuple is the option value itself """ AllCommandOptions = Dict["str", SingleCommandOptions] # cmd name => its options -Target = TypeVar("Target", bound=Union["Distribution", "DistributionMetadata"]) +Target = TypeVar("Target", "Distribution", "DistributionMetadata") def read_configuration( @@ -96,7 +95,7 @@ def _apply( filepath: StrPath, other_files: Iterable[StrPath] = (), ignore_option_errors: bool = False, -) -> tuple[ConfigHandler, ...]: +) -> tuple[ConfigMetadataHandler, ConfigOptionsHandler]: """Read configuration from ``filepath`` and applies to the ``dist`` object.""" from setuptools.dist import _Distribution @@ -122,7 +121,7 @@ def _apply( return handlers -def _get_option(target_obj: Target, key: str): +def _get_option(target_obj: Distribution | DistributionMetadata, key: str): """ Given a target object and option key, get that option from the target object, either through a get_{key} method or @@ -134,10 +133,14 @@ def _get_option(target_obj: Target, key: str): return getter() -def configuration_to_dict(handlers: tuple[ConfigHandler, ...]) -> dict: +def configuration_to_dict( + handlers: Iterable[ + ConfigHandler[Distribution] | ConfigHandler[DistributionMetadata] + ], +) -> dict: """Returns configuration data gathered by given handlers as a dict. - :param list[ConfigHandler] handlers: Handlers list, + :param Iterable[ConfigHandler] handlers: Handlers list, usually from parse_configuration() :rtype: dict @@ -254,7 +257,7 @@ def __init__( ensure_discovered: expand.EnsurePackagesDiscovered, ): self.ignore_option_errors = ignore_option_errors - self.target_obj = target_obj + self.target_obj: Target = target_obj self.sections = dict(self._section_options(options)) self.set_options: list[str] = [] self.ensure_discovered = ensure_discovered diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py index 4f0a7349f5..8d95798123 100644 --- a/setuptools/tests/config/test_setupcfg.py +++ b/setuptools/tests/config/test_setupcfg.py @@ -7,7 +7,7 @@ import pytest from packaging.requirements import InvalidRequirement -from setuptools.config.setupcfg import ConfigHandler, read_configuration +from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration from setuptools.dist import Distribution, _Distribution from setuptools.warnings import SetuptoolsDeprecationWarning @@ -16,7 +16,7 @@ from distutils.errors import DistutilsFileError, DistutilsOptionError -class ErrConfigHandler(ConfigHandler): +class ErrConfigHandler(ConfigHandler[Target]): """Erroneous handler. Fails to implement required methods.""" section_prefix = "**err**"