def run_with_model_cls(self, model_cls: Type[Model]) -> None: # add related managers for relation in self.django_context.get_model_relations(model_cls): attname = relation.get_accessor_name() if attname is None: # no reverse accessor continue related_model_cls = self.django_context.get_field_related_model_cls( relation) if related_model_cls is None: continue try: related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error( related_model_cls) except helpers.IncompleteDefnException as exc: if not self.api.final_iteration: raise exc else: continue if isinstance(relation, OneToOneRel): self.add_new_node_to_model_class( attname, Instance(related_model_info, [])) continue if isinstance(relation, (ManyToOneRel, ManyToManyRel)): try: related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error( fullnames.RELATED_MANAGER_CLASS) # noqa: E501 if "objects" not in related_model_info.names: raise helpers.IncompleteDefnException() except helpers.IncompleteDefnException as exc: if not self.api.final_iteration: raise exc else: continue # create new RelatedManager subclass parametrized_related_manager_type = Instance( related_manager_info, [Instance(related_model_info, [])]) default_manager_type = related_model_info.names["objects"].type if (default_manager_type is None or not isinstance(default_manager_type, Instance) or default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME): self.add_new_node_to_model_class( attname, parametrized_related_manager_type) continue name = related_model_cls.__name__ + "_" + "RelatedManager" bases = [ parametrized_related_manager_type, default_manager_type ] new_related_manager_info = self.add_new_class_for_current_module( name, bases) self.add_new_node_to_model_class( attname, Instance(new_related_manager_info, []))
def create_new_model_parametrized_manager(self, name: str, base_manager_info: TypeInfo) -> Instance: bases = [] for original_base in base_manager_info.bases: if self.is_any_parametrized_manager(original_base): if original_base.type is None: raise helpers.IncompleteDefnException() original_base = helpers.reparametrize_instance(original_base, [Instance(self.model_classdef.info, [])]) bases.append(original_base) new_manager_info = self.add_new_class_for_current_module(name, bases) # copy fields to a new manager new_cls_def_context = ClassDefContext(cls=new_manager_info.defn, reason=self.ctx.reason, api=self.api) custom_manager_type = Instance(new_manager_info, [Instance(self.model_classdef.info, [])]) for name, sym in base_manager_info.names.items(): # replace self type with new class, if copying method if isinstance(sym.node, FuncDef): helpers.copy_method_to_another_class( new_cls_def_context, self_type=custom_manager_type, new_method_name=name, method_node=sym.node ) continue new_sym = sym.copy() if isinstance(new_sym.node, Var): new_var = Var(name, type=sym.type) new_var.info = new_manager_info new_var._fullname = new_manager_info.fullname + "." + name new_sym.node = new_var new_manager_info.names[name] = new_sym return custom_manager_type
def run_with_model_cls(self, model_cls: Type[Model]) -> None: for manager_name, manager in model_cls._meta.managers_map.items(): manager_fullname = helpers.get_class_fullname(manager.__class__) manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname) if manager_name not in self.model_classdef.info.names: manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])]) self.add_new_node_to_model_class(manager_name, manager_type) else: # creates new MODELNAME_MANAGERCLASSNAME class that represents manager parametrized with current model has_manager_any_base = any(self._is_manager_any(base) for base in manager_info.bases) if has_manager_any_base: custom_model_manager_name = manager.model.__name__ + '_' + manager.__class__.__name__ bases = [] for original_base in manager_info.bases: if self._is_manager_any(original_base): if original_base.type is None: raise helpers.IncompleteDefnException() original_base = helpers.reparametrize_instance(original_base, [Instance(self.model_classdef.info, [])]) bases.append(original_base) current_module = self.api.modules[self.model_classdef.info.module_name] custom_manager_info = helpers.add_new_class_for_module(current_module, custom_model_manager_name, bases=bases, fields=OrderedDict()) custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])]) self.add_new_node_to_model_class(manager_name, custom_manager_type)
def run_with_model_cls(self, model_cls: Type[Model]) -> None: manager_info: Optional[TypeInfo] encountered_incomplete_manager_def = False for manager_name, manager in model_cls._meta.managers_map.items(): manager_class_name = manager.__class__.__name__ manager_fullname = helpers.get_class_fullname(manager.__class__) try: manager_info = self.lookup_typeinfo_or_incomplete_defn_error( manager_fullname) except helpers.IncompleteDefnException as exc: # Check if manager is a generated (dynamic class) manager base_manager_fullname = helpers.get_class_fullname( manager.__class__.__bases__[0]) manager_info = self.get_generated_manager_info( manager_fullname, base_manager_fullname) if manager_info is None: # Manager doesn't appear to be generated. Track that we encountered an # incomplete definition and skip encountered_incomplete_manager_def = True continue _, manager_class_name = manager_info.fullname.rsplit( ".", maxsplit=1) if manager_name not in self.model_classdef.info.names: manager_type = Instance( manager_info, [Instance(self.model_classdef.info, [])]) self.add_new_node_to_model_class(manager_name, manager_type) else: # Ending up here could for instance be due to having a custom _Manager_ # that is not built from a custom QuerySet. Another example is a # related manager. # Don't interfere with dynamically generated manager classes is_dynamically_generated = "django" in manager_info.metadata and manager_info.metadata[ "django"].get("from_queryset_manager") if not self.has_any_parametrized_manager_as_base( manager_info) or is_dynamically_generated: continue custom_model_manager_name = manager.model.__name__ + "_" + manager_class_name try: custom_manager_type = self.create_new_model_parametrized_manager( custom_model_manager_name, base_manager_info=manager_info) except helpers.IncompleteDefnException: continue self.add_new_node_to_model_class(manager_name, custom_manager_type) if encountered_incomplete_manager_def and not self.api.final_iteration: # Unless we're on the final round, see if another round could figuring out all manager types raise helpers.IncompleteDefnException()
def lookup_typeinfo_or_incomplete_defn_error(self, fullname: str) -> TypeInfo: sym = self.api.lookup_fully_qualified_or_none(fullname) if sym is None or not isinstance(sym.node, TypeInfo): raise helpers.IncompleteDefnException(f'No {fullname!r} found') return sym.node
def lookup_typeinfo_or_incomplete_defn_error(self, fullname: str) -> TypeInfo: info = self.lookup_typeinfo(fullname) if info is None: raise helpers.IncompleteDefnException(f'No {fullname!r} found') return info
def run_with_model_cls(self, model_cls: Type[Model]) -> None: # add related managers for relation in self.django_context.get_model_relations(model_cls): attname = relation.get_accessor_name() if attname is None or attname in self.model_classdef.info.names: # No reverse accessor or already declared. Note that this would also leave any # explicitly declared(i.e. non-inferred) reverse accessors alone continue related_model_cls = self.django_context.get_field_related_model_cls( relation) if related_model_cls is None: continue try: related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error( related_model_cls) except helpers.IncompleteDefnException as exc: if not self.api.final_iteration: raise exc else: continue if isinstance(relation, OneToOneRel): self.add_new_node_to_model_class( attname, Instance(related_model_info, [])) continue if isinstance(relation, (ManyToOneRel, ManyToManyRel)): try: related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error( fullnames.RELATED_MANAGER_CLASS) # noqa: E501 default_manager = related_model_info.names.get( "_default_manager") if not default_manager: raise helpers.IncompleteDefnException() except helpers.IncompleteDefnException as exc: if not self.api.final_iteration: raise exc else: continue # Check if the related model has a related manager subclassed from the default manager # TODO: Support other reverse managers than `_default_manager` default_reverse_manager_info = self.get_reverse_manager_info( model_info=related_model_info, derived_from="_default_manager") if default_reverse_manager_info: self.add_new_node_to_model_class( attname, Instance(default_reverse_manager_info, []), no_serialize=True) continue # The reverse manager we're looking for doesn't exist. So we create it. # The (default) reverse manager type is built from a RelatedManager and the default manager on the related model parametrized_related_manager_type = Instance( related_manager_info, [Instance(related_model_info, [])]) default_manager_type = default_manager.type assert default_manager_type is not None assert isinstance(default_manager_type, Instance) # When the default manager isn't custom there's no need to create a new type # as `RelatedManager` has `models.Manager` as base if default_manager_type.type.fullname == fullnames.MANAGER_CLASS_FULLNAME: self.add_new_node_to_model_class( attname, parametrized_related_manager_type) continue # The reverse manager is based on the related model's manager, so it makes most sense to add the new # related manager in that module new_related_manager_info = helpers.add_new_class_for_module( module=self.api.modules[related_model_info.module_name], name=f"{related_model_cls.__name__}_RelatedManager", bases=[ parametrized_related_manager_type, default_manager_type ], no_serialize=True, ) new_related_manager_info.metadata["django"] = { "related_manager_to_model": related_model_info.fullname } # Stash the new reverse manager type fullname on the related model, so we don't duplicate # or have to create it again for other reverse relations self.set_reverse_manager_info( related_model_info, derived_from="_default_manager", fullname=new_related_manager_info.fullname, ) self.add_new_node_to_model_class(attname, Instance( new_related_manager_info, []), no_serialize=True)