ssh-handler 1.0.9__tar.gz → 1.2.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.
Files changed (29) hide show
  1. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/PKG-INFO +127 -26
  2. ssh_handler-1.0.9/ssh_handler.egg-info/PKG-INFO → ssh_handler-1.2.0/README.md +480 -405
  3. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/pyproject.toml +10 -7
  4. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/__init__.py +4 -1
  5. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/cli.py +57 -0
  6. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/core.py +99 -0
  7. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/pyqt_worker.py +25 -0
  8. ssh_handler-1.2.0/ssh_handler/serial_handler.py +200 -0
  9. ssh_handler-1.0.9/README.md → ssh_handler-1.2.0/ssh_handler.egg-info/PKG-INFO +506 -374
  10. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/SOURCES.txt +1 -0
  11. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/requires.txt +4 -12
  12. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/LICENSE +0 -0
  13. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/setup.cfg +0 -0
  14. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/__main__.py +0 -0
  15. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/config.py +0 -0
  16. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/credentials.py +0 -0
  17. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/exceptions.py +0 -0
  18. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/ftp.py +0 -0
  19. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-ARM64.zip +0 -0
  20. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-Win32.zip +0 -0
  21. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-Win64.zip +0 -0
  22. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/pool.py +0 -0
  23. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/results.py +0 -0
  24. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/setup_openssh_server.ps1 +0 -0
  25. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/winrm_bootstrap.py +0 -0
  26. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/dependency_links.txt +0 -0
  27. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/entry_points.txt +0 -0
  28. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/top_level.txt +0 -0
  29. {ssh_handler-1.0.9 → ssh_handler-1.2.0}/tests/test_offline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-handler
3
- Version: 1.0.9
3
+ Version: 1.2.0
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
@@ -14,19 +14,14 @@ Requires-Python: >=3.8
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
16
  Requires-Dist: paramiko>=3.0
17
- Provides-Extra: secure
18
- Requires-Dist: keyring>=23.0; extra == "secure"
19
- Provides-Extra: scp
20
- Requires-Dist: scp>=0.14; extra == "scp"
17
+ Requires-Dist: scp>=0.14
18
+ Requires-Dist: pyserial>=3.5
19
+ Requires-Dist: keyring>=23.0
20
+ Requires-Dist: pywinrm>=0.4.3
21
21
  Provides-Extra: gui
22
22
  Requires-Dist: PyQt5>=5.15; extra == "gui"
23
- Provides-Extra: winrm
24
- Requires-Dist: pywinrm>=0.4.3; extra == "winrm"
25
23
  Provides-Extra: all
26
- Requires-Dist: keyring>=23.0; extra == "all"
27
- Requires-Dist: scp>=0.14; extra == "all"
28
24
  Requires-Dist: PyQt5>=5.15; extra == "all"
29
- Requires-Dist: pywinrm>=0.4.3; extra == "all"
30
25
  Dynamic: license-file
31
26
 
32
27
  # ssh-handler
@@ -86,15 +81,17 @@ behind one object that:
86
81
  ## Install
87
82
 
88
83
  ```bash
89
- pip install ssh-handler # core (paramiko only)
90
- pip install "ssh-handler[secure]" # + keyring (OS credential vault)
91
- pip install "ssh-handler[scp]" # + scp (SCP-protocol transfers)
92
- pip install "ssh-handler[gui]" # + PyQt5 (the GUI worker)
93
- pip install "ssh-handler[all]" # everything
84
+ pip install ssh-handler # everything: SSH, SFTP, SCP, FTP, serial,
85
+ # credential vault, WinRM bootstrap
86
+ pip install "ssh-handler[gui]" # also installs PyQt5 for the GUI worker
94
87
  ```
95
88
 
96
- `scp`, `keyring`, and `PyQt5` are optional the core works without them, and
97
- those features raise a clear, actionable message if you use them without the extra.
89
+ **Batteries included.** A plain `pip install ssh-handler` pulls in `paramiko`,
90
+ `scp`, `pyserial`, `keyring`, and `pywinrm`, so SSH, SFTP/SCP/FTP transfers,
91
+ serial/COM ports, confidential credential storage, and the WinRM bootstrap all
92
+ work out of the box. Only **PyQt5** is optional (`[gui]`), because it's a large
93
+ GUI toolkit you only need when building a GUI — forcing it would bloat headless
94
+ and CI installs.
98
95
 
99
96
  ## Quick start
