scientiflow-cli 0.4.3__tar.gz → 0.4.6__tar.gz

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 (29) hide show
  1. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/PKG-INFO +1 -1
  2. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/pyproject.toml +1 -1
  3. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/config.py +1 -1
  4. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/main.py +34 -0
  5. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/pipeline/decode_and_execute.py +40 -11
  6. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/singularity.py +12 -7
  7. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/LICENSE.md +0 -0
  8. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/README.md +0 -0
  9. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/__init__.py +0 -0
  10. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/__main__.py +0 -0
  11. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/cli/__init__.py +0 -0
  12. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/cli/auth_utils.py +0 -0
  13. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/cli/login.py +0 -0
  14. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/cli/logout.py +0 -0
  15. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/pipeline/__init__.py +0 -0
  16. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/pipeline/container_manager.py +0 -0
  17. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/pipeline/get_jobs.py +0 -0
  18. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/__init__.py +0 -0
  19. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/auth_service.py +0 -0
  20. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/base_directory.py +0 -0
  21. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/executor.py +0 -0
  22. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/request_handler.py +0 -0
  23. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/rich_printer.py +0 -0
  24. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/services/status_updater.py +0 -0
  25. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/__init__.py +0 -0
  26. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/encryption.py +0 -0
  27. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/file_manager.py +0 -0
  28. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/logger.py +0 -0
  29. {scientiflow_cli-0.4.3 → scientiflow_cli-0.4.6}/scientiflow_cli/utils/mock.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scientiflow-cli
3
- Version: 0.4.3
3
+ Version: 0.4.6
4
4
  Summary: CLI tool for scientiflow. This application runs on the client side, decodes pipelines, and executes them in the configured order!
5
5
  License: Proprietary
6
6
  Author: ScientiFlow
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "scientiflow-cli"
3
- version = "0.4.3"
3
+ version = "0.4.6"
4
4
  description = "CLI tool for scientiflow. This application runs on the client side, decodes pipelines, and executes them in the configured order!"
5
5
  authors = ["ScientiFlow <scientiflow@gmail.com>"]
6
6
  license = "Proprietary"
@@ -6,4 +6,4 @@ class Config:
6
6
  APP_BASE_URL = os.getenv("APP_BASE_URL", "https://www.backend.scientiflow.com/api")
7
7
  elif mode=="dev":
8
8
  APP_BASE_URL = "http://127.0.0.1:8000/api"
9
- # APP_BASE_URL = os.getenv("APP_BASE_URL", "https://www.scientiflow-backend-dev.scientiflow.com/api")
9
+ # APP_BASE_URL = os.getenv("APP_BASE_URL", "https://www.scientiflow-backend-dev.scientiflow.com/api")
@@ -1,5 +1,9 @@
1
1
  import argparse
2
2
  import sys
3
+ import pathlib
4
+ import re
5
+ from importlib import metadata as importlib_metadata
6
+ from importlib.metadata import PackageNotFoundError
3
7
  from scientiflow_cli.cli.login import login_user
4
8
  from scientiflow_cli.cli.logout import logout_user
5
9
  from scientiflow_cli.pipeline.get_jobs import get_jobs
@@ -37,6 +41,30 @@ def display_help(parser):
37
41
 
38
42
  printer.print_table("Scientiflow Agent CLI", columns, rows)
39
43
 
44
+
45
+ def get_package_version() -> str:
46
+ """Return the package version.
47
+
48
+ Try to get the installed package version via importlib.metadata. If the
49
+ package isn't installed (PackageNotFoundError), fall back to reading the
50
+ `pyproject.toml` file in the project root.
51
+ """
52
+ try:
53
+ return importlib_metadata.version("scientiflow-cli")
54
+ except PackageNotFoundError:
55
+ try:
56
+ # Project root is two levels up from this file: .../scientiflow_cli/main.py
57
+ project_root = pathlib.Path(__file__).resolve().parents[1]
58
+ pyproject = project_root / "pyproject.toml"
59
+ if pyproject.exists():
60
+ text = pyproject.read_text(encoding="utf-8")
61
+ m = re.search(r"^version\s*=\s*[\"']([^\"']+)[\"']", text, re.MULTILINE)
62
+ if m:
63
+ return m.group(1)
64
+ except Exception:
65
+ pass
66
+ return "unknown"
67
+
40
68
  def main():
41
69
  parser = RichArgumentParser(description="Scientiflow Agent CLI", add_help=False)
42
70
  parser.formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position=50)
@@ -44,6 +72,7 @@ def main():
44
72
  parser.add_argument('-h', '--help', action='store_true', help="Show this help message and exit")
45
73
  parser.add_argument('--login', action='store_true', help="Login using your scientiflow credentials")
46
74
  parser.add_argument('--logout', action='store_true', help="Logout from scientiflow")
75
+ parser.add_argument('-v', '--version', action='store_true', help="Show package version and exit")
47
76
  parser.add_argument('--list-jobs', action='store_true', help="Get all the pending jobs to execute")
48
77
  parser.add_argument('--set-base-directory', action='store_true', help="Set the base directory to the current working directory \nOptionally, use --hostname to specify the hostname for this server")
49
78
  parser.add_argument('-p', '--parallel', action='store_true', help=argparse.SUPPRESS) # Hide -p or --parallel from --help
@@ -74,6 +103,11 @@ def main():
74
103
  display_help(parser)
75
104
  sys.exit()
76
105
 
106
+ if args.version:
107
+ version = get_package_version()
108
+ printer.print_message(f"Version: {version}", style="bold green")
109
+ sys.exit()
110
+
77
111
  try:
78
112
  if args.login:
79
113
  login_user(token=args.token)
@@ -55,6 +55,10 @@ class PipelineExecutor:
55
55
  self.background_jobs_count = 0 # Track number of active background jobs
56
56
  self.background_jobs_completed = 0 # Track completed background jobs
57
57
  self.background_jobs_lock = threading.Lock() # Thread-safe counter updates
58
+
59
+ # For resuming: flag to track if we've reached the resume point
60
+ self.resume_mode = (job_status == "running" and current_node_from_config is not None)
61
+ self.reached_resume_point = False
58
62
 
59
63
  # Set up job-specific log file
60
64
  self.log_file_path = os.path.join(self.base_dir, self.project_title, self.job_dir_name, "logs", "output.log")
@@ -109,13 +113,10 @@ class PipelineExecutor:
109
113
  except Exception as e:
110
114
  print(f"[ERROR] Failed to update terminal output: {e}")
111
115
 
112
-
113
- # def replace_variables(self, command: str) -> str:
114
- # """Replace placeholders like ${VAR} with environment values."""
115
- # print(self.environment_variables)
116
- # return re.sub(r'\$\{(\w+)\}', lambda m: self.environment_variables.get(m.group(1), m.group(0)), command)
117
-
118
116
  def replace_variables(self, command: str) -> str:
