Skip to content

Commit 37001bc

Browse files
Use Dataclass For Workflow Run
Signed-off-by: Hassan Abouelela <[email protected]>
1 parent 5be5cf2 commit 37001bc

File tree

2 files changed

+62
-29
lines changed

2 files changed

+62
-29
lines changed

pydis_site/apps/api/github_utils.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Utilities for working with the GitHub API."""
2-
2+
import dataclasses
33
import datetime
44
import math
5+
import typing
56

67
import httpx
78
import jwt
@@ -50,6 +51,29 @@ class RunPendingError(ArtifactProcessingError):
5051
status = 202
5152

5253

54+
@dataclasses.dataclass(frozen=True)
55+
class WorkflowRun:
56+
"""
57+
A workflow run from the GitHub API.
58+
59+
https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run
60+
"""
61+
62+
name: str
63+
head_sha: str
64+
created_at: str
65+
status: str
66+
conclusion: str
67+
artifacts_url: str
68+
69+
@classmethod
70+
def from_raw(cls, data: dict[str, typing.Any]):
71+
"""Create an instance using the raw data from the API, discarding unused fields."""
72+
return cls(**{
73+
key.name: data[key.name] for key in dataclasses.fields(cls)
74+
})
75+
76+
5377
def generate_token() -> str:
5478
"""
5579
Generate a JWT token to access the GitHub API.
@@ -121,12 +145,12 @@ def authorize(owner: str, repo: str) -> httpx.Client:
121145
raise e
122146

123147

124-
def check_run_status(run: dict) -> str:
148+
def check_run_status(run: WorkflowRun) -> str:
125149
"""Check if the provided run has been completed, otherwise raise an exception."""
126-
created_at = datetime.datetime.strptime(run["created_at"], ISO_FORMAT_STRING)
150+
created_at = datetime.datetime.strptime(run.created_at, ISO_FORMAT_STRING)
127151
run_time = datetime.datetime.now() - created_at
128152

129-
if run["status"] != "completed":
153+
if run.status != "completed":
130154
if run_time <= MAX_RUN_TIME:
131155
raise RunPendingError(
132156
f"The requested run is still pending. It was created "
@@ -135,12 +159,12 @@ def check_run_status(run: dict) -> str:
135159
else:
136160
raise RunTimeoutError("The requested workflow was not ready in time.")
137161

138-
if run["conclusion"] != "success":
162+
if run.conclusion != "success":
139163
# The action failed, or did not run
140-
raise ActionFailedError(f"The requested workflow ended with: {run['conclusion']}")
164+
raise ActionFailedError(f"The requested workflow ended with: {run.conclusion}")
141165

142166
# The requested action is ready
143-
return run["artifacts_url"]
167+
return run.artifacts_url
144168

145169

146170
def get_artifact(owner: str, repo: str, sha: str, action_name: str, artifact_name: str) -> str:
@@ -155,7 +179,8 @@ def get_artifact(owner: str, repo: str, sha: str, action_name: str, artifact_nam
155179

156180
# Filter the runs for the one associated with the given SHA
157181
for run in runs["workflow_runs"]:
158-
if run["name"] == action_name and sha == run["head_sha"]:
182+
run = WorkflowRun.from_raw(run)
183+
if run.name == action_name and sha == run.head_sha:
159184
break
160185
else:
161186
raise NotFoundError(

pydis_site/apps/api/tests/test_github_utils.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import dataclasses
12
import datetime
3+
import typing
24
import unittest
35
from unittest import mock
46

@@ -42,45 +44,46 @@ def encode(payload: dict, _: str, algorithm: str, *args, **kwargs) -> str:
4244
class CheckRunTests(unittest.TestCase):
4345
"""Tests the check_run_status utility."""
4446

47+
run_kwargs: typing.Mapping = {
48+
"name": "run_name",
49+
"head_sha": "sha",
50+
"status": "completed",
51+
"conclusion": "success",
52+
"created_at": datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
53+
"artifacts_url": "url",
54+
}
55+
4556
def test_completed_run(self):
4657
"""Test that an already completed run returns the correct URL."""
4758
final_url = "some_url_string_1234"
4859

49-
result = github_utils.check_run_status({
50-
"status": "completed",
51-
"conclusion": "success",
52-
"created_at": datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
53-
"artifacts_url": final_url,
54-
})
60+
kwargs = dict(self.run_kwargs, artifacts_url=final_url)
61+
result = github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
5562
self.assertEqual(final_url, result)
5663

5764
def test_pending_run(self):
5865
"""Test that a pending run raises the proper exception."""
66+
kwargs = dict(self.run_kwargs, status="pending")
5967
with self.assertRaises(github_utils.RunPendingError):
60-
github_utils.check_run_status({
61-
"status": "pending",
62-
"created_at": datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
63-
})
68+
github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
6469

6570
def test_timeout_error(self):
6671
"""Test that a timeout is declared after a certain duration."""
72+
kwargs = dict(self.run_kwargs, status="pending")
6773
# Set the creation time to well before the MAX_RUN_TIME
6874
# to guarantee the right conclusion
69-
created = (
75+
kwargs["created_at"] = (
7076
datetime.datetime.now() - github_utils.MAX_RUN_TIME - datetime.timedelta(minutes=10)
7177
).strftime(github_utils.ISO_FORMAT_STRING)
7278

7379
with self.assertRaises(github_utils.RunTimeoutError):
74-
github_utils.check_run_status({"status": "pending", "created_at": created})
80+
github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
7581

7682
def test_failed_run(self):
7783
"""Test that a failed run raises the proper exception."""
84+
kwargs = dict(self.run_kwargs, conclusion="failed")
7885
with self.assertRaises(github_utils.ActionFailedError):
79-
github_utils.check_run_status({
80-
"status": "completed",
81-
"conclusion": "failed",
82-
"created_at": datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
83-
})
86+
github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
8487

8588

8689
def get_response_authorize(_: httpx.Client, request: httpx.Request, **__) -> httpx.Response:
@@ -172,11 +175,16 @@ def get_response_get_artifact(request: httpx.Request, **_) -> httpx.Response:
172175

173176
if request.method == "GET":
174177
if path == "/repos/owner/repo/actions/runs":
178+
run = github_utils.WorkflowRun(
179+
name="action_name",
180+
head_sha="action_sha",
181+
created_at=datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
182+
status="completed",
183+
conclusion="success",
184+
artifacts_url="artifacts_url"
185+
)
175186
return httpx.Response(
176-
200, request=request, json={"workflow_runs": [{
177-
"name": "action_name",
178-
"head_sha": "action_sha"
179-
}]}
187+
200, request=request, json={"workflow_runs": [dataclasses.asdict(run)]}
180188
)
181189
elif path == "/artifact_url":
182190
return httpx.Response(

0 commit comments

Comments
 (0)