Skip to content

Commit 86c63d7

Browse files
syastrovmkurnikov
authored andcommitted
Fix type errors on other models' managers when using objects = models.Manager() in Model. (#34)
* Fix bug where models with a class variable using a manager defined would interfere with other managers. - Fill in the type argument for that particular instance of the manager, rather than modifying the bases of the Manager type. - Instantiate a new Instance from determine_proper_manager_type so The code doesn't crash under mypy-mypyc. * Use helpers.reparametrize_instance per review comment. * Updated ignored errors in Django test for get_objects_or_404. - For some reason, `Manager[nothing]` is now removed from expected types. However, I think this makes sense anyway, as Manager is a subclass of QuerySet.
1 parent 050c1b8 commit 86c63d7

File tree

3 files changed

+74
-5
lines changed

3 files changed

+74
-5
lines changed

mypy_django_plugin/main.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,20 @@ def determine_proper_manager_type(ctx: FunctionContext) -> Type:
6464
if not isinstance(ret, Instance):
6565
return ret
6666

67+
has_manager_base = False
6768
for i, base in enumerate(ret.type.bases):
6869
if base.type.fullname() in {helpers.MANAGER_CLASS_FULLNAME,
6970
helpers.RELATED_MANAGER_CLASS_FULLNAME,
7071
helpers.BASE_MANAGER_CLASS_FULLNAME}:
71-
ret.type.bases[i] = Instance(base.type, [Instance(outer_model_info, [])])
72-
return ret
73-
return ret
72+
has_manager_base = True
73+
break
74+
75+
if has_manager_base:
76+
# Fill in the manager's type argument from the outer model
77+
new_type_args = [Instance(outer_model_info, [])]
78+
return helpers.reparametrize_instance(ret, new_type_args)
79+
else:
80+
return ret
7481

7582

7683
def return_user_model_hook(ctx: FunctionContext) -> Type:

scripts/typecheck_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@
199199
],
200200
'get_object_or_404': [
201201
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
202-
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
202+
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
203203
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
204-
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
204+
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>]]"',
205205
'CustomClass'
206206
],
207207
'get_or_create': [

test-data/typecheck/managers.test

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,66 @@ class AbstractBase2(models.Model):
136136

137137
class Child(AbstractBase1, AbstractBase2):
138138
pass
139+
[out]
140+
141+
[CASE managers_from_unrelated_models_dont_interfere]
142+
143+
from django.db import models
144+
145+
# Normal scenario where one model has a manager with an annotation of the same type as the model
146+
class UnrelatedModel(models.Model):
147+
objects = models.Manager[UnrelatedModel]()
148+
149+
class MyModel(models.Model):
150+
pass
151+
152+
reveal_type(UnrelatedModel.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel]'
153+
reveal_type(UnrelatedModel.objects.first()) # E: Revealed type is 'Union[main.UnrelatedModel*, None]'
154+
155+
reveal_type(MyModel.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
156+
reveal_type(MyModel.objects.first()) # E: Revealed type is 'Union[main.MyModel*, None]'
157+
158+
# Possible to specify objects without explicit annotation of models.Manager()
159+
class UnrelatedModel2(models.Model):
160+
objects = models.Manager()
161+
162+
class MyModel2(models.Model):
163+
pass
164+
165+
reveal_type(UnrelatedModel2.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel2]'
166+
reveal_type(UnrelatedModel2.objects.first()) # E: Revealed type is 'Union[main.UnrelatedModel2*, None]'
167+
168+
reveal_type(MyModel2.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel2]'
169+
reveal_type(MyModel2.objects.first()) # E: Revealed type is 'Union[main.MyModel2*, None]'
170+
171+
172+
# Inheritance works
173+
class ParentOfMyModel3(models.Model):
174+
objects = models.Manager()
175+
176+
class MyModel3(ParentOfMyModel3):
177+
pass
178+
179+
180+
reveal_type(ParentOfMyModel3.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel3]'
181+
reveal_type(ParentOfMyModel3.objects.first()) # E: Revealed type is 'Union[main.ParentOfMyModel3*, None]'
182+
183+
reveal_type(MyModel3.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel3]'
184+
reveal_type(MyModel3.objects.first()) # E: Revealed type is 'Union[main.MyModel3*, None]'
185+
186+
187+
# Inheritance works with explicit objects in child
188+
class ParentOfMyModel4(models.Model):
189+
objects = models.Manager()
190+
191+
class MyModel4(ParentOfMyModel4):
192+
objects = models.Manager[MyModel4]()
193+
194+
reveal_type(ParentOfMyModel4.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel4]'
195+
reveal_type(ParentOfMyModel4.objects.first()) # E: Revealed type is 'Union[main.ParentOfMyModel4*, None]'
196+
197+
reveal_type(MyModel4.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel4]'
198+
reveal_type(MyModel4.objects.first()) # E: Revealed type is 'Union[main.MyModel4*, None]'
199+
200+
139201
[out]

0 commit comments

Comments
 (0)