terralab-cli 3.2.1__tar.gz → 3.2.3__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 (27) hide show
  1. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/PKG-INFO +3 -3
  2. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/pyproject.toml +3 -3
  3. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/cli.py +4 -0
  4. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/pipeline_runs_commands.py +41 -1
  5. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/log.py +1 -1
  6. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/pipeline_runs_logic.py +19 -0
  7. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/utils.py +17 -1
  8. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/LICENSE +0 -0
  9. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/README.md +0 -0
  10. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/.terralab-cli-config +0 -0
  11. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/__init__.py +0 -0
  12. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/auth_helper.py +0 -0
  13. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/client.py +0 -0
  14. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/__init__.py +0 -0
  15. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/account_commands.py +0 -0
  16. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/auth_commands.py +0 -0
  17. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/pipelines_commands.py +0 -0
  18. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/commands/quotas_commands.py +0 -0
  19. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/config.py +0 -0
  20. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/constants.py +0 -0
  21. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/__init__.py +0 -0
  22. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/account_logic.py +0 -0
  23. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/auth_logic.py +0 -0
  24. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/pipelines_logic.py +0 -0
  25. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/logic/quotas_logic.py +0 -0
  26. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/sam_helper.py +0 -0
  27. {terralab_cli-3.2.1 → terralab_cli-3.2.3}/terralab/version_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: terralab-cli
3
- Version: 3.2.1
3
+ Version: 3.2.3
4
4
  Summary: Command line interface for interacting with the Terra Scientific Pipelines Service, or Teaspoons.
5
5
  License-File: LICENSE
6
6
  Author: Terra Scientific Services
@@ -13,14 +13,14 @@ Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Dist: PyJWT (>=2.9.0,<3.0.0)
14
14
  Requires-Dist: click (>=8.1.7,<9.0.0)
15
15
  Requires-Dist: colorlog (>=6.9.0,<7.0.0)
16
- Requires-Dist: cryptography (>=44.0.1,<47.0.0)
16
+ Requires-Dist: cryptography (>=44.0.1,<48.0.0)
17
17
  Requires-Dist: oauth2-cli-auth (>=1.5,<3.0)
18
18
  Requires-Dist: poetry (==2.3.4)
19
19
  Requires-Dist: prompt-toolkit (>=3.0.51,<4.0.0)
20
20
  Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
21
21
  Requires-Dist: requests (>=2.32.3,<3.0.0)
22
22
  Requires-Dist: tabulate (>=0.9,<0.11)
23
- Requires-Dist: terra-scientific-pipelines-service-api-client (==2.3.16)
23
+ Requires-Dist: terra-scientific-pipelines-service-api-client (==2.4.2)
24
24
  Requires-Dist: tqdm (>=4.67.0,<5.0.0)
25
25
  Requires-Dist: tzlocal (>=5.2,<6.0)
26
26
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "terralab-cli"
3
3
  # version is auto-incremented. do not edit.
4
- version = "3.2.1"
4
+ version = "3.2.3"
5
5
  description = "Command line interface for interacting with the Terra Scientific Pipelines Service, or Teaspoons."
6
6
  authors = ["Terra Scientific Services <teaspoons-developers@broadinstitute.org>"]
7
7
  readme = "README.md"
@@ -14,7 +14,7 @@ terralab = "terralab.cli:cli"
14
14
 
15
15
  [tool.poetry.dependencies]
16
16
  python = "^3.12"
17
- terra-scientific-pipelines-service-api-client = "2.3.16"
17
+ terra-scientific-pipelines-service-api-client = "2.4.2"
18
18
  python-dotenv = "^1.0.1"
19
19
  click = "^8.1.7"
20
20
  colorlog = "^6.9.0"
@@ -25,7 +25,7 @@ tqdm = "^4.67.0"
25
25
  tzlocal = "^5.2"
26
26
  tabulate = ">=0.9,<0.11"
27
27
  poetry = "2.3.4"
