ultralytics-actions 0.0.58__py3-none-any.whl → 0.0.59__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.58"
25
+ __version__ = "0.0.59"
@@ -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
@@ -7,6 +8,7 @@ from urllib import parse
7
8
 
8
9
  import requests
9
10
 
11
+ BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
10
12
  REQUESTS_HEADERS = {
11
13
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
12
14
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
@@ -24,6 +26,7 @@ REQUESTS_HEADERS = {
24
26
  }
25
27
  BAD_HTTP_CODES = frozenset(
26
28
  {
29
+ # 204, # No content
27
30
  # 403, # Forbidden - client lacks permission to access the resource (commented as works in browser typically)
28
31
  404, # Not Found - requested resource doesn't exist
29
32
  405, # Method Not Allowed - HTTP method not supported for this endpoint
@@ -32,6 +35,7 @@ BAD_HTTP_CODES = frozenset(
32
35
  502, # Bad Gateway - upstream server sent invalid response
33
36
  503, # Service Unavailable - server temporarily unable to handle request
34
37
  504, # Gateway Timeout - upstream server didn't respond in time
38
+ 525, # Cloudfare handshake error
35
39
  }
36
40
  )
37
41
  URL_IGNORE_LIST = { # use a set (not frozenset) to update with possible private GitHub repos
@@ -80,6 +84,16 @@ def clean_url(url):
80
84
  return url
81
85
 
82
86
 
87
+ def brave_search(query, api_key, count=5):
88
+ """Search for alternative URLs using Brave Search API."""
89
+ headers = {"X-Subscription-Token": api_key, "Accept": "application/json"}
90
+ url = f"https://api.search.brave.com/res/v1/web/search?q={parse.quote(query)}&count={count}"
91
+ response = requests.get(url, headers=headers)
92
+ data = response.json() if response.status_code == 200 else {}
93
+ results = data.get("web", {}).get("results", []) if data else []
94
+ return [result.get("url") for result in results if result.get("url")]
95
+
96
+
83
97
  def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
84
98
  """Check if string is URL and optionally verify it exists, with fallback for GitHub repos."""
85
99
  try:
@@ -103,7 +117,8 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
103
117
  try:
104
118
  # Try HEAD first, then GET if needed
105
119
  for method in (requester.head, requester.get):
106
- if method(url, stream=method == requester.get, **kwargs).status_code not in BAD_HTTP_CODES:
120
+ status_code = method(url, stream=method == requester.get, **kwargs).status_code
121
+ if status_code not in BAD_HTTP_CODES:
107
122
  return True
108
123
 
109
124
  # If GitHub and check fails (repo might be private), add the base GitHub URL to ignore list
@@ -126,23 +141,50 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
126
141
  return False
127
142
 
128
143
 
129
- def check_links_in_string(text, verbose=True, return_bad=False):
144
+ def check_links_in_string(text, verbose=True, return_bad=False, replace=False):
130
145
  """Process a given text, find unique URLs within it, and check for any 404 errors."""
131
146
  all_urls = []
132
147
  for md_text, md_url, plain_url in URL_PATTERN.findall(text):
133
148
  url = md_url or plain_url
134
149
  if url and parse.urlparse(url).scheme:
135
- all_urls.append(url)
150
+ all_urls.append((md_text, url, md_url != ""))
151
+
152
+ urls = [(t, clean_url(u), is_md) for t, u, is_md in all_urls] # clean URLs
136
153
 
137
- urls = set(map(clean_url, all_urls)) # remove extra characters and make unique
138
154
  with requests.Session() as session, ThreadPoolExecutor(max_workers=16) as executor:
139
155
  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]
156
+ valid_results = list(executor.map(lambda x: is_url(x[1], session), urls))
157
+ bad_urls = [url for (_, url, _), valid in zip(urls, valid_results) if not valid]
158
+
159
+ if replace and bad_urls and BRAVE_API_KEY:
160
+ replacements = {}
161
+ modified_text = text
162
+
163
+ for (title, url, is_md), valid in zip(urls, valid_results):
164
+ if not valid:
165
+ alternative_urls = brave_search(f"{title} {url}", BRAVE_API_KEY, count=3)
166
+ if alternative_urls:
167
+ # Try each alternative URL until we find one that works
168
+ for alt_url in alternative_urls:
169
+ if is_url(alt_url, session):
170
+ break
171
+ replacements[url] = alt_url
172
+ modified_text = modified_text.replace(url, alt_url)
173
+
174
+ if verbose and replacements:
175
+ print(
176
+ f"WARNING ⚠️ replaced {len(replacements)} broken links:\n"
177
+ + "\n".join(f" {k}: {v}" for k, v in replacements.items())
178
+ )
179
+ if replacements:
180
+ return (True, [], modified_text) if return_bad else modified_text
141
181
 
142
182
  passing = not bad_urls
143
183
  if verbose and not passing:
144
184
  print(f"WARNING ⚠️ errors found in URLs {bad_urls}")
145
185
 
186
+ if replace:
187
+ return (passing, bad_urls, text) if return_bad else text
146
188
  return (passing, bad_urls) if return_bad else passing
147
189
 
148
190
 
@@ -152,3 +194,4 @@ if __name__ == "__main__":
152
194
 
153
195
  print(f"is_url(): {is_url(url)}")
154
196
  print(f"check_links_in_string(): {check_links_in_string(string)}")
197
+ 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.59
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>
@@ -1,15 +1,15 @@
1
- actions/__init__.py,sha256=6Y9w-2wmcfeoTzPGK6EuKj1QUEZQ4puqm_g29PweRMY,742
1
+ actions/__init__.py,sha256=RivsSzZZVLYTkIVSK4dsOzKSKmmIsMsFi-5_WJnRZzM,742
2
2
  actions/first_interaction.py,sha256=1_WvQHCi5RWaSfyi49ClF2Zk_3CKGjFnZqz6FlxPRAc,17868
3
3
  actions/summarize_pr.py,sha256=BKttOq-MGaanVaChLU5B1ewKUA8K6S05Cy3FQtyRmxU,11681
4
4
  actions/summarize_release.py,sha256=tov6qsYGC68lfobvkwVyoWZBGtJ598G0m097n4Ydzvo,8472
5
5
  actions/update_markdown_code_blocks.py,sha256=9PL7YIQfApRNAa0que2hYHv7umGZTZoHlblesB0xFj4,8587
6
6
  actions/utils/__init__.py,sha256=WStdEAYROVnF0nubEOmrFLrejkRiMXIefA5O1ckfcFs,476
7
- actions/utils/common_utils.py,sha256=0ZUphOWBu8gFWnFVnyye2UX8iJCtSgxv_nZYFiyER8M,6094
7
+ actions/utils/common_utils.py,sha256=K7um3mHwx0sWAWfnEcDqfRs_x0Kr1mBhPuKbUfl1tWw,8150
8
8
  actions/utils/github_utils.py,sha256=-F--JgxtXE0fSPMFEzakz7iZilp-vonzLiyXfg0b17Y,7117
9
9
  actions/utils/openai_utils.py,sha256=qQbmrJpOUANxSMf7inDSgPIwgf0JHD1fWZuab-y2W6g,2942
10
- ultralytics_actions-0.0.58.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
- ultralytics_actions-0.0.58.dist-info/METADATA,sha256=eQGJsnbvwi1MNC3vsQUZlK8w6kWphACueNhQuvMpows,10923
12
- ultralytics_actions-0.0.58.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
13
- ultralytics_actions-0.0.58.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
- ultralytics_actions-0.0.58.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
- ultralytics_actions-0.0.58.dist-info/RECORD,,
10
+ ultralytics_actions-0.0.59.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
11
+ ultralytics_actions-0.0.59.dist-info/METADATA,sha256=D2M-beZpJYd0gC27E_d-iriL8zfb-YUkMFzydowf9rc,10923
12
+ ultralytics_actions-0.0.59.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
13
+ ultralytics_actions-0.0.59.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
14
+ ultralytics_actions-0.0.59.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
15
+ ultralytics_actions-0.0.59.dist-info/RECORD,,