-
-
Notifications
You must be signed in to change notification settings - Fork 454
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
Return Promise for lazy functions. #689
Conversation
@@ -3,6 +3,7 @@ from contextlib import ContextDecorator | |||
from typing import Any, Callable, Optional, Union | |||
|
|||
from django.core.handlers.wsgi import WSGIRequest | |||
from django.utils.functional import Promise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that Promise
is a new thing, isn't it? Looks like that latest 3.2.x
does not have this primitive: https://github.com/django/django/tree/3.2.6/django/utils
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moreover, even in latest master
it is just a plain class with no methods or anything: https://github.com/django/django/blob/main/django/utils/functional.py#L68
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. Promise
has been there for quite a while: django/django@e4e28d9. But you are right that Promise
is just a plain class. Since the proxy method knows about the resultclasses
, I guess creating generics like Promise[str]
would be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can add these methods to Promise
as well? https://github.com/django/django/blob/3.2.6/django/utils/functional.py#L84-L191
Hi, I have updated the PR. I think it is probably not necessary to make |
There is still a problem with this. |
This comment was marked as resolved.
This comment was marked as resolved.
0a8e100
to
71a6b57
Compare
I believe that this implementation should be good enough for another review. @sobolevn could you take a look at this? I ditched the generic approach and reimplement this with a simpler plugin handling the use case for Looks like the CI is still failing for another unrelated issue. Will look into this in a separate PR probably. Otherwise, I think we are good to go. |
The return value of the lazy translation functions is a proxied `Promise` object. https://github.com/django/django/blob/3.2.6/django/utils/translation/__init__.py#L135-L221. Signed-off-by: Zixuan James Li <[email protected]>
Although there is nothing defined in `Promise` itself, the only instances of `Promise` are created by the `lazy` function, with magic methods defined on it. https://github.com/django/django/blob/3.2.6/django/utils/functional.py#L84-L191. Signed-off-by: Zixuan James Li <[email protected]>
This allows the user to access methods defined on lazy strings while still letting mypy be aware of that they are not instances of `str`. The definitions for some of the magic methods are pulled from typeshed. We need those definitions in the stubs so that `_StrPromise` objects will work properly with operators, as refining operator types is tricky with the mypy plugins API. The rest of the methods will be covered by an attribute hook. Signed-off-by: Zixuan James Li <[email protected]>
This implements an attribute hook that provides type information for methods that are available on `builtins.str` for `_StrPromise` except the supported operators. This allows us to avoid copying stubs from the builtins for all supported methods on `str`. Signed-off-by: Zixuan James Li <[email protected]>
One intended usage of lazystr is to postpone the translation of the error message of a validation error. It is possible that we pass a Promise (specifically _StrPromise) and only evaluate it when a ValidationError is raised. Signed-off-by: Zixuan James Li <[email protected]>
@sobolevn Hi! The PR has been rebased and the CI is passing. I think it is ready for review. |
|
||
str_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx), f"builtins.str") | ||
assert str_info is not None | ||
method = str_info.get(method_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that you should use analyze_member_access
here from mypy/checkmember.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
Signed-off-by: Zixuan James Li <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try it out! Thank you!
This PR causes errors when passing error: Argument "verbose_name" to "PositiveBigIntegerField" has incompatible type
"_StrPromise"; expected "Optional[str]" [arg-type]
null=True, blank=True, verbose_name=_("number") (where |
Please, open a new issue. I am not releasing new version yet, we have time to fix it. |
@PIG208 was there a reason django-stubs/django-stubs/utils/functional.pyi Lines 31 to 46 in fa972f1
Is a subclass of I'm raising this question here instead of creating a separate issue since I figured I'd ask about the intent before completely suggesting this should change (maybe there was a reason I am not aware of). It seems like @sobolevn not sure if you have any thoughts or would prefer this to be a separate issue. |
@terencehonles Thanks for bringing this up!
The point of having We did not make |
See also #1139 (comment) for concrete examples of what would go wrong if we incorrectly told mypy that |
ok, thanks for the explanation.
A I had believed that Django's Promise implementation should be a more or less drop in replacement for the object it's promising since I thought it's blockingly coerced into the object when it's used, but it sounds like there's been some discussion of this and I will check out the comment @andersk pointed to 👍 |
Django uses a proxy method for lazy translation functions that actually returns instances of
Promise
instead ofstr
. To be accurate, we should change the signatures for these functions.Related issues
gettext_lazy
should return a Promise object #688gettext_lazy
should return a Promise object #688