cloudos-cli 2.68.0__tar.gz → 2.69.1__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.
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/PKG-INFO +72 -1
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/README.md +71 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/__main__.py +172 -4
- cloudos_cli-2.69.1/cloudos_cli/_version.py +1 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/clos.py +433 -1
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/import_wf/import_wf.py +1 -2
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/PKG-INFO +72 -1
- cloudos_cli-2.68.0/cloudos_cli/_version.py +0 -1
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/LICENSE +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/cost/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/cost/cost.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/datasets/datasets.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/link/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/link/link.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/logging/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/logging/logger.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/procurement/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/procurement/images.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/queue/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/queue/queue.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/related_analyses/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/related_analyses/related_analyses.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/details.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/errors.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/last_wf.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/SOURCES.txt +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/setup.cfg +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/setup.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/functions_for_pytest.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_cli_project_create.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_cost/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_cost/test_job_cost.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_logging/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_logging/test_logger.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_related_analyses/__init__.py +0 -0
- {cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_related_analyses/test_related_analyses.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.69.1
|
|
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
|
|
@@ -669,6 +669,41 @@ You can also link all result directories to an interactive session using the `--
|
|
|
669
669
|
cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
|
|
670
670
|
```
|
|
671
671
|
|
|
672
|
+
**Check Results Deletion Status**
|
|
673
|
+
|
|
674
|
+
You can check the deletion status of a job's results folder using the `--status` flag. This is useful for monitoring the deletion lifecycle of analysis results.
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
cloudos job results --status --profile my_profile --job-id "12345678910"
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
The command will display the current status of the results folder. Possible statuses include:
|
|
681
|
+
- **available**: Results are available and accessible
|
|
682
|
+
- **scheduled for deletion**: Results are scheduled to be deleted
|
|
683
|
+
- **deleting**: Results are currently being deleted
|
|
684
|
+
- **deleted**: Results have been deleted
|
|
685
|
+
- **failed to delete**: Deletion process failed
|
|
686
|
+
|
|
687
|
+
Example output for available results:
|
|
688
|
+
```console
|
|
689
|
+
The results of job 1234567890 are in status: available
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
For results in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
693
|
+
```console
|
|
694
|
+
The results of job 6912036aa6ed001148c96018 are in status: scheduled for deletion
|
|
695
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
696
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Use the `--verbose` flag to see detailed information including the results folder name, folder ID, creation and update timestamps:
|
|
700
|
+
```bash
|
|
701
|
+
cloudos job results --status --profile my_profile --job-id "12345678910" --verbose
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
> [!NOTE]
|
|
705
|
+
> If results have been completely deleted, the command will report that the results folder was not found, which may indicate that results have been deleted or scheduled for deletion.
|
|
706
|
+
|
|
672
707
|
|
|
673
708
|
#### Clone or Resume job
|
|
674
709
|
|
|
@@ -930,6 +965,42 @@ cloudos job workdir \
|
|
|
930
965
|
--link --session-id your_session_id
|
|
931
966
|
```
|
|
932
967
|
|
|
968
|
+
**Check Working Directory Deletion Status**
|
|
969
|
+
|
|
970
|
+
You can check the deletion status of a job's working directory using the `--status` flag. This is useful for monitoring the deletion lifecycle of intermediate job files.
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910"
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
The command will display the current status of the working directory folder. Possible statuses include:
|
|
977
|
+
- **available**: Working directory is available and accessible
|
|
978
|
+
- **scheduled for deletion**: Working directory is scheduled to be deleted
|
|
979
|
+
- **deleting**: Working directory is currently being deleted
|
|
980
|
+
- **deleted**: Working directory has been deleted
|
|
981
|
+
- **failed to delete**: Deletion process failed
|
|
982
|
+
|
|
983
|
+
Example output for available working directory:
|
|
984
|
+
```console
|
|
985
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: available
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
For working directories in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
989
|
+
```console
|
|
990
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: scheduled for deletion
|
|
991
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
992
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
Use the `--verbose` flag to see detailed information including the working directory folder name, folder ID, creation and update timestamps:
|
|
996
|
+
```bash
|
|
997
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910" --verbose
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
> [!NOTE]
|
|
1001
|
+
> This command only works for jobs that were run with resumable mode enabled (using the `--resumable` flag). Jobs without resumable mode will not have a working directory to check.
|
|
1002
|
+
> If the working directory has been completely deleted, the command will report that the working directory was not found.
|
|
1003
|
+
|
|
933
1004
|
#### List Jobs
|
|
934
1005
|
|
|
935
1006
|
You can get a summary of workspace jobs in two different formats:
|
|
@@ -634,6 +634,41 @@ You can also link all result directories to an interactive session using the `--
|
|
|
634
634
|
cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
|
|
635
635
|
```
|
|
636
636
|
|
|
637
|
+
**Check Results Deletion Status**
|
|
638
|
+
|
|
639
|
+
You can check the deletion status of a job's results folder using the `--status` flag. This is useful for monitoring the deletion lifecycle of analysis results.
|
|
640
|
+
|
|
641
|
+
```bash
|
|
642
|
+
cloudos job results --status --profile my_profile --job-id "12345678910"
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
The command will display the current status of the results folder. Possible statuses include:
|
|
646
|
+
- **available**: Results are available and accessible
|
|
647
|
+
- **scheduled for deletion**: Results are scheduled to be deleted
|
|
648
|
+
- **deleting**: Results are currently being deleted
|
|
649
|
+
- **deleted**: Results have been deleted
|
|
650
|
+
- **failed to delete**: Deletion process failed
|
|
651
|
+
|
|
652
|
+
Example output for available results:
|
|
653
|
+
```console
|
|
654
|
+
The results of job 1234567890 are in status: available
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
For results in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
658
|
+
```console
|
|
659
|
+
The results of job 6912036aa6ed001148c96018 are in status: scheduled for deletion
|
|
660
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
661
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Use the `--verbose` flag to see detailed information including the results folder name, folder ID, creation and update timestamps:
|
|
665
|
+
```bash
|
|
666
|
+
cloudos job results --status --profile my_profile --job-id "12345678910" --verbose
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
> [!NOTE]
|
|
670
|
+
> If results have been completely deleted, the command will report that the results folder was not found, which may indicate that results have been deleted or scheduled for deletion.
|
|
671
|
+
|
|
637
672
|
|
|
638
673
|
#### Clone or Resume job
|
|
639
674
|
|
|
@@ -895,6 +930,42 @@ cloudos job workdir \
|
|
|
895
930
|
--link --session-id your_session_id
|
|
896
931
|
```
|
|
897
932
|
|
|
933
|
+
**Check Working Directory Deletion Status**
|
|
934
|
+
|
|
935
|
+
You can check the deletion status of a job's working directory using the `--status` flag. This is useful for monitoring the deletion lifecycle of intermediate job files.
|
|
936
|
+
|
|
937
|
+
```bash
|
|
938
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910"
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
The command will display the current status of the working directory folder. Possible statuses include:
|
|
942
|
+
- **available**: Working directory is available and accessible
|
|
943
|
+
- **scheduled for deletion**: Working directory is scheduled to be deleted
|
|
944
|
+
- **deleting**: Working directory is currently being deleted
|
|
945
|
+
- **deleted**: Working directory has been deleted
|
|
946
|
+
- **failed to delete**: Deletion process failed
|
|
947
|
+
|
|
948
|
+
Example output for available working directory:
|
|
949
|
+
```console
|
|
950
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: available
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
For working directories in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
954
|
+
```console
|
|
955
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: scheduled for deletion
|
|
956
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
957
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
Use the `--verbose` flag to see detailed information including the working directory folder name, folder ID, creation and update timestamps:
|
|
961
|
+
```bash
|
|
962
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910" --verbose
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
> [!NOTE]
|
|
966
|
+
> This command only works for jobs that were run with resumable mode enabled (using the `--resumable` flag). Jobs without resumable mode will not have a working directory to check.
|
|
967
|
+
> If the working directory has been completely deleted, the command will report that the working directory was not found.
|
|
968
|
+
|
|
898
969
|
#### List Jobs
|
|
899
970
|
|
|
900
971
|
You can get a summary of workspace jobs in two different formats:
|
|
@@ -729,6 +729,9 @@ def job_status(ctx,
|
|
|
729
729
|
@click.option('--session-id',
|
|
730
730
|
help='The specific CloudOS interactive session id. Required when using --link flag.',
|
|
731
731
|
required=False)
|
|
732
|
+
@click.option('--status',
|
|
733
|
+
help='Check the deletion status of the working directory.',
|
|
734
|
+
is_flag=True)
|
|
732
735
|
@click.option('--verbose',
|
|
733
736
|
help='Whether to print information messages or not.',
|
|
734
737
|
is_flag=True)
|
|
@@ -748,20 +751,101 @@ def job_workdir(ctx,
|
|
|
748
751
|
job_id,
|
|
749
752
|
link,
|
|
750
753
|
session_id,
|
|
754
|
+
status,
|
|
751
755
|
verbose,
|
|
752
756
|
disable_ssl_verification,
|
|
753
757
|
ssl_cert,
|
|
754
758
|
profile):
|
|
755
|
-
"""Get the path to the working directory of a specified job."""
|
|
759
|
+
"""Get the path to the working directory of a specified job or check deletion status."""
|
|
756
760
|
# apikey, cloudos_url, and workspace_id are now automatically resolved by the decorator
|
|
757
761
|
# session_id is also resolved if provided in profile
|
|
758
762
|
|
|
763
|
+
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
764
|
+
|
|
765
|
+
# Handle --status flag
|
|
766
|
+
if status:
|
|
767
|
+
console = Console()
|
|
768
|
+
|
|
769
|
+
if verbose:
|
|
770
|
+
console.print('[bold cyan]Checking deletion status of job working directory...[/bold cyan]')
|
|
771
|
+
console.print('\t[dim]...Preparing objects[/dim]')
|
|
772
|
+
console.print('\t[bold]Using the following parameters:[/bold]')
|
|
773
|
+
console.print(f'\t\t[cyan]CloudOS url:[/cyan] {cloudos_url}')
|
|
774
|
+
console.print(f'\t\t[cyan]Workspace ID:[/cyan] {workspace_id}')
|
|
775
|
+
console.print(f'\t\t[cyan]Job ID:[/cyan] {job_id}')
|
|
776
|
+
|
|
777
|
+
# Use Cloudos object to access the deletion status method
|
|
778
|
+
cl = Cloudos(cloudos_url, apikey, None)
|
|
779
|
+
|
|
780
|
+
if verbose:
|
|
781
|
+
console.print('\t[dim]The following Cloudos object was created:[/dim]')
|
|
782
|
+
console.print('\t' + str(cl) + '\n')
|
|
783
|
+
|
|
784
|
+
try:
|
|
785
|
+
deletion_status = cl.get_workdir_deletion_status(
|
|
786
|
+
job_id=job_id,
|
|
787
|
+
workspace_id=workspace_id,
|
|
788
|
+
verify=verify_ssl
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# Convert API status to user-friendly terminology with color
|
|
792
|
+
status_config = {
|
|
793
|
+
"ready": ("available", "green"),
|
|
794
|
+
"deleting": ("deleting", "yellow"),
|
|
795
|
+
"scheduledForDeletion": ("scheduled for deletion", "yellow"),
|
|
796
|
+
"deleted": ("deleted", "red"),
|
|
797
|
+
"failedToDelete": ("failed to delete", "red")
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
# Get the status of the workdir folder itself and convert it
|
|
801
|
+
api_status = deletion_status.get("status", "unknown")
|
|
802
|
+
folder_status, status_color = status_config.get(api_status, (api_status, "white"))
|
|
803
|
+
folder_info = deletion_status.get("items", {})
|
|
804
|
+
|
|
805
|
+
# Display results in a clear, styled format with human-readable sentence
|
|
806
|
+
console.print(f'The working directory of job [cyan]{deletion_status["job_id"]}[/cyan] is in status: [bold {status_color}]{folder_status}[/bold {status_color}]')
|
|
807
|
+
|
|
808
|
+
# For non-available statuses, always show update time and user info
|
|
809
|
+
if folder_status != "available":
|
|
810
|
+
if folder_info.get("updatedAt"):
|
|
811
|
+
console.print(f'[magenta]Status changed at:[/magenta] {folder_info.get("updatedAt")}')
|
|
812
|
+
|
|
813
|
+
# Show user information - prefer deletedBy over user field
|
|
814
|
+
user_info = folder_info.get("deletedBy") or folder_info.get("user", {})
|
|
815
|
+
if user_info:
|
|
816
|
+
user_name = f"{user_info.get('name', '')} {user_info.get('surname', '')}".strip()
|
|
817
|
+
user_email = user_info.get('email', '')
|
|
818
|
+
if user_name or user_email:
|
|
819
|
+
user_display = f'{user_name} ({user_email})' if user_name and user_email else (user_name or user_email)
|
|
820
|
+
console.print(f'[blue]User:[/blue] {user_display}')
|
|
821
|
+
|
|
822
|
+
# Display detailed information if verbose
|
|
823
|
+
if verbose:
|
|
824
|
+
console.print(f'\n[bold]Additional information:[/bold]')
|
|
825
|
+
console.print(f' [cyan]Job name:[/cyan] {deletion_status["job_name"]}')
|
|
826
|
+
console.print(f' [cyan]Working directory folder name:[/cyan] {deletion_status["workdir_folder_name"]}')
|
|
827
|
+
console.print(f' [cyan]Working directory folder ID:[/cyan] {deletion_status["workdir_folder_id"]}')
|
|
828
|
+
|
|
829
|
+
# Show folder metadata if available
|
|
830
|
+
if folder_info.get("createdAt"):
|
|
831
|
+
console.print(f' [cyan]Created at:[/cyan] {folder_info.get("createdAt")}')
|
|
832
|
+
if folder_info.get("updatedAt"):
|
|
833
|
+
console.print(f' [cyan]Updated at:[/cyan] {folder_info.get("updatedAt")}')
|
|
834
|
+
if folder_info.get("folderType"):
|
|
835
|
+
console.print(f' [cyan]Folder type:[/cyan] {folder_info.get("folderType")}')
|
|
836
|
+
|
|
837
|
+
except ValueError as e:
|
|
838
|
+
raise click.ClickException(str(e))
|
|
839
|
+
except Exception as e:
|
|
840
|
+
raise click.ClickException(f"Failed to retrieve deletion status: {str(e)}")
|
|
841
|
+
|
|
842
|
+
return
|
|
843
|
+
|
|
759
844
|
# Validate link flag requirements AFTER loading profile
|
|
760
845
|
if link and not session_id:
|
|
761
846
|
raise click.ClickException("--session-id is required when using --link flag")
|
|
762
847
|
|
|
763
848
|
print('Finding working directory path...')
|
|
764
|
-
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
765
849
|
if verbose:
|
|
766
850
|
print('\t...Preparing objects')
|
|
767
851
|
print('\tUsing the following parameters:')
|
|
@@ -932,6 +1016,9 @@ def job_logs(ctx,
|
|
|
932
1016
|
@click.option('--session-id',
|
|
933
1017
|
help='The specific CloudOS interactive session id. Required when using --link flag.',
|
|
934
1018
|
required=False)
|
|
1019
|
+
@click.option('--status',
|
|
1020
|
+
help='Check the deletion status of the job results.',
|
|
1021
|
+
is_flag=True)
|
|
935
1022
|
@click.option('--verbose',
|
|
936
1023
|
help='Whether to print information messages or not.',
|
|
937
1024
|
is_flag=True)
|
|
@@ -951,20 +1038,101 @@ def job_results(ctx,
|
|
|
951
1038
|
job_id,
|
|
952
1039
|
link,
|
|
953
1040
|
session_id,
|
|
1041
|
+
status,
|
|
954
1042
|
verbose,
|
|
955
1043
|
disable_ssl_verification,
|
|
956
1044
|
ssl_cert,
|
|
957
1045
|
profile):
|
|
958
|
-
"""Get the path to the results of a specified job."""
|
|
1046
|
+
"""Get the path to the results of a specified job or check deletion status."""
|
|
959
1047
|
# apikey, cloudos_url, and workspace_id are now automatically resolved by the decorator
|
|
960
1048
|
# session_id is also resolved if provided in profile
|
|
961
1049
|
|
|
1050
|
+
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
1051
|
+
|
|
1052
|
+
# Handle --status flag
|
|
1053
|
+
if status:
|
|
1054
|
+
console = Console()
|
|
1055
|
+
|
|
1056
|
+
if verbose:
|
|
1057
|
+
console.print('[bold cyan]Checking deletion status of job results...[/bold cyan]')
|
|
1058
|
+
console.print('\t[dim]...Preparing objects[/dim]')
|
|
1059
|
+
console.print('\t[bold]Using the following parameters:[/bold]')
|
|
1060
|
+
console.print(f'\t\t[cyan]CloudOS url:[/cyan] {cloudos_url}')
|
|
1061
|
+
console.print(f'\t\t[cyan]Workspace ID:[/cyan] {workspace_id}')
|
|
1062
|
+
console.print(f'\t\t[cyan]Job ID:[/cyan] {job_id}')
|
|
1063
|
+
|
|
1064
|
+
# Use Cloudos object to access the deletion status method
|
|
1065
|
+
cl = Cloudos(cloudos_url, apikey, None)
|
|
1066
|
+
|
|
1067
|
+
if verbose:
|
|
1068
|
+
console.print('\t[dim]The following Cloudos object was created:[/dim]')
|
|
1069
|
+
console.print('\t' + str(cl) + '\n')
|
|
1070
|
+
|
|
1071
|
+
try:
|
|
1072
|
+
deletion_status = cl.get_results_deletion_status(
|
|
1073
|
+
job_id=job_id,
|
|
1074
|
+
workspace_id=workspace_id,
|
|
1075
|
+
verify=verify_ssl
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# Convert API status to user-friendly terminology with color
|
|
1079
|
+
status_config = {
|
|
1080
|
+
"ready": ("available", "green"),
|
|
1081
|
+
"deleting": ("deleting", "yellow"),
|
|
1082
|
+
"scheduledForDeletion": ("scheduled for deletion", "yellow"),
|
|
1083
|
+
"deleted": ("deleted", "red"),
|
|
1084
|
+
"failedToDelete": ("failed to delete", "red")
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
# Get the status of the results folder itself and convert it
|
|
1088
|
+
api_status = deletion_status.get("status", "unknown")
|
|
1089
|
+
folder_status, status_color = status_config.get(api_status, (api_status, "white"))
|
|
1090
|
+
folder_info = deletion_status.get("items", {})
|
|
1091
|
+
|
|
1092
|
+
# Display results in a clear, styled format with human-readable sentence
|
|
1093
|
+
console.print(f'The results of job [cyan]{deletion_status["job_id"]}[/cyan] are in status: [bold {status_color}]{folder_status}[/bold {status_color}]')
|
|
1094
|
+
|
|
1095
|
+
# For non-available statuses, always show update time and user info
|
|
1096
|
+
if folder_status != "available":
|
|
1097
|
+
if folder_info.get("updatedAt"):
|
|
1098
|
+
console.print(f'[magenta]Status changed at:[/magenta] {folder_info.get("updatedAt")}')
|
|
1099
|
+
|
|
1100
|
+
# Show user information - prefer deletedBy over user field
|
|
1101
|
+
user_info = folder_info.get("deletedBy") or folder_info.get("user", {})
|
|
1102
|
+
if user_info:
|
|
1103
|
+
user_name = f"{user_info.get('name', '')} {user_info.get('surname', '')}".strip()
|
|
1104
|
+
user_email = user_info.get('email', '')
|
|
1105
|
+
if user_name or user_email:
|
|
1106
|
+
user_display = f'{user_name} ({user_email})' if user_name and user_email else (user_name or user_email)
|
|
1107
|
+
console.print(f'[blue]User:[/blue] {user_display}')
|
|
1108
|
+
|
|
1109
|
+
# Display detailed information if verbose
|
|
1110
|
+
if verbose:
|
|
1111
|
+
console.print(f'\n[bold]Additional information:[/bold]')
|
|
1112
|
+
console.print(f' [cyan]Job name:[/cyan] {deletion_status["job_name"]}')
|
|
1113
|
+
console.print(f' [cyan]Results folder name:[/cyan] {deletion_status["results_folder_name"]}')
|
|
1114
|
+
console.print(f' [cyan]Results folder ID:[/cyan] {deletion_status["results_folder_id"]}')
|
|
1115
|
+
|
|
1116
|
+
# Show folder metadata if available
|
|
1117
|
+
if folder_info.get("createdAt"):
|
|
1118
|
+
console.print(f' [cyan]Created at:[/cyan] {folder_info.get("createdAt")}')
|
|
1119
|
+
if folder_info.get("updatedAt"):
|
|
1120
|
+
console.print(f' [cyan]Updated at:[/cyan] {folder_info.get("updatedAt")}')
|
|
1121
|
+
if folder_info.get("folderType"):
|
|
1122
|
+
console.print(f' [cyan]Folder type:[/cyan] {folder_info.get("folderType")}')
|
|
1123
|
+
|
|
1124
|
+
except ValueError as e:
|
|
1125
|
+
raise click.ClickException(str(e))
|
|
1126
|
+
except Exception as e:
|
|
1127
|
+
raise click.ClickException(f"Failed to retrieve deletion status: {str(e)}")
|
|
1128
|
+
|
|
1129
|
+
return
|
|
1130
|
+
|
|
962
1131
|
# Validate link flag requirements AFTER loading profile
|
|
963
1132
|
if link and not session_id:
|
|
964
1133
|
raise click.ClickException("--session-id is required when using --link flag")
|
|
965
1134
|
|
|
966
1135
|
print('Executing results...')
|
|
967
|
-
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
968
1136
|
if verbose:
|
|
969
1137
|
print('\t...Preparing objects')
|
|
970
1138
|
print('\tUsing the following parameters:')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.69.1'
|
|
@@ -224,8 +224,56 @@ class Cloudos:
|
|
|
224
224
|
if "resumeWorkDir" not in r_json:
|
|
225
225
|
raise ValueError("Working directories are not available. This may be because the analysis was run without resumable mode enabled, or because intermediate results have since been removed.")
|
|
226
226
|
|
|
227
|
-
#
|
|
227
|
+
# Check if intermediate results have been deleted
|
|
228
|
+
# When intermediate results are deleted, resumeWorkDir becomes None but workDirectory still exists with folderId
|
|
228
229
|
resume_workdir_id = r_json.get("resumeWorkDir")
|
|
230
|
+
if resume_workdir_id is None and "workDirectory" in r_json:
|
|
231
|
+
work_directory = r_json["workDirectory"]
|
|
232
|
+
# If workDirectory has a folderId, it means the job was resumeable but intermediate results were deleted
|
|
233
|
+
if work_directory.get("folderId") is not None:
|
|
234
|
+
# Get the actual deletion status from the folders API
|
|
235
|
+
api_status = None
|
|
236
|
+
try:
|
|
237
|
+
folder_id = work_directory["folderId"]
|
|
238
|
+
folder_response = self.get_folder_deletion_status(folder_id, workspace_id, verify)
|
|
239
|
+
folder_data = json.loads(folder_response.content)
|
|
240
|
+
|
|
241
|
+
# If the API returns the folder, get its status
|
|
242
|
+
if folder_data and len(folder_data) > 0:
|
|
243
|
+
api_status = folder_data[0].get("status")
|
|
244
|
+
else:
|
|
245
|
+
# If the folder is not returned, check if deletedBy exists in workDirectory
|
|
246
|
+
if "deletedBy" in work_directory:
|
|
247
|
+
api_status = "scheduledForDeletion" # Assume scheduled for deletion
|
|
248
|
+
|
|
249
|
+
except Exception:
|
|
250
|
+
# If we can't get the status, check if deletedBy exists
|
|
251
|
+
if "deletedBy" in work_directory:
|
|
252
|
+
api_status = "scheduledForDeletion" # Assume scheduled for deletion
|
|
253
|
+
|
|
254
|
+
# Build contextually appropriate error message based on status
|
|
255
|
+
# Only raise error for non-ready statuses (ready means it's available, so no error)
|
|
256
|
+
if api_status == "deleting":
|
|
257
|
+
error_msg = "Intermediate job results are currently being deleted. The working directory is not accessible."
|
|
258
|
+
raise ValueError(error_msg)
|
|
259
|
+
elif api_status == "scheduledForDeletion":
|
|
260
|
+
error_msg = "Intermediate job results have been scheduled for deletion. The working directory is no longer available."
|
|
261
|
+
raise ValueError(error_msg)
|
|
262
|
+
elif api_status == "deleted":
|
|
263
|
+
error_msg = "Intermediate job results have been deleted. The working directory is no longer available."
|
|
264
|
+
raise ValueError(error_msg)
|
|
265
|
+
elif api_status == "failedToDelete":
|
|
266
|
+
error_msg = "Intermediate job results were marked for deletion but failed to delete. The working directory may not be accessible."
|
|
267
|
+
raise ValueError(error_msg)
|
|
268
|
+
elif api_status != "ready" and api_status is not None:
|
|
269
|
+
# For any other unknown status (not ready), raise generic error
|
|
270
|
+
error_msg = "Intermediate job results have been removed. The working directory is no longer available."
|
|
271
|
+
raise ValueError(error_msg)
|
|
272
|
+
# If status is "ready", set resume_workdir_id so we can retrieve the workdir path
|
|
273
|
+
elif api_status == "ready":
|
|
274
|
+
resume_workdir_id = folder_id
|
|
275
|
+
|
|
276
|
+
# If resumeWorkDir exists, use the folders API to get the shared working directory
|
|
229
277
|
if resume_workdir_id:
|
|
230
278
|
try:
|
|
231
279
|
# Use folders API to get the actual shared working directory
|
|
@@ -421,6 +469,53 @@ class Cloudos:
|
|
|
421
469
|
job_workspace = req_obj["team"]
|
|
422
470
|
if job_workspace != workspace_id:
|
|
423
471
|
raise ValueError("Workspace provided or configured is different from workspace where the job was executed")
|
|
472
|
+
|
|
473
|
+
# Check if analysis results have been deleted or scheduled for deletion
|
|
474
|
+
# Similar to workdir check - if analysisResults exists with folderId, check its status
|
|
475
|
+
if "analysisResults" in req_obj and req_obj.get("analysisResults"):
|
|
476
|
+
analysis_results = req_obj["analysisResults"]
|
|
477
|
+
results_folder_id = analysis_results.get("folderId")
|
|
478
|
+
|
|
479
|
+
if results_folder_id:
|
|
480
|
+
# Get the actual deletion status from the folders API
|
|
481
|
+
api_status = None
|
|
482
|
+
try:
|
|
483
|
+
folder_response = self.get_folder_deletion_status(results_folder_id, workspace_id, verify)
|
|
484
|
+
folder_data = json.loads(folder_response.content)
|
|
485
|
+
|
|
486
|
+
# If the API returns the folder, get its status
|
|
487
|
+
if folder_data and len(folder_data) > 0:
|
|
488
|
+
api_status = folder_data[0].get("status")
|
|
489
|
+
else:
|
|
490
|
+
# If the folder is not returned, check if deletedBy exists in analysisResults
|
|
491
|
+
if "deletedBy" in analysis_results:
|
|
492
|
+
api_status = "scheduledForDeletion" # Assume scheduled for deletion
|
|
493
|
+
|
|
494
|
+
except Exception:
|
|
495
|
+
# If we can't get the status, check if deletedBy exists
|
|
496
|
+
if "deletedBy" in analysis_results:
|
|
497
|
+
api_status = "scheduledForDeletion" # Assume scheduled for deletion
|
|
498
|
+
|
|
499
|
+
# Build contextually appropriate error message based on status
|
|
500
|
+
# Only raise error for non-ready statuses (ready means it's available, so no error)
|
|
501
|
+
if api_status == "deleting":
|
|
502
|
+
error_msg = "Analysis results are currently being deleted. The results folder is not accessible."
|
|
503
|
+
raise ValueError(error_msg)
|
|
504
|
+
elif api_status == "scheduledForDeletion":
|
|
505
|
+
error_msg = "Analysis results have been scheduled for deletion. The results folder is no longer available."
|
|
506
|
+
raise ValueError(error_msg)
|
|
507
|
+
elif api_status == "deleted":
|
|
508
|
+
error_msg = "Analysis results have been deleted. The results folder is no longer available."
|
|
509
|
+
raise ValueError(error_msg)
|
|
510
|
+
elif api_status == "failedToDelete":
|
|
511
|
+
error_msg = "Analysis results were marked for deletion but failed to delete. The results folder may not be accessible."
|
|
512
|
+
raise ValueError(error_msg)
|
|
513
|
+
elif api_status != "ready" and api_status is not None:
|
|
514
|
+
# For any other unknown status (not ready), raise generic error
|
|
515
|
+
error_msg = "Analysis results have been removed. The results folder is no longer available."
|
|
516
|
+
raise ValueError(error_msg)
|
|
517
|
+
# If status is "ready" or None, don't raise error - let the code continue to retrieve the results path
|
|
518
|
+
|
|
424
519
|
cloud_name, meta, cloud_storage = find_cloud(self.cloudos_url, self.apikey, workspace_id, req_obj["logs"])
|
|
425
520
|
# cont_name
|
|
426
521
|
results_obj = req_obj["results"]
|
|
@@ -439,6 +534,343 @@ class Cloudos:
|
|
|
439
534
|
results[filename] = f"{scheme}://{storage_account_prefix}{results_container}/{item['path']}"
|
|
440
535
|
return results
|
|
441
536
|
|
|
537
|
+
def get_folder_items_deletion_status(self, folder_id, workspace_id, verify=True):
|
|
538
|
+
"""Get deletion status of items within a folder.
|
|
539
|
+
|
|
540
|
+
Simple API wrapper to query the datasets API for items in a folder
|
|
541
|
+
with their deletion status (ready/deleting).
|
|
542
|
+
|
|
543
|
+
Parameters
|
|
544
|
+
----------
|
|
545
|
+
folder_id : str
|
|
546
|
+
The CloudOS folder ID.
|
|
547
|
+
workspace_id : str
|
|
548
|
+
The CloudOS workspace ID.
|
|
549
|
+
verify : [bool | str], optional
|
|
550
|
+
Whether to use SSL verification or not. Alternatively, if
|
|
551
|
+
a string is passed, it will be interpreted as the path to
|
|
552
|
+
the SSL certificate file. Default is True.
|
|
553
|
+
|
|
554
|
+
Returns
|
|
555
|
+
-------
|
|
556
|
+
Response
|
|
557
|
+
The API response containing folders and files with their status.
|
|
558
|
+
|
|
559
|
+
Raises
|
|
560
|
+
------
|
|
561
|
+
BadRequestException
|
|
562
|
+
If the request fails with a status code indicating an error.
|
|
563
|
+
"""
|
|
564
|
+
headers = {
|
|
565
|
+
"Content-type": "application/json",
|
|
566
|
+
"apikey": self.apikey
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# Query all possible deletion statuses
|
|
570
|
+
params = {
|
|
571
|
+
"status": ["ready", "deleted", "deleting", "scheduledForDeletion", "failedToDelete"],
|
|
572
|
+
"teamId": workspace_id
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
url = f"{self.cloudos_url}/api/v1/datasets/{folder_id}/items"
|
|
576
|
+
response = retry_requests_get(url, params=params, headers=headers, verify=verify)
|
|
577
|
+
|
|
578
|
+
if response.status_code >= 400:
|
|
579
|
+
raise BadRequestException(response)
|
|
580
|
+
|
|
581
|
+
return response
|
|
582
|
+
|
|
583
|
+
def get_results_deletion_status(self, job_id, workspace_id, verify=True):
|
|
584
|
+
"""Get the deletion status of a specific job's results folder.
|
|
585
|
+
|
|
586
|
+
This method orchestrates finding the job's results folder and retrieving
|
|
587
|
+
the deletion status of items within it.
|
|
588
|
+
|
|
589
|
+
Parameters
|
|
590
|
+
----------
|
|
591
|
+
job_id : str
|
|
592
|
+
The CloudOS job ID.
|
|
593
|
+
workspace_id : str
|
|
594
|
+
The CloudOS workspace ID.
|
|
595
|
+
verify : [bool | str], optional
|
|
596
|
+
Whether to use SSL verification or not. Alternatively, if
|
|
597
|
+
a string is passed, it will be interpreted as the path to
|
|
598
|
+
the SSL certificate file. Default is True.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
-------
|
|
602
|
+
dict
|
|
603
|
+
A dictionary containing the deletion status information with the following structure:
|
|
604
|
+
{
|
|
605
|
+
"job_id": str, # The job ID
|
|
606
|
+
"job_name": str, # The job name
|
|
607
|
+
"results_folder_id": str, # The ID of the job's results folder
|
|
608
|
+
"results_folder_name": str, # The name of the job's results folder
|
|
609
|
+
"items": dict # Dictionary with 'folders' and 'files' arrays containing items and their status
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
Raises
|
|
613
|
+
------
|
|
614
|
+
BadRequestException
|
|
615
|
+
If the request fails with a status code indicating an error.
|
|
616
|
+
ValueError
|
|
617
|
+
If the job's results folder is not found.
|
|
618
|
+
"""
|
|
619
|
+
# First, get job details to find the project and job name
|
|
620
|
+
job_status = self.get_job_status(job_id, workspace_id, verify)
|
|
621
|
+
job_data = json.loads(job_status.content)
|
|
622
|
+
job_name = job_data.get("name", job_id)
|
|
623
|
+
project_info = job_data.get("project")
|
|
624
|
+
|
|
625
|
+
# Extract deletedBy info from analysisResults if available
|
|
626
|
+
analysis_results_deleted_by = None
|
|
627
|
+
if "analysisResults" in job_data and job_data.get("analysisResults"):
|
|
628
|
+
analysis_results_deleted_by = job_data["analysisResults"].get("deletedBy")
|
|
629
|
+
|
|
630
|
+
if not project_info:
|
|
631
|
+
raise ValueError(f"Could not find project for job '{job_id}'")
|
|
632
|
+
|
|
633
|
+
# Extract project ID and name from the project info dict
|
|
634
|
+
project_id = project_info.get("_id")
|
|
635
|
+
project_name = project_info.get("name")
|
|
636
|
+
|
|
637
|
+
if not project_name or not project_id:
|
|
638
|
+
raise ValueError(f"Could not extract project information from job '{job_id}'")
|
|
639
|
+
|
|
640
|
+
from cloudos_cli.datasets.datasets import Datasets
|
|
641
|
+
|
|
642
|
+
# Create Datasets object to navigate to the Analysis Results folder
|
|
643
|
+
ds = Datasets(
|
|
644
|
+
cloudos_url=self.cloudos_url,
|
|
645
|
+
apikey=self.apikey,
|
|
646
|
+
cromwell_token=self.cromwell_token,
|
|
647
|
+
workspace_id=workspace_id,
|
|
648
|
+
project_name=project_name,
|
|
649
|
+
verify=verify
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
# Get project content to find Analysis Results folder
|
|
653
|
+
try:
|
|
654
|
+
project_content = ds.list_project_content()
|
|
655
|
+
except Exception as e:
|
|
656
|
+
raise ValueError(f"Failed to list project content for project '{project_name}': {str(e)}")
|
|
657
|
+
|
|
658
|
+
# Find the Analysis Results folder ID
|
|
659
|
+
analysis_results_id = None
|
|
660
|
+
for folder in project_content.get("folders", []):
|
|
661
|
+
if folder['name'] in ['Analyses Results', 'AnalysesResults']:
|
|
662
|
+
analysis_results_id = folder['_id']
|
|
663
|
+
break
|
|
664
|
+
|
|
665
|
+
if not analysis_results_id:
|
|
666
|
+
raise ValueError(f"Analyses Results folder not found in project '{project_name}'.")
|
|
667
|
+
|
|
668
|
+
# Get items in Analysis Results folder to find the job's specific results folder
|
|
669
|
+
# The Analysis Results folder contains folders for each job's results
|
|
670
|
+
try:
|
|
671
|
+
response = self.get_folder_items_deletion_status(analysis_results_id, workspace_id, verify)
|
|
672
|
+
content = json.loads(response.content)
|
|
673
|
+
except Exception as e:
|
|
674
|
+
raise ValueError(f"Failed to get items from Analyses Results folder: {str(e)}")
|
|
675
|
+
|
|
676
|
+
# The API response contains folders and files arrays
|
|
677
|
+
# Find the entry matching our job_id
|
|
678
|
+
job_status_info = None
|
|
679
|
+
|
|
680
|
+
# Check if it's a dict with folders/files arrays
|
|
681
|
+
if isinstance(content, dict):
|
|
682
|
+
# Check for 'folders' or 'files' keys (common dataset API response format)
|
|
683
|
+
items_to_search = []
|
|
684
|
+
if 'folders' in content:
|
|
685
|
+
items_to_search.extend(content['folders'])
|
|
686
|
+
if 'files' in content:
|
|
687
|
+
items_to_search.extend(content['files'])
|
|
688
|
+
|
|
689
|
+
# If no folders/files keys, treat dict values as items
|
|
690
|
+
if not items_to_search:
|
|
691
|
+
items_to_search = list(content.values())
|
|
692
|
+
|
|
693
|
+
for item in items_to_search:
|
|
694
|
+
if not isinstance(item, dict):
|
|
695
|
+
continue
|
|
696
|
+
|
|
697
|
+
item_name = item.get("name", "")
|
|
698
|
+
|
|
699
|
+
# Match by exact job ID in the item name (format: workflowname-jobid)
|
|
700
|
+
# The folder name should contain the exact job ID
|
|
701
|
+
if job_id in item_name:
|
|
702
|
+
job_status_info = item
|
|
703
|
+
break
|
|
704
|
+
elif isinstance(content, list):
|
|
705
|
+
for item in content:
|
|
706
|
+
if not isinstance(item, dict):
|
|
707
|
+
continue
|
|
708
|
+
|
|
709
|
+
item_name = item.get("name", "")
|
|
710
|
+
|
|
711
|
+
# Match by exact job ID in the item name
|
|
712
|
+
if job_id in item_name:
|
|
713
|
+
job_status_info = item
|
|
714
|
+
break
|
|
715
|
+
|
|
716
|
+
if not job_status_info:
|
|
717
|
+
raise ValueError(
|
|
718
|
+
f"Results folder for job '{job_name}' (ID: {job_id}) not found in Analyses Results.\n"
|
|
719
|
+
f"This may indicate that the results have been deleted or are scheduled for deletion."
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
# Merge the deletedBy info from job data with the folder info
|
|
723
|
+
# The deletedBy from analysisResults is more reliable than folder's user field
|
|
724
|
+
if analysis_results_deleted_by:
|
|
725
|
+
job_status_info["deletedBy"] = analysis_results_deleted_by
|
|
726
|
+
|
|
727
|
+
return {
|
|
728
|
+
"job_id": job_id,
|
|
729
|
+
"job_name": job_name,
|
|
730
|
+
"results_folder_id": job_status_info.get("_id"),
|
|
731
|
+
"results_folder_name": job_status_info.get("name"),
|
|
732
|
+
"status": job_status_info.get("status"),
|
|
733
|
+
"items": job_status_info
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
def get_folder_deletion_status(self, folder_id, workspace_id, verify=True):
|
|
737
|
+
"""Get deletion status of a specific folder by ID.
|
|
738
|
+
|
|
739
|
+
Simple API wrapper to query the folders API for a specific folder
|
|
740
|
+
with its deletion status (ready/deleting/etc).
|
|
741
|
+
|
|
742
|
+
Parameters
|
|
743
|
+
----------
|
|
744
|
+
folder_id : str
|
|
745
|
+
The CloudOS folder ID.
|
|
746
|
+
workspace_id : str
|
|
747
|
+
The CloudOS workspace ID.
|
|
748
|
+
verify : [bool | str], optional
|
|
749
|
+
Whether to use SSL verification or not. Alternatively, if
|
|
750
|
+
a string is passed, it will be interpreted as the path to
|
|
751
|
+
the SSL certificate file. Default is True.
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
Response
|
|
756
|
+
The API response containing folder information with status.
|
|
757
|
+
|
|
758
|
+
Raises
|
|
759
|
+
------
|
|
760
|
+
BadRequestException
|
|
761
|
+
If the request fails with a status code indicating an error.
|
|
762
|
+
"""
|
|
763
|
+
headers = {
|
|
764
|
+
"Content-type": "application/json",
|
|
765
|
+
"apikey": self.apikey
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
# Query with all possible deletion statuses
|
|
769
|
+
params = {
|
|
770
|
+
"id": folder_id,
|
|
771
|
+
"status": ["ready", "deleted", "deleting", "scheduledForDeletion", "failedToDelete"],
|
|
772
|
+
"teamId": workspace_id
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
url = f"{self.cloudos_url}/api/v1/folders/"
|
|
776
|
+
response = retry_requests_get(url, params=params, headers=headers, verify=verify)
|
|
777
|
+
|
|
778
|
+
if response.status_code >= 400:
|
|
779
|
+
raise BadRequestException(response)
|
|
780
|
+
|
|
781
|
+
return response
|
|
782
|
+
|
|
783
|
+
def get_workdir_deletion_status(self, job_id, workspace_id, verify=True):
|
|
784
|
+
"""Get the deletion status of a specific job's working directory.
|
|
785
|
+
|
|
786
|
+
This method retrieves the deletion status of the job's working directory
|
|
787
|
+
using the folders API endpoint.
|
|
788
|
+
|
|
789
|
+
Parameters
|
|
790
|
+
----------
|
|
791
|
+
job_id : str
|
|
792
|
+
The CloudOS job ID.
|
|
793
|
+
workspace_id : str
|
|
794
|
+
The CloudOS workspace ID.
|
|
795
|
+
verify : [bool | str], optional
|
|
796
|
+
Whether to use SSL verification or not. Alternatively, if
|
|
797
|
+
a string is passed, it will be interpreted as the path to
|
|
798
|
+
the SSL certificate file. Default is True.
|
|
799
|
+
|
|
800
|
+
Returns
|
|
801
|
+
-------
|
|
802
|
+
dict
|
|
803
|
+
A dictionary containing the deletion status information with the following structure:
|
|
804
|
+
{
|
|
805
|
+
"job_id": str, # The job ID
|
|
806
|
+
"job_name": str, # The job name
|
|
807
|
+
"workdir_folder_id": str, # The ID of the job's working directory folder
|
|
808
|
+
"workdir_folder_name": str, # The name of the job's working directory folder
|
|
809
|
+
"status": str, # The deletion status
|
|
810
|
+
"items": dict # Full folder object with metadata
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
Raises
|
|
814
|
+
------
|
|
815
|
+
BadRequestException
|
|
816
|
+
If the request fails with a status code indicating an error.
|
|
817
|
+
ValueError
|
|
818
|
+
If the job's working directory is not found or not accessible.
|
|
819
|
+
"""
|
|
820
|
+
# First, get job details to find the working directory folder ID
|
|
821
|
+
job_status = self.get_job_status(job_id, workspace_id, verify)
|
|
822
|
+
job_data = json.loads(job_status.content)
|
|
823
|
+
job_name = job_data.get("name", job_id)
|
|
824
|
+
|
|
825
|
+
# Try to get the workdir folder ID from workDirectory.folderId first (new format)
|
|
826
|
+
# If not available, fall back to resumeWorkDir (old format)
|
|
827
|
+
workdir_folder_id = None
|
|
828
|
+
workdir_deleted_by = None
|
|
829
|
+
|
|
830
|
+
if "workDirectory" in job_data and job_data.get("workDirectory"):
|
|
831
|
+
workdir_folder_id = job_data["workDirectory"].get("folderId")
|
|
832
|
+
# Get deletedBy info if available (contains user who scheduled deletion)
|
|
833
|
+
workdir_deleted_by = job_data["workDirectory"].get("deletedBy")
|
|
834
|
+
|
|
835
|
+
if not workdir_folder_id and "resumeWorkDir" in job_data:
|
|
836
|
+
workdir_folder_id = job_data.get("resumeWorkDir")
|
|
837
|
+
|
|
838
|
+
if not workdir_folder_id:
|
|
839
|
+
raise ValueError(
|
|
840
|
+
"Working directory is not available for this job. "
|
|
841
|
+
"This may be because the analysis was run without resumable mode enabled, "
|
|
842
|
+
"or because intermediate results have been removed."
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
# Use the folders API to get the working directory status
|
|
846
|
+
response = self.get_folder_deletion_status(workdir_folder_id, workspace_id, verify)
|
|
847
|
+
|
|
848
|
+
# Parse the response
|
|
849
|
+
content = json.loads(response.content)
|
|
850
|
+
|
|
851
|
+
# The API returns an array with the folder info
|
|
852
|
+
if not content or len(content) == 0:
|
|
853
|
+
raise ValueError(
|
|
854
|
+
f"Working directory for job '{job_name}' (ID: {job_id}) not found.\n"
|
|
855
|
+
f"This may indicate that the working directory has been deleted or is scheduled for deletion."
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
workdir_info = content[0] # Get the first (and should be only) result
|
|
859
|
+
|
|
860
|
+
# Merge the deletedBy info from job data with the folder info
|
|
861
|
+
# The deletedBy from workDirectory is more reliable than folder's user field
|
|
862
|
+
if workdir_deleted_by:
|
|
863
|
+
workdir_info["deletedBy"] = workdir_deleted_by
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
"job_id": job_id,
|
|
867
|
+
"job_name": job_name,
|
|
868
|
+
"workdir_folder_id": workdir_info.get("_id"),
|
|
869
|
+
"workdir_folder_name": workdir_info.get("name"),
|
|
870
|
+
"status": workdir_info.get("status"),
|
|
871
|
+
"items": workdir_info
|
|
872
|
+
}
|
|
873
|
+
|
|
442
874
|
def _create_cromwell_header(self):
|
|
443
875
|
"""Generates cromwell header.
|
|
444
876
|
|
|
@@ -4,14 +4,13 @@ from cloudos_cli.utils.errors import BadRequestException, AccountNotLinkedExcept
|
|
|
4
4
|
from cloudos_cli.utils.requests import retry_requests_post, retry_requests_get
|
|
5
5
|
import json
|
|
6
6
|
from requests.exceptions import RetryError
|
|
7
|
-
import sys
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class WFImport(ABC):
|
|
11
10
|
def __init__(self, cloudos_url, cloudos_apikey, workspace_id, platform,
|
|
12
11
|
workflow_name, workflow_url, workflow_docs_link="", workflow_description="", cost_limit=30, main_file=None, verify=True):
|
|
13
12
|
self.cloudos_url = cloudos_url
|
|
14
|
-
self.workflow_url = workflow_url.
|
|
13
|
+
self.workflow_url = workflow_url.removesuffix('.git')
|
|
15
14
|
self.workspace_id = workspace_id
|
|
16
15
|
self.platform = platform
|
|
17
16
|
self.parsed_url = urlsplit(self.workflow_url)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.69.1
|
|
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
|
|
@@ -669,6 +669,41 @@ You can also link all result directories to an interactive session using the `--
|
|
|
669
669
|
cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
|
|
670
670
|
```
|
|
671
671
|
|
|
672
|
+
**Check Results Deletion Status**
|
|
673
|
+
|
|
674
|
+
You can check the deletion status of a job's results folder using the `--status` flag. This is useful for monitoring the deletion lifecycle of analysis results.
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
cloudos job results --status --profile my_profile --job-id "12345678910"
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
The command will display the current status of the results folder. Possible statuses include:
|
|
681
|
+
- **available**: Results are available and accessible
|
|
682
|
+
- **scheduled for deletion**: Results are scheduled to be deleted
|
|
683
|
+
- **deleting**: Results are currently being deleted
|
|
684
|
+
- **deleted**: Results have been deleted
|
|
685
|
+
- **failed to delete**: Deletion process failed
|
|
686
|
+
|
|
687
|
+
Example output for available results:
|
|
688
|
+
```console
|
|
689
|
+
The results of job 1234567890 are in status: available
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
For results in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
693
|
+
```console
|
|
694
|
+
The results of job 6912036aa6ed001148c96018 are in status: scheduled for deletion
|
|
695
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
696
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Use the `--verbose` flag to see detailed information including the results folder name, folder ID, creation and update timestamps:
|
|
700
|
+
```bash
|
|
701
|
+
cloudos job results --status --profile my_profile --job-id "12345678910" --verbose
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
> [!NOTE]
|
|
705
|
+
> If results have been completely deleted, the command will report that the results folder was not found, which may indicate that results have been deleted or scheduled for deletion.
|
|
706
|
+
|
|
672
707
|
|
|
673
708
|
#### Clone or Resume job
|
|
674
709
|
|
|
@@ -930,6 +965,42 @@ cloudos job workdir \
|
|
|
930
965
|
--link --session-id your_session_id
|
|
931
966
|
```
|
|
932
967
|
|
|
968
|
+
**Check Working Directory Deletion Status**
|
|
969
|
+
|
|
970
|
+
You can check the deletion status of a job's working directory using the `--status` flag. This is useful for monitoring the deletion lifecycle of intermediate job files.
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910"
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
The command will display the current status of the working directory folder. Possible statuses include:
|
|
977
|
+
- **available**: Working directory is available and accessible
|
|
978
|
+
- **scheduled for deletion**: Working directory is scheduled to be deleted
|
|
979
|
+
- **deleting**: Working directory is currently being deleted
|
|
980
|
+
- **deleted**: Working directory has been deleted
|
|
981
|
+
- **failed to delete**: Deletion process failed
|
|
982
|
+
|
|
983
|
+
Example output for available working directory:
|
|
984
|
+
```console
|
|
985
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: available
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
For working directories in any state other than available, the output includes additional information about when the status changed and who initiated the change:
|
|
989
|
+
```console
|
|
990
|
+
The working directory of job 6912036aa6ed001148c96018 is in status: scheduled for deletion
|
|
991
|
+
Status changed at: 2025-11-11T14:43:44.416Z
|
|
992
|
+
User: Leila Mansouri (leila.mansouri@lifebit.ai)
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
Use the `--verbose` flag to see detailed information including the working directory folder name, folder ID, creation and update timestamps:
|
|
996
|
+
```bash
|
|
997
|
+
cloudos job workdir --status --profile my_profile --job-id "12345678910" --verbose
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
> [!NOTE]
|
|
1001
|
+
> This command only works for jobs that were run with resumable mode enabled (using the `--resumable` flag). Jobs without resumable mode will not have a working directory to check.
|
|
1002
|
+
> If the working directory has been completely deleted, the command will report that the working directory was not found.
|
|
1003
|
+
|
|
933
1004
|
#### List Jobs
|
|
934
1005
|
|
|
935
1006
|
You can get a summary of workspace jobs in two different formats:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.68.0'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudos_cli-2.68.0 → cloudos_cli-2.69.1}/tests/test_related_analyses/test_related_analyses.py
RENAMED
|
File without changes
|