ultralytics-actions 0.0.62__py3-none-any.whl → 0.0.64__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/__init__.py +4 -7
- actions/utils/common_utils.py +95 -44
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/METADATA +1 -1
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/RECORD +9 -9
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/WHEEL +0 -0
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/top_level.txt +0 -0
actions/__init__.py
CHANGED
actions/utils/__init__.py
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
|
-
from .common_utils import REQUESTS_HEADERS, remove_html_comments
|
4
|
-
from .github_utils import
|
5
|
-
GITHUB_API_URL,
|
6
|
-
Action,
|
7
|
-
check_pypi_version,
|
8
|
-
ultralytics_actions_info,
|
9
|
-
)
|
3
|
+
from .common_utils import REDIRECT_IGNORE_LIST, REQUESTS_HEADERS, URL_IGNORE_LIST, remove_html_comments
|
4
|
+
from .github_utils import GITHUB_API_URL, Action, check_pypi_version, ultralytics_actions_info
|
10
5
|
from .openai_utils import get_completion
|
11
6
|
|
12
7
|
__all__ = (
|
13
8
|
"GITHUB_API_URL",
|
14
9
|
"REQUESTS_HEADERS",
|
10
|
+
"URL_IGNORE_LIST",
|
11
|
+
"REDIRECT_IGNORE_LIST",
|
15
12
|
"Action",
|
16
13
|
"check_pypi_version",
|
17
14
|
"get_completion",
|
actions/utils/common_utils.py
CHANGED
@@ -58,6 +58,43 @@ URL_IGNORE_LIST = { # use a set (not frozenset) to update with possible private
|
|
58
58
|
"(", # breaks pattern matches
|
59
59
|
"api", # ignore api endpoints
|
60
60
|
}
|
61
|
+
REDIRECT_IGNORE_LIST = {
|
62
|
+
"{", # possible f-string
|
63
|
+
"}", # possible f-string
|
64
|
+
"/es/",
|
65
|
+
"/us/",
|
66
|
+
"en-us",
|
67
|
+
"es-es",
|
68
|
+
"/latest/",
|
69
|
+
"/2022/",
|
70
|
+
"/2023/",
|
71
|
+
"/2024/",
|
72
|
+
"/2025/",
|
73
|
+
"/2026/",
|
74
|
+
"/2027/",
|
75
|
+
"/2028/",
|
76
|
+
"/2029/",
|
77
|
+
"/2030/",
|
78
|
+
"credential",
|
79
|
+
"login",
|
80
|
+
"consent",
|
81
|
+
"verify",
|
82
|
+
"badge",
|
83
|
+
"shields.io",
|
84
|
+
"bit.ly",
|
85
|
+
"ow.ly",
|
86
|
+
"https://youtu.be/",
|
87
|
+
"latex.codecogs.com",
|
88
|
+
"svg.image",
|
89
|
+
"?view=azureml",
|
90
|
+
"ultralytics.com/actions",
|
91
|
+
"ultralytics.com/bilibili",
|
92
|
+
"ultralytics.com/images",
|
93
|
+
"app.gong.io/call?",
|
94
|
+
"https://code.visualstudio.com/", # errors
|
95
|
+
"?rdt=", # problems with reddit redirecting to https://www.reddit.com/r/ultralytics/?rdt=48616
|
96
|
+
"objects.githubusercontent.com", # Prevent replacement with temporary signed GitHub asset URLs
|
97
|
+
}
|
61
98
|
URL_PATTERN = re.compile(
|
62
99
|
r"\[([^]]+)]\(([^)]+)\)" # Matches Markdown links [text](url)
|
63
100
|
r"|"
|
@@ -83,8 +120,16 @@ def clean_url(url):
|
|
83
120
|
return url
|
84
121
|
|
85
122
|
|
123
|
+
def allow_redirect(url):
|
124
|
+
"""Check if URL should be skipped based on simple rules."""
|
125
|
+
url_lower = url.lower()
|
126
|
+
return url and url.startswith("https://") and not any(item in url_lower for item in REDIRECT_IGNORE_LIST)
|
127
|
+
|
128
|
+
|
86
129
|
def brave_search(query, api_key, count=5):
|
87
130
|
"""Search for alternative URLs using Brave Search API."""
|
131
|
+
if not api_key:
|
132
|
+
return
|
88
133
|
headers = {"X-Subscription-Token": api_key, "Accept": "application/json"}
|
89
134
|
if len(query) > 400:
|
90
135
|
print(f"WARNING ⚠️ Brave search query length {len(query)} exceed limit of 400 characters, truncating.")
|
@@ -95,18 +140,18 @@ def brave_search(query, api_key, count=5):
|
|
95
140
|
return [result.get("url") for result in results if result.get("url")]
|
96
141
|
|
97
142
|
|
98
|
-
def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
|
143
|
+
def is_url(url, session=None, check=True, max_attempts=3, timeout=2, return_url=False, redirect=False):
|
99
144
|
"""Check if string is URL and optionally verify it exists, with fallback for GitHub repos."""
|
100
145
|
try:
|
101
146
|
# Check allow list
|
102
147
|
if any(x in url for x in URL_IGNORE_LIST):
|
103
|
-
return True
|
148
|
+
return (True, url) if return_url else True
|
104
149
|
|
105
150
|
# Check structure
|
106
151
|
result = parse.urlparse(url)
|
107
152
|
partition = result.netloc.partition(".") # i.e. netloc = "github.com" -> ("github", ".", "com")
|
108
153
|
if not result.scheme or not partition[0] or not partition[2]:
|
109
|
-
return False
|
154
|
+
return (False, url) if return_url else False
|
110
155
|
|
111
156
|
if check:
|
112
157
|
requester = session or requests
|
@@ -118,9 +163,11 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
|
|
118
163
|
try:
|
119
164
|
# Try HEAD first, then GET if needed
|
120
165
|
for method in (requester.head, requester.get):
|
121
|
-
|
122
|
-
if
|
123
|
-
|
166
|
+
response = method(url, stream=method == requester.get, **kwargs)
|
167
|
+
if redirect and allow_redirect(response.url):
|
168
|
+
url = response.url
|
169
|
+
if response.status_code not in BAD_HTTP_CODES:
|
170
|
+
return (True, url) if return_url else True
|
124
171
|
|
125
172
|
# If GitHub and check fails (repo might be private), add the base GitHub URL to ignore list
|
126
173
|
if result.hostname == "github.com":
|
@@ -129,57 +176,61 @@ def is_url(url, session=None, check=True, max_attempts=3, timeout=2):
|
|
129
176
|
base_url = f"https://github.com/{parts[0]}/{parts[1]}" # https://github.com/org/repo
|
130
177
|
if requester.head(base_url, **kwargs).status_code == 404:
|
131
178
|
URL_IGNORE_LIST.add(base_url)
|
132
|
-
return True
|
179
|
+
return (True, url) if return_url else True
|
133
180
|
|
134
|
-
return False
|
181
|
+
return (False, url) if return_url else False
|
135
182
|
except Exception:
|
136
183
|
if attempt == max_attempts - 1: # last attempt
|
137
|
-
return False
|
184
|
+
return (False, url) if return_url else False
|
138
185
|
time.sleep(2**attempt) # exponential backoff
|
139
|
-
return False
|
140
|
-
return True
|
186
|
+
return (False, url) if return_url else False
|
187
|
+
return (True, url) if return_url else True
|
141
188
|
except Exception:
|
142
|
-
return False
|
189
|
+
return (False, url) if return_url else False
|
143
190
|
|
144
191
|
|
145
|
-
def check_links_in_string(text, verbose=True, return_bad=False, replace=False):
|
146
|
-
"""Process
|
147
|
-
|
192
|
+
def check_links_in_string(text, verbose=True, return_bad=False, replace=False, redirect=True):
|
193
|
+
"""Process text, find URLs, check for 404s, and handle replacements with redirects or Brave search."""
|
194
|
+
urls = []
|
148
195
|
for md_text, md_url, plain_url in URL_PATTERN.findall(text):
|
149
196
|
url = md_url or plain_url
|
150
197
|
if url and parse.urlparse(url).scheme:
|
151
|
-
|
152
|
-
|
153
|
-
urls = [(t, clean_url(u), is_md) for t, u, is_md in all_urls] # clean URLs
|
198
|
+
urls.append((md_text, clean_url(url)))
|
154
199
|
|
155
200
|
with requests.Session() as session, ThreadPoolExecutor(max_workers=64) as executor:
|
156
201
|
session.headers.update(REQUESTS_HEADERS)
|
157
|
-
|
158
|
-
bad_urls = [url for (
|
159
|
-
|
160
|
-
if replace
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
)
|
181
|
-
|
182
|
-
|
202
|
+
results = list(executor.map(lambda x: is_url(x[1], session, return_url=True, redirect=redirect), urls))
|
203
|
+
bad_urls = [url for (title, url), (valid, redirect) in zip(urls, results) if not valid]
|
204
|
+
|
205
|
+
if replace:
|
206
|
+
replacements = {}
|
207
|
+
modified_text = text
|
208
|
+
|
209
|
+
# Process all URLs for replacements
|
210
|
+
brave_api_key = os.getenv("BRAVE_API_KEY")
|
211
|
+
for (title, url), (valid, redirect) in zip(urls, results):
|
212
|
+
# Handle invalid URLs with Brave search
|
213
|
+
if not valid and brave_api_key:
|
214
|
+
alternative_urls = brave_search(f"{title[:200]} {url[:200]}", brave_api_key, count=3)
|
215
|
+
if alternative_urls:
|
216
|
+
# Try each alternative URL until we find one that works
|
217
|
+
for alt_url in alternative_urls:
|
218
|
+
if is_url(alt_url, session):
|
219
|
+
replacements[url] = alt_url
|
220
|
+
modified_text = modified_text.replace(url, alt_url)
|
221
|
+
break
|
222
|
+
# Handle redirects for valid URLs
|
223
|
+
elif valid and redirect and redirect != url:
|
224
|
+
replacements[url] = redirect
|
225
|
+
modified_text = modified_text.replace(url, redirect)
|
226
|
+
|
227
|
+
if verbose and replacements:
|
228
|
+
print(
|
229
|
+
f"WARNING ⚠️ replaced {len(replacements)} links:\n"
|
230
|
+
+ "\n".join(f" {k}: {v}" for k, v in replacements.items())
|
231
|
+
)
|
232
|
+
if replacements:
|
233
|
+
return (True, bad_urls, modified_text) if return_bad else modified_text
|
183
234
|
|
184
235
|
passing = not bad_urls
|
185
236
|
if verbose and not passing:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ultralytics-actions
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.64
|
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=_0iCAgCQC2PbO-T1qslfJ9W1VETJy0oGXGI1hGhHklQ,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
|
-
actions/utils/__init__.py,sha256=
|
7
|
-
actions/utils/common_utils.py,sha256=
|
6
|
+
actions/utils/__init__.py,sha256=XjFyREuhiA7pHfHHBQQqHYKokHhq_px1P9sezk_f1vA,545
|
7
|
+
actions/utils/common_utils.py,sha256=KIwFev9mGdgD7Z5IT0UuHNgZi-phKcZ1A1pggBU8Xbo,10191
|
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.64.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
11
|
+
ultralytics_actions-0.0.64.dist-info/METADATA,sha256=GpiuJf5WlfPKaSl8hCZsXDH-JuKUJunJzweiokhJzAg,10923
|
12
|
+
ultralytics_actions-0.0.64.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
13
|
+
ultralytics_actions-0.0.64.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
|
14
|
+
ultralytics_actions-0.0.64.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
15
|
+
ultralytics_actions-0.0.64.dist-info/RECORD,,
|
File without changes
|
{ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/entry_points.txt
RENAMED
File without changes
|
{ultralytics_actions-0.0.62.dist-info → ultralytics_actions-0.0.64.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|