Skip to content

Commit 3099697

Browse files
authored
Merge pull request #217 from Maxteabag/fix-ssh-paramiko-186
Detect paramiko 4 incompatibility and route through installer prompt
2 parents dad6de1 + 9a8b4e7 commit 3099697

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

sqlit/domains/connections/app/tunnel.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,20 @@
1111

1212

1313
def ensure_ssh_tunnel_available() -> None:
14-
"""Ensure SSH tunnel dependencies are installed."""
14+
"""Ensure SSH tunnel dependencies are installed and compatible."""
1515
try:
16+
import paramiko
1617
import sshtunnel # noqa: F401
18+
19+
# paramiko 4 removed DSSKey, but sshtunnel still references it at
20+
# runtime. Surface this as a MissingDriverError so the installer
21+
# prompt suggests reinstalling with the [ssh] extra (pins paramiko<4).
22+
if not hasattr(paramiko, "DSSKey"):
23+
raise RuntimeError(
24+
f"paramiko {getattr(paramiko, '__version__', '?')} is incompatible "
25+
"with sshtunnel (DSSKey was removed in paramiko 4); reinstall with "
26+
"the [ssh] extra to pin paramiko<4."
27+
)
1728
except Exception as e:
1829
from sqlit.domains.connections.providers.exceptions import MissingDriverError
1930

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Regression test for issue #186: paramiko 4 removed DSSKey, breaking sshtunnel."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
import types
7+
8+
import pytest
9+
10+
from sqlit.domains.connections.app.tunnel import ensure_ssh_tunnel_available
11+
from sqlit.domains.connections.providers.exceptions import MissingDriverError
12+
13+
14+
def test_ensure_ssh_tunnel_raises_missing_driver_when_paramiko_lacks_dsskey(monkeypatch):
15+
"""A paramiko without DSSKey (i.e. 4.x) must surface as MissingDriverError, not AttributeError."""
16+
fake_paramiko = types.ModuleType("paramiko")
17+
fake_paramiko.__version__ = "4.0.0" # type: ignore[attr-defined]
18+
# Intentionally no DSSKey attribute — simulating paramiko 4.
19+
20+
fake_sshtunnel = types.ModuleType("sshtunnel")
21+
22+
monkeypatch.setitem(sys.modules, "paramiko", fake_paramiko)
23+
monkeypatch.setitem(sys.modules, "sshtunnel", fake_sshtunnel)
24+
25+
with pytest.raises(MissingDriverError) as excinfo:
26+
ensure_ssh_tunnel_available()
27+
28+
assert excinfo.value.extra_name == "ssh"
29+
assert "paramiko" in str(excinfo.value.import_error or "").lower()
30+
31+
32+
def test_ensure_ssh_tunnel_passes_when_paramiko_has_dsskey(monkeypatch):
33+
"""A paramiko 3.x with DSSKey must not raise."""
34+
fake_paramiko = types.ModuleType("paramiko")
35+
fake_paramiko.__version__ = "3.5.0" # type: ignore[attr-defined]
36+
fake_paramiko.DSSKey = object # type: ignore[attr-defined]
37+
38+
fake_sshtunnel = types.ModuleType("sshtunnel")
39+
40+
monkeypatch.setitem(sys.modules, "paramiko", fake_paramiko)
41+
monkeypatch.setitem(sys.modules, "sshtunnel", fake_sshtunnel)
42+
43+
ensure_ssh_tunnel_available() # must not raise

0 commit comments

Comments
 (0)