scitex 2.16.1__py3-none-any.whl → 2.17.0__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 (48) hide show
  1. scitex/_mcp_resources/_cheatsheet.py +1 -1
  2. scitex/_mcp_resources/_modules.py +1 -1
  3. scitex/_mcp_tools/__init__.py +2 -0
  4. scitex/_mcp_tools/verify.py +256 -0
  5. scitex/cli/main.py +2 -0
  6. scitex/cli/verify.py +476 -0
  7. scitex/dev/plt/__init__.py +1 -1
  8. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  9. scitex/dev/plt/mpl/get_signatures.py +1 -1
  10. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  11. scitex/io/_load.py +8 -1
  12. scitex/io/_save.py +12 -0
  13. scitex/session/README.md +2 -2
  14. scitex/session/__init__.py +1 -0
  15. scitex/session/_decorator.py +57 -33
  16. scitex/session/_lifecycle/__init__.py +23 -0
  17. scitex/session/_lifecycle/_close.py +225 -0
  18. scitex/session/_lifecycle/_config.py +112 -0
  19. scitex/session/_lifecycle/_matplotlib.py +83 -0
  20. scitex/session/_lifecycle/_start.py +246 -0
  21. scitex/session/_lifecycle/_utils.py +186 -0
  22. scitex/session/_manager.py +40 -3
  23. scitex/session/template.py +1 -1
  24. scitex/template/_templates/plt.py +1 -1
  25. scitex/template/_templates/session.py +1 -1
  26. scitex/verify/README.md +312 -0
  27. scitex/verify/__init__.py +212 -0
  28. scitex/verify/_chain.py +369 -0
  29. scitex/verify/_db.py +600 -0
  30. scitex/verify/_hash.py +187 -0
  31. scitex/verify/_integration.py +127 -0
  32. scitex/verify/_rerun.py +253 -0
  33. scitex/verify/_tracker.py +330 -0
  34. scitex/verify/_visualize.py +48 -0
  35. scitex/verify/_viz/__init__.py +56 -0
  36. scitex/verify/_viz/_colors.py +84 -0
  37. scitex/verify/_viz/_format.py +302 -0
  38. scitex/verify/_viz/_json.py +192 -0
  39. scitex/verify/_viz/_mermaid.py +440 -0
  40. scitex/verify/_viz/_plotly.py +193 -0
  41. scitex/verify/_viz/_templates.py +246 -0
  42. scitex/verify/_viz/_utils.py +56 -0
  43. {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/METADATA +1 -1
  44. {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/RECORD +47 -23
  45. scitex/session/_lifecycle.py +0 -827
  46. {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/WHEEL +0 -0
  47. {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/entry_points.txt +0 -0
  48. {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-02-01 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/session/_lifecycle/_start.py
4
+ """Session start function."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import inspect
9
+ import logging
10
+ import os as _os
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional, Tuple, Union
13
+
14
+ from scitex.dict import DotDict
15
+ from scitex.logging import getLogger
16
+ from scitex.repro import RandomStateManager
17
+ from scitex.str._clean_path import clean_path
18
+
19
+ from .._manager import get_global_session_manager
20
+ from ._config import setup_configs
21
+ from ._matplotlib import setup_matplotlib
22
+ from ._utils import (
23
+ clear_python_log_dir,
24
+ get_debug_mode,
25
+ initialize_env,
26
+ print_header,
27
+ simplify_relative_path,
28
+ )
29
+
30
+ logger = getLogger(__name__)
31
+
32
+ # For development code flow analysis
33
+ try:
34
+ from scitex.dev._analyze_code_flow import analyze_code_flow
35
+ except ImportError:
36
+
37
+ def analyze_code_flow(file):
38
+ return "Code flow analysis not available"
39
+
40
+
41
+ def start(
42
+ sys=None,
43
+ plt=None,
44
+ file: Optional[str] = None,
45
+ sdir: Optional[Union[str, Path]] = None,
46
+ sdir_suffix: Optional[str] = None,
47
+ args: Optional[Any] = None,
48
+ os: Optional[Any] = None,
49
+ random: Optional[Any] = None,
50
+ np: Optional[Any] = None,
51
+ torch: Optional[Any] = None,
52
+ seed: int = 42,
53
+ agg: bool = False,
54
+ fig_size_mm: Tuple[int, int] = (160, 100),
55
+ fig_scale: float = 1.0,
56
+ dpi_display: int = 100,
57
+ dpi_save: int = 300,
58
+ fontsize="small",
59
+ autolayout=True,
60
+ show_execution_flow=False,
61
+ hide_top_right_spines: bool = True,
62
+ alpha: float = 0.9,
63
+ line_width: float = 1.0,
64
+ clear_logs: bool = False,
65
+ verbose: bool = True,
66
+ ) -> Tuple[DotDict, Any, Any, Any, Optional[Dict[str, Any]], Any]:
67
+ """Initialize experiment session with reproducibility settings.
68
+
69
+ Parameters
70
+ ----------
71
+ sys : module, optional
72
+ Python sys module for I/O redirection
73
+ plt : module, optional
74
+ Matplotlib pyplot module for plotting configuration
75
+ file : str, optional
76
+ Script file path. If None, automatically detected
77
+ sdir : Union[str, Path], optional
78
+ Save directory path
79
+ sdir_suffix : str, optional
80
+ Suffix to append to save directory
81
+ args : object, optional
82
+ Command line arguments or configuration object
83
+ seed : int, default=42
84
+ Random seed for reproducibility
85
+ agg : bool, default=False
86
+ Whether to use matplotlib Agg backend
87
+ verbose : bool, default=True
88
+ Whether to print detailed information
89
+
90
+ Returns
91
+ -------
92
+ tuple
93
+ (CONFIGS, stdout, stderr, plt, COLORS, rng)
94
+ """
95
+ IS_DEBUG = get_debug_mode()
96
+ ID, PID = initialize_env(IS_DEBUG)
97
+
98
+ # Convert Path objects to strings for internal processing
99
+ if sdir is not None and isinstance(sdir, Path):
100
+ sdir = str(sdir)
101
+
102
+ # Defines SDIR
103
+ if sdir is None:
104
+ # Define __file__
105
+ if file:
106
+ caller_file = file
107
+ else:
108
+ caller_file = inspect.stack()[1].filename
109
+ if "ipython" in caller_file:
110
+ caller_file = f"/tmp/{_os.getenv('USER')}.py"
111
+
112
+ # Convert to absolute path if relative and resolve symlinks
113
+ if not _os.path.isabs(caller_file):
114
+ caller_file = _os.path.realpath(_os.path.abspath(caller_file))
115
+ else:
116
+ caller_file = _os.path.realpath(caller_file)
117
+
118
+ # Define sdir
119
+ sdir = clean_path(_os.path.splitext(caller_file)[0] + f"_out/RUNNING/{ID}/")
120
+
121
+ # Optional
122
+ if sdir_suffix:
123
+ sdir = sdir[:-1] + f"-{sdir_suffix}/"
124
+ else:
125
+ caller_file = file
126
+
127
+ if clear_logs and caller_file:
128
+ clear_python_log_dir(sdir + caller_file + "/")
129
+ _os.makedirs(sdir, exist_ok=True)
130
+ relative_sdir = simplify_relative_path(sdir)
131
+
132
+ # Setup configs - use caller_file (computed) instead of file (parameter)
133
+ CONFIGS = setup_configs(
134
+ IS_DEBUG, ID, PID, caller_file, sdir, relative_sdir, verbose
135
+ )
136
+
137
+ # Logging
138
+ if sys is not None:
139
+ from scitex.io._flush import flush
140
+ from scitex.logging._Tee import tee
141
+
142
+ flush(sys)
143
+ sys.stdout, sys.stderr = tee(sys, sdir=sdir, verbose=verbose)
144
+ CONFIGS["_sys"] = sys
145
+
146
+ # Redirect logging handlers to use the tee-wrapped streams
147
+ _redirect_logging_handlers(sys)
148
+
149
+ # Initialize RandomStateManager
150
+ rng = RandomStateManager(seed=seed, verbose=verbose)
151
+ if verbose:
152
+ logger.info(f"Initialized RandomStateManager with seed {seed}")
153
+
154
+ # Matplotlib configurations
155
+ plt, COLORS = setup_matplotlib(
156
+ plt,
157
+ agg,
158
+ fig_size_mm=fig_size_mm,
159
+ fig_scale=fig_scale,
160
+ dpi_display=dpi_display,
161
+ dpi_save=dpi_save,
162
+ hide_top_right_spines=hide_top_right_spines,
163
+ alpha=alpha,
164
+ line_width=line_width,
165
+ fontsize=fontsize,
166
+ autolayout=autolayout,
167
+ verbose=verbose,
168
+ )
169
+
170
+ # Adds argument-parsed variables
171
+ if args is not None:
172
+ CONFIGS["ARGS"] = vars(args) if hasattr(args, "__dict__") else args
173
+
174
+ CONFIGS = DotDict(CONFIGS)
175
+
176
+ # Register session
177
+ session_manager = get_global_session_manager()
178
+ session_manager.create_session(ID, CONFIGS)
179
+
180
+ print_header(ID, PID, caller_file, args, CONFIGS, verbose)
181
+
182
+ if show_execution_flow:
183
+ from scitex.str._printc import printc as _printc
184
+
185
+ structure = analyze_code_flow(caller_file)
186
+ _printc(structure)
187
+
188
+ # Start verification tracking
189
+ _start_verification(CONFIGS)
190
+
191
+ # Return appropriate values based on whether sys was provided
192
+ if sys is not None:
193
+ return CONFIGS, sys.stdout, sys.stderr, plt, COLORS, rng
194
+ else:
195
+ return CONFIGS, None, None, plt, COLORS, rng
196
+
197
+
198
+ def _redirect_logging_handlers(sys) -> None:
199
+ """Redirect logging handlers to use tee-wrapped streams."""
200
+ # Update all existing StreamHandler instances
201
+ for logger_name in list(logging.Logger.manager.loggerDict.keys()):
202
+ try:
203
+ lgr = logging.getLogger(logger_name)
204
+ for handler in lgr.handlers:
205
+ if isinstance(handler, logging.StreamHandler):
206
+ if not hasattr(handler, "stream"):
207
+ continue
208
+ if handler.stream in (sys.__stderr__, sys.__stdout__):
209
+ handler.stream = (
210
+ sys.stderr
211
+ if handler.stream == sys.__stderr__
212
+ else sys.stdout
213
+ )
214
+ except Exception:
215
+ pass
216
+
217
+ # Also update the root logger handlers
218
+ try:
219
+ root_logger = logging.getLogger()
220
+ for handler in root_logger.handlers:
221
+ if isinstance(handler, logging.StreamHandler):
222
+ if not hasattr(handler, "stream"):
223
+ continue
224
+ if handler.stream in (sys.__stderr__, sys.__stdout__):
225
+ handler.stream = (
226
+ sys.stderr if handler.stream == sys.__stderr__ else sys.stdout
227
+ )
228
+ except Exception:
229
+ pass
230
+
231
+
232
+ def _start_verification(CONFIG) -> None:
233
+ """Start verification tracking for this session."""
234
+ try:
235
+ from scitex.verify import on_session_start
236
+
237
+ session_id = CONFIG.get("ID", "unknown")
238
+ file_path = CONFIG.get("FILE")
239
+ if file_path is not None:
240
+ file_path = str(file_path)
241
+ on_session_start(session_id=session_id, script_path=file_path)
242
+ except Exception:
243
+ pass
244
+
245
+
246
+ # EOF
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-02-01 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/session/_lifecycle/_utils.py
4
+ """Utility functions for session lifecycle."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import os as _os
9
+ import re
10
+ from datetime import datetime
11
+ from time import sleep
12
+ from typing import Any, Dict, Tuple
13
+
14
+ from scitex.logging import getLogger
15
+ from scitex.repro._gen_ID import gen_ID
16
+ from scitex.str._printc import printc as _printc
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ def get_scitex_version() -> str:
22
+ """Gets scitex version."""
23
+ try:
24
+ import scitex
25
+
26
+ return scitex.__version__
27
+ except Exception as e:
28
+ print(e)
29
+ return "(not found)"
30
+
31
+
32
+ def get_debug_mode() -> bool:
33
+ """Get debug mode from configuration."""
34
+ try:
35
+ from scitex.io._load import load
36
+
37
+ IS_DEBUG_PATH = "./config/IS_DEBUG.yaml"
38
+ if _os.path.exists(IS_DEBUG_PATH):
39
+ IS_DEBUG = load(IS_DEBUG_PATH).get("IS_DEBUG", False)
40
+ if IS_DEBUG == "true":
41
+ IS_DEBUG = True
42
+ else:
43
+ IS_DEBUG = False
44
+
45
+ except Exception as e:
46
+ print(e)
47
+ IS_DEBUG = False
48
+ return IS_DEBUG
49
+
50
+
51
+ def initialize_env(IS_DEBUG: bool) -> Tuple[str, int]:
52
+ """Initialize environment with ID and PID.
53
+
54
+ Parameters
55
+ ----------
56
+ IS_DEBUG : bool
57
+ Debug mode flag
58
+
59
+ Returns
60
+ -------
61
+ tuple
62
+ (ID, PID) - Unique identifier and Process ID
63
+ """
64
+ ID = gen_ID(N=4) if not IS_DEBUG else "DEBUG_" + gen_ID(N=4)
65
+ PID = _os.getpid()
66
+ return ID, PID
67
+
68
+
69
+ def simplify_relative_path(sdir: str) -> str:
70
+ """Simplify the relative path by removing specific patterns.
71
+
72
+ Parameters
73
+ ----------
74
+ sdir : str
75
+ The directory path to simplify
76
+
77
+ Returns
78
+ -------
79
+ str
80
+ Simplified relative path
81
+ """
82
+ base_path = _os.getcwd()
83
+ relative_sdir = _os.path.relpath(sdir, base_path) if base_path else sdir
84
+ simplified_path = relative_sdir.replace("scripts/", "./scripts/").replace(
85
+ "RUNNING/", ""
86
+ )
87
+ # Remove date-time pattern and random string
88
+ simplified_path = re.sub(
89
+ r"\d{4}Y-\d{2}M-\d{2}D-\d{2}h\d{2}m\d{2}s_\w+/?$", "", simplified_path
90
+ )
91
+ return simplified_path
92
+
93
+
94
+ def clear_python_log_dir(log_dir: str) -> None:
95
+ """Clear Python log directory."""
96
+ try:
97
+ if _os.path.exists(log_dir):
98
+ _os.system(f"rm -rf {log_dir}")
99
+ except Exception as e:
100
+ print(f"Failed to clear directory {log_dir}: {e}")
101
+
102
+
103
+ def print_header(
104
+ ID: str,
105
+ PID: int,
106
+ file: str,
107
+ args: Any,
108
+ configs: Dict[str, Any],
109
+ verbose: bool = True,
110
+ ) -> None:
111
+ """Prints formatted header with scitex version, ID, and PID information."""
112
+ if args is not None and hasattr(args, "_get_kwargs"):
113
+ args_str = "Arguments:"
114
+ for arg, value in args._get_kwargs():
115
+ args_str += f"\n {arg}: {value}"
116
+ else:
117
+ args_str = "Arguments: None"
118
+
119
+ _printc(
120
+ (f"SciTeX v{get_scitex_version()} | {ID} (PID: {PID})\n\n{file}\n\n{args_str}"),
121
+ char="=",
122
+ )
123
+
124
+ sleep(1)
125
+ if verbose:
126
+ from pprint import pformat
127
+
128
+ config_str = pformat(configs.to_dict())
129
+ logger.info(f"\n{'-' * 40}\n\n{config_str}\n\n{'-' * 40}\n")
130
+ sleep(1)
131
+
132
+
133
+ def format_diff_time(diff_time) -> str:
134
+ """Format time difference as HH:MM:SS."""
135
+ total_seconds = int(diff_time.total_seconds())
136
+ hours = total_seconds // 3600
137
+ minutes = (total_seconds % 3600) // 60
138
+ seconds = total_seconds % 60
139
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
140
+
141
+
142
+ def process_timestamp(CONFIG, verbose=True):
143
+ """Process session timestamps."""
144
+ try:
145
+ CONFIG["END_DATETIME"] = datetime.now()
146
+ CONFIG["RUN_DURATION"] = format_diff_time(
147
+ CONFIG["END_DATETIME"] - CONFIG["START_DATETIME"]
148
+ )
149
+ if verbose:
150
+ logger.info(
151
+ f"\nSTART TIME: {CONFIG['START_DATETIME']}\n"
152
+ f"END TIME: {CONFIG['END_DATETIME']}\n"
153
+ f"RUN DURATION: {CONFIG['RUN_DURATION']}\n"
154
+ )
155
+
156
+ except Exception as e:
157
+ print(e)
158
+
159
+ return CONFIG
160
+
161
+
162
+ def args_to_str(args_dict) -> str:
163
+ """Convert args dictionary to formatted string."""
164
+ if args_dict:
165
+ max_key_length = max(len(str(k)) for k in args_dict.keys())
166
+ return "\n".join(
167
+ f"{str(k):<{max_key_length}} : {str(v)}"
168
+ for k, v in sorted(args_dict.items())
169
+ )
170
+ else:
171
+ return ""
172
+
173
+
174
+ def escape_ansi_from_log_files(log_files) -> None:
175
+ """Remove ANSI escape sequences from log files."""
176
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
177
+
178
+ for f in log_files:
179
+ with open(f, encoding="utf-8") as file:
180
+ content = file.read()
181
+ cleaned_content = ansi_escape.sub("", content)
182
+ with open(f, "w", encoding="utf-8") as file:
183
+ file.write(cleaned_content)
184
+
185
+
186
+ # EOF
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Timestamp: "2025-08-21 20:36:50 (ywatanabe)"
4
3
  # File: /home/ywatanabe/proj/SciTeX-Code/src/scitex/session/_manager.py
5
4
  # ----------------------------------------
6
5
  from __future__ import annotations
6
+
7
7
  import os
8
8
 
9
9
  __FILE__ = __file__
@@ -22,7 +22,12 @@ class SessionManager:
22
22
  def __init__(self):
23
23
  self.active_sessions = {}
24
24
 
25
- def create_session(self, session_id: str, config: Dict[str, Any]) -> None:
25
+ def create_session(
26
+ self,
27
+ session_id: str,
28
+ config: Dict[str, Any],
29
+ script_path: str = None,
30
+ ) -> None:
26
31
  """Register a new session.
27
32
 
28
33
  Parameters
@@ -31,24 +36,56 @@ class SessionManager:
31
36
  Unique identifier for the session
32
37
  config : Dict[str, Any]
33
38
  Session configuration dictionary
39
+ script_path : str, optional
40
+ Path to the script being run
34
41
  """
35
42
  self.active_sessions[session_id] = {
36
43
  "config": config,
37
44
  "start_time": datetime.now(),
38
45
  "status": "running",
46
+ "script_path": script_path,
39
47
  }
40
48
 
41
- def close_session(self, session_id: str) -> None:
49
+ # Start verification tracking (silent fail)
50
+ try:
51
+ from scitex.verify import on_session_start
52
+
53
+ on_session_start(
54
+ session_id=session_id,
55
+ script_path=script_path,
56
+ )
57
+ except Exception:
58
+ pass
59
+
60
+ def close_session(
61
+ self,
62
+ session_id: str,
63
+ status: str = "success",
64
+ exit_code: int = 0,
65
+ ) -> None:
42
66
  """Mark a session as closed.
43
67
 
44
68
  Parameters
45
69
  ----------
46
70
  session_id : str
47
71
  Unique identifier for the session to close
72
+ status : str, optional
73
+ Final status (success, failed, error)
74
+ exit_code : int, optional
75
+ Exit code of the session
48
76
  """
49
77
  if session_id in self.active_sessions:
50
78
  self.active_sessions[session_id]["status"] = "closed"
51
79
  self.active_sessions[session_id]["end_time"] = datetime.now()
80
+ self.active_sessions[session_id]["exit_code"] = exit_code
81
+
82
+ # Stop verification tracking (silent fail)
83
+ try:
84
+ from scitex.verify import on_session_close
85
+
86
+ on_session_close(status=status, exit_code=exit_code)
87
+ except Exception:
88
+ pass
52
89
 
53
90
  def get_active_sessions(self) -> Dict[str, Any]:
54
91
  """Get all active sessions.
@@ -16,7 +16,7 @@ def main(
16
16
  CONFIG=INJECTED,
17
17
  plt=INJECTED,
18
18
  COLORS=INJECTED,
19
- rng=INJECTED,
19
+ rngg=scitex.INJECTED,
20
20
  logger=INJECTED,
21
21
  ):
22
22
  """Demonstration for scitex.session.session"""
@@ -74,7 +74,7 @@ def main(
74
74
  CONFIG=stx.INJECTED, # Session config
75
75
  plt=stx.INJECTED, # Pre-configured matplotlib
76
76
  COLORS=stx.INJECTED, # Color palette
77
- rng=stx.INJECTED, # Random generator
77
+ rngg=stx.INJECTED, # Random generator
78
78
  logger=stx.INJECTED, # Logger
79
79
  ):
80
80
  """Example plotting with session management."""
@@ -91,7 +91,7 @@ def main(
91
91
  CONFIG=stx.INJECTED, # Auto-injected from ./config/*.yaml
92
92
  plt=stx.INJECTED, # Pre-configured matplotlib
93
93
  COLORS=stx.INJECTED, # Color palette
94
- rng=stx.INJECTED, # Random number generator
94
+ rngg=stx.INJECTED, # Random number generator
95
95
  logger=stx.INJECTED, # Session logger
96
96
  ):
97
97
  """