1616 SymbolTableNode ,
1717 TypeInfo ,
1818)
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
2021from mypy .semanal import SemanticAnalyzer
2122from mypy .semanal_shared import has_placeholder
2223from mypy .subtypes import find_member
@@ -482,44 +483,37 @@ def populate_manager_from_queryset(manager_info: TypeInfo, queryset_info: TypeIn
482483 )
483484
484485
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 :
493487 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
499488
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 :
504490 if not semanal_api .final_iteration :
505491 semanal_api .defer ()
506- return
507492
508- assert isinstance (manager_base , TypeInfo )
493+ queryset_info = semanal_api .type
494+ if queryset_info is None :
495+ return _defer ()
509496
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
513500
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+ ):
518507 return
519508
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 ()
521514
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 } "
523517 current_module = semanal_api .modules [semanal_api .cur_mod_id ]
524518 existing_sym = current_module .names .get (manager_class_name )
525519 if (
@@ -535,54 +529,37 @@ def create_new_manager_class_from_as_manager_method(ctx: DynamicClassDefContext)
535529 try :
536530 new_manager_info = create_manager_class (
537531 api = semanal_api ,
538- base_manager_info = manager_base ,
532+ base_manager_info = base_ret_type ,
539533 name = manager_class_name ,
540- line = ctx . call .line ,
534+ line = queryset_info .line ,
541535 with_unique_name = True ,
542536 )
543537 except helpers .IncompleteDefnException :
544- if not semanal_api .final_iteration :
545- semanal_api .defer ()
546- return
538+ return _defer ()
547539
548540 populate_manager_from_queryset (new_manager_info , queryset_info )
549541 register_dynamically_created_manager (
550542 fullname = new_manager_info .fullname ,
551543 manager_name = manager_class_name ,
552544 manager_base = manager_base ,
553545 )
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
556546
557547 # 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 ] = (
562551 # 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 )
564553 )
565- assert added
566-
567554
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+ )
586563
587564
588565def reparametrize_any_manager_hook (ctx : ClassDefContext ) -> None :
0 commit comments