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 CHANGED
@@ -22,4 +22,4 @@
22
22
  # ├── test_summarize_pr.py
23
23
  # └── ...
24
24
 
25
- __version__ = "0.0.68"
25
+ __version__ = "0.0.70"
@@ -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()
@@ -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:
@@ -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')}"
@@ -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-4o-2024-11-20")
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.68
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-4o, improving clarity and review efficiency.
63
- - **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-4o for intelligent categorization.
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-4o.
73
- - Applies relevant labels using GPT-4o for intelligent categorization.
74
- - **Issues:** Automatically applies relevant labels using GPT-4o when new issues are created.
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-4o (requires 'openai_api_key')
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-4o (requires 'openai_api_key')
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,