diff --git a/stubs/setuptools/@tests/test_cases/check_setup.py b/stubs/setuptools/@tests/test_cases/check_setup.py new file mode 100644 index 000000000000..b6bac7b01de5 --- /dev/null +++ b/stubs/setuptools/@tests/test_cases/check_setup.py @@ -0,0 +1,23 @@ +from typing_extensions import assert_type + +from setuptools import Command as setuptools_Command, Distribution as setuptools_Distribution, setup +from setuptools._distutils.cmd import Command as distutils_Command +from setuptools._distutils.dist import Distribution as distutils_Distribution + +# Ensure that any distutils-derived classes are usable w/o type variance issues +assert_type( + setup( + cmdclass=dict[str, type[distutils_Command]](), + command_obj=dict[str, distutils_Command](), + distclass=distutils_Distribution, + ), + distutils_Distribution, +) +assert_type( + setup( + cmdclass=dict[str, type[setuptools_Command]](), + command_obj=dict[str, setuptools_Command](), + distclass=setuptools_Distribution, + ), + setuptools_Distribution, +) diff --git a/stubs/setuptools/setuptools/__init__.pyi b/stubs/setuptools/setuptools/__init__.pyi index eaea1f558070..45c1a79416b1 100644 --- a/stubs/setuptools/setuptools/__init__.pyi +++ b/stubs/setuptools/setuptools/__init__.pyi @@ -1,4 +1,4 @@ -from _typeshed import StrPath +from _typeshed import Incomplete, StrPath from abc import abstractmethod from collections.abc import ItemsView, Iterable, Mapping, Sequence from typing import Any, Literal, Protocol, TypedDict, TypeVar, overload, type_check_only @@ -36,9 +36,7 @@ from .warnings import SetuptoolsDeprecationWarning as SetuptoolsDeprecationWarni _CommandT = TypeVar("_CommandT", bound=_Command) _DistributionT = TypeVar("_DistributionT", bound=_Distribution, default=Distribution) -_T = TypeVar("_T") _KT = TypeVar("_KT") -_VT = TypeVar("_VT") _VT_co = TypeVar("_VT_co", covariant=True) __all__ = [ @@ -54,22 +52,23 @@ __all__ = [ __version__: str +# We need any Command subclass to be valid +# Any: pyright would accept using covariance in __setitem__, but mypy won't let a dict be assignable to this protocol +# This is unsound, but it's a quirk of setuptools' internals @type_check_only -class _DictLike(Protocol[_KT, _VT_co]): # type: ignore[misc] # Covariant type as parameter - @overload - def get(self, key: _KT, /) -> _VT_co | None: ... - @overload - def get(self, key: _KT, default: _VT_co, /) -> _VT_co: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter - @overload - def get(self, key: _KT, default: _T, /) -> _VT_co | _T: ... +class _DictLike(Protocol[_KT, _VT_co]): + # See note about using _VT_co instead of Any + def get(self, key: _KT, default: Any | None = None, /) -> _VT_co | None: ... def items(self) -> ItemsView[_KT, _VT_co]: ... def keys(self) -> Iterable[_KT]: ... def __getitem__(self, key: _KT, /) -> _VT_co: ... - def __contains__(self, x: Any, /) -> bool: ... + def __contains__(self, x: object, /) -> bool: ... @type_check_only -class _MutableDictLike(_DictLike[_KT, _VT], Protocol): - def __setitem__(self, key: _KT, value: _VT, /) -> None: ... +class _MutableDictLike(_DictLike[_KT, _VT_co], Protocol): + # See note about using _VT_co instead of Any + def __setitem__(self, key: _KT, value: Any, /) -> None: ... + def setdefault(self, key: _KT, default: Any, /) -> _VT_co: ... @type_check_only class _BuildInfo(TypedDict): @@ -107,9 +106,9 @@ def setup( download_url: str | None = None, # Attributes from distutils.dist.Distribution.__init__ (except self.metadata) # These take priority over attributes from distutils.dist.Distribution.display_option_names - verbose=True, - dry_run=False, - help=False, + verbose: bool = True, + dry_run: bool = False, + help: bool = False, cmdclass: _MutableDictLike[str, type[_Command]] = {}, command_packages: str | list[str] | None = None, script_name: StrPath | None = ..., # default is actually set in distutils.core.setup @@ -123,7 +122,7 @@ def setup( ext_modules: Sequence[_Extension] | None = None, ext_package: str | None = None, include_dirs: list[str] | None = None, - extra_path=None, + extra_path: Never = ..., # Deprecated scripts: list[str] | None = None, data_files: list[tuple[str, Sequence[str]]] | None = None, password: str = "", @@ -149,13 +148,13 @@ def setup( setup_requires: list[str] = [], # From Distribution._DISTUTILS_UNSUPPORTED_METADATA set in Distribution._set_metadata_defaults long_description_content_type: str | None = None, - project_urls={}, - provides_extras={}, - license_expression=None, - license_file=None, - license_files=None, - install_requires=[], - extras_require={}, + project_urls: _DictLike[Incomplete, Incomplete] = {}, + provides_extras: _MutableDictLike[Incomplete, Incomplete] = {}, + license_expression: str | None = None, + license_file: Never = ..., # Deprecated + license_files: Iterable[str] | None = None, + install_requires: str | Iterable[str] = [], + extras_require: _DictLike[Incomplete, Incomplete] = {}, # kwargs used directly in distutils.core.setup distclass: type[_DistributionT] = Distribution, # type: ignore[assignment] # noqa: Y011 # Custom Distributions could accept more params