Repository: python-gitlab/python-gitlab

Issue:
Job Artifact Retrieval Does Not Work
Hello, I'm not quite sure why this is failing. My understanding is I should be able to use the path correctly to retrieve the artifact, but it completely fails for me.

This may have something to do with the fact that my artifact has spaces in its path?

## Description of the problem, including code/CLI snippet
I'm trying to download a single artifact from a pipeline job.

## Expected Behavior
- Get Pipeline
- Get Pipeline Job
- Convert Pipeline Job to a Job
- Get the artifact by path from the job

## Actual Behavior
- Get Pipeline
- Get Pipeline Job
- Convert Pipeline Job to a Job
- Get the artifact by path from the job, except it returns an error saying the attribute does not exist
```
(venv) ╭─user ~/Repositories/ExampleRepo  ‹feature/ci-ci-testing*› 
╰─$ python gitlab_artifact.py                                      1 ↵
Traceback (most recent call last):
  File "gitlab_artifact.py", line 60, in <module>
    main()
  File "gitlab_artifact.py", line 52, in main
    a = most_recent_job.artifact("ExampleRepo/cached_service_specs.json")
  File "/Users/lknecht/Repositories/ExampleRepo/venv/lib/python3.7/site-packages/gitlab/cli.py", line 43, in wrapped_f
    return f(*args, **kwargs)
  File "/Users/lknecht/Repositories/ExampleRepo/venv/lib/python3.7/site-packages/gitlab/exceptions.py", line 251, in wrapped_f
    return f(*args, **kwargs)
  File "/Users/lknecht/Repositories/ExampleRepo/venv/lib/python3.7/site-packages/gitlab/v4/objects.py", line 1397, in artifact
    return utils.response_content(result, streamed, action, chunk_size)
  File "/Users/lknecht/Repositories/ExampleRepo/venv/lib/python3.7/site-packages/gitlab/utils.py", line 28, in response_content
    return response.content
AttributeError: 'list' object has no attribute 'content'
```

## Specifications

  - python-gitlab version: 1.7.0
  - API version you are using (v3/v4): Unknown
  - Gitlab server version (or gitlab.com): GitLab Enterprise Edition 11.6.3-ee

Here is the script I've written that is failing

```python
# Python Standard Libraries
# N/A
# Third-Party Libraries
import gitlab
# Custom Libraries
# N/A

SERVER_URL = "<REDACTED>"
TOKEN = "<REDACTED>"

PROJECT_ID = <REDACTED>
ARTIFACT_JOB_NAME = "<REDACTED>"
ARTIFACT_NAME = "ExampleRepo/cached_service_specs.json"

def main():
    gitlab_client = gitlab.Gitlab(SERVER_URL, private_token=TOKEN)
    monitor_project = gitlab_client.projects.get(PROJECT_ID)

    pipelines = [pipeline
                 for pipeline in monitor_project.pipelines.list()
                 if len(pipeline.jobs.list()) > 0
                 for job in pipeline.jobs.list()
                 if job.attributes["name"] == ARTIFACT_JOB_NAME]

    if(not (len(pipelines) > 0)):
        raise Exception("No pipelines are available to retrieve artifacts from.")

    most_recent_pipeline = pipelines[0]
    most_recent_pipeline_job = most_recent_pipeline.jobs.list()[0]

    most_recent_job = monitor_project.jobs.get(most_recent_pipeline_job.id, lazy=True)


    artifact = most_recent_job.artifact(ARTIFACT_NAME)


main()
```


Discussion:
Hi @loganknecht 

The artifact you retrieve contains json data so python-gitlab interprets it instead of just returning the content as string. This shouldn't happen obviously.

I'll push a patch ASAP.

Resolving patch:
diff --git a/gitlab/__init__.py b/gitlab/__init__.py
--- a/gitlab/__init__.py
+++ b/gitlab/__init__.py
@@ -517,7 +517,8 @@ def http_request(self, verb, path, query_data={}, post_data=None,
                                   error_message=error_message,
                                   response_body=result.content)
 
