ssh-handler 1.4.2__tar.gz → 1.5.0__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.
- {ssh_handler-1.4.2/ssh_handler.egg-info → ssh_handler-1.5.0}/PKG-INFO +1 -1
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/pyproject.toml +1 -1
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/__init__.py +1 -1
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/core.py +16 -2
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/results.py +7 -9
- {ssh_handler-1.4.2 → ssh_handler-1.5.0/ssh_handler.egg-info}/PKG-INFO +1 -1
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/LICENSE +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/README.md +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/setup.cfg +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/__main__.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/cli.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/config.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/credentials.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/exceptions.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/ftp.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/openssh/OpenSSH-ARM64.zip +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/openssh/OpenSSH-Win32.zip +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/openssh/OpenSSH-Win64.zip +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/pool.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/pyqt_worker.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/serial_handler.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/setup_openssh_server.ps1 +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler/winrm_bootstrap.py +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler.egg-info/SOURCES.txt +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler.egg-info/dependency_links.txt +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler.egg-info/entry_points.txt +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler.egg-info/requires.txt +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/ssh_handler.egg-info/top_level.txt +0 -0
- {ssh_handler-1.4.2 → ssh_handler-1.5.0}/tests/test_offline.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ssh-handler"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.5.0"
|
|
8
8
|
description = "Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -603,7 +603,7 @@ class SSHHandler:
|
|
|
603
603
|
# Continuous / streaming output (tail -f, slog2info -w, journalctl -f…)
|
|
604
604
|
# ------------------------------------------------------------------ #
|
|
605
605
|
def iter_lines(self, command: str, *, timeout: Optional[float] = None,
|
|
606
|
-
stop_event=None, get_pty: bool =
|
|
606
|
+
stop_event=None, get_pty: bool = False, encoding: str = "utf-8",
|
|
607
607
|
idle_poll: float = 0.4):
|
|
608
608
|
"""
|
|
609
609
|
Run a (possibly never-ending) command and yield its stdout **line by
|
|
@@ -1032,6 +1032,10 @@ class SSHHandler:
|
|
|
1032
1032
|
local_path = os.path.expanduser(local_path)
|
|
1033
1033
|
sftp = self.sftp()
|
|
1034
1034
|
start = time.time()
|
|
1035
|
+
# Verify the remote path exists FIRST, so a missing source never leaves
|
|
1036
|
+
# behind an empty local file (sftp.get opens the local file before fetch).
|
|
1037
|
+
if not self._remote_exists(sftp, remote_path):
|
|
1038
|
+
raise SSHTransferError(f"Remote path does not exist: {remote_path}")
|
|
1035
1039
|
try:
|
|
1036
1040
|
if self._remote_is_dir(sftp, remote_path):
|
|
1037
1041
|
if not recursive:
|
|
@@ -1044,7 +1048,17 @@ class SSHHandler:
|
|
|
1044
1048
|
if parent and not os.path.exists(parent):
|
|
1045
1049
|
os.makedirs(parent, exist_ok=True)
|
|
1046
1050
|
self._emit(logging.INFO, f"PULL {remote_path} -> {local_path}")
|
|
1047
|
-
|
|
1051
|
+
existed = os.path.exists(local_path)
|
|
1052
|
+
try:
|
|
1053
|
+
sftp.get(remote_path, local_path, callback=callback)
|
|
1054
|
+
except BaseException:
|
|
1055
|
+
# don't leave a partial/empty local file behind on failure
|
|
1056
|
+
if not existed and os.path.exists(local_path):
|
|
1057
|
+
try:
|
|
1058
|
+
os.remove(local_path)
|
|
1059
|
+
except OSError:
|
|
1060
|
+
pass
|
|
1061
|
+
raise
|
|
1048
1062
|
size, count = os.path.getsize(local_path), 1
|
|
1049
1063
|
except SSHTransferError:
|
|
1050
1064
|
raise
|
|
@@ -60,10 +60,8 @@ class CommandResult:
|
|
|
60
60
|
return asdict(self)
|
|
61
61
|
|
|
62
62
|
def __str__(self) -> str:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
f"ok={self.ok} dur={self.duration:.2f}s cmd={self.command!r}>"
|
|
66
|
-
)
|
|
63
|
+
status = "ok" if self.ok else f"FAILED (exit {self.exit_code})"
|
|
64
|
+
return f"$ {self.command} [{status}, {self.duration:.2f}s]"
|
|
67
65
|
|
|
68
66
|
|
|
69
67
|
@dataclass
|
|
@@ -96,11 +94,11 @@ class TransferResult:
|
|
|
96
94
|
return d
|
|
97
95
|
|
|
98
96
|
def __str__(self) -> str:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
verb = "Uploaded" if self.direction == "push" else "Downloaded"
|
|
98
|
+
files = f"{self.files} files, " if self.files != 1 else ""
|
|
99
|
+
return (f"{verb} {self.source} -> {self.dest} "
|
|
100
|
+
f"({files}{self.human_size} in {self.duration:.2f}s, "
|
|
101
|
+
f"{self.human_speed})")
|
|
104
102
|
|
|
105
103
|
|
|
106
104
|
@dataclass
|
|
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
|
|
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
|