speedtest-ssh 2.6.0__tar.gz → 2.6.2__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.
- {speedtest_ssh-2.6.0/speedtest_ssh.egg-info → speedtest_ssh-2.6.2}/PKG-INFO +1 -1
- speedtest_ssh-2.6.2/speedtest_ssh/_version.py +1 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/data_transfer.py +16 -16
- speedtest_ssh-2.6.2/speedtest_ssh/ping.py +38 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/util.py +13 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2/speedtest_ssh.egg-info}/PKG-INFO +1 -1
- speedtest_ssh-2.6.0/speedtest_ssh/_version.py +0 -1
- speedtest_ssh-2.6.0/speedtest_ssh/ping.py +0 -41
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/LICENSE +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/README.md +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/pyproject.toml +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/setup.cfg +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/__init__.py +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/config.py +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/py.typed +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/sftp_wrapper.py +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh/speedtest_ssh.py +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh.egg-info/SOURCES.txt +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh.egg-info/dependency_links.txt +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh.egg-info/entry_points.txt +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh.egg-info/requires.txt +0 -0
- {speedtest_ssh-2.6.0 → speedtest_ssh-2.6.2}/speedtest_ssh.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__: str = "2.6.2"
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
from subprocess import CalledProcessError, PIPE
|
|
2
3
|
from typing import TYPE_CHECKING
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
import subprocess
|
|
6
6
|
import random
|
|
7
7
|
import string
|
|
8
|
-
import shlex
|
|
9
8
|
import re
|
|
10
9
|
import os
|
|
11
10
|
|
|
12
11
|
from tqdm import tqdm
|
|
13
12
|
|
|
14
13
|
from .sftp_wrapper import sftp_wrapper
|
|
14
|
+
from .util import find_exe, run_cmd
|
|
15
15
|
from .config import Config
|
|
16
|
-
from .util import find_exe
|
|
17
16
|
|
|
18
17
|
if TYPE_CHECKING:
|
|
19
18
|
from paramiko import SFTPClient
|
|
@@ -52,20 +51,26 @@ class DataTransfer:
|
|
|
52
51
|
:param config: The Config object the DataTransfer instance should use
|
|
53
52
|
:param verbose: If true, be more verbose
|
|
54
53
|
"""
|
|
54
|
+
self._verbose = verbose
|
|
55
55
|
rand = lambda: random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
|
|
56
56
|
self._remote_f: str = f"/tmp/speedtest-ssh_{datetime.now()}_{''.join(rand() for _ in range(8))}.tmp"
|
|
57
57
|
self._remote_f = self._remote_f.replace(":", "-").replace(" ", "_")
|
|
58
58
|
# We promise that _remote_f components match: ^[a-zA-Z\d_.-]+$ (old rsync args suck)
|
|
59
|
+
if self._verbose:
|
|
60
|
+
print("Parsing ssh config and loading keys...")
|
|
59
61
|
self._sftp_cm = sftp_wrapper(config)
|
|
60
62
|
self._sftp: SFTPClient # Defined in __enter__
|
|
61
|
-
self._verbose = verbose
|
|
62
63
|
|
|
63
64
|
def __enter__(self) -> DataTransfer:
|
|
65
|
+
if self._verbose:
|
|
66
|
+
print("Establishing SFTP connection...")
|
|
64
67
|
self._sftp = self._sftp_cm.__enter__()
|
|
65
68
|
return self
|
|
66
69
|
|
|
67
70
|
def __exit__(self, *args, **kwargs):
|
|
68
71
|
self.clean_remote()
|
|
72
|
+
if self._verbose:
|
|
73
|
+
print("Terminating SFTP connection...")
|
|
69
74
|
self._sftp_cm.__exit__(*args, **kwargs)
|
|
70
75
|
|
|
71
76
|
def put(self, local: Path) -> None:
|
|
@@ -85,6 +90,8 @@ class DataTransfer:
|
|
|
85
90
|
Removes the remote file
|
|
86
91
|
"""
|
|
87
92
|
try:
|
|
93
|
+
if self._verbose:
|
|
94
|
+
print(f"Removing remote file (if it exists): {self._remote_f}")
|
|
88
95
|
self._sftp.remove(self._remote_f)
|
|
89
96
|
except FileNotFoundError:
|
|
90
97
|
pass
|
|
@@ -120,13 +127,13 @@ class Rsync(DataTransfer):
|
|
|
120
127
|
self._rsync = find_exe("rsync")
|
|
121
128
|
# Determine rsync version
|
|
122
129
|
try:
|
|
123
|
-
|
|
124
|
-
output =
|
|
130
|
+
p = run_cmd(self._rsync, "--version", verbose=self._verbose, stdout=PIPE, check=True)
|
|
131
|
+
output = p.stdout.decode().split("version")[1].split("protocol")[0].strip()
|
|
125
132
|
version: tuple[int, ...] = tuple(int(i) for i in output.split("."))
|
|
126
133
|
self._old = version <= (3, 1, 0) # macOS has an old version by default
|
|
127
134
|
if self._old:
|
|
128
135
|
print("\tOld version of rsync detected. Output will be more verbose.")
|
|
129
|
-
except (
|
|
136
|
+
except (CalledProcessError, KeyError) as e:
|
|
130
137
|
raise RuntimeError(f"Could not determine rsync version from {self._rsync}") from e
|
|
131
138
|
# Determine rsync command
|
|
132
139
|
self._flags: list[str | Path] = []
|
|
@@ -143,21 +150,14 @@ class Rsync(DataTransfer):
|
|
|
143
150
|
# Determine host info
|
|
144
151
|
self._target: str = ("" if config.user is None else f"{config.user}@") + f"{config.host}:{self._remote_f}"
|
|
145
152
|
|
|
146
|
-
def _run(self, cmd: Path, *args: str | Path, **kwargs) -> subprocess.CompletedProcess:
|
|
147
|
-
full = (cmd, *args)
|
|
148
|
-
if self._verbose:
|
|
149
|
-
msg = f"{'*'*30} Running Command: {cmd.name} {'*'*30}"
|
|
150
|
-
print(f"{msg}\n{' '.join(shlex.quote(str(i)) for i in full)}\n{'*'*len(msg)}")
|
|
151
|
-
return subprocess.run(full, **kwargs)
|
|
152
|
-
|
|
153
153
|
def _transfer(self, src: str | Path, dst: str | Path) -> None:
|
|
154
154
|
"""
|
|
155
155
|
:param src: The file to rsync to dst
|
|
156
156
|
:param dst: The location to rsync src to
|
|
157
157
|
"""
|
|
158
158
|
try:
|
|
159
|
-
_ =
|
|
160
|
-
except
|
|
159
|
+
_ = run_cmd(self._rsync, *self._flags, src, dst, verbose=self._verbose, env=self._env, check=True)
|
|
160
|
+
except CalledProcessError as e:
|
|
161
161
|
raise RuntimeError("Failed to transfer data during speed test") from e
|
|
162
162
|
|
|
163
163
|
def put(self, local: Path) -> None:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from subprocess import CalledProcessError, PIPE
|
|
2
|
+
from sys import platform
|
|
3
|
+
|
|
4
|
+
from .util import find_exe, run_cmd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = ("PingFailed", "ping")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PingFailed(RuntimeError):
|
|
11
|
+
"""
|
|
12
|
+
Raise on ping failure
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ping(host: str, verbose: bool, n_pings: int = 10, max_wait: int = 3) -> float:
|
|
17
|
+
"""
|
|
18
|
+
:param host: The host to ping
|
|
19
|
+
:return: The number Ping host
|
|
20
|
+
"""
|
|
21
|
+
# We use subprocess b/c ping is suid and we don't want to require root
|
|
22
|
+
print("Pinging...")
|
|
23
|
+
exe = find_exe("ping")
|
|
24
|
+
try:
|
|
25
|
+
wait: str = ("-W" if platform == "darwin" else "-w") + str(max_wait)
|
|
26
|
+
q = [] if verbose else ["-q"]
|
|
27
|
+
p = run_cmd(exe, "-i.1", f"-c{n_pings}", wait, *q, host, verbose=verbose, stdout=PIPE)
|
|
28
|
+
if p.returncode:
|
|
29
|
+
raise PingFailed(f"{exe} exit code: {p.returncode}")
|
|
30
|
+
output: str = p.stdout.decode().strip()
|
|
31
|
+
except CalledProcessError as e:
|
|
32
|
+
raise PingFailed("Unkown error") from e
|
|
33
|
+
if verbose:
|
|
34
|
+
print(output)
|
|
35
|
+
try:
|
|
36
|
+
return float(output.split("/")[-3])
|
|
37
|
+
except IndexError as e:
|
|
38
|
+
raise PingFailed("Bad output", output) from e
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from shutil import which
|
|
3
|
+
import subprocess
|
|
4
|
+
import shlex
|
|
3
5
|
import os
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
__all__ = ("find_exe", "run_cmd")
|
|
9
|
+
|
|
10
|
+
|
|
6
11
|
def find_exe(name: str) -> Path:
|
|
7
12
|
"""
|
|
8
13
|
:param name: The name of the executable to find
|
|
@@ -15,3 +20,11 @@ def find_exe(name: str) -> Path:
|
|
|
15
20
|
if not os.access(exe, os.X_OK):
|
|
16
21
|
raise RuntimeError(f"Cannot find valid {name} executable")
|
|
17
22
|
return exe
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_cmd(cmd: Path, *args: str | Path, verbose: bool, **kwargs) -> subprocess.CompletedProcess:
|
|
26
|
+
full = (cmd, *args)
|
|
27
|
+
if verbose:
|
|
28
|
+
msg = f"{'*'*30} Running Command: {cmd.name} {'*'*30}"
|
|
29
|
+
print(f"{msg}\n{' '.join(shlex.quote(str(i)) for i in full)}\n{'*'*len(msg)}")
|
|
30
|
+
return subprocess.run(full, **kwargs)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__: str = "2.6.0"
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
from sys import platform
|
|
3
|
-
import subprocess
|
|
4
|
-
|
|
5
|
-
from .util import find_exe
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class PingFailed(RuntimeError):
|
|
12
|
-
"""
|
|
13
|
-
Raise on ping failure
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def ping(host: str, verbose: bool, n_pings: int = 10, max_wait: int = 3) -> float:
|
|
18
|
-
"""
|
|
19
|
-
:param host: The host to ping
|
|
20
|
-
:return: The number Ping host
|
|
21
|
-
"""
|
|
22
|
-
# We use subprocess b/c ping is suid and we don't want to require root
|
|
23
|
-
print("Pinging...")
|
|
24
|
-
cmd: tuple[Path | str, ...] = (
|
|
25
|
-
find_exe("ping"),
|
|
26
|
-
"-i.1",
|
|
27
|
-
f"-c{n_pings}",
|
|
28
|
-
("-W" if platform == "darwin" else "-w") + str(max_wait),
|
|
29
|
-
*([] if verbose else ["-q"]),
|
|
30
|
-
host,
|
|
31
|
-
)
|
|
32
|
-
try:
|
|
33
|
-
output: str = subprocess.check_output(cmd).decode().strip()
|
|
34
|
-
except subprocess.CalledProcessError as e:
|
|
35
|
-
raise PingFailed("Non-zero exit code") from e
|
|
36
|
-
if verbose:
|
|
37
|
-
print(output)
|
|
38
|
-
try:
|
|
39
|
-
return float(output.split("/")[-3])
|
|
40
|
-
except IndexError as e:
|
|
41
|
-
raise PingFailed("Bad output", output) from e
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|