Skip to content

Commit c232226

Browse files
committed
fixup! fixup! fixup! fixup! Implement support for <QuerySet>.as_manager()
1 parent 506ce06 commit c232226

File tree

3 files changed

+49
-10
lines changed

3 files changed

+49
-10
lines changed

mypy_django_plugin/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,12 @@ def get_type_analyze_hook(self, fullname: str) -> Optional[Callable[[AnalyzeType
304304

305305
def get_dynamic_class_hook(self, fullname: str) -> Optional[Callable[[DynamicClassDefContext], None]]:
306306
# Create a new manager class definition when a manager's '.from_queryset' classmethod is called
307-
if fullname.endswith(".from_queryset"):
308-
class_name, _, _ = fullname.rpartition(".")
307+
class_name, _, method_name = fullname.rpartition(".")
308+
if method_name == "from_queryset":
309309
info = self._get_typeinfo_or_none(class_name)
310310
if info and info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME):
311311
return create_new_manager_class_from_from_queryset_method
312-
elif fullname.endswith(".as_manager"):
313-
class_name, _, _ = fullname.rpartition(".")
312+
elif method_name == "as_manager":
314313
info = self._get_typeinfo_or_none(class_name)
315314
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
316315
return create_new_manager_class_from_as_manager_method

mypy_django_plugin/transformers/managers.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,13 +286,15 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
286286
queryset_info = queryset_sym.node
287287
assert isinstance(queryset_info, TypeInfo)
288288

289+
class_name_param_value = None
289290
if len(ctx.call.args) > 1:
290291
expr = ctx.call.args[1]
291-
assert isinstance(expr, StrExpr)
292-
manager_class_name = expr.value
293-
else:
294-
manager_class_name = manager_base.name + "From" + queryset_info.name
292+
if isinstance(expr, StrExpr):
293+
class_name_param_value = expr.value
294+
# When the argument is not a `StrExpr` we're gonna have a hard time figuring
295+
# out what the runtime value will be, so we'll just use the default instead.
295296

297+
manager_class_name = class_name_param_value or manager_base.name + "From" + queryset_info.name
296298
manager_base_instance = fill_typevars(manager_base)
297299
assert isinstance(manager_base_instance, Instance)
298300
# Create a new `TypeInfo` instance for the manager type
@@ -324,8 +326,8 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte
324326
var._fullname = f"{semanal_api.cur_mod_id}.{ctx.name}"
325327
var.is_inferred = True
326328
# Note: Order of `add_symbol_table_node` calls matter. Case being if
327-
# `ctx.name == new_manager_info.name`, then we'd _only_ like the type and not the
328-
# `Var` to exist..
329+
# `ctx.name == new_manager_info.name`, then we'd like the type to override the
330+
# `Var`, so the `Var` won't exist in the end.
329331
assert semanal_api.add_symbol_table_node(ctx.name, SymbolTableNode(symbol_kind, var, plugin_generated=True))
330332
# Insert the new manager dynamic class
331333
assert semanal_api.add_symbol_table_node(

tests/typecheck/managers/querysets/test_from_queryset.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,41 @@
545545
BaseManagerFromModelQuerySet = BaseManager.from_queryset(ModelQuerySet)
546546
class MyModel(models.Model):
547547
objects = BaseManagerFromModelQuerySet()
548+
549+
- case: accepts_explicit_none_as_class_name
550+
main: |
551+
from myapp.models import PositionalNone, NoneAsKwarg
552+
reveal_type(PositionalNone) # N: Revealed type is "Type[myapp.models.BaseManagerFromModelQuerySet[Any]]"
553+
reveal_type(NoneAsKwarg) # N: Revealed type is "Type[myapp.models.BaseManagerFromModelQuerySet[Any]]"
554+
installed_apps:
555+
- myapp
556+
files:
557+
- path: myapp/__init__.py
558+
- path: myapp/models.py
559+
content: |
560+
from django.db import models
561+
from django.db.models.manager import BaseManager
562+
563+
class ModelQuerySet(models.QuerySet):
564+
...
565+
566+
PositionalNone = BaseManager.from_queryset(ModelQuerySet, None)
567+
NoneAsKwarg = BaseManager.from_queryset(ModelQuerySet, class_name=None)
568+
569+
- case: uses_fallback_class_name_when_argument_is_not_string_expression
570+
main: |
571+
from myapp.models import StrCallable
572+
reveal_type(StrCallable) # N: Revealed type is "Type[myapp.models.BaseManagerFromModelQuerySet[Any]]"
573+
installed_apps:
574+
- myapp
575+
files:
576+
- path: myapp/__init__.py
577+
- path: myapp/models.py
578+
content: |
579+
from django.db import models
580+
from django.db.models.manager import BaseManager
581+
582+
class ModelQuerySet(models.QuerySet):
583+
...
584+
585+
StrCallable = BaseManager.from_queryset(ModelQuerySet, class_name=str(1))

0 commit comments

Comments
 (0)