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 +1 -1
- actions/utils/common_utils.py +48 -5
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/METADATA +1 -1
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/RECORD +8 -8
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/WHEEL +0 -0
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/top_level.txt +0 -0
actions/__init__.py
CHANGED
actions/utils/common_utils.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
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=
|
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=
|
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.
|
11
|
-
ultralytics_actions-0.0.
|
12
|
-
ultralytics_actions-0.0.
|
13
|
-
ultralytics_actions-0.0.
|
14
|
-
ultralytics_actions-0.0.
|
15
|
-
ultralytics_actions-0.0.
|
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,,
|
File without changes
|
{ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/entry_points.txt
RENAMED
File without changes
|
{ultralytics_actions-0.0.58.dist-info → ultralytics_actions-0.0.59.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|