ultralytics-actions 0.0.68__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
- actions/utils/openai_utils.py +1 -1
- {ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/METADATA +8 -8
- ultralytics_actions-0.0.70.dist-info/RECORD +16 -0
- {ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/WHEEL +1 -1
- ultralytics_actions-0.0.68.dist-info/RECORD +0 -15
- {ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.0.68.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')}"
|
actions/utils/openai_utils.py
CHANGED
@@ -9,7 +9,7 @@ import requests
|
|
9
9
|
from actions.utils.common_utils import check_links_in_string
|
10
10
|
|
11
11
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
12
|
-
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-
|
12
|
+
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4.1-2025-04-14")
|
13
13
|
SYSTEM_PROMPT_ADDITION = """
|
14
14
|
Guidance:
|
15
15
|
- Ultralytics Branding: Use YOLO11, YOLO12, etc., not YOLOv11, YOLOv12 (only older versions like YOLOv10 have a v). Always capitalize "HUB" in "Ultralytics HUB"; use "Ultralytics HUB", not "The Ultralytics HUB".
|
@@ -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>
|
@@ -59,8 +59,8 @@ Ultralytics Actions automatically applies formats, updates, and enhancements usi
|
|
59
59
|
- **Swift Code:** Formatted with [`swift-format`](https://github.com/swiftlang/swift-format) to maintain a uniform coding style across Swift projects. _(Note: Requires the `macos-latest` runner.)_
|
60
60
|
- **Spell Check:** Common misspellings are caught using [codespell](https://github.com/codespell-project/codespell).
|
61
61
|
- **Broken Links Check:** Broken links in documentation and Markdown files are identified using [Lychee](https://github.com/lycheeverse/lychee).
|
62
|
-
- **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-
|
63
|
-
- **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-
|
62
|
+
- **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-4.1, improving clarity and review efficiency.
|
63
|
+
- **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-4.1 for intelligent categorization.
|
64
64
|
|
65
65
|
## 🛠️ How It Works
|
66
66
|
|
@@ -69,9 +69,9 @@ Ultralytics Actions triggers on various GitHub events to streamline workflows:
|
|
69
69
|
- **Push Events:** Automatically formats code when changes are pushed to the `main` branch.
|
70
70
|
- **Pull Requests:**
|
71
71
|
- Ensures contributions meet formatting standards before merging.
|
72
|
-
- Generates a concise summary of changes using GPT-
|
73
|
-
- Applies relevant labels using GPT-
|
74
|
-
- **Issues:** Automatically applies relevant labels using GPT-
|
72
|
+
- Generates a concise summary of changes using GPT-4.1.
|
73
|
+
- Applies relevant labels using GPT-4.1 for intelligent categorization.
|
74
|
+
- **Issues:** Automatically applies relevant labels using GPT-4.1 when new issues are created.
|
75
75
|
|
76
76
|
These automated actions help maintain high code quality, improve documentation clarity, and streamline the review process by providing consistent formatting, informative summaries, and appropriate categorization.
|
77
77
|
|
@@ -101,13 +101,13 @@ To integrate this action into your Ultralytics repository:
|
|
101
101
|
uses: ultralytics/actions@main
|
102
102
|
with:
|
103
103
|
token: ${{ secrets.GITHUB_TOKEN }} # Automatically generated, do not modify
|
104
|
-
labels: true # Autolabel issues and PRs using GPT-
|
104
|
+
labels: true # Autolabel issues and PRs using GPT-4.1 (requires 'openai_api_key')
|
105
105
|
python: true # Format Python code and docstrings with Ruff and docformatter
|
106
106
|
prettier: true # Format YAML, JSON, Markdown, and CSS with Prettier
|
107
107
|
swift: false # Format Swift code with swift-format (requires 'runs-on: macos-latest')
|
108
108
|
spelling: true # Check spelling with codespell
|
109
109
|
links: true # Check for broken links with Lychee
|
110
|
-
summary: true # Generate PR summary with GPT-
|
110
|
+
summary: true # Generate PR summary with GPT-4.1 (requires 'openai_api_key')
|
111
111
|
openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Add your OpenAI API key as a repository secret
|
112
112
|
```
|
113
113
|
|
@@ -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=JRXgdKfSoTN4UJEHO2P9prHZYIHPLBQo9hbiRD1WC20,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=qQbmrJpOUANxSMf7inDSgPIwgf0JHD1fWZuab-y2W6g,2942
|
10
|
-
ultralytics_actions-0.0.68.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
11
|
-
ultralytics_actions-0.0.68.dist-info/METADATA,sha256=CpcyV3LeuvtCpvOAzycM_EwA3Gsnq190EPQQuTZ3vUw,10923
|
12
|
-
ultralytics_actions-0.0.68.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
13
|
-
ultralytics_actions-0.0.68.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
|
14
|
-
ultralytics_actions-0.0.68.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
15
|
-
ultralytics_actions-0.0.68.dist-info/RECORD,,
|
{ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/entry_points.txt
RENAMED
File without changes
|
{ultralytics_actions-0.0.68.dist-info → ultralytics_actions-0.0.70.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|