ultralytics-actions 0.0.27__py3-none-any.whl → 0.0.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
actions/__init__.py CHANGED
@@ -22,4 +22,4 @@
22
22
  # ├── test_summarize_pr.py
23
23
  # └── ...
24
24
 
25
- __version__ = "0.0.27"
25
+ __version__ = "0.0.34"
@@ -6,15 +6,9 @@ from typing import Dict, List, Tuple
6
6
  import requests
7
7
 
8
8
  from .utils import (
9
- EVENT_DATA,
10
9
  GITHUB_API_URL,
11
- GITHUB_EVENT_NAME,
12
- GITHUB_HEADERS,
13
- GITHUB_REPOSITORY,
10
+ Action,
14
11
  get_completion,
15
- get_github_data,
16
- get_pr_diff,
17
- graphql_request,
18
12
  remove_html_comments,
19
13
  )
20
14
 
@@ -22,21 +16,23 @@ from .utils import (
22
16
  BLOCK_USER = os.getenv("BLOCK_USER", "false").lower() == "true"
23
17
 
24
18
 
25
- def get_event_content() -> Tuple[int, str, str, str, str, str, str]:
19
+ def get_event_content(event) -> Tuple[int, str, str, str, str, str, str]:
26
20
  """Extracts key information from GitHub event data for issues, pull requests, or discussions."""
27
- action = EVENT_DATA["action"] # 'opened', 'closed', 'created' (discussion), etc.
28
- if GITHUB_EVENT_NAME == "issues":
29
- item = EVENT_DATA["issue"]
21
+ data = event.event_data
22
+ name = event.event_name
23
+ action = data["action"] # 'opened', 'closed', 'created' (discussion), etc.
24
+ if name == "issues":
25
+ item = data["issue"]
30
26
  issue_type = "issue"
31
- elif GITHUB_EVENT_NAME in ["pull_request", "pull_request_target"]:
32
- pr_number = EVENT_DATA["pull_request"]["number"]
33
- item = get_github_data(f"pulls/{pr_number}")
27
+ elif name in ["pull_request", "pull_request_target"]:
28
+ pr_number = data["pull_request"]["number"]
29
+ item = event.get_repo_data(f"pulls/{pr_number}")
34
30
  issue_type = "pull request"
35
- elif GITHUB_EVENT_NAME == "discussion":
36
- item = EVENT_DATA["discussion"]
31
+ elif name == "discussion":
32
+ item = data["discussion"]
37
33
  issue_type = "discussion"
38
34
  else:
39
- raise ValueError(f"Unsupported event type: {GITHUB_EVENT_NAME}")
35
+ raise ValueError(f"Unsupported event type: {name}")
40
36
 
41
37
  number = item["number"]
42
38
  node_id = item.get("node_id") or item.get("id")
@@ -46,7 +42,7 @@ def get_event_content() -> Tuple[int, str, str, str, str, str, str]:
46
42
  return number, node_id, title, body, username, issue_type, action
47
43
 
48
44
 
49
- def update_issue_pr_content(number: int, node_id: str, issue_type: str):
45
+ def update_issue_pr_content(event, number: int, node_id: str, issue_type: str):
50
46
  """Updates the title and body of an issue, pull request, or discussion with predefined content."""
51
47
  new_title = "Content Under Review"
52
48
  new_body = """This post has been flagged for review by [Ultralytics Actions](https://ultralytics.com/actions) due to possible spam, abuse, or off-topic content. For more information please see our:
@@ -68,14 +64,14 @@ mutation($discussionId: ID!, $title: String!, $body: String!) {
68
64
  }
69
65
  }
70
66
  """
71
- graphql_request(mutation, variables={"discussionId": node_id, "title": new_title, "body": new_body})
67
+ event.graphql_request(mutation, variables={"discussionId": node_id, "title": new_title, "body": new_body})
72
68
  else:
73
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{number}"
74
- r = requests.patch(url, json={"title": new_title, "body": new_body}, headers=GITHUB_HEADERS)
69
+ url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}"
70
+ r = requests.patch(url, json={"title": new_title, "body": new_body}, headers=event.headers)
75
71
  print(f"{'Successful' if r.status_code == 200 else 'Fail'} issue/PR #{number} update: {r.status_code}")
76
72
 
77
73
 
78
- def close_issue_pr(number: int, node_id: str, issue_type: str):
74
+ def close_issue_pr(event, number: int, node_id: str, issue_type: str):
79
75
  """Closes the specified issue, pull request, or discussion using the GitHub API."""
80
76
  if issue_type == "discussion":
81
77
  mutation = """
@@ -87,14 +83,14 @@ mutation($discussionId: ID!) {
87
83
  }
88
84
  }
89
85
  """
90
- graphql_request(mutation, variables={"discussionId": node_id})
86
+ event.graphql_request(mutation, variables={"discussionId": node_id})
91
87
  else:
92
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{number}"
93
- r = requests.patch(url, json={"state": "closed"}, headers=GITHUB_HEADERS)
88
+ url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}"
89
+ r = requests.patch(url, json={"state": "closed"}, headers=event.headers)
94
90
  print(f"{'Successful' if r.status_code == 200 else 'Fail'} issue/PR #{number} close: {r.status_code}")
95
91
 
96
92
 
97
- def lock_issue_pr(number: int, node_id: str, issue_type: str):
93
+ def lock_issue_pr(event, number: int, node_id: str, issue_type: str):
98
94
  """Locks an issue, pull request, or discussion to prevent further interactions."""
99
95
  if issue_type == "discussion":
100
96
  mutation = """
@@ -108,17 +104,17 @@ mutation($lockableId: ID!, $lockReason: LockReason) {
108
104
  }
109
105
  }
110
106
  """
111
- graphql_request(mutation, variables={"lockableId": node_id, "lockReason": "OFF_TOPIC"})
107
+ event.graphql_request(mutation, variables={"lockableId": node_id, "lockReason": "OFF_TOPIC"})
112
108
  else:
113
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{number}/lock"
114
- r = requests.put(url, json={"lock_reason": "off-topic"}, headers=GITHUB_HEADERS)
109
+ url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}/lock"
110
+ r = requests.put(url, json={"lock_reason": "off-topic"}, headers=event.headers)
115
111
  print(f"{'Successful' if r.status_code in {200, 204} else 'Fail'} issue/PR #{number} lock: {r.status_code}")
116
112
 
117
113
 
118
- def block_user(username: str):
114
+ def block_user(event, username: str):
119
115
  """Blocks a user from the organization using the GitHub API."""
120
- url = f"{GITHUB_API_URL}/orgs/{GITHUB_REPOSITORY.split('/')[0]}/blocks/{username}"
121
- r = requests.put(url, headers=GITHUB_HEADERS)
116
+ url = f"{GITHUB_API_URL}/orgs/{event.repository.split('/')[0]}/blocks/{username}"
117
+ r = requests.put(url, headers=event.headers)
122
118
  print(f"{'Successful' if r.status_code == 204 else 'Fail'} user block for {username}: {r.status_code}")
123
119
 
124
120
 
