Skip to content

Commit e2ea8b8

Browse files
authored
fix: optionally keep user-provided base URL for pagination (#2149)
1 parent 1136b17 commit e2ea8b8

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

gitlab/client.py

+29
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""Wrapper for the GitLab API."""
1818

1919
import os
20+
import re
2021
import time
2122
from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
2223

@@ -65,6 +66,8 @@ class Gitlab:
6566
user_agent: A custom user agent to use for making HTTP requests.
6667
retry_transient_errors: Whether to retry after 500, 502, 503, 504
6768
or 52x responses. Defaults to False.
69+
keep_base_url: keep user-provided base URL for pagination if it
70+
differs from response headers
6871
"""
6972

7073
def __init__(
@@ -84,6 +87,7 @@ def __init__(
8487
order_by: Optional[str] = None,
8588
user_agent: str = gitlab.const.USER_AGENT,
8689
retry_transient_errors: bool = False,
90+
keep_base_url: bool = False,
8791
) -> None:
8892

8993
self._api_version = str(api_version)
@@ -94,6 +98,7 @@ def __init__(
9498
#: Timeout to use for requests to gitlab server
9599
self.timeout = timeout
96100
self.retry_transient_errors = retry_transient_errors
101+
self.keep_base_url = keep_base_url
97102
#: Headers that will be used in request to GitLab
98103
self.headers = {"User-Agent": user_agent}
99104

@@ -1132,6 +1137,30 @@ def _query(
11321137
next_url = requests.utils.parse_header_links(result.headers["links"])[
11331138
0
11341139
]["url"]
1140+
# if the next url is different with user provided server URL
1141+
# then give a warning it may because of misconfiguration
1142+
# but if the option to fix provided then just reconstruct it
1143+
if not next_url.startswith(self._gl.url):
1144+
search_api_url = re.search(r"(^.*?/api)", next_url)
1145+
if search_api_url:
1146+
next_api_url = search_api_url.group(1)
1147+
if self._gl.keep_base_url:
1148+
next_url = next_url.replace(
1149+
next_api_url, f"{self._gl._base_url}/api"
1150+
)
1151+
else:
1152+
utils.warn(
1153+
message=(
1154+
f"The base URL in the server response"
1155+
f"differs from the user-provided base URL "
1156+
f"({self._gl.url}/api/ -> {next_api_url}/). "
1157+
f"This may lead to unexpected behavior and "
1158+
f"broken pagination. Use `keep_base_url=True` "
1159+
f"when initializing the Gitlab instance "
1160+
f"to follow the user-provided base URL."
1161+
),
1162+
category=UserWarning,
1163+
)
11351164
self._next_url = next_url
11361165
except KeyError:
11371166
self._next_url = None

tests/unit/test_gitlab.py

+55
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,58 @@ def test_gitlab_plain_const_does_not_warn(recwarn):
353353

354354
assert not recwarn
355355
assert no_access == 0
356+
357+
358+
@responses.activate
359+
@pytest.mark.parametrize(
360+
"kwargs,link_header,expected_next_url,show_warning",
361+
[
362+
(
363+
{},
364+
"<http://localhost/api/v4/tests?per_page=1&page=2>;" ' rel="next"',
365+
"http://localhost/api/v4/tests?per_page=1&page=2",
366+
False,
367+
),
368+
(
369+
{},
370+
"<http://orig_host/api/v4/tests?per_page=1&page=2>;" ' rel="next"',
371+
"http://orig_host/api/v4/tests?per_page=1&page=2",
372+
True,
373+
),
374+
(
375+
{"keep_base_url": True},
376+
"<http://orig_host/api/v4/tests?per_page=1&page=2>;" ' rel="next"',
377+
"http://localhost/api/v4/tests?per_page=1&page=2",
378+
False,
379+
),
380+
],
381+
ids=["url-match-does-not-warn", "url-mismatch-warns", "url-mismatch-keeps-url"],
382+
)
383+
def test_gitlab_keep_base_url(kwargs, link_header, expected_next_url, show_warning):
384+
responses.add(
385+
**{
386+
"method": responses.GET,
387+
"url": "http://localhost/api/v4/tests",
388+
"json": [{"a": "b"}],
389+
"headers": {
390+
"X-Page": "1",
391+
"X-Next-Page": "2",
392+
"X-Per-Page": "1",
393+
"X-Total-Pages": "2",
394+
"X-Total": "2",
395+
"Link": (link_header),
396+
},
397+
"content_type": "application/json",
398+
"status": 200,
399+
"match": helpers.MATCH_EMPTY_QUERY_PARAMS,
400+
}
401+
)
402+
403+
gl = gitlab.Gitlab(url="http://localhost", **kwargs)
404+
if show_warning:
405+
with pytest.warns(UserWarning) as warn_record:
406+
obj = gl.http_list("/tests", iterator=True)
407+
assert len(warn_record) == 1
408+
else:
409+
obj = gl.http_list("/tests", iterator=True)
410+
assert obj._next_url == expected_next_url

0 commit comments

Comments
 (0)