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 CHANGED
@@ -22,4 +22,4 @@
22
22
  # ├── test_summarize_pr.py
23
23
  # └── ...
24
24
 
25
- __version__ = "0.0.69"
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')}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.0.69
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,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=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,,