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.
Files changed (46) hide show
  1. scitex/_dev/__init__.py +122 -0
  2. scitex/_dev/_config.py +391 -0
  3. scitex/_dev/_dashboard/__init__.py +11 -0
  4. scitex/_dev/_dashboard/_app.py +89 -0
  5. scitex/_dev/_dashboard/_routes.py +182 -0
  6. scitex/_dev/_dashboard/_scripts.py +422 -0
  7. scitex/_dev/_dashboard/_styles.py +295 -0
  8. scitex/_dev/_dashboard/_templates.py +130 -0
  9. scitex/_dev/_dashboard/static/version-dashboard-favicon.svg +12 -0
  10. scitex/_dev/_ecosystem.py +109 -0
  11. scitex/_dev/_github.py +360 -0
  12. scitex/_dev/_mcp/__init__.py +11 -0
  13. scitex/_dev/_mcp/handlers.py +182 -0
  14. scitex/_dev/_rtd.py +122 -0
  15. scitex/_dev/_ssh.py +362 -0
  16. scitex/_dev/_versions.py +272 -0
  17. scitex/_mcp_tools/__init__.py +2 -0
  18. scitex/_mcp_tools/dev.py +186 -0
  19. scitex/audio/_audio_check.py +84 -41
  20. scitex/cli/capture.py +45 -22
  21. scitex/cli/dev.py +494 -0
  22. scitex/cli/main.py +2 -0
  23. scitex/cli/stats.py +48 -20
  24. scitex/cli/verify.py +33 -36
  25. scitex/plt/__init__.py +16 -6
  26. scitex/scholar/_mcp/crossref_handlers.py +45 -7
  27. scitex/scholar/_mcp/openalex_handlers.py +45 -7
  28. scitex/scholar/config/default.yaml +2 -0
  29. scitex/scholar/local_dbs/__init__.py +5 -1
  30. scitex/scholar/local_dbs/export.py +93 -0
  31. scitex/scholar/local_dbs/unified.py +505 -0
  32. scitex/scholar/metadata_engines/ScholarEngine.py +11 -0
  33. scitex/scholar/metadata_engines/individual/OpenAlexLocalEngine.py +346 -0
  34. scitex/scholar/metadata_engines/individual/__init__.py +1 -0
  35. scitex/template/__init__.py +18 -1
  36. scitex/template/clone_research_minimal.py +111 -0
  37. scitex/verify/README.md +0 -12
  38. scitex/verify/__init__.py +0 -4
  39. scitex/verify/_visualize.py +0 -4
  40. scitex/verify/_viz/__init__.py +0 -18
  41. {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/METADATA +2 -1
  42. {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/RECORD +45 -24
  43. scitex/verify/_viz/_plotly.py +0 -193
  44. {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/WHEEL +0 -0
  45. {scitex-2.17.0.dist-info → scitex-2.17.4.dist-info}/entry_points.txt +0 -0
  46. {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