-
-
Notifications
You must be signed in to change notification settings - Fork 464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
No way to achieve typing of managers built from querysets #738
Comments
.objects
built from QuerySet
sQuerySet
s
QuerySet
s
I end up doing this a lot, I haven't run into the phantom errors but will keep an eye out for it. This feels a bit hacky (so hopefully this spurs further discussion) but could you try option 2 and change the return annotation from - def custom_filter(self: CustomQSSelf) -> CustomQSSelf:
+ def custom_filter(self: CustomQSSelf) -> "CustomQS": That gets around the unbound issue, and avoids the reveal_type(A.objects) # -> AManager[A]
reveal_type(A.objects.get()) # -> A*
reveal_type(A.objects.all()) # -> CustomQS[A*] Can't say it's a fail safe solution but what about a fuller example likefrom typing import TypeVar
from django.db import models
CustomQuerySetSelf = TypeVar("CustomQuerySetSelf", bound="CustomQuerySet")
class CustomQuerySet(models.QuerySet["MyModel"]):
def my_method(self: CustomQuerySetSelf) -> "CustomQuerySet":
return self.filter(pk=1)
MyManager = models.Manager.from_queryset(CustomQuerySet)
class MyModel(models.Model):
objects = MyManager()
# Manager
reveal_type(MyModel.objects) # MyManager[MyModel]
# QuerySet
reveal_type(MyModel.objects.all()) # CustomQuerySet[MyModel*]
reveal_type(MyModel.objects.my_method()) # CustomQuerySet
reveal_type(MyModel.objects.all().my_method()) # CustomQuerySet
# Instance
reveal_type(MyModel.objects.get()) # MyModel*
reveal_type(MyModel.objects.my_method().get()) # MyModel*
reveal_type(MyModel.objects.all().my_method().get()) # MyModel*
# Optional Instance
reveal_type(MyModel.objects.first()) # Union[MyModel*, None]
reveal_type(MyModel.objects.my_method().first()) # Union[MyModel*, None]
reveal_type(MyModel.objects.all().my_method().first()) # Union[MyModel*, None] The |
@kevinmarsh Oh, I'll try getting rid of the self types and see if that helps with the phantom errors! Thanks for the input 👍 |
@kevinmarsh So, I've inspected another model in our codebase now that doesn't use self types, and tried breaking out the manager creation from the model class body. It seems to be an improvement at least, because I don't get weird phantom errors, and it does achieve typing of But now I instead bump into another issue:
Now it instead seems like something is broken with the descriptor typing, and everything that comes out of |
... and while the distinction between queryset and the generated manager class mostly isn't important, I have no idea how to write a union type that accepts the generated manager class. |
I suspect the problem with unbound type vars is that django-stubs copies all of the methods from the QuerySet onto the Manager. However, I don't think it copies the type arguments along with them, so those methods end up with unbound type vars. |
This has been fixed in recent versions and we were able to update by changing to Option 2 mentioned in the description. |
Currently there seems to be no way to get non-
Any
typing forModel.objects
and by extension alsoModel.objects.get()
,Model.objects.all()
et al, for models that specify a custom manager built from aQuerySet
. I'm submitting this as a tracking-issue, even though there are individual issues for some of the underlying problems, I thought it'd be nice to have a unified discussion about how to find a way forward here, whether it's a workaround or a need of a fix in the stubs or in the extension.Option 1
Managers built dynamically within the model class body, just seem to not work and induces
Any
.Option 2
Moving the manager construction to outside of the model body. This induces very strange phantom errors, as well as errors complaining that the "self annotations" on the QuerySet are unbound.
Option 3
Using
QuerySet.as_manager()
. I tried this even though it seems generally recommended against in other issues, and to have less support thanBaseManager.from_queryset()
. As expected this also yieldsAny
, whether or not it's defined inside the model body.I'd be really happy if someone has a workaround for this issue as it was a real cold shower realizing a large portion of our models are inducing
Any
.The text was updated successfully, but these errors were encountered: