cloudos-cli 2.48.0__tar.gz → 2.50.0__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 (41) hide show
  1. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/PKG-INFO +6 -7
  2. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/README.md +5 -6
  3. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/__main__.py +49 -10
  4. cloudos_cli-2.50.0/cloudos_cli/_version.py +1 -0
  5. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/clos.py +168 -17
  6. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/errors.py +15 -1
  7. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/PKG-INFO +6 -7
  8. cloudos_cli-2.48.0/cloudos_cli/_version.py +0 -1
  9. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/LICENSE +0 -0
  10. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/__init__.py +0 -0
  11. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/configure/__init__.py +0 -0
  12. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/configure/configure.py +0 -0
  13. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/datasets/__init__.py +0 -0
  14. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/datasets/datasets.py +0 -0
  15. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/import_wf/__init__.py +0 -0
  16. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/import_wf/import_wf.py +0 -0
  17. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/jobs/__init__.py +0 -0
  18. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/jobs/job.py +0 -0
  19. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/link/__init__.py +0 -0
  20. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/link/link.py +0 -0
  21. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/procurement/__init__.py +0 -0
  22. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/procurement/images.py +0 -0
  23. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/queue/__init__.py +0 -0
  24. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/queue/queue.py +0 -0
  25. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/__init__.py +0 -0
  26. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/array_job.py +0 -0
  27. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/cloud.py +0 -0
  28. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/details.py +0 -0
  29. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/last_wf.py +0 -0
  30. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/requests.py +0 -0
  31. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli/utils/resources.py +0 -0
  32. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/SOURCES.txt +0 -0
  33. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/dependency_links.txt +0 -0
  34. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/entry_points.txt +0 -0
  35. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/requires.txt +0 -0
  36. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/cloudos_cli.egg-info/top_level.txt +0 -0
  37. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/setup.cfg +0 -0
  38. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/setup.py +0 -0
  39. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/tests/__init__.py +0 -0
  40. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/tests/functions_for_pytest.py +0 -0
  41. {cloudos_cli-2.48.0 → cloudos_cli-2.50.0}/tests/test_cli_project_create.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudos_cli
3
- Version: 2.48.0
3
+ Version: 2.50.0
4
4
  Summary: Python package for interacting with CloudOS
5
5
  Home-page: https://github.com/lifebit-ai/cloudos-cli
6
6
  Author: David Piñeyro
@@ -775,16 +775,15 @@ This file can later be used when running a job with `cloudos job run --job-confi
775
775
  > [!NOTE]
776
776
  > Job details can only be retrieved for a single user, cannot see other user's job details.
777
777
 
778
- #### Get a list of your jobs from a CloudOS workspace
778
+ #### Get a list of workspace jobs from a CloudOS
779
779
 
780
- You can get a summary of your last 30 submitted jobs (or your selected number of last jobs using `--last-n-jobs n`
781
- parameter) in two different formats:
780
+ You can get a summary of the workspace's last 30 submitted jobs (or a selected number of last jobs using `--last-n-jobs n` parameter) in two different formats:
782
781
 
783
782
  - CSV: this is a table with a minimum predefined set of columns by default, or all the
784
783
  available columns using the `--all-fields` argument.
785
- - JSON: all the available information from your jobs, in JSON format.
784
+ - JSON: all the available information from the workspace jobs, in JSON format (`--all-fields` is always enabled for this format).
786
785
 
787
- To get a list with your last 30 submitted jobs to a given workspace, in CSV format, use
786
+ To get a list with the workspace's last 30 submitted jobs, in CSV format, use
788
787
  the following command:
789
788
 
790
789
  ```bash
@@ -806,7 +805,7 @@ Executing list...
806
805
 
807
806
  In addition, a file named `joblist.csv` is created.
808
807
 
809
- To get the same information, but for all your jobs and in JSON format, use the following command:
808
+ To get the same information, but for all the workspace's jobs and in JSON format, use the following command:
810
809
 
811
810
  ```bash
812
811
  cloudos job list \
@@ -740,16 +740,15 @@ This file can later be used when running a job with `cloudos job run --job-confi
740
740
  > [!NOTE]
