django-stubs
django-stubs copied to clipboard
Abstract models cannot be used in type annotations
Bug report
What's wrong
If BaseMessage
is an abstract model and EmailMessage
is a concrete model based on BaseMessage
:
class _BaseViewSet(GenericViewSet):
model: type[BaseMessage]
def create(self):
query = self.model.objects.filter(brand_id=brand.pk)
class EmailViewSet(_BaseViewSet):
model = EmailMessage
The code produces an error:
error: "Type[BaseMessage] has no attribute "objects"
How is that should be
The code should pass type checking without errors, like in django-stubs 4.2.3. It's a common practice in Python to use a base class for type annotations, even if only its subclasses may be used. The idea is that the base class defines all the attributes and methods that every subclass must define, and then abc.ABC is used if you need to ensure that only subclasses can be instantiated. There is no way in mypy to tell on the user side "This attribute is any subclass of the class but not the class itself".
For example, this code won't report any errors despite the type
attribute being actually defined only on subclasses (and so if you try to use BaseMessage.type
it will fail in runtime):
class BaseMessage:
type: str
class EmailMessage(BaseMessage):
type = "email"
def f(m: BaseMessage):
return m.type
m = EmailMessage()
f(m)
System information
- OS: linux
-
python
version:3.8
-
django
version: 4.2.4 -
mypy
version: 1.5.1 -
django-stubs
version: 4.2.4 -
django-stubs-ext
version: 4.2.2
The issue was introduced in #1663 as a fix for #1653. The comment https://github.com/typeddjango/django-stubs/issues/1653#issuecomment-1674496381 suggests a workaround solution which is listing all possible subtypes for the type explicitly and using their TypeVar or Union. But that's a bad solution that doesn't scale well. In my case, the base abstract model has 6 submodels and more will be added in the future, and I don't want it to require changing a bunch of unrelated annotations.
There's 2 options here, I think
- Declare an
objects
manager on your abstract class - Use
._default_manager
instead ofobjects
. See #1684
Tried the first option, and it works 👍🏿 That's a bit confusing, though. I like @sobolevn 's suggestion to put it in the docs (https://github.com/typeddjango/django-stubs/issues/1684#issuecomment-1706756028).
Would it make sense to keep the issue open to update the documentation? I think the suggestion https://github.com/typeddjango/django-stubs/issues/1684#issuecomment-1706756028 hasn't been addressed, and it should prevent questions like I had in the future.
Yes, we should totally work on docs :)