100
97
 
@@ -127,6 +124,18 @@ with SSHHandler(SSHConfig(host="10.0.0.5", username="root", password="pw")) as s
127
124
  - `open_shell()` — a persistent interactive `ShellSession` with `send` /
128
125
  `read_until` (send-expect) / `read_available`.
129
126
 
127
+ **Continuous / streaming output (logs)**
128
+ - `iter_lines(cmd)` — generator yielding a never-ending command's output **line
129
+ by line, live** (`slog2info -w`, `tail -f`, `journalctl -f`, `dmesg -w`).
130
+ - `stream(cmd, on_line=, match=, save_to=, stop_on_match=, stop_event=)` — stream
131
+ with **live regex matching**, a per-line/per-match callback, and **tee to a
132
+ local file**, all built in.
133
+
134
+ **Serial / COM ports** (`SerialHandler`, included by default)
135
+ - `list_serial_ports()`, `open`/`close`, `write` / `write_line`.
136
+ - `iter_lines()` and `stream(...)` — same live streaming + match + save-to-file
137
+ model as SSH, for device consoles.
138
+
130
139
  **File operations (SFTP) — full Paramiko parity**
131
140
  - Transfers: `push` / `pull` (single file **or** recursive directory, with progress
132
141
  callbacks and transfer statistics), plus `scp_push` / `scp_pull` (SCP protocol).
