Skip to content

Commit 150f438

Browse files
Merge pull request #864 from planetlabs/data-asset-download-507
Implement CLI commands: planet data asset-get, download, activate, wait
2 parents c7721cc + ee268a7 commit 150f438

File tree

4 files changed

+414
-15
lines changed

4 files changed

+414
-15
lines changed

docs/cli/cli-data.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,25 @@ that are of standard (aka not test) quality. Therefore, these filters can be eas
326326
planet data filter --permission --std-quality --asset ortho_analytic_8b_sr | planet data search PSScene --filter -
327327
```
328328

329+
## `data asset` command basics
330+
331+
To activate an asset for download three commands must be queried, in sequence:
332+
1. `asset-activate` - activate an asset
333+
2. `asset-wait` - wait for an asset to be activated
334+
3. `asset-download` - download an activated asset
335+
336+
For example, if we want to download a `basic_udm2` asset from item ID
337+
`20221003_002705_38_2461`, a `PSScene` item type:
338+
339+
```
340+
planet data asset-activate PSScene 20221003_002705_38_2461 basic_udm2 && \
341+
planet data asset-wait PSScene 20221003_002705_38_2461 basic_udm2 && \
342+
planet data asset-download PSScene 20221003_002705_38_2461 basic_udm2 --directory /path/to/data/
343+
00:00 - order my asset - state: active
344+
{'_links': {'_self': 'https://api.planet.com/data/v1/assets/eyJpIjogIjIwMjIxMDAzXzAwMjcwNV8zOF8yNDYxIiwgImMiOiAiUFNTY2VuZSIsICJ0IjogImJhc2ljX3VkbTIiLCAiY3QiOiAiaXRlbS10eXBlIn0', 'activate': 'https://api.planet.com/data/v1/assets/eyJpIjogIjIwMjIxMDAzXzAwMjcwNV8zOF8yNDYxIiwgImMiOiAiUFNTY2VuZSIsICJ0IjogImJhc2ljX3VkbTIiLCAiY3QiOiAiaXRlbS10eXBlIn0/activate', 'type': 'https://api.planet.com/data/v1/asset-types/basic_udm2'}, '_permissions': ['download'], 'expires_at': '2023-03-02T19:30:48.942718', 'location': 'https://api.planet.com/data/v1/download?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJQYWtGNHZuUEs3WXRmSFNGUklHY2I3YTNXT3piaTlaam4zWUpZMmxnd0x5cVlFMVBRSHU5QXNCcjR5Q3FxSjBNbl9yN3VwVEFQYUI1ZzhYNUJmcDhmUT09IiwiZXhwIjoxNjc3Nzg1NDQ4LCJ0b2tlbl90eXBlIjoidHlwZWQtaXRlbSIsIml0ZW1fdHlwZV9pZCI6IlBTU2NlbmUiLCJpdGVtX2lkIjoiMjAyMjEwMDNfMDAyNzA1XzM4XzI0NjEiLCJhc3NldF90eXBlIjoiYmFzaWNfdWRtMiJ9.Dd0opDjW3bBS6qLLZoNiJkfBsO2n5Xz9pM5apEUz_K6viDPFexhJiy6bMbaySbby8W0YvuATdb1uYXS2FkweDg', 'md5_digest': '3a9f7dd1ce500f699d0a96afdd0e3aa2', 'status': 'active', 'type': 'basic_udm2'}
345+
/path/to/data/20221003_002705_38_2461_1A_udm2.tif: 100%|██████████████████████████████████| 3.16k/3.16k [00:00<00:00, 32.0MB/s]
346+
```
347+
329348
## Stats
330349

331350
TODO

planet/cli/data.py

Lines changed: 141 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@
1414
"""The Planet Data CLI."""
1515
from typing import List, Optional
1616
from contextlib import asynccontextmanager
17+
from pathlib import Path
1718

1819
import click
1920

21+
from planet.reporting import StateBar
2022
from planet import data_filter, DataClient, exceptions
21-
from planet.clients.data import (LIST_SEARCH_TYPE,
23+
from planet.clients.data import (SEARCH_SORT,
24+
LIST_SEARCH_TYPE,
2225
LIST_SEARCH_TYPE_DEFAULT,
2326
LIST_SORT_ORDER,
2427
LIST_SORT_DEFAULT,
25-
SEARCH_SORT,
2628
SEARCH_SORT_DEFAULT,
2729
STATS_INTERVAL)
30+
2831
from planet.specs import (get_item_types,
2932
validate_item_type,
3033
SpecificationException)
@@ -68,8 +71,8 @@ def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]:
6871

6972

7073
def check_item_types(ctx, param, item_types) -> Optional[List[dict]]:
71-
'''Validates the item type by comparing the inputted item type to all
72-
supported item types.'''
74+
'''Validates each item types provided by comparing them to all supported
75+
item types.'''
7376
try:
7477
for item_type in item_types:
7578
validate_item_type(item_type)
@@ -78,6 +81,17 @@ def check_item_types(ctx, param, item_types) -> Optional[List[dict]]:
7881
raise click.BadParameter(str(e))
7982

8083

84+
def check_item_type(ctx, param, item_type) -> Optional[List[dict]]:
85+
'''Validates the item type provided by comparing it to all supported
86+
item types.'''
87+
try:
88+
validate_item_type(item_type)
89+
except SpecificationException as e:
90+
raise click.BadParameter(str(e))
91+
92+
return item_type
93+
94+
8195
def check_search_id(ctx, param, search_id) -> str:
8296
'''Ensure search id is a valix hex string'''
8397
try:
@@ -480,8 +494,128 @@ async def search_update(ctx,
480494
echo_json(items, pretty)
481495

482496

497+
@data.command()
498+
@click.pass_context
499+
@translate_exceptions
500+
@coro
501+
@click.argument("item_type", type=str, callback=check_item_type)
502+
@click.argument("item_id")
503+
@click.argument("asset_type")
504+
@click.option('--directory',
505+
default='.',
506+
help=('Base directory for file download.'),
507+
type=click.Path(exists=True,
508+
resolve_path=True,
509+
writable=True,
510+
file_okay=False))
511+
@click.option('--filename',
512+
default=None,
513+
help=('Custom name to assign to downloaded file.'),
514+
type=str)
515+
@click.option('--overwrite',
516+
is_flag=True,
517+
default=False,
518+
help=('Overwrite files if they already exist.'))
519+
@click.option('--checksum',
520+
is_flag=True,
521+
default=None,
522+
help=('Verify that checksums match.'))
523+
async def asset_download(ctx,
524+
item_type,
525+
item_id,
526+
asset_type,
527+
directory,
528+
filename,
529+
overwrite,
530+
checksum):
531+
"""Download an activated asset.
532+
533+
This function will fail if the asset state is not activated. Consider
534+
calling `asset-wait` before this command to ensure the asset is activated.
535+
536+
If --checksum is provided, the associated checksums given in the manifest
537+
are compared against the downloaded files to verify that they match.
538+
539+
If --checksum is provided, files are already downloaded, and --overwrite is
540+
not specified, this will simply validate the checksums of the files against
541+
the manifest.
542+
543+
Output:
544+
The full path of the downloaded file. If the quiet flag is not set, this
545+
also provides ANSI download status reporting.
546+
"""
547+
quiet = ctx.obj['QUIET']
548+
async with data_client(ctx) as cl:
549+
asset = await cl.get_asset(item_type, item_id, asset_type)
550+
path = await cl.download_asset(asset=asset,
551+
filename=filename,
552+
directory=Path(directory),
553+
overwrite=overwrite,
554+
progress_bar=not quiet)
555+
if checksum:
556+
cl.validate_checksum(asset, path)
557+
558+
559+
@data.command()
560+
@click.pass_context
561+
@translate_exceptions
562+
@coro
563+
@click.argument("item_type", type=str, callback=check_item_type)
564+
@click.argument("item_id")
565+
@click.argument("asset_type")
566+
async def asset_activate(ctx, item_type, item_id, asset_type):
567+
'''Activate an asset.'''
568+
async with data_client(ctx) as cl:
569+
asset = await cl.get_asset(item_type, item_id, asset_type)
570+
await cl.activate_asset(asset)
571+
572+
573+
@data.command()
574+
@click.pass_context
575+
@translate_exceptions
576+
@coro
577+
@click.argument("item_type", type=str, callback=check_item_type)
578+
@click.argument("item_id")
579+
@click.argument("asset_type")
580+
@click.option('--delay',
581+
type=int,
582+
default=5,
583+
help='Time (in seconds) between polls.')
584+
@click.option('--max-attempts',
585+
type=int,
586+
default=200,
587+
show_default=True,
588+
help='Maximum number of polls. Set to zero for no limit.')
589+
async def asset_wait(ctx, item_type, item_id, asset_type, delay, max_attempts):
590+
'''Wait for an asset to be activated.
591+
592+
Returns when the asset state has reached "activated" and the asset is
593+
available.
594+
'''
595+
quiet = ctx.obj['QUIET']
596+
async with data_client(ctx) as cl:
597+
asset = await cl.get_asset(item_type, item_id, asset_type)
598+
with StateBar(order_id="my asset", disable=quiet) as bar:
599+
state = await cl.wait_asset(asset,
600+
delay,
601+
max_attempts,
602+
callback=bar.update_state)
603+
click.echo(state)
604+
605+
606+
# @data.command()
607+
# @click.pass_context
608+
# @translate_exceptions
609+
# @coro
610+
# @click.argument("item_type")
611+
# @click.argument("item_id")
612+
# @click.argument("asset_type_id")
613+
# @pretty
614+
# async def asset_get(ctx, item_type, item_id, asset_type_id, pretty):
615+
# '''Get an item asset.'''
616+
# async with data_client(ctx) as cl:
617+
# asset = await cl.get_asset(item_type, item_id, asset_type_id)
618+
# echo_json(asset, pretty)
619+
483620
# TODO: search_run()".
484621
# TODO: item_get()".
485-
# TODO: asset_activate()".
486-
# TODO: asset_wait()".
487-
# TODO: asset_download()".

0 commit comments

Comments
 (0)