8000 feat: emit a warning when using a `list()` method returns max · python-gitlab/python-gitlab@d6ef9fe · GitHub
[go: up one dir, main page]

Skip to content

Commit d6ef9fe

Browse files
feat: emit a warning when using a list() method returns max
A common cause of issues filed and questions raised is that a user will call a `list()` method and only get 20 items. As this is the default maximum of items that will be returned from a `list()` method. To help with this we now emit a warning when the result from a `list()` method is greater-than or equal to 20 (or the specified `per_page` value) and the user is not using either `all=True`, `as_list=False`, or `page=X`.
1 parent 64d01ef commit d6ef9fe

File tree

3 files changed

+100
-5
lines changed

3 files changed

+100
-5
lines changed

gitlab/client.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
import os
2020
import time
21+
import warnings
2122
from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
2223

2324
import requests
2425
import requests.utils
2526
from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore
2627

28+
import gitlab
2729
import gitlab.config
2830
import gitlab.const
2931
import gitlab.exceptions
@@ -35,6 +37,14 @@
3537
"{source!r} to {target!r}"
3638
)
3739

40+
# https://docs.gitlab.com/ee/api/#offset-based-pagination
41+
DEFAULT_PER_PAGE = 20
42+
MAXIMUM_PER_PAGE = 100
43+
_PAGINATION_URL = (
44+
f"https://python-gitlab.readthedocs.io/en/v{gitlab.__version__}/"
45+
f"api-usage.html#pagination"
46+
)
47+
3848

3949
class Gitlab:
4050
"""Represents a GitLab server connection.
@@ -816,9 +826,29 @@ def http_list(
816826
if get_all is True and as_list is True:
817827
return list(GitlabList(self, url, query_data, **kwargs))
818828

829+
per_page = min(kwargs.get("per_page", DEFAULT_PER_PAGE), MAXIMUM_PER_PAGE)
819830
if page or as_list is True:
820831
# pagination requested, we return a list
821-
return list(GitlabList(self, url, query_data, get_next=False, **kwargs))
832+
gl_list = GitlabList(self, url, query_data, get_next=False, **kwargs)
833+
items = list(gl_list)
834+
if (
835+
page is None
836+
and len(items) >= per_page
837+
and (gl_list.total is None or len(items) < gl_list.total)
838+
):
839+
total_items = "10,000+" if gl_list.total is None else gl_list.total
840+
# Warn the user that they are only going to retrieve `per_page` maximum
841+
# items. This is a common cause of issues filed.
842+
warnings.warn(
843+
(
844+
f"Calling a `list()` method without specifying `all=True` or "
845+
f"`as_list=False` will return a maximum of {per_page} items. "
846+
f"Your query returned {len(items)} of {total_items} items. "
847+
f"See {_PAGINATION_URL} for more details"
848+
),
849+
UserWarning,
850+
)
851+
return items
822852

823853
# No pagination, generator requested
824854
return GitlabList(self, url, query_data, **kwargs)

tests/functional/api/test_gitlab.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ def test_template_dockerfile(gl):
8181

8282

8383
def test_template_gitignore(gl):
84-
assert gl.gitignores.list()
84+
assert gl.gitignores.list(all=True)
8585
gitignore = gl.gitignores.get("Node")
8686
assert gitignore.content is not None
8787

8888

8989
def test_template_gitlabciyml(gl):
90-
assert gl.gitlabciymls.list()
90+
assert gl.gitlabciymls.list(all=True)
9191
gitlabciyml = gl.gitlabciymls.get("Nodejs")
9292
assert gitlabciyml.content is not None
9393

tests/unit/test_gitlab_http_methods.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
import pytest
24
import requests
35
import responses
@@ -329,20 +331,83 @@ def test_list_request(gl):
329331
match=MATCH_EMPTY_QUERY_PARAMS,
330332
)
331333

332-
result = gl.http_list("/projects", as_list=True)
334+
with warnings.catch_warnings(record=True) as caught_warnings:
335+
result = gl.http_list("/projects", as_list=True)
336+
assert len(caught_warnings) == 0
333337
assert isinstance(result, list)
334338
assert len(result) == 1
335339

336340
result = gl.http_list("/projects", as_list=False)
337341
assert isinstance(result, GitlabList)
338-
assert len(result) == 1
342+
assert len(list(result)) == 1
339343

340344
result = gl.http_list("/projects", all=True)
341345
assert isinstance(result, list)
342346
assert len(result) == 1
343347
assert responses.assert_call_count(url, 3) is True
344348

345349

350+
@responses.activate
351+
def test_list_request_pagination_warning(gl):
352+
url = "http://localhost/api/v4/projects"
353+
responses.add(
354+
method=responses.GET,
355+
url=url,
356+
json=[
357+
{"name": "project01"},
358+
{"name": "project02"},
359+
{"name": "project03"},
360+
{"name": "project04"},
361+
{"name": "project05"},
362+
{"name": "project06"},
363+
{"name": "project07"},
364+
{"name": "project08"},
365+
{"name": "project09"},
366+
{"name": "project10"},
367+
{"name": "project11"},
368+
{"name": "project12"},
369+
{"name": "project13"},
370+
{"name": "project14"},
371+
{"name": "project15"},
372+
{"name": "project16"},
373+
{"name": "project17"},
374+
{"name": "project18"},
375+
{"name": "project19"},
376+
{"name": "project20"},
377+
],
378+
headers={"X-Total": "30"},
379+
status=200,
380+
match=MATCH_EMPTY_QUERY_PARAMS,
381+
)
382+
383+
with warnings.catch_warnings(record=True) as caught_warnings:
384+
result = gl.http_list("/projects", as_list=True)
385+
assert len(caught_warnings) == 1
386+
warning = caught_warnings[0]
387+
assert isinstance(warning.message, UserWarning)
388+
message = str(caught_warnings[0].message)
389+
assert "Calling" in message
390+
assert "return a maximum of" in message
391+
assert "readthedocs" in message
392+
393+
assert isinstance(result, list)
394+
assert len(result) == 20
395+
396+
with warnings.catch_warnings(record=True) as caught_warnings:
397+
result = gl.http_list("/projects", as_list=False)
398+
assert len(caught_warnings) == 0
399+
assert isinstance(result, GitlabList)
400+
assert len(list(result)) == 20
401+
402+
with warnings.catch_warnings(record=True) as caught_warnings:
403+
result = gl.http_list("/projects", all=True)
404+
assert len(caught_warnings) == 0
405+
406+
assert isinstance(result, list)
407+
assert len(result) == 20
408+
assert responses.assert_call_count(url, 3) is True
409+
410+
346411
@responses.activate
347412
def test_list_request_404(gl):
348413
url = "http://localhost/api/v4/not_there"

0 commit comments

Comments
 (0)
0