ultralytics-actions 0.0.25__py3-none-any.whl → 0.0.30__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.25"
25
+ __version__ = "0.0.30"
@@ -335,7 +335,7 @@ INSTRUCTIONS:
335
335
  - Adapt the example {issue_type} response below as appropriate, keeping all badges, links and references provided
336
336
  - For bug reports, specifically request a minimum reproducible example (MRE) if not provided
337
337
  - INCLUDE ALL LINKS AND INSTRUCTIONS IN THE EXAMPLE BELOW, customized as appropriate
338
- - In your response, mention to the user that this is an automated response and that an Ultralytics engineer will also assist soon
338
+ - Mention to the user that this is an automated response and that an Ultralytics engineer will also assist soon
339
339
  - Do not add a sign-off or valediction like "best regards" at the end of your response
340
340
  - Do not add spaces between bullet points or numbered lists
341
341
  - Only link to files or URLs in the example below, do not add external links
@@ -359,7 +359,7 @@ YOUR {issue_type.upper()} RESPONSE:
359
359
  messages = [
360
360
  {
361
361
  "role": "system",
362
- "content": f"You are a helpful assistant responding to GitHub {issue_type}s for the {org_name} organization.",
362
+ "content": f"You are a helpful assistant responding to GitHub {issue_type}s for {org_name}.",
363
363
  },
364
364
  {"role": "user", "content": prompt},
365
365
  ]
actions/summarize_pr.py CHANGED
@@ -10,15 +10,72 @@ from .utils import (
10
10
  GITHUB_REPOSITORY,
11
11
  PR,
12
12
  get_completion,
13
+ get_github_username,
13
14
  get_pr_diff,
14
15
  )
15
16
 
16
- # Action settings
17
+ # Constants
17
18
  SUMMARY_START = (
18
19
  "## 🛠️ PR Summary\n\n<sub>Made with ❤️ by [Ultralytics Actions](https://github.com/ultralytics/actions)<sub>\n\n"
19
20
  )
20
21
 
21
22
 
23
+ def generate_merge_message(pr_author, contributors, pr_summary=None):
24
+ """Generates a thank-you message for merged PR contributors."""
25
+ contributors_str = ", ".join(f"@{c}" for c in contributors if c != pr_author)
26
+ mention_str = f"@{pr_author}"
27
+ if contributors_str:
28
+ mention_str += f" and {contributors_str}"
29
+
30
+ messages = [
31
+ {
32
+ "role": "system",
33
+ "content": "You are an Ultralytics AI assistant. Generate meaningful, inspiring messages to GitHub users.",
34
+ },
35
+ {
36
+ "role": "user",
37
+ "content": f"Write a friendly thank you for a merged PR by these GitHub contributors: {mention_str}. "
38
+ f"Context from PR:\n{pr_summary}\n\n"
39
+ f"Start with the exciting message that this PR is now merged, and weave in an inspiring quote "
40
+ f"from a famous figure in science, philosophy or stoicism. "
41
+ f"Keep the message concise yet relevant to the specific contributions in this PR. "
42
+ f"We want the contributors to feel their effort is appreciated and will make a difference in the world.",
43
+ },
44
+ ]
45
+ return get_completion(messages)
46
+
47
+
48
+ def post_merge_message(pr_number, pr_author, contributors, summary):
49
+ """Posts thank you message on PR after merge."""
50
+ message = generate_merge_message(pr_author, contributors, summary)
51
+ comment_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{pr_number}/comments"
52
+ response = requests.post(comment_url, json={"body": message}, headers=GITHUB_HEADERS)
53
+ return response.status_code == 201
54
+
55
+
56
+ def generate_issue_comment(pr_url, pr_summary):
57
+ """Generates a personalized issue comment using based on the PR context."""
58
+ messages = [
59
+ {
60
+ "role": "system",
61
+ "content": "You are an Ultralytics AI assistant. Generate friendly GitHub issue comments. No @ mentions or direct addressing.",
62
+ },
63
+ {
64
+ "role": "user",
65
+ "content": f"Write a GitHub issue comment announcing a potential fix has been merged in linked PR {pr_url}\n\n"
66
+ f"Context from PR:\n{pr_summary}\n\n"
67
+ f"Include:\n"
68
+ f"1. An explanation of key changes from the PR that may resolve this issue\n"
69
+ f"2. Options for testing if PR changes have resolved this issue:\n"
70
+ f" - pip install git+https://github.com/ultralytics/ultralytics.git@main # test latest changes\n"
71
+ f" - or await next official PyPI release\n"
72
+ f"3. Request feedback on whether the PR changes resolve the issue\n"
73
+ f"4. Thank 🙏 for reporting the issue and welcome any further feedback if the issue persists\n\n",
74
+ },
75
+ ]
76
+ return get_completion(messages)
77
+
78
+
22
79
  def generate_pr_summary(repo_name, diff_text):
