16
16
SymbolTableNode ,
17
17
TypeInfo ,
18
18
)
19
- from mypy .plugin import AttributeContext , ClassDefContext , DynamicClassDefContext , MethodContext
19
+ from mypy .plugin import AttributeContext , ClassDefContext , DynamicClassDefContext
20
+ from mypy .plugins .common import add_method_to_class
20
21
from mypy .semanal import SemanticAnalyzer
21
22
from mypy .semanal_shared import has_placeholder
22
23
from mypy .subtypes import find_member
@@ -482,44 +483,37 @@ def populate_manager_from_queryset(manager_info: TypeInfo, queryset_info: TypeIn
482
483
)
483
484
484
485
485
- def create_new_manager_class_from_as_manager_method (ctx : DynamicClassDefContext ) -> None :
486
- """
487
- Insert a new manager class node for a
488
-
489
- ```
490
- <manager name> = <QuerySet>.as_manager()
491
- ```
492
- """
486
+ def add_as_manager_to_queryset_class (ctx : ClassDefContext ) -> None :
493
487
semanal_api = helpers .get_semanal_api (ctx )
494
- # Don't redeclare the manager class if we've already defined it.
495
- manager_node = semanal_api .lookup_current_scope (ctx .name )
496
- if manager_node and manager_node .type is not None :
497
- # This is just a deferral run where our work is already finished
498
- return
499
488
500
- manager_sym = semanal_api .lookup_fully_qualified_or_none (fullnames .MANAGER_CLASS_FULLNAME )
501
- assert manager_sym is not None
502
- manager_base = manager_sym .node
503
- if manager_base is None :
489
+ def _defer () -> None :
504
490
if not semanal_api .final_iteration :
505
491
semanal_api .defer ()
506
- return
507
492
508
- assert isinstance (manager_base , TypeInfo )
493
+ queryset_info = semanal_api .type
494
+ if queryset_info is None :
495
+ return _defer ()
509
496
510
- callee = ctx . call . callee
511
- assert isinstance ( callee , MemberExpr )
512
- assert isinstance ( callee . expr , RefExpr )
497
+ # either a manual `as_manager` definition or this is a deferral pass
498
+ if "as_manager" in queryset_info . names :
499
+ return
513
500
514
- queryset_info = callee .expr .node
515
- if queryset_info is None :
516
- if not semanal_api .final_iteration :
517
- semanal_api .defer ()
501
+ base_as_manager = queryset_info .get ("as_manager" )
502
+ if (
503
+ base_as_manager is None
504
+ or not isinstance (base_as_manager .type , CallableType )
505
+ or not isinstance (base_as_manager .type .ret_type , Instance )
506
+ ):
518
507
return
519
508
520
- assert isinstance (queryset_info , TypeInfo )
509
+ base_ret_type = base_as_manager .type .ret_type .type
510
+
511
+ manager_sym = semanal_api .lookup_fully_qualified_or_none (fullnames .MANAGER_CLASS_FULLNAME )
512
+ if manager_sym is None or not isinstance (manager_sym .node , TypeInfo ):
513
+ return _defer ()
521
514
522
- manager_class_name = manager_base .name + "From" + queryset_info .name
515
+ manager_base = manager_sym .node
516
+ manager_class_name = f"{ manager_base .name } From{ queryset_info .name } "
523
517
current_module = semanal_api .modules [semanal_api .cur_mod_id ]
524
518
existing_sym = current_module .names .get (manager_class_name )
525
519
if (
@@ -535,54 +529,37 @@ def create_new_manager_class_from_as_manager_method(ctx: DynamicClassDefContext)
535
529
try :
536
530
new_manager_info = create_manager_class (
537
531
api = semanal_api ,
538
- base_manager_info = manager_base ,
532
+ base_manager_info = base_ret_type ,
539
533
name = manager_class_name ,
540
- line = ctx . call .line ,
534
+ line = queryset_info .line ,
541
535
with_unique_name = True ,
542
536
)
543
537
except helpers .IncompleteDefnException :
544
- if not semanal_api .final_iteration :
545
- semanal_api .defer ()
546
- return
538
+ return _defer ()
547
539
548
540
populate_manager_from_queryset (new_manager_info , queryset_info )
549
541
register_dynamically_created_manager (
550
542
fullname = new_manager_info .fullname ,
551
543
manager_name = manager_class_name ,
552
544
manager_base = manager_base ,
553
545
)
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
556
546
557
547
# Add the new manager to the current module
558
- added = semanal_api .add_symbol_table_node (
559
- # We'll use `new_manager_info.name` instead of `manager_class_name` here
560
- # to handle possible name collisions, as it's unique.
561
- new_manager_info .name ,
548
+ # We'll use `new_manager_info.name` instead of `manager_class_name` here
549
+ # to handle possible name collisions, as it's unique.
550
+ current_module .names [new_manager_info .name ] = (
562
551
# Note that the generated manager type is always inserted at module level
563
- SymbolTableNode (GDEF , new_manager_info , plugin_generated = True ),
552
+ SymbolTableNode (GDEF , new_manager_info , plugin_generated = True )
564
553
)
565
- assert added
566
-
567
554
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
- # Whenever `<QuerySet>.as_manager()` isn't called at class level, we want to ensure
581
- # that the variable is an instance of our generated manager. Instead of the return
582
- # value of `.as_manager()`. Though model argument is populated as `Any`.
583
- # `transformers.models.AddManagers` will populate a model's manager(s), when it
584
- # finds it on class level.
585
- return Instance (manager_node .node , [AnyType (TypeOfAny .from_omitted_generics )])
555
+ add_method_to_class (
556
+ semanal_api ,
557
+ ctx .cls ,
558
+ "as_manager" ,
559
+ args = [],
560
+ return_type = Instance (new_manager_info , [AnyType (TypeOfAny .from_omitted_generics )]),
561
+ is_classmethod = True ,
562
+ )
586
563
587
564
588
565
def reparametrize_any_manager_hook (ctx : ClassDefContext ) -> None :
0 commit comments