@@ -167,7 +163,7 @@ YOUR RESPONSE (label names only):
167
163
  messages = [
168
164
  {
169
165
  "role": "system",
170
- "content": "You are a helpful assistant that labels GitHub issues, pull requests, and discussions.",
166
+ "content": "You are an Ultralytics AI assistant that labels GitHub issues, PRs, and discussions.",
171
167
  },
172
168
  {"role": "user", "content": prompt},
173
169
  ]
@@ -183,7 +179,7 @@ YOUR RESPONSE (label names only):
183
179
  ]
184
180
 
185
181
 
186
- def get_label_ids(labels: List[str]) -> List[str]:
182
+ def get_label_ids(event, labels: List[str]) -> List[str]:
187
183
  """Retrieves GitHub label IDs for a list of label names using the GraphQL API."""
188
184
  query = """
189
185
  query($owner: String!, $name: String!) {
@@ -197,8 +193,8 @@ query($owner: String!, $name: String!) {
197
193
  }
198
194
  }
199
195
  """
200
- owner, repo = GITHUB_REPOSITORY.split("/")
201
- result = graphql_request(query, variables={"owner": owner, "name": repo})
196
+ owner, repo = event.repository.split("/")
197
+ result = event.graphql_request(query, variables={"owner": owner, "name": repo})
202
198
  if "data" in result and "repository" in result["data"]:
203
199
  all_labels = result["data"]["repository"]["labels"]["nodes"]
204
200
  label_map = {label["name"].lower(): label["id"] for label in all_labels}
@@ -208,14 +204,14 @@ query($owner: String!, $name: String!) {
208
204
  return []
209
205
 
210
206
 
211
- def apply_labels(number: int, node_id: str, labels: List[str], issue_type: str):
207
+ def apply_labels(event, number: int, node_id: str, labels: List[str], issue_type: str):
212
208
  """Applies specified labels to a GitHub issue, pull request, or discussion using the appropriate API."""
213
209
  if "Alert" in labels:
214
- create_alert_label()
210
+ create_alert_label(event)
215
211
 
216
212
  if issue_type == "discussion":
217
213
  print(f"Using node_id: {node_id}") # Debug print
218
- label_ids = get_label_ids(labels)
214
+ label_ids = get_label_ids(event, labels)
219
215
  if not label_ids:
220
216
  print("No valid labels to apply.")
221
217
  return
@@ -231,29 +227,29 @@ mutation($labelableId: ID!, $labelIds: [ID!]!) {
231
227
  }
232
228
  }
233
229
  """
234
- graphql_request(mutation, {"labelableId": node_id, "labelIds": label_ids})
230
+ event.graphql_request(mutation, {"labelableId": node_id, "labelIds": label_ids})
235
231
  print(f"Successfully applied labels: {', '.join(labels)}")
236
232
  else:
237
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{number}/labels"
238
- r = requests.post(url, json={"labels": labels}, headers=GITHUB_HEADERS)
233
+ url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}/labels"
234
+ r = requests.post(url, json={"labels": labels}, headers=event.headers)
239
235
  print(f"{'Successful' if r.status_code == 200 else 'Fail'} apply labels {', '.join(labels)}: {r.status_code}")
240
236
 
241
237
 
242
- def create_alert_label():
238
+ def create_alert_label(event):
243
239
  """Creates the 'Alert' label in the repository if it doesn't exist, with a red color and description."""
244
240
  alert_label = {"name": "Alert", "color": "FF0000", "description": "Potential spam, abuse, or off-topic."}
245
- requests.post(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/labels", json=alert_label, headers=GITHUB_HEADERS)
241
+ requests.post(f"{GITHUB_API_URL}/repos/{event.repository}/labels", json=alert_label, headers=event.headers)
246
242
 
247
243
 
248
- def is_org_member(username: str) -> bool:
244
+ def is_org_member(event, username: str) -> bool:
249
245
  """Checks if a user is a member of the organization using the GitHub API."""
250
- org_name = GITHUB_REPOSITORY.split("/")[0]
246
+ org_name = event.repository.split("/")[0]
251
247
  url = f"{GITHUB_API_URL}/orgs/{org_name}/members/{username}"
252
- r = requests.get(url, headers=GITHUB_HEADERS)
248
+ r = requests.get(url, headers=event.headers)
253
249
  return r.status_code == 204 # 204 means the user is a member
254
250
 
255
251
 
256
- def add_comment(number: int, node_id: str, comment: str, issue_type: str):
252
+ def add_comment(event, number: int, node_id: str, comment: str, issue_type: str):
257
253
  """Adds a comment to the specified issue, pull request, or discussion using the GitHub API."""
258
254
  if issue_type == "discussion":
259
255
  mutation = """
@@ -265,17 +261,17 @@ mutation($discussionId: ID!, $body: String!) {
265
261
  }
266
262
  }
267
263
  """
268
- graphql_request(mutation, variables={"discussionId": node_id, "body": comment})
264
+ event.graphql_request(mutation, variables={"discussionId": node_id, "body": comment})
269
265
  else:
270
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{number}/comments"
271
- r = requests.post(url, json={"body": comment}, headers=GITHUB_HEADERS)
266
+ url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}/comments"
267
+ r = requests.post(url, json={"body": comment}, headers=event.headers)
272
268
  print(f"{'Successful' if r.status_code in {200, 201} else 'Fail'} issue/PR #{number} comment: {r.status_code}")
273
269
 
274
270
 
275
- def get_first_interaction_response(issue_type: str, title: str, body: str, username: str, number: int) -> str:
271
+ def get_first_interaction_response(event, issue_type: str, title: str, body: str, username: str) -> str:
276
272
  """Generates a custom LLM response for GitHub issues, PRs, or discussions based on content."""
277
273
  issue_discussion_response = f"""
278
- 👋 Hello @{username}, thank you for submitting a `{GITHUB_REPOSITORY}` 🚀 {issue_type.capitalize()}. To help us address your concern efficiently, please ensure you've provided the following information:
274
+ 👋 Hello @{username}, thank you for submitting a `{event.repository}` 🚀 {issue_type.capitalize()}. To help us address your concern efficiently, please ensure you've provided the following information:
279
275
 
280
276
  1. For bug reports:
281
277
  - A clear and concise description of the bug
@@ -300,10 +296,10 @@ Thank you for your contribution to improving our project!
300
296
  """
301
297
 
