From a8c95c800648ffdef3510a5e74b44034746c999f Mon Sep 17 00:00:00 2001 From: Nejc Habjan <nejc.habjan@siemens.com> Date: Sun, 12 Feb 2023 12:30:29 +0100 Subject: [PATCH] refactor: move response_content into backend code --- gitlab/_backends/protocol.py | 13 +++++++++- gitlab/_backends/requests_backend.py | 39 +++++++++++++++++++++++++++- gitlab/mixins.py | 2 +- gitlab/utils.py | 37 +++++++------------------- gitlab/v4/objects/artifacts.py | 5 ++-- gitlab/v4/objects/files.py | 2 +- gitlab/v4/objects/jobs.py | 7 +++-- gitlab/v4/objects/packages.py | 3 +-- gitlab/v4/objects/projects.py | 2 +- gitlab/v4/objects/repositories.py | 4 +-- gitlab/v4/objects/secure_files.py | 3 +-- gitlab/v4/objects/snippets.py | 5 ++-- tests/unit/test_backends.py | 23 ++++++++++++++++ tests/unit/test_utils.py | 21 --------------- 14 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 tests/unit/test_backends.py diff --git a/gitlab/_backends/protocol.py b/gitlab/_backends/protocol.py index 72cee226d..dcbc87a3d 100644 --- a/gitlab/_backends/protocol.py +++ b/gitlab/_backends/protocol.py @@ -1,6 +1,6 @@ import abc import sys -from typing import Any, Dict, Optional, Union +from typing import Any, Callable, Dict, Iterator, Optional, Union import requests from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore @@ -17,6 +17,17 @@ def __init__(self, response: requests.Response) -> None: ... class Backend(Protocol): + @staticmethod + @abc.abstractmethod + def response_content( + response: requests.Response, + streamed: bool, + action: Optional[Callable[[bytes], None]], + chunk_size: int, + *, + iterator: bool, + ) -> Optional[Union[bytes, Iterator[Any]]]: ... + @abc.abstractmethod def http_request( self, diff --git a/gitlab/_backends/requests_backend.py b/gitlab/_backends/requests_backend.py index 79e3cbf12..57692c2e9 100644 --- a/gitlab/_backends/requests_backend.py +++ b/gitlab/_backends/requests_backend.py @@ -1,7 +1,16 @@ from __future__ import annotations import dataclasses -from typing import Any, BinaryIO, Dict, Optional, TYPE_CHECKING, Union +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + Iterator, + Optional, + TYPE_CHECKING, + Union, +) import requests from requests import PreparedRequest @@ -55,6 +64,11 @@ def __post_init__(self) -> None: ) +class _StdoutStream: + def __call__(self, chunk: Any) -> None: + print(chunk) + + class RequestsResponse(protocol.BackendResponse): def __init__(self, response: requests.Response) -> None: self._response: requests.Response = response @@ -126,6 +140,29 @@ def prepare_send_data( return SendData(json=post_data, content_type="application/json") + @staticmethod + def response_content( + response: requests.Response, + streamed: bool, + action: Optional[Callable[[bytes], None]], + chunk_size: int, + *, + iterator: bool, + ) -> Optional[Union[bytes, Iterator[Any]]]: + if iterator: + return response.iter_content(chunk_size=chunk_size) + + if streamed is False: + return response.content + + if action is None: + action = _StdoutStream() + + for chunk in response.iter_content(chunk_size=chunk_size): + if chunk: + action(chunk) + return None + def http_request( self, method: str, diff --git a/gitlab/mixins.py b/gitlab/mixins.py index 8f29c7b08..d3643206a 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -649,7 +649,7 @@ def download( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/utils.py b/gitlab/utils.py index c2d088204..45f4dc9bd 100644 --- a/gitlab/utils.py +++ b/gitlab/utils.py @@ -4,16 +4,9 @@ import traceback import urllib.parse import warnings -from typing import Any, Callable, Dict, Iterator, Literal, Optional, Tuple, Type, Union +from typing import Any, Dict, Iterator, Literal, Optional, Tuple, Type, Union -import requests - -from gitlab import types - - -class _StdoutStream: - def __call__(self, chunk: Any) -> None: - print(chunk) +from gitlab import _backends, types def get_content_type(content_type: Optional[str]) -> str: @@ -50,26 +43,14 @@ def format(self, record: logging.LogRecord) -> str: def response_content( - response: requests.Response, - streamed: bool, - action: Optional[Callable[[bytes], None]], - chunk_size: int, - *, - iterator: bool, + *args: Any, **kwargs: Any ) -> Optional[Union[bytes, Iterator[Any]]]: - if iterator: - return response.iter_content(chunk_size=chunk_size) - - if streamed is False: - return response.content - - if action is None: - action = _StdoutStream() - - for chunk in response.iter_content(chunk_size=chunk_size): - if chunk: - action(chunk) - return None + warn( + "`utils.response_content()` is deprecated and will be removed in a future" + "version.\nUse the current backend's `response_content()` method instead.", + category=DeprecationWarning, + ) + return _backends.DefaultBackend.response_content(*args, **kwargs) def _transform_types( diff --git a/gitlab/v4/objects/artifacts.py b/gitlab/v4/objects/artifacts.py index 4643ad3b1..fe6b05212 100644 --- a/gitlab/v4/objects/artifacts.py +++ b/gitlab/v4/objects/artifacts.py @@ -9,7 +9,6 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab import utils from gitlab.base import RESTManager, RESTObject __all__ = ["ProjectArtifact", "ProjectArtifactManager"] @@ -90,7 +89,7 @@ def download( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) @@ -142,6 +141,6 @@ def raw( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/files.py b/gitlab/v4/objects/files.py index 3eeea4ca4..068559e92 100644 --- a/gitlab/v4/objects/files.py +++ b/gitlab/v4/objects/files.py @@ -272,7 +272,7 @@ def raw( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py index 28a46d775..92420109a 100644 --- a/gitlab/v4/objects/jobs.py +++ b/gitlab/v4/objects/jobs.py @@ -4,7 +4,6 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab import utils from gitlab.base import RESTManager, RESTObject from gitlab.mixins import RefreshMixin, RetrieveMixin from gitlab.types import ArrayAttribute @@ -152,7 +151,7 @@ def artifacts( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) @@ -195,7 +194,7 @@ def artifact( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) @@ -236,7 +235,7 @@ def trace( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py index 8dcc3bdc4..0c0927876 100644 --- a/gitlab/v4/objects/packages.py +++ b/gitlab/v4/objects/packages.py @@ -20,7 +20,6 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab import utils from gitlab.base import RESTManager, RESTObject from gitlab.mixins import DeleteMixin, GetMixin, ListMixin, ObjectDeleteMixin @@ -166,7 +165,7 @@ def download( result = self.gitlab.http_get(path, streamed=streamed, raw=True, **kwargs) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index e7a4b399e..4e580b1e0 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -499,7 +499,7 @@ def snapshot( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/repositories.py b/gitlab/v4/objects/repositories.py index 7d5b79df4..da1392209 100644 --- a/gitlab/v4/objects/repositories.py +++ b/gitlab/v4/objects/repositories.py @@ -145,7 +145,7 @@ def repository_raw_blob( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) @@ -247,7 +247,7 @@ def repository_archive( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/secure_files.py b/gitlab/v4/objects/secure_files.py index d96c129e4..e03b304b0 100644 --- a/gitlab/v4/objects/secure_files.py +++ b/gitlab/v4/objects/secure_files.py @@ -9,7 +9,6 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab import utils from gitlab.base import RESTManager, RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin from gitlab.types import FileAttribute, RequiredOptional @@ -54,7 +53,7 @@ def download( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py index 68a20d0e3..fe733055e 100644 --- a/gitlab/v4/objects/snippets.py +++ b/gitlab/v4/objects/snippets.py @@ -4,7 +4,6 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab import utils from gitlab.base import RESTManager, RESTObject, RESTObjectList from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin, UserAgentDetailMixin from gitlab.types import RequiredOptional @@ -61,7 +60,7 @@ def content( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) @@ -154,7 +153,7 @@ def content( ) if TYPE_CHECKING: assert isinstance(result, requests.Response) - return utils.response_content( + return self.manager.gitlab._backend.response_content( result, streamed, action, chunk_size, iterator=iterator ) diff --git a/tests/unit/test_backends.py b/tests/unit/test_backends.py new file mode 100644 index 000000000..a70e4efcd --- /dev/null +++ b/tests/unit/test_backends.py @@ -0,0 +1,23 @@ +import requests +import responses + +from gitlab import _backends + + +@responses.activate +def test_streamed_response_content_with_requests(capsys): + responses.add( + method="GET", + url="https://example.com", + status=200, + body="test", + content_type="application/octet-stream", + ) + + resp = requests.get("https://example.com", stream=True) + _backends.RequestsBackend.response_content( + resp, streamed=True, action=None, chunk_size=1024, iterator=False + ) + + captured = capsys.readouterr() + assert "test" in captured.out diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index db030b61f..35111c586 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,8 +3,6 @@ import warnings import pytest -import requests -import responses from gitlab import types, utils @@ -23,25 +21,6 @@ def test_get_content_type(content_type, expected_type): assert parsed_type == expected_type -@responses.activate -def test_response_content(capsys): - responses.add( - method="GET", - url="https://example.com", - status=200, - body="test", - content_type="application/octet-stream", - ) - - resp = requests.get("https://example.com", stream=True) - utils.response_content( - resp, streamed=True, action=None, chunk_size=1024, iterator=False - ) - - captured = capsys.readouterr() - assert "test" in captured.out - - class TestEncodedId: def test_init_str(self): obj = utils.EncodedId("Hello")