-
Notifications
You must be signed in to change notification settings - Fork 671
feat(api): Use protocols and overload to type hint mixins #3080
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
feat(api): Use protocols and overload to type hint mixins #3080
Conversation
Currently mixins like ListMixin are type hinted to return base RESTObject instead of a specific class like `MergeRequest`. The GetMixin and GetWithoutIdMixin solve this problem by defining a new `get` method for every defined class. However, this creates a lot of duplicated code. `typing.Protocol` can be used to type hint that the mixed in method will return a class matching attribute `_obj_cls`. The type checker will lookup the mixed in class attribute and adjust the return type accordinly. Delete `tests/unit/meta/test_ensure_type_hints.py` file as the `get` method is no required to be defined for every class. Signed-off-by: Igor Ponomarev <[email protected]>
This turned out to be a larger commit than I expected. I can probably split it by a mixin. For example, first commit for Get and GetWithoutId mixins, second one for ListMixin... Also this is a pretty ad-hoc solution but requires the least amount of changes. Ideally making a I made a table of the attributes each mixin uses: #2228 (comment) Looking at it the RESTManager hierarchy would probably be a base object without |
@@ -347,7 +347,6 @@ class RESTManager: | |||
_create_attrs: g_types.RequiredOptional = g_types.RequiredOptional() | |||
_update_attrs: g_types.RequiredOptional = g_types.RequiredOptional() | |||
_path: Optional[str] = None | |||
_obj_cls: Optional[Type[RESTObject]] = 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.
The _obj_cls
needs to be specific for each class. Having a _obj_cls
as None in base class will prevent Protocol from working.
@@ -397,10 +397,11 @@ def auth(self) -> None: | |||
The `user` attribute will hold a `gitlab.objects.CurrentUser` object on | |||
success. | |||
""" | |||
self.user = self._objects.CurrentUserManager(self).get() | |||
user = self._objects.CurrentUserManager(self).get() | |||
self.user = user |
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.
For some reason self.user
will now be type evaluated as Optional[User]
. I had to separate user to a new variable that can't be None.
*, | ||
iterator: Literal[True] = True, | ||
**kwargs: Any, | ||
) -> base.RESTObjectList: ... |
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 added an overload to the list method so that passing iterator
will change if a list is returned or an iterator. This can be separated to another commit and MR.
Also RESTObjectList
is not generic. I want to create a follow-up MR there it will be made generic to iterate over a specific type.
@@ -104,12 +104,12 @@ def reset_gitlab(gl: gitlab.Gitlab) -> None: | |||
) | |||
continue | |||
|
|||
for deploy_token in group.deploytokens.list(): | |||
for group_deploy_token in group.deploytokens.list(): |
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.
Because list()
now returns a list containing a specific type it would case a type error here because the same variable is used between two different types.
@@ -4,7 +4,9 @@ | |||
https://docs.gitlab.com/ee/api/projects.html#list-projects-starred-by-a-user | |||
""" | |||
|
|||
from typing import Any, cast, Dict, List, Optional, Union | |||
from __future__ import annotations |
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.
For some reason without delayed annotations type checks failed to resolve types for UserProject.list
. This means the quotes around the type annotation had to be removed or managers would not be initialized.
@igorp-collabora Thanks for this. Not ignoring your PRs. Just I haven't had time yet to fully review them. Hopefully this week or next week. Sorry for the delay. Thanks for the PRs! They look interesting from the description 👍 |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3080 +/- ##
==========================================
+ Coverage 96.68% 97.19% +0.50%
==========================================
Files 96 96
Lines 6213 5913 -300
==========================================
- Hits 6007 5747 -260
+ Misses 206 166 -40
Flags with carried forward coverage won't be shown. Click here to find out more.
|
No worries. I want to experiment with reworking RESTManager hierarchy meanwhile. |
The #3083 is a cleaner solution but more verbose. This one builds more work-around code on top of existing one. |
Changes
Currently mixins like ListMixin are type hinted to return base RESTObject instead of a specific class like
MergeRequest
.The GetMixin and GetWithoutIdMixin solve this problem by defining a new
get
method for every defined class. However, this creates a lot of duplicated code.typing.Protocol
can be used to type hint that the mixed in method will return a class matching attribute_obj_cls
. The type checker will lookup the mixed in class attribute and adjust the return type accordinly.Delete
tests/unit/meta/test_ensure_type_hints.py
file as theget
method is no required to be defined for every class.Closes #2228
Closes #2062