302
298
  pr_response = f"""
303
- 👋 Hello @{username}, thank you for submitting an `{GITHUB_REPOSITORY}` 🚀 PR! To ensure a seamless integration of your work, please review the following checklist:
299
+ 👋 Hello @{username}, thank you for submitting an `{event.repository}` 🚀 PR! To ensure a seamless integration of your work, please review the following checklist:
304
300
 
305
- - ✅ **Define a Purpose**: Clearly explain the purpose of your fix or feature in your PR description, and link to any [relevant issues](https://github.com/{GITHUB_REPOSITORY}/issues). Ensure your commit messages are clear, concise, and adhere to the project's conventions.
306
- - ✅ **Synchronize with Source**: Confirm your PR is synchronized with the `{GITHUB_REPOSITORY}` `main` branch. If it's behind, update it by clicking the 'Update branch' button or by running `git pull` and `git merge main` locally.
301
+ - ✅ **Define a Purpose**: Clearly explain the purpose of your fix or feature in your PR description, and link to any [relevant issues](https://github.com/{event.repository}/issues). Ensure your commit messages are clear, concise, and adhere to the project's conventions.
302
+ - ✅ **Synchronize with Source**: Confirm your PR is synchronized with the `{event.repository}` `main` branch. If it's behind, update it by clicking the 'Update branch' button or by running `git pull` and `git merge main` locally.
307
303
  - ✅ **Ensure CI Checks Pass**: Verify all Ultralytics [Continuous Integration (CI)](https://docs.ultralytics.com/help/CI/) checks are passing. If any checks fail, please address the issues.
308
304
  - ✅ **Update Documentation**: Update the relevant [documentation](https://docs.ultralytics.com) for any new or modified features.
309
305
  - ✅ **Add Tests**: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
@@ -318,9 +314,9 @@ For more guidance, please refer to our [Contributing Guide](https://docs.ultraly
318
314
  else:
319
315
  example = os.getenv("FIRST_ISSUE_RESPONSE") or issue_discussion_response
320
316
 
321
- org_name, repo_name = GITHUB_REPOSITORY.split("/")
322
- repo_url = f"https://github.com/{GITHUB_REPOSITORY}"
323
- diff = get_pr_diff(number)[:32000] if issue_type == "pull request" else ""
317
+ org_name, repo_name = event.repository.split("/")
318
+ repo_url = f"https://github.com/{event.repository}"
319
+ diff = event.get_pr_diff()[:32000] if issue_type == "pull request" else ""
324
320
 
325
321
  prompt = f"""Generate a customized response to the new GitHub {issue_type} below:
326
322
 
@@ -359,39 +355,40 @@ YOUR {issue_type.upper()} RESPONSE:
359
355
  messages = [
360
356
  {
361
357
  "role": "system",
362
- "content": f"You are a helpful assistant responding to GitHub {issue_type}s for {org_name}.",
358
+ "content": f"You are an Ultralytics AI assistant responding to GitHub {issue_type}s for {org_name}.",
363
359
  },
364
360
  {"role": "user", "content": prompt},
365
361
  ]
366
362
  return get_completion(messages)
367
363
 
368
364
 
369
- def main():
370
- """Executes autolabeling and custom response generation for new GitHub issues, PRs, and discussions."""
371
- number, node_id, title, body, username, issue_type, action = get_event_content()
372
- available_labels = get_github_data("labels")
365
+ def main(*args, **kwargs):
366
+ """Executes auto-labeling and custom response generation for new GitHub issues, PRs, and discussions."""
367
+ event = Action(*args, **kwargs)
368
+ number, node_id, title, body, username, issue_type, action = get_event_content(event)
369
+ available_labels = event.get_repo_data("labels")
373
370
  label_descriptions = {label["name"]: label.get("description", "") for label in available_labels}
374
371
  if issue_type == "discussion":
375
372
  current_labels = [] # For discussions, labels may need to be fetched differently or adjusted
376
373
  else:
377
- current_labels = [label["name"].lower() for label in get_github_data(f"issues/{number}/labels")]
374
+ current_labels = [label["name"].lower() for label in event.get_repo_data(f"issues/{number}/labels")]
378
375
  relevant_labels = get_relevant_labels(issue_type, title, body, label_descriptions, current_labels)
379
376
 
380
377
  if relevant_labels:
381
- apply_labels(number, node_id, relevant_labels, issue_type)
382
- if "Alert" in relevant_labels and not is_org_member(username):
383
- update_issue_pr_content(number, node_id, issue_type)
378
+ apply_labels(event, number, node_id, relevant_labels, issue_type)
379
+ if "Alert" in relevant_labels and not is_org_member(event, username):
380
+ update_issue_pr_content(event, number, node_id, issue_type)
384
381
  if issue_type != "pull request":
385
- close_issue_pr(number, node_id, issue_type)
386
- lock_issue_pr(number, node_id, issue_type)
382
+ close_issue_pr(event, number, node_id, issue_type)
383
+ lock_issue_pr(event, number, node_id, issue_type)
387
384
  if BLOCK_USER:
388
- block_user(username=username)
385
+ block_user(event, username=username)
389
386
  else:
390
387
  print("No relevant labels found or applied.")
391
388
 
392
389
  if action in {"opened", "created"}:
393
- custom_response = get_first_interaction_response(issue_type, title, body, username, number)
394
- add_comment(number, node_id, custom_response, issue_type)
390
+ custom_response = get_first_interaction_response(event, issue_type, title, body, username)
391
+ add_comment(event, number, node_id, custom_response, issue_type)
395
392
 
396
393
 
397
394
  if __name__ == "__main__":
actions/summarize_pr.py CHANGED
@@ -6,11 +6,8 @@ import requests
6
6
 
7
7
  from .utils import (
8
8
  GITHUB_API_URL,
9
- GITHUB_HEADERS,
10
- GITHUB_REPOSITORY,
11
- PR,
9
+ Action,
12
10
  get_completion,
13
- get_pr_diff,
14
11
  )
15
12
 
16
13
  # Constants
@@ -19,29 +16,59 @@ SUMMARY_START = (
19
16
  )
20
17
 
21
18
 
