1
+ from mypy .checker import fill_typevars
1
2
from mypy .nodes import GDEF , Decorator , FuncDef , MemberExpr , NameExpr , RefExpr , StrExpr , SymbolTableNode , TypeInfo
2
3
from mypy .plugin import ClassDefContext , DynamicClassDefContext
3
- from mypy .types import AnyType , Instance , TypeOfAny
4
+ from mypy .types import CallableType , Instance , TypeVarType , UnboundType , get_proper_type
4
5
5
6
from mypy_django_plugin .lib import fullnames , helpers
6
7
@@ -29,15 +30,11 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
29
30
# But it should be analyzed again, so this isn't a problem.
30
31
return
31
32
33
+ base_manager_instance = fill_typevars (base_manager_info )
34
+ assert isinstance (base_manager_instance , Instance )
32
35
new_manager_info = semanal_api .basic_new_typeinfo (
33
- ctx .name , basetype_or_fallback = Instance ( base_manager_info , [ AnyType ( TypeOfAny . unannotated )]) , line = ctx .call .line
36
+ ctx .name , basetype_or_fallback = base_manager_instance , line = ctx .call .line
34
37
)
35
- new_manager_info .line = ctx .call .line
36
- new_manager_info .defn .line = ctx .call .line
37
- new_manager_info .metaclass_type = new_manager_info .calculate_metaclass_type ()
38
-
39
- current_module = semanal_api .cur_mod_node
40
- current_module .names [ctx .name ] = SymbolTableNode (GDEF , new_manager_info , plugin_generated = True )
41
38
42
39
sym = semanal_api .lookup_fully_qualified_or_none (derived_queryset_fullname )
43
40
assert sym is not None
@@ -52,6 +49,15 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
52
49
derived_queryset_info = sym .node
53
50
assert isinstance (derived_queryset_info , TypeInfo )
54
51
52
+ new_manager_info .line = ctx .call .line
53
+ new_manager_info .type_vars = base_manager_info .type_vars
54
+ new_manager_info .defn .type_vars = base_manager_info .defn .type_vars
55
+ new_manager_info .defn .line = ctx .call .line
56
+ new_manager_info .metaclass_type = new_manager_info .calculate_metaclass_type ()
57
+
58
+ current_module = semanal_api .cur_mod_node
59
+ current_module .names [ctx .name ] = SymbolTableNode (GDEF , new_manager_info , plugin_generated = True )
60
+
55
61
if len (ctx .call .args ) > 1 :
56
62
expr = ctx .call .args [1 ]
57
63
assert isinstance (expr , StrExpr )
@@ -64,11 +70,19 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
64
70
base_manager_info .metadata ["from_queryset_managers" ] = {}
65
71
base_manager_info .metadata ["from_queryset_managers" ][custom_manager_generated_fullname ] = new_manager_info .fullname
66
72
73
+ # So that the plugin will reparameterize the manager when it is constructed inside of a Model definition
74
+ helpers .add_new_manager_base (semanal_api , new_manager_info .fullname )
75
+
67
76
class_def_context = ClassDefContext (cls = new_manager_info .defn , reason = ctx .call , api = semanal_api )
68
- self_type = Instance (new_manager_info , [])
77
+ self_type = fill_typevars (new_manager_info )
78
+ assert isinstance (self_type , Instance )
79
+ queryset_method_names = []
80
+
69
81
# we need to copy all methods in MRO before django.db.models.query.QuerySet
70
82
for class_mro_info in derived_queryset_info .mro :
71
83
if class_mro_info .fullname == fullnames .QUERYSET_CLASS_FULLNAME :
84
+ for name , sym in class_mro_info .names .items ():
85
+ queryset_method_names .append (name )
72
86
break
73
87
for name , sym in class_mro_info .names .items ():
74
88
if isinstance (sym .node , FuncDef ):
@@ -80,3 +94,59 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
80
94
helpers .copy_method_to_another_class (
81
95
class_def_context , self_type , new_method_name = name , method_node = func_node
82
96
)
97
+
98
+ # Gather names of all BaseManager methods
99
+ manager_method_names = []
100
+ for manager_mro_info in new_manager_info .mro :
101
+ if manager_mro_info .fullname == fullnames .BASE_MANAGER_CLASS_FULLNAME :
102
+ for name , sym in manager_mro_info .names .items ():
103
+ manager_method_names .append (name )
104
+
105
+ # Copy/alter all methods in common between BaseManager/QuerySet over to the new manager if their return type is
106
+ # the QuerySet's self-type. Alter the return type to be the custom queryset, parameterized by the manager's model
107
+ # type variable.
108
+ for class_mro_info in derived_queryset_info .mro :
109
+ if class_mro_info .fullname != fullnames .QUERYSET_CLASS_FULLNAME :
110
+ continue
111
+ for name , sym in class_mro_info .names .items ():
112
+ if name not in manager_method_names :
113
+ continue
114
+
115
+ if isinstance (sym .node , FuncDef ):
116
+ func_node = sym .node
117
+ elif isinstance (sym .node , Decorator ):
118
+ func_node = sym .node .func
119
+ else :
120
+ continue
121
+
122
+ method_type = func_node .type
123
+ if not isinstance (method_type , CallableType ):
124
+ if not semanal_api .final_iteration :
125
+ semanal_api .defer ()
126
+ return None
127
+ original_return_type = method_type .ret_type
128
+ if original_return_type is None :
129
+ continue
130
+
131
+ # Skip any method that doesn't return _QS
132
+ original_return_type = get_proper_type (original_return_type )
133
+ if isinstance (original_return_type , UnboundType ):
134
+ if original_return_type .name != "_QS" :
135
+ continue
136
+ elif isinstance (original_return_type , TypeVarType ):
137
+ if original_return_type .name != "_QS" :
138
+ continue
139
+ else :
140
+ continue
141
+
142
+ # Return the custom queryset parameterized by the manager's type vars
143
+ return_type = Instance (derived_queryset_info , self_type .args )
144
+
145
+ helpers .copy_method_to_another_class (
146
+ class_def_context ,
147
+ self_type ,
148
+ new_method_name = name ,
149
+ method_node = func_node ,
150
+ return_type = return_type ,
151
+ original_module_name = class_mro_info .module_name ,
152
+ )
0 commit comments