Пример #1
0
    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, []))
Пример #2
0
    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
Пример #3
0
    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)
Пример #4
0
    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()
Пример #5
0
 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
Пример #6
0
 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
Пример #7
0
    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)