22
- def generate_issue_comment(pr_url, pr_body):
23
- """Generates a personalized issue comment using AI based on the PR context."""
19
+ def generate_merge_message(pr_summary=None, pr_credit=None):
20
+ """Generates a thank-you message for merged PR contributors."""
24
21
  messages = [
25
22
  {
26
23
  "role": "system",
27
- "content": "You are the Ultralytics AI assistant. Generate friendly GitHub issue comments. No @ mentions or direct addressing.",
24
+ "content": "You are an Ultralytics AI assistant. Generate meaningful, inspiring messages to GitHub users.",
28
25
  },
29
26
  {
30
27
  "role": "user",
31
- "content": f"Write a comment for a fixed GitHub issue using this merged PR context:\n\n{pr_body}\n\n"
28
+ "content": f"Write a friendly thank you for a merged GitHub PR by {pr_credit}. "
29
+ f"Context from PR:\n{pr_summary}\n\n"
30
+ f"Start with the exciting message that this PR is now merged, and weave in an inspiring but obscure quote "
31
+ f"from a historical figure in science, art, stoicism and philosophy. "
32
+ f"Keep the message concise yet relevant to the specific contributions in this PR. "
33
+ f"We want the contributors to feel their effort is appreciated and will make a difference in the world.",
34
+ },
35
+ ]
36
+ return get_completion(messages)
37
+
38
+
39
+ def post_merge_message(pr_number, repository, summary, pr_credit, headers):
40
+ """Posts thank you message on PR after merge."""
41
+ message = generate_merge_message(summary, pr_credit)
42
+ comment_url = f"{GITHUB_API_URL}/repos/{repository}/issues/{pr_number}/comments"
43
+ response = requests.post(comment_url, json={"body": message}, headers=headers)
44
+ return response.status_code == 201
45
+
46
+
47
+ def generate_issue_comment(pr_url, pr_summary, pr_credit):
48
+ """Generates a personalized issue comment using based on the PR context."""
49
+ messages = [
50
+ {
51
+ "role": "system",
52
+ "content": "You are an Ultralytics AI assistant. Generate friendly GitHub issue comments. No @ mentions or direct addressing.",
53
+ },
54
+ {
55
+ "role": "user",
56
+ "content": f"Write a GitHub issue comment announcing a potential fix for this issue is now merged in linked PR {pr_url} by {pr_credit}\n\n"
57
+ f"Context from PR:\n{pr_summary}\n\n"
32
58
  f"Include:\n"
33
- f"1. Reference to fix PR: {pr_url}\n"
34
- f"2. Key changes in the PR and instructions to test the fix with:\n"
35
- f" - pip install git+https://github.com/ultralytics/ultralytics.git@main # immediate testing\n"
36
- f" - or await next release\n"
37
- f"3. Request verification that PR fix works\n"
38
- f"4. Thank 🙏 for reporting the issue and encourage reporting any new issues in the future\n\n",
59
+ f"1. An explanation of key changes from the PR that may resolve this issue\n"
60
+ f"2. Credit to the PR author and contributors\n"
61
+ f"3. Options for testing if PR changes have resolved this issue:\n"
62
+ f" - pip install git+https://github.com/ultralytics/ultralytics.git@main # test latest changes\n"
63
+ f" - or await next official PyPI release\n"
64
+ f"4. Request feedback on whether the PR changes resolve the issue\n"
65
+ f"5. Thank 🙏 for reporting the issue and welcome any further feedback if the issue persists\n\n",
39
66
  },
40
67
  ]
41
68
  return get_completion(messages)
42
69
 
43
70
 
44
- def generate_pr_summary(repo_name, diff_text):
71
+ def generate_pr_summary(repository, diff_text):
45
72
  """Generates a concise, professional summary of a PR using OpenAI's API for Ultralytics repositories."""
46
73
  if not diff_text:
47
74
  diff_text = "**ERROR: DIFF IS EMPTY, THERE ARE ZERO CODE CHANGES IN THIS PR."
@@ -54,7 +81,7 @@ def generate_pr_summary(repo_name, diff_text):
54
81
  },
55
82
  {
56
83
  "role": "user",
57
- "content": f"Summarize this '{repo_name}' PR, focusing on major changes, their purpose, and potential impact. Keep the summary clear and concise, suitable for a broad audience. Add emojis to enliven the summary. Reply directly with a summary along these example guidelines, though feel free to adjust as appropriate:\n\n"
84
+ "content": f"Summarize this '{repository}' PR, focusing on major changes, their purpose, and potential impact. Keep the summary clear and concise, suitable for a broad audience. Add emojis to enliven the summary. Reply directly with a summary along these example guidelines, though feel free to adjust as appropriate:\n\n"
58
85
  f"### 🌟 Summary (single-line synopsis)\n"
59
86
  f"### 📊 Key Changes (bullet points highlighting any major changes)\n"
60
87
  f"### 🎯 Purpose & Impact (bullet points explaining any benefits and potential impact to users)\n"
@@ -67,12 +94,12 @@ def generate_pr_summary(repo_name, diff_text):
67
94
  return SUMMARY_START + reply
68
95
 
69
96
 
70
- def update_pr_description(repo_name, pr_number, new_summary, max_retries=2):
97
+ def update_pr_description(repository, pr_number, new_summary, headers, max_retries=2):
71
98
  """Updates PR description with new summary, retrying if description is None."""
72
- pr_url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
99
+ pr_url = f"{GITHUB_API_URL}/repos/{repository}/pulls/{pr_number}"
73
100
  description = ""
74
101
  for i in range(max_retries + 1):
75
- description = requests.get(pr_url, headers=GITHUB_HEADERS).json().get("body") or ""
102
+ description = requests.get(pr_url, headers=headers).json().get("body") or ""
76
103
  if description:
77
104
  break
78
105
  if i < max_retries:
@@ -89,52 +116,76 @@ def update_pr_description(repo_name, pr_number, new_summary, max_retries=2):
89
116
  updated_description = description + "\n\n" + new_summary
90
117
 
91
118
  # Update the PR description
92
- update_response = requests.patch(pr_url, json={"body": updated_description}, headers=GITHUB_HEADERS)
119
+ update_response = requests.patch(pr_url, json={"body": updated_description}, headers=headers)
93
120
  return update_response.status_code
94
121
 
95
122
 
96
- def label_fixed_issues(pr_number):
97
- """Labels issues closed by this PR when merged and notifies users about the fix with AI-generated comments."""
123
+ def label_fixed_issues(repository, pr_number, pr_summary, headers, action):
124
+ """Labels issues closed by PR when merged, notifies users, returns PR contributors."""
98
125
  query = """
99
126
  query($owner: String!, $repo: String!, $pr_number: Int!) {
100
127
  repository(owner: $owner, name: $repo) {
101
128
  pullRequest(number: $pr_number) {
102
- closingIssuesReferences(first: 50) {
103
- nodes {
104
- number
105
- }
106
- }
129
+ closingIssuesReferences(first: 50) { nodes { number } }
107
130
  url
108
131
  body
132
+ author { login, __typename }
133
+ reviews(first: 50) { nodes { author { login, __typename } } }
134
+ comments(first: 50) { nodes { author { login, __typename } } }
135
+ commits(first: 100) { nodes { commit { author { user { login } }, committer { user { login } } } } }
109
136
  }
110
137
  }
111
138
  }
112
139
  """
113
-
114
- owner, repo = GITHUB_REPOSITORY.split("/")
140
+ owner, repo = repository.split("/")
115
141
  variables = {"owner": owner, "repo": repo, "pr_number": pr_number}
116
142
  graphql_url = "https://api.github.com/graphql"
117
- response = requests.post(graphql_url, json={"query": query, "variables": variables}, headers=GITHUB_HEADERS)
143
+ response = requests.post(graphql_url, json={"query": query, "variables": variables}, headers=headers)
144
+
118
145
  if response.status_code != 200:
119
146
  print(f"Failed to fetch linked issues. Status code: {response.status_code}")
120
- return
147
+ return [], None
121
148
 
122
149
  try:
123
150
  data = response.json()["data"]["repository"]["pullRequest"]
124
- issues = data["closingIssuesReferences"]["nodes"]
151
+ comments = data["reviews"]["nodes"] + data["comments"]["nodes"]
152
+ token_username = action.get_username() # get GITHUB_TOKEN username
153
+ author = data["author"]["login"] if data["author"]["__typename"] != "Bot" else None
154
+
155
+ # Get unique contributors from reviews and comments
156
+ contributors = {x["author"]["login"] for x in comments if x["author"]["__typename"] != "Bot"}
157
+
158
+ # Add commit authors and committers that have GitHub accounts linked
159
+ for commit in data["commits"]["nodes"]:
160
+ commit_data = commit["commit"]
161
+ for user_type in ["author", "committer"]:
162
+ if user := commit_data[user_type].get("user"):
163
+ if login := user.get("login"):
164
+ contributors.add(login)
165
+
166
+ contributors.discard(author)
167
+ contributors.discard(token_username)
168
+
169
+ # Write credit string
170
+ pr_credit = "" # i.e. "@user1 with contributions from @user2, @user3"
171
+ if author and author != token_username:
172
+ pr_credit += f"@{author}"
173
+ if contributors:
174
+ pr_credit += (" with contributions from " if pr_credit else "") + ", ".join(f"@{c}" for c in contributors)
125
175
 
126
176
  # Generate personalized comment
127
- comment = generate_issue_comment(pr_url=data["url"], pr_body=data["body"])
177
+ comment = generate_issue_comment(pr_url=data["url"], pr_summary=pr_summary, pr_credit=pr_credit)
128
178
 
129
- for issue in issues:
179
+ # Update linked issues
180
+ for issue in data["closingIssuesReferences"]["nodes"]:
130
181
  issue_number = issue["number"]
131
182
  # Add fixed label
132
- label_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/labels"
133
- label_response = requests.post(label_url, json={"labels": ["fixed"]}, headers=GITHUB_HEADERS)
183
+ label_url = f"{GITHUB_API_URL}/repos/{repository}/issues/{issue_number}/labels"
184
+ label_response = requests.post(label_url, json={"labels": ["fixed"]}, headers=headers)
134
185
 
135
- # Add AI-generated comment
136
- comment_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/comments"
137
- comment_response = requests.post(comment_url, json={"body": comment}, headers=GITHUB_HEADERS)
186
+ # Add comment
187
+ comment_url = f"{GITHUB_API_URL}/repos/{repository}/issues/{issue_number}/comments"
188
+ comment_response = requests.post(comment_url, json={"body": comment}, headers=headers)
138
189
 
139
190
  if label_response.status_code == 200 and comment_response.status_code == 201:
140
191
  print(f"Added 'fixed' label and comment to issue #{issue_number}")
@@ -143,44 +194,50 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
143
194
  f"Failed to update issue #{issue_number}. Label status: {label_response.status_code}, "
144
195
  f"Comment status: {comment_response.status_code}"
145
196
  )
