Skip to content

Commit aa86858

Browse files
committed
Add test and other minor tweaks
1 parent 034060c commit aa86858

File tree

3 files changed

+40
-55
lines changed

3 files changed

+40
-55
lines changed

mypy_django_plugin/transformers/managers.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from typing import Optional, Tuple, Union
1+
from typing import Optional, Union
22

3-
from django.db.models import base, manager
43
from mypy.checker import TypeChecker, fill_typevars
54
from mypy.nodes import (
65
GDEF,
@@ -9,15 +8,14 @@
98
FuncBase,
109
FuncDef,
1110
MemberExpr,
12-
NameExpr,
1311
OverloadedFuncDef,
1412
RefExpr,
1513
StrExpr,
1614
SymbolTableNode,
1715
TypeInfo,
1816
Var,
1917
)
20-
from mypy.plugin import AttributeContext, ClassDefContext, DynamicClassDefContext, SemanticAnalyzerPluginInterface
18+
from mypy.plugin import AttributeContext, DynamicClassDefContext, SemanticAnalyzerPluginInterface
2119
from mypy.types import AnyType, CallableType, Instance, ProperType
2220
from mypy.types import Type as MypyType
2321
from mypy.types import TypeOfAny
@@ -192,19 +190,12 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
192190
# This is just a deferral run where our work is already finished
193191
return
194192

195-
new_manager_info, generated_name = create_manager_info_from_from_queryset_call(ctx.api, ctx.call, ctx.name)
193+
new_manager_info = create_manager_info_from_from_queryset_call(ctx.api, ctx.call, ctx.name)
196194
if new_manager_info is None:
197195
if not ctx.api.final_iteration:
198196
ctx.api.defer()
199197
return
200198

201-
assert generated_name
202-
manager_fullname = ".".join(["django.db.models.manager", generated_name])
203-
204-
base_manager_info = new_manager_info.mro[1]
205-
base_manager_info.metadata.setdefault("from_queryset_managers", {})
206-
base_manager_info.metadata["from_queryset_managers"][manager_fullname] = new_manager_info.fullname
207-
208199
# So that the plugin will reparameterize the manager when it is constructed inside of a Model definition
209200
helpers.add_new_manager_base(semanal_api, new_manager_info.fullname)
210201

@@ -217,7 +208,7 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
217208

218209
def create_manager_info_from_from_queryset_call(
219210
api: SemanticAnalyzerPluginInterface, call_expr: CallExpr, name: Optional[str] = None
220-
) -> Tuple[Optional[TypeInfo], Optional[str]]:
211+
) -> Optional[TypeInfo]:
221212
"""
222213
Extract manager and queryset TypeInfo from a from_queryset call.
223214
"""
@@ -236,14 +227,14 @@ def create_manager_info_from_from_queryset_call(
236227
or not isinstance(call_expr.args[0].node, TypeInfo)
237228
or not call_expr.args[0].node.has_base(fullnames.QUERYSET_CLASS_FULLNAME)
238229
):
239-
return None, None
230+
return None
240231

241232
base_manager_info, queryset_info = call_expr.callee.expr.node, call_expr.args[0].node
242233
if queryset_info.fullname is None:
243234
# In some cases, due to the way the semantic analyzer works, only
244235
# passed_queryset.name is available. But it should be analyzed again,
245236
# so this isn't a problem.
246-
return None, None
237+
return None
247238