28
- cryptography = ">=44.0.1,<47.0.0"
28
+ cryptography = ">=44.0.1,<48.0.0"
29
29
  prompt-toolkit = "^3.0.51"
30
30
 
31
31
  [tool.poetry.group.dev.dependencies]
@@ -13,6 +13,7 @@ from terralab.commands.auth_commands import logout, login_with_oauth, login
13
13
  from terralab.commands.pipeline_runs_commands import (
14
14
  submit,
15
15
  download,
16
+ deliver,
16
17
  jobs,
17
18
  details as details_jobs,
18
19
  list_command as list_jobs,
@@ -74,6 +75,9 @@ def cli(debug: bool) -> None:
74
75
  cli.add_command(submit)
75
76
  cli.add_command(download)
76
77
 
78
+ # deliver
79
+ cli.add_command(deliver)
80
+
77
81
  # jobs
78
82
  cli.add_command(jobs)
79
83
  cli.add_command(list_jobs, name=" jobs list")
@@ -5,7 +5,7 @@ from typing import Any
5
5
  import uuid
6
6
 
7
7
  import click
8
- from teaspoons_client import AsyncPipelineRunResponseV2, PipelineRun # type: ignore[attr-defined]
8
+ from teaspoons_client import AsyncPipelineRunResponseV2, DataDeliveryReport, PipelineRun # type: ignore[attr-defined]
9
9
 
10
10
  from terralab.constants import FAILED_KEY, SUPPORT_EMAIL_TEXT, SUCCEEDED_KEY
11
11
  from terralab.log import (
@@ -19,6 +19,7 @@ from terralab.utils import (
19
19
  convert_file_size_to_human_readable,
20
20
  handle_api_exceptions,
21
21
  process_inputs_to_dict,
22
+ validate_gcs_path,
22
23
  validate_job_id,
23
24
  format_timestamp,
24
25
  )
@@ -151,6 +152,8 @@ def details(job_id: str) -> None:
151
152
  )
152
153
  )
153
154
 
155
+ display_data_delivery(response.pipeline_run_report.data_delivery_report)
156
+
154
157
 
155
158
  def display_outputs(outputs: dict[str, dict[str, Any]] | None) -> None:
156
159
  if not outputs:
@@ -178,6 +181,14 @@ def display_outputs(outputs: dict[str, dict[str, Any]] | None) -> None:
178
181
  )
179
182
 
180
183
 
184
+ def display_data_delivery(data_delivery_report: DataDeliveryReport | None) -> None:
185
+ if not data_delivery_report:
186
+ return
187
+ LOGGER.info(add_blankline_before("Data Delivery:"))
188
+ LOGGER.info(indented(f"Status: {format_status(data_delivery_report.status)}"))
189
+ LOGGER.info(indented(f"Destination: {data_delivery_report.destination}"))
190
+
191
+
181
192
  @jobs.command(name="list", short_help="List your jobs")
182
193
  @click.option(
183
194
  "--num_results",
@@ -213,3 +224,32 @@ def list_command(num_results: int) -> None:
213
224
  )
214
225
 
215
226
  LOGGER.info(format_table_with_status(row_list))
