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.
Files changed (70) 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 +169 -0
  6. scitex/_dev/_dashboard/_scripts.py +301 -0
  7. scitex/_dev/_dashboard/_styles.py +205 -0
  8. scitex/_dev/_dashboard/_templates.py +117 -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/_ssh.py +332 -0
  15. scitex/_dev/_versions.py +272 -0
  16. scitex/_mcp_resources/_cheatsheet.py +1 -1
  17. scitex/_mcp_resources/_modules.py +1 -1
  18. scitex/_mcp_tools/__init__.py +4 -0
  19. scitex/_mcp_tools/dev.py +186 -0
  20. scitex/_mcp_tools/verify.py +256 -0
  21. scitex/audio/_audio_check.py +84 -41
  22. scitex/cli/capture.py +45 -22
  23. scitex/cli/dev.py +494 -0
  24. scitex/cli/main.py +4 -0
  25. scitex/cli/stats.py +48 -20
  26. scitex/cli/verify.py +473 -0
  27. scitex/dev/plt/__init__.py +1 -1
  28. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  29. scitex/dev/plt/mpl/get_signatures.py +1 -1
  30. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  31. scitex/io/_load.py +8 -1
  32. scitex/io/_save.py +12 -0
  33. scitex/plt/__init__.py +16 -6
  34. scitex/session/README.md +2 -2
  35. scitex/session/__init__.py +1 -0
  36. scitex/session/_decorator.py +57 -33
  37. scitex/session/_lifecycle/__init__.py +23 -0
  38. scitex/session/_lifecycle/_close.py +225 -0
  39. scitex/session/_lifecycle/_config.py +112 -0
  40. scitex/session/_lifecycle/_matplotlib.py +83 -0
  41. scitex/session/_lifecycle/_start.py +246 -0
  42. scitex/session/_lifecycle/_utils.py +186 -0
  43. scitex/session/_manager.py +40 -3
  44. scitex/session/template.py +1 -1
  45. scitex/template/__init__.py +18 -1
  46. scitex/template/_templates/plt.py +1 -1
  47. scitex/template/_templates/session.py +1 -1
  48. scitex/template/clone_research_minimal.py +111 -0
  49. scitex/verify/README.md +300 -0
  50. scitex/verify/__init__.py +208 -0
  51. scitex/verify/_chain.py +369 -0
  52. scitex/verify/_db.py +600 -0
  53. scitex/verify/_hash.py +187 -0
  54. scitex/verify/_integration.py +127 -0
  55. scitex/verify/_rerun.py +253 -0
  56. scitex/verify/_tracker.py +330 -0
  57. scitex/verify/_visualize.py +44 -0
  58. scitex/verify/_viz/__init__.py +38 -0
  59. scitex/verify/_viz/_colors.py +84 -0
  60. scitex/verify/_viz/_format.py +302 -0
  61. scitex/verify/_viz/_json.py +192 -0
  62. scitex/verify/_viz/_mermaid.py +440 -0
  63. scitex/verify/_viz/_templates.py +246 -0
  64. scitex/verify/_viz/_utils.py +56 -0
  65. {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/METADATA +2 -1
  66. {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/RECORD +69 -28
  67. scitex/session/_lifecycle.py +0 -827
  68. {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/WHEEL +0 -0
  69. {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/entry_points.txt +0 -0
  70. {scitex-2.16.2.dist-info → scitex-2.17.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_dev/_versions.py
4
+
5
+ """Core version checking logic for the scitex ecosystem."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import subprocess
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from ._ecosystem import ECOSYSTEM, get_all_packages, get_local_path
15
+
16
+
17
+ def get_version_from_toml(path: Path) -> str | None:
18
+ """Read version from pyproject.toml."""
19
+ toml_path = path / "pyproject.toml"
20
+ if not toml_path.exists():
21
+ return None
22
+
23
+ try:
24
+ # Python 3.11+
25
+ import tomllib
26
+
27
+ with open(toml_path, "rb") as f:
28
+ data = tomllib.load(f)
29
+ except ImportError:
30
+ try:
31
+ import tomli
32
+
33
+ with open(toml_path, "rb") as f:
34
+ data = tomli.load(f)
35
+ except ImportError:
36
+ # Fallback: regex parse
37
+ content = toml_path.read_text()
38
+ match = re.search(
39
+ r'^version\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE
40
+ )
41
+ return match.group(1) if match else None
42
+
43
+ return data.get("project", {}).get("version")
44
+
45
+
46
+ def get_version_installed(package: str) -> str | None:
47
+ """Get version from importlib.metadata."""
48
+ try:
49
+ from importlib.metadata import version
50
+
51
+ return version(package)
52
+ except Exception:
53
+ return None
54
+
55
+
56
+ def get_git_latest_tag(path: Path) -> str | None:
57
+ """Get latest git tag (version tags only)."""
58
+ if not path.exists():
59
+ return None
60
+
61
+ try:
62
+ result = subprocess.run(
63
+ ["git", "describe", "--tags", "--abbrev=0", "--match", "v*"],
64
+ cwd=path,
65
+ capture_output=True,
66
+ text=True,
67
+ timeout=5,
68
+ )
69
+ if result.returncode == 0:
70
+ return result.stdout.strip()
71
+ except Exception:
72
+ pass
73
+
74
+ # Fallback: list all tags
75
+ try:
76
+ result = subprocess.run(
77
+ ["git", "tag", "-l", "v*", "--sort=-v:refname"],
78
+ cwd=path,
79
+ capture_output=True,
80
+ text=True,
81
+ timeout=5,
82
+ )
83
+ if result.returncode == 0 and result.stdout.strip():
84
+ return result.stdout.strip().split("\n")[0]
85
+ except Exception:
86
+ pass
87
+
88
+ return None
89
+
90
+
91
+ def get_git_branch(path: Path) -> str | None:
92
+ """Get current git branch."""
93
+ if not path.exists():
94
+ return None
95
+
96
+ try:
97
+ result = subprocess.run(
98
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
99
+ cwd=path,
100
+ capture_output=True,
101
+ text=True,
102
+ timeout=5,
103
+ )
104
+ if result.returncode == 0:
105
+ return result.stdout.strip()
106
+ except Exception:
107
+ pass
108
+
109
+ return None
110
+
111
+
112
+ def get_pypi_version(package: str) -> str | None:
113
+ """Fetch latest version from PyPI API."""
114
+ try:
115
+ import urllib.request
116
+
117
+ url = f"https://pypi.org/pypi/{package}/json"
118
+ with urllib.request.urlopen(url, timeout=5) as response:
119
+ import json
120
+
121
+ data = json.loads(response.read().decode())
122
+ return data.get("info", {}).get("version")
123
+ except Exception:
124
+ return None
125
+
126
+
127
+ def _normalize_version(v: str | None) -> str | None:
128
+ """Normalize version string (strip v prefix)."""
129
+ if v is None:
130
+ return None
131
+ return v.lstrip("v")
132
+
133
+
134
+ def _compare_versions(v1: str | None, v2: str | None) -> int:
135
+ """Compare two version strings. Returns -1, 0, or 1."""
136
+ if v1 is None or v2 is None:
137
+ return 0
138
+
139
+ from packaging.version import Version
140
+
141
+ try:
142
+ ver1 = Version(_normalize_version(v1))
143
+ ver2 = Version(_normalize_version(v2))
144
+ if ver1 < ver2:
145
+ return -1
146
+ if ver1 > ver2:
147
+ return 1
148
+ return 0
149
+ except Exception:
150
+ # Fallback: string comparison
151
+ return 0
152
+
153
+
154
+ def _determine_status(info: dict[str, Any]) -> tuple[str, list[str]]:
155
+ """Determine version status and issues."""
156
+ issues = []
157
+
158
+ toml_ver = info.get("local", {}).get("pyproject_toml")
159
+ installed_ver = info.get("local", {}).get("installed")
160
+ tag_ver = _normalize_version(info.get("git", {}).get("latest_tag"))
161
+ pypi_ver = info.get("remote", {}).get("pypi")
162
+
163
+ # Check local consistency
164
+ if toml_ver and installed_ver and toml_ver != installed_ver:
165
+ issues.append(f"pyproject.toml ({toml_ver}) != installed ({installed_ver})")
166
+
167
+ # Check if toml matches tag
168
+ if toml_ver and tag_ver and toml_ver != tag_ver:
169
+ issues.append(f"pyproject.toml ({toml_ver}) != git tag ({tag_ver})")
170
+
171
+ # Check pypi status
172
+ if toml_ver and pypi_ver:
173
+ cmp = _compare_versions(toml_ver, pypi_ver)
174
+ if cmp > 0:
175
+ issues.append(f"local ({toml_ver}) > pypi ({pypi_ver}) - ready to release")
176
+ return "unreleased", issues
177
+ if cmp < 0:
178
+ issues.append(f"local ({toml_ver}) < pypi ({pypi_ver}) - outdated")
179
+ return "outdated", issues
180
+
181
+ if issues:
182
+ return "mismatch", issues
183
+
184
+ if not toml_ver:
185
+ return "unavailable", ["package not found locally"]
186
+
187
+ return "ok", []
188
+
189
+
190
+ def list_versions(packages: list[str] | None = None) -> dict[str, Any]:
191
+ """List versions for all ecosystem packages.
192
+
193
+ Parameters
194
+ ----------
195
+ packages : list[str] | None
196
+ List of package names to check. If None, checks all ecosystem packages.
197
+
198
+ Returns
199
+ -------
200
+ dict
201
+ Version information for each package.
202
+ """
203
+ if packages is None:
204
+ packages = get_all_packages()
205
+
206
+ result = {}
207
+ for pkg in packages:
208
+ if pkg not in ECOSYSTEM:
209
+ result[pkg] = {"status": "unknown", "issues": [f"'{pkg}' not in ecosystem"]}
210
+ continue
211
+
212
+ info: dict[str, Any] = {"local": {}, "git": {}, "remote": {}}
213
+ local_path = get_local_path(pkg)
214
+ pypi_name = ECOSYSTEM[pkg].get("pypi_name", pkg)
215
+
216
+ # Local sources
217
+ if local_path and local_path.exists():
218
+ info["local"]["pyproject_toml"] = get_version_from_toml(local_path)
219
+ info["local"]["installed"] = get_version_installed(pypi_name)
220
+
221
+ # Git sources
222
+ if local_path and local_path.exists():
223
+ info["git"]["latest_tag"] = get_git_latest_tag(local_path)
224
+ info["git"]["branch"] = get_git_branch(local_path)
225
+
226
+ # Remote sources
227
+ info["remote"]["pypi"] = get_pypi_version(pypi_name)
228
+
229
+ # Determine status
230
+ status, issues = _determine_status(info)
231
+ info["status"] = status
232
+ info["issues"] = issues
233
+
234
+ result[pkg] = info
235
+
236
+ return result
237
+
238
+
239
+ def check_versions(packages: list[str] | None = None) -> dict[str, Any]:
240
+ """Check version consistency and return detailed status.
241
+
242
+ Parameters
243
+ ----------
244
+ packages : list[str] | None
245
+ List of package names to check. If None, checks all ecosystem packages.
246
+
247
+ Returns
248
+ -------
249
+ dict
250
+ Detailed version check results with overall summary.
251
+ """
252
+ versions = list_versions(packages)
253
+
254
+ summary = {
255
+ "total": len(versions),
256
+ "ok": 0,
257
+ "mismatch": 0,
258
+ "unreleased": 0,
259
+ "outdated": 0,
260
+ "unavailable": 0,
261
+ "unknown": 0,
262
+ }
263
+
264
+ for _pkg, info in versions.items():
265
+ status = info.get("status", "unknown")
266
+ if status in summary:
267
+ summary[status] += 1
268
+
269
+ return {"packages": versions, "summary": summary}
270
+
271
+
272
+ # EOF
@@ -32,7 +32,7 @@ def main(
32
32
  CONFIG=stx.INJECTED, # Session config with ID, paths
33
33
  plt=stx.INJECTED, # Pre-configured matplotlib
34
34
  COLORS=stx.INJECTED, # Color palette
35
- rng=stx.INJECTED, # Seeded random generator
35
+ rngg=stx.INJECTED, # Seeded random generator
36
36
  logger=stx.INJECTED, # Session logger
37
37
  ):
38
38
  \"\"\"This docstring becomes --help description.\"\"\"
@@ -223,7 +223,7 @@ def main(
223
223
  CONFIG=stx.INJECTED, # Session config
224
224
  plt=stx.INJECTED, # matplotlib
225
225
  COLORS=stx.INJECTED, # Color palette
226
- rng=stx.INJECTED, # Random generator
226
+ rngg=stx.INJECTED, # Random generator
227
227
  logger=stx.INJECTED, # Logger
228
228
  ):
229
229
  \"\"\"Docstring becomes --help.\"\"\"
@@ -9,6 +9,7 @@ from .audio import register_audio_tools
9
9
  from .canvas import register_canvas_tools
10
10
  from .capture import register_capture_tools
11
11
  from .dataset import register_dataset_tools
12
+ from .dev import register_dev_tools
12
13
  from .diagram import register_diagram_tools
13
14
  from .introspect import register_introspect_tools
14
15
  from .plt import register_plt_tools
@@ -17,6 +18,7 @@ from .social import register_social_tools
17
18
  from .stats import register_stats_tools
18
19
  from .template import register_template_tools
19
20
  from .ui import register_ui_tools
21
+ from .verify import register_verify_tools
20
22
  from .writer import register_writer_tools
21
23
 
22
24
  __all__ = ["register_all_tools"]
@@ -28,6 +30,7 @@ def register_all_tools(mcp) -> None:
28
30
  register_canvas_tools(mcp)
29
31
  register_capture_tools(mcp)
30
32
  register_dataset_tools(mcp)
33
+ register_dev_tools(mcp)
31
34
  register_diagram_tools(mcp)
32
35
  register_introspect_tools(mcp)
33
36
  register_plt_tools(mcp)
@@ -36,6 +39,7 @@ def register_all_tools(mcp) -> None:
36
39
  register_stats_tools(mcp)
37
40
  register_template_tools(mcp)
38
41
  register_ui_tools(mcp)
42
+ register_verify_tools(mcp)
39
43
  register_writer_tools(mcp)
40
44
 
41
45
 
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_mcp_tools/dev.py
4
+
5
+ """MCP tool registration for developer utilities."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+
11
+
12
+ def _json(obj) -> str:
13
+ """Serialize object to JSON string."""
14
+ return json.dumps(obj, indent=2, default=str)
15
+
16
+
17
+ def register_dev_tools(mcp) -> None:
18
+ """Register developer tools with FastMCP server."""
19
+
20
+ @mcp.tool()
21
+ async def dev_list_versions(
22
+ packages: list[str] | None = None,
23
+ ) -> str:
24
+ """[dev] List versions across the scitex ecosystem.
25
+
26
+ Shows version information from multiple sources:
27
+ - pyproject.toml (local source)
28
+ - installed package (importlib.metadata)
29
+ - git tag (latest version tag)
30
+ - git branch (current branch)
31
+ - PyPI (remote published version)
32
+
33
+ Parameters
34
+ ----------
35
+ packages : list[str] | None
36
+ List of package names to check. If None, checks all ecosystem packages.
37
+ Available packages: scitex, scitex-cloud, scitex-writer, scitex-dataset,
38
+ figrecipe, crossref-local, openalex-local, socialia
39
+
40
+ Returns
41
+ -------
42
+ str
43
+ JSON with version information for each package.
44
+ """
45
+ from scitex._dev._mcp.handlers import list_versions_handler
46
+
47
+ result = await list_versions_handler(packages)
48
+ return _json(result)
49
+
50
+ @mcp.tool()
51
+ async def dev_check_versions(
52
+ packages: list[str] | None = None,
53
+ ) -> str:
54
+ """[dev] Check version consistency across the scitex ecosystem.
55
+
56
+ Checks for version mismatches between sources and returns a summary.
57
+
58
+ Status values:
59
+ - ok: All versions consistent
60
+ - mismatch: Local sources disagree
61
+ - unreleased: Local > PyPI (ready to release)
62
+ - outdated: Local < PyPI (should update)
63
+ - unavailable: Package not found locally
64
+
65
+ Parameters
66
+ ----------
67
+ packages : list[str] | None
68
+ List of package names to check. If None, checks all ecosystem packages.
69
+
70
+ Returns
71
+ -------
72
+ str
73
+ JSON with detailed version check results and summary.
74
+ """
75
+ from scitex._dev._mcp.handlers import check_versions_handler
76
+
77
+ result = await check_versions_handler(packages)
78
+ return _json(result)
79
+
80
+ @mcp.tool()
81
+ async def dev_check_hosts(
82
+ packages: list[str] | None = None,
83
+ hosts: list[str] | None = None,
84
+ ) -> str:
85
+ """[dev] Check package versions on SSH hosts.
86
+
87
+ Connects to configured SSH hosts and checks installed package versions.
88
+ Hosts are configured in ~/.scitex/dev_config.yaml or via SCITEX_DEV_HOSTS.
89
+
90
+ Parameters
91
+ ----------
92
+ packages : list[str] | None
93
+ List of package names to check. If None, checks all ecosystem packages.
94
+ hosts : list[str] | None
95
+ List of host names to check. If None, checks all enabled hosts.
96
+
97
+ Returns
98
+ -------
99
+ str
100
+ JSON with host -> package -> version mapping.
101
+ """
102
+ from scitex._dev._mcp.handlers import check_hosts_handler
103
+
104
+ result = await check_hosts_handler(packages, hosts)
105
+ return _json(result)
106
+
107
+ @mcp.tool()
108
+ async def dev_check_remotes(
109
+ packages: list[str] | None = None,
110
+ remotes: list[str] | None = None,
111
+ ) -> str:
112
+ """[dev] Check package versions on GitHub remotes.
113
+
114
+ Fetches tags and releases from configured GitHub organizations.
115
+ Remotes are configured in ~/.scitex/dev_config.yaml.
116
+
117
+ Parameters
118
+ ----------
119
+ packages : list[str] | None
120
+ List of package names to check. If None, checks all ecosystem packages.
121
+ remotes : list[str] | None
122
+ List of remote names to check. If None, checks all enabled remotes.
123
+
124
+ Returns
125
+ -------
126
+ str
127
+ JSON with remote -> package -> version mapping.
128
+ """
129
+ from scitex._dev._mcp.handlers import check_remotes_handler
130
+
131
+ result = await check_remotes_handler(packages, remotes)
132
+ return _json(result)
133
+
134
+ @mcp.tool()
135
+ async def dev_get_config() -> str:
136
+ """[dev] Get current developer configuration.
137
+
138
+ Returns the configuration from ~/.scitex/dev_config.yaml including:
139
+ - Packages to track
140
+ - SSH hosts
141
+ - GitHub remotes
142
+ - Branches to track
143
+
144
+ Returns
145
+ -------
146
+ str
147
+ JSON with current configuration.
148
+ """
149
+ from scitex._dev._mcp.handlers import get_config_handler
150
+
151
+ result = await get_config_handler()
152
+ return _json(result)
153
+
154
+ @mcp.tool()
155
+ async def dev_full_versions(
156
+ packages: list[str] | None = None,
157
+ include_hosts: bool = False,
158
+ include_remotes: bool = True,
159
+ ) -> str:
160
+ """[dev] Get comprehensive version data from all sources.
161
+
162
+ Combines local, host, and GitHub remote version information.
163
+
164
+ Parameters
165
+ ----------
166
+ packages : list[str] | None
167
+ List of package names to check. If None, checks all ecosystem packages.
168
+ include_hosts : bool
169
+ Include SSH host version checks (slower, requires SSH access).
170
+ include_remotes : bool
171
+ Include GitHub remote version checks.
172
+
173
+ Returns
174
+ -------
175
+ str
176
+ JSON with combined version data.
177
+ """
178
+ from scitex._dev._mcp.handlers import get_full_versions_handler
179
+
180
+ result = await get_full_versions_handler(
181
+ packages, include_hosts, include_remotes
182
+ )
183
+ return _json(result)
184
+
185
+
186
+ # EOF