66from typing import ClassVar , Final , Self , cast
77
88from mypy .checker import TypeChecker
9- from mypy .nodes import CallExpr , Context , Decorator , Expression , FuncDef
9+ from mypy .nodes import (
10+ GDEF ,
11+ CallExpr ,
12+ Context ,
13+ Decorator ,
14+ Expression ,
15+ FuncDef ,
16+ SymbolTableNode ,
17+ Var ,
18+ )
1019from mypy .subtypes import is_subtype
1120from mypy .types import (
1221 AnyType ,
1726 Type ,
1827 TypeOfAny ,
1928 TypeVarLikeType ,
29+ UnionType ,
2030)
2131
32+ from .argmapper import ArgMapper
2233from .checker_wrapper import CheckerWrapper
23- from .defer import DeferralError
24- from .error_codes import DUPLICATE_FIXTURE , INVALID_FIXTURE_SCOPE , MARKED_FIXTURE , REQUEST_KEYWORD
34+ from .defer import DeferralError , DeferralReason
35+ from .error_codes import (
36+ DUPLICATE_FIXTURE ,
37+ INVALID_FIXTURE_AUTOUSE ,
38+ INVALID_FIXTURE_SCOPE ,
39+ MARKED_FIXTURE ,
40+ REQUEST_KEYWORD ,
41+ )
2542from .fullname import Fullname
2643from .test_argument import TestArgument
2744from .types_module import TYPES_MODULE
45+ from .utils import strict_cast , strict_not_none
2846
2947FixtureScope = enum .IntEnum (
3048 "FixtureScope" , ["function" , "class" , "module" , "package" , "session" , "unknown" ]
3452
3553@dataclass (frozen = True , slots = True , kw_only = True )
3654class Fixture :
55+ AUTOUSE_NAME : ClassVar [str ] = "__autouse__"
3756 fullname : Fullname
3857 file : str
3958 return_type : Type
4059 arguments : Sequence [TestArgument ]
4160 scope : FixtureScope
61+ autouse : bool
4262 type_variables : Sequence [TypeVarLikeType ]
4363 context : Context
4464
@@ -54,13 +74,15 @@ def from_type(
5474 scope : FixtureScope ,
5575 file : str ,
5676 is_generator : bool ,
77+ autouse : bool ,
5778 fullname : str ,
5879 ) -> Self :
5980 func = type .definition
6081 assert isinstance (func , FuncDef | None )
6182 if isinstance (func , FuncDef ):
62- arguments = TestArgument .from_fn_def (func , checker = None , source = "fixture" )
63- assert arguments is not None
83+ arguments = strict_not_none (
84+ TestArgument .from_fn_def (func , checker = None , source = "fixture" )
85+ )
6486 context : Context = func
6587 else :
6688 arguments = TestArgument .from_type (type )
@@ -71,6 +93,7 @@ def from_type(
7193 return_type = FixtureParser .fixture_return_type (type .ret_type , is_generator = is_generator ),
7294 arguments = arguments ,
7395 scope = scope ,
96+ autouse = autouse ,
7497 context = context ,
7598 type_variables = type .variables ,
7699 )
@@ -97,6 +120,8 @@ def module_name(self) -> Fullname:
97120
98121 def as_fixture_type (self , * , decorator : Decorator , checker : TypeChecker ) -> Type :
99122 assert decorator .func .type is not None
123+ if self .autouse :
124+ self .save_to_autouse (checker )
100125 return checker .named_generic_type (
101126 f"{ TYPES_MODULE } .FixtureType" ,
102127 [
@@ -106,9 +131,37 @@ def as_fixture_type(self, *, decorator: Decorator, checker: TypeChecker) -> Type
106131 decorator .func .is_generator , fallback = checker .named_type ("builtins.object" )
107132 ),
108133 LiteralType (decorator .fullname , fallback = checker .named_type ("builtins.object" )),
134+ LiteralType (self .autouse , fallback = checker .named_type ("builtins.object" )),
109135 ],
110136 )
111137
138+ def save_to_autouse (self , checker : TypeChecker ) -> None :
139+ if str (self .module_name ) in checker .modules :
140+ node = checker .modules [str (self .module_name )].names .setdefault (
141+ self .AUTOUSE_NAME ,
142+ SymbolTableNode (
143+ GDEF ,
144+ Var (
145+ self .AUTOUSE_NAME ,
146+ UnionType ([]),
147+ ),
148+ implicit = True ,
149+ module_hidden = True ,
150+ plugin_generated = True ,
151+ ),
152+ )
153+ literal_type = LiteralType (
154+ self .name ,
155+ fallback = checker .named_type ("builtins.str" ),
156+ )
157+ assert isinstance (node .node , Var )
158+ assert isinstance (node .type , UnionType )
159+ if not any (
160+ strict_cast (LiteralType , item ).value == literal_type .value
161+ for item in node .type .items
162+ ):
163+ node .type .items .append (literal_type )
164+
112165
113166@dataclass (frozen = True , slots = True )
114167class FixtureParser (CheckerWrapper ):
@@ -134,6 +187,7 @@ def from_decorator(self, decorator: Decorator) -> Fixture | None:
134187 ),
135188 arguments = arguments ,
136189 scope = self ._fixture_scope_from_decorator (fixture_decorator ),
190+ autouse = self ._fixture_autouse_from_decorator (fixture_decorator ),
137191 context = decorator .func ,
138192 type_variables = type_ .variables ,
139193 )
@@ -210,7 +264,7 @@ def _warn_extra_decorator(self, decorator: Expression) -> None:
210264 def _is_fixture_decorator (self , decorator : Expression ) -> bool :
211265 decorator_type = self .checker .lookup_type_or_none (decorator )
212266 if decorator_type is None :
213- raise DeferralError ()
267+ raise DeferralError (DeferralReason . REQUIRED_WAIT )
214268 return self ._is_fixture_type (decorator_type ) or (
215269 isinstance (decorator_type , Overloaded )
216270 and any (self ._is_fixture_type (overload .ret_type ) for overload in decorator_type .items )
@@ -229,12 +283,9 @@ def _fixture_scope_from_decorator(self, decorator: Expression) -> FixtureScope:
229283 return DEFAULT_SCOPE
230284
231285 def _fixture_scope_from_call (self , call : CallExpr ) -> FixtureScope :
232- scope_expressions = [
233- arg for name , arg in zip (call .arg_names , call .args , strict = True ) if name == "scope"
234- ]
235- if not scope_expressions :
286+ scope_expression = ArgMapper .named_arg (call , "scope" )
287+ if scope_expression is None :
236288 return DEFAULT_SCOPE
237- [scope_expression ] = scope_expressions
238289 return self ._fixture_scope_from_type (
239290 self .checker .lookup_type (scope_expression ), context = scope_expression
240291 )
@@ -250,6 +301,39 @@ def _fixture_scope_from_type(self, type_: Type, context: Context) -> FixtureScop
250301
251302 return FixtureScope .unknown
252303
304+ def _fixture_autouse_from_decorator (self , decorator : Expression ) -> bool :
305+ if isinstance (decorator , CallExpr ):
306+ return self ._fixture_autouse_from_call (decorator )
307+ return False
308+
309+ def _fixture_autouse_from_call (self , call : CallExpr ) -> bool :
310+ autouse_expression = ArgMapper .named_arg (call , "autouse" )
311+ if autouse_expression is None :
312+ return False
313+ return self ._fixture_autouse_from_type (
314+ self .checker .lookup_type (autouse_expression ), context = autouse_expression
315+ )
316+
317+ def _fixture_autouse_from_type (self , type_ : Type , context : Context ) -> bool :
318+ for value in [True , False ]:
319+ if is_subtype (
320+ type_ , LiteralType (value , fallback = self .checker .named_type ("builtins.bool" ))
321+ ):
322+ return value
323+ if isinstance (type_ , LiteralType ) and isinstance (type_ .value , bool ):
324+ return type_ .value
325+ self .fail (
326+ """Invalid type for "autouse". This fixture will be not be applied automatically when type checking.""" ,
327+ context = context ,
328+ code = INVALID_FIXTURE_AUTOUSE ,
329+ )
330+ self .note (
331+ "Use `autouse=True` directly." ,
332+ context = context ,
333+ code = INVALID_FIXTURE_AUTOUSE ,
334+ )
335+ return False
336+
253337 def is_request_name (self , decorator : Decorator ) -> bool :
254338 if is_request_name := decorator .name == "request" :
255339 self .fail (
0 commit comments