Skip to content

Commit

Permalink
Ban more imports from typing_extensions as part of Y023 (#459)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Jan 5, 2024
1 parent 8fba6ed commit a113980
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Other features:
* Support flake8>=7.0.0
* Y061 is no longer emitted in situations where Y062 would also be emitted.
* Improve error message for Y060.
* Y023 now bans more imports from `typing_extensions` now that typeshed has
dropped support for Python 3.7.

Bugfixes:
* Y016: Fix false positive if a method had positional-only parameters (using
Expand Down
19 changes: 16 additions & 3 deletions pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,39 @@ def all_equal(iterable: Iterable[object]) -> bool:
}

# Y023: Import things from typing instead of typing_extensions
# if they're available from the typing module on 3.7+
# if they're available from the typing module on 3.8+
_BAD_TYPINGEXTENSIONS_Y023_IMPORTS = frozenset(
{
"Any",
"AnyStr",
"BinaryIO",
"Final",
"ForwardRef",
"Generic",
"IO",
"Literal",
"Protocol",
"TextIO",
"runtime_checkable",
"NewType",
"SupportsAbs",
"SupportsComplex",
"SupportsFloat",
"SupportsIndex",
"SupportsInt",
"SupportsRound",
"TypedDict",
"TypeVar",
"final",
"overload",
"NoReturn",
# ClassVar deliberately omitted,
# as it's the only one in this group that should be parameterised.
# It is special-cased elsewhere.
#
# Text is also deliberately omitted,
# as you shouldn't be importing it from anywhere! (Y039)
# Text/Optional/Union are also deliberately omitted,
# as well as the various stdlib aliases in typing(_extensions),
# as you shouldn't be importing them from anywhere! (Y039)
}
)

Expand Down
6 changes: 3 additions & 3 deletions tests/aliases.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ from collections.abc import Mapping
from typing import (
Annotated,
Any,
Literal,
Optional,
ParamSpec as _ParamSpec,
TypeAlias,
Expand All @@ -17,7 +18,6 @@ from typing import (
from weakref import WeakValueDictionary

import typing_extensions
from typing_extensions import Literal

class Foo:
def baz(self) -> None: ...
Expand All @@ -33,7 +33,7 @@ S = Optional[str] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g.
T = Annotated[int, "some very useful metadata"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "T: TypeAlias = Annotated[int, 'some very useful metadata']"
U = typing.Literal["ham", "bacon"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "U: TypeAlias = typing.Literal['ham', 'bacon']"
V = Literal["[(", ")]"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "V: TypeAlias = Literal['[(', ')]']"
X = typing_extensions.Literal["foo", "bar"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "X: TypeAlias = typing_extensions.Literal['foo', 'bar']"
X = typing_extensions.Literal["foo", "bar"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "X: TypeAlias = typing_extensions.Literal['foo', 'bar']" # Y023 Use "typing.Literal" instead of "typing_extensions.Literal"
Y = int | str # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Y: TypeAlias = int | str"
Z = Union[str, bytes] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Z: TypeAlias = Union[str, bytes]"
ZZ = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "ZZ: TypeAlias = None"
Expand All @@ -44,7 +44,7 @@ IntArray: TypeAlias = array.array[int]
FooWeakDict: TypeAlias = WeakValueDictionary[str, Foo]
A: typing.TypeAlias = typing.Literal["ham", "bacon"]
B: typing_extensions.TypeAlias = Literal["spam", "eggs"]
C: TypeAlias = typing_extensions.Literal["foo", "bar"]
C: TypeAlias = typing_extensions.Literal["foo", "bar"] # Y023 Use "typing.Literal" instead of "typing_extensions.Literal"
D: TypeAlias = int | str
E: TypeAlias = Union[str, bytes]
F: TypeAlias = int
Expand Down
4 changes: 2 additions & 2 deletions tests/attribute_annotations.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ field13: Final = b"foo"
field14: Final = True
field15: _Final = True
field16: typing.Final = "foo"
field17: typing_extensions.Final = "foo"
field17: typing_extensions.Final = "foo" # Y023 Use "typing.Final" instead of "typing_extensions.Final"
field18: Final = -24j
field181: Final = field18
field182: Final = os.pathsep
Expand Down Expand Up @@ -96,7 +96,7 @@ class Foo:
field12: Final = True
field13: _Final = True
field14: typing.Final = "foo"
field15: typing_extensions.Final = "foo"
field15: typing_extensions.Final = "foo" # Y023 Use "typing.Final" instead of "typing_extensions.Final"
# Standalone strings used to cause issues
field16 = "x" # Y052 Need type annotation for "field16"
if sys.platform == "linux":
Expand Down
4 changes: 2 additions & 2 deletions tests/calls.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class V(NamedTuple):
# BAD TYPEDDICTS
W = TypedDict("W", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible
B = typing.TypedDict("B", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible
WithTotal = typing_extensions.TypedDict("WithTotal", {'foo': str, 'bar': int}, total=False) # Y031 Use class-based syntax for TypedDicts where possible
WithTotal = typing_extensions.TypedDict("WithTotal", {'foo': str, 'bar': int}, total=False) # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" # Y031 Use class-based syntax for TypedDicts where possible
BB = mypy_extensions.TypedDict("BB", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible

# we don't want these two to raise errors (type-checkers already do that for us),
Expand All @@ -25,7 +25,7 @@ WeirdThirdArg = TypedDict("WeirdThirdArg", {'foo': int, "wot": str}, "who knows?

# GOOD TYPEDDICTS
C = typing.TypedDict("B", {'field has a space': list[int]})
D = typing_extensions.TypedDict("C", {'while': bytes, 'for': int})
D = typing_extensions.TypedDict("C", {'while': bytes, 'for': int}) # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict"
E = TypedDict("D", {'[][]': dict[str, int]})
F = TypedDict("E", {'1': list[str], '2': str})

Expand Down
19 changes: 12 additions & 7 deletions tests/imports.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ from re import Match, Pattern
from typing import (
Any,
ClassVar,
Final,
Generic,
Protocol,
TypeVar,
Expand All @@ -55,24 +56,22 @@ from typing import (
SupportsRound,
BinaryIO,
IO,
Literal,
NamedTuple,
TextIO,
TypedDict,
AnyStr,
NewType,
NoReturn,
final,
overload,
)
from typing_extensions import (
Concatenate,
Final,
ParamSpec,
SupportsIndex,
final,
Literal,
TypeAlias,
TypeGuard,
Annotated,
TypedDict,
)

# BAD IMPORTS (Y022 code)
Expand Down Expand Up @@ -142,6 +141,10 @@ from typing_extensions import Pattern # Y022 Use "re.Pattern[T]" instead of "ty
# BAD IMPORTS (Y023 code)
from typing_extensions import ClassVar # Y023 Use "typing.ClassVar[T]" instead of "typing_extensions.ClassVar[T]"
from typing_extensions import runtime_checkable # Y023 Use "typing.runtime_checkable" instead of "typing_extensions.runtime_checkable"
from typing_extensions import Literal # Y023 Use "typing.Literal" instead of "typing_extensions.Literal"
from typing_extensions import final # Y023 Use "typing.final" instead of "typing_extensions.final"
from typing_extensions import Final # Y023 Use "typing.Final" instead of "typing_extensions.Final"
from typing_extensions import TypedDict # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict"

# BAD IMPORTS: OTHER
from collections import namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
Expand All @@ -157,8 +160,8 @@ from collections.abc import ByteString # Y057 Do not use collections.abc.ByteSt
foo: typing.SupportsIndex
baz: re.Pattern[str]

@typing_extensions.final
def bar(arg: collections.abc.Sized) -> typing_extensions.Literal[True]: ...
@typing.final
def bar(arg: collections.abc.Sized) -> typing.Literal[True]: ...

class Fish:
blah: collections.deque[int]
Expand All @@ -182,8 +185,10 @@ class Spam:
def meth3(self, g: typing_extensions.AsyncContextManager[Any] = ...) -> None: ... # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing_extensions.AsyncContextManager[T]" (PEP 585 syntax)

# BAD ATTRIBUTE ACCESS (Y023 code)
@typing_extensions.final # Y023 Use "typing.final" instead of "typing_extensions.final"
class Foo:
attribute: typing_extensions.ClassVar[int] # Y023 Use "typing.ClassVar[T]" instead of "typing_extensions.ClassVar[T]"
attribute2: typing_extensions.Final[int] # Y023 Use "typing.Final" instead of "typing_extensions.Final"

# BAD ATTRIBUTE ACCESS: OTHER
j: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
Expand Down
2 changes: 1 addition & 1 deletion tests/quotes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ _V = TypeVar()

def make_sure_those_typevars_arent_flagged_as_unused(a: _T, b: _T2, c: _S, d: _U, e: _U2, f: _V) -> tuple[_T, _T2, _S, _U, _U2, _V]: ...

def h(w: Literal["a", "b"], x: typing.Literal["c"], y: typing_extensions.Literal["d"], z: _T) -> _T: ...
def h(w: Literal["a", "b"], x: typing.Literal["c"], y: typing_extensions.Literal["d"], z: _T) -> _T: ... # Y023 Use "typing.Literal" instead of "typing_extensions.Literal"

def i(x: Annotated[int, "lots", "of", "strings"], b: typing.Annotated[str, "more", "strings"]) -> None:
"""Documented and guaranteed useful.""" # Y021 Docstrings should not be included in stubs
Expand Down
4 changes: 2 additions & 2 deletions tests/union_duplicates.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import builtins
import typing
from collections.abc import Mapping
from typing import ( # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax)
Literal,
Type,
Union,
)

import typing_extensions
from typing_extensions import ( # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax)
Literal,
Type as Type_,
TypeAlias,
)
Expand Down Expand Up @@ -49,7 +49,7 @@ def f13_union(x: Union[Type_[int], Type_[str]]) -> None: ...
def f14_union(x: Union[typing_extensions.Type[int], typing_extensions.Type[str]]) -> None: ... # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax)

just_literals_subscript_union: Union[Literal[1], typing.Literal[2]] # Y030 Multiple Literal members in a union. Use a single Literal, e.g. "Literal[1, 2]".
mixed_subscript_union: Union[bytes, Literal['foo'], typing_extensions.Literal['bar']] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']".
mixed_subscript_union: Union[bytes, Literal['foo'], typing_extensions.Literal['bar']] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']". # Y023 Use "typing.Literal" instead of "typing_extensions.Literal"
just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # Y042 Type aliases should use the CamelCase naming convention # Y030 Multiple Literal members in a union. Use a single Literal, e.g. "Literal[True, 'idk']".
_mixed_pipe_union: TypeAlias = Union[Literal[966], bytes, Literal['baz']] # Y042 Type aliases should use the CamelCase naming convention # Y047 Type alias "_mixed_pipe_union" is not used # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[966, 'baz']".
ManyLiteralMembersButNeedsCombining: TypeAlias = int | Literal['a', 'b'] | Literal['baz'] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['a', 'b', 'baz']".
Expand Down
8 changes: 4 additions & 4 deletions tests/unused_things.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys
import typing
from typing import Protocol, TypedDict, TypeVar
from typing import Literal, Protocol, TypedDict, TypeVar

import mypy_extensions
import typing_extensions
from typing_extensions import Literal, TypeAlias
from typing_extensions import TypeAlias

_T = TypeVar("_T")

Expand Down Expand Up @@ -41,7 +41,7 @@ def uses__UsedAlias(arg: _UsedAlias) -> None: ...
class _UnusedTypedDict(TypedDict): # Y049 TypedDict "_UnusedTypedDict" is not used
foo: str

class _UnusedTypedDict2(typing_extensions.TypedDict): # Y049 TypedDict "_UnusedTypedDict2" is not used
class _UnusedTypedDict2(typing_extensions.TypedDict): # Y049 TypedDict "_UnusedTypedDict2" is not used # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict"
bar: int

class _UnusedTypedDict3(mypy_extensions.TypedDict): # Y049 TypedDict "_UnusedTypedDict3" is not used
Expand All @@ -59,7 +59,7 @@ class _UsedTypedDict2(TypedDict):
def uses__UsedTypeDict2(arg: _UsedTypedDict2) -> None: ...

_UnusedTypedDict4 = TypedDict("_UnusedTypedDict4", {"-": int, "def": str}) # Y049 TypedDict "_UnusedTypedDict4" is not used
_UnusedTypedDict5 = typing_extensions.TypedDict("_UnusedTypedDict5", {"foo": bytes, "bar": str}) # Y049 TypedDict "_UnusedTypedDict5" is not used # Y031 Use class-based syntax for TypedDicts where possible
_UnusedTypedDict5 = typing_extensions.TypedDict("_UnusedTypedDict5", {"foo": bytes, "bar": str}) # Y049 TypedDict "_UnusedTypedDict5" is not used # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" # Y031 Use class-based syntax for TypedDicts where possible
_UsedTypedDict3 = mypy_extensions.TypedDict("_UsedTypedDict3", {".": list[int]})

uses__UsedTypedDict3: _UsedTypedDict3
Expand Down

0 comments on commit a113980

Please sign in to comment.