Dynamic type annotation for Django model managers custom method

I need help with type hint for a custom model manager method.

This is my custom manager and a base model. I inherit this base model to other models so that I don't have to write common fields again and again.

class BaseManager(models.Manager):
    def get_or_none(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except self.model.DoesNotExist:
            return None

class BaseModel(models.Model):
    id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, verbose_name="ID", editable=False
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = BaseManager()  # Set the custom manager

    class Meta:
        abstract = True

This is an example model:

class MyModel(BaseModel):
    category = models.CharField(max_length=10)

Now for this:

my_object = MyModel.objects.get_or_none(
            category="...",
        )

The type annotation is like this when I hover in my IDE:

my_object: BaseModel | None = MyModel.objects. get_or_none(...

But I want the type annotation like this:

my_object: MyModel | None = MyModel.objects. get_or_none(...

How can I do that? This works for the default methods like get and filter. But how to do this for custom methods like get_or_none?

Please help me.

Thanks

In order for your IDE to see get_or_none method, You should add type annotation to objects variable in your BaseModel class like so:

objects: BaseManager = BaseManager()

Python's type inference system can not interfere this, because the code works a bit too much with meta-programming, etc. and the interpreter can not "get through" that (yet). For example a package like django-stubs [GitHub] contains type hints that help the type checker to understand the types.

This then looks like [GitHub]:

from typing_extensions import Self


class Model(metaclass=ModelBase):
    # …
    objects: ClassVar[Manager[Self]]

If you enable Django support in PyCharm, PyCharm will normally install django-stubs behind the curtains to help you with these type checks.

They thus rewrote the Manager type to make sure one can subscript it with the type of the model the manager deals with.

You can add a .pyi file that drops the hint with:

# path/to/base/manager.pyi

from typing import Generic, Tuple, TypeVar

from django.db.models.base import Model
from django.db.models.manager import Manager

_T = TypeVar('_T', bound=Model, covariant=True)


class BaseManager(Generic[_T], Manager[_T]):
    def get_or_none(self, *args, **kwargs) -> _T | None: …

and ensure that _T contains a reference to the model with:

# path/to/base/model.pyi

from typing import ClassVar
from django.db.models.base import Model
from path.to.base.manager import BaseManager
from typing_extensions import Self

class BaseModel(Model):
    objects: ClassVar[OrderHistoryManager[Self]]

Normally PyCharm will then pick up the type hints.

Вернуться на верх