-    def http_get(self, path, query_data={}, streamed=False, **kwargs):
+    def http_get(self, path, query_data={}, streamed=False, raw=False,
+                 **kwargs):
         """Make a GET request to the Gitlab server.
 
         Args:
@@ -525,6 +526,7 @@ def http_get(self, path, query_data={}, streamed=False, **kwargs):
                         'http://whatever/v4/api/projecs')
             query_data (dict): Data to send as query parameters
             streamed (bool): Whether the data should be streamed
+            raw (bool): If True do not try to parse the output as json
             **kwargs: Extra options to send to the server (e.g. sudo)
 
         Returns:
@@ -538,8 +540,10 @@ def http_get(self, path, query_data={}, streamed=False, **kwargs):
         """
         result = self.http_request('get', path, query_data=query_data,
                                    streamed=streamed, **kwargs)
-        if (result.headers['Content-Type'] == 'application/json' and
-           not streamed):
+
+        if (result.headers['Content-Type'] == 'application/json'
+           and not streamed
+           and not raw):
             try:
                 return result.json()
             except Exception:
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -1128,7 +1128,7 @@ def content(self, streamed=False, action=None, chunk_size=1024, **kwargs):
         """
         path = '/snippets/%s/raw' % self.get_id()
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
 
@@ -1365,7 +1365,7 @@ def artifacts(self, streamed=False, action=None, chunk_size=1024,
         """
         path = '%s/%s/artifacts' % (self.manager.path, self.get_id())
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
     @cli.register_custom_action('ProjectJob')
@@ -1393,7 +1393,7 @@ def artifact(self, path, streamed=False, action=None, chunk_size=1024,
         """
         path = '%s/%s/artifacts/%s' % (self.manager.path, self.get_id(), path)
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
     @cli.register_custom_action('ProjectJob')
@@ -1419,7 +1419,7 @@ def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs):
         """
         path = '%s/%s/trace' % (self.manager.path, self.get_id())
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
 
@@ -2654,7 +2654,7 @@ def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024,
         path = '%s/%s/raw' % (self.path, file_path)
         query_data = {'ref': ref}
         result = self.gitlab.http_get(path, query_data=query_data,
-                                      streamed=streamed, **kwargs)
+                                      streamed=streamed, raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
 
@@ -2897,7 +2897,7 @@ def content(self, streamed=False, action=None, chunk_size=1024, **kwargs):
         """
         path = "%s/%s/raw" % (self.manager.path, self.get_id())
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
 
@@ -3174,7 +3174,7 @@ def download(self, streamed=False, action=None, chunk_size=1024, **kwargs):
         """
         path = '/projects/%d/export/download' % self.project_id
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
 
@@ -3315,7 +3315,7 @@ def repository_raw_blob(self, sha, streamed=False, action=None,
         """
         path = '/projects/%s/repository/blobs/%s/raw' % (self.get_id(), sha)
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
     @cli.register_custom_action('Project', ('from_', 'to'))
@@ -3391,7 +3391,8 @@ def repository_archive(self, sha=None, streamed=False, action=None,
         if sha:
             query_data['sha'] = sha
         result = self.manager.gitlab.http_get(path, query_data=query_data,
-                                              streamed=streamed, **kwargs)
+                                              raw=True, streamed=streamed,
+                                              **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
     @cli.register_custom_action('Project', ('forked_from_id', ))
@@ -3674,7 +3675,7 @@ def snapshot(self, wiki=False, streamed=False, action=None,
         """
         path = '/projects/%d/snapshot' % self.get_id()
         result = self.manager.gitlab.http_get(path, streamed=streamed,
-                                              **kwargs)
+                                              raw=True, **kwargs)
         return utils.response_content(result, streamed, action, chunk_size)
 
     @cli.register_custom_action('Project', ('scope', 'search'))


Test patch: