-
-
Notifications
You must be signed in to change notification settings - Fork 481
Added dynamic class hook for from_queryset
manager
#427
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
Added dynamic class hook for from_queryset
manager
#427
Conversation
@abstractmethod | ||
def create_new_dynamic_class(self) -> None: | ||
raise NotImplementedError | ||
|
||
class DynamicClassFromMethodCallback(DynamicClassPluginCallback): | ||
|
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.
Add class attribute callee
here, it should be certain that it's a MemberExpr
. Also, add assert with the type in the __init__
so that it would fail if this subclass is used incorrectly. It will allow a bit nicer autocompletion and maybe will save you an assert somewhere in subclasses.
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.
Adding an assert in __init__
makes like no sense, we don't have DynamicClassDefContext
available up there (only NewSemanalDjangoPlugin
instance passed in ManagerFromQuerySetCallback(self)
in get_dynamic_class_hook
. I will add an assert in __call__
though :)
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.
Yes, in __call__
, my bad:)
|
||
method_type = method_node.type | ||
if not isinstance(method_type, CallableType): | ||
if not self.semanal_api.final_iteration: |
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.
defer_till_next_iteration
too?
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.
@mkurnikov will if not isinstance(method_type, CallableType) and not self.defer_till_next_iteration()
work? I'm not 100% sure if I get it right how deferring works
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.
If it's not isinstance(CallableType)
, which type it is? Placeholder?
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.
It might be Overloaded
, CallableType
or None
actually
method_node.arguments[1:]): | ||
bound_arg_type = self.semanal_api.anal_type(arg_type, allow_placeholder=True) | ||
if bound_arg_type is None and not self.semanal_api.final_iteration: | ||
self.semanal_api.defer() |
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.
Same thing as above, defer_till_next_iteration
or/and exception raise, instead of just return
.
|
||
base_manager_info = self.callee.expr.node | ||
|
||
if base_manager_info is None and not self.defer_till_next_iteration(reason='base_manager_info is None'): |
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 don't think this reason
is useful. base_manager_info is None
means something, it should be added as the reason.
How could it happen that TypeInfo
is None
here?
Also, let's see what type self.callee.expr
is. It's an expression on the left side of the a.b
. It could be get_manager_class().from_queryset()
(which will be CallExpr
as self.callee.expr
, not a RefExpr
), so there's no node
attribute available, and so forth.
If it's not a RefExpr
, a good idea would be to fallback to Any
here.
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.
@kszmigiel I still want you to add proper error string here with an explanation of the conditions of why it could be None
.
mypy_django_plugin/lib/helpers.py
Outdated
@@ -115,7 +115,7 @@ def copy_method_to_another_class( | |||
self_type=self_type) | |||
return | |||
|
|||
method_type: CallableType = method_node.type | |||
method_type: CallableType = method_node.type # type: ignore |
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.
cast
maybe?
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.
Applied, works, thanks for the tip 👍
@@ -135,7 +135,7 @@ class ModelAdmin(BaseModelAdmin): | |||
delete_selected_confirmation_template: str = ... | |||
object_history_template: str = ... | |||
popup_response_template: str = ... | |||
actions: Sequence[Callable[[ModelAdmin, HttpRequest, QuerySet], None]] = ... | |||
actions: Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]] = ... |
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 guess that this PR has unrelated changes from other PRs. Is that the case, @kszmigiel?
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.
Yes, I've updated stub files to the master
version, I can revert it if needed (this commit: b032a3d)
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.
The proper way to do it is to rebase
this PR based on current master
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.
👍 It'll be quite a challenge for me, but I'll try
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.
LGTM! 👍
Can we merge it? @kszmigiel
@kszmigiel looks like it is passing now. |
@sobolevn I think we're ready to merge, but I would like to hear @mkurnikov opinion first. I know there are a few fragments I need to cleanup, but I think I can do this along with writing docs and comments after the rebase to master. BTW how about getting readthedocs.io page for docs? I can take care of writing the docs as the task for next week - we lack a lot of them, and I could use a short break from coding too. |
|
||
assert ctx.call.callee is not None | ||
if not isinstance(ctx.call.callee, MemberExpr): | ||
# throw error? |
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.
Add assert
here, instead of isinstance+return.
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.
Done, I will include this in next PR
|
||
base_manager_info = self.callee.expr.node | ||
|
||
if base_manager_info is None and not self.defer_till_next_iteration(reason='base_manager_info is None'): |
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.
@kszmigiel I still want you to add proper error string here with an explanation of the conditions of why it could be None
.
@@ -0,0 +1,236 @@ | |||
- case: from_queryset_with_base_manager |
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.
According to the test, there has to be some kind of magic somewhere which adds MyModel
as a generic argument for NewManager
. Could we get rid of that?
So, what I'm trying to say is that tests are a bit wrong. from_queryset
line should be
class ModelQuerySet(models.QuerySet[MyModel]):
...
NewManager = BaseManager[MyModel].from_queryset(ModelQuerySet)
and current line should error out with Need type annotation for
like it's done here https://github.com/typeddjango/django-stubs/blob/newsemanal-progress/test-data/typecheck/managers/test_managers.yml#L128
and NewManager
in the current setup should give Any
as a generic argument, see https://github.com/typeddjango/django-stubs/blob/newsemanal-progress/test-data/typecheck/managers/test_managers.yml#L106
Could be done in a separate PR though.
🎉 |
No description provided.