zspeedtest 0.1.1__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.
zspeedtest/__init__.py
ADDED
zspeedtest/main.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Internet speed tester.
|
|
2
|
+
|
|
3
|
+
Usage: zspeedtest <URL> [--requests N]
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
import textwrap
|
|
9
|
+
import time
|
|
10
|
+
from typing import NamedTuple
|
|
11
|
+
from urllib.request import Request, urlopen
|
|
12
|
+
|
|
13
|
+
KB = 1024
|
|
14
|
+
MB = KB**2
|
|
15
|
+
CHUNK_SIZE = KB * 64
|
|
16
|
+
DEFAULT_URL = "http://ipv4.download.thinkbroadband.com/10MB.zip"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SpeedTestArgs(NamedTuple):
|
|
20
|
+
url: str
|
|
21
|
+
requests: int
|
|
22
|
+
timeout: int
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_cli(cls) -> "SpeedTestArgs":
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
description="Internet speed tester",
|
|
28
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"url",
|
|
32
|
+
nargs="?",
|
|
33
|
+
default=DEFAULT_URL,
|
|
34
|
+
help="URL of a large file to download",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--requests",
|
|
38
|
+
"-n",
|
|
39
|
+
type=int,
|
|
40
|
+
default=10,
|
|
41
|
+
metavar="N",
|
|
42
|
+
help="Number of requests",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--timeout",
|
|
46
|
+
"-t",
|
|
47
|
+
type=int,
|
|
48
|
+
default=30,
|
|
49
|
+
metavar="N",
|
|
50
|
+
help="Request timeout",
|
|
51
|
+
)
|
|
52
|
+
namespace = parser.parse_args()
|
|
53
|
+
|
|
54
|
+
if not namespace.url.startswith(("http:", "https:")):
|
|
55
|
+
msg = "URL must start with 'http:' or 'https:'"
|
|
56
|
+
raise ValueError(msg)
|
|
57
|
+
|
|
58
|
+
return cls(**namespace.__dict__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RequestResult(NamedTuple):
|
|
62
|
+
duration_seconds: float
|
|
63
|
+
bytes_downloaded: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def download_url(req: Request, timeout: int) -> RequestResult:
|
|
67
|
+
start = time.perf_counter()
|
|
68
|
+
total_bytes: int = 0
|
|
69
|
+
|
|
70
|
+
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected # noqa: E501, ERA001
|
|
71
|
+
with urlopen(req, timeout=timeout) as response: # nosec
|
|
72
|
+
while chunk := response.read(CHUNK_SIZE):
|
|
73
|
+
total_bytes += len(chunk)
|
|
74
|
+
|
|
75
|
+
duration = time.perf_counter() - start
|
|
76
|
+
return RequestResult(
|
|
77
|
+
duration_seconds=duration,
|
|
78
|
+
bytes_downloaded=total_bytes,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_size(bytes_count: float) -> str:
|
|
83
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
84
|
+
if bytes_count < KB:
|
|
85
|
+
return f"{bytes_count:.1f} {unit}"
|
|
86
|
+
bytes_count /= KB
|
|
87
|
+
return f"{bytes_count:.1f} TB"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run() -> None:
|
|
91
|
+
args = SpeedTestArgs.from_cli()
|
|
92
|
+
start_info = textwrap.dedent(f"""\
|
|
93
|
+
URL: {args.url}
|
|
94
|
+
Requests: {args.requests}
|
|
95
|
+
{"-" * 52}
|
|
96
|
+
{"#":>3} {"Size":>10} {"Time":>8} {"Speed":>12}
|
|
97
|
+
{"-" * 52}
|
|
98
|
+
""")
|
|
99
|
+
print(start_info)
|
|
100
|
+
|
|
101
|
+
results: list[RequestResult] = []
|
|
102
|
+
|
|
103
|
+
req = Request(args.url)
|
|
104
|
+
req.add_header("User-Agent", "zspeedtest/1.0")
|
|
105
|
+
|
|
106
|
+
for i in range(1, args.requests + 1):
|
|
107
|
+
try:
|
|
108
|
+
r = download_url(req, timeout=args.timeout)
|
|
109
|
+
except Exception as e: # noqa: BLE001
|
|
110
|
+
print(f"{i:>3} ERROR: {e}")
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
results.append(r)
|
|
114
|
+
|
|
115
|
+
speed_mbps = (r.bytes_downloaded / r.duration_seconds) / MB
|
|
116
|
+
print(
|
|
117
|
+
f"{i:>3} {format_size(r.bytes_downloaded):>10}"
|
|
118
|
+
f" {r.duration_seconds:>7.2f}s {speed_mbps:>10.2f} MB/s"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if not results:
|
|
122
|
+
print("\nNo requests completed successfully.")
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
print("=" * 52)
|
|
126
|
+
|
|
127
|
+
total_bytes = sum(r.bytes_downloaded for r in results)
|
|
128
|
+
total_time = sum(r.duration_seconds for r in results)
|
|
129
|
+
avg_time = total_time / len(results)
|
|
130
|
+
avg_speed = (total_bytes / total_time) / MB
|
|
131
|
+
min_speed = min(
|
|
132
|
+
(r.bytes_downloaded / r.duration_seconds) / MB for r in results
|
|
133
|
+
)
|
|
134
|
+
max_speed = max(
|
|
135
|
+
(r.bytes_downloaded / r.duration_seconds) / MB for r in results
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
end_info = textwrap.dedent(f"""\
|
|
139
|
+
Successful requests : {len(results)} / {args.requests}
|
|
140
|
+
Total downloaded : {format_size(total_bytes)}
|
|
141
|
+
Average time : {avg_time:.2f} s
|
|
142
|
+
Average speed : {avg_speed:.2f} MB/s
|
|
143
|
+
Min / Max : {min_speed:.2f} / {max_speed:.2f} MB/s
|
|
144
|
+
""")
|
|
145
|
+
print(end_info)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
run()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zspeedtest
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: zspeedtest
|
|
5
|
+
Keywords: zspeedtest
|
|
6
|
+
Author: Sergey
|
|
7
|
+
Author-email: Sergey <kava.develop@protonmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Typing :: Typed
|
|
10
|
+
Classifier: Framework :: Pytest
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.10, <3.15
|
|
25
|
+
Project-URL: Homepage, https://theseriff.github.io/zspeedtest/
|
|
26
|
+
Project-URL: Repository, https://github.com/theseriff/zspeedtest
|
|
27
|
+
Project-URL: Documentation, https://theseriff.github.io/zspeedtest/
|
|
28
|
+
Project-URL: Changelog, https://github.com/theseriff/zspeedtest/blob/main/docs/CHANGELOG.md
|
|
29
|
+
Project-URL: Issues, https://github.com/theseriff/zspeedtest/issues
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+
<h1>zspeedtest</h1>
|
|
35
|
+
<p><strong>Simple CLI internet speed tester. Downloads a file and measures throughput.</strong></p>
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/zspeedtest)
|
|
38
|
+
[](https://pypi.python.org/pypi/zspeedtest)
|
|
39
|
+
[](https://github.com/theseriff/zspeedtest/actions/workflows/pr_tests.yaml)
|
|
40
|
+
[](https://coverage-badge.samuelcolvin.workers.dev/redirect/theseriff/zspeedtest)
|
|
41
|
+
[](https://opensource.org/licenses/MIT)
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv add zspeedtest
|
|
47
|
+
zspeedtest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
zspeedtest [URL] [--requests N] [--timeout N]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `URL` — large file to download (default: 10MB test file from ThinkBroadband)
|
|
57
|
+
- `--requests`/`-n` — number of test requests (default: 10)
|
|
58
|
+
- `--timeout`/`-t` — per-request timeout in seconds (default: 30)
|
|
59
|
+
|
|
60
|
+
### Examples
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
zspeedtest
|
|
64
|
+
zspeedtest http://example.com/file.bin --requests 5
|
|
65
|
+
zspeedtest http://example.com/file.bin -n 3 -t 15
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Output:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
URL: http://ipv4.download.thinkbroadband.com/10MB.zip
|
|
72
|
+
Requests: 1
|
|
73
|
+
----------------------------------------------------
|
|
74
|
+
# Size Time Speed
|
|
75
|
+
----------------------------------------------------
|
|
76
|
+
|
|
77
|
+
1 10.0 MB 29.95s 0.33 MB/s
|
|
78
|
+
====================================================
|
|
79
|
+
Successful requests : 1 / 1
|
|
80
|
+
Total downloaded : 10.0 MB
|
|
81
|
+
Average time : 29.95 s
|
|
82
|
+
Average speed : 0.33 MB/s
|
|
83
|
+
Min / Max : 0.33 / 0.33 MB/s
|
|
84
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
zspeedtest/__init__.py,sha256=g67SDd0H4atCLjpFUcESP0JlWXyOkYMmf5nLAavR5us,157
|
|
2
|
+
zspeedtest/main.py,sha256=nKlFO_VFdM6iUlYo5-rHSiRGVCrGuI4zgou4cNp6yCI,4053
|
|
3
|
+
zspeedtest-0.1.1.dist-info/WHEEL,sha256=s49dN1sxqzkgPplo4QuUaKomil-_cbDzeLK4-pZKD-A,81
|
|
4
|
+
zspeedtest-0.1.1.dist-info/entry_points.txt,sha256=y1ChsvhdTkNwup6Cgwm0VRE5wCRZgzxdHySE6qXc9K0,52
|
|
5
|
+
zspeedtest-0.1.1.dist-info/METADATA,sha256=OIKwxSHlUT8JRGZU-u9PNQCsM9692l2spWHkBrttYSQ,3090
|
|
6
|
+
zspeedtest-0.1.1.dist-info/RECORD,,
|