ultralytics-actions 0.0.58__tar.gz → 0.0.60__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 (21) hide show
  1. {ultralytics_actions-0.0.58/ultralytics_actions.egg-info → ultralytics_actions-0.0.60}/PKG-INFO +1 -1
  2. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/__init__.py +1 -1
  3. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/utils/common_utils.py +50 -5
  4. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60/ultralytics_actions.egg-info}/PKG-INFO +1 -1
  5. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/LICENSE +0 -0
  6. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/README.md +0 -0
  7. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/first_interaction.py +0 -0
  8. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/summarize_pr.py +0 -0
  9. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/summarize_release.py +0 -0
  10. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/update_markdown_code_blocks.py +0 -0
  11. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/utils/__init__.py +0 -0
  12. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/utils/github_utils.py +0 -0
  13. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/actions/utils/openai_utils.py +0 -0
  14. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/pyproject.toml +0 -0
  15. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/setup.cfg +0 -0
  16. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/tests/test_urls.py +0 -0
  17. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  18. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  19. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  20. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/ultralytics_actions.egg-info/requires.txt +0 -0
  21. {ultralytics_actions-0.0.58 → ultralytics_actions-0.0.60}/ultralytics_actions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.0.58
3
+ Version: 0.0.60
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.58"
25
+ __version__ = "0.0.60"
@@ -1,5 +1,6 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
+ import os
3
4
  import re
4
5
  import time
5
6
  from concurrent.futures import ThreadPoolExecutor
@@ -24,6 +25,7 @@ REQUESTS_HEADERS = {
24
25
  }
25
26
  BAD_HTTP_CODES = frozenset(
26
27
  {
28
+ # 204, # No content
27
29
  # 403, # Forbidden - client lacks permission to access the resource (commented as works in browser typically)
28
30
  404, # Not Found - requested resource doesn't exist
29
31
  405, # Method Not Allowed - HTTP method not supported for this endpoint
@@ -32,6 +34,7 @@ BAD_HTTP_CODES = frozenset(
32
34
  502, # Bad Gateway - upstream server sent invalid response
33
35
  503, # Service Unavailable - server temporarily unable to handle request
34
36
  504, # Gateway Timeout - upstream server didn't respond in time
37
+ 525, # Cloudfare handshake error
35
38
  }
36
39
  )
37
40
  URL_IGNORE_LIST = { # use a set (not frozenset) to update with possible private GitHub repos
@@ -80,6 +83,18 @@ def clean_url(url):
80
83
  return url
81
84
 
82
85
 
86
+ def brave_search(query, api_key, count=5):
87
+ """Search for alternative URLs using Brave Search API."""
88
+ headers = {"X-Subscription-Token": api_key, "Accept": "application/json"}
89
+ if len(query) > 400:
90
+ print(f"WARNING ⚠️ Brave search query length {len(query)} exceed limit of 400 characters, truncating.")
91
+ url = f"https://api.search.brave.com/res/v1/web/search?q={parse.quote(query.strip()[:400])}&count={count}"
92
+ response = requests.get(url, headers=headers)
93
+ data = response.json() if response.status_code == 200 else {}
94
+ results = data.get("web", {}).get("results", []) if data else []
95
+ return [result.get("url") for result in results if result.get("url")]
96
+
97
+
83
98
  def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
84
99
  """Check if string is URL and optionally verify it exists, with fallback for GitHub repos."""
85
100
  try:
@@ -103,7 +118,8 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
103
118
  try:
104
119
  # Try HEAD first, then GET if needed
105
120
  for method in (requester.head, requester.get):
106
- if method(url, stream=method == requester.get, **kwargs).status_code not in BAD_HTTP_CODES:
121
+ status_code = method(url, stream=method == requester.get, **kwargs).status_code
122
+ if status_code not in BAD_HTTP_CODES:
107
123
  return True
108
124
 
109
125
  # If GitHub and check fails (repo might be private), add the base GitHub URL to ignore list
@@ -126,23 +142,51 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
126
142
  return False
127
143
 
128
144
 
129
- def check_links_in_string(text, verbose=True, return_bad=False):
145
+ def check_links_in_string(text, verbose=True, return_bad=False, replace=False):
130
146
  """Process a given text, find unique URLs within it, and check for any 404 errors."""
131
147
  all_urls = []
132
148
  for md_text, md_url, plain_url in URL_PATTERN.findall(text):
133
149
  url = md_url or plain_url
134
150
  if url and parse.urlparse(url).scheme:
135
- all_urls.append(url)
151
+ all_urls.append((md_text, url, md_url != ""))
152
+
153
+ urls = [(t, clean_url(u), is_md) for t, u, is_md in all_urls] # clean URLs
136
154
 
137
- urls = set(map(clean_url, all_urls)) # remove extra characters and make unique
138
155
  with requests.Session() as session, ThreadPoolExecutor(max_workers=16) as executor:
139
156
  session.headers.update(REQUESTS_HEADERS)
140
- bad_urls = [url for url, valid in zip(urls, executor.map(lambda x: not is_url(x, session), urls)) if valid]
157
+ valid_results = list(executor.map(lambda x: is_url(x[1], session), urls))
158
+ bad_urls = [url for (_, url, _), valid in zip(urls, valid_results) if not valid]
159
+
160
+ if replace and bad_urls:
161
+ if brave_api_key := os.getenv("BRAVE_API_KEY"):
162
+ replacements = {}
163
+ modified_text = text
164
+
165
+ for (title, url, is_md), valid in zip(urls, valid_results):
166
+ if not valid:
167
+ alternative_urls = brave_search(f"{title[:200]} {url[:200]}", brave_api_key, count=3)
168
+ if alternative_urls:
169
+ # Try each alternative URL until we find one that works
170
+ for alt_url in alternative_urls:
171
+ if is_url(alt_url, session):
172
+ break
173
+ replacements[url] = alt_url
174
+ modified_text = modified_text.replace(url, alt_url)
175
+
176
+ if verbose and replacements:
177
+ print(
178
+ f"WARNING ⚠️ replaced {len(replacements)} broken links:\n"
179
+ + "\n".join(f" {k}: {v}" for k, v in replacements.items())
180
+ )
181
+ if replacements:
182
+ return (True, [], modified_text) if return_bad else modified_text
141
183
 
142
184
  passing = not bad_urls
143
185
  if verbose and not passing:
144
186
  print(f"WARNING ⚠️ errors found in URLs {bad_urls}")
145
187
 
188
+ if replace:
189
+ return (passing, bad_urls, text) if return_bad else text
146
190
  return (passing, bad_urls) if return_bad else passing
147
191
 
148
192
 
@@ -152,3 +196,4 @@ if __name__ == "__main__":
152
196
 
153
197
  print(f"is_url(): {is_url(url)}")
154
198
  print(f"check_links_in_string(): {check_links_in_string(string)}")
199
+ print(f"check_links_in_string() with replace: {check_links_in_string(string, replace=True)}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.0.58
3
+ Version: 0.0.60
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>