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,122 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_dev/__init__.py
4
+
5
+ """
6
+ SciTeX Developer Utilities (Internal Module).
7
+
8
+ This module provides internal developer tools for managing the scitex ecosystem.
9
+
10
+ Functions
11
+ ---------
12
+ list_versions : List versions across ecosystem packages
13
+ check_versions : Check version consistency
14
+ load_config : Load developer configuration
15
+ check_all_hosts : Check versions on SSH hosts
16
+ check_all_remotes : Check versions on GitHub remotes
17
+ run_dashboard : Run the Flask version dashboard
18
+
19
+ Examples
20
+ --------
21
+ >>> from scitex._dev import list_versions
22
+ >>> versions = list_versions()
23
+ >>> versions["scitex"]["local"]["pyproject_toml"]
24
+ '2.17.1'
25
+
26
+ >>> from scitex._dev import check_versions
27
+ >>> result = check_versions(["scitex", "figrecipe"])
28
+ >>> result["summary"]["ok"]
29
+ 2
30
+
31
+ >>> from scitex._dev import load_config
32
+ >>> config = load_config()
33
+ >>> len(config.packages)
34
+ 8
35
+ """
36
+
37
+ from ._config import (
38
+ DevConfig,
39
+ GitHubRemote,
40
+ HostConfig,
41
+ PackageConfig,
42
+ PyPIAccount,
43
+ create_default_config,
44
+ get_config_path,
45
+ get_enabled_hosts,
46
+ get_enabled_remotes,
47
+ load_config,
48
+ )
49
+ from ._ecosystem import ECOSYSTEM, get_all_packages, get_local_path
50
+ from ._github import (
51
+ check_all_remotes,
52
+ compare_with_local,
53
+ get_github_latest_tag,
54
+ get_github_release,
55
+ get_github_tags,
56
+ )
57
+ from ._ssh import (
58
+ check_all_hosts,
59
+ get_remote_version,
60
+ get_remote_versions,
61
+ test_host_connection,
62
+ )
63
+ from ._versions import check_versions, list_versions
64
+
65
+ __all__ = [
66
+ # Versions
67
+ "list_versions",
68
+ "check_versions",
69
+ # Ecosystem
70
+ "ECOSYSTEM",
71
+ "get_all_packages",
72
+ "get_local_path",
73
+ # Config
74
+ "load_config",
75
+ "get_config_path",
76
+ "create_default_config",
77
+ "get_enabled_hosts",
78
+ "get_enabled_remotes",
79
+ "DevConfig",
80
+ "HostConfig",
81
+ "GitHubRemote",
82
+ "PackageConfig",
83
+ "PyPIAccount",
84
+ # SSH
85
+ "check_all_hosts",
86
+ "get_remote_version",
87
+ "get_remote_versions",
88
+ "test_host_connection",
89
+ # GitHub
90
+ "check_all_remotes",
91
+ "compare_with_local",
92
+ "get_github_tags",
93
+ "get_github_latest_tag",
94
+ "get_github_release",
95
+ ]
96
+
97
+
98
+ def run_dashboard(
99
+ host: str = "127.0.0.1",
100
+ port: int = 5000,
101
+ debug: bool = False,
102
+ open_browser: bool = True,
103
+ ) -> None:
104
+ """Run the Flask version dashboard.
105
+
106
+ Parameters
107
+ ----------
108
+ host : str
109
+ Host to bind to.
110
+ port : int
111
+ Port to listen on.
112
+ debug : bool
113
+ Enable debug mode.
114
+ open_browser : bool
115
+ Open browser automatically.
116
+ """
117
+ from ._dashboard import run_dashboard as _run
118
+
119
+ _run(host=host, port=port, debug=debug, open_browser=open_browser)
120
+
121
+
122
+ # EOF
scitex/_dev/_config.py ADDED
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_dev/_config.py
4
+
5
+ """Configuration management for scitex developer utilities."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+
15
+ @dataclass
16
+ class HostConfig:
17
+ """SSH host configuration."""
18
+
19
+ name: str
20
+ hostname: str
21
+ user: str
22
+ role: str = "dev" # dev, staging, prod
23
+ enabled: bool = True
24
+ ssh_key: str | None = None
25
+ port: int = 22
26
+
27
+
28
+ @dataclass
29
+ class GitHubRemote:
30
+ """GitHub remote configuration."""
31
+
32
+ name: str
33
+ org: str
34
+ enabled: bool = True
35
+
36
+
37
+ @dataclass
38
+ class PyPIAccount:
39
+ """PyPI account configuration."""
40
+
41
+ name: str
42
+ enabled: bool = True
43
+
44
+
45
+ @dataclass
46
+ class PackageConfig:
47
+ """Package configuration."""
48
+
49
+ name: str
50
+ local_path: str
51
+ pypi_name: str
52
+ github_repo: str | None = None
53
+ import_name: str | None = None
54
+
55
+
56
+ @dataclass
57
+ class DevConfig:
58
+ """Full developer configuration."""
59
+
60
+ packages: list[PackageConfig] = field(default_factory=list)
61
+ hosts: list[HostConfig] = field(default_factory=list)
62
+ github_remotes: list[GitHubRemote] = field(default_factory=list)
63
+ pypi_accounts: list[PyPIAccount] = field(default_factory=list)
64
+ branches: list[str] = field(default_factory=lambda: ["main", "develop"])
65
+
66
+
67
+ def _get_default_config_path() -> Path:
68
+ """Get default config file path."""
69
+ return Path.home() / ".scitex" / "dev_config.yaml"
70
+
71
+
72
+ def _load_yaml(path: Path) -> dict[str, Any]:
73
+ """Load YAML file."""
74
+ if not path.exists():
75
+ return {}
76
+
77
+ try:
78
+ import yaml
79
+
80
+ with open(path) as f:
81
+ return yaml.safe_load(f) or {}
82
+ except ImportError:
83
+ # Fallback: basic YAML parsing for simple configs
84
+ content = path.read_text()
85
+ # Very basic parsing - handles simple key: value pairs
86
+ result: dict[str, Any] = {}
87
+ current_key = None
88
+ current_list: list[Any] = []
89
+
90
+ for line in content.split("\n"):
91
+ line = line.rstrip()
92
+ if not line or line.startswith("#"):
93
+ continue
94
+ if line.startswith(" - "):
95
+ # List item
96
+ current_list.append(line[4:].strip())
97
+ elif line.startswith(" "):
98
+ # Nested dict item - skip for basic parsing
99
+ continue
100
+ elif ":" in line:
101
+ if current_key and current_list:
102
+ result[current_key] = current_list
103
+ current_list = []
104
+ key, val = line.split(":", 1)
105
+ current_key = key.strip()
106
+ val = val.strip()
107
+ if val:
108
+ result[current_key] = val
109
+ if current_key and current_list:
110
+ result[current_key] = current_list
111
+ return result
112
+ except Exception:
113
+ return {}
114
+
115
+
116
+ def _parse_host_config(data: dict[str, Any]) -> HostConfig:
117
+ """Parse host config from dict."""
118
+ return HostConfig(
119
+ name=data.get("name", "unknown"),
120
+ hostname=data.get("hostname", "localhost"),
121
+ user=data.get("user", os.getenv("USER", "user")),
122
+ role=data.get("role", "dev"),
123
+ enabled=data.get("enabled", True),
124
+ ssh_key=data.get("ssh_key"),
125
+ port=data.get("port", 22),
126
+ )
127
+
128
+
129
+ def _parse_github_remote(data: dict[str, Any]) -> GitHubRemote:
130
+ """Parse GitHub remote from dict."""
131
+ return GitHubRemote(
132
+ name=data.get("name", "unknown"),
133
+ org=data.get("org", ""),
134
+ enabled=data.get("enabled", True),
135
+ )
136
+
137
+
138
+ def _parse_pypi_account(data: dict[str, Any]) -> PyPIAccount:
139
+ """Parse PyPI account from dict."""
140
+ return PyPIAccount(
141
+ name=data.get("name", ""),
142
+ enabled=data.get("enabled", True),
143
+ )
144
+
145
+
146
+ def _parse_package_config(data: dict[str, Any]) -> PackageConfig:
147
+ """Parse package config from dict."""
148
+ return PackageConfig(
149
+ name=data.get("name", "unknown"),
150
+ local_path=data.get("local_path", ""),
151
+ pypi_name=data.get("pypi_name", data.get("name", "")),
152
+ github_repo=data.get("github_repo"),
153
+ import_name=data.get("import_name"),
154
+ )
155
+
156
+
157
+ def load_config(config_path: str | Path | None = None) -> DevConfig:
158
+ """Load config from YAML with environment variable overrides.
159
+
160
+ Parameters
161
+ ----------
162
+ config_path : str | Path | None
163
+ Path to config file. If None, uses SCITEX_DEV_CONFIG env var
164
+ or ~/.scitex/dev_config.yaml
165
+
166
+ Returns
167
+ -------
168
+ DevConfig
169
+ Loaded configuration.
170
+ """
171
+ # Determine config path
172
+ if config_path is None:
173
+ config_path = os.getenv("SCITEX_DEV_CONFIG")
174
+ if config_path is None:
175
+ config_path = _get_default_config_path()
176
+ else:
177
+ config_path = Path(config_path).expanduser()
178
+
179
+ # Load YAML
180
+ data = _load_yaml(config_path)
181
+
182
+ # Parse packages
183
+ packages = []
184
+ if "packages" in data and isinstance(data["packages"], list):
185
+ for pkg_data in data["packages"]:
186
+ if isinstance(pkg_data, dict):
187
+ packages.append(_parse_package_config(pkg_data))
188
+
189
+ # If no packages in config, use ecosystem defaults
190
+ if not packages:
191
+ from ._ecosystem import ECOSYSTEM
192
+
193
+ for name, info in ECOSYSTEM.items():
194
+ packages.append(
195
+ PackageConfig(
196
+ name=name,
197
+ local_path=info.get("local_path", ""),
198
+ pypi_name=info.get("pypi_name", name),
199
+ github_repo=info.get("github_repo"),
200
+ import_name=info.get("import_name"),
201
+ )
202
+ )
203
+
204
+ # Parse hosts
205
+ hosts = []
206
+ if "hosts" in data and isinstance(data["hosts"], list):
207
+ for host_data in data["hosts"]:
208
+ if isinstance(host_data, dict):
209
+ hosts.append(_parse_host_config(host_data))
210
+
211
+ # Override from env
212
+ env_hosts = os.getenv("SCITEX_DEV_HOSTS", "").strip()
213
+ if env_hosts:
214
+ enabled_names = set(env_hosts.split(","))
215
+ for host in hosts:
216
+ host.enabled = host.name in enabled_names
217
+
218
+ # Parse GitHub remotes
219
+ github_remotes = []
220
+ if "github_remotes" in data and isinstance(data["github_remotes"], list):
221
+ for remote_data in data["github_remotes"]:
222
+ if isinstance(remote_data, dict):
223
+ github_remotes.append(_parse_github_remote(remote_data))
224
+
225
+ # Default GitHub remote from ecosystem
226
+ if not github_remotes:
227
+ github_remotes.append(GitHubRemote(name="ywatanabe1989", org="ywatanabe1989"))
228
+
229
+ # Override from env
230
+ env_remotes = os.getenv("SCITEX_DEV_GITHUB_REMOTES", "").strip()
231
+ if env_remotes:
232
+ enabled_names = set(env_remotes.split(","))
233
+ for remote in github_remotes:
234
+ remote.enabled = remote.name in enabled_names
235
+
236
+ # Parse PyPI accounts
237
+ pypi_accounts = []
238
+ if "pypi_accounts" in data and isinstance(data["pypi_accounts"], list):
239
+ for acct_data in data["pypi_accounts"]:
240
+ if isinstance(acct_data, dict):
241
+ pypi_accounts.append(_parse_pypi_account(acct_data))
242
+
243
+ if not pypi_accounts:
244
+ pypi_accounts.append(PyPIAccount(name="ywatanabe1989"))
245
+
246
+ # Parse branches
247
+ branches = data.get("branches", ["main", "develop"])
248
+ if not isinstance(branches, list):
249
+ branches = ["main", "develop"]
250
+
251
+ return DevConfig(
252
+ packages=packages,
253
+ hosts=hosts,
254
+ github_remotes=github_remotes,
255
+ pypi_accounts=pypi_accounts,
256
+ branches=branches,
257
+ )
258
+
259
+
260
+ def get_enabled_hosts(config: DevConfig | None = None) -> list[HostConfig]:
261
+ """Get list of enabled hosts.
262
+
263
+ Parameters
264
+ ----------
265
+ config : DevConfig | None
266
+ Configuration to use. If None, loads default config.
267
+
268
+ Returns
269
+ -------
270
+ list[HostConfig]
271
+ List of enabled hosts.
272
+ """
273
+ if config is None:
274
+ config = load_config()
275
+ return [h for h in config.hosts if h.enabled]
276
+
277
+
278
+ def get_enabled_remotes(config: DevConfig | None = None) -> list[GitHubRemote]:
279
+ """Get list of enabled GitHub remotes.
280
+
281
+ Parameters
282
+ ----------
283
+ config : DevConfig | None
284
+ Configuration to use. If None, loads default config.
285
+
286
+ Returns
287
+ -------
288
+ list[GitHubRemote]
289
+ List of enabled remotes.
290
+ """
291
+ if config is None:
292
+ config = load_config()
293
+ return [r for r in config.github_remotes if r.enabled]
294
+
295
+
296
+ def get_config_path() -> Path:
297
+ """Get the config file path (may not exist)."""
298
+ path = os.getenv("SCITEX_DEV_CONFIG")
299
+ if path:
300
+ return Path(path).expanduser()
301
+ return _get_default_config_path()
302
+
303
+
304
+ def create_default_config() -> Path:
305
+ """Create default config file if it doesn't exist.
306
+
307
+ Returns
308
+ -------
309
+ Path
310
+ Path to the config file.
311
+ """
312
+ config_path = _get_default_config_path()
313
+ config_path.parent.mkdir(parents=True, exist_ok=True)
314
+
315
+ if config_path.exists():
316
+ return config_path
317
+
318
+ default_config = """\
319
+ # SciTeX Developer Configuration
320
+ # Timestamp: 2026-02-02
321
+
322
+ # Ecosystem packages to track
323
+ packages:
324
+ - name: scitex
325
+ local_path: ~/proj/scitex-python
326
+ pypi_name: scitex
327
+ github_repo: ywatanabe1989/scitex-python
328
+ import_name: scitex
329
+ - name: figrecipe
330
+ local_path: ~/proj/figrecipe
331
+ pypi_name: figrecipe
332
+ github_repo: ywatanabe1989/figrecipe
333
+ import_name: figrecipe
334
+ - name: scitex-cloud
335
+ local_path: ~/proj/scitex-cloud
336
+ pypi_name: scitex-cloud
337
+ github_repo: ywatanabe1989/scitex-cloud
338
+ import_name: scitex_cloud
339
+ - name: scitex-writer
340
+ local_path: ~/proj/scitex-writer
341
+ pypi_name: scitex-writer
342
+ github_repo: ywatanabe1989/scitex-writer
343
+ import_name: scitex_writer
344
+ - name: crossref-local
345
+ local_path: ~/proj/crossref-local
346
+ pypi_name: crossref-local
347
+ github_repo: ywatanabe1989/crossref-local
348
+ import_name: crossref_local
349
+
350
+ # Hosts to check via SSH
351
+ hosts:
352
+ - name: ywata-note-win
353
+ hostname: localhost
354
+ user: ywatanabe
355
+ role: dev
356
+ enabled: true
357
+ - name: nas
358
+ hostname: nas.local
359
+ user: ywatanabe
360
+ role: staging
361
+ enabled: true
362
+ - name: scitex-cloud
363
+ hostname: scitex.ai
364
+ user: deploy
365
+ role: prod
366
+ enabled: false
367
+
368
+ # GitHub remotes to check
369
+ github_remotes:
370
+ - name: ywatanabe1989
371
+ org: ywatanabe1989
372
+ enabled: true
373
+ - name: scitex-ai
374
+ org: scitex-ai
375
+ enabled: false
376
+
377
+ # PyPI accounts
378
+ pypi_accounts:
379
+ - name: ywatanabe1989
380
+ enabled: true
381
+
382
+ # Branches to track
383
+ branches:
384
+ - main
385
+ - develop
386
+ """
387
+ config_path.write_text(default_config)
388
+ return config_path
389
+
390
+
391
+ # EOF
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_dev/_dashboard/__init__.py
4
+
5
+ """Flask dashboard for scitex version management."""
6
+
7
+ from ._app import create_app, run_dashboard
8
+
9
+ __all__ = ["create_app", "run_dashboard"]
10
+
11
+ # EOF
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-02-02
3
+ # File: scitex/_dev/_dashboard/_app.py
4
+
5
+ """Flask application factory for the dashboard."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from flask import Flask
13
+
14
+
15
+ def create_app() -> Flask:
16
+ """Create and configure the Flask application.
17
+
18
+ Returns
19
+ -------
20
+ Flask
21
+ Configured Flask application.
22
+ """
23
+ try:
24
+ from flask import Flask
25
+ except ImportError as e:
26
+ raise ImportError(
27
+ "Flask is required for the dashboard. Install with: pip install flask"
28
+ ) from e
29
+
30
+ from pathlib import Path
31
+
32
+ static_folder = Path(__file__).parent / "static"
33
+ app = Flask(__name__, static_folder=str(static_folder), static_url_path="/static")
34
+
35
+ # Disable JSON key sorting to preserve insertion order (Flask 2.2+)
36
+ app.json.sort_keys = False
37
+
38
+ # Register routes
39
+ from ._routes import register_routes
40
+
41
+ register_routes(app)
42
+
43
+ return app
44
+
45
+
46
+ def run_dashboard(
47
+ host: str = "127.0.0.1",
48
+ port: int = 5000,
49
+ debug: bool = False,
50
+ open_browser: bool = True,
51
+ ) -> None:
52
+ """Run the Flask dashboard server.
53
+
54
+ Parameters
55
+ ----------
56
+ host : str
57
+ Host to bind to. Default "127.0.0.1".
58
+ port : int
59
+ Port to listen on. Default 5000.
60
+ debug : bool
61
+ Enable Flask debug mode.
62
+ open_browser : bool
63
+ Open browser automatically.
64
+ """
65
+ app = create_app()
66
+
67
+ url = f"http://{host}:{port}"
68
+ print(f"Starting SciTeX Version Dashboard at {url}")
69
+ print("Press Ctrl+C to stop.")
70
+
71
+ if open_browser:
72
+ import threading
73
+ import webbrowser
74
+
75
+ def open_url():
76
+ import time
77
+
78
+ time.sleep(1) # Wait for server to start
79
+ webbrowser.open(url)
80
+
81
+ threading.Thread(target=open_url, daemon=True).start()
82
+
83
+ try:
84
+ app.run(host=host, port=port, debug=debug, threaded=True)
85
+ except KeyboardInterrupt:
86
+ print("\nDashboard stopped.")
87
+
88
+
89
+ # EOF