197
+
198
+ return pr_credit
146
199
  except KeyError as e:
147
200
  print(f"Error parsing GraphQL response: {e}")
148
- return
201
+ return [], None
149
202
 
150
203
 
151
- def remove_todos_on_merge(pr_number):
204
+ def remove_todos_on_merge(pr_number, repository, headers):
152
205
  """Removes specified labels from PR."""
153
206
  for label in ["TODO"]: # Can be extended with more labels in the future
154
- requests.delete(
155
- f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{pr_number}/labels/{label}", headers=GITHUB_HEADERS
156
- )
207
+ requests.delete(f"{GITHUB_API_URL}/repos/{repository}/issues/{pr_number}/labels/{label}", headers=headers)
157
208
 
158
209
 
159
- def main():
160
- """Summarize a pull request and update its description with an AI-generated summary."""
161
- pr_number = PR["number"]
210
+ def main(*args, **kwargs):
211
+ """Summarize a pull request and update its description with a summary."""
212
+ action = Action(*args, **kwargs)
213
+ pr_number = action.pr["number"]
214
+ headers = action.headers
215
+ repository = action.repository
162
216
 
163
217
  print(f"Retrieving diff for PR {pr_number}")
164
- diff = get_pr_diff(pr_number)
218
+ diff = action.get_pr_diff()
165
219
 
166
220
  # Generate PR summary
167
221
  print("Generating PR summary...")
168
- summary = generate_pr_summary(GITHUB_REPOSITORY, diff)
222
+ summary = generate_pr_summary(repository, diff)
169
223
 
170
224
  # Update PR description
171
225
  print("Updating PR description...")
172
- status_code = update_pr_description(GITHUB_REPOSITORY, pr_number, summary)
226
+ status_code = update_pr_description(repository, pr_number, summary, headers)
173
227
  if status_code == 200:
174
228
  print("PR description updated successfully.")
175
229
  else:
176
230
  print(f"Failed to update PR description. Status code: {status_code}")
177
231
 
178
- # Update linked issues
179
- if PR.get("merged"):
232
+ # Update linked issues and post thank you message if merged
233
+ if action.pr.get("merged"):
180
234
  print("PR is merged, labeling fixed issues...")
181
- label_fixed_issues(pr_number)
235
+ pr_credit = label_fixed_issues(repository, pr_number, summary, headers, action)
182
236
  print("Removing TODO label from PR...")
183
- remove_todos_on_merge(pr_number)
237
+ remove_todos_on_merge(pr_number, repository, headers)
238
+ if pr_credit:
239
+ print("Posting PR author thank you message...")
240
+ post_merge_message(pr_number, repository, summary, pr_credit, headers)
184
241
 
185
242
 
186
243
  if __name__ == "__main__":
@@ -10,10 +10,7 @@ import requests
10
10
 
11
11
  from .utils import (
12
12
  GITHUB_API_URL,
13
- GITHUB_HEADERS,
14
- GITHUB_HEADERS_DIFF,
15
- GITHUB_REPOSITORY,
16
- GITHUB_TOKEN,
13
+ Action,
17
14
  get_completion,
18
15
  remove_html_comments,
19
16
  )
@@ -23,17 +20,17 @@ CURRENT_TAG = os.getenv("CURRENT_TAG")
23
20
  PREVIOUS_TAG = os.getenv("PREVIOUS_TAG")
24
21
 
25
22
 
26
- def get_release_diff(repo_name: str, previous_tag: str, latest_tag: str) -> str:
23
+ def get_release_diff(repo_name: str, previous_tag: str, latest_tag: str, headers: dict) -> str:
27
24
  """Retrieves the differences between two specified Git tags in a GitHub repository."""
28
25
  url = f"{GITHUB_API_URL}/repos/{repo_name}/compare/{previous_tag}...{latest_tag}"
29
- r = requests.get(url, headers=GITHUB_HEADERS_DIFF)
26
+ r = requests.get(url, headers=headers)
30
27
  return r.text if r.status_code == 200 else f"Failed to get diff: {r.content}"
31
28
 
32
29
 
33
- def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str) -> list:
30
+ def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str, headers: dict) -> list:
34
31
  """Retrieves and processes pull requests merged between two specified tags in a GitHub repository."""
35
32
  url = f"{GITHUB_API_URL}/repos/{repo_name}/compare/{previous_tag}...{latest_tag}"
36
- r = requests.get(url, headers=GITHUB_HEADERS)
33
+ r = requests.get(url, headers=headers)
37
34
  r.raise_for_status()
38
35
 
39
36
  data = r.json()
@@ -47,7 +44,7 @@ def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str) ->
47
44
  time.sleep(10) # sleep 10 seconds to allow final PR summary to update on merge
48
45
  for pr_number in sorted(pr_numbers): # earliest to latest
49
46
  pr_url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