@@ -247,6 +256,71 @@ When a connection just fails, the error now self-diagnoses — it probes the SSH
247
256
  RDP ports and tells you *why* (e.g. "Port 22 is closed but RDP (3389) is open … no
248
257
  SSH server listening"). Call `ssh.diagnose()` for a pre-flight reachability check.
249
258
 
259
+ ## Continuous logs & live pattern matching
260
+
261
+ Stream a long-running remote command and react to lines as they arrive — match a
262
+ pattern, save to a file, or stop when something appears. Works through the jump
263
+ host too.
264
+
265
+ ```python
266
+ from ssh_handler import SSHHandler, SSHConfig
267
+
268
+ with SSHHandler(SSHConfig(host="10.120.1.91", username="root", password="pw",
269
+ jump_host=rdp_box), quiet=True) as ssh:
270
+
271
+ # (a) simplest: iterate lines live
272
+ for line in ssh.iter_lines("slog2info -w"):
273
+ print(line)
274
+ if "FATAL" in line:
275
+ break
276
+
277
+ # (b) full: match + tee to a local file + callback, stop on a pattern
278
+ result = ssh.stream(
279
+ "tail -f /var/log/messages",
280
+ on_line=print, # called for every line
281
+ match=r"error|fail", # regex; matching lines collected
282
+ on_match=lambda l: print("HIT:", l),
283
+ save_to="device.log", # tee every line to this local file
284
+ stop_on_match=False, # set True to stop at the first match
285
+ timeout=60, # optional overall time limit
286
+ )
287
+ print(result["lines"], "lines,", len(result["matches"]), "matched")
288
+ ```
289
+
290
+ To stop a stream from another thread (e.g. a GUI Stop button), pass a
291
+ `threading.Event` as `stop_event=` and `.set()` it.
292
+
293
+ ## Serial / COM ports
294
+
295
+ Same streaming + match + save model for device serial consoles (included by
296
+ default — no extra install).
297
+
298
+ > **Important — where the COM port physically is.** `pyserial` opens a *local*
299
+ > port, so this runs on the machine the device is **plugged into**. If the device
300
+ > is on your laptop, run it on your laptop. If it's on the remote/RDP machine,
301
+ > either run the script there, or — on Linux targets — stream the device file
302
+ > over SSH instead: `ssh.stream("cat /dev/ttyUSB0", match=..., save_to=...)`.
303
+
304
+ ```python
305
+ from ssh_handler import SerialHandler, list_serial_ports
306
+
307
+ print(list_serial_ports()) # [{'device':'COM5','description':...}, ...]
308
+
309
+ with SerialHandler("COM5", baudrate=115200, quiet=True) as ser:
310
+ ser.write_line("version") # send a command
311
+ res = ser.stream(
312
+ on_line=print,
313
+ match=r"login:", # wait for the login prompt
314
+ stop_on_match=True,
315
+ save_to="serial_console.log", # tee to file
316
+ timeout=120,
317
+ )
318
+ print("matched:", res["matched"])
319
+ ```
320
+
321
+ `write_line(..., eol="\r\n")` for consoles that need CRLF. Everything returns the
322
+ same `OperationResult` in safe mode and raises `SerialError` otherwise.
323
+
250
324
  ## Confidential credentials
251
325
 
252
326
  | Mechanism | What it does |
@@ -306,17 +380,29 @@ Every action returns structured data, not bare strings:
306
380
  ## CLI reference
307
381
 
308
382
  ```bash
309
- python -m ssh_handler run --host H --user U --domain CORP uptime
310
- python -m ssh_handler push --host H --user U ./build /tmp/build --recursive
311
- python -m ssh_handler pull --host H --user U /var/log ./logs --recursive
312
- python -m ssh_handler info --host H --user U --json
383
+ python -m ssh_handler run --host H --user U --domain CORP uptime
384
+ python -m ssh_handler push --host H --user U ./build /tmp/build --recursive
385
+ python -m ssh_handler pull --host H --user U /var/log ./logs --recursive
386
+ python -m ssh_handler info --host H --user U --json
313
387
  python -m ssh_handler store-credential --user U --domain CORP --service my_test_lab
388
+
389
+ # continuous logs over SSH, with live matching + save:
390
+ python -m ssh_handler stream --host H --user U --match "error|fail" \
391
+ --save run.log -- slog2info -w
392
+
393
+ # serial / COM ports:
394
+ python -m ssh_handler list-serial
395
+ python -m ssh_handler serial-monitor --port COM5 --baud 115200 \
396
+ --match "login:" --stop-on-match --save console.log
397
+
398
+ # install OpenSSH Server on THIS Windows machine (offline, self-elevates):
399
+ ssh-handler-setup
314
400
  ```
315
401
 
316
402
  Password options: `--password` (hidden prompt), `--use-stored` (read from the OS
317
403
  vault), `--key FILE` (private key). Add `--json` for machine-readable output.
318
- After `pip install`, a `ssh-handler` console script is also available
319
- (`ssh-handler run --host …`).
404
+ Put `--match`/`--save` *before* the streamed command. After `pip install`, the
405
+ `ssh-handler` and `ssh-handler-setup` console scripts are also available.
320
406
 
321
407
  ## PyQt5 integration
322
408
 
@@ -337,8 +423,22 @@ thread.started.connect(lambda: worker.run_command("uptime"))
337
423
  thread.start()
338
424
  ```
339
425
 
340
- Signals: `log`, `connected`, `command_done`, `transfer_done`, `progress`, `error`,
341
- `finished`. The import is lazy, so the rest of the package works where PyQt5 isn't installed.
426
+ Signals: `log`, `connected`, `command_done`, `transfer_done`, `progress`,
427
+ `stream_line`, `stream_match`, `stream_done`, `error`, `finished`. The import is
428
+ lazy, so the rest of the package works where PyQt5 isn't installed.
429
+
430
+ **Streaming logs into the GUI** — drive `start_stream` in the worker thread and
431
+ wire the per-line signals to your widgets; `stop_stream()` ends it cleanly:
432
+
433
+ ```python
434
+ worker.stream_line.connect(log_view.append) # every live line
435
+ worker.stream_match.connect(lambda l: alerts.append(l)) # only matching lines
436
+ thread.started.connect(lambda: worker.start_stream("slog2info -w",
437
+ match="error|fail",
438
+ save_to="device.log"))
439
+ # later, from a Stop button:
440
+ worker.stop_stream()
441
+ ```
342
442
 
343
443
  ## Parallel fleet operations
344
444
 
@@ -372,8 +472,9 @@ with FTPHandler(FTPConfig(host="ftp.example.com", username="u",
372
472
  ssh_handler/
373
473
  config.py SSHConfig, FTPConfig
374
474
  credentials.py Secret, CredentialStore, mask, prompt_password
375
- core.py SSHHandler, ShellSession (SSH + SFTP + SCP + diagnose)
475
+ core.py SSHHandler, ShellSession (SSH + SFTP + SCP + stream + diagnose)
376
476
  ftp.py FTPHandler (FTP / FTPS)
477
+ serial_handler.py SerialHandler, list_serial_ports (serial / COM ports)
377
478
  winrm_bootstrap.py enable_openssh_via_winrm (one-time sshd enable over WinRM)
378
479
  pool.py SSHPool (parallel multi-host)
379
480
  cli.py argparse entry point (python -m ssh_handler / ssh-handler)