diff --git a/gitlab/client.py b/gitlab/client.py index 11038df22..b1738210e 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -881,18 +881,16 @@ def http_list( page = kwargs.get("page") - if iterator and page is not None: - arg_used_message = f"iterator={iterator}" - utils.warn( - message=( - f"`{arg_used_message}` and `page={page}` were both specified. " - f"`{arg_used_message}` will be ignored and a `list` will be " - f"returned." - ), - category=UserWarning, - ) + if iterator: + if page is not None: + utils.warn( + message=( + f"`{iterator=}` and `{page=}` were both specified. " + f"`{page=}` will be ignored." + ), + category=UserWarning, + ) - if iterator and page is None: # Generator requested return GitlabList(self, url, query_data, **kwargs) diff --git a/gitlab/mixins.py b/gitlab/mixins.py index 16a3dcd63..ff99abdf6 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -161,9 +161,24 @@ def refresh(self, **kwargs: Any) -> None: class ListMixin(HeadMixin[base.TObjCls]): _list_filters: tuple[str, ...] = () + @overload + def list( + self, *, iterator: Literal[False] = False, **kwargs: Any + ) -> list[base.TObjCls]: ... + + @overload + def list( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> base.RESTObjectList[base.TObjCls]: ... + + @overload + def list( + self, *, iterator: bool = False, **kwargs: Any + ) -> base.RESTObjectList[base.TObjCls] | list[base.TObjCls]: ... + @exc.on_http_error(exc.GitlabListError) def list( - self, **kwargs: Any + self, *, iterator: bool = False, **kwargs: Any ) -> base.RESTObjectList[base.TObjCls] | list[base.TObjCls]: """Retrieve a list of objects. @@ -203,7 +218,7 @@ def list( # Allow to overwrite the path, handy for custom listings path = data.pop("path", self.path) - obj = self.gitlab.http_list(path, **data) + obj = self.gitlab.http_list(path, iterator=iterator, **data) if isinstance(obj, list): return [self._obj_cls(self, item, created_from_list=True) for item in obj] return base.RESTObjectList(self, self._obj_cls, obj) diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 69ebf734b..067a0a155 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -133,12 +133,7 @@ def do_create(self) -> gitlab.base.RESTObject: cli.die("Impossible to create object", e) return result - def do_list( - self, - ) -> ( - gitlab.base.RESTObjectList[gitlab.base.RESTObject] - | list[gitlab.base.RESTObject] - ): + def do_list(self) -> list[gitlab.base.RESTObject]: if TYPE_CHECKING: assert isinstance(self.mgr, gitlab.mixins.ListMixin) message_details = gitlab.utils.WarnMessageData( @@ -150,7 +145,9 @@ def do_list( ) try: - result = self.mgr.list(**self.args, message_details=message_details) + result = self.mgr.list( + **self.args, message_details=message_details, iterator=False + ) except Exception as e: # pragma: no cover, cli.die is unit-tested cli.die("Impossible to list objects", e) return result diff --git a/gitlab/v4/objects/ldap.py b/gitlab/v4/objects/ldap.py index cc23ade44..8b9c88f4f 100644 --- a/gitlab/v4/objects/ldap.py +++ b/gitlab/v4/objects/ldap.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import Any, Literal, overload from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject, RESTObjectList @@ -17,8 +17,25 @@ class LDAPGroupManager(RESTManager[LDAPGroup]): _obj_cls = LDAPGroup _list_filters = ("search", "provider") + @overload + def list( + self, *, iterator: Literal[False] = False, **kwargs: Any + ) -> list[LDAPGroup]: ... + + @overload + def list( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> RESTObjectList[LDAPGroup]: ... + + @overload + def list( + self, *, iterator: bool = False, **kwargs: Any + ) -> list[LDAPGroup] | RESTObjectList[LDAPGroup]: ... + @exc.on_http_error(exc.GitlabListError) - def list(self, **kwargs: Any) -> list[LDAPGroup] | RESTObjectList[LDAPGroup]: + def list( + self, *, iterator: bool = False, **kwargs: Any + ) -> list[LDAPGroup] | RESTObjectList[LDAPGroup]: """Retrieve a list of objects. Args: @@ -45,7 +62,7 @@ def list(self, **kwargs: Any) -> list[LDAPGroup] | RESTObjectList[LDAPGroup]: else: path = self._path - obj = self.gitlab.http_list(path, **data) + obj = self.gitlab.http_list(path, iterator=iterator, **data) if isinstance(obj, list): return [self._obj_cls(self, item) for item in obj] return RESTObjectList(self, self._obj_cls, obj) diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py index ffada66d6..b6e136131 100644 --- a/gitlab/v4/objects/snippets.py +++ b/gitlab/v4/objects/snippets.py @@ -108,8 +108,25 @@ class SnippetManager(CRUDMixin[Snippet]): optional=("title", "files", "file_name", "content", "visibility", "description") ) + @overload + def list_public( + self, *, iterator: Literal[False] = False, **kwargs: Any + ) -> list[Snippet]: ... + + @overload + def list_public( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> RESTObjectList[Snippet]: ... + + @overload + def list_public( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: ... + @cli.register_custom_action(cls_names="SnippetManager") - def list_public(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: + def list_public( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: """List all public snippets. Args: @@ -126,10 +143,27 @@ def list_public(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: Returns: The list of snippets, or a generator if `iterator` is True """ - return self.list(path="/snippets/public", **kwargs) + return self.list(path="/snippets/public", iterator=iterator, **kwargs) + + @overload + def list_all( + self, *, iterator: Literal[False] = False, **kwargs: Any + ) -> list[Snippet]: ... + + @overload + def list_all( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> RESTObjectList[Snippet]: ... + + @overload + def list_all( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: ... @cli.register_custom_action(cls_names="SnippetManager") - def list_all(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: + def list_all( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: """List all snippets. Args: @@ -146,9 +180,30 @@ def list_all(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: Returns: A generator for the snippets list """ - return self.list(path="/snippets/all", **kwargs) + return self.list(path="/snippets/all", iterator=iterator, **kwargs) + + @overload + def public( + self, + *, + iterator: Literal[False] = False, + page: int | None = None, + **kwargs: Any, + ) -> list[Snippet]: ... + + @overload + def public( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> RESTObjectList[Snippet]: ... + + @overload + def public( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: ... - def public(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: + def public( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[Snippet] | list[Snippet]: """List all public snippets. Args: @@ -172,7 +227,7 @@ def public(self, **kwargs: Any) -> RESTObjectList[Snippet] | list[Snippet]: ), category=DeprecationWarning, ) - return self.list(path="/snippets/public", **kwargs) + return self.list(path="/snippets/public", iterator=iterator, **kwargs) class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index 980ccc2dc..2c7c28a2c 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -6,7 +6,7 @@ from __future__ import annotations -from typing import Any, cast, Optional +from typing import Any, cast, Literal, Optional, overload import requests @@ -623,7 +623,24 @@ class UserProjectManager(ListMixin[UserProject], CreateMixin[UserProject]): "id_before", ) - def list(self, **kwargs: Any) -> RESTObjectList[UserProject] | list[UserProject]: + @overload + def list( + self, *, iterator: Literal[False] = False, **kwargs: Any + ) -> list[UserProject]: ... + + @overload + def list( + self, *, iterator: Literal[True] = True, **kwargs: Any + ) -> RESTObjectList[UserProject]: ... + + @overload + def list( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[UserProject] | list[UserProject]: ... + + def list( + self, *, iterator: bool = False, **kwargs: Any + ) -> RESTObjectList[UserProject] | list[UserProject]: """Retrieve a list of objects. Args: @@ -645,7 +662,7 @@ def list(self, **kwargs: Any) -> RESTObjectList[UserProject] | list[UserProject] path = f"/users/{self._parent.id}/projects" else: path = f"/users/{self._from_parent_attrs['user_id']}/projects" - return super().list(path=path, **kwargs) + return super().list(path=path, iterator=iterator, **kwargs) class StarredProject(RESTObject): diff --git a/tests/functional/api/test_merge_requests.py b/tests/functional/api/test_merge_requests.py index a4fef7122..8357a817d 100644 --- a/tests/functional/api/test_merge_requests.py +++ b/tests/functional/api/test_merge_requests.py @@ -177,7 +177,7 @@ def test_merge_request_reset_approvals(gitlab_url, project): # Pause to let GL catch up (happens on hosted too, sometimes takes a while for server to be ready to merge) time.sleep(5) - mr = bot_project.mergerequests.list()[0] # type: ignore[index] + mr = bot_project.mergerequests.list()[0] assert mr.reset_approvals() diff --git a/tests/unit/test_gitlab_http_methods.py b/tests/unit/test_gitlab_http_methods.py index 1e549400d..f85035fc2 100644 --- a/tests/unit/test_gitlab_http_methods.py +++ b/tests/unit/test_gitlab_http_methods.py @@ -567,8 +567,8 @@ def test_list_request_page_and_iterator(gl): UserWarning, match="`iterator=True` and `page=1` were both specified" ): result = gl.http_list("/projects", iterator=True, page=1) - assert isinstance(result, list) - assert len(result) == 20 + assert isinstance(result, GitlabList) + assert len(list(result)) == 20 assert len(responses.calls) == 1