8000 feat(api): Add argument that appends extra HTTP headers to a request · python-gitlab/python-gitlab@fb07b5c · GitHub
[go: up one dir, main page]

Skip to content

Commit fb07b5c

Browse files
igorp-collaboranejch
authored andcommitted
feat(api): Add argument that appends extra HTTP headers to a request
Currently the only way to manipulate the headers for a request is to use `Gitlab.headers` attribute. However, this makes it very concurrently unsafe because the `Gitlab` object can be shared between multiple requests at the same time. Instead add a new keyword argument `extra_headers` which will update the headers dictionary with new values just before the request is sent. For example, this can be used to download a part of a artifacts file using the `Range` header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests Signed-off-by: Igor Ponomarev <igor.ponomarev@collabora.com>
1 parent e4673d8 commit fb07b5c

File tree

4 files changed

+69
-0
lines changed

4 files changed

+69
-0
lines changed

docs/api-usage-advanced.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,20 @@ on your own, such as for nested API responses and ``Union`` return types. For ex
211211
212212
if TYPE_CHECKING:
213213
assert isinstance(license["plan"], str)
214+
215+
Per request HTTP headers override
216+
---------------------------------
217+
218+
The ``extra_headers`` keyword argument can be used to add and override
219+
the HTTP headers for a specific request. For example, it can be used do add ``Range``
220+
header to download a part of artifacts archive:
221+
222+
.. code-block:: python
223+
224+
import gitlab
225+
226+
gl = gitlab.Gitlab(url, token)
227+
project = gl.projects.get(1)
228+
job = project.jobs.get(123)
229+
230+
artifacts = job.artifacts(extra_headers={"Range": "bytes=0-9"})

gitlab/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ def http_request(
654654
obey_rate_limit: bool = True,
655655
retry_transient_errors: Optional[bool] = None,
656656
max_retries: int = 10,
657+
extra_headers: Optional[Dict[str, Any]] = None,
657658
**kwargs: Any,
658659
) -> requests.Response:
659660
"""Make an HTTP request to the Gitlab server.
@@ -675,6 +676,7 @@ def http_request(
675676
or 52x responses. Defaults to False.
676677
max_retries: Max retries after 429 or transient errors,
677678
set to -1 to retry forever. Defaults to 10.
679+
extra_headers: Add and override HTTP headers for the request.
678680
**kwargs: Extra options to send to the server (e.g. sudo)
679681
680682
Returns:
@@ -721,6 +723,9 @@ def http_request(
721723
send_data = self._backend.prepare_send_data(files, post_data, raw)
722724
opts["headers"]["Content-type"] = send_data.content_type
723725

726+
if extra_headers is not None:
727+
opts["headers"].update(extra_headers)
728+
724729
retry = utils.Retry(
725730
max_retries=max_retries,
726731
obey_rate_limit=obey_rate_limit,

tests/unit/objects/test_job_artifacts.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ def resp_project_artifacts_delete():
3535
yield rsps
3636

3737

38+
@pytest.fixture
39+
def resp_job_artifact_bytes_range(binary_content):
40+
with responses.RequestsMock() as rsps:
41+
rsps.add(
42+
method=responses.GET,
43+
url="http://localhost/api/v4/projects/1/jobs/123/artifacts",
44+
body=binary_content[:10],
45+
content_type="application/octet-stream",
46+
status=206,
47+
match=[responses.matchers.header_matcher({"Range": "bytes=0-9"})],
48+
)
49+
yield rsps
50+
51+
3852
def test_project_artifacts_delete(gl, resp_project_artifacts_delete):
3953
project = gl.projects.get(1, lazy=True)
4054
project.artifacts.delete()
@@ -46,3 +60,13 @@ def test_project_artifacts_download_by_ref_name(
4660
project = gl.projects.get(1, lazy=True)
4761
artifacts = project.artifacts.download(ref_name=ref_name, job=job)
4862
assert artifacts == binary_content
63+
64+
65+
def test_job_artifact_download_bytes_range(
66+
gl, binary_content, resp_job_artifact_bytes_range
67+
):
68+
project = gl.projects.get(1, lazy=True)
69+
job = project.jobs.get(123, lazy=True)
70+
71+
artifacts = job.artifacts(extra_headers={"Range": "bytes=0-9"})
72+
assert len(artifacts) == 10

tests/unit/test_gitlab_http_methods.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ def request_callback(request):
117117
assert len(responses.calls) == calls_before_success
118118

119119

120+
@responses.activate
121+
def test_http_request_extra_headers(gl):
122+
path = "/projects/123/jobs/123456"
123+
url = "http://localhost/api/v4" + path
124+
125+
range_headers = {"Range": "bytes=0-99"}
126+
127+
responses.add(
128+
method=responses.GET,
129+
url=url,
130+
body=b"a" * 100,
131+
status=206,
132+
content_type="application/octet-stream",
133+
match=helpers.MATCH_EMPTY_QUERY_PARAMS
134+
+ [responses.matchers.header_matcher(range_headers)],
135+
)
136+
137+
http_r = gl.http_request("get", path, extra_headers=range_headers)
138+
139+
assert http_r.status_code == 206
140+
assert len(http_r.content) == 100
141+
142+
120143
@responses.activate
121144
@pytest.mark.parametrize(
122145
"exception",

0 commit comments

Comments
 (0)
0