Skip to content

Commit c884607

Browse files
authored
Bug 1997236 - Don't try to re-parse JSON in util.taskcluster.get_artifact (#846)
The previous code was only checking if the returned response from the taskcluster API was a dict to verify if it had been parsed or not. That works well until the JSON file we're getting contains something like a list in which case we skip the first condition, end up with a parsed response in `if path.endswith(".json")` and boom `AttributeError: 'list' object has no attribute 'json'`. Since we already rely on the response type being `requests.Request`, I changed the logic to be: - If we receive a dict, it might be either the parsed artifact content, or taskcluster giving us `{"response": requests.Response}` because it was unable to parse it. - In the case where it's taskcluster's (detected by checking if "response" is in the dict and if it has the right type), peel it off, response is now the `Response` object and we can read from it. - At this point we have 2 choices again, either response is a `request.Response` that we have to read, or anything else. If it is anything else then it's already been parsed by taskcluster so we just return it as is. - Continue with the previous logic, try to parse the file based on the extension. I left the JSON case in there even though we should not be hitting it because it will raise on invalid JSON, which was the case previously too. Taskcluster masks JSON failures by returning the response object instead.
1 parent 773f5d2 commit c884607

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

src/taskgraph/util/taskcluster.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,17 @@ def get_taskcluster_client(service: str):
7373
def _handle_artifact(
7474
path: str, response: Union[requests.Response, dict[str, Any]]
7575
) -> Any:
76-
if isinstance(response, dict):
77-
# When taskcluster client returns non-JSON responses, it wraps them in {"response": <Response>}
78-
if "response" in response and isinstance(
79-
response["response"], requests.Response
80-
):
81-
response = response["response"]
82-
else:
83-
# If we already a dict (parsed JSON), return it directly.
84-
return response
76+
# When taskcluster client returns non-JSON responses, it wraps them in {"response": <Response>}
77+
if (
78+
isinstance(response, dict)
79+
and "response" in response
80+
and isinstance(response["response"], requests.Response)
81+
):
82+
response = response["response"]
83+
84+
if not isinstance(response, requests.Response):
85+
# At this point, if we don't have a response object, it's already parsed, return it
86+
return response
8587

8688
# We have a response object, load the content based on the path extension.
8789
if path.endswith(".json"):

test/test_util_taskcluster.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ def test_get_artifact(responses, root_url):
124124
result = tc.get_artifact(tid, "artifact.json")
125125
assert result == {"foo": "bar"}
126126

127+
# Test JSON artifact that isn't a dict (bug 1997236)
128+
responses.get("http://foo.bar/artifact.json", json=[1, 2, 3])
129+
responses.get(
130+
f"{root_url}/api/queue/v1/task/{tid}/artifacts/artifact.json",
131+
body=b'{"type": "s3", "url": "http://foo.bar/artifact.json"}',
132+
status=303,
133+
headers={"Location": "http://foo.bar/artifact.json"},
134+
)
135+
result = tc.get_artifact(tid, "artifact.json")
136+
assert result == [1, 2, 3]
137+
127138
# Test YAML artifact
128139
expected_result = {"foo": b"\xe2\x81\x83".decode()}
129140
responses.get(

0 commit comments

Comments
 (0)