Skip to content

Commit 3b658fa

Browse files
committed
construct as_manager instance at checker time
1 parent 9fa3404 commit 3b658fa

File tree

3 files changed

+32
-21
lines changed

3 files changed

+32
-21
lines changed

mypy_django_plugin/main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
)
3838
from mypy_django_plugin.transformers.functional import resolve_str_promise_attribute
3939
from mypy_django_plugin.transformers.managers import (
40+
construct_as_manager_instance,
4041
create_new_manager_class_from_as_manager_method,
4142
create_new_manager_class_from_from_queryset_method,
4243
reparametrize_any_manager_hook,
@@ -208,6 +209,10 @@ def get_method_hook(self, fullname: str) -> Optional[Callable[[MethodContext], M
208209
fullnames.REVERSE_MANY_TO_ONE_DESCRIPTOR: manytoone.refine_many_to_one_related_manager,
209210
}
210211
return hooks.get(class_fullname)
212+
elif method_name == "as_manager":
213+
info = self._get_typeinfo_or_none(class_fullname)
214+
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
215+
return partial(construct_as_manager_instance, info=info)
211216

212217
if method_name in self.manager_and_queryset_method_hooks:
213218
info = self._get_typeinfo_or_none(class_fullname)

mypy_django_plugin/transformers/managers.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
StrExpr,
1616
SymbolTableNode,
1717
TypeInfo,
18-
Var,
1918
)
20-
from mypy.plugin import AttributeContext, ClassDefContext, DynamicClassDefContext
19+
from mypy.plugin import AttributeContext, ClassDefContext, DynamicClassDefContext, MethodContext
2120
from mypy.semanal import SemanticAnalyzer
2221
from mypy.semanal_shared import has_placeholder
2322
from mypy.subtypes import find_member
@@ -552,23 +551,9 @@ def create_new_manager_class_from_as_manager_method(ctx: DynamicClassDefContext)
552551
manager_name=manager_class_name,
553552
manager_base=manager_base,
554553
)
554+
queryset_info.metadata.setdefault("django_as_manager_names", {})
555+
queryset_info.metadata["django_as_manager_names"][semanal_api.cur_mod_id] = new_manager_info.name
555556

556-
# Whenever `<QuerySet>.as_manager()` isn't called at class level, we want to ensure
557-
# that the variable is an instance of our generated manager. Instead of the return
558-
# value of `.as_manager()`. Though model argument is populated as `Any`.
559-
# `transformers.models.AddManagers` will populate a model's manager(s), when it
560-
# finds it on class level.
561-
var = Var(name=ctx.name, type=Instance(new_manager_info, [AnyType(TypeOfAny.from_omitted_generics)]))
562-
var.info = new_manager_info
563-
var._fullname = f"{current_module.fullname}.{ctx.name}"
564-
var.is_inferred = True
565-
# Note: Order of `add_symbol_table_node` calls matters. Depending on what level
566-
# we've found the `.as_manager()` call. Point here being that we want to replace the
567-
# `.as_manager` return value with our newly created manager.
568-
added = semanal_api.add_symbol_table_node(
569-
ctx.name, SymbolTableNode(semanal_api.current_symbol_kind(), var, plugin_generated=True)
570-
)
571-
assert added
572557
# Add the new manager to the current module
573558
added = semanal_api.add_symbol_table_node(
574559
# We'll use `new_manager_info.name` instead of `manager_class_name` here
@@ -580,6 +565,27 @@ def create_new_manager_class_from_as_manager_method(ctx: DynamicClassDefContext)
580565
assert added
581566

582567

568+
def construct_as_manager_instance(ctx: MethodContext, *, info: TypeInfo) -> MypyType:
569+
api = helpers.get_typechecker_api(ctx)
570+
module = helpers.get_current_module(api)
571+
try:
572+
manager_name = info.metadata["django_as_manager_names"][module.fullname]
573+
except KeyError:
574+
return ctx.default_return_type
575+
576+
manager_node = api.lookup(manager_name)
577+
if not isinstance(manager_node.node, TypeInfo):
578+
return ctx.default_return_type
579+
580+
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
581+
if outer_model_info is not None and outer_model_info.self_type is not None:
582+
model_tp: MypyType = outer_model_info.self_type
583+
else:
584+
model_tp = AnyType(TypeOfAny.from_omitted_generics)
585+
586+
return Instance(manager_node.node, [model_tp])
587+
588+
583589
def reparametrize_any_manager_hook(ctx: ClassDefContext) -> None:
584590
"""
585591
Add implicit generics to manager classes that are defined without generic.

tests/typecheck/managers/querysets/test_as_manager.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
def just_int(self) -> int: ...
2929
3030
class MyModel(models.Model):
31-
objects = MyQuerySet.as_manager() # type: ignore[var-annotated]
31+
objects = MyQuerySet.as_manager()
3232
3333
class QuerySetWithoutSelf(models.QuerySet["MyModelWithoutSelf"]):
3434
def method(self) -> "QuerySetWithoutSelf":
@@ -75,7 +75,7 @@
7575
...
7676
7777
class MyModel(models.Model):
78-
objects = MyQuerySet.as_manager() # type: ignore[var-annotated]
78+
objects = MyQuerySet.as_manager()
7979
8080
- case: model_gets_generated_manager_as_default_manager
8181
main: |
@@ -285,7 +285,7 @@
285285
objects = MyModelQuerySet.as_manager()
286286
287287
class MyOtherModel(models.Model):
288-
objects = _MyModelQuerySet2.as_manager() # type: ignore
288+
objects = _MyModelQuerySet2.as_manager()
289289
290290
- case: handles_type_vars
291291
main: |

0 commit comments

Comments
 (0)