23
80
  """Generates a concise, professional summary of a PR using OpenAI's API for Ultralytics repositories."""
24
81
  if not diff_text:
@@ -71,9 +128,8 @@ def update_pr_description(repo_name, pr_number, new_summary, max_retries=2):
71
128
  return update_response.status_code
72
129
 
73
130
 
74
- def label_fixed_issues(pr_number):
75
- """Labels issues that are closed by this PR when it's merged."""
76
- # GraphQL query to get closing issues
131
+ def label_fixed_issues(pr_number, pr_summary):
132
+ """Labels issues closed by this PR when merged, notifies users, and returns PR contributors."""
77
133
  query = """
78
134
  query($owner: String!, $repo: String!, $pr_number: Int!) {
79
135
  repository(owner: $owner, name: $repo) {
@@ -83,6 +139,15 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
83
139
  number
84
140
  }
85
141
  }
142
+ url
143
+ body
144
+ author { login, __typename }
145
+ reviews(first: 50) {
146
+ nodes { author { login, __typename } }
147
+ }
148
+ comments(first: 50) {
149
+ nodes { author { login, __typename } }
150
+ }
86
151
  }
87
152
  }
88
153
  }
@@ -94,29 +159,59 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
94
159
  response = requests.post(graphql_url, json={"query": query, "variables": variables}, headers=GITHUB_HEADERS)
95
160
  if response.status_code != 200:
96
161
  print(f"Failed to fetch linked issues. Status code: {response.status_code}")
97
- return
162
+ return [], None
98
163
 
99
164
  try:
100
- issues = response.json()["data"]["repository"]["pullRequest"]["closingIssuesReferences"]["nodes"]
101
- for issue in issues:
165
+ data = response.json()["data"]["repository"]["pullRequest"]
166
+ comments = data["reviews"]["nodes"] + data["comments"]["nodes"] # merge lists
167
+ author = data["author"]["login"]
168
+
169
+ # Get unique contributors from reviews and comments
170
+ contributors = {x["author"]["login"] for x in comments if x["author"]["__typename"] != "Bot"}
171
+ contributors.discard(author) # Remove author from contributors list
172
+
173
+ # Generate personalized comment
174
+ comment = generate_issue_comment(pr_url=data["url"], pr_summary=pr_summary)
175
+
176
+ # Update linked issues
177
+ for issue in data["closingIssuesReferences"]["nodes"]:
102
178
  issue_number = issue["number"]
179
+ # Add fixed label
103
180
  label_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/labels"
104
181
  label_response = requests.post(label_url, json={"labels": ["fixed"]}, headers=GITHUB_HEADERS)
105
- if label_response.status_code == 200:
106
- print(f"Added 'fixed' label to issue #{issue_number}")
182
+
183
+ # Add comment
184
+ comment_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/comments"
185
+ comment_response = requests.post(comment_url, json={"body": comment}, headers=GITHUB_HEADERS)
186
+
187
+ if label_response.status_code == 200 and comment_response.status_code == 201:
188
+ print(f"Added 'fixed' label and comment to issue #{issue_number}")
107
189
  else:
108
- print(f"Failed to add label to issue #{issue_number}. Status: {label_response.status_code}")
190
+ print(
191
+ f"Failed to update issue #{issue_number}. Label status: {label_response.status_code}, "
192
+ f"Comment status: {comment_response.status_code}"
193
+ )
194
+
195
+ return contributors, author
109
196
  except KeyError as e:
110
197
  print(f"Error parsing GraphQL response: {e}")
111
- return
198
+ return [], None
199
+
200
+
201
+ def remove_todos_on_merge(pr_number):
202
+ """Removes specified labels from PR."""
203
+ for label in ["TODO"]: # Can be extended with more labels in the future
204
+ requests.delete(
205
+ f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{pr_number}/labels/{label}", headers=GITHUB_HEADERS
206
+ )
112
207
 
113
208
 
114
209
  def main():
115
- """Summarize a pull request and update its description with an AI-generated summary."""
210
+ """Summarize a pull request and update its description with a summary."""
116
211
  pr_number = PR["number"]
117
212
 
118
213
  print(f"Retrieving diff for PR {pr_number}")
119
- diff = get_pr_diff(PR["number"])
214
+ diff = get_pr_diff(pr_number)
120
215
 
121
216
  # Generate PR summary
122
217
  print("Generating PR summary...")
@@ -130,10 +225,17 @@ def main():
130
225
  else:
131
226
  print(f"Failed to update PR description. Status code: {status_code}")
132
227
 
133
- # Update linked issues
228
+ # Update linked issues and post thank you message if merged
134
229
  if PR.get("merged"):
135
230
  print("PR is merged, labeling fixed issues...")
136
- label_fixed_issues(PR["number"])
231
+ contributors, author = label_fixed_issues(pr_number, summary)
232
+ print("Removing TODO label from PR...")
233
+ remove_todos_on_merge(pr_number)
234
+ username = get_github_username() # get GITHUB_TOKEN username
235
+ if author and author != username:
236
+ print("Posting PR author thank you message...")
237
+ contributors.discard(username)
238
+ post_merge_message(pr_number, author, contributors, summary)
137
239
 
138
240
 
139
241
  if __name__ == "__main__":
actions/utils/__init__.py CHANGED
@@ -14,6 +14,7 @@ from .github_utils import (
14
14
  PR,
15
15
  check_pypi_version,
16
16
  get_github_data,
17
+ get_github_username,
17
18
  get_pr_diff,
18
19
  graphql_request,
19
20
  ultralytics_actions_info,
@@ -38,6 +39,7 @@ __all__ = (
38
39
  "OPENAI_API_KEY",
39
40
  "OPENAI_MODEL",
40
41
  "get_completion",
42
+ "get_github_username",
41
43
  "check_pypi_version",
42
44
  "ultralytics_actions_info",
43
45
  )
@@ -24,6 +24,27 @@ PR = EVENT_DATA.get("pull_request", {})
24
24
  DISCUSSION = EVENT_DATA.get("discussion", {})
25
25
 
26
26
 
27
+ def get_github_username():
28
+ """Gets username associated with the GitHub token in GITHUB_HEADERS."""
29
+ query = """
30
+ query {
31
+ viewer {
32
+ login
33
+ }
34
+ }
35
+ """
36
+ response = requests.post("https://api.github.com/graphql", json={"query": query}, headers=GITHUB_HEADERS)
37
+ if response.status_code != 200:
38
+ print(f"Failed to fetch authenticated user. Status code: {response.status_code}")
39
+ return None
40
+
41
+ try:
42
+ return response.json()["data"]["viewer"]["login"]
43
+ except KeyError as e:
44
+ print(f"Error parsing authenticated user response: {e}")
45
+ return None
46
+
47
+
27
48
  def get_pr_diff(pr_number: int) -> str:
28
49
  """Retrieves the diff content for a specified pull request in a GitHub repository."""
29
50
  url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/pulls/{pr_number}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics-actions
3
- Version: 0.0.25
3
+ Version: 0.0.30
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=vI2djkUXlHUwTefYfOmHPdRKls05ZS0all3rQSdIKXw,749
2
+ actions/first_interaction.py,sha256=ehHkFwWr-14LgesGmO4lOthu8z626FgvV9e6fiyzN8Q,17648
3
+ actions/summarize_pr.py,sha256=vkMVfy8ukoWMf_BrS1Ykh34GHFDkREaw9hNCiUUIClE,10529
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=e3vKraD3_YpFiVUn3B3KR0diqG1ZrXMV9eXQYikObxo,1025
7
+ actions/utils/common_utils.py,sha256=XT8GG0SWBtlZLruA0nKrY4AJpkitvPbM8zndE8etuDo,3548
8
+ actions/utils/github_utils.py,sha256=QBNBx_qb3cbMwYmAQqEmUmGj33jir8Hjc6dr8Jk3vws,6286
9
+ actions/utils/openai_utils.py,sha256=O0sYtLFTPqC4inw9_NWcVNlhElcp0iJ1FvAX3L3arKo,1834
10
+ ultralytics_actions-0.0.30.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
+ ultralytics_actions-0.0.30.dist-info/METADATA,sha256=TZDWraFY1tpuTQMnkkF2FHmTM8u5JiP-TLE6oHtvDJc,10591
12
+ ultralytics_actions-0.0.30.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
13
+ ultralytics_actions-0.0.30.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
+ ultralytics_actions-0.0.30.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
+ ultralytics_actions-0.0.30.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- actions/__init__.py,sha256=t0B8aBSaP2FlwHHA-wL74q6apk3Kgkh3m6RddLrLwaw,749
2
- actions/first_interaction.py,sha256=cLXo5hmhOgTfk7F9LbGJeRdg6vvrKUe-0taCa_TkpAg,17683
3
- actions/summarize_pr.py,sha256=R-GqxWF24CYRb-jPNF2tckXUV9g5BG_e3tKZwHlhtNQ,5737
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.25.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
- ultralytics_actions-0.0.25.dist-info/METADATA,sha256=lp9CnsStoZ6FQdgpEpCpxcLCZbna4D-cKCC5mOAAVys,10591
12
- ultralytics_actions-0.0.25.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
13
- ultralytics_actions-0.0.25.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
- ultralytics_actions-0.0.25.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
- ultralytics_actions-0.0.25.dist-info/RECORD,,