117
+ # """Replace placeholders like ${VAR} with environment values."""
118
+ # print(self.environment_variables)
119
+ # return re.sub(r'\$\{(\w+)\}', lambda m: self.environment_variables.get(m.group(1), m.group(0)), command)
119
120
  """Replace placeholders like ${VAR} with environment values.
120
121
  - If value is a list: safely join into space-separated arguments (quoted).
121
122
  - Otherwise: use the old behavior (direct substitution).
@@ -130,7 +131,6 @@ class PipelineExecutor:
130
131
  return self.environment_variables.get(key, match.group(0))
131
132
  return re.sub(r'\$\{(\w+)\}', replacer, command)
132
133
 
133
-
134
134
  def execute_command(self, command: str):
135
135
  """Run the command in the terminal, display output in real-time, and log the captured output."""
136
136
  import sys
@@ -214,7 +214,35 @@ class PipelineExecutor:
214
214
 
215
215
  self.current_node = node
216
216
  current_node = self.nodes_map[node]
217
-
217
+
218
+ # Check if we've reached the resume point
219
+ if self.resume_mode and not self.reached_resume_point:
220
+ if node == self.current_node_from_config:
221
+ self.reached_resume_point = True
222
+ node_label = current_node['data'].get('label', node)
223
+ printer.print_message(f"[INFO] Reached resume point: {node_label} - continuing execution", style="bold green")
224
+ else:
225
+ # Skip execution for this node, just traverse
226
+ if current_node['type'] == "splitterParent":
227
+ collector = None
228
+ for child in self.adj_list[node]:
229
+ if self.nodes_map[child]['data']['active']:
230
+ collector = self.dfs(child)
231
+ if collector and self.adj_list[collector]:
232
+ return self.dfs(self.adj_list[collector][0])
233
+ return
234
+ elif current_node['type'] == "splitter-child":
235
+ if current_node['data']['active'] and self.adj_list[node]:
236
+ return self.dfs(self.adj_list[node][0])
237
+ return
238
+ elif current_node['type'] == "terminal":
239
+ if self.adj_list[node]:
240
+ return self.dfs(self.adj_list[node][0])
241
+ return
242
+ elif current_node['type'] == "collector":
243
+ return node if self.adj_list[node] else None
244
+
245
+ # Normal execution (either not in resume mode or already reached resume point)
218
246
  if current_node['type'] == "splitterParent":
219
247
  collector = None
220
248
  for child in self.adj_list[node]:
@@ -297,13 +325,14 @@ class PipelineExecutor:
297
325
  current_status = self.job_status
298
326
 
299
327
  if current_status == "running":
300
- # Job is already running, resume from current node
328
+ # Job is already running, resume from start but skip until current node
301
329
  current_node_id = self.current_node_from_config
302
330
  if current_node_id and current_node_id in self.nodes_map:
303
331
  # Get the label from the current node
304
332
  current_node_label = self.nodes_map[current_node_id]['data'].get('label', current_node_id)
305
- printer.print_message(f"[INFO] Resuming execution from current node: {current_node_label}", style="bold blue")
306
- starting_node = current_node_id
333
+ printer.print_message(f"[INFO] Resuming job - will skip to node: {current_node_label}", style="bold blue")
334
+ # Start from the beginning (start_node or root)
335
+ starting_node = self.start_node or next(iter(self.root_nodes), None)
307
336
  else:
308
337
  printer.print_message("[WARNING] Current node not found, starting from beginning", style="bold yellow")
309
338
  starting_node = self.start_node or next(iter(self.root_nodes), None)
@@ -39,18 +39,23 @@ def install_singularity():
39
39
 
40
40
  printer.print_message("[bold yellow][+] Installing Singularity[/bold yellow]")
41
41
  os_release_path = Path("/etc/os-release")
42
- ubuntu_codename = None
42
+ os_codename = None
43
+
43
44
  if os_release_path.exists():
44
45
  for line in os_release_path.read_text().splitlines():
45
- if line.startswith("UBUNTU_CODENAME="):
46
- ubuntu_codename = line.split("=")[1]
46
+ if line.startswith("VERSION_CODENAME="):
47
+ os_codename = line.split("=")[1]
47
48
  break
48
49
 
49
- if not ubuntu_codename:
50
- raise ValueError("[bold red]Could not determine Ubuntu codename from /etc/os-release.[/bold red]")
50
+ if not os_codename:
51
+ raise ValueError("[bold red]Could not determine Ubuntu / Debian codename from /etc/os-release.[/bold red]")
52
+
53
+ if os_codename == "bookworm":
54
+ printer.print_message("[bold yellow][+] WARNING: Debian distros are not officially supported. Compatibility issues may arise[/bold yellow]")
55
+ os_codename = "jammy"
51
56
 
52
- singularity_url = f"https://github.com/sylabs/singularity/releases/download/v{SINGULARITY_VERSION}/singularity-ce_{SINGULARITY_VERSION}-{ubuntu_codename}_amd64.deb"
53
- temp_file = Path(f"/tmp/singularity-ce_{SINGULARITY_VERSION}-{ubuntu_codename}_amd64.deb")
57
+ singularity_url = f"https://github.com/sylabs/singularity/releases/download/v{SINGULARITY_VERSION}/singularity-ce_{SINGULARITY_VERSION}-{os_codename}_amd64.deb"
58
+ temp_file = Path(f"/tmp/singularity-ce_{SINGULARITY_VERSION}-{os_codename}_amd64.deb")
54
59
 
55
60
  printer.print_message("[bold cyan]Downloading Singularity package...[/bold cyan]")
56
61
  progress, task = printer.create_progress_bar("[cyan]Downloading...", total=100)