5
5
import os
6
6
import sys
7
7
from abc import ABC , abstractmethod
8
- from ast import Index , literal_eval
8
+ from ast import literal_eval
9
9
from collections import defaultdict
10
10
from contextlib import contextmanager , suppress
11
11
from dataclasses import dataclass
12
12
from itertools import chain
13
13
from pathlib import Path
14
- from typing import TYPE_CHECKING , Literal , NamedTuple , cast
14
+ from typing import TYPE_CHECKING , Any , Literal , NamedTuple , cast
15
15
16
16
from classify_imports import Classified , classify_base
17
17
36
36
TC200 ,
37
37
TC201 ,
38
38
builtin_names ,
39
- py38 ,
40
39
sqlalchemy_default_mapped_dotted_names ,
41
40
)
42
41
43
- try :
44
- ast_unparse = ast .unparse # type: ignore[attr-defined]
45
- except AttributeError : # pragma: no cover
46
- # Python < 3.9
47
-
48
- import astor
49
-
50
- def ast_unparse (node : ast .AST ) -> str :
51
- """AST unparsing helper for Python < 3.9."""
52
- return cast ('str' , astor .to_source (node )).strip ()
53
-
54
-
55
42
if TYPE_CHECKING :
56
43
from _ast import AsyncFunctionDef , FunctionDef
57
44
from argparse import Namespace
58
45
from collections .abc import Iterator
59
- from typing import Any , Optional , Union
60
46
61
47
from flake8_type_checking .types import (
62
48
Comprehension ,
@@ -104,18 +90,18 @@ def visit(self, node: ast.AST) -> None:
104
90
setattr (node .right , BINOP_OPERAND_PROPERTY , True )
105
91
self .visit (node .left )
106
92
self .visit (node .right )
107
- elif ( py38 and isinstance ( node , Index )) or isinstance (node , ast .Attribute ):
93
+ elif isinstance (node , ast .Attribute ):
108
94
self .visit (node .value )
109
95
elif isinstance (node , ast .Subscript ):
110
96
self .visit (node .value )
111
97
if self .is_typing (node .value , 'Literal' ):
112
98
return
113
99
elif self .is_typing (node .value , 'Annotated' ) and isinstance (
114
- ( elts_node := node .slice . value if py38 and isinstance ( node . slice , Index ) else node . slice ) ,
100
+ node .slice ,
115
101
(ast .Tuple , ast .List ),
116
102
):
117
- if elts_node .elts :
118
- elts_iter = iter (elts_node .elts )
103
+ if node . slice .elts :
104
+ elts_iter = iter (node . slice .elts )
119
105
# only visit the first element like a type expression
120
106
self .visit_annotated_type (next (elts_iter ))
121
107
for value_node in elts_iter :
@@ -144,9 +130,9 @@ class AttrsMixin:
144
130
if TYPE_CHECKING :
145
131
third_party_imports : dict [str , Import ]
146
132
147
- def get_all_attrs_imports (self ) -> dict [Optional [ str ] , str ]:
133
+ def get_all_attrs_imports (self ) -> dict [str | None , str ]:
148
134
"""Return a map of all attrs/attr imports."""
149
- attrs_imports : dict [Optional [ str ] , str ] = {} # map of alias to full import name
135
+ attrs_imports : dict [str | None , str ] = {} # map of alias to full import name
150
136
151
137
for node in self .third_party_imports .values ():
152
138
module = getattr (node , 'module' , '' )
@@ -166,7 +152,7 @@ def is_attrs_class(self, class_node: ast.ClassDef) -> bool:
166
152
attrs_imports = self .get_all_attrs_imports ()
167
153
return any (self .is_attrs_decorator (decorator , attrs_imports ) for decorator in class_node .decorator_list )
168
154
169
- def is_attrs_decorator (self , decorator : Any , attrs_imports : dict [Optional [ str ] , str ]) -> bool :
155
+ def is_attrs_decorator (self , decorator : Any , attrs_imports : dict [str | None , str ]) -> bool :
170
156
"""Check whether a class decorator is an attrs decorator or not."""
171
157
if isinstance (decorator , ast .Call ):
172
158
return self .is_attrs_decorator (decorator .func , attrs_imports )
@@ -185,7 +171,7 @@ def is_attrs_attribute(attribute: ast.Attribute) -> bool:
185
171
return any (e for e in actual if e in ATTRS_DECORATORS )
186
172
187
173
@staticmethod
188
- def is_attrs_str (attribute : Union [ str , ast .expr ] , attrs_imports : dict [Optional [ str ] , str ]) -> bool :
174
+ def is_attrs_str (attribute : str | ast .expr , attrs_imports : dict [str | None , str ]) -> bool :
189
175
"""Check whether an ast.expr or string is an attrs string or not."""
190
176
actual = attrs_imports .get (str (attribute ), '' )
191
177
return actual in ATTRS_DECORATORS
@@ -211,7 +197,7 @@ class DunderAllMixin:
211
197
"""
212
198
213
199
if TYPE_CHECKING :
214
- uses : dict [str , list [tuple [ast .AST , Scope ]]]
200
+ uses : dict [str , list [tuple [ast .expr , Scope ]]]
215
201
current_scope : Scope
216
202
217
203
def generic_visit (self , node : ast .AST ) -> None : # noqa: D102
@@ -285,12 +271,12 @@ class PydanticMixin:
285
271
286
272
if TYPE_CHECKING :
287
273
pydantic_enabled : bool
288
- pydantic_validate_arguments_import_name : Optional [ str ]
274
+ pydantic_validate_arguments_import_name : str | None
289
275
290
276
def visit (self , node : ast .AST ) -> ast .AST : # noqa: D102
291
277
...
292
278
293
- def _function_is_wrapped_by_validate_arguments (self , node : Union [ FunctionDef , AsyncFunctionDef ] ) -> bool :
279
+ def _function_is_wrapped_by_validate_arguments (self , node : FunctionDef | AsyncFunctionDef ) -> bool :
294
280
if self .pydantic_enabled and node .decorator_list :
295
281
for decorator_node in node .decorator_list :
296
282
if getattr (decorator_node , 'id' , '' ) == self .pydantic_validate_arguments_import_name :
@@ -360,7 +346,7 @@ class SQLAlchemyMixin:
360
346
sqlalchemy_enabled : bool
361
347
sqlalchemy_mapped_dotted_names : set [str ]
362
348
current_scope : Scope
363
- uses : dict [str , list [tuple [ast .AST , Scope ]]]
349
+ uses : dict [str , list [tuple [ast .expr , Scope ]]]
364
350
soft_uses : set [str ]
365
351
in_soft_use_context : bool
366
352
@@ -504,7 +490,7 @@ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
504
490
if self .injector_enabled :
505
491
self .handle_injector_declaration (node )
506
492
507
- def handle_injector_declaration (self , node : Union [ AsyncFunctionDef , FunctionDef ] ) -> None :
493
+ def handle_injector_declaration (self , node : AsyncFunctionDef | FunctionDef ) -> None :
508
494
"""
509
495
Adjust for injector declaration setting.
510
496
@@ -553,7 +539,7 @@ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
553
539
if (self .fastapi_enabled and node .decorator_list ) or self .fastapi_dependency_support_enabled :
554
540
self .handle_fastapi_decorator (node )
555
541
556
- def handle_fastapi_decorator (self , node : Union [ AsyncFunctionDef , FunctionDef ] ) -> None :
542
+ def handle_fastapi_decorator (self , node : AsyncFunctionDef | FunctionDef ) -> None :
557
543
"""
558
544
Adjust for FastAPI decorator setting.
559
545
@@ -648,7 +634,7 @@ class ImportName:
648
634
649
635
_module : str
650
636
_name : str
651
- _alias : Optional [ str ]
637
+ _alias : str | None
652
638
653
639
#: Whether or not this import is exempt from TC001-004 checks.
654
640
exempt : bool
@@ -1034,8 +1020,8 @@ def __init__(
1034
1020
injector_enabled : bool ,
1035
1021
cattrs_enabled : bool ,
1036
1022
pydantic_enabled_baseclass_passlist : list [str ],
1037
- typing_modules : Optional [ list [str ]] = None ,
1038
- exempt_modules : Optional [ list [str ]] = None ,
1023
+ typing_modules : list [str ] | None = None ,
1024
+ exempt_modules : list [str ] | None = None ,
1039
1025
) -> None :
1040
1026
super ().__init__ ()
1041
1027
@@ -1074,7 +1060,7 @@ def __init__(
1074
1060
self .scopes : list [Scope ] = []
1075
1061
1076
1062
#: List of all names and ids, except type declarations
1077
- self .uses : dict [str , list [tuple [ast .AST , Scope ]]] = defaultdict (list )
1063
+ self .uses : dict [str , list [tuple [ast .expr , Scope ]]] = defaultdict (list )
1078
1064
1079
1065
#: Contains a set of all names to be treated like soft-uses.
1080
1066
# i.e. we don't know if it will be used at runtime or not, so
@@ -1085,7 +1071,7 @@ def __init__(
1085
1071
self .annotation_visitor = ImportAnnotationVisitor (self )
1086
1072
1087
1073
#: Whether there is a `from __futures__ import annotations` present in the file
1088
- self .futures_annotation : Optional [ bool ] = None
1074
+ self .futures_annotation : bool | None = None
1089
1075
1090
1076
#: Where the type checking block exists (line_start, line_end, col_offset)
1091
1077
# Empty type checking blocks are used for TC005 errors, while the type
@@ -1098,7 +1084,7 @@ def __init__(
1098
1084
self .unquoted_types_in_casts : list [tuple [int , int , str ]] = []
1099
1085
1100
1086
#: For tracking which comprehension/IfExp we're currently inside of
1101
- self .active_context : Optional [ Comprehension | ast .IfExp ] = None
1087
+ self .active_context : Comprehension | ast .IfExp | None = None
1102
1088
1103
1089
#: Whether or not we're in a context where uses count as soft-uses.
1104
1090
# E.g. the type expression of `typing.Annotated[type, value]`
@@ -1914,7 +1900,7 @@ def register_unquoted_type_in_typing_cast(self, node: ast.Call) -> None:
1914
1900
if isinstance (arg , ast .Constant ) and isinstance (arg .value , str ):
1915
1901
return # Type argument is already a string literal.
1916
1902
1917
- self .unquoted_types_in_casts .append ((arg .lineno , arg .col_offset , ast_unparse (arg )))
1903
+ self .unquoted_types_in_casts .append ((arg .lineno , arg .col_offset , ast . unparse (arg )))
1918
1904
1919
1905
def visit_Call (self , node : ast .Call ) -> None :
1920
1906
"""Check arguments of calls, e.g. typing.cast()."""
@@ -1937,7 +1923,7 @@ class TypingOnlyImportsChecker:
1937
1923
'future_option_enabled' ,
1938
1924
]
1939
1925
1940
- def __init__ (self , node : ast .Module , options : Optional [ Namespace ] ) -> None :
1926
+ def __init__ (self , node : ast .Module , options : Namespace | None ) -> None :
1941
1927
self .cwd = Path (os .getcwd ())
1942
1928
self .strict_mode = getattr (options , 'type_checking_strict' , False )
1943
1929
0 commit comments