scitex 2.16.2__py3-none-any.whl → 2.17.3__py3-none-any.whl
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.
- scitex/_dev/__init__.py +122 -0
- scitex/_dev/_config.py +391 -0
- scitex/_dev/_dashboard/__init__.py +11 -0
- scitex/_dev/_dashboard/_app.py +89 -0
- scitex/_dev/_dashboard/_routes.py +169 -0
- scitex/_dev/_dashboard/_scripts.py +301 -0
- scitex/_dev/_dashboard/_styles.py +205 -0
- scitex/_dev/_dashboard/_templates.py +117 -0
- scitex/_dev/_dashboard/static/version-dashboard-favicon.svg +12 -0
- scitex/_dev/_ecosystem.py +109 -0
- scitex/_dev/_github.py +360 -0
- scitex/_dev/_mcp/__init__.py +11 -0
- scitex/_dev/_mcp/handlers.py +182 -0
- scitex/_dev/_ssh.py +332 -0
- scitex/_dev/_versions.py +272 -0
- scitex/_mcp_resources/_cheatsheet.py +1 -1
- scitex/_mcp_resources/_modules.py +1 -1
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/dev.py +186 -0
- scitex/_mcp_tools/verify.py +256 -0
- scitex/audio/_audio_check.py +84 -41
- scitex/cli/capture.py +45 -22
- scitex/cli/dev.py +494 -0
- scitex/cli/main.py +4 -0
- scitex/cli/stats.py +48 -20
- scitex/cli/verify.py +473 -0
- scitex/dev/plt/__init__.py +1 -1
- scitex/dev/plt/mpl/get_dir_ax.py +1 -1
- scitex/dev/plt/mpl/get_signatures.py +1 -1
- scitex/dev/plt/mpl/get_signatures_details.py +1 -1
- scitex/io/_load.py +8 -1
- scitex/io/_save.py +12 -0
- scitex/plt/__init__.py +16 -6
- scitex/session/README.md +2 -2
- scitex/session/__init__.py +1 -0
- scitex/session/_decorator.py +57 -33
- scitex/session/_lifecycle/__init__.py +23 -0
- scitex/session/_lifecycle/_close.py +225 -0
- scitex/session/_lifecycle/_config.py +112 -0
- scitex/session/_lifecycle/_matplotlib.py +83 -0
- scitex/session/_lifecycle/_start.py +246 -0
- scitex/session/_lifecycle/_utils.py +186 -0
- scitex/session/_manager.py +40 -3
- scitex/session/template.py +1 -1
- scitex/template/__init__.py +18 -1
- scitex/template/_templates/plt.py +1 -1
- scitex/template/_templates/session.py +1 -1
- scitex/template/clone_research_minimal.py +111 -0
- scitex/verify/README.md +300 -0
- scitex/verify/__init__.py +208 -0
- scitex/verify/_chain.py +369 -0
- scitex/verify/_db.py +600 -0
- scitex/verify/_hash.py +187 -0
- scitex/verify/_integration.py +127 -0
- scitex/verify/_rerun.py +253 -0
- scitex/verify/_tracker.py +330 -0
- scitex/verify/_visualize.py +44 -0
- scitex/verify/_viz/__init__.py +38 -0
- scitex/verify/_viz/_colors.py +84 -0
- scitex/verify/_viz/_format.py +302 -0
- scitex/verify/_viz/_json.py +192 -0
- scitex/verify/_viz/_mermaid.py +440 -0
- scitex/verify/_viz/_templates.py +246 -0
- scitex/verify/_viz/_utils.py +56 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/METADATA +2 -1
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/RECORD +69 -28
- scitex/session/_lifecycle.py +0 -827
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/WHEEL +0 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-02
|
|
3
|
+
# File: scitex/_dev/_mcp/handlers.py
|
|
4
|
+
|
|
5
|
+
"""MCP handler implementations for developer utilities."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def list_versions_handler(
|
|
13
|
+
packages: list[str] | None = None,
|
|
14
|
+
) -> dict[str, Any]:
|
|
15
|
+
"""List versions across the scitex ecosystem.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
packages : list[str] | None
|
|
20
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
dict
|
|
25
|
+
Version information for each package.
|
|
26
|
+
"""
|
|
27
|
+
from scitex._dev import list_versions
|
|
28
|
+
|
|
29
|
+
return list_versions(packages)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def check_versions_handler(
|
|
33
|
+
packages: list[str] | None = None,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""Check version consistency across the scitex ecosystem.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
packages : list[str] | None
|
|
40
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
dict
|
|
45
|
+
Detailed version check results with summary.
|
|
46
|
+
"""
|
|
47
|
+
from scitex._dev import check_versions
|
|
48
|
+
|
|
49
|
+
return check_versions(packages)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def check_hosts_handler(
|
|
53
|
+
packages: list[str] | None = None,
|
|
54
|
+
hosts: list[str] | None = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
"""Check versions on SSH hosts.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
packages : list[str] | None
|
|
61
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
62
|
+
hosts : list[str] | None
|
|
63
|
+
List of host names to check. If None, checks all enabled hosts.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
dict
|
|
68
|
+
Host name -> package name -> version info mapping.
|
|
69
|
+
"""
|
|
70
|
+
from scitex._dev import check_all_hosts
|
|
71
|
+
|
|
72
|
+
return check_all_hosts(packages=packages, hosts=hosts)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def check_remotes_handler(
|
|
76
|
+
packages: list[str] | None = None,
|
|
77
|
+
remotes: list[str] | None = None,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""Check versions on GitHub remotes.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
packages : list[str] | None
|
|
84
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
85
|
+
remotes : list[str] | None
|
|
86
|
+
List of remote names to check. If None, checks all enabled remotes.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
dict
|
|
91
|
+
Remote name -> package name -> version info mapping.
|
|
92
|
+
"""
|
|
93
|
+
from scitex._dev import check_all_remotes
|
|
94
|
+
|
|
95
|
+
return check_all_remotes(packages=packages, remotes=remotes)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def get_config_handler() -> dict[str, Any]:
|
|
99
|
+
"""Get current developer configuration.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
dict
|
|
104
|
+
Configuration including packages, hosts, remotes, branches.
|
|
105
|
+
"""
|
|
106
|
+
from scitex._dev import get_config_path, load_config
|
|
107
|
+
|
|
108
|
+
config = load_config()
|
|
109
|
+
return {
|
|
110
|
+
"config_path": str(get_config_path()),
|
|
111
|
+
"packages": [
|
|
112
|
+
{
|
|
113
|
+
"name": p.name,
|
|
114
|
+
"local_path": p.local_path,
|
|
115
|
+
"pypi_name": p.pypi_name,
|
|
116
|
+
"github_repo": p.github_repo,
|
|
117
|
+
}
|
|
118
|
+
for p in config.packages
|
|
119
|
+
],
|
|
120
|
+
"hosts": [
|
|
121
|
+
{
|
|
122
|
+
"name": h.name,
|
|
123
|
+
"hostname": h.hostname,
|
|
124
|
+
"user": h.user,
|
|
125
|
+
"role": h.role,
|
|
126
|
+
"enabled": h.enabled,
|
|
127
|
+
}
|
|
128
|
+
for h in config.hosts
|
|
129
|
+
],
|
|
130
|
+
"github_remotes": [
|
|
131
|
+
{"name": r.name, "org": r.org, "enabled": r.enabled}
|
|
132
|
+
for r in config.github_remotes
|
|
133
|
+
],
|
|
134
|
+
"branches": config.branches,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def get_full_versions_handler(
|
|
139
|
+
packages: list[str] | None = None,
|
|
140
|
+
include_hosts: bool = False,
|
|
141
|
+
include_remotes: bool = False,
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
"""Get comprehensive version data from all sources.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
packages : list[str] | None
|
|
148
|
+
List of package names to check. If None, checks all ecosystem packages.
|
|
149
|
+
include_hosts : bool
|
|
150
|
+
Include SSH host version checks.
|
|
151
|
+
include_remotes : bool
|
|
152
|
+
Include GitHub remote version checks.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
dict
|
|
157
|
+
Combined version data from local, hosts, and remotes.
|
|
158
|
+
"""
|
|
159
|
+
from scitex._dev import check_all_hosts, check_all_remotes, list_versions
|
|
160
|
+
|
|
161
|
+
result = {
|
|
162
|
+
"packages": list_versions(packages),
|
|
163
|
+
"hosts": {},
|
|
164
|
+
"remotes": {},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if include_hosts:
|
|
168
|
+
try:
|
|
169
|
+
result["hosts"] = check_all_hosts(packages=packages)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
result["hosts"] = {"error": str(e)}
|
|
172
|
+
|
|
173
|
+
if include_remotes:
|
|
174
|
+
try:
|
|
175
|
+
result["remotes"] = check_all_remotes(packages=packages)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
result["remotes"] = {"error": str(e)}
|
|
178
|
+
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# EOF
|
scitex/_dev/_ssh.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-02
|
|
3
|
+
# File: scitex/_dev/_ssh.py
|
|
4
|
+
|
|
5
|
+
"""SSH-based remote version checking for scitex ecosystem."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ._config import DevConfig, HostConfig, get_enabled_hosts, load_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_remote_version(host: HostConfig, package: str) -> dict[str, Any]:
|
|
16
|
+
"""Get version of a package on a remote host via SSH.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
host : HostConfig
|
|
21
|
+
Host configuration.
|
|
22
|
+
package : str
|
|
23
|
+
Package name (PyPI name).
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
dict
|
|
28
|
+
Version info with keys: installed, status, error (if any).
|
|
29
|
+
"""
|
|
30
|
+
# Build SSH command
|
|
31
|
+
ssh_args = ["ssh"]
|
|
32
|
+
|
|
33
|
+
if host.ssh_key:
|
|
34
|
+
ssh_args.extend(["-i", host.ssh_key])
|
|
35
|
+
|
|
36
|
+
if host.port != 22:
|
|
37
|
+
ssh_args.extend(["-p", str(host.port)])
|
|
38
|
+
|
|
39
|
+
# Add connection options for non-interactive use
|
|
40
|
+
ssh_args.extend(
|
|
41
|
+
[
|
|
42
|
+
"-o",
|
|
43
|
+
"BatchMode=yes",
|
|
44
|
+
"-o",
|
|
45
|
+
"StrictHostKeyChecking=accept-new",
|
|
46
|
+
"-o",
|
|
47
|
+
"ConnectTimeout=5",
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
ssh_target = f"{host.user}@{host.hostname}"
|
|
52
|
+
ssh_args.append(ssh_target)
|
|
53
|
+
|
|
54
|
+
# Python command to get version
|
|
55
|
+
python_cmd = f"""python3 -c "
|
|
56
|
+
try:
|
|
57
|
+
from importlib.metadata import version
|
|
58
|
+
print(version('{package}'))
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print('ERROR:' + str(e))
|
|
61
|
+
"
|
|
62
|
+
"""
|
|
63
|
+
ssh_args.append(python_cmd)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
result = subprocess.run(
|
|
67
|
+
ssh_args,
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
timeout=15,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
output = result.stdout.strip()
|
|
74
|
+
|
|
75
|
+
if result.returncode != 0:
|
|
76
|
+
error = result.stderr.strip() or "SSH connection failed"
|
|
77
|
+
return {
|
|
78
|
+
"installed": None,
|
|
79
|
+
"status": "error",
|
|
80
|
+
"error": error,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if output.startswith("ERROR:"):
|
|
84
|
+
return {
|
|
85
|
+
"installed": None,
|
|
86
|
+
"status": "not_installed",
|
|
87
|
+
"error": output[6:],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"installed": output,
|
|
92
|
+
"status": "ok",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
except subprocess.TimeoutExpired:
|
|
96
|
+
return {
|
|
97
|
+
"installed": None,
|
|
98
|
+
"status": "timeout",
|
|
99
|
+
"error": "SSH connection timed out",
|
|
100
|
+
}
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return {
|
|
103
|
+
"installed": None,
|
|
104
|
+
"status": "error",
|
|
105
|
+
"error": str(e),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_remote_versions(
|
|
110
|
+
host: HostConfig,
|
|
111
|
+
packages: list[str],
|
|
112
|
+
) -> dict[str, dict[str, Any]]:
|
|
113
|
+
"""Get versions of multiple packages on a remote host.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
host : HostConfig
|
|
118
|
+
Host configuration.
|
|
119
|
+
packages : list[str]
|
|
120
|
+
List of package names.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
dict
|
|
125
|
+
Package name -> version info mapping.
|
|
126
|
+
"""
|
|
127
|
+
# Build SSH command that checks all packages at once
|
|
128
|
+
ssh_args = ["ssh"]
|
|
129
|
+
|
|
130
|
+
if host.ssh_key:
|
|
131
|
+
ssh_args.extend(["-i", host.ssh_key])
|
|
132
|
+
|
|
133
|
+
if host.port != 22:
|
|
134
|
+
ssh_args.extend(["-p", str(host.port)])
|
|
135
|
+
|
|
136
|
+
ssh_args.extend(
|
|
137
|
+
[
|
|
138
|
+
"-o",
|
|
139
|
+
"BatchMode=yes",
|
|
140
|
+
"-o",
|
|
141
|
+
"StrictHostKeyChecking=accept-new",
|
|
142
|
+
"-o",
|
|
143
|
+
"ConnectTimeout=5",
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
ssh_target = f"{host.user}@{host.hostname}"
|
|
148
|
+
ssh_args.append(ssh_target)
|
|
149
|
+
|
|
150
|
+
# Build Python command to check all packages
|
|
151
|
+
packages_str = ",".join(f"'{p}'" for p in packages)
|
|
152
|
+
python_cmd = f"""python3 -c "
|
|
153
|
+
import json
|
|
154
|
+
from importlib.metadata import version
|
|
155
|
+
results = {{}}
|
|
156
|
+
for pkg in [{packages_str}]:
|
|
157
|
+
try:
|
|
158
|
+
results[pkg] = {{'installed': version(pkg), 'status': 'ok'}}
|
|
159
|
+
except Exception as e:
|
|
160
|
+
results[pkg] = {{'installed': None, 'status': 'not_installed', 'error': str(e)}}
|
|
161
|
+
print(json.dumps(results))
|
|
162
|
+
"
|
|
163
|
+
"""
|
|
164
|
+
ssh_args.append(python_cmd)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
ssh_args,
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
timeout=30,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if result.returncode != 0:
|
|
175
|
+
error = result.stderr.strip() or "SSH connection failed"
|
|
176
|
+
return {
|
|
177
|
+
pkg: {"installed": None, "status": "error", "error": error}
|
|
178
|
+
for pkg in packages
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
import json
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
return json.loads(result.stdout.strip())
|
|
185
|
+
except json.JSONDecodeError:
|
|
186
|
+
return {
|
|
187
|
+
pkg: {
|
|
188
|
+
"installed": None,
|
|
189
|
+
"status": "error",
|
|
190
|
+
"error": f"Invalid response: {result.stdout[:100]}",
|
|
191
|
+
}
|
|
192
|
+
for pkg in packages
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
except subprocess.TimeoutExpired:
|
|
196
|
+
return {
|
|
197
|
+
pkg: {"installed": None, "status": "timeout", "error": "SSH timed out"}
|
|
198
|
+
for pkg in packages
|
|
199
|
+
}
|
|
200
|
+
except Exception as e:
|
|
201
|
+
return {
|
|
202
|
+
pkg: {"installed": None, "status": "error", "error": str(e)}
|
|
203
|
+
for pkg in packages
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def check_all_hosts(
|
|
208
|
+
packages: list[str] | None = None,
|
|
209
|
+
hosts: list[str] | None = None,
|
|
210
|
+
config: DevConfig | None = None,
|
|
211
|
+
) -> dict[str, dict[str, dict[str, Any]]]:
|
|
212
|
+
"""Check versions on all enabled hosts.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
packages : list[str] | None
|
|
217
|
+
List of package names. If None, uses ecosystem packages.
|
|
218
|
+
hosts : list[str] | None
|
|
219
|
+
List of host names to check. If None, checks all enabled hosts.
|
|
220
|
+
config : DevConfig | None
|
|
221
|
+
Configuration to use. If None, loads default config.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
dict
|
|
226
|
+
Mapping: host_name -> package_name -> version_info
|
|
227
|
+
"""
|
|
228
|
+
if config is None:
|
|
229
|
+
config = load_config()
|
|
230
|
+
|
|
231
|
+
if packages is None:
|
|
232
|
+
from ._ecosystem import get_all_packages
|
|
233
|
+
|
|
234
|
+
packages = get_all_packages()
|
|
235
|
+
|
|
236
|
+
# Get pypi names for packages
|
|
237
|
+
from ._ecosystem import ECOSYSTEM
|
|
238
|
+
|
|
239
|
+
pypi_names = []
|
|
240
|
+
name_map = {} # pypi_name -> package_name
|
|
241
|
+
for pkg in packages:
|
|
242
|
+
if pkg in ECOSYSTEM:
|
|
243
|
+
pypi_name = ECOSYSTEM[pkg].get("pypi_name", pkg)
|
|
244
|
+
else:
|
|
245
|
+
pypi_name = pkg
|
|
246
|
+
pypi_names.append(pypi_name)
|
|
247
|
+
name_map[pypi_name] = pkg
|
|
248
|
+
|
|
249
|
+
# Get enabled hosts
|
|
250
|
+
enabled_hosts = get_enabled_hosts(config)
|
|
251
|
+
if hosts:
|
|
252
|
+
enabled_hosts = [h for h in enabled_hosts if h.name in hosts]
|
|
253
|
+
|
|
254
|
+
results: dict[str, dict[str, dict[str, Any]]] = {}
|
|
255
|
+
|
|
256
|
+
for host in enabled_hosts:
|
|
257
|
+
host_versions = get_remote_versions(host, pypi_names)
|
|
258
|
+
# Map back to package names
|
|
259
|
+
results[host.name] = {
|
|
260
|
+
name_map.get(pypi, pypi): info for pypi, info in host_versions.items()
|
|
261
|
+
}
|
|
262
|
+
# Add host metadata
|
|
263
|
+
results[host.name]["_host"] = {
|
|
264
|
+
"hostname": host.hostname,
|
|
265
|
+
"role": host.role,
|
|
266
|
+
"user": host.user,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return results
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_host_connection(host: HostConfig) -> dict[str, Any]:
|
|
273
|
+
"""Test SSH connection to a host.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
host : HostConfig
|
|
278
|
+
Host to test.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
dict
|
|
283
|
+
Connection status with keys: connected, error, python_version.
|
|
284
|
+
"""
|
|
285
|
+
ssh_args = ["ssh"]
|
|
286
|
+
|
|
287
|
+
if host.ssh_key:
|
|
288
|
+
ssh_args.extend(["-i", host.ssh_key])
|
|
289
|
+
|
|
290
|
+
if host.port != 22:
|
|
291
|
+
ssh_args.extend(["-p", str(host.port)])
|
|
292
|
+
|
|
293
|
+
ssh_args.extend(
|
|
294
|
+
[
|
|
295
|
+
"-o",
|
|
296
|
+
"BatchMode=yes",
|
|
297
|
+
"-o",
|
|
298
|
+
"StrictHostKeyChecking=accept-new",
|
|
299
|
+
"-o",
|
|
300
|
+
"ConnectTimeout=5",
|
|
301
|
+
]
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
ssh_target = f"{host.user}@{host.hostname}"
|
|
305
|
+
ssh_args.append(ssh_target)
|
|
306
|
+
ssh_args.append("python3 --version")
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
result = subprocess.run(
|
|
310
|
+
ssh_args,
|
|
311
|
+
capture_output=True,
|
|
312
|
+
text=True,
|
|
313
|
+
timeout=10,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if result.returncode == 0:
|
|
317
|
+
return {
|
|
318
|
+
"connected": True,
|
|
319
|
+
"python_version": result.stdout.strip(),
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
"connected": False,
|
|
323
|
+
"error": result.stderr.strip() or "Connection failed",
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
except subprocess.TimeoutExpired:
|
|
327
|
+
return {"connected": False, "error": "Connection timed out"}
|
|
328
|
+
except Exception as e:
|
|
329
|
+
return {"connected": False, "error": str(e)}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# EOF
|