55import os
66import sys
77from abc import ABC , abstractmethod
8- from ast import Index , literal_eval
8+ from ast import literal_eval
99from collections import defaultdict
1010from contextlib import contextmanager , suppress
1111from dataclasses import dataclass
1212from itertools import chain
1313from pathlib import Path
14- from typing import TYPE_CHECKING , Literal , NamedTuple , cast
14+ from typing import TYPE_CHECKING , Any , Literal , NamedTuple , cast
1515
1616from classify_imports import Classified , classify_base
1717
3636 TC200 ,
3737 TC201 ,
3838 builtin_names ,
39- py38 ,
4039 sqlalchemy_default_mapped_dotted_names ,
4140)
4241
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-
5542if TYPE_CHECKING :
5643 from _ast import AsyncFunctionDef , FunctionDef
5744 from argparse import Namespace
5845 from collections .abc import Iterator
59- from typing import Any , Optional , Union
6046
6147 from flake8_type_checking .types import (
6248 Comprehension ,
@@ -104,18 +90,18 @@ def visit(self, node: ast.AST) -> None:
10490 setattr (node .right , BINOP_OPERAND_PROPERTY , True )
10591 self .visit (node .left )
10692 self .visit (node .right )
107- elif ( py38 and isinstance ( node , Index )) or isinstance (node , ast .Attribute ):
93+ elif isinstance (node , ast .Attribute ):
10894 self .visit (node .value )
10995 elif isinstance (node , ast .Subscript ):
11096 self .visit (node .value )
11197 if self .is_typing (node .value , 'Literal' ):
11298 return
11399 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 ,
115101 (ast .Tuple , ast .List ),
116102 ):
117- if elts_node .elts :
118- elts_iter = iter (elts_node .elts )
103+ if node . slice .elts :
104+ elts_iter = iter (node . slice .elts )
119105 # only visit the first element like a type expression
120106 self .visit_annotated_type (next (elts_iter ))
121107 for value_node in elts_iter :
@@ -144,9 +130,9 @@ class AttrsMixin:
144130 if TYPE_CHECKING :
145131 third_party_imports : dict [str , Import ]
146132
147- def get_all_attrs_imports (self ) -> dict [Optional [ str ] , str ]:
133+ def get_all_attrs_imports (self ) -> dict [str | None , str ]:
148134 """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
150136
151137 for node in self .third_party_imports .values ():
152138 module = getattr (node , 'module' , '' )
@@ -166,7 +152,7 @@ def is_attrs_class(self, class_node: ast.ClassDef) -> bool:
166152 attrs_imports = self .get_all_attrs_imports ()
167153 return any (self .is_attrs_decorator (decorator , attrs_imports ) for decorator in class_node .decorator_list )
168154
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 :
170156 """Check whether a class decorator is an attrs decorator or not."""
171157 if isinstance (decorator , ast .Call ):
172158 return self .is_attrs_decorator (decorator .func , attrs_imports )
@@ -185,7 +171,7 @@ def is_attrs_attribute(attribute: ast.Attribute) -> bool:
185171 return any (e for e in actual if e in ATTRS_DECORATORS )
186172
187173 @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 :
189175 """Check whether an ast.expr or string is an attrs string or not."""
190176 actual = attrs_imports .get (str (attribute ), '' )
191177 return actual in ATTRS_DECORATORS
@@ -211,7 +197,7 @@ class DunderAllMixin:
211197 """
212198
213199 if TYPE_CHECKING :
214- uses : dict [str , list [tuple [ast .AST , Scope ]]]
200+ uses : dict [str , list [tuple [ast .expr , Scope ]]]
215201 current_scope : Scope
216202
217203 def generic_visit (self , node : ast .AST ) -> None : # noqa: D102
@@ -285,12 +271,12 @@ class PydanticMixin:
285271
286272 if TYPE_CHECKING :
287273 pydantic_enabled : bool
288- pydantic_validate_arguments_import_name : Optional [ str ]
274+ pydantic_validate_arguments_import_name : str | None
289275
290276 def visit (self , node : ast .AST ) -> ast .AST : # noqa: D102
291277 ...
292278
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 :
294280 if self .pydantic_enabled and node .decorator_list :
295281 for decorator_node in node .decorator_list :
296282 if getattr (decorator_node , 'id' , '' ) == self .pydantic_validate_arguments_import_name :
@@ -360,7 +346,7 @@ class SQLAlchemyMixin:
360346 sqlalchemy_enabled : bool
361347 sqlalchemy_mapped_dotted_names : set [str ]
362348 current_scope : Scope
363- uses : dict [str , list [tuple [ast .AST , Scope ]]]
349+ uses : dict [str , list [tuple [ast .expr , Scope ]]]
364350 soft_uses : set [str ]
365351 in_soft_use_context : bool
366352
@@ -504,7 +490,7 @@ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
504490 if self .injector_enabled :
505491 self .handle_injector_declaration (node )
506492
507- def handle_injector_declaration (self , node : Union [ AsyncFunctionDef , FunctionDef ] ) -> None :
493+ def handle_injector_declaration (self , node : AsyncFunctionDef | FunctionDef ) -> None :
508494 """
509495 Adjust for injector declaration setting.
510496
@@ -553,7 +539,7 @@ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
553539 if (self .fastapi_enabled and node .decorator_list ) or self .fastapi_dependency_support_enabled :
554540 self .handle_fastapi_decorator (node )
555541
556- def handle_fastapi_decorator (self , node : Union [ AsyncFunctionDef , FunctionDef ] ) -> None :
542+ def handle_fastapi_decorator (self , node : AsyncFunctionDef | FunctionDef ) -> None :
557543 """
558544 Adjust for FastAPI decorator setting.
559545
@@ -648,7 +634,7 @@ class ImportName:
648634
649635 _module : str
650636 _name : str
651- _alias : Optional [ str ]
637+ _alias : str | None
652638
653639 #: Whether or not this import is exempt from TC001-004 checks.
654640 exempt : bool
@@ -1034,8 +1020,8 @@ def __init__(
10341020 injector_enabled : bool ,
10351021 cattrs_enabled : bool ,
10361022 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 ,
10391025 ) -> None :
10401026 super ().__init__ ()
10411027
@@ -1074,7 +1060,7 @@ def __init__(
10741060 self .scopes : list [Scope ] = []
10751061
10761062 #: 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 )
10781064
10791065 #: Contains a set of all names to be treated like soft-uses.
10801066 # i.e. we don't know if it will be used at runtime or not, so
@@ -1085,7 +1071,7 @@ def __init__(
10851071 self .annotation_visitor = ImportAnnotationVisitor (self )
10861072
10871073 #: 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
10891075
10901076 #: Where the type checking block exists (line_start, line_end, col_offset)
10911077 # Empty type checking blocks are used for TC005 errors, while the type
@@ -1098,7 +1084,7 @@ def __init__(
10981084 self .unquoted_types_in_casts : list [tuple [int , int , str ]] = []
10991085
11001086 #: 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
11021088
11031089 #: Whether or not we're in a context where uses count as soft-uses.
11041090 # 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:
19141900 if isinstance (arg , ast .Constant ) and isinstance (arg .value , str ):
19151901 return # Type argument is already a string literal.
19161902
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 )))
19181904
19191905 def visit_Call (self , node : ast .Call ) -> None :
19201906 """Check arguments of calls, e.g. typing.cast()."""
@@ -1937,7 +1923,7 @@ class TypingOnlyImportsChecker:
19371923 'future_option_enabled' ,
19381924 ]
19391925
1940- def __init__ (self , node : ast .Module , options : Optional [ Namespace ] ) -> None :
1926+ def __init__ (self , node : ast .Module , options : Namespace | None ) -> None :
19411927 self .cwd = Path (os .getcwd ())
19421928 self .strict_mode = getattr (options , 'type_checking_strict' , False )
19431929
0 commit comments