ultralytics-actions 0.0.34__tar.gz → 0.0.37__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.
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/PKG-INFO +3 -4
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/__init__.py +1 -1
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/utils/common_utils.py +22 -16
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/pyproject.toml +6 -3
- ultralytics_actions-0.0.37/tests/test_urls.py +98 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/PKG-INFO +3 -4
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/SOURCES.txt +1 -0
- ultralytics_actions-0.0.37/ultralytics_actions.egg-info/requires.txt +6 -0
- ultralytics_actions-0.0.34/ultralytics_actions.egg-info/requires.txt +0 -7
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/LICENSE +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/README.md +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/first_interaction.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/summarize_pr.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/summarize_release.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/update_markdown_code_blocks.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/utils/__init__.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/utils/github_utils.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/utils/openai_utils.py +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/setup.cfg +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/entry_points.txt +0 -0
- {ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ultralytics-actions
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.37
|
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>
|
@@ -28,12 +28,11 @@ Classifier: Operating System :: OS Independent
|
|
28
28
|
Requires-Python: >=3.8
|
29
29
|
Description-Content-Type: text/markdown
|
30
30
|
License-File: LICENSE
|
31
|
-
Requires-Dist: requests>=2.
|
32
|
-
Requires-Dist: ruff>=0.
|
31
|
+
Requires-Dist: requests>=2.32.3
|
32
|
+
Requires-Dist: ruff>=0.8.4
|
33
33
|
Requires-Dist: docformatter>=1.7.5
|
34
34
|
Provides-Extra: dev
|
35
35
|
Requires-Dist: pytest; extra == "dev"
|
36
|
-
Requires-Dist: black; extra == "dev"
|
37
36
|
|
38
37
|
<a href="https://www.ultralytics.com/" target="_blank"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
39
38
|
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# Ultralytics Actions 🚀, AGPL-3.0 license https://ultralytics.com/license
|
2
2
|
|
3
3
|
import re
|
4
|
-
import socket
|
5
4
|
import time
|
6
|
-
import urllib
|
7
5
|
from concurrent.futures import ThreadPoolExecutor
|
6
|
+
from urllib import parse
|
7
|
+
|
8
|
+
import requests
|
8
9
|
|
9
10
|
|
10
11
|
def remove_html_comments(body: str) -> str:
|
@@ -37,6 +38,10 @@ def is_url(url, check=True, max_attempts=3, timeout=2):
|
|
37
38
|
"github.com", # ignore GitHub links that may be private repos
|
38
39
|
"kaggle.com", # blocks automated header requests
|
39
40
|
"reddit.com", # blocks automated header requests
|
41
|
+
"linkedin.com",
|
42
|
+
"twitter.com",
|
43
|
+
"x.com",
|
44
|
+
"storage.googleapis.com", # private GCS buckets
|
40
45
|
)
|
41
46
|
try:
|
42
47
|
# Check allow list
|
@@ -44,24 +49,23 @@ def is_url(url, check=True, max_attempts=3, timeout=2):
|
|
44
49
|
return True
|
45
50
|
|
46
51
|
# Check structure
|
47
|
-
result =
|
48
|
-
|
52
|
+
result = parse.urlparse(url)
|
53
|
+
partition = result.netloc.partition(".") # i.e. netloc = "github.com" -> ("github", ".", "com")
|
54
|
+
if not result.scheme or not partition[0] or not partition[2]:
|
49
55
|
return False
|
50
56
|
|
51
57
|
# Check response
|
52
58
|
if check:
|
53
59
|
for attempt in range(max_attempts):
|
54
60
|
try:
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
)
|
62
|
-
|
63
|
-
return response.getcode() < 400
|
64
|
-
except (urllib.error.URLError, socket.timeout):
|
61
|
+
headers = {
|
62
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
|
63
|
+
"Accept": "*",
|
64
|
+
"Accept-Language": "*",
|
65
|
+
"Accept-Encoding": "*",
|
66
|
+
}
|
67
|
+
return requests.head(url, headers=headers, timeout=timeout, allow_redirects=True).status_code < 400
|
68
|
+
except Exception:
|
65
69
|
if attempt == max_attempts - 1: # last attempt
|
66
70
|
return False
|
67
71
|
time.sleep(2**attempt) # exponential backoff
|
@@ -79,18 +83,20 @@ def check_links_in_string(text, verbose=True, return_bad=False):
|
|
79
83
|
r"(" # Start capturing group for plaintext URLs
|
80
84
|
r"(?:https?://)?" # Optional http:// or https://
|
81
85
|
r"(?:www\.)?" # Optional www.
|
82
|
-
r"[\w.-]+" #
|
86
|
+
r"(?:[\w.-]+)?" # Optional domain name and subdomains
|
83
87
|
r"\.[a-zA-Z]{2,}" # TLD
|
84
88
|
r"(?:/[^\s\"')\]]*)?" # Optional path
|
85
89
|
r")"
|
86
90
|
)
|
91
|
+
# all_urls.extend([url for url in match if url and parse.urlparse(url).scheme])
|
87
92
|
all_urls = []
|
88
93
|
for md_text, md_url, plain_url in re.findall(pattern, text):
|
89
94
|
url = md_url or plain_url
|
90
|
-
if url and
|
95
|
+
if url and parse.urlparse(url).scheme:
|
91
96
|
all_urls.append(url)
|
92
97
|
|
93
98
|
urls = set(map(clean_url, all_urls)) # remove extra characters and make unique
|
99
|
+
# bad_urls = [x for x in urls if not is_url(x, check=True)] # single-thread
|
94
100
|
with ThreadPoolExecutor(max_workers=16) as executor: # multi-thread
|
95
101
|
bad_urls = [url for url, valid in zip(urls, executor.map(lambda x: not is_url(x, check=True), urls)) if valid]
|
96
102
|
|
@@ -65,15 +65,14 @@ classifiers = [
|
|
65
65
|
]
|
66
66
|
|
67
67
|
dependencies = [
|
68
|
-
"requests>=2.
|
69
|
-
"ruff>=0.
|
68
|
+
"requests>=2.32.3",
|
69
|
+
"ruff>=0.8.4",
|
70
70
|
"docformatter>=1.7.5",
|
71
71
|
]
|
72
72
|
|
73
73
|
[project.optional-dependencies]
|
74
74
|
dev = [
|
75
75
|
"pytest",
|
76
|
-
"black",
|
77
76
|
]
|
78
77
|
|
79
78
|
[project.urls]
|
@@ -96,6 +95,10 @@ packages = { find = { where = ["."], include = ["actions", "actions.*"] } }
|
|
96
95
|
[tool.setuptools.dynamic]
|
97
96
|
version = { attr = "actions.__version__" }
|
98
97
|
|
98
|
+
[tool.pytest.ini_options]
|
99
|
+
addopts = "--doctest-modules --durations=30 --color=yes"
|
100
|
+
norecursedirs = [".git", "dist", "build"]
|
101
|
+
|
99
102
|
[tool.ruff]
|
100
103
|
line-length = 120
|
101
104
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Ultralytics Actions 🚀, AGPL-3.0 license
|
2
|
+
# Continuous Integration (CI) GitHub Actions tests
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from actions.utils.common_utils import check_links_in_string, is_url
|
7
|
+
|
8
|
+
URLS = [
|
9
|
+
"https://docs.ultralytics.com/help/CLA/",
|
10
|
+
"https://docs.ultralytics.com/help/contributing",
|
11
|
+
"https://docs.ultralytics.com",
|
12
|
+
"https://ultralytics.com",
|
13
|
+
"https://github.com/ultralytics/ultralytics",
|
14
|
+
]
|
15
|
+
|
16
|
+
|
17
|
+
@pytest.fixture
|
18
|
+
def verbose():
|
19
|
+
"""Fixture that provides a verbose logging utility for detailed output during testing and debugging."""
|
20
|
+
return False # Set False to suppress print statements during tests
|
21
|
+
|
22
|
+
|
23
|
+
def test_is_url():
|
24
|
+
"""Test each URL using is_url function."""
|
25
|
+
for url in URLS:
|
26
|
+
assert is_url(url), f"URL check failed: {url}"
|
27
|
+
|
28
|
+
|
29
|
+
def test_html_links(verbose):
|
30
|
+
"""Tests the validity of URLs within HTML anchor tags and returns any invalid URLs found."""
|
31
|
+
text = "Visit <a href='https://err.com'>our site</a> or <a href=\"http://test.org\">test site</a>"
|
32
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
33
|
+
assert result is False
|
34
|
+
assert set(urls) == {"https://err.com", "http://test.org"}
|
35
|
+
|
36
|
+
|
37
|
+
def test_markdown_links(verbose):
|
38
|
+
"""Validates URLs in markdown links within a given text using check_links_in_string."""
|
39
|
+
text = "Check [Example](https://err.com) or [Test](http://test.org)"
|
40
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
41
|
+
assert result is False
|
42
|
+
assert set(urls) == {"https://err.com", "http://test.org"}
|
43
|
+
|
44
|
+
|
45
|
+
def test_mixed_formats(verbose):
|
46
|
+
"""Tests URL detection in mixed text formats (HTML, Markdown, plain text) using check_links_in_string."""
|
47
|
+
text = "A <a href='https://1.com'>link</a> and [markdown](https://2.org) and https://3.net"
|
48
|
+
result, urls = check_links_in_string(text, return_bad=True)
|
49
|
+
assert result is False
|
50
|
+
assert set(urls) == {"https://1.com", "https://2.org", "https://3.net"}
|
51
|
+
|
52
|
+
|
53
|
+
def test_duplicate_urls(verbose):
|
54
|
+
"""Tests detection of duplicate URLs in various text formats using the check_links_in_string function."""
|
55
|
+
text = "Same URL: https://err.com and <a href='https://err.com'>link</a>"
|
56
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
57
|
+
assert result is False
|
58
|
+
assert set(urls) == {"https://err.com"}
|
59
|
+
|
60
|
+
|
61
|
+
def test_no_urls(verbose):
|
62
|
+
"""Tests that a string with no URLs returns True when checked using the check_links_in_string function."""
|
63
|
+
text = "This text contains no URLs."
|
64
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
65
|
+
assert result is True
|
66
|
+
assert not set(urls)
|
67
|
+
|
68
|
+
|
69
|
+
def test_invalid_urls(verbose):
|
70
|
+
"""Test invalid URLs."""
|
71
|
+
text = "Invalid URL: http://.com"
|
72
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
73
|
+
assert result is False
|
74
|
+
assert set(urls) == {"http://.com"}
|
75
|
+
|
76
|
+
|
77
|
+
def test_urls_with_paths_and_queries(verbose):
|
78
|
+
"""Test URLs with paths and query parameters to ensure they are correctly identified and validated."""
|
79
|
+
text = "Complex URL: https://err.com/path?query=value#fragment"
|
80
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
81
|
+
assert result is False
|
82
|
+
assert set(urls) == {"https://err.com/path?query=value#fragment"}
|
83
|
+
|
84
|
+
|
85
|
+
def test_urls_with_different_tlds(verbose):
|
86
|
+
"""Test URLs with various top-level domains (TLDs) to ensure correct identification and handling."""
|
87
|
+
text = "Different TLDs: https://err.ml https://err.org https://err.net https://err.io https://err.ai"
|
88
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
89
|
+
assert result is False
|
90
|
+
assert set(urls) == {"https://err.ml", "https://err.org", "https://err.net", "https://err.io", "https://err.ai"}
|
91
|
+
|
92
|
+
|
93
|
+
def test_case_sensitivity(verbose):
|
94
|
+
"""Tests URL case sensitivity by verifying that URLs with different cases are correctly identified and handled."""
|
95
|
+
text = "Case test: HTTPS://err.com and https://err.com"
|
96
|
+
result, urls = check_links_in_string(text, verbose, return_bad=True)
|
97
|
+
assert result is False
|
98
|
+
assert set(urls) == {"https://err.com"}
|
{ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ultralytics-actions
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.37
|
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>
|
@@ -28,12 +28,11 @@ Classifier: Operating System :: OS Independent
|
|
28
28
|
Requires-Python: >=3.8
|
29
29
|
Description-Content-Type: text/markdown
|
30
30
|
License-File: LICENSE
|
31
|
-
Requires-Dist: requests>=2.
|
32
|
-
Requires-Dist: ruff>=0.
|
31
|
+
Requires-Dist: requests>=2.32.3
|
32
|
+
Requires-Dist: ruff>=0.8.4
|
33
33
|
Requires-Dist: docformatter>=1.7.5
|
34
34
|
Provides-Extra: dev
|
35
35
|
Requires-Dist: pytest; extra == "dev"
|
36
|
-
Requires-Dist: black; extra == "dev"
|
37
36
|
|
38
37
|
<a href="https://www.ultralytics.com/" target="_blank"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
39
38
|
|
{ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/SOURCES.txt
RENAMED
@@ -10,6 +10,7 @@ actions/utils/__init__.py
|
|
10
10
|
actions/utils/common_utils.py
|
11
11
|
actions/utils/github_utils.py
|
12
12
|
actions/utils/openai_utils.py
|
13
|
+
tests/test_urls.py
|
13
14
|
ultralytics_actions.egg-info/PKG-INFO
|
14
15
|
ultralytics_actions.egg-info/SOURCES.txt
|
15
16
|
ultralytics_actions.egg-info/dependency_links.txt
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/actions/update_markdown_code_blocks.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ultralytics_actions-0.0.34 → ultralytics_actions-0.0.37}/ultralytics_actions.egg-info/top_level.txt
RENAMED
File without changes
|