741
741
  > Job details can only be retrieved for a single user, cannot see other user's job details.
742
742
 
743
- #### Get a list of your jobs from a CloudOS workspace
743
+ #### Get a list of workspace jobs from a CloudOS
744
744
 
745
- You can get a summary of your last 30 submitted jobs (or your selected number of last jobs using `--last-n-jobs n`
746
- parameter) in two different formats:
745
+ You can get a summary of the workspace's last 30 submitted jobs (or a selected number of last jobs using `--last-n-jobs n` parameter) in two different formats:
747
746
 
748
747
  - CSV: this is a table with a minimum predefined set of columns by default, or all the
749
748
  available columns using the `--all-fields` argument.
750
- - JSON: all the available information from your jobs, in JSON format.
749
+ - JSON: all the available information from the workspace jobs, in JSON format (`--all-fields` is always enabled for this format).
751
750
 
752
- To get a list with your last 30 submitted jobs to a given workspace, in CSV format, use
751
+ To get a list with the workspace's last 30 submitted jobs, in CSV format, use
753
752
  the following command:
754
753
 
755
754
  ```bash
@@ -771,7 +770,7 @@ Executing list...
771
770
 
772
771
  In addition, a file named `joblist.csv` is created.
773
772
 
774
- To get the same information, but for all your jobs and in JSON format, use the following command:
773
+ To get the same information, but for all the workspace's jobs and in JSON format, use the following command:
775
774
 
776
775
  ```bash
777
776
  cloudos job list \
@@ -9,6 +9,7 @@ from cloudos_cli.utils.errors import BadRequestException
9
9
  import json
10
10
  import time
11
11
  import sys
12
+ import traceback
12
13
  from ._version import __version__
13
14
  from cloudos_cli.configure.configure import ConfigurationProfile
14
15
  from rich.console import Console
@@ -36,12 +37,40 @@ CLOUDOS_URL = 'https://cloudos.lifebit.ai'
36
37
  INIT_PROFILE = 'initialisingProfile'
37
38
 
38
39
 
40
+ def handle_exception(show_traceback=False):
41
+ """Custom exception handler for CloudOS CLI"""
42
+ def exception_handler(exc_type, exc_value, exc_traceback):
43
+ # Handle keyboard interrupt gracefully
44
+ if issubclass(exc_type, KeyboardInterrupt):
45
+ sys.exit(1)
46
+
47
+ if show_traceback:
48
+ # Show full traceback for debugging
49
+ traceback.print_exception(exc_type, exc_value, exc_traceback)
50
+ else:
51
+ # Show only the error message
52
+ click.echo(click.style(f"Error: {exc_value}", fg='red'), err=True)
53
+
54
+ sys.exit(1)
55
+
56
+ return exception_handler
57
+
58
+
39
59
  @click.group()
60
+ @click.option('--debug', is_flag=True, help='Show detailed error information and tracebacks')
40
61
  @click.version_option(__version__)
41
62
  @click.pass_context
42
- def run_cloudos_cli(ctx):
63
+ def run_cloudos_cli(ctx, debug):
43
64
  """CloudOS python package: a package for interacting with CloudOS."""
44
65
  ctx.ensure_object(dict)
66
+ ctx.obj['debug'] = debug
67
+
68
+ # Set up custom exception handling based on debug flag
69
+ if not debug:
70
+ sys.excepthook = handle_exception(show_traceback=False)
71
+ else:
72
+ sys.excepthook = handle_exception(show_traceback=True)
73
+
45
74
  if ctx.invoked_subcommand not in ['datasets']:
46
75
  print(run_cloudos_cli.__doc__ + '\n')
47
76
  print('Version: ' + __version__ + '\n')
@@ -1176,17 +1205,17 @@ def job_details(ctx,
1176
1205
  default='joblist',
1177
1206
  required=False)
1178
1207
  @click.option('--output-format',
1179
- help='The desired file format (file extension) for the output. Default=csv.',
1208
+ help='The desired file format (file extension) for the output. For json option --all-fields will be automatically set to True. Default=csv.',
1180
1209
  type=click.Choice(['csv', 'json'], case_sensitive=False),
1181
1210
  default='csv')
