ssh-handler 1.0.2__tar.gz → 1.0.4__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.
Files changed (24) hide show
  1. {ssh_handler-1.0.2/ssh_handler.egg-info → ssh_handler-1.0.4}/PKG-INFO +1 -1
  2. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/pyproject.toml +1 -1
  3. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/__init__.py +1 -1
  4. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/core.py +36 -3
  5. {ssh_handler-1.0.2 → ssh_handler-1.0.4/ssh_handler.egg-info}/PKG-INFO +1 -1
  6. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/tests/test_offline.py +11 -0
  7. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/LICENSE +0 -0
  8. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/README.md +0 -0
  9. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/setup.cfg +0 -0
  10. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/__main__.py +0 -0
  11. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/cli.py +0 -0
  12. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/config.py +0 -0
  13. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/credentials.py +0 -0
  14. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/exceptions.py +0 -0
  15. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/ftp.py +0 -0
  16. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/pool.py +0 -0
  17. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/pyqt_worker.py +0 -0
  18. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/results.py +0 -0
  19. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/winrm_bootstrap.py +0 -0
  20. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/SOURCES.txt +0 -0
  21. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/dependency_links.txt +0 -0
  22. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/entry_points.txt +0 -0
  23. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/requires.txt +0 -0
  24. {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-handler
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools.
5
5
  Author: ssh-handler contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ssh-handler"
7
- version = "1.0.2"
7
+ version = "1.0.4"
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"
@@ -22,7 +22,7 @@ dependencies (PyQt5 / scp) don't break the core package.
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
- __version__ = "1.0.2"
25
+ __version__ = "1.0.4"
26
26
 
27
27
  from .config import SSHConfig, FTPConfig
28
28
  from .core import SSHHandler, ShellSession
@@ -118,10 +118,17 @@ class SSHHandler:
118
118
  log_callback: Optional[Callable[[str], None]] = None,
119
119
  logger: Optional[logging.Logger] = None,
120
120
  safe: bool = False,
121
+ quiet: bool = False,
121
122
  ):
123
+ """
124
+ :param quiet: when True, suppress the INFO narration (connect/command
125
+ lines) and emit only warnings and errors. Command results
126
+ are returned in CommandResult.stdout regardless.
127
+ """
122
128
  self.config = config
123
129
  self._safe_default = safe
124
130
  self._log_callback = log_callback
131
+ self._quiet = quiet
125
132
  self.log = logger or self._build_logger()
126
133
 
127
134
  self._client: Optional[paramiko.SSHClient] = None
@@ -143,7 +150,9 @@ class SSHHandler:
143
150
  logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
144
151
  )
145
152
  logger.addHandler(handler)
146
- logger.setLevel(logging.INFO)
153
+ # set the level every time so quiet= is honored even if the per-host
154
+ # logger was created by an earlier handler instance
155
+ logger.setLevel(logging.WARNING if self._quiet else logging.INFO)
147
156
  return logger
148
157
 
149
158
  def _secrets(self) -> list:
@@ -269,7 +278,12 @@ class SSHHandler:
269
278
  if cfg.passwordless:
270
279
  kwargs["allow_agent"] = True
271
280
  kwargs["look_for_keys"] = True
272
- elif empty_password:
281
+ elif empty_password or pw == "":
282
+ # Explicit empty-password authentication. An empty string means
283
+ # "log in with a blank password" (PermitEmptyPasswords hosts) - send
284
+ # it directly and don't waste the first attempt probing keys/agent,
285
+ # which would otherwise fail with "No authentication methods
286
+ # available" before the empty password is ever tried.
273
287
  kwargs["password"] = ""
274
288
  kwargs["look_for_keys"] = False
275
289
  kwargs["allow_agent"] = False
@@ -308,9 +322,28 @@ class SSHHandler:
308
322
  last_auth = exc
309
323
  client.close()
310
324
  self._emit(logging.DEBUG, f"Auth strategy '{label}' rejected.")
311
- except (paramiko.SSHException, OSError) as exc:
325
+ except paramiko.SSHException as exc:
312
326
  client.close()
327
+ # "No authentication methods available" means paramiko had no
328
+ # credential the server would accept (no password/key/agent).
329
+ # That's an auth problem, not a transport one - try the next
330
+ # strategy, and if none are left raise a clear, guided error.
331
+ if "no authentication methods" in str(exc).lower():
332
+ last_auth = exc
333
+ self._emit(logging.DEBUG,
334
+ f"Auth strategy '{label}': {exc}")
335
+ continue
313
336
  raise exc
337
+ except OSError as exc:
338
+ client.close()
339
+ raise exc
340
+ if last_auth and "no authentication methods" in str(last_auth).lower():
341
+ raise SSHAuthenticationError(
342
+ f"No usable credentials for {cfg.auth_username}@{cfg.host}. "
343
+ f"Provide one of: password=..., key_filename=..., "
344
+ f"passwordless=True (use your SSH key/agent), or password='' for a "
345
+ f"blank-password account (the server must allow PermitEmptyPasswords)."
346
+ )
314
347
  raise SSHAuthenticationError(
315
348
  f"All authentication strategies failed for "
316
349
  f"{cfg.auth_username}@{cfg.host}: {last_auth}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-handler
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools.
5
5
  Author: ssh-handler contributors
6
6
  License-Expression: MIT
@@ -38,6 +38,17 @@ kw2 = h._build_connect_kwargs(empty_password=True, sock=None)
38
38
  assert kw2["password"] == "" and kw2["allow_agent"] is False
39
39
  print("empty-password strategy OK")
40
40
 
41
+ # explicit password="" must send a blank password on the PRIMARY attempt
42
+ # (regression guard: previously the primary probed keys and failed with
43
+ # "No authentication methods available" before the empty password was tried)
44
+ hp = SSHHandler.__new__(SSHHandler)
45
+ hp.config = SSHConfig(host="t", username="u", password="")
46
+ hp._jump = None; hp.log = logging.getLogger("x2"); hp._log_callback = None
47
+ kw3 = hp._build_connect_kwargs(empty_password=False, sock=None)
48
+ assert kw3["password"] == "" and kw3["allow_agent"] is False \
49
+ and kw3["look_for_keys"] is False
50
+ print("explicit empty-password (primary) OK")
51
+
41
52
  # TransferResult math
42
53
  tr = TransferResult("a", "b", "push", "sftp", 1048576, 2.0, 1)
43
54
  assert abs(tr.speed_bps - 524288) < 1 and tr.human_size == "1.0MB"
File without changes
File without changes
File without changes