248239
if len(call_expr.args) == 2 and isinstance(call_expr.args[1], StrExpr):
249240
manager_name = call_expr.args[1].value
@@ -254,7 +245,13 @@ def create_manager_info_from_from_queryset_call(
254245

255246
popuplate_manager_from_queryset(new_manager_info, queryset_info)
256247

257-
return new_manager_info, manager_name
248+
manager_fullname = ".".join(["django.db.models.manager", manager_name])
249+
250+
base_manager_info = new_manager_info.mro[1]
251+
base_manager_info.metadata.setdefault("from_queryset_managers", {})
252+
base_manager_info.metadata["from_queryset_managers"][manager_fullname] = new_manager_info.fullname
253+
254+
return new_manager_info
258255

259256

260257
def create_manager_class(

mypy_django_plugin/transformers/models.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from mypy.checker import TypeChecker
88
from mypy.nodes import (
99
ARG_STAR2,
10-
GDEF,
10+
MDEF,
1111
Argument,
1212
AssignmentStmt,
1313
CallExpr,
@@ -24,7 +24,6 @@
2424
from mypy.types import AnyType, Instance
2525
from mypy.types import Type as MypyType
2626
from mypy.types import TypedDictType, TypeOfAny
27-
from typing_extensions import reveal_type
2827

2928
from mypy_django_plugin.django.context import DjangoContext
3029
from mypy_django_plugin.errorcodes import MANAGER_MISSING
@@ -317,18 +316,15 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
317316

318317
def get_manager_expression(self, name: str) -> Optional[AssignmentStmt]:
319318
# TODO: What happens if the manager is defined multiple times?
320-
return next(
321-
(
322-
expr
323-
for expr in self.ctx.cls.defs.body
324-
if (
325-
isinstance(expr, AssignmentStmt)
326-
and isinstance(expr.lvalues[0], NameExpr)
327-
and expr.lvalues[0].name == name
328-
)
329-
),
330-
None,
331-
)
319+
for expr in self.ctx.cls.defs.body:
320+
if (
321+
isinstance(expr, AssignmentStmt)
322+
and isinstance(expr.lvalues[0], NameExpr)
323+
and expr.lvalues[0].name == name
324+
):
325+
return expr
326+
327+
return None
332328

333329
def get_dynamic_manager(self, fullname: str, manager: Manager) -> Optional[TypeInfo]:
334330
"""
@@ -361,15 +357,15 @@ class MyModel(models.Model):
361357
if not isinstance(expr, CallExpr) or not isinstance(expr.callee, CallExpr):
362358
return None
363359

364-
new_manager_info, _ = create_manager_info_from_from_queryset_call(self.api, expr.callee)
360+
new_manager_info = create_manager_info_from_from_queryset_call(self.api, expr.callee)
365361

366362
if new_manager_info:
367363
assert self.api.add_symbol_table_node(
368364
# We'll use `new_manager_info.name` instead of `manager_class_name` here
369365
# to handle possible name collisions, as it's unique.
370366
new_manager_info.name,
371367
# Note that the generated manager type is always inserted at module level
372-
SymbolTableNode(GDEF, new_manager_info, plugin_generated=True),
368+
SymbolTableNode(MDEF, new_manager_info, plugin_generated=True),
373369
)
374370

375371
return new_manager_info
@@ -382,6 +378,7 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
382378

383379
default_manager_cls = model_cls._meta.default_manager.__class__
384380
default_manager_fullname = helpers.get_class_fullname(default_manager_cls)
381+
385382
try:
386383
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(default_manager_fullname)
387384
except helpers.IncompleteDefnException as exc:

tests/typecheck/managers/querysets/test_from_queryset.yml

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -359,42 +359,33 @@
359359
class MyModel(models.Model):
360360
objects = NewManager()
361361
362-
- case: from_queryset_in_model_class_body_yields_message
362+
- case: test_queryset_in_model_class_body
363363
main: |
364364
from myapp.models import MyModel
365-
reveal_type(MyModel.base_manager) # N: Revealed type is "myapp.models.BaseManagerFromMyQuerySet[myapp.models.MyModel]"
366-
reveal_type(MyModel.manager) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel]"
367-
reveal_type(MyModel.custom_manager) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
365+
reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.MyModel.MyManagerFromMyQuerySet[myapp.models.MyModel]"
366+
reveal_type(MyModel.objects.all) # N: Revealed type is "def () -> myapp.models.MyQuerySet[myapp.models.MyModel]"
367+
reveal_type(MyModel.objects.custom) # N: Revealed type is "def () -> myapp.models.MyQuerySet"
368+
reveal_type(MyModel.objects.all().filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]"
369+
reveal_type(MyModel.objects.custom().filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet"
370+
reveal_type(MyModel.objects2) # N: Revealed type is "myapp.models.MyModel.MyManagerFromMyQuerySet[myapp.models.MyModel]"
368371
installed_apps:
369372
- myapp
370373
files:
371374
- path: myapp/__init__.py
372375
- path: myapp/models.py
373376
content: |
374377
from django.db import models
375-
from django.db.models.manager import BaseManager
376378
377-
class MyQuerySet(models.QuerySet["MyModel"]):
378-
def queryset_method(self) -> int:
379-
return 1
379+
class MyManager(models.Manager["MyModel"]):
380+
pass
380381
381-
class MyManager(BaseManager):
382-
...
382+
class MyQuerySet(models.QuerySet["MyModel"]):
383+
def custom(self) -> "MyQuerySet":
384+
pass
383385
384-
BaseManagerFromMyQuerySet = BaseManager.from_queryset(MyQuerySet)
385-
ManagerFromMyQuerySet = models.Manager.from_queryset(MyQuerySet)
386-
MyManagerFromMyQuerySet = MyManager.from_queryset(MyQuerySet)
387386
class MyModel(models.Model):
388-
objects1 = BaseManager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
389-
objects2 = BaseManager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
390-
objects3 = models.Manager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
391-
objects4 = models.Manager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
392-
objects5 = MyManager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
393-
objects6 = MyManager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
394-
# Initiating the manager type is fine
395-
base_manager = BaseManagerFromMyQuerySet()
396-
manager = ManagerFromMyQuerySet()
397-
custom_manager = MyManagerFromMyQuerySet()
387+
objects = MyManager.from_queryset(MyQuerySet)()
388+
objects2 = MyManager.from_queryset(MyQuerySet)()
398389
399390
- case: from_queryset_includes_methods_returning_queryset
400391
main: |

0 commit comments

Comments
 (0)