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")