Skip to content

Commit b6e1770

Browse files
authored
Merge branch 'main' into Ruff-PLR
2 parents e818e41 + 9f11db4 commit b6e1770

File tree

36 files changed

+330
-210
lines changed

36 files changed

+330
-210
lines changed

.flake8

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
[flake8]
2-
# NQA: Ruff won't warn about redundant `# noqa: Y`
32
# Y: Flake8 is only used to run flake8-pyi, everything else is in Ruff
43
# F821: Typeshed is a testing ground for flake8-pyi, which monkeypatches F821
5-
select = NQA, Y, F821
4+
select = Y, F821
65
# Ignore rules normally excluded by default
76
extend-ignore = Y090
87
per-file-ignores =
9-
# We should only need to noqa Y and F821 codes in .pyi files
10-
*.py: NQA
118
# Generated protobuf files:
129
# Y021: Include docstrings
1310
# Y023: Alias typing as typing_extensions
@@ -16,4 +13,3 @@ per-file-ignores =
1613
stubs/*_pb2.pyi: Y021, Y023, Y026, Y053
1714

1815
exclude = .venv*,.git
19-
noqa_require_code = true

.github/workflows/tests.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,6 @@ jobs:
110110
DEPENDENCIES=$( python tests/get_external_stub_requirements.py )
111111
if [ -n "$DEPENDENCIES" ]; then
112112
printf "Installing packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
113-
# TODO: We need to specify the platform here, but the platforms
114-
# strings supported by uv are different from the ones supported by
115-
# pyright.
116113
uv pip install --python-version ${{ matrix.python-version }} $DEPENDENCIES
117114
fi
118115
- name: Activate the isolated venv for the rest of the job

.pre-commit-config.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ repos:
3131
hooks:
3232
- id: black
3333
- repo: https://github.com/pycqa/flake8
34-
rev: 7.1.1
34+
rev: 7.1.2
3535
hooks:
3636
- id: flake8
3737
language: python
3838
additional_dependencies:
39-
- "flake8-noqa==1.4.0"
4039
- "flake8-pyi==24.9.0"
4140
types: [file]
4241
types_or: [python, pyi]

CONTRIBUTING.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ it takes a bit longer. For more details, read below.
2828

2929
Typeshed runs continuous integration (CI) on all pull requests. This means that
3030
if you file a pull request (PR), our full test suite
31-
-- including our linter, [Flake8](https://github.com/PyCQA/flake8) --
31+
-- including our linter, [`flake8-pyi`](https://github.com/pycqa/flake8-pyi) --
3232
is run on your PR. It also means that bots will automatically apply
3333
changes to your PR (using [Black](https://github.com/psf/black) and
3434
[Ruff](https://github.com/astral-sh/ruff)) to fix any formatting issues.
@@ -88,8 +88,7 @@ The code is formatted using [`Black`](https://github.com/psf/black).
8888
Various other autofixes and lint rules are
8989
also performed by [`Ruff`](https://github.com/astral-sh/ruff) and
9090
[`Flake8`](https://github.com/pycqa/flake8),
91-
with plugins [`flake8-pyi`](https://github.com/pycqa/flake8-pyi),
92-
and [`flake8-noqa`](https://github.com/plinss/flake8-noqa).
91+
with plugin [`flake8-pyi`](https://github.com/pycqa/flake8-pyi).
9392

9493
The repository is equipped with a [pre-commit.ci](https://pre-commit.ci/)
9594
configuration file. This means that you don't *need* to do anything yourself to

pyproject.toml

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ exclude = [
3333
# are invoked via separate runs of ruff in pre-commit:
3434
# see our .pre-commit-config.yaml file for details
3535
exclude = ["**/test_cases/**/*.py"]
36-
# We still use flake8-pyi and flake8-noqa to check these (see .flake8 config file);
36+
# We still use flake8-pyi to check these (see .flake8 config file);
3737
# tell ruff not to flag these as e.g. "unused noqa comments"
38-
external = ["F821", "NQA", "Y"]
38+
external = ["F821", "Y"]
3939
select = [
4040
"ARG", # flake8-unused-arguments
4141
"B", # flake8-bugbear
@@ -45,6 +45,7 @@ select = [
4545
"I", # isort
4646
"N", # pep8-naming
4747
"PGH", # pygrep-hooks
48+
"PLC", # Pylint Convention
4849
"PLR", # Pylint Refactor
4950
"RUF", # Ruff-specific and unused-noqa
5051
"TRY", # tryceratops
@@ -67,8 +68,7 @@ select = [
6768
"FURB177", # Prefer `Path.cwd()` over `Path().resolve()` for current-directory lookups
6869
"FURB187", # Use of assignment of `reversed` on list `{name}`
6970
# PYI: only enable rules that have autofixes and that we always want to fix (even manually),
70-
# avoids duplicate # noqa with flake8-pyi and flake8-noqa flagging `PYI` codes
71-
# See https://github.com/plinss/flake8-noqa/issues/22
71+
# avoids duplicate # noqa with flake8-pyi
7272
"PYI009", # Empty body should contain `...`, not pass
7373
"PYI010", # Function body must contain only `...`
7474
"PYI012", # Class bodies must not contain `pass`
@@ -82,7 +82,6 @@ select = [
8282
# "PYI026", Waiting for this mypy bug to be fixed: https://github.com/python/mypy/issues/16581
8383
"PYI030", # Multiple literal members in a union. Use a single literal, e.g. `Literal[{}]`
8484
"PYI032", # Prefer `object` to `Any` for the second parameter to `{method_name}`
85-
"PYI034", # `__new__` methods usually return self at runtime
8685
"PYI036", # Star-args in `{method_name}` should be annotated with `object`
8786
"PYI044", # `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics
8887
"PYI055", # Multiple `type[T]` usages in a union. Combine them into one, e.g., `type[{union_str}]`.
@@ -166,19 +165,26 @@ ignore = [
166165
# A lot of stubs are incomplete on purpose, and that's configured through pyright
167166
# Some ANN204 (special method) are autofixable in stubs, but not all.
168167
"ANN2", # Missing return type annotation for ...
169-
# Most pep8-naming rules don't apply for third-party stubs like typeshed.
170-
# N811 to N814 could apply, but we often use them to disambiguate a name whilst making it look like a more common one
171-
"N8",
168+
# Ruff 0.8.0 added sorting of __all__ and __slots_.
169+
# There is no consensus on whether we want to apply this to stubs, so keeping the status quo.
170+
# See https://github.com/python/typeshed/pull/13108
171+
"RUF022", # `__all__` is not sorted
172+
"RUF023", # `{}.__slots__` is not sorted
173+
###
172174
# Rules that are out of the control of stub authors:
175+
###
173176
"F403", # `from . import *` used; unable to detect undefined names
174177
# Stubs can sometimes re-export entire modules.
175178
# Issues with using a star-imported name will be caught by type-checkers.
176179
"F405", # may be undefined, or defined from star imports
177-
# Ruff 0.8.0 added sorting of __all__ and __slots_.
178-
# There is no consensus on whether we want to apply this to stubs, so keeping the status quo.
179-
# See https://github.com/python/typeshed/pull/13108
180-
"RUF022",
181-
"RUF023",
180+
# Most pep8-naming rules don't apply for third-party stubs like typeshed.
181+
# N811 to N814 could apply, but we often use them to disambiguate a name whilst making it look like a more common one
182+
"N8", # pep8-naming
183+
"PLC2701", # Private name import from external module
184+
]
185+
"lib/ts_utils/**" = [
186+
# Doesn't affect stubs. The only re-exports we have should be in our local lib ts_utils
187+
"PLC0414", # Import alias does not rename original package
182188
]
183189
"*_pb2.pyi" = [
184190
# Leave the docstrings as-is, matching source

stdlib/@tests/stubtest_allowlists/common.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ http.client.HTTPConnection.response_class # the actual type at runtime is abc.A
1010
importlib.abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined for backwards compatibility
1111
importlib.abc.MetaPathFinder.find_spec # Not defined on the actual class, but expected to exist.
1212
importlib.abc.PathEntryFinder.find_spec # Not defined on the actual class, but expected to exist.
13-
socketserver.BaseServer.fileno # implemented in derived classes
14-
socketserver.BaseServer.get_request # implemented in derived classes
15-
socketserver.BaseServer.server_bind # implemented in derived classes
1613
tkinter.simpledialog.[A-Z_]+
1714
tkinter.simpledialog.TclVersion
1815
tkinter.simpledialog.TkVersion
@@ -411,6 +408,7 @@ pickle._Pickler\..* # Best effort typing for undocumented internals
411408
pickle._Unpickler\..* # Best effort typing for undocumented internals
412409
_?queue.SimpleQueue.__init__ # C signature is broader than what is actually accepted
413410
shutil.rmtree # function with attributes, which we approximate with a callable protocol
411+
socketserver.BaseServer.get_request # Not implemented, but expected to exist on subclasses.
414412
ssl.PROTOCOL_SSLv2 # Depends on the existence and flags of SSL
415413
ssl.PROTOCOL_SSLv3 # Depends on the existence and flags of SSL
416414
sys.implementation # Actually SimpleNamespace but then you wouldn't have convenient attributes
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from __future__ import annotations
2+
3+
from decimal import Decimal
4+
from fractions import Fraction
5+
from math import prod
6+
from typing import Any, Literal, Union
7+
from typing_extensions import assert_type
8+
9+
10+
class SupportsMul:
11+
def __mul__(self, other: Any) -> SupportsMul:
12+
return SupportsMul()
13+
14+
15+
class SupportsRMul:
16+
def __rmul__(self, other: Any) -> SupportsRMul:
17+
return SupportsRMul()
18+
19+
20+
class SupportsMulAndRMul:
21+
def __mul__(self, other: Any) -> SupportsMulAndRMul:
22+
return SupportsMulAndRMul()
23+
24+
def __rmul__(self, other: Any) -> SupportsMulAndRMul:
25+
return SupportsMulAndRMul()
26+
27+
28+
literal_list: list[Literal[0, 1]] = [0, 1, 1]
29+
30+
assert_type(prod([2, 4]), int)
31+
assert_type(prod([3, 5], start=4), int)
32+
33+
assert_type(prod([True, False]), int)
34+
assert_type(prod([True, False], start=True), int)
35+
assert_type(prod(literal_list), int)
36+
37+
assert_type(prod([SupportsMul(), SupportsMul()], start=SupportsMul()), SupportsMul)
38+
assert_type(prod([SupportsMulAndRMul(), SupportsMulAndRMul()]), Union[SupportsMulAndRMul, Literal[1]])
39+
40+
assert_type(prod([5.6, 3.2]), Union[float, Literal[1]])
41+
assert_type(prod([5.6, 3.2], start=3), Union[float, int])
42+
43+
assert_type(prod([Fraction(7, 2), Fraction(3, 5)]), Union[Fraction, Literal[1]])
44+
assert_type(prod([Fraction(7, 2), Fraction(3, 5)], start=Fraction(1)), Fraction)
45+
assert_type(prod([Decimal("3.14"), Decimal("2.71")]), Union[Decimal, Literal[1]])
46+
assert_type(prod([Decimal("3.14"), Decimal("2.71")], start=Decimal("1.00")), Decimal)
47+
assert_type(prod([complex(7, 2), complex(3, 5)]), Union[complex, Literal[1]])
48+
assert_type(prod([complex(7, 2), complex(3, 5)], start=complex(1, 0)), complex)
49+
50+
51+
# mypy and pyright infer the types differently for these, so we can't use assert_type
52+
# Just test that no error is emitted for any of these
53+
prod([5.6, 3.2]) # mypy: `float`; pyright: `float | Literal[0]`
54+
prod([2.5, 5.8], start=5) # mypy: `float`; pyright: `float | int`
55+
56+
# These all fail at runtime
57+
prod([SupportsMul(), SupportsMul()]) # type: ignore
58+
prod([SupportsRMul(), SupportsRMul()], start=SupportsRMul()) # type: ignore
59+
prod([SupportsRMul(), SupportsRMul()]) # type: ignore
60+
61+
# TODO: these pass pyright with the current stubs, but mypy erroneously emits an error:
62+
# prod([3, Fraction(7, 22), complex(8, 0), 9.83])
63+
# prod([3, Decimal("0.98")])

stdlib/_typeshed/__init__.pyi

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ class SupportsSub(Protocol[_T_contra, _T_co]):
117117
class SupportsRSub(Protocol[_T_contra, _T_co]):
118118
def __rsub__(self, x: _T_contra, /) -> _T_co: ...
119119

120+
class SupportsMul(Protocol[_T_contra, _T_co]):
121+
def __mul__(self, x: _T_contra, /) -> _T_co: ...
122+
123+
class SupportsRMul(Protocol[_T_contra, _T_co]):
124+
def __rmul__(self, x: _T_contra, /) -> _T_co: ...
125+
120126
class SupportsDivMod(Protocol[_T_contra, _T_co]):
121127
def __divmod__(self, other: _T_contra, /) -> _T_co: ...
122128

@@ -151,11 +157,8 @@ class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
151157
def keys(self) -> Iterable[_KT]: ...
152158
def __getitem__(self, key: _KT, /) -> _VT_co: ...
153159

154-
# This protocol is currently under discussion. Use SupportsContainsAndGetItem
155-
# instead, if you require the __contains__ method.
156-
# See https://github.com/python/typeshed/issues/11822.
160+
# stable
157161
class SupportsGetItem(Protocol[_KT_contra, _VT_co]):
158-
def __contains__(self, x: Any, /) -> bool: ...
159162
def __getitem__(self, key: _KT_contra, /) -> _VT_co: ...
160163

161164
# stable

stdlib/builtins.pyi

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# ruff: noqa: PYI036 # This is the module declaring BaseException
21
import _ast
32
import _sitebuiltins
43
import _typeshed
@@ -89,8 +88,8 @@ _T2 = TypeVar("_T2")
8988
_T3 = TypeVar("_T3")
9089
_T4 = TypeVar("_T4")
9190
_T5 = TypeVar("_T5")
92-
_SupportsNextT = TypeVar("_SupportsNextT", bound=SupportsNext[Any], covariant=True)
93-
_SupportsAnextT = TypeVar("_SupportsAnextT", bound=SupportsAnext[Any], covariant=True)
91+
_SupportsNextT_co = TypeVar("_SupportsNextT_co", bound=SupportsNext[Any], covariant=True)
92+
_SupportsAnextT_co = TypeVar("_SupportsAnextT_co", bound=SupportsAnext[Any], covariant=True)
9493
_AwaitableT = TypeVar("_AwaitableT", bound=Awaitable[Any])
9594
_AwaitableT_co = TypeVar("_AwaitableT_co", bound=Awaitable[Any], covariant=True)
9695
_P = ParamSpec("_P")
@@ -870,7 +869,11 @@ class memoryview(Sequence[_I]):
870869
def __new__(cls, obj: ReadableBuffer) -> Self: ...
871870
def __enter__(self) -> Self: ...
872871
def __exit__(
873-
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, /
872+
self,
873+
exc_type: type[BaseException] | None, # noqa: PYI036 # This is the module declaring BaseException
874+
exc_val: BaseException | None,
875+
exc_tb: TracebackType | None,
876+
/,
874877
) -> None: ...
875878
@overload
876879
def cast(self, format: Literal["c", "@c"], shape: list[int] | tuple[int, ...] = ...) -> memoryview[bytes]: ...
@@ -1319,7 +1322,7 @@ class _PathLike(Protocol[AnyStr_co]):
13191322
def __fspath__(self) -> AnyStr_co: ...
13201323

13211324
if sys.version_info >= (3, 10):
1322-
def aiter(async_iterable: SupportsAiter[_SupportsAnextT], /) -> _SupportsAnextT: ...
1325+
def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co: ...
13231326

13241327
class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]):
13251328
def __anext__(self) -> _AwaitableT_co: ...
@@ -1481,7 +1484,7 @@ class _GetItemIterable(Protocol[_T_co]):
14811484
def __getitem__(self, i: int, /) -> _T_co: ...
14821485

14831486
@overload
1484-
def iter(object: SupportsIter[_SupportsNextT], /) -> _SupportsNextT: ...
1487+
def iter(object: SupportsIter[_SupportsNextT_co], /) -> _SupportsNextT_co: ...
14851488
@overload
14861489
def iter(object: _GetItemIterable[_T], /) -> Iterator[_T]: ...
14871490
@overload
@@ -1688,17 +1691,17 @@ def print(
16881691
*values: object, sep: str | None = " ", end: str | None = "\n", file: _SupportsWriteAndFlush[str] | None = None, flush: bool
16891692
) -> None: ...
16901693

1691-
_E = TypeVar("_E", contravariant=True)
1692-
_M = TypeVar("_M", contravariant=True)
1694+
_E_contra = TypeVar("_E_contra", contravariant=True)
1695+
_M_contra = TypeVar("_M_contra", contravariant=True)
16931696

1694-
class _SupportsPow2(Protocol[_E, _T_co]):
1695-
def __pow__(self, other: _E, /) -> _T_co: ...
1697+
class _SupportsPow2(Protocol[_E_contra, _T_co]):
1698+
def __pow__(self, other: _E_contra, /) -> _T_co: ...
16961699

1697-
class _SupportsPow3NoneOnly(Protocol[_E, _T_co]):
1698-
def __pow__(self, other: _E, modulo: None = None, /) -> _T_co: ...
1700+
class _SupportsPow3NoneOnly(Protocol[_E_contra, _T_co]):
1701+
def __pow__(self, other: _E_contra, modulo: None = None, /) -> _T_co: ...
16991702

1700-
class _SupportsPow3(Protocol[_E, _M, _T_co]):
1701-
def __pow__(self, other: _E, modulo: _M, /) -> _T_co: ...
1703+
class _SupportsPow3(Protocol[_E_contra, _M_contra, _T_co]):
1704+
def __pow__(self, other: _E_contra, modulo: _M_contra, /) -> _T_co: ...
17021705

17031706
_SupportsSomeKindOfPow = ( # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
17041707
_SupportsPow2[Any, Any] | _SupportsPow3NoneOnly[Any, Any] | _SupportsPow3[Any, Any, Any]
@@ -1734,11 +1737,11 @@ def pow(base: float, exp: complex | _SupportsSomeKindOfPow, mod: None = None) ->
17341737
@overload
17351738
def pow(base: complex, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> complex: ...
17361739
@overload
1737-
def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
1740+
def pow(base: _SupportsPow2[_E_contra, _T_co], exp: _E_contra, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
17381741
@overload
1739-
def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
1742+
def pow(base: _SupportsPow3NoneOnly[_E_contra, _T_co], exp: _E_contra, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
17401743
@overload
1741-
def pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co: ...
1744+
def pow(base: _SupportsPow3[_E_contra, _M_contra, _T_co], exp: _E_contra, mod: _M_contra) -> _T_co: ...
17421745
@overload
17431746
def pow(base: _SupportsSomeKindOfPow, exp: float, mod: None = None) -> Any: ...
17441747
@overload

stdlib/contextlib.pyi

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ _T_co = TypeVar("_T_co", covariant=True)
3333
_T_io = TypeVar("_T_io", bound=IO[str] | None)
3434
_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None)
3535
_F = TypeVar("_F", bound=Callable[..., Any])
36-
_G = TypeVar("_G", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True)
36+
_G_co = TypeVar("_G_co", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True)
3737
_P = ParamSpec("_P")
3838

3939
_SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None)
@@ -68,11 +68,11 @@ class ContextDecorator:
6868
def _recreate_cm(self) -> Self: ...
6969
def __call__(self, func: _F) -> _F: ...
7070

71-
class _GeneratorContextManagerBase(Generic[_G]):
71+
class _GeneratorContextManagerBase(Generic[_G_co]):
7272
# Ideally this would use ParamSpec, but that requires (*args, **kwargs), which this isn't. see #6676
73-
def __init__(self, func: Callable[..., _G], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
74-
gen: _G
75-
func: Callable[..., _G]
73+
def __init__(self, func: Callable[..., _G_co], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
74+
gen: _G_co
75+
func: Callable[..., _G_co]
7676
args: tuple[Any, ...]
7777
kwds: dict[str, Any]
7878

0 commit comments

Comments
 (0)