50
- pr_response = requests.get(pr_url, headers=GITHUB_HEADERS)
47
+ pr_response = requests.get(pr_url, headers=headers)
51
48
  if pr_response.status_code == 200:
52
49
  pr_data = pr_response.json()
53
50
  prs.append(
@@ -67,14 +64,14 @@ def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str) ->
67
64
  return prs
68
65
 
69
66
 
70
- def get_new_contributors(repo: str, prs: list) -> set:
67
+ def get_new_contributors(repo: str, prs: list, headers: dict) -> set:
71
68
  """Identify new contributors who made their first merged PR in the current release."""
72
69
  new_contributors = set()
73
70
  for pr in prs:
74
71
  author = pr["author"]
75
72
  # Check if this is the author's first contribution
76
73
  url = f"{GITHUB_API_URL}/search/issues?q=repo:{repo}+author:{author}+is:pr+is:merged&sort=created&order=asc"
77
- r = requests.get(url, headers=GITHUB_HEADERS)
74
+ r = requests.get(url, headers=headers)
78
75
  if r.status_code == 200:
79
76
  data = r.json()
80
77
  if data["total_count"] > 0:
@@ -84,7 +81,9 @@ def get_new_contributors(repo: str, prs: list) -> set:
84
81
  return new_contributors
85
82
 
86
83
 
87
- def generate_release_summary(diff: str, prs: list, latest_tag: str, previous_tag: str, repo_name: str) -> str:
84
+ def generate_release_summary(
85
+ diff: str, prs: list, latest_tag: str, previous_tag: str, repo_name: str, headers: dict
86
+ ) -> str:
88
87
  """Generate a concise release summary with key changes, purpose, and impact for a new Ultralytics version."""
