ultralytics-actions 0.0.27__tar.gz → 0.0.28__tar.gz

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.
Files changed (20) hide show
  1. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/PKG-INFO +1 -1
  2. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/__init__.py +1 -1
  3. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/summarize_pr.py +73 -17
  4. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/utils/__init__.py +2 -0
  5. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/utils/github_utils.py +21 -0
  6. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/PKG-INFO +1 -1
  7. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/LICENSE +0 -0
  8. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/README.md +0 -0
  9. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/first_interaction.py +0 -0
  10. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/summarize_release.py +0 -0
  11. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/update_markdown_code_blocks.py +0 -0
  12. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/utils/common_utils.py +0 -0
  13. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/actions/utils/openai_utils.py +0 -0
  14. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/pyproject.toml +0 -0
  15. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/setup.cfg +0 -0
  16. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  17. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  18. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  19. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/requires.txt +0 -0
  20. {ultralytics_actions-0.0.27 → ultralytics_actions-0.0.28}/ultralytics_actions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics-actions
3
- Version: 0.0.27
3
+ Version: 0.0.28
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>
@@ -22,4 +22,4 @@
22
22
  # ├── test_summarize_pr.py
23
23
  # └── ...
24
24
 
25
- __version__ = "0.0.27"
25
+ __version__ = "0.0.28"
@@ -10,6 +10,7 @@ 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
 
@@ -19,23 +20,57 @@ SUMMARY_START = (
19
20
  )
20
21
 
21
22
 
22
- def generate_issue_comment(pr_url, pr_body):
23
+ def generate_merge_message(pr_author, contributors, pr_summary=None):
24
+ """Generates an AI thank you message for merged PRs using OpenAI."""
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"Make the message relevant to the specific contributions in this PR. "
42
+ f"We want them to feel their hard work is acknowledged 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 AI-generated 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):
23
57
  """Generates a personalized issue comment using AI based on the PR context."""
24
58
  messages = [
25
59
  {
26
60
  "role": "system",
27
- "content": "You are the Ultralytics AI assistant. Generate friendly GitHub issue comments. No @ mentions or direct addressing.",
61
+ "content": "You are an Ultralytics AI assistant. Generate friendly GitHub issue comments. No @ mentions or direct addressing.",
28
62
  },
29
63
  {
30
64
  "role": "user",
31
- "content": f"Write a comment for a fixed GitHub issue using this merged PR context:\n\n{pr_body}\n\n"
65
+ "content": f"Write a comment for a GitHub issue where a potential fix has been merged in PR: {pr_url}\n\n"
66
+ f"Context from PR:\n{pr_summary}\n\n"
32
67
  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",
68
+ f"1. An explanation of key changes from the PR that may resolve this issue\n"
69
+ f"2. Testing options:\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 these changes resolve the issue\n"
73
+ f"4. Thank 🙏 for reporting the issue and welcome any further feedback if the issue persists\n\n",
39
74
  },
40
75
  ]
41
76
  return get_completion(messages)
@@ -93,8 +128,8 @@ def update_pr_description(repo_name, pr_number, new_summary, max_retries=2):
93
128
  return update_response.status_code
94
129
 
95
130
 
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."""
131
+ def label_fixed_issues(pr_number, pr_summary):
132
+ """Labels issues closed by this PR when merged, notifies users, and returns PR contributors."""
98
133
  query = """
99
134
  query($owner: String!, $repo: String!, $pr_number: Int!) {
100
135
  repository(owner: $owner, name: $repo) {
@@ -106,6 +141,13 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
106
141
  }
107
142
  url
108
143
  body
144
+ author { login }
145
+ reviews(first: 50) {
146
+ nodes { author { login } }
147
+ }
148
+ comments(first: 50) {
149
+ nodes { author { login } }
150
+ }
109
151
  }
110
152
  }
111
153
  }
@@ -117,22 +159,29 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
117
159
  response = requests.post(graphql_url, json={"query": query, "variables": variables}, headers=GITHUB_HEADERS)
118
160
  if response.status_code != 200:
119
161
  print(f"Failed to fetch linked issues. Status code: {response.status_code}")
120
- return
162
+ return [], None
121
163
 
122
164
  try:
123
165
  data = response.json()["data"]["repository"]["pullRequest"]
124
166
  issues = data["closingIssuesReferences"]["nodes"]
167
+ author = data["author"]["login"]
168
+
169
+ # Get unique contributors from reviews and comments
170
+ contributors = {review["author"]["login"] for review in data["reviews"]["nodes"]}
171
+ contributors.update(comment["author"]["login"] for comment in data["comments"]["nodes"])
172
+ contributors.discard(author) # Remove author from contributors list
125
173
 
126
174
  # Generate personalized comment
127
- comment = generate_issue_comment(pr_url=data["url"], pr_body=data["body"])
175
+ comment = generate_issue_comment(pr_url=data["url"], pr_summary=pr_summary)
128
176
 
177
+ # Update linked issues
129
178
  for issue in issues:
130
179
  issue_number = issue["number"]
131
180
  # Add fixed label
132
181
  label_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/labels"
133
182
  label_response = requests.post(label_url, json={"labels": ["fixed"]}, headers=GITHUB_HEADERS)
134
183
 
135
- # Add AI-generated comment
184
+ # Add comment
136
185
  comment_url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{issue_number}/comments"
137
186
  comment_response = requests.post(comment_url, json={"body": comment}, headers=GITHUB_HEADERS)
138
187
 
@@ -143,9 +192,11 @@ query($owner: String!, $repo: String!, $pr_number: Int!) {
143
192
  f"Failed to update issue #{issue_number}. Label status: {label_response.status_code}, "
144
193
  f"Comment status: {comment_response.status_code}"
145
194
  )
195
+
196
+ return contributors, author
146
197
  except KeyError as e:
147
198
  print(f"Error parsing GraphQL response: {e}")
148
- return
199
+ return [], None
149
200
 
150
201
 
151
202
  def remove_todos_on_merge(pr_number):
@@ -175,12 +226,17 @@ def main():
175
226
  else:
176
227
  print(f"Failed to update PR description. Status code: {status_code}")
177
228
 
178
- # Update linked issues
229
+ # Update linked issues and post thank you message if merged
179
230
  if PR.get("merged"):
180
231
  print("PR is merged, labeling fixed issues...")
181
- label_fixed_issues(pr_number)
232
+ contributors, author = label_fixed_issues(pr_number, summary)
182
233
  print("Removing TODO label from PR...")
183
234
  remove_todos_on_merge(pr_number)
235
+ username = get_github_username() # get GITHUB_TOKEN username
236
+ if author and author != username:
237
+ print("Posting PR author thank you message...")
238
+ contributors.discard(username)
239
+ post_merge_message(pr_number, author, contributors, summary)
184
240
 
185
241
 
186
242
  if __name__ == "__main__":
@@ -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.27
3
+ Version: 0.0.28
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>