Drop Python 3.8 support in builtins (#13762)

* remove py38 branches in `builtins`

* combined `builtins.dict` tests with those exclusive to `>=3.9`
This commit is contained in:
Joren Hammudoglu
2025-04-02 09:01:36 +02:00
committed by GitHub
parent f6503fd9ab
commit 5513d3f19b
3 changed files with 95 additions and 122 deletions
@@ -1,70 +0,0 @@
"""
Tests for `dict.__(r)or__`.
`dict.__or__` and `dict.__ror__` were only added in py39,
hence why these are in a separate file to the other test cases for `dict`.
"""
from __future__ import annotations
import os
import sys
from typing import Mapping, TypeVar, Union
from typing_extensions import Self, assert_type
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
if sys.version_info >= (3, 9):
class CustomDictSubclass(dict[_KT, _VT]):
pass
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
return {}
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
return {}
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
return self
def test_dict_dot_or(
a: dict[int, int],
b: CustomDictSubclass[int, int],
c: dict[str, str],
d: Mapping[int, int],
e: CustomMappingWithDunderOr[str, str],
) -> None:
# dict.__(r)or__ always returns a dict, even if called on a subclass of dict:
assert_type(a | b, dict[int, int])
assert_type(b | a, dict[int, int])
assert_type(a | c, dict[Union[int, str], Union[int, str]])
# arbitrary mappings are not accepted by `dict.__or__`;
# it has to be a subclass of `dict`
a | d # type: ignore
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
# which define `__ror__` methods that accept `dict`, are fine:
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
assert_type(c | os.environ, dict[str, str])
assert_type(c | e, dict[str, str])
assert_type(os.environ | c, dict[str, str])
assert_type(e | c, dict[str, str])
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` dict[str, str]` later
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
f = e
e |= c
e |= a # type: ignore
c |= f
c |= a # type: ignore
@@ -1,7 +1,8 @@
from __future__ import annotations
from typing import Any, Dict, Generic, Iterable, TypeVar, Union
from typing_extensions import assert_type
import os
from typing import Any, Dict, Generic, Iterable, Mapping, TypeVar, Union
from typing_extensions import Self, assert_type
# These do follow `__init__` overloads order:
# mypy and pyright have different opinions about this one:
@@ -148,3 +149,61 @@ def test11() -> str:
def test12() -> str:
return d_str.get("key", int_value) # type: ignore[arg-type]
# Tests for `dict.__(r)or__`.
class CustomDictSubclass(dict[_KT, _VT]):
pass
class CustomMappingWithDunderOr(Mapping[_KT, _VT]):
def __or__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
return {}
def __ror__(self, other: Mapping[_KT, _VT]) -> dict[_KT, _VT]:
return {}
def __ior__(self, other: Mapping[_KT, _VT]) -> Self:
return self
def test_dict_dot_or(
a: dict[int, int],
b: CustomDictSubclass[int, int],
c: dict[str, str],
d: Mapping[int, int],
e: CustomMappingWithDunderOr[str, str],
) -> None:
# dict.__(r)or__ always returns a dict, even if called on a subclass of dict:
assert_type(a | b, dict[int, int])
assert_type(b | a, dict[int, int])
assert_type(a | c, dict[Union[int, str], Union[int, str]])
# arbitrary mappings are not accepted by `dict.__or__`;
# it has to be a subclass of `dict`
a | d # type: ignore
# but Mappings such as `os._Environ` or `CustomMappingWithDunderOr`,
# which define `__ror__` methods that accept `dict`, are fine:
assert_type(a | os.environ, dict[Union[str, int], Union[str, int]])
assert_type(os.environ | a, dict[Union[str, int], Union[str, int]])
assert_type(c | os.environ, dict[str, str])
assert_type(c | e, dict[str, str])
assert_type(os.environ | c, dict[str, str])
assert_type(e | c, dict[str, str])
# store "untainted" `CustomMappingWithDunderOr[str, str]` to test `__ior__` against ` dict[str, str]` later
# Invalid `e |= a` causes pyright to join `Unknown` to `e`'s type
f = e
e |= c
e |= a # type: ignore
c |= f
c |= a # type: ignore
+34 -50
View File
@@ -32,7 +32,7 @@ from _typeshed import (
)
from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from types import CellType, CodeType, TracebackType
from types import CellType, CodeType, GenericAlias, TracebackType
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
# are imported from collections.abc in builtins.pyi
@@ -72,9 +72,6 @@ from typing_extensions import ( # noqa: Y023
deprecated,
)
if sys.version_info >= (3, 9):
from types import GenericAlias
_T = TypeVar("_T")
_I = TypeVar("_I", default=int)
_T_co = TypeVar("_T_co", covariant=True)
@@ -377,10 +374,8 @@ class float:
def __rpow__(self, value: float, mod: None = None, /) -> Any: ...
def __getnewargs__(self) -> tuple[float]: ...
def __trunc__(self) -> int: ...
if sys.version_info >= (3, 9):
def __ceil__(self) -> int: ...
def __floor__(self) -> int: ...
def __ceil__(self) -> int: ...
def __floor__(self) -> int: ...
@overload
def __round__(self, ndigits: None = None, /) -> int: ...
@overload
@@ -519,16 +514,15 @@ class str(Sequence[str]):
) -> LiteralString: ...
@overload
def replace(self, old: str, new: str, count: SupportsIndex = -1, /) -> str: ... # type: ignore[misc]
if sys.version_info >= (3, 9):
@overload
def removeprefix(self: LiteralString, prefix: LiteralString, /) -> LiteralString: ...
@overload
def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc]
@overload
def removesuffix(self: LiteralString, suffix: LiteralString, /) -> LiteralString: ...
@overload
def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc]
@overload
def removeprefix(self: LiteralString, prefix: LiteralString, /) -> LiteralString: ...
@overload
def removeprefix(self, prefix: str, /) -> str: ... # type: ignore[misc]
@overload
def removesuffix(self: LiteralString, suffix: LiteralString, /) -> LiteralString: ...
@overload
def removesuffix(self, suffix: str, /) -> str: ... # type: ignore[misc]
def rfind(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ...
def rindex(self, sub: str, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /) -> int: ...
@overload
@@ -666,10 +660,8 @@ class bytes(Sequence[int]):
def lstrip(self, bytes: ReadableBuffer | None = None, /) -> bytes: ...
def partition(self, sep: ReadableBuffer, /) -> tuple[bytes, bytes, bytes]: ...
def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytes: ...
if sys.version_info >= (3, 9):
def removeprefix(self, prefix: ReadableBuffer, /) -> bytes: ...
def removesuffix(self, suffix: ReadableBuffer, /) -> bytes: ...
def removeprefix(self, prefix: ReadableBuffer, /) -> bytes: ...
def removesuffix(self, suffix: ReadableBuffer, /) -> bytes: ...
def rfind(
self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /
) -> int: ...
@@ -771,10 +763,8 @@ class bytearray(MutableSequence[int]):
def partition(self, sep: ReadableBuffer, /) -> tuple[bytearray, bytearray, bytearray]: ...
def pop(self, index: int = -1, /) -> int: ...
def remove(self, value: int, /) -> None: ...
if sys.version_info >= (3, 9):
def removeprefix(self, prefix: ReadableBuffer, /) -> bytearray: ...
def removesuffix(self, suffix: ReadableBuffer, /) -> bytearray: ...
def removeprefix(self, prefix: ReadableBuffer, /) -> bytearray: ...
def removesuffix(self, suffix: ReadableBuffer, /) -> bytearray: ...
def replace(self, old: ReadableBuffer, new: ReadableBuffer, count: SupportsIndex = -1, /) -> bytearray: ...
def rfind(
self, sub: ReadableBuffer | SupportsIndex, start: SupportsIndex | None = ..., end: SupportsIndex | None = ..., /
@@ -1009,8 +999,7 @@ class tuple(Sequence[_T_co]):
def __rmul__(self, value: SupportsIndex, /) -> tuple[_T_co, ...]: ...
def count(self, value: Any, /) -> int: ...
def index(self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
# Doesn't exist at runtime, but deleting this breaks mypy and pyright. See:
# https://github.com/python/typeshed/issues/7580
@@ -1092,8 +1081,7 @@ class list(MutableSequence[_T]):
def __lt__(self, value: list[_T], /) -> bool: ...
def __le__(self, value: list[_T], /) -> bool: ...
def __eq__(self, value: object, /) -> bool: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
class dict(MutableMapping[_KT, _VT]):
# __init__ should be kept roughly in line with `collections.UserDict.__init__`, which has similar semantics
@@ -1162,21 +1150,20 @@ class dict(MutableMapping[_KT, _VT]):
def __eq__(self, value: object, /) -> bool: ...
def __reversed__(self) -> Iterator[_KT]: ...
__hash__: ClassVar[None] # type: ignore[assignment]
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
@overload
def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
@overload
def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
@overload
def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
@overload
def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
# dict.__ior__ should be kept roughly in line with MutableMapping.update()
@overload # type: ignore[misc]
def __ior__(self, value: SupportsKeysAndGetItem[_KT, _VT], /) -> Self: ...
@overload
def __ior__(self, value: Iterable[tuple[_KT, _VT]], /) -> Self: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
@overload
def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
@overload
def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
@overload
def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ...
@overload
def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ...
# dict.__ior__ should be kept roughly in line with MutableMapping.update()
@overload # type: ignore[misc]
def __ior__(self, value: SupportsKeysAndGetItem[_KT, _VT], /) -> Self: ...
@overload
def __ior__(self, value: Iterable[tuple[_KT, _VT]], /) -> Self: ...
class set(MutableSet[_T]):
@overload
@@ -1215,8 +1202,7 @@ class set(MutableSet[_T]):
def __gt__(self, value: AbstractSet[object], /) -> bool: ...
def __eq__(self, value: object, /) -> bool: ...
__hash__: ClassVar[None] # type: ignore[assignment]
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
class frozenset(AbstractSet[_T_co]):
@overload
@@ -1244,15 +1230,13 @@ class frozenset(AbstractSet[_T_co]):
def __gt__(self, value: AbstractSet[object], /) -> bool: ...
def __eq__(self, value: object, /) -> bool: ...
def __hash__(self) -> int: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
class enumerate(Generic[_T]):
def __new__(cls, iterable: Iterable[_T], start: int = 0) -> Self: ...
def __iter__(self) -> Self: ...
def __next__(self) -> tuple[int, _T]: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
@final
class range(Sequence[int]):