ultralytics-actions 0.0.69__py3-none-any.whl → 0.0.70__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 +1 -1
- actions/dispatch_actions.py +127 -0
- actions/utils/common_utils.py +17 -2
- actions/utils/github_utils.py +7 -0
- {ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/METADATA +1 -1
- ultralytics_actions-0.0.70.dist-info/RECORD +16 -0
- {ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/WHEEL +1 -1
- ultralytics_actions-0.0.69.dist-info/RECORD +0 -15
- {ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/top_level.txt +0 -0
actions/__init__.py
CHANGED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
import time
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Dict, List
|
6
|
+
|
7
|
+
import requests
|
8
|
+
|
9
|
+
from .utils import GITHUB_API_URL, Action, remove_html_comments
|
10
|
+
|
11
|
+
# Configuration
|
12
|
+
RUN_CI_KEYWORD = "@ultralytics/run-ci" # and then to merge "@ultralytics/run-ci-and-merge"
|
13
|
+
WORKFLOW_FILES = ["format.yml", "ci.yml", "docker.yml"]
|
14
|
+
|
15
|
+
|
16
|
+
def get_pr_branch(event) -> str:
|
17
|
+
"""Gets the PR branch name."""
|
18
|
+
pr_number = event.event_data["issue"]["number"]
|
19
|
+
pr_data = event.get_repo_data(f"pulls/{pr_number}")
|
20
|
+
return pr_data.get("head", {}).get("ref", "main")
|
21
|
+
|
22
|
+
|
23
|
+
def trigger_and_get_workflow_info(event, branch: str) -> List[Dict]:
|
24
|
+
"""Triggers workflows and returns their information."""
|
25
|
+
repo = event.repository
|
26
|
+
results = []
|
27
|
+
|
28
|
+
# Trigger all workflows
|
29
|
+
for file in WORKFLOW_FILES:
|
30
|
+
requests.post(
|
31
|
+
f"{GITHUB_API_URL}/repos/{repo}/actions/workflows/{file}/dispatches",
|
32
|
+
json={"ref": branch},
|
33
|
+
headers=event.headers,
|
34
|
+
)
|
35
|
+
|
36
|
+
# Wait for workflows to be created
|
37
|
+
time.sleep(10)
|
38
|
+
|
39
|
+
# Collect information about all workflows
|
40
|
+
for file in WORKFLOW_FILES:
|
41
|
+
# Get workflow name
|
42
|
+
response = requests.get(f"{GITHUB_API_URL}/repos/{repo}/actions/workflows/{file}", headers=event.headers)
|
43
|
+
name = file.replace(".yml", "").title()
|
44
|
+
if response.status_code == 200:
|
45
|
+
name = response.json().get("name", name)
|
46
|
+
|
47
|
+
# Get run information
|
48
|
+
run_url = f"https://github.com/{repo}/actions/workflows/{file}"
|
49
|
+
run_number = None
|
50
|
+
|
51
|
+
runs_response = requests.get(
|
52
|
+
f"{GITHUB_API_URL}/repos/{repo}/actions/workflows/{file}/runs?branch={branch}&event=workflow_dispatch&per_page=1",
|
53
|
+
headers=event.headers,
|
54
|
+
)
|
55
|
+
|
56
|
+
if runs_response.status_code == 200:
|
57
|
+
runs = runs_response.json().get("workflow_runs", [])
|
58
|
+
if runs:
|
59
|
+
run_url = runs[0].get("html_url", run_url)
|
60
|
+
run_number = runs[0].get("run_number")
|
61
|
+
|
62
|
+
results.append({"name": name, "file": file, "url": run_url, "run_number": run_number})
|
63
|
+
|
64
|
+
return results
|
65
|
+
|
66
|
+
|
67
|
+
def update_comment(event, comment_body: str, triggered_actions: List[Dict], branch: str) -> bool:
|
68
|
+
"""Updates the comment with workflow information."""
|
69
|
+
if not triggered_actions:
|
70
|
+
return False
|
71
|
+
|
72
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
|
73
|
+
summary = (
|
74
|
+
f"\n\n## ⚡ Actions Trigger\n\n"
|
75
|
+
f"<sub>Made with ❤️ by [Ultralytics Actions](https://www.ultralytics.com/actions)<sub>\n\n"
|
76
|
+
f"GitHub Actions below triggered via workflow dispatch on this "
|
77
|
+
f"PR branch `{branch}` at {timestamp} with `@ultralytics/dispatch-actions command`:\n\n"
|
78
|
+
)
|
79
|
+
|
80
|
+
for action in triggered_actions:
|
81
|
+
run_info = f" run {action['run_number']}" if action["run_number"] else ""
|
82
|
+
summary += f"* ✅ [{action['name']}]({action['url']}): `{action['file']}`{run_info}\n"
|
83
|
+
|
84
|
+
new_body = comment_body.replace(RUN_CI_KEYWORD, summary).strip()
|
85
|
+
comment_id = event.event_data["comment"]["id"]
|
86
|
+
|
87
|
+
response = requests.patch(
|
88
|
+
f"{GITHUB_API_URL}/repos/{event.repository}/issues/comments/{comment_id}",
|
89
|
+
json={"body": new_body},
|
90
|
+
headers=event.headers,
|
91
|
+
)
|
92
|
+
|
93
|
+
return response.status_code == 200
|
94
|
+
|
95
|
+
|
96
|
+
def main(*args, **kwargs):
|
97
|
+
"""Handles triggering workflows from PR comments."""
|
98
|
+
event = Action(*args, **kwargs)
|
99
|
+
|
100
|
+
# Only process new comments on PRs
|
101
|
+
if (
|
102
|
+
event.event_name != "issue_comment"
|
103
|
+
or "pull_request" not in event.event_data.get("issue", {})
|
104
|
+
or event.event_data.get("action") != "created"
|
105
|
+
):
|
106
|
+
return
|
107
|
+
|
108
|
+
# Get comment info
|
109
|
+
comment_body = remove_html_comments(event.event_data["comment"].get("body", ""))
|
110
|
+
username = event.event_data["comment"]["user"]["login"]
|
111
|
+
|
112
|
+
# Check for trigger keyword and permissions
|
113
|
+
if RUN_CI_KEYWORD not in comment_body or not event.is_org_member(username):
|
114
|
+
return
|
115
|
+
|
116
|
+
# Get branch, trigger workflows, and update comment
|
117
|
+
branch = get_pr_branch(event)
|
118
|
+
print(f"Triggering workflows on branch: {branch}")
|
119
|
+
|
120
|
+
triggered_actions = trigger_and_get_workflow_info(event, branch)
|
121
|
+
success = update_comment(event, comment_body, triggered_actions, branch)
|
122
|
+
|
123
|
+
print(f"Comment update {'succeeded' if success else 'failed'}.")
|
124
|
+
|
125
|
+
|
126
|
+
if __name__ == "__main__":
|
127
|
+
main()
|
actions/utils/common_utils.py
CHANGED
@@ -36,6 +36,15 @@ BAD_HTTP_CODES = frozenset(
|
|
36
36
|
525, # Cloudfare handshake error
|
37
37
|
}
|
38
38
|
)
|
39
|
+
|
40
|
+
URL_ERROR_LIST = { # automatically reject these URLs (important: replace spaces with '%20')
|
41
|
+
"https://blog.research.google/search/label/Spam%20and%20Abuse",
|
42
|
+
"https://blog.research.google/search/label/Adversarial%20Attacks",
|
43
|
+
"https://www.microsoft.com/en-us/security/business/ai-machine-learning-security",
|
44
|
+
"https://about.netflix.com/en/news/netflix-recommendations-beyond-the-5-stars-part-1",
|
45
|
+
"https://about.netflix.com/en/news/netflix-research-recommendations",
|
46
|
+
}
|
47
|
+
|
39
48
|
URL_IGNORE_LIST = { # use a set (not frozenset) to update with possible private GitHub repos
|
40
49
|
"localhost",
|
41
50
|
"127.0.0",
|
@@ -51,10 +60,11 @@ URL_IGNORE_LIST = { # use a set (not frozenset) to update with possible private
|
|
51
60
|
"mailto:",
|
52
61
|
"linkedin.com",
|
53
62
|
"twitter.com",
|
54
|
-
"x.com",
|
63
|
+
"https://x.com", # do not use just 'x' as this will catch other domains like netflix.com
|
55
64
|
"storage.googleapis.com", # private GCS buckets
|
56
65
|
"{", # possible Python fstring
|
57
66
|
"(", # breaks pattern matches
|
67
|
+
")",
|
58
68
|
"api.", # ignore api endpoints
|
59
69
|
}
|
60
70
|
REDIRECT_START_IGNORE_LIST = frozenset(
|
@@ -69,9 +79,14 @@ REDIRECT_START_IGNORE_LIST = frozenset(
|
|
69
79
|
"ultralytics.com/actions",
|
70
80
|
"ultralytics.com/bilibili",
|
71
81
|
"ultralytics.com/images",
|
82
|
+
"ultralytics.com/license",
|
83
|
+
"ultralytics.com/assets",
|
72
84
|
"app.gong.io/call?",
|
73
85
|
"docs.openvino.ai",
|
86
|
+
".git",
|
87
|
+
"/raw/", # GitHub images
|
74
88
|
}
|
89
|
+
| URL_IGNORE_LIST
|
75
90
|
)
|
76
91
|
REDIRECT_END_IGNORE_LIST = frozenset(
|
77
92
|
{
|
@@ -161,7 +176,7 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=3, return_url=
|
|
161
176
|
# Check structure
|
162
177
|
result = parse.urlparse(url)
|
163
178
|
partition = result.netloc.partition(".") # i.e. netloc = "github.com" -> ("github", ".", "com")
|
164
|
-
if not result.scheme or not partition[0] or not partition[2]:
|
179
|
+
if not result.scheme or not partition[0] or not partition[2] or (url in URL_ERROR_LIST):
|
165
180
|
return (False, url) if return_url else False
|
166
181
|
|
167
182
|
if check:
|
actions/utils/github_utils.py
CHANGED
@@ -50,6 +50,13 @@ class Action:
|
|
50
50
|
print(f"Error parsing authenticated user response: {e}")
|
51
51
|
return None
|
52
52
|
|
53
|
+
def is_org_member(self, username: str) -> bool:
|
54
|
+
"""Checks if a user is a member of the organization using the GitHub API."""
|
55
|
+
org_name = self.repository.split("/")[0]
|
56
|
+
url = f"{GITHUB_API_URL}/orgs/{org_name}/members/{username}"
|
57
|
+
r = requests.get(url, headers=self.headers)
|
58
|
+
return r.status_code == 204 # 204 means the user is a member
|
59
|
+
|
53
60
|
def get_pr_diff(self) -> str:
|
54
61
|
"""Retrieves the diff content for a specified pull request."""
|
55
62
|
url = f"{GITHUB_API_URL}/repos/{self.repository}/pulls/{self.pr.get('number')}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ultralytics-actions
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.70
|
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,16 @@
|
|
1
|
+
actions/__init__.py,sha256=jkNfCMkrM58RMv6ijJXC4tASCnP7q1VvKsh1yKtLQn4,742
|
2
|
+
actions/dispatch_actions.py,sha256=plfDaFdfBPldqI1uUQPfgSeIDaRTKbkzkXnsYbGkmwI,4415
|
3
|
+
actions/first_interaction.py,sha256=1_WvQHCi5RWaSfyi49ClF2Zk_3CKGjFnZqz6FlxPRAc,17868
|
4
|
+
actions/summarize_pr.py,sha256=BKttOq-MGaanVaChLU5B1ewKUA8K6S05Cy3FQtyRmxU,11681
|
5
|
+
actions/summarize_release.py,sha256=tov6qsYGC68lfobvkwVyoWZBGtJ598G0m097n4Ydzvo,8472
|
6
|
+
actions/update_markdown_code_blocks.py,sha256=9PL7YIQfApRNAa0que2hYHv7umGZTZoHlblesB0xFj4,8587
|
7
|
+
actions/utils/__init__.py,sha256=ZE0RmC9qOCt9TUhvORd6uVhbxOKVFWJDobR454v55_M,682
|
8
|
+
actions/utils/common_utils.py,sha256=BCqB2noJRXxXLAH8rmZ7c4ss1WjFXDC9OPPPFX5OuRU,11758
|
9
|
+
actions/utils/github_utils.py,sha256=EZIATz5OSrNgdBrLFxL-9KRxn0i_Ph5-ZXHhhcIrtPE,7495
|
10
|
+
actions/utils/openai_utils.py,sha256=txbsEPQnIOieejatBuE6Yk7xR1fQ0erWOEs6cYgUQX4,2943
|
11
|
+
ultralytics_actions-0.0.70.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
12
|
+
ultralytics_actions-0.0.70.dist-info/METADATA,sha256=c9aZUeDQntfNp4gptnUlEVAxDVoEieK1h2qcGQ88jDs,10930
|
13
|
+
ultralytics_actions-0.0.70.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
14
|
+
ultralytics_actions-0.0.70.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
|
15
|
+
ultralytics_actions-0.0.70.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
16
|
+
ultralytics_actions-0.0.70.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
actions/__init__.py,sha256=jfTLYF5qlHNX7JutT_TeZ7oSc5jmqIYB-X87ISRWvzc,742
|
2
|
-
actions/first_interaction.py,sha256=1_WvQHCi5RWaSfyi49ClF2Zk_3CKGjFnZqz6FlxPRAc,17868
|
3
|
-
actions/summarize_pr.py,sha256=BKttOq-MGaanVaChLU5B1ewKUA8K6S05Cy3FQtyRmxU,11681
|
4
|
-
actions/summarize_release.py,sha256=tov6qsYGC68lfobvkwVyoWZBGtJ598G0m097n4Ydzvo,8472
|
5
|
-
actions/update_markdown_code_blocks.py,sha256=9PL7YIQfApRNAa0que2hYHv7umGZTZoHlblesB0xFj4,8587
|
6
|
-
actions/utils/__init__.py,sha256=ZE0RmC9qOCt9TUhvORd6uVhbxOKVFWJDobR454v55_M,682
|
7
|
-
actions/utils/common_utils.py,sha256=YRdEz8qluwzCZfWgqXNmyhKqNhdxNMpoHhGaHUD4AaM,11013
|
8
|
-
actions/utils/github_utils.py,sha256=-F--JgxtXE0fSPMFEzakz7iZilp-vonzLiyXfg0b17Y,7117
|
9
|
-
actions/utils/openai_utils.py,sha256=txbsEPQnIOieejatBuE6Yk7xR1fQ0erWOEs6cYgUQX4,2943
|
10
|
-
ultralytics_actions-0.0.69.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
11
|
-
ultralytics_actions-0.0.69.dist-info/METADATA,sha256=94Bmnj4tirzyhs7DAqvkEvjP0cPg_uIUXSLqlt1_fBI,10930
|
12
|
-
ultralytics_actions-0.0.69.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
13
|
-
ultralytics_actions-0.0.69.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
|
14
|
-
ultralytics_actions-0.0.69.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
15
|
-
ultralytics_actions-0.0.69.dist-info/RECORD,,
|
{ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/entry_points.txt
RENAMED
File without changes
|
{ultralytics_actions-0.0.69.dist-info → ultralytics_actions-0.0.70.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|