1182
1211
  @click.option('--all-fields',
1183
1212
  help=('Whether to collect all available fields from jobs or ' +
1184
1213
  'just the preconfigured selected fields. Only applicable ' +
1185
- 'when --output-format=csv'),
1214
+ 'when --output-format=csv. Automatically enabled for json output.'),
1186
1215
  is_flag=True)
1187
1216
  @click.option('--last-n-jobs',
1188
- help=("The number of last user's jobs to retrieve. You can use 'all' to " +
1189
- "retrieve all user's jobs. Default=30."),
1217
+ help=("The number of last workspace jobs to retrieve. You can use 'all' to " +
1218
+ "retrieve all workspace jobs. Default=30."),
1190
1219
  default='30')
1191
1220
  @click.option('--page',
1192
1221
  help=('Response page to retrieve. If --last-n-jobs is set, then --page ' +
@@ -1221,7 +1250,7 @@ def list_jobs(ctx,
1221
1250
  disable_ssl_verification,
1222
1251
  ssl_cert,
1223
1252
  profile):
1224
- """Collect all your jobs from a CloudOS workspace in CSV format."""
1253
+ """Collect workspace jobs from a CloudOS workspace in CSV or JSON format."""
1225
1254
  profile = profile or ctx.default_map['job']['list']['profile']
1226
1255
  # Create a dictionary with required and non-required params
1227
1256
  required_dict = {
@@ -1270,6 +1299,7 @@ def list_jobs(ctx,
1270
1299
  except ValueError:
1271
1300
  print("[ERROR] last-n-jobs value was not valid. Please use a positive int or 'all'")
1272
1301
  raise
1302
+
1273
1303
  my_jobs_r = cl.get_job_list(workspace_id, last_n_jobs, page, archived, verify_ssl)
1274
1304
  if len(my_jobs_r) == 0:
1275
1305
  if ctx.get_parameter_source('page') == click.core.ParameterSource.DEFAULT:
@@ -1281,15 +1311,14 @@ def list_jobs(ctx,
1281
1311
  'using --page parameter.')
1282
1312
  elif output_format == 'csv':
1283
1313
  my_jobs = cl.process_job_list(my_jobs_r, all_fields)
1284
- my_jobs.to_csv(outfile, index=False)
1285
- print(f'\tJob list collected with a total of {my_jobs.shape[0]} jobs.')
1314
+ cl.save_job_list_to_csv(my_jobs, outfile)
1286
1315
  elif output_format == 'json':
1287
1316
  with open(outfile, 'w') as o:
1288
1317
  o.write(json.dumps(my_jobs_r))
1289
1318
  print(f'\tJob list collected with a total of {len(my_jobs_r)} jobs.')
1319
+ print(f'\tJob list saved to {outfile}')
1290
1320
  else:
1291
1321
  raise ValueError('Unrecognised output format. Please use one of [csv|json]')
1292
- print(f'\tJob list saved to {outfile}')
1293
1322
 
1294
1323
 
1295
1324
  @job.command('abort')
@@ -3913,4 +3942,14 @@ def reset_organisation_image(ctx,
3913
3942
 
3914
3943
 
3915
3944
  if __name__ == "__main__":
3916
- run_cloudos_cli()
3945
+ try:
3946
+ run_cloudos_cli()
3947
+ except Exception as e:
3948
+ # Check if debug flag was passed (fallback for cases where Click doesn't handle it)
3949
+ debug_mode = '--debug' in sys.argv
3950
+
3951
+ if debug_mode:
3952
+ traceback.print_exc()
3953
+ else:
3954
+ click.echo(click.style(f"Error: {e}", fg='red'), err=True)
3955
+ sys.exit(1)
@@ -0,0 +1 @@
1
+ __version__ = '2.50.0'
@@ -11,6 +11,8 @@ from cloudos_cli.utils.errors import BadRequestException, JoBNotCompletedExcepti
11
11
  from cloudos_cli.utils.requests import retry_requests_get, retry_requests_post, retry_requests_put
12
12
  import pandas as pd
13
13
  from cloudos_cli.utils.last_wf import youngest_workflow_id_by_name
14
+ from datetime import datetime
15
+
14
16
 
15
17
  # GLOBAL VARS
16
18
  JOB_COMPLETED = 'completed'
@@ -430,28 +432,27 @@ class Cloudos:
430
432
  df : pandas.DataFrame
431
433
  A DataFrame with the requested columns from the jobs.
432
434
  """
433
- COLUMNS = ['_id',
434
- 'team',
435
+ COLUMNS = ['status',
435
436
  'name',
436
- 'parameters',
437
- 'status',
437
+ 'project.name',
438
+ 'user.name',
439
+ 'user.surname',
440
+ 'workflow.name',
441
+ '_id',
438
442
  'startTime',
439
443
  'endTime',
440
444
  'createdAt',
441
445
  'updatedAt',
442
- 'computeCostSpent',
443
- 'masterInstanceStorageCost',
444
- 'user.id',
445
- 'workflow._id',
446
- 'workflow.name',
447
- 'workflow.description',
448
- 'workflow.createdAt',
449
- 'workflow.updatedAt',
450
- 'workflow.workflowType',
451
- 'project._id',
452
- 'project.name',
453
- 'project.createdAt',
454
- 'project.updatedAt'
446
+ 'revision.commit',
447
+ 'realInstancesExecutionCost',
448
+ 'masterInstance.usedInstance.type',
449
+ 'storageMode',
450
+ 'workflow.repository.url',
451
+ 'nextflowVersion',
452
+ 'batch.enabled',
453
+ 'storageSizeInGb',
454
+ 'batch.jobQueue.id',
455
+ 'usesFusionFileSystem'
455
456
  ]
456
457
  df_full = pd.json_normalize(r)
457
458
  if df_full.empty:
@@ -462,6 +463,156 @@ class Cloudos:
462
463
  df = df_full.loc[:, COLUMNS]
463
464
  return df
464
465
 
466
+ def reorder_job_list(self, my_jobs_df, filename='my_jobs.csv'):
467
+ """Save a job list DataFrame to a CSV file with renamed and ordered columns.
468
+
469
+ Parameters
470
+ ----------
471
+ my_jobs_df : pandas.DataFrame
472
+ A DataFrame containing job information from process_job_list.
473
+ filename : str
474
+ The name of the file to save the DataFrame to. Default is 'my_jobs.csv'.
475
+
476
+ Returns
477
+ -------
478
+ None
479
+ Saves the DataFrame to a CSV file with renamed and ordered columns.
480
+ """
481
+ # Handle empty DataFrame
482
+ if my_jobs_df.empty:
483
+ print("Warning: DataFrame is empty. Creating empty CSV file.")
484
+ empty_df = pd.DataFrame()
485
+ empty_df.to_csv(filename, index=False)
486
+ return
487
+
488
+ # Create a copy to avoid modifying the original DataFrame
489
+ jobs_df = my_jobs_df.copy()
490
+
491
+ # 1. Fusion user.name and user.surname into user
492
+ if 'user.name' in jobs_df.columns and 'user.surname' in jobs_df.columns:
493
+ jobs_df['user'] = jobs_df.apply(
494
+ lambda row: f"{row.get('user.name', '')} {row.get('user.surname', '')}".strip()
495
+ if pd.notna(row.get('user.name')) or pd.notna(row.get('user.surname'))
496
+ else None, axis=1
497
+ )
498
+ # Remove original columns
499
+ jobs_df = jobs_df.drop(columns=['user.name', 'user.surname'], errors='ignore')
500
+
501
+ # 2. Convert time fields to human-readable format
502
+ time_columns = ['startTime', 'endTime', 'createdAt', 'updatedAt']
503
+ for col in time_columns:
504
+ if col in jobs_df.columns:
505
+ def format_time(x):
506
+ if pd.notna(x) and isinstance(x, str) and x:
507
+ try:
508
+ return datetime.fromisoformat(x.replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S UTC')
509
+ except (ValueError, TypeError):
510
+ return x # Return original value if parsing fails
511
+ return None
512
+ jobs_df[col] = jobs_df[col].apply(format_time)
513
+
514
+ # 3. Format realInstancesExecutionCost (divide by 100, show 4 decimals)
515
+ if 'realInstancesExecutionCost' in jobs_df.columns:
516
+ def format_cost(x):
517
+ if pd.notna(x) and x != '' and x is not None:
518
+ try:
519
+ return f"{float(x) / 100:.4f}"
520
+ except (ValueError, TypeError):
521
+ return x # Return original value if conversion fails
522
+ return None
523
+ jobs_df['realInstancesExecutionCost'] = jobs_df['realInstancesExecutionCost'].apply(format_cost)
524
+
525
+ # 4. Calculate Run time (endTime - startTime)
526
+ if 'startTime' in jobs_df.columns and 'endTime' in jobs_df.columns:
527
+ def calculate_runtime(row):
528
+ start_time = row.get('startTime')
529
+ end_time = row.get('endTime')
530
+ if pd.notna(start_time) and pd.notna(end_time) and start_time and end_time:
531
+ # Use original times from the original DataFrame for calculation
532
+ original_start = my_jobs_df.iloc[row.name].get('startTime') if row.name < len(my_jobs_df) else start_time
533
+ original_end = my_jobs_df.iloc[row.name].get('endTime') if row.name < len(my_jobs_df) else end_time
534
+ if pd.notna(original_start) and pd.notna(original_end) and original_start and original_end:
535
+ try:
536
+ start_dt = datetime.fromisoformat(str(original_start).replace('Z', '+00:00'))
537
+ end_dt = datetime.fromisoformat(str(original_end).replace('Z', '+00:00'))
538
+ duration = end_dt - start_dt
539
+ # Format duration as hours:minutes:seconds
540
+ total_seconds = int(duration.total_seconds())
541
+ hours = total_seconds // 3600
542
+ minutes = (total_seconds % 3600) // 60
543
+ seconds = total_seconds % 60
544
+ if hours > 0:
545
+ return f"{hours}h {minutes}m {seconds}s"
546
+ elif minutes > 0:
547
+ return f"{minutes}m {seconds}s"
548
+ else:
549
+ return f"{seconds}s"
550
+ except (ValueError, TypeError):
551
+ return None
552
+ return None
553
+
554
+ jobs_df['Run time'] = jobs_df.apply(calculate_runtime, axis=1)
555
+
556
+ # 5. Format batch.enabled (True -> "Batch", else "N/A")
557
+ if 'batch.enabled' in jobs_df.columns:
558
+ jobs_df['batch.enabled'] = jobs_df['batch.enabled'].apply(
559
+ lambda x: "Batch" if x is True else "N/A"
560
+ )
561
+
562
+ # 6. Rename columns using the provided dictionary
563
+ column_name_mapping = {
564
+ "status": "Status",
565
+ "name": "Name",
566
+ "project.name": "Project",
567
+ "user": "Owner",
568
+ "workflow.name": "Pipeline",
569
+ "_id": "ID",
570
+ "createdAt": "Submit time",
571
+ "updatedAt": "End time",
572
+ "revision.commit": "Commit",
573
+ "realInstancesExecutionCost": "Cost",
574
+ "masterInstance.usedInstance.type": "Resources",
575
+ "storageMode": "Storage type",
576
+ "workflow.repository.url": "Pipeline url",
577
+ "nextflowVersion": "Nextflow version",
578
+ "batch.enabled": "Executor",
579
+ "storageSizeInGb": "Storage size",
580
+ "batch.jobQueue.id": "Job queue ID",
581
+ "usesFusionFileSystem": "Accelerated file staging"
582
+ }
583
+
584
+ # Rename columns that exist in the DataFrame
585
+ jobs_df = jobs_df.rename(columns=column_name_mapping)
586
+
587
+ # Remove the original startTime and endTime columns since we now have Submit time, End time, and Run time
588
+ jobs_df = jobs_df.drop(columns=['startTime', 'endTime'], errors='ignore')
589
+
590
+ # 7. Define the desired order of columns
591
+ desired_order = [
592
+ "Status", "Name", "Project", "Owner", "Pipeline", "ID",
593
+ "Submit time", "End time", "Run time", "Commit", "Cost",
594
+ "Resources", "Storage type", "Pipeline url",
595
+ "Nextflow version", "Executor", "Storage size", "Job queue ID",
596
+ "Accelerated file staging"
597
+ ]
598
+
599
+ # Reorder columns - only include columns that exist in the DataFrame
600
+ available_columns = [col for col in desired_order if col in jobs_df.columns]
601
+ # Add any remaining columns that aren't in the desired order
602
+ remaining_columns = [col for col in jobs_df.columns if col not in desired_order]
603
+ final_column_order = available_columns + remaining_columns
604
+
605
+ # Reorder the DataFrame
606
+ jobs_df = jobs_df[final_column_order]
607
+ return jobs_df
608
+
609
+ def save_job_list_to_csv(self, my_jobs_df, filename='my_jobs.csv'):
610
+ # Save to CSV
611
+ jobs_df = self.reorder_job_list(my_jobs_df, filename)
612
+ jobs_df.to_csv(filename, index=False)
613
+ print(f'\tJob list collected with a total of {len(jobs_df)} jobs.')
614
+ print(f'\tJob list saved to {filename}')
615
+
465
616
  def get_workflow_list(self, workspace_id, verify=True, get_all=True,
466
617
  page=1, page_size=10, max_page_size=100,
467
618
  archived_status=False):
@@ -12,7 +12,21 @@ class BadRequestException(Exception):
12
12
  The request variable returned that caused the error.
13
13
  """
14
14
  def __init__(self, rv):
15
- msg = "Server returned status {}. Reason: {}".format(rv.status_code, rv.reason)
15
+ # Try to get the message from response body first
16
+ error_message = None
17
+ try:
18
+ response_body = rv.json()
19
+ error_message = response_body.get('message')
20
+ except (ValueError, AttributeError):
21
+ # Response is not JSON or doesn't have expected structure
22
+ pass
23
+
24
+ # Prioritize message from response, fallback to reason
25
+ if error_message:
26
+ msg = "Server returned status {}. Message: {}".format(rv.status_code, error_message)
27
+ else:
28
+ msg = "Server returned status {}. Reason: {}".format(rv.status_code, rv.reason)
29
+
16
30
  super(BadRequestException, self).__init__(msg)
17
31
  self.rv = rv
18
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudos_cli
3
- Version: 2.48.0
3
+ Version: 2.50.0
4
4
  Summary: Python package for interacting with CloudOS
5
5
  Home-page: https://github.com/lifebit-ai/cloudos-cli
6
6
  Author: David Piñeyro
@@ -775,16 +775,15 @@ This file can later be used when running a job with `cloudos job run --job-confi
775
775
  > [!NOTE]
776
776
  > Job details can only be retrieved for a single user, cannot see other user's job details.
777
777
 
778
- #### Get a list of your jobs from a CloudOS workspace
778
+ #### Get a list of workspace jobs from a CloudOS
779
779
 
780
- You can get a summary of your last 30 submitted jobs (or your selected number of last jobs using `--last-n-jobs n`
781
- parameter) in two different formats:
780
+ You can get a summary of the workspace's last 30 submitted jobs (or a selected number of last jobs using `--last-n-jobs n` parameter) in two different formats:
782
781
 
783
782
  - CSV: this is a table with a minimum predefined set of columns by default, or all the
784
783
  available columns using the `--all-fields` argument.
785
- - JSON: all the available information from your jobs, in JSON format.
784
+ - JSON: all the available information from the workspace jobs, in JSON format (`--all-fields` is always enabled for this format).
786
785
 
787
- To get a list with your last 30 submitted jobs to a given workspace, in CSV format, use
786
+ To get a list with the workspace's last 30 submitted jobs, in CSV format, use
788
787
  the following command:
789
788
 
790
789
  ```bash
@@ -806,7 +805,7 @@ Executing list...
806
805
 
807
806
  In addition, a file named `joblist.csv` is created.
808
807
 
809
- To get the same information, but for all your jobs and in JSON format, use the following command:
808
+ To get the same information, but for all the workspace's jobs and in JSON format, use the following command:
810
809
 
811
810
  ```bash
812
811
  cloudos job list \
@@ -1 +0,0 @@
1
- __version__ = '2.48.0'
File without changes
File without changes
File without changes