django-stubs
django-stubs copied to clipboard
Transform user_passes_test signature
Add a transformer that updates the signature of user_passes_test
to reflect settings.AUTH_USER_MODEL
.
Related issues
Fixes #1058
This seems like significant complexity to dynamically update the signature of just one function.
There are lots more places in Django that depend on AUTH_USER_MODEL
. Are you planning to extend this and add update the other places as well, and can this be implemented generically?
request.user
seems like an important one for example.
This seems like significant complexity to dynamically update the signature of just one function.
There are lots more places in Django that depend on
AUTH_USER_MODEL
. Are you planning to extend this and add update the other places as well, and can this be implemented generically?
request.user
seems like an important one for example.
This is already done for request.user
: https://github.com/typeddjango/django-stubs/blob/e88f942f861809d2b824ecc12b908c8c90bbf6d2/mypy_django_plugin/transformers/request.py#L10
Same with get_user_model()
: https://github.com/typeddjango/django-stubs/blob/e88f942f861809d2b824ecc12b908c8c90bbf6d2/mypy_django_plugin/transformers/settings.py#L11
I'm not sure if there's an easy way to do this generically (I'm also unsure how many places this is needed). I've just fixed the only place where this was causing new type issues in our codebase
I think instead we can make AUTH_USER_MODEL
an actual type and use it everywhere 🤔
I'm not exactly sure if I'm following how you'd envision that working? Altering the type of settings.AUTH_USER_MODEL
to be a type alias? Wouldn't that cause issues when directly accessing that setting and expecting a string?
I'm happy to do some cleanup here and add a helper to extract the 'current' user type (to reduce duplication), but I don't see how this could be done in another way.
I think we can at least experiment to do something like this:
- Import
settings.AUTH_USER_MODEL
model from runtime - Pass it through our regular plugin machinery
- Redefine
settings.AUTH_USER_MODEL
to be an alias for that type
So, ideally we can change all (most) User
references in stubs with AUTH_USER_MODEL
type. Like this:
def user_passes_test(func: Callable[[AUTH_USER_MODEL | AnonymousUser], bool]) -> ...: ...`
However, I don't say that:
- It is a good idea, I am not completely sure how it can be implemented
- That it will work at all
One immediate problem I see with that is that settings.USER_AUTH_MODEL
should stay defined as a string, as someone might rely on that at runtime as well. I guess get_user_model()
is the API to use to get the actual model. I don't think that would be accepted as a type though. Maybe we could add something like this somewhere, but I'm not sure where? And I'm not sure if it would even work?
User = get_user_model()
It can work because of get_dynamic_class_hook()
, but I am not sure this will be accepted as a type.
Ok, we can try to populate _USER_AUTH_MODEL
magic thing 🤔
Hmm, wouldn't that break usage of the stubs without the plugin? For this to work properly I'd think it has to be defined somewhere in the stubs at least?
It can work because of
get_dynamic_class_hook()
, but I am not sure this will be accepted as a type.Ok, we can try to populate
_USER_AUTH_MODEL
magic thing 🤔
I got a spin on this idea:
settings.AUTH_USER_MODEL
is Django utilising its lazy referencing ("<app_label>.<object_name>"
), and we also know that it looks for a registered model under the hood. Which means that (probably currently) we've typed or can assume type[Model]
wherever settings.AUTH_USER_MODEL
has been resolved.
So, what if the stubs add some variable: _AUTH_USER_MODEL = NewType("_AUTH_USER_MODEL", Type[Model])
? That might perhaps trigger get_dynamic_class_hook
and give us a statically declared type which is "bound" to be correct but the plugin is able to refine it into a even more specific type?
Then the stubs replaces all places where we expect settings.AUTH_USER_MODEL
to have been resolved e.g.
- -> Type[Model]:
+ -> _AUTH_USER_MODEL:
Which (hopefully) would align all types.
I like this idea (I think that adding a hook is a must, however - otherwise, we will have too many errors in users' code).
Hmm, interesting. Sounds like it's worth a try! Maybe best to try in a separate PR though?
Yeah a separate PR would be best
Are we sure we can't just use get_auth_user()
together with get_dynamic_class_hook()
though? Something like this?
# stubs.pyi
User = get_auth_user()
class HttpRequest:
user: User | AnonymousUser
I think we should, because it is a perfect use-case for it.
I don't think that works. Isn't it only TypeAlias that can be annotated with?
And that function call can't return a type alias, I've tried make the plugin spoof that kind of aliasing as well, in #1699
Mypy makes User
a variable(Var
) and that can't be annotated with
But can't we change that using get_dynamic_class_hook
?
But can't we change that using
get_dynamic_class_hook
?
Probably, I've tried that in #1699 but it isn't trivial
I think I managed to make it work: #1730
Main trick was replacing the symbol with a placeholder when deferring, as otherwise the variable error popped up (that seems like a bug in mypy)