89
88
  pr_summaries = "\n\n".join(
90
89
  [f"PR #{pr['number']}: {pr['title']} by @{pr['author']}\n{pr['body'][:1000]}" for pr in prs]
@@ -100,7 +99,7 @@ def generate_release_summary(diff: str, prs: list, latest_tag: str, previous_tag
100
99
  whats_changed = "\n".join([f"* {pr['title']} by @{pr['author']} in {pr['html_url']}" for pr in prs])
101
100
 
102
101
  # Generate New Contributors section
103
- new_contributors = get_new_contributors(repo_name, prs)
102
+ new_contributors = get_new_contributors(repo_name, prs, headers)
104
103
  new_contributors_section = (
105
104
  "\n## New Contributors\n"
106
105
  + "\n".join(
@@ -138,11 +137,11 @@ def generate_release_summary(diff: str, prs: list, latest_tag: str, previous_tag
138
137
  return get_completion(messages) + release_suffix
139
138
 
140
139
 
141
- def create_github_release(repo_name: str, tag_name: str, name: str, body: str) -> int:
140
+ def create_github_release(repo_name: str, tag_name: str, name: str, body: str, headers: dict) -> int:
142
141
  """Creates a GitHub release with specified tag, name, and body content for the given repository."""
143
142
  url = f"{GITHUB_API_URL}/repos/{repo_name}/releases"
144
143
  data = {"tag_name": tag_name, "name": name, "body": body, "draft": False, "prerelease": False}
145
- r = requests.post(url, headers=GITHUB_HEADERS, json=data)
144
+ r = requests.post(url, headers=headers, json=data)
146
145
  return r.status_code
147
146
 
148
147
 
@@ -156,22 +155,24 @@ def get_previous_tag() -> str:
156
155
  return "HEAD~1"
157
156
 
158
157
 
159
- def main():
158
+ def main(*args, **kwargs):
160
159
  """Automates generating and publishing a GitHub release summary from PRs and commit differences."""
161
- if not all([GITHUB_TOKEN, CURRENT_TAG]):
160
+ action = Action(*args, **kwargs)
161
+
162
+ if not all([action.token, CURRENT_TAG]):
162
163
  raise ValueError("One or more required environment variables are missing.")
163
164
 
164
165
  previous_tag = PREVIOUS_TAG or get_previous_tag()
165
166
 
166
167
  # Get the diff between the tags
167
- diff = get_release_diff(GITHUB_REPOSITORY, previous_tag, CURRENT_TAG)
168
+ diff = get_release_diff(action.repository, previous_tag, CURRENT_TAG, action.headers_diff)
168
169
 
169
170
  # Get PRs merged between the tags
170
- prs = get_prs_between_tags(GITHUB_REPOSITORY, previous_tag, CURRENT_TAG)
171
+ prs = get_prs_between_tags(action.repository, previous_tag, CURRENT_TAG, action.headers)
171
172
 
172
173
  # Generate release summary
173
174
  try:
174
- summary = generate_release_summary(diff, prs, CURRENT_TAG, previous_tag, GITHUB_REPOSITORY)
175
+ summary = generate_release_summary(diff, prs, CURRENT_TAG, previous_tag, action.repository, action.headers)
175
176
  except Exception as e:
176
177
  print(f"Failed to generate summary: {str(e)}")
177
178
  summary = "Failed to generate summary."
@@ -181,7 +182,8 @@ def main():
181
182
  commit_message = subprocess.run(cmd, check=True, text=True, capture_output=True).stdout.split("\n")[0].strip()
182
183
 
183
184
  # Create the release on GitHub
184
- status_code = create_github_release(GITHUB_REPOSITORY, CURRENT_TAG, f"{CURRENT_TAG} - {commit_message}", summary)
185
+ msg = f"{CURRENT_TAG} - {commit_message}"
186
+ status_code = create_github_release(action.repository, CURRENT_TAG, msg, summary, action.headers)
185
187
  if status_code == 201:
186
188
  print(f"Successfully created release {CURRENT_TAG}")
187
189
  else:
actions/utils/__init__.py CHANGED
@@ -2,42 +2,18 @@
2
2
 
3
3
  from .common_utils import remove_html_comments
4
4
  from .github_utils import (
5
- DISCUSSION,
6
- EVENT_DATA,
7
5
  GITHUB_API_URL,
8
- GITHUB_EVENT_NAME,
9
- GITHUB_EVENT_PATH,
10
- GITHUB_HEADERS,
11
- GITHUB_HEADERS_DIFF,
12
- GITHUB_REPOSITORY,
13
- GITHUB_TOKEN,
14
- PR,
6
+ Action,
15
7
  check_pypi_version,
16
- get_github_data,
17
- get_pr_diff,
18
- graphql_request,
19
8
  ultralytics_actions_info,
20
9
  )
21
- from .openai_utils import OPENAI_API_KEY, OPENAI_MODEL, get_completion
10
+ from .openai_utils import get_completion
22
11
 
23
12
  __all__ = (
24
- "remove_html_comments",
25
- "EVENT_DATA",
26
13
  "GITHUB_API_URL",
27
- "GITHUB_HEADERS",
28
- "GITHUB_HEADERS_DIFF",
29
- "GITHUB_TOKEN",
30
- "GITHUB_REPOSITORY",
31
- "PR",
32
- "DISCUSSION",
33
- "GITHUB_EVENT_NAME",
34
- "GITHUB_EVENT_PATH",
35
- "get_github_data",
36
- "get_pr_diff",
37
- "graphql_request",
38
- "OPENAI_API_KEY",
39
- "OPENAI_MODEL",
40
- "get_completion",
14
+ "Action",
41
15
  "check_pypi_version",
16
+ "get_completion",
17
+ "remove_html_comments",
42
18
  "ultralytics_actions_info",
43
19
  )
@@ -7,50 +7,110 @@ import requests
7
7
 
8
8
  from actions import __version__
9
9
 
10
- GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
11
- GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
12
- GITHUB_EVENT_NAME = os.getenv("GITHUB_EVENT_NAME")
13
- GITHUB_EVENT_PATH = os.getenv("GITHUB_EVENT_PATH")
14
10
  GITHUB_API_URL = "https://api.github.com"
15
- GITHUB_HEADERS = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"}
16
- GITHUB_HEADERS_DIFF = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3.diff"}
17
-
18
- EVENT_DATA = {}
19
- if GITHUB_EVENT_PATH:
20
- event_path = Path(GITHUB_EVENT_PATH)
21
- if event_path.exists():
22
- EVENT_DATA = json.loads(event_path.read_text())
23
- PR = EVENT_DATA.get("pull_request", {})
24
- DISCUSSION = EVENT_DATA.get("discussion", {})
25
-
26
-
27
- def get_pr_diff(pr_number: int) -> str:
28
- """Retrieves the diff content for a specified pull request in a GitHub repository."""
29
- url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/pulls/{pr_number}"
30
- r = requests.get(url, headers=GITHUB_HEADERS_DIFF)
31
- return r.text if r.status_code == 200 else ""
32
-
33
-
34
- def get_github_data(endpoint: str) -> dict:
35
- """Fetches GitHub repository data from a specified endpoint using the GitHub API."""
36
- r = requests.get(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/{endpoint}", headers=GITHUB_HEADERS)
37
- r.raise_for_status()
38
- return r.json()
39
-
40
-
41
- def graphql_request(query: str, variables: dict = None) -> dict:
42
- """Executes a GraphQL query against the GitHub API and returns the response as a dictionary."""
43
- headers = {
44
- "Authorization": f"Bearer {GITHUB_TOKEN}",
45
- "Content-Type": "application/json",
46
- "Accept": "application/vnd.github.v4+json",
47
- }
48
- r = requests.post(f"{GITHUB_API_URL}/graphql", json={"query": query, "variables": variables}, headers=headers)
49
- r.raise_for_status()
50
- result = r.json()
51
- success = "data" in result and not result.get("errors")
52
- print(f"{'Successful' if success else 'Fail'} discussion GraphQL request: {result.get('errors', 'No errors')}")
53
- return result
11
+
12
+
13
+ class Action:
14
+ """Handles GitHub Actions API interactions and event processing."""
15
+
16
+ def __init__(
17
+ self,
18
+ token: str = None,
19
+ event_name: str = None,
20
+ event_data: dict = None,
21
+ ):
22
+ self.token = token or os.getenv("GITHUB_TOKEN")
23
+ self.event_name = event_name or os.getenv("GITHUB_EVENT_NAME")
24
+ self.event_data = event_data or self._load_event_data(os.getenv("GITHUB_EVENT_PATH"))
25
+
26
+ self.pr = self.event_data.get("pull_request", {})
27
+ self.repository = self.event_data.get("repository", {}).get("full_name")
28
+ self.headers = {"Authorization": f"token {self.token}", "Accept": "application/vnd.github.v3+json"}
29
+ self.headers_diff = {"Authorization": f"token {self.token}", "Accept": "application/vnd.github.v3.diff"}
30
+
31
+ @staticmethod
32
+ def _load_event_data(event_path: str) -> dict:
33
+ """Loads GitHub event data from path if it exists."""
34
+ if event_path and Path(event_path).exists():
35
+ return json.loads(Path(event_path).read_text())
36
+ return {}
37
+
38
+ def get_username(self) -> str | None:
39
+ """Gets username associated with the GitHub token."""
40
+ query = "query { viewer { login } }"
41
+ response = requests.post(f"{GITHUB_API_URL}/graphql", json={"query": query}, headers=self.headers)
42
+ if response.status_code != 200:
43
+ print(f"Failed to fetch authenticated user. Status code: {response.status_code}")
44
+ return None
45
+ try:
46
+ return response.json()["data"]["viewer"]["login"]
47
+ except KeyError as e:
48
+ print(f"Error parsing authenticated user response: {e}")
49
+ return None
50
+
51
+ def get_pr_diff(self) -> str:
52
+ """Retrieves the diff content for a specified pull request."""
53
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/pulls/{self.pr.get('number')}"
54
+ r = requests.get(url, headers=self.headers_diff)
55
+ return r.text if r.status_code == 200 else ""
56
+
57
+ def get_repo_data(self, endpoint: str) -> dict:
58
+ """Fetches repository data from a specified endpoint."""
59
+ r = requests.get(f"{GITHUB_API_URL}/repos/{self.repository}/{endpoint}", headers=self.headers)
60
+ r.raise_for_status()
61
+ return r.json()
62
+
63
+ def graphql_request(self, query: str, variables: dict = None) -> dict:
64
+ """Executes a GraphQL query against the GitHub API."""
65
+ headers = {
66
+ "Authorization": f"Bearer {self.token}",
67
+ "Content-Type": "application/json",
68
+ "Accept": "application/vnd.github.v4+json",
69
+ }
70
+ r = requests.post(f"{GITHUB_API_URL}/graphql", json={"query": query, "variables": variables}, headers=headers)
71
+ r.raise_for_status()
72
+ result = r.json()
73
+ success = "data" in result and not result.get("errors")
74
+ print(
75
+ f"{'Successful' if success else 'Failed'} discussion GraphQL request: {result.get('errors', 'No errors')}"
76
+ )
77
+ return result
78
+
79
+ def print_info(self):
80
+ """Print GitHub Actions information."""
81
+ info = {
82
+ "github.event_name": self.event_name,
83
+ "github.event.action": self.event_data.get("action"),
84
+ "github.repository": self.repository,
85
+ "github.event.pull_request.number": self.pr.get("number"),
86
+ "github.event.pull_request.head.repo.full_name": self.pr.get("head", {}).get("repo", {}).get("full_name"),
87
+ "github.actor": os.environ.get("GITHUB_ACTOR"),
88
+ "github.event.pull_request.head.ref": self.pr.get("head", {}).get("ref"),
89
+ "github.ref": os.environ.get("GITHUB_REF"),
90
+ "github.head_ref": os.environ.get("GITHUB_HEAD_REF"),
91
+ "github.base_ref": os.environ.get("GITHUB_BASE_REF"),
92
+ "github.base_sha": self.pr.get("base", {}).get("sha"),
93
+ }
94
+
95
+ if self.event_name == "discussion":
96
+ discussion = self.event_data.get("discussion", {})
97
+ info.update(
98
+ {
99
+ "github.event.discussion.node_id": discussion.get("node_id"),
100
+ "github.event.discussion.number": discussion.get("number"),
101
+ }
102
+ )
103
+
104
+ max_key_length = max(len(key) for key in info)
105
+ header = f"Ultralytics Actions {__version__} Information " + "-" * 40
106
+ print(header)
107
+ for key, value in info.items():
108
+ print(f"{key:<{max_key_length + 5}}{value}")
109
+ print("-" * len(header))
110
+
111
+
112
+ def ultralytics_actions_info():
113
+ Action().print_info()
54
114
 
55
115
 
56
116
  def check_pypi_version(pyproject_toml="pyproject.toml"):
@@ -101,36 +161,3 @@ def check_pypi_version(pyproject_toml="pyproject.toml"):
101
161
  publish = True # publish as this is likely a first release
102
162
 
103
163
  return local_version, online_version, publish
104
-
105
-
106
- def ultralytics_actions_info():
107
- """Print Ultralytics Actions information."""
108
- info = {
109
- "github.event_name": GITHUB_EVENT_NAME,
110
- "github.event.action": EVENT_DATA.get("action"),
111
- "github.repository": GITHUB_REPOSITORY,
112
- "github.event.pull_request.number": PR.get("number"),
113
- "github.event.pull_request.head.repo.full_name": PR.get("head", {}).get("repo", {}).get("full_name"),
114
- "github.actor": os.environ.get("GITHUB_ACTOR"),
115
- "github.event.pull_request.head.ref": PR.get("head", {}).get("ref"),
116
- "github.ref": os.environ.get("GITHUB_REF"),
117
- "github.head_ref": os.environ.get("GITHUB_HEAD_REF"),
118
- "github.base_ref": os.environ.get("GITHUB_BASE_REF"),
119
- "github.base_sha": PR.get("base", {}).get("sha"),
120
- }
121
-
122
- if GITHUB_EVENT_NAME == "discussion":
123
- info.update(
124
- {
125
- "github.event.discussion.node_id": DISCUSSION.get("node_id"),
126
- "github.event.discussion.number": DISCUSSION.get("number"),
127
- }
128
- )
129
-
130
- # Print information
131
- max_key_length = max(len(key) for key in info)
132
- header = f"Ultralytics Actions {__version__} Information " + "-" * 40
133
- print(header)
134
- for key, value in info.items():
135
- print(f"{key:<{max_key_length + 5}}{value}")
136
- print("-" * len(header)) # footer
@@ -1,7 +1,7 @@
1
1
  # Ultralytics Actions 🚀, AGPL-3.0 license https://ultralytics.com/license
2
2
 
3
3
  import os
4
- import random
4
+ import time
5
5
  from typing import Dict, List
6
6
 
7
7
  import requests
@@ -25,7 +25,7 @@ def get_completion(
25
25
  content = ""
26
26
  max_retries = 2
27
27
  for attempt in range(max_retries + 2): # attempt = [0, 1, 2, 3], 2 random retries before asking for no links
28
- data = {"model": OPENAI_MODEL, "messages": messages, "seed": random.randint(1, 1000000)}
28
+ data = {"model": OPENAI_MODEL, "messages": messages, "seed": int(time.time() * 1000)}
29
29
 
30
30
  r = requests.post(url, headers=headers, json=data)
31
31
  r.raise_for_status()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics-actions
3
- Version: 0.0.27
3
+ Version: 0.0.34
4
4
  Summary: Ultralytics Actions for GitHub automation and PR management.
5
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -0,0 +1,15 @@
1
+ actions/__init__.py,sha256=GtlE-ku_TTk_fBoKBzCennk6SAk2-Cju6XccN_3qlVI,749
2
+ actions/first_interaction.py,sha256=MCjDdpc4TgdeB1IWIdkFvXuvkolySBehdBRm1nPPAgo,17707
3
+ actions/summarize_pr.py,sha256=Uq5TswlNZuxn7HenU8KhWr3z3m496_THWsW1zlYKQvU,11173
4
+ actions/summarize_release.py,sha256=eEah_BkvdHaFsR1Nxx-WbiOC_xmoXfzQpfwQS0cGJqc,8462
5
+ actions/update_markdown_code_blocks.py,sha256=WBNcMD_KKsZS-qSPBn6O1G0ggQ_VrT-jTQffbg7xH_M,6369
6
+ actions/utils/__init__.py,sha256=W82wrlyOAlIPDOtJkgSKjJVXn6QMAoa43gEI0-aWkjs,441
7
+ actions/utils/common_utils.py,sha256=XT8GG0SWBtlZLruA0nKrY4AJpkitvPbM8zndE8etuDo,3548
8
+ actions/utils/github_utils.py,sha256=42w9iHRayI275T7PqJaWpDSPCgDADhrbpnoVarT5ERs,6907
9
+ actions/utils/openai_utils.py,sha256=CU0FdeUW6qeZsxYCC2NUcGjns7w6hDvT8PwaRlG7j9E,1829
10
+ ultralytics_actions-0.0.34.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
+ ultralytics_actions-0.0.34.dist-info/METADATA,sha256=IRDqEbAx1A8IaHxuGzP7SHRQOAiu3WAsptNL7QuPH8I,10591
12
+ ultralytics_actions-0.0.34.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
13
+ ultralytics_actions-0.0.34.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
+ ultralytics_actions-0.0.34.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
+ ultralytics_actions-0.0.34.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- actions/__init__.py,sha256=rP2L5U_HiqdOSZfLwC9bi3qy1LgEOS2o-6TN1piyDgY,749
2
- actions/first_interaction.py,sha256=ehHkFwWr-14LgesGmO4lOthu8z626FgvV9e6fiyzN8Q,17648
3
- actions/summarize_pr.py,sha256=FUFKuCKTH4AAvo_k6C-gfITV6LEYrmZiCCUOy5380d0,7775
4
- actions/summarize_release.py,sha256=l8NBdTAXLysfNKl1Kf_1tyuBRmeEBLyzTDXS6s5_eQg,8350
5
- actions/update_markdown_code_blocks.py,sha256=WBNcMD_KKsZS-qSPBn6O1G0ggQ_VrT-jTQffbg7xH_M,6369
6
- actions/utils/__init__.py,sha256=0vRjFc7i2WOlphuxdUxQo5BuNipgwGw2Bs-fdUBDeUw,973
7
- actions/utils/common_utils.py,sha256=XT8GG0SWBtlZLruA0nKrY4AJpkitvPbM8zndE8etuDo,3548
8
- actions/utils/github_utils.py,sha256=O9hTo8amqT890YIixMvALNur7R8SGRMJ8xXJT3nwpTc,5659
9
- actions/utils/openai_utils.py,sha256=O0sYtLFTPqC4inw9_NWcVNlhElcp0iJ1FvAX3L3arKo,1834
10
- ultralytics_actions-0.0.27.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
- ultralytics_actions-0.0.27.dist-info/METADATA,sha256=D1TtSjTkqmc85qXDHusUeN4GPhWfiD8NTCLCifJxME4,10591
12
- ultralytics_actions-0.0.27.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
13
- ultralytics_actions-0.0.27.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
- ultralytics_actions-0.0.27.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
- ultralytics_actions-0.0.27.dist-info/RECORD,,