1
1
import itertools
2
2
import sys
3
- from functools import partial
3
+ from functools import cached_property , partial
4
4
from typing import Any , Callable , Dict , List , Optional , Tuple , Type
5
5
6
6
from mypy .build import PRI_MED , PRI_MYPY
19
19
)
20
20
from mypy .types import Type as MypyType
21
21
22
- import mypy_django_plugin .transformers .orm_lookups
23
22
from mypy_django_plugin .config import DjangoPluginConfig
24
23
from mypy_django_plugin .django .context import DjangoContext
25
24
from mypy_django_plugin .exceptions import UnregisteredModelError
31
30
manytomany ,
32
31
manytoone ,
33
32
meta ,
33
+ orm_lookups ,
34
34
querysets ,
35
35
request ,
36
36
settings ,
@@ -60,10 +60,6 @@ def transform_form_class(ctx: ClassDefContext) -> None:
60
60
forms .make_meta_nested_class_inherit_from_any (ctx )
61
61
62
62
63
- def add_new_manager_base_hook (ctx : ClassDefContext ) -> None :
64
- helpers .add_new_manager_base (ctx .api , ctx .cls .fullname )
65
-
66
-
67
63
class NewSemanalDjangoPlugin (Plugin ):
68
64
def __init__ (self , options : Options ) -> None :
69
65
super ().__init__ (options )
@@ -83,15 +79,6 @@ def _get_current_queryset_bases(self) -> Dict[str, int]:
83
79
else :
84
80
return {}
85
81
86
- def _get_current_manager_bases (self ) -> Dict [str , int ]:
87
- model_sym = self .lookup_fully_qualified (fullnames .MANAGER_CLASS_FULLNAME )
88
- if model_sym is not None and isinstance (model_sym .node , TypeInfo ):
89
- bases = helpers .get_django_metadata_bases (model_sym .node , "manager_bases" )
90
- bases [fullnames .MANAGER_CLASS_FULLNAME ] = 1
91
- return bases
92
- else :
93
- return {}
94
-
95
82
def _get_current_form_bases (self ) -> Dict [str , int ]:
96
83
model_sym = self .lookup_fully_qualified (fullnames .BASEFORM_CLASS_FULLNAME )
97
84
if model_sym is not None and isinstance (model_sym .node , TypeInfo ):
@@ -165,10 +152,6 @@ def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext
165
152
if fullname == "django.contrib.auth.get_user_model" :
166
153
return partial (settings .get_user_model_hook , django_context = self .django_context )
167
154
168
- manager_bases = self ._get_current_manager_bases ()
169
- if fullname in manager_bases :
170
- return querysets .determine_proper_manager_type
171
-
172
155
info = self ._get_typeinfo_or_none (fullname )
173
156
if info :
174
157
if info .has_base (fullnames .FIELD_FULLNAME ):
@@ -177,8 +160,26 @@ def get_function_hook(self, fullname: str) -> Optional[Callable[[FunctionContext
177
160
if helpers .is_model_type (info ):
178
161
return partial (init_create .redefine_and_typecheck_model_init , django_context = self .django_context )
179
162
163
+ if info .has_base (fullnames .BASE_MANAGER_CLASS_FULLNAME ):
164
+ return querysets .determine_proper_manager_type
165
+
180
166
return None
181
167
168
+ @cached_property
169
+ def manager_and_queryset_method_hooks (self ) -> Dict [str , Callable [[MethodContext ], MypyType ]]:
170
+ typecheck_filtering_method = partial (orm_lookups .typecheck_queryset_filter , django_context = self .django_context )
171
+ return {
172
+ "values" : partial (querysets .extract_proper_type_queryset_values , django_context = self .django_context ),
173
+ "values_list" : partial (
174
+ querysets .extract_proper_type_queryset_values_list , django_context = self .django_context
175
+ ),
176
+ "annotate" : partial (querysets .extract_proper_type_queryset_annotate , django_context = self .django_context ),
177
+ "create" : partial (init_create .redefine_and_typecheck_model_create , django_context = self .django_context ),
178
+ "filter" : typecheck_filtering_method ,
179
+ "get" : typecheck_filtering_method ,
180
+ "exclude" : typecheck_filtering_method ,
181
+ }
182
+
182
183
def get_method_hook (self , fullname : str ) -> Optional [Callable [[MethodContext ], MypyType ]]:
183
184
class_fullname , _ , method_name = fullname .rpartition ("." )
184
185
# Methods called very often -- short circuit for minor speed up
@@ -208,38 +209,17 @@ def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], M
208
209
}
209
210
return hooks .get (class_fullname )
210
211
211
- manager_classes = self ._get_current_manager_bases ()
212
-
213
- if method_name == "values" :
212
+ if method_name in self .manager_and_queryset_method_hooks :
214
213
info = self ._get_typeinfo_or_none (class_fullname )
215
- if info and info .has_base (fullnames .QUERYSET_CLASS_FULLNAME ) or class_fullname in manager_classes :
216
- return partial (querysets .extract_proper_type_queryset_values , django_context = self .django_context )
217
-
218
- elif method_name == "values_list" :
219
- info = self ._get_typeinfo_or_none (class_fullname )
220
- if info and info .has_base (fullnames .QUERYSET_CLASS_FULLNAME ) or class_fullname in manager_classes :
221
- return partial (querysets .extract_proper_type_queryset_values_list , django_context = self .django_context )
222
-
223
- elif method_name == "annotate" :
224
- info = self ._get_typeinfo_or_none (class_fullname )
225
- if info and info .has_base (fullnames .QUERYSET_CLASS_FULLNAME ) or class_fullname in manager_classes :
226
- return partial (querysets .extract_proper_type_queryset_annotate , django_context = self .django_context )
227
-
214
+ if info and helpers .has_any_of_bases (
215
+ info , [fullnames .QUERYSET_CLASS_FULLNAME , fullnames .MANAGER_CLASS_FULLNAME ]
216
+ ):
217
+ return self .manager_and_queryset_method_hooks [method_name ]
228
218
elif method_name == "get_field" :
229
219
info = self ._get_typeinfo_or_none (class_fullname )
230
220
if info and info .has_base (fullnames .OPTIONS_CLASS_FULLNAME ):
231
221
return partial (meta .return_proper_field_type_from_get_field , django_context = self .django_context )
232
222
233
- elif method_name == "create" :
234
- # We need `BASE_MANAGER_CLASS_FULLNAME` to check abstract models.
235
- if class_fullname in manager_classes or class_fullname == fullnames .BASE_MANAGER_CLASS_FULLNAME :
236
- return partial (init_create .redefine_and_typecheck_model_create , django_context = self .django_context )
237
- elif method_name in {"filter" , "get" , "exclude" } and class_fullname in manager_classes :
238
- return partial (
239
- mypy_django_plugin .transformers .orm_lookups .typecheck_queryset_filter ,
240
- django_context = self .django_context ,
241
- )
242
-
243
223
return None
244
224
245
225
def get_customize_class_mro_hook (self , fullname : str ) -> Optional [Callable [[ClassDefContext ], None ]]:
@@ -262,10 +242,6 @@ def get_base_class_hook(self, fullname: str) -> Optional[Callable[[ClassDefConte
262
242
if sym is not None and isinstance (sym .node , TypeInfo ) and helpers .is_model_type (sym .node ):
263
243
return partial (process_model_class , django_context = self .django_context )
264
244
265
- # Base class is a Manager class definition
266
- if fullname in self ._get_current_manager_bases ():
267
- return add_new_manager_base_hook
268
-
269
245
# Base class is a Form class definition
270
246
if fullname in self ._get_current_form_bases ():
271
247
return transform_form_class
0 commit comments