Skip to content

Commit e0b13d0

Browse files
committed
refactor(github): address PR review comments for external_trigger and generic dispatch args
1 parent 8120b0f commit e0b13d0

2 files changed

Lines changed: 99 additions & 26 deletions

File tree

xlml/utils/github.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Utilities for GitHub API integration."""
1616

17+
from typing import Any
1718
import requests
1819
from airflow.decorators import task
1920
from airflow.exceptions import AirflowFailException
@@ -22,60 +23,80 @@
2223

2324
@task
2425
def validate_git_trigger() -> None:
25-
"""Validates that the DAG run has required GitHub parameters.
26-
27-
Prevents manual runs from the Airflow UI by ensuring github_run_id,
28-
github_repo, and github_token are present.
29-
"""
26+
"""Validates that the DAG run has required GitHub parameters and was externally triggered."""
3027
context = get_current_context()
28+
dag_run = context.get("dag_run")
29+
30+
if not dag_run or not dag_run.external_trigger:
31+
raise AirflowFailException(
32+
"This DAG should not be run manually from the Airflow UI."
33+
)
34+
3135
params = context.get("params", {})
3236
run_id = params.get("github_run_id")
3337
repo = params.get("github_repo")
3438
token = params.get("github_token") or params.get("github_callback_token")
3539

3640
if not run_id or not repo or not token:
3741
raise AirflowFailException(
38-
"Missing required GitHub parameters (run_id, repo, token). "
39-
"This DAG should not be run manually from the Airflow UI."
42+
"Missing required GitHub parameters (run_id, repo, token)."
4043
)
4144

4245

4346
@task
44-
def fire_github_callback(test_type: str | None = None) -> None:
47+
def fire_github_callback(
48+
test_type: str | None = None,
49+
repo: str | None = None,
50+
token: str | None = None,
51+
event_type: str = "airflow-dag-complete",
52+
client_payload: dict[str, Any] | None = None,
53+
) -> None:
4554
"""Fires a GitHub repository_dispatch callback with the DAG run result."""
4655
context = get_current_context()
4756
params = context.get("params", {})
4857
dag_run = context.get("dag_run")
4958

50-
repo = params.get("github_repo")
51-
token = params.get("github_token") or params.get("github_callback_token")
59+
target_repo = repo or params.get("github_repo")
60+
target_token = (
61+
token or params.get("github_token") or params.get("github_callback_token")
62+
)
5263

53-
if not repo or not token:
64+
if not target_repo or not target_token:
5465
raise AirflowFailException(
5566
"Missing required GitHub parameters (repo, token) to fire callback."
5667
)
5768

58-
client_payload = {
59-
"state": "success",
60-
"dag_id": dag_run.dag_id if dag_run else None,
61-
"dag_run_id": dag_run.run_id if dag_run else None,
62-
"sha": params.get("commit_sha") or params.get("sha"),
63-
"github_run_id": params.get("github_run_id"),
64-
}
65-
if test_type:
66-
client_payload["test_type"] = test_type
69+
if client_payload is None:
70+
payload: dict[str, Any] = {
71+
"state": "success",
72+
"dag_id": dag_run.dag_id if dag_run else None,
73+
"dag_run_id": dag_run.run_id if dag_run else None,
74+
"sha": params.get("commit_sha")
75+
or params.get("maxtext_sha")
76+
or params.get("sha"),
77+
"github_run_id": params.get("github_run_id"),
78+
}
79+
if test_type:
80+
payload["test_type"] = test_type
81+
else:
82+
payload = dict(client_payload)
83+
if test_type and "test_type" not in payload:
84+
payload["test_type"] = test_type
6785

6886
response = requests.post(
69-
f"https://api.github.com/repos/{repo}/dispatches",
87+
f"https://api.github.com/repos/{target_repo}/dispatches",
7088
headers={
71-
"Authorization": f"Bearer {token}",
89+
"Authorization": f"Bearer {target_token}",
7290
"Accept": "application/vnd.github+json",
7391
"X-GitHub-Api-Version": "2022-11-28",
7492
},
7593
json={
76-
"event_type": "airflow-dag-complete",
77-
"client_payload": client_payload,
94+
"event_type": event_type,
95+
"client_payload": payload,
7896
},
7997
timeout=30,
8098
)
8199
response.raise_for_status()
100+
101+
102+
trigger_github_repository_dispatch = fire_github_callback

xlml/utils/github_test.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,44 @@ class GithubTest(unittest.TestCase):
2424

2525
@mock.patch("xlml.utils.github.get_current_context")
2626
def test_validate_git_trigger_success(self, mock_context):
27+
mock_dag_run = mock.MagicMock()
28+
mock_dag_run.external_trigger = True
2729
mock_context.return_value = {
30+
"dag_run": mock_dag_run,
2831
"params": {
2932
"github_run_id": "12345",
3033
"github_repo": "owner/repo",
3134
"github_token": "secret_token",
32-
}
35+
},
3336
}
3437
# Should not raise exception
3538
github.validate_git_trigger.function()
3639

40+
@mock.patch("xlml.utils.github.get_current_context")
41+
def test_validate_git_trigger_manual_run(self, mock_context):
42+
mock_dag_run = mock.MagicMock()
43+
mock_dag_run.external_trigger = False
44+
mock_context.return_value = {
45+
"dag_run": mock_dag_run,
46+
"params": {
47+
"github_run_id": "12345",
48+
"github_repo": "owner/repo",
49+
"github_token": "secret_token",
50+
},
51+
}
52+
with self.assertRaises(AirflowFailException):
53+
github.validate_git_trigger.function()
54+
3755
@mock.patch("xlml.utils.github.get_current_context")
3856
def test_validate_git_trigger_missing_params(self, mock_context):
57+
mock_dag_run = mock.MagicMock()
58+
mock_dag_run.external_trigger = True
3959
mock_context.return_value = {
60+
"dag_run": mock_dag_run,
4061
"params": {
4162
"github_run_id": "12345",
4263
# missing repo and token
43-
}
64+
},
4465
}
4566
with self.assertRaises(AirflowFailException):
4667
github.validate_git_trigger.function()
@@ -89,6 +110,37 @@ def test_fire_github_callback_success(self, mock_context, mock_post):
89110
)
90111
mock_response.raise_for_status.assert_called_once()
91112

113+
@mock.patch("requests.post")
114+
@mock.patch("xlml.utils.github.get_current_context")
115+
def test_trigger_github_repository_dispatch_explicit_args(
116+
self, mock_context, mock_post
117+
):
118+
mock_context.return_value = {}
119+
mock_response = mock.MagicMock()
120+
mock_post.return_value = mock_response
121+
122+
github.trigger_github_repository_dispatch.function(
123+
repo="custom/repo",
124+
token="custom_token",
125+
event_type="custom-event",
126+
client_payload={"foo": "bar"},
127+
)
128+
129+
mock_post.assert_called_once_with(
130+
"https://api.github.com/repos/custom/repo/dispatches",
131+
headers={
132+
"Authorization": "Bearer custom_token",
133+
"Accept": "application/vnd.github+json",
134+
"X-GitHub-Api-Version": "2022-11-28",
135+
},
136+
json={
137+
"event_type": "custom-event",
138+
"client_payload": {"foo": "bar"},
139+
},
140+
timeout=30,
141+
)
142+
mock_response.raise_for_status.assert_called_once()
143+
92144
@mock.patch("xlml.utils.github.get_current_context")
93145
def test_fire_github_callback_missing_token(self, mock_context):
94146
mock_context.return_value = {

0 commit comments

Comments
 (0)