Open asottile-sentry opened 2 weeks ago
If I read what you wrote correctly, here are some relevant references:
Some time ago I attempted to introduce support for inheriting .from_queryset
generated managers via the plugin, but got stuck: #1699. It also links upstream to: https://github.com/python/mypy/issues/8897#issuecomment-634241682
(I don't think that it's too generic to understand this form of inheritance for django-stubs
mypy plugin, but I don't seem to have found the right tools/place to extend mypy)
a little different from that -- it already works for simple cases (as shown in the testcase in my report) -- just not in our codebase for whatever reason
I think I may have reduced this problem to a bug (?) in mypy: https://github.com/python/mypy/issues/17402
Ah, nice find!
Just also want to mention that the plugin doesn't always behave when it comes to generic QuerySets. Not sure if it's affecting anything here though
had a bit of a breakthrough on the defer
problem -- with at least a workaround that seems to get a lot further!
https://github.com/python/mypy/issues/17402#issuecomment-2181509517
I have a fix in #2228 though I'm struggling to write a test to demonstrate the problem -- if you know of some way to force a round of deferral through a cycle it'd probably help me write a test for this
I've tried to have a look but I have to say that I don't know. I was about to suggest to have a look at
To see if there's some inspiration for testing cycles there. But I realised that you were already involved in those.
The only idea I can come up with, while I'm writing this, is that there might be a case when setting up a myapp/managers.py
and a myapp/models.py
where a model from myapp/models.py
is imported to myapp/managers.py
and a manager from myapp/managers.py
is imported to myapp/models.py
. We should already have some tests laying around somewhere doing that, might be something cyclic that triggers a defer if you dissect those.
But I'm totally not sure.
Bug report
so I realize this is not the best of reports -- all attempts I've made to make a minimal case do not reproduce the bug so I suspect it is something to do with how our repository is set up somehow.
I have thrown together a reproducible docker image to show the problem:
after the patch is applied this line here should look like this (the dockerfile does this automatically):
and the end of the file should have:
this is one of the custom methods added by our custom base
QuerySet
we technically use a fork of
django-stubs
-- but I've removed that in the docker image above to demonstrate that this is not caused by our patches there (though there will be some additional output because we use django'scache
with strings in a ~somewhat unsafe way).I build and run the docker image via:
What's wrong
the full output of the above is:
full output
```console $ docker run --rm -ti mypy-repro src/sentry/db/models/manager/base.py:48: error: Variable "sentry.db.models.manager.base._base" is not valid as a type [valid-type] src/sentry/db/models/manager/base.py:48: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases src/sentry/db/models/manager/base.py:48: error: Invalid base class "_base" [misc] src/sentry/db/models/manager/base.py:169: error: Item "None" of "Field[Any, Any] | None" has no attribute "name" [union-attr] src/sentry/db/models/manager/base.py:181: error: Argument "version" to "set" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:193: error: Argument "version" to "set" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:208: error: Argument "version" to "delete" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:219: error: Item "None" of "Field[Any, Any] | None" has no attribute "name" [union-attr] src/sentry/db/models/manager/base.py:226: error: Argument "version" to "delete" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:230: error: Argument "version" to "delete" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:287: error: Argument "version" to "delete" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:293: error: Argument "version" to "get" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:381: error: Argument "version" to "get_many" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:459: error: Argument "version" to "delete" of "BaseCache" has incompatible type "str"; expected "int | None" [arg-type] src/sentry/db/models/manager/base.py:575: note: Revealed type is "Any" Found 13 errors in 1 file (checked 1 source file) ```the important bits of that are:
showing that the result of the
from_queryset
manager isn't suitable as a base class (for whatever reason!)I've done a bit of debugging and thrown some prints in mypy and
django-stubs
:this produces the following:
to me this ~maybe points at a bug in mypy in that it's producing the error but with an extra pass it would have resolved the base class? maybe it's somehow a bug here in that we need to indicate the base class should be a higher priority? I don't know
How is that should be
hopefully clear from above, the class should be a suitable base class and the methods from the queryset should be usable.
in a minimal example everything annoyingly works as expected:
System information
python
version: 3.11.9django
version: 5.0.6mypy
version: 1.10.0django-stubs
version: 5.0.2django-stubs-ext
version: 5.0.2