Skip to content

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

Merged
merged 28 commits into from
Aug 7, 2020

Conversation

kszmigiel
Copy link
Member

No description provided.

@kszmigiel kszmigiel requested a review from mkurnikov July 16, 2020 17:51
@abstractmethod
def create_new_dynamic_class(self) -> None:
raise NotImplementedError

class DynamicClassFromMethodCallback(DynamicClassPluginCallback):

Copy link
Member

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.

Copy link
Member Author

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 :)

Copy link
Member

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:
Copy link
Member

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?

Copy link
Member Author

@kszmigiel kszmigiel Jul 19, 2020

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

Copy link
Member

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?

Copy link
Member Author

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()
Copy link
Member

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'):
Copy link
Member

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.

Copy link
Member

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.

@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast maybe?

Copy link
Member Author

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]] = ...
Copy link
Member

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?

Copy link
Member Author

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)

Copy link
Member

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

Copy link
Member Author

@kszmigiel kszmigiel Aug 5, 2020

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

@kszmigiel
Copy link
Member Author

@sobolevn do you have any idea why this test I've added don't pass? I added

from .models import ModelA

reveal_type(ModelA.objects.do_something())

in views.py of reproduction repository provided by @danifus and it works just fine

My Django version is 3.1b1 and Mypy is 0.782 if that helps

Copy link
Member

@sobolevn sobolevn left a 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

@sobolevn
Copy link
Member

sobolevn commented Aug 6, 2020

@kszmigiel looks like it is passing now.

@kszmigiel
Copy link
Member Author

@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?
Copy link
Member

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.

Copy link
Member Author

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'):
Copy link
Member

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
Copy link
Member

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.

@mkurnikov mkurnikov merged commit 50372fa into typeddjango:newsemanal-progress Aug 7, 2020
@kszmigiel
Copy link
Member Author

🎉

@kszmigiel kszmigiel mentioned this pull request Aug 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants