scitex 2.17.0__py3-none-any.whl → 2.17.4__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 +182 -0
- scitex/_dev/_dashboard/_scripts.py +422 -0
- scitex/_dev/_dashboard/_styles.py +295 -0
- scitex/_dev/_dashboard/_templates.py +130 -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/_rtd.py +122 -0
- scitex/_dev/_ssh.py +362 -0
- scitex/_dev/_versions.py +272 -0
- scitex/_mcp_tools/__init__.py +2 -0
- scitex/_mcp_tools/dev.py +186 -0
- scitex/audio/_audio_check.py +84 -41
- scitex/cli/capture.py +45 -22
- scitex/cli/dev.py +494 -0
- scitex/cli/main.py +2 -0
- scitex/cli/stats.py +48 -20
- scitex/cli/verify.py +33 -36
- scitex/plt/__init__.py +16 -6
- scitex/scholar/_mcp/crossref_handlers.py +45 -7
- scitex/scholar/_mcp/openalex_handlers.py +45 -7
- scitex/scholar/config/default.yaml +2 -0
- scitex/scholar/local_dbs/__init__.py +5 -1
- scitex/scholar/local_dbs/export.py +93 -0
- scitex/scholar/local_dbs/unified.py +505 -0
- scitex/scholar/metadata_engines/ScholarEngine.py +11 -0
- scitex/scholar/metadata_engines/individual/OpenAlexLocalEngine.py +346 -0
- scitex/scholar/metadata_engines/individual/__init__.py +1 -0
- scitex/template/__init__.py +18 -1
- scitex/template/clone_research_minimal.py +111 -0
- scitex/verify/README.md +0 -12
- scitex/verify/__init__.py +0 -4
- scitex/verify/_visualize.py +0 -4
- scitex/verify/_viz/__init__.py +0 -18
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/METADATA +2 -1
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/RECORD +45 -24
- scitex/verify/_viz/_plotly.py +0 -193
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/WHEEL +0 -0
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/entry_points.txt +0 -0
- {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/licenses/LICENSE +0 -0
scitex/_dev/_github.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-02
|
|
3
|
+
# File: scitex/_dev/_github.py
|
|
4
|
+
|
|
5
|
+
"""GitHub API integration for version checking."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import urllib.request
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ._config import DevConfig, GitHubRemote, get_enabled_remotes, load_config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_github_token() -> str | None:
|
|
17
|
+
"""Get GitHub token from environment."""
|
|
18
|
+
return os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _github_api_request(url: str) -> dict[str, Any] | list[Any] | None:
|
|
22
|
+
"""Make a GitHub API request.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
url : str
|
|
27
|
+
API URL to fetch.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
dict | list | None
|
|
32
|
+
JSON response or None on error.
|
|
33
|
+
"""
|
|
34
|
+
import json
|
|
35
|
+
|
|
36
|
+
headers = {
|
|
37
|
+
"Accept": "application/vnd.github.v3+json",
|
|
38
|
+
"User-Agent": "scitex-dev",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
token = _get_github_token()
|
|
42
|
+
if token:
|
|
43
|
+
headers["Authorization"] = f"token {token}"
|
|
44
|
+
|
|
45
|
+
req = urllib.request.Request(url, headers=headers)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
49
|
+
return json.loads(response.read().decode())
|
|
50
|
+
except urllib.error.HTTPError as e:
|
|
51
|
+
if e.code == 404:
|
|
52
|
+
return None
|
|
53
|
+
raise
|
|
54
|
+
except Exception:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_github_tags(org: str, repo: str) -> list[str]:
|
|
59
|
+
"""Get tags from a GitHub repository.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
org : str
|
|
64
|
+
GitHub organization or user.
|
|
65
|
+
repo : str
|
|
66
|
+
Repository name.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
list[str]
|
|
71
|
+
List of tag names (most recent first).
|
|
72
|
+
"""
|
|
73
|
+
url = f"https://api.github.com/repos/{org}/{repo}/tags?per_page=10"
|
|
74
|
+
data = _github_api_request(url)
|
|
75
|
+
|
|
76
|
+
if not data or not isinstance(data, list):
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
return [tag.get("name", "") for tag in data if tag.get("name")]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_github_latest_tag(org: str, repo: str) -> str | None:
|
|
83
|
+
"""Get the latest version tag from a GitHub repository.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
org : str
|
|
88
|
+
GitHub organization or user.
|
|
89
|
+
repo : str
|
|
90
|
+
Repository name.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
str | None
|
|
95
|
+
Latest version tag or None.
|
|
96
|
+
"""
|
|
97
|
+
tags = get_github_tags(org, repo)
|
|
98
|
+
|
|
99
|
+
# Filter to version tags (v*)
|
|
100
|
+
version_tags = [t for t in tags if t.startswith("v")]
|
|
101
|
+
if version_tags:
|
|
102
|
+
return version_tags[0]
|
|
103
|
+
|
|
104
|
+
# Fallback: any tags
|
|
105
|
+
return tags[0] if tags else None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_github_release(org: str, repo: str) -> dict[str, Any] | None:
|
|
109
|
+
"""Get the latest release from a GitHub repository.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
org : str
|
|
114
|
+
GitHub organization or user.
|
|
115
|
+
repo : str
|
|
116
|
+
Repository name.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
dict | None
|
|
121
|
+
Release info with keys: tag_name, name, published_at, prerelease.
|
|
122
|
+
"""
|
|
123
|
+
url = f"https://api.github.com/repos/{org}/{repo}/releases/latest"
|
|
124
|
+
data = _github_api_request(url)
|
|
125
|
+
|
|
126
|
+
if not data or not isinstance(data, dict):
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"tag_name": data.get("tag_name"),
|
|
131
|
+
"name": data.get("name"),
|
|
132
|
+
"published_at": data.get("published_at"),
|
|
133
|
+
"prerelease": data.get("prerelease", False),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_github_repo_info(org: str, repo: str) -> dict[str, Any] | None:
|
|
138
|
+
"""Get basic info about a GitHub repository.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
org : str
|
|
143
|
+
GitHub organization or user.
|
|
144
|
+
repo : str
|
|
145
|
+
Repository name.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
dict | None
|
|
150
|
+
Repository info with keys: default_branch, description, stars, forks.
|
|
151
|
+
"""
|
|
152
|
+
url = f"https://api.github.com/repos/{org}/{repo}"
|
|
153
|
+
data = _github_api_request(url)
|
|
154
|
+
|
|
155
|
+
if not data or not isinstance(data, dict):
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"default_branch": data.get("default_branch"),
|
|
160
|
+
"description": data.get("description"),
|
|
161
|
+
"stars": data.get("stargazers_count", 0),
|
|
162
|
+
"forks": data.get("forks_count", 0),
|
|
163
|
+
"private": data.get("private", False),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def check_github_remote(
|
|
168
|
+
remote: GitHubRemote,
|
|
169
|
+
packages: list[str],
|
|
170
|
+
config: DevConfig | None = None,
|
|
171
|
+
) -> dict[str, dict[str, Any]]:
|
|
172
|
+
"""Check versions for packages on a GitHub remote.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
remote : GitHubRemote
|
|
177
|
+
GitHub remote configuration.
|
|
178
|
+
packages : list[str]
|
|
179
|
+
List of package names to check.
|
|
180
|
+
config : DevConfig | None
|
|
181
|
+
Configuration for package -> repo mapping.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
dict
|
|
186
|
+
Package name -> version info mapping.
|
|
187
|
+
"""
|
|
188
|
+
if config is None:
|
|
189
|
+
config = load_config()
|
|
190
|
+
|
|
191
|
+
from ._ecosystem import ECOSYSTEM
|
|
192
|
+
|
|
193
|
+
results = {}
|
|
194
|
+
|
|
195
|
+
for pkg in packages:
|
|
196
|
+
# Get repo name from ecosystem or config
|
|
197
|
+
repo = None
|
|
198
|
+
if pkg in ECOSYSTEM:
|
|
199
|
+
github_repo = ECOSYSTEM[pkg].get("github_repo", "")
|
|
200
|
+
if github_repo:
|
|
201
|
+
# Extract repo name (last part after /)
|
|
202
|
+
repo = github_repo.split("/")[-1]
|
|
203
|
+
|
|
204
|
+
# Check config packages
|
|
205
|
+
if not repo:
|
|
206
|
+
for pkg_conf in config.packages:
|
|
207
|
+
if pkg_conf.name == pkg and pkg_conf.github_repo:
|
|
208
|
+
repo = pkg_conf.github_repo.split("/")[-1]
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
if not repo:
|
|
212
|
+
# Try package name as repo name
|
|
213
|
+
repo = pkg
|
|
214
|
+
|
|
215
|
+
# Get tag and release info
|
|
216
|
+
try:
|
|
217
|
+
latest_tag = get_github_latest_tag(remote.org, repo)
|
|
218
|
+
release = get_github_release(remote.org, repo)
|
|
219
|
+
|
|
220
|
+
results[pkg] = {
|
|
221
|
+
"org": remote.org,
|
|
222
|
+
"repo": repo,
|
|
223
|
+
"latest_tag": latest_tag,
|
|
224
|
+
"release": release.get("tag_name") if release else None,
|
|
225
|
+
"release_date": release.get("published_at") if release else None,
|
|
226
|
+
"status": "ok" if latest_tag else "no_tags",
|
|
227
|
+
}
|
|
228
|
+
except Exception as e:
|
|
229
|
+
results[pkg] = {
|
|
230
|
+
"org": remote.org,
|
|
231
|
+
"repo": repo,
|
|
232
|
+
"latest_tag": None,
|
|
233
|
+
"release": None,
|
|
234
|
+
"status": "error",
|
|
235
|
+
"error": str(e),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return results
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def check_all_remotes(
|
|
242
|
+
packages: list[str] | None = None,
|
|
243
|
+
remotes: list[str] | None = None,
|
|
244
|
+
config: DevConfig | None = None,
|
|
245
|
+
) -> dict[str, dict[str, dict[str, Any]]]:
|
|
246
|
+
"""Check versions on all enabled GitHub remotes.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
packages : list[str] | None
|
|
251
|
+
List of package names. If None, uses ecosystem packages.
|
|
252
|
+
remotes : list[str] | None
|
|
253
|
+
List of remote names to check. If None, checks all enabled remotes.
|
|
254
|
+
config : DevConfig | None
|
|
255
|
+
Configuration to use. If None, loads default config.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
dict
|
|
260
|
+
Mapping: remote_name -> package_name -> version_info
|
|
261
|
+
"""
|
|
262
|
+
if config is None:
|
|
263
|
+
config = load_config()
|
|
264
|
+
|
|
265
|
+
if packages is None:
|
|
266
|
+
from ._ecosystem import get_all_packages
|
|
267
|
+
|
|
268
|
+
packages = get_all_packages()
|
|
269
|
+
|
|
270
|
+
enabled_remotes = get_enabled_remotes(config)
|
|
271
|
+
if remotes:
|
|
272
|
+
enabled_remotes = [r for r in enabled_remotes if r.name in remotes]
|
|
273
|
+
|
|
274
|
+
results = {}
|
|
275
|
+
|
|
276
|
+
for remote in enabled_remotes:
|
|
277
|
+
results[remote.name] = check_github_remote(remote, packages, config)
|
|
278
|
+
results[remote.name]["_remote"] = {
|
|
279
|
+
"org": remote.org,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return results
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def compare_with_local(
|
|
286
|
+
packages: list[str] | None = None,
|
|
287
|
+
config: DevConfig | None = None,
|
|
288
|
+
) -> dict[str, dict[str, Any]]:
|
|
289
|
+
"""Compare local versions with GitHub remotes.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
packages : list[str] | None
|
|
294
|
+
List of package names. If None, uses ecosystem packages.
|
|
295
|
+
config : DevConfig | None
|
|
296
|
+
Configuration to use.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
dict
|
|
301
|
+
Comparison results with local vs remote versions.
|
|
302
|
+
"""
|
|
303
|
+
if config is None:
|
|
304
|
+
config = load_config()
|
|
305
|
+
|
|
306
|
+
if packages is None:
|
|
307
|
+
from ._ecosystem import get_all_packages
|
|
308
|
+
|
|
309
|
+
packages = get_all_packages()
|
|
310
|
+
|
|
311
|
+
from ._versions import list_versions
|
|
312
|
+
|
|
313
|
+
local_versions = list_versions(packages)
|
|
314
|
+
remote_versions = check_all_remotes(packages, config=config)
|
|
315
|
+
|
|
316
|
+
results = {}
|
|
317
|
+
|
|
318
|
+
for pkg in packages:
|
|
319
|
+
local_info = local_versions.get(pkg, {})
|
|
320
|
+
local_tag = local_info.get("git", {}).get("latest_tag")
|
|
321
|
+
local_toml = local_info.get("local", {}).get("pyproject_toml")
|
|
322
|
+
|
|
323
|
+
pkg_result = {
|
|
324
|
+
"local": {
|
|
325
|
+
"tag": local_tag,
|
|
326
|
+
"toml": local_toml,
|
|
327
|
+
},
|
|
328
|
+
"remotes": {},
|
|
329
|
+
"sync_status": "ok",
|
|
330
|
+
"issues": [],
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for remote_name, remote_data in remote_versions.items():
|
|
334
|
+
if remote_name.startswith("_"):
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
pkg_remote = remote_data.get(pkg, {})
|
|
338
|
+
remote_tag = pkg_remote.get("latest_tag")
|
|
339
|
+
|
|
340
|
+
pkg_result["remotes"][remote_name] = {
|
|
341
|
+
"tag": remote_tag,
|
|
342
|
+
"release": pkg_remote.get("release"),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Check sync status
|
|
346
|
+
if local_tag and remote_tag and local_tag != remote_tag:
|
|
347
|
+
pkg_result["issues"].append(
|
|
348
|
+
f"local tag ({local_tag}) != {remote_name} ({remote_tag})"
|
|
349
|
+
)
|
|
350
|
+
pkg_result["sync_status"] = "out_of_sync"
|
|
351
|
+
|
|
352
|
+
if pkg_result["issues"]:
|
|
353
|
+
pkg_result["sync_status"] = "out_of_sync"
|
|
354
|
+
|
|
355
|
+
results[pkg] = pkg_result
|
|
356
|
+
|
|
357
|
+
return results
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# EOF
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-02
|
|
3
|
+
# File: scitex/_dev/_mcp/__init__.py
|
|
4
|
+
|
|
5
|
+
"""MCP handlers for developer utilities."""
|
|
6
|
+
|
|
7
|
+
from .handlers import check_versions_handler, list_versions_handler
|
|
8
|
+
|
|
9
|
+
__all__ = ["list_versions_handler", "check_versions_handler"]
|
|
10
|
+
|
|
11
|
+
# EOF
|
|
@@ -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/_rtd.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-02-03
|
|
3
|
+
# File: scitex/_dev/_rtd.py
|
|
4
|
+
|
|
5
|
+
"""Read the Docs build status checking for scitex ecosystem."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import urllib.request
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ._ecosystem import ECOSYSTEM
|
|
13
|
+
|
|
14
|
+
# RTD project slugs (if different from package name)
|
|
15
|
+
RTD_SLUGS: dict[str, str] = {
|
|
16
|
+
"scitex": "scitex-python",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_rtd_slug(package: str) -> str:
|
|
21
|
+
"""Get RTD project slug for a package."""
|
|
22
|
+
return RTD_SLUGS.get(package, package)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_rtd_status(package: str, version: str = "latest") -> dict[str, Any]:
|
|
26
|
+
"""Check Read the Docs build status for a package.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
package : str
|
|
31
|
+
Package name.
|
|
32
|
+
version : str
|
|
33
|
+
RTD version to check (latest, stable, etc.).
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
dict
|
|
38
|
+
Status info with keys: status, version, url, error (if any).
|
|
39
|
+
"""
|
|
40
|
+
slug = get_rtd_slug(package)
|
|
41
|
+
badge_url = f"https://readthedocs.org/projects/{slug}/badge/?version={version}"
|
|
42
|
+
docs_url = f"https://{slug}.readthedocs.io/en/{version}/"
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Fetch the badge SVG content to determine status
|
|
46
|
+
req = urllib.request.Request(badge_url)
|
|
47
|
+
req.add_header("User-Agent", "scitex-dev-tools/1.0")
|
|
48
|
+
|
|
49
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
50
|
+
content = response.read().decode("utf-8")
|
|
51
|
+
|
|
52
|
+
# Parse SVG content for status
|
|
53
|
+
if "passing" in content.lower():
|
|
54
|
+
status = "passing"
|
|
55
|
+
elif "failing" in content.lower():
|
|
56
|
+
status = "failing"
|
|
57
|
+
elif "unknown" in content.lower():
|
|
58
|
+
status = "unknown"
|
|
59
|
+
else:
|
|
60
|
+
status = "unknown"
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"status": status,
|
|
64
|
+
"version": version,
|
|
65
|
+
"url": docs_url,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
except urllib.error.HTTPError as e:
|
|
69
|
+
if e.code == 404:
|
|
70
|
+
return {
|
|
71
|
+
"status": "not_found",
|
|
72
|
+
"version": version,
|
|
73
|
+
"error": f"Project '{slug}' not found on RTD",
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
"status": "error",
|
|
77
|
+
"version": version,
|
|
78
|
+
"error": f"HTTP {e.code}: {e.reason}",
|
|
79
|
+
}
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return {
|
|
82
|
+
"status": "error",
|
|
83
|
+
"version": version,
|
|
84
|
+
"error": str(e),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_all_rtd(
|
|
89
|
+
packages: list[str] | None = None,
|
|
90
|
+
versions: list[str] | None = None,
|
|
91
|
+
) -> dict[str, dict[str, dict[str, Any]]]:
|
|
92
|
+
"""Check RTD status for all ecosystem packages.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
packages : list[str] | None
|
|
97
|
+
List of package names. If None, uses ecosystem packages.
|
|
98
|
+
versions : list[str] | None
|
|
99
|
+
List of versions to check. Default: ["latest", "stable"].
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
dict
|
|
104
|
+
Mapping: version -> package_name -> status_info
|
|
105
|
+
"""
|
|
106
|
+
if packages is None:
|
|
107
|
+
packages = list(ECOSYSTEM.keys())
|
|
108
|
+
|
|
109
|
+
if versions is None:
|
|
110
|
+
versions = ["latest", "stable"]
|
|
111
|
+
|
|
112
|
+
results: dict[str, dict[str, dict[str, Any]]] = {}
|
|
113
|
+
|
|
114
|
+
for version in versions:
|
|
115
|
+
results[version] = {}
|
|
116
|
+
for package in packages:
|
|
117
|
+
results[version][package] = check_rtd_status(package, version)
|
|
118
|
+
|
|
119
|
+
return results
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# EOF
|