-1๐
You can use models.Model
or Type[models.Model]
All Django models inherit from models.Model
and the model that is sent to the function is of the same type, of course, if you want to say that it is a class and is a subset of this class, you can use Type[models.Model]
from typing import Type
from django.db import models
def get_or_create_object(model: Type[models.Model], data: dict) -> models.Model:
try:
obj, _ = model.objects.get_or_create(**data)
except model.MultipleObjectsReturned:
obj = model.objects.filter(**data).first()
return obj
note:
get_or_create
is exist in Django build-in:
exp = Example.objects.get_or_create(**data)
2๐
Since you want your function to be generic in terms of the specific model class you pass to it, the solution is what @SUTerliakov said in his comment. You need to define a typing.TypeVar
and ideally give it an upper bound of Model
to narrow the types it can represent. See the relevant PEP 484 section for details.
Here is how you do it:
from typing import Any, TypeVar, cast
from django.db.models import Model
M = TypeVar("M", bound=Model)
def get_or_create_object(model: type[M], data: dict[str, Any]) -> M:
try:
obj, _ = model.objects.get_or_create(**data)
except model.MultipleObjectsReturned:
obj = cast(M, model.objects.filter(**data).first())
return obj
Note that we need to use typing.cast
because the QuerySet.first
method can return an instance of the model or None
and type checkers are not smart enough to know that in this specific situation it can never return None
(due to the previously caught MultipleObjectsReturned
exception). So we need to make it clear to the type checker that the value is indeed an instance of our model.
To demonstrate:
class SpecificModel(Model):
def foo(self) -> int:
return 1
instance = get_or_create_object(SpecificModel, {})
reveal_type(instance)
reveal_type(instance.foo())
Running mypy
over this gives the following output:
note: Revealed type is "SpecificModel"
note: Revealed type is "builtins.int"
Thus the type checker correctly infers the type of the instance returned by the function.
As a side note, if you wanted you could actually broaden the type of data
because it can be literally any Mapping
, not necessarily a dictionary. For that you just do from collections.abc import Mapping
and annotate it with data: Mapping[str, Any]
instead.
- [Answered ]-Django filter from date to date
- [Answered ]-Large functionality change based on variables
- [Answered ]-Django: Question about model architecture