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.