227
+
228
+
229
+ # DELIVER group
230
+
231
+
232
+ @click.command(short_help="Deliver output files from a job to a cloud destination")
233
+ @click.argument("job_id", type=str)
234
+ @click.argument("destination", type=str)
235
+ @handle_api_exceptions
236
+ def deliver(job_id: str, destination: str) -> None:
237
+ """Deliver output files from a job with JOB_ID identifier to a DESTINATION GCS path.
238
+
239
+ Note: delivering your data to a cloud destination will disable downloading locally.
240
+ """
241
+ job_id_uuid: uuid.UUID = validate_job_id(job_id)
242
+ validated_destination: str = validate_gcs_path(destination)
243
+
244
+ pipeline_runs_logic.deliver_pipeline_run_to_cloud(
245
+ job_id_uuid, validated_destination
246
+ )
247
+
248
+ LOGGER.info(
249
+ f"Successfully initiated data delivery for job {job_id} to {validated_destination}. Delivery may take a few minutes to complete."
250
+ )
251
+ LOGGER.info(
252
+ add_blankline_before(
253
+ f"You can check the status of the delivery with `terralab jobs details {job_id}`."
254
+ )
255
+ )
@@ -124,7 +124,7 @@ def format_status_in_table_row(
124
124
 
125
125
 
126
126
  def format_status(status_str: str) -> str:
127
- return COLORFUL_STATUS[status_str]
127
+ return COLORFUL_STATUS.get(status_str, status_str)
128
128
 
129
129
 
130
130
  # This filter makes the retry messages from urllib3 more user-friendly.
@@ -7,11 +7,13 @@ from typing import Any
7
7
  from teaspoons_client import ( # type: ignore[attr-defined]
8
8
  AsyncPipelineRunResponseV2,
9
9
  JobControl,
10
+ JobReport,
10
11
  PipelineRun,
11
12
  PipelineRunOutputSignedUrlsResponse,
12
13
  PipelineRunsApi,
13
14
  PreparePipelineRunRequestBody,
14
15
  PreparePipelineRunResponseV2,
16
+ StartDataDeliveryRequestBody,
15
17
  StartPipelineRunRequestBody,
16
18
  )
17
19
 
@@ -167,6 +169,23 @@ def prepare_upload_start_pipeline_run(
167
169
  return start_pipeline_run(job_id)
168
170
 
169
171
 
172
+ ## deliver action
173
+
174
+
175
+ def deliver_pipeline_run_to_cloud(
176
+ job_id: uuid.UUID, destination_gcs_path: str
177
+ ) -> JobReport:
178
+ """Call the deliverPipelineRunOutputFilesToCloud Teaspoons endpoint and return the JobReport."""
179
+ start_data_delivery_request_body = StartDataDeliveryRequestBody(
180
+ destinationGcsPath=destination_gcs_path
181
+ )
182
+ with ClientWrapper() as api_client:
183
+ pipeline_runs_client = PipelineRunsApi(api_client=api_client)
184
+ return pipeline_runs_client.deliver_pipeline_run_output_files_to_cloud(
185
+ str(job_id), start_data_delivery_request_body
186
+ )
187
+
188
+
170
189
  ## download action
171
190
 
172
191
 
@@ -17,7 +17,11 @@ from tqdm import tqdm
17
17
  from tqdm.contrib.logging import logging_redirect_tqdm
18
18
  from urllib3.exceptions import MaxRetryError
19
19
 
20
- from terralab.constants import MAX_FILE_UPLOAD_SIZE_BYTES, SUPPORT_EMAIL_TEXT
20
+ from terralab.constants import (
21
+ MAX_FILE_UPLOAD_SIZE_BYTES,
22
+ SUPPORT_EMAIL_TEXT,
23
+ GCS_PREFIX,
24
+ )
21
25
  from terralab.log import add_blankline_before
22
26
 
23
27
  LOGGER = logging.getLogger(__name__)
@@ -284,6 +288,18 @@ def validate_job_id(job_id: str) -> uuid.UUID:
284
288
  exit(1)
285
289
 
286
290
 
291
+ def validate_gcs_path(gcs_path: str) -> str:
292
+ """Validates that the provided string is a valid GCS path (gs://bucket/...).
293
+
294
+ Returns the path if valid, otherwise logs an error and exits."""
295
+ if not gcs_path.startswith(GCS_PREFIX) or len(gcs_path) <= len(GCS_PREFIX):
296
+ LOGGER.error(
297
+ f"Error: '{gcs_path}' is not a valid GCS path. GCS paths must start with '{GCS_PREFIX}' followed by a bucket name."
298
+ )
299
+ exit(1)
300
+ return gcs_path
301
+
302
+
287
303
  def format_timestamp(
288
304
  timestamp_string: str | None, timestamp_format: str = "%Y-%m-%d %H:%M"
289
305
  ) -> str:
File without changes
File without changes