Closed
Description
When trying to upgrade to django-stubs 5.0.3 or 5.0.4 in Zulip, I get 25 unexpected errors like error: "type[RealmFilter]" has no attribute "objects" [attr-defined]
. This is not #1684; RealmFilter
is a normal concrete model class. The issue seems to depend on the structure of the project’s import graph.
Based on git bisect
, it first occurs at
I’ve reduced the issue to this minimized test case: https://github.com/andersk/no-attribute-objects
$ mypy .
zerver/models/__init__.py:6: error: "type[RealmFilter]" has no attribute "objects" [attr-defined]
Found 1 error in 1 file (checked 10 source files)
pyproject.toml
[tool.django-stubs]
django_settings_module = "zproject.settings"
[tool.mypy]
plugins = ["mypy_django_plugin.main"]
confirmation/__init__.py
(empty)
confirmation/models.py
from django.db import models
from zerver.models import Realm
class Confirmation(models.Model):
realm = models.ForeignKey(Realm, on_delete=models.RESTRICT)
zerver/__init__.py
(empty)
zerver/models/__init__.py
from zerver.models.linkifiers import RealmFilter as RealmFilter
from zerver.models.realms import Realm as Realm
from zerver.models.streams import Stream as Stream
from zerver.models.users import UserProfile as UserProfile
RealmFilter.objects
zerver/models/linkifiers.py
from django.db import models
class RealmFilter(models.Model):
pass
zerver/models/realms.py
from django.db import models
class Realm(models.Model):
pass
zerver/models/streams.py
from django.db import models
from zerver.models.realms import Realm
from zerver.models.users import UserProfile
class Stream(models.Model):
realm = models.ForeignKey(Realm, on_delete=models.RESTRICT)
creator = models.ForeignKey(UserProfile, on_delete=models.RESTRICT)
zerver/models/users.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
class UserProfile(AbstractBaseUser, PermissionsMixin):
pass
zproject/__init__.py
(empty)
zproject/settings.py
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"confirmation",
"zerver",
]
Activity
asottile commentedon Aug 1, 2024
appears to be a weird ordering thing -- #2280 will fix this probably (that or the
objects
removal needs to happen earlier)what's happening is the plugin does a pass over your class -- notices the correct type exists (
Manager[Self]
) frommodels.Model
and leaves it -- and then later the plugin deletesmodels.Model.objects
I'm surprised none of the tests hits this -- and we don't hit this at sentry because we turn off deletion of
objects
and friends in our forkobjects
attribute fromModel
in plugin #2280Add regression test from typeddjango#2304
fixup! Add regression test from typeddjango#2304
flaeppe commentedon Aug 1, 2024
I couldn't get the case above to reproduce in
django-stubs
test suite, but I was able to check out https://github.com/andersk/no-attribute-objects and run it against #2280 successfullyflaeppe commentedon Aug 1, 2024
I just want to reference that
djangorestframework-stubs
also ran in to this issue forrest_framework.authtoken.models.Token.objects
(some additional info in typeddjango/djangorestframework-stubs#644). Which, in some way, means that project also has a structure with the ability to makemypy
process files in an order that breaks the plugin.Add regression test from typeddjango#2304
ci: downgrade django-stubs to not install v5.x
ci: downgrade django-stubs to not install v5.x (#3062)
flaeppe commentedon Aug 6, 2024
I realised that our plugin tries to adjust the model class from the
get_customize_class_mro_hook
which, per its definition, is called:Ref: https://mypy.readthedocs.io/en/stable/extending_mypy.html#current-list-of-plugin-hooks
But the code tries to expect and look through the class body...
A fix for this is instead to move to adjust the model class via the
get_metaclass_hook
. Which is called beforeget_base_class_hook
: See https://github.com/python/mypy/blob/570b90a7a368f04c64f60af339d0ac1808c49c15/mypy/semanal.py#L1998-L2010Anyways, above is some quite specific details, in the end it's just to change the plugin to use another hook. I'll add a fix for this.
models.Model
adjustments fromget_metaclass_hook
#2322