Repository: planetlabs/planet-client-python

Issue:
The --cloudconfig CLI option  has an innaccurate name
The `--cloudconfig` option of the `planet orders create` CLI command has an inaccurate name, as it is not limited to cloud delivery configurations. 

It is entirely possible to provide a local delivery config using the `--cloudconfig` option, e.g.:
```
{  
    "archive_type": "zip",
    "single_archive": true,
    "archive_filename": "{{name}}_{{order_id}}.zip"
}
```

The option should be named `--delivery` or `--deliveryconfig`, and [the docs](https://planet-sdk-for-python-v2.readthedocs.io/en/latest/cli/cli-orders/#cloud-delivery) updated accordingly. The [equivalent SDK method](https://planet-sdk-for-python-v2.readthedocs.io/en/latest/python/sdk-reference/#planet.order_request.delivery) is more accurately named `delivery` 


Discussion:

Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index 18b7033..a78e118 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,11 @@
 2.1.0 (TBD)
 
+- The --cloudconfig option of the request command of the orders CLI has been
+  superseded by a new --delivery option, with --cloudconfig left as an alias.
+  New --archive-type, --archive-filename, and --single-archive options to
+  control the zip archiving of order outputs without any cloud storage delivery
+  have also been added (#990).
+
 2.1b1 (2023-07-11)
 
 Added:
diff --git a/planet/cli/orders.py b/planet/cli/orders.py
index 40ce1ba..6a6b511 100644
--- a/planet/cli/orders.py
+++ b/planet/cli/orders.py
@@ -253,11 +253,27 @@ async def create(ctx, request: str, pretty):
               default=False,
               is_flag=True,
               help='Send email notification when order is complete.')
+@click.option('--archive-type',
+              type=click.Choice(['zip']),
+              help="Optionally zip archive each item bundle.")
+@click.option('--archive-filename',
+              default='{{name}}-{{order_id}}.zip',
+              show_default=True,
+              help="Templated filename for archived bundles or orders.")
+@click.option('--single-archive',
+              is_flag=True,
+              default=False,
+              show_default=True,
+              help="Optionally zip archive all item bundles together.")
 @click.option(
+    '--delivery',
     '--cloudconfig',
     type=types.JSON(),
-    help="""Credentials for cloud storage provider to enable cloud delivery of
-    data. Can be a json string, filename, or '-' for stdin.""")
+    help=("Delivery configuration, which may include credentials for a cloud "
+          "storage provider, to enable cloud delivery of data, and/or "
+          "parameters for bundling deliveries as zip archives. Can be a JSON "
+          "string, a filename, or '-' for stdin. The --cloudconfig option is "
+          "an alias for this use case."))
 @click.option(
     '--stac/--no-stac',
     default=True,
@@ -274,7 +290,10 @@ async def request(ctx,
                   clip,
                   tools,
                   email,
-                  cloudconfig,
+                  archive_type,
+                  archive_filename,
+                  single_archive,
+                  delivery,
                   stac,
                   pretty):
     """Generate an order request.
@@ -303,14 +322,12 @@ async def request(ctx,
         except planet.exceptions.ClientError as e:
             raise click.BadParameter(e)
 
-    if cloudconfig:
-        delivery = planet.order_request.delivery(cloud_config=cloudconfig)
-        if "google_earth_engine" in cloudconfig:
-            stac = False
-    else:
-        delivery = None
+    delivery = planet.order_request.delivery(archive_type=archive_type,
+                                             archive_filename=archive_filename,
+                                             single_archive=single_archive,
+                                             cloud_config=delivery)
 
-    if stac:
+    if stac and "google_earth_engine" not in delivery:
         stac_json = {'stac': {}}
     else:
         stac_json = {}
diff --git a/planet/cli/subscriptions.py b/planet/cli/subscriptions.py
index f5551e8..eebc1a8 100644
--- a/planet/cli/subscriptions.py
+++ b/planet/cli/subscriptions.py
@@ -226,10 +226,13 @@ async def list_subscription_results_cmd(ctx,
               required=True,
               type=types.JSON(),
               help='Source JSON. Can be a string, filename, or - for stdin.')
-@click.option('--delivery',
-              required=True,
-              type=types.JSON(),
-              help='Delivery JSON. Can be a string, filename, or - for stdin.')
+@click.option(
+    '--delivery',
+    required=True,
+    type=types.JSON(),
+    help=("Delivery configuration, including credentials for a cloud "
+          "storage provider, to enable cloud delivery of data. Can be a "
+          "JSON string, a filename, or '-' for stdin. "))
 @click.option(
     '--notifications',
     type=types.JSON(),
diff --git a/planet/order_request.py b/planet/order_request.py
index f36644c..0d4b603 100644
--- a/planet/order_request.py
+++ b/planet/order_request.py
@@ -15,7 +15,7 @@
 """Functionality for preparing order details for use in creating an order"""
 from __future__ import annotations  # https://stackoverflow.com/a/33533514
 import logging
-from typing import Optional, Any, Dict, List, Union
+from typing import Any, Dict, List, Mapping, Optional, Union
 
 from . import geojson, specs
 from .exceptions import ClientError
@@ -163,9 +163,9 @@ def notifications(email: Optional[bool] = None,
 
 
 def delivery(archive_type: Optional[str] = None,
-             single_archive: bool = False,
+             single_archive: Optional[bool] = False,
              archive_filename: Optional[str] = None,
-             cloud_config: Optional[dict] = None) -> dict:
+             cloud_config: Optional[Mapping] = None) -> dict:
     """Order delivery configuration.
 
     Example:
@@ -196,20 +196,21 @@ def delivery(archive_type: Optional[str] = None,
     Raises:
         planet.specs.SpecificationException: If archive_type is not valid.
     """
+    config: Dict[str, Any] = {}
+
     if archive_type:
         archive_type = specs.validate_archive_type(archive_type)
 
-        # for missing archive file name
         if archive_filename is None:
             archive_filename = "{{name}}_{{order_id}}.zip"
 
-    fields = ['archive_type', 'single_archive', 'archive_filename']
-    values = [archive_type, single_archive, archive_filename]
-
-    config = dict((k, v) for k, v in zip(fields, values) if v)
+        config.update(archive_type=archive_type,
+                      archive_filename=archive_filename,
+                      single_archive=single_archive)
 
     if cloud_config:
         config.update(cloud_config)
+
     return config
 
 


Test patch:
diff --git a/tests/unit/test_order_request.py b/tests/unit/test_order_request.py
index e702cce..46a01c4 100644
--- a/tests/unit/test_order_request.py
+++ b/tests/unit/test_order_request.py
@@ -175,6 +175,7 @@ def test_delivery_missing_archive_details():
     expected = {
         'archive_type': 'zip',
         'archive_filename': "{{name}}_{{order_id}}.zip",
+        'single_archive': False,
         'amazon_s3': {
             'aws_access_key_id': 'aws_access_key_id',
             'aws_secret_access_key': 'aws_secret_access_key',
@@ -317,3 +318,14 @@ def test_band_math_tool_invalid_pixel_type():
         order_request.band_math_tool(b1='b1',
                                      b2='arctan(b1)',
                                      pixel_type="invalid")
+
+
+def test_no_archive_items_without_type():
+    """Without an archive type no filename or single option are passed."""
+    delivery_config = order_request.delivery(
+        None, True, TEST_ARCHIVE_FILENAME, cloud_config={"bogus_storage": {}})
+
+    assert "bogus_storage" in delivery_config
+    assert "archive_type" not in delivery_config
+    assert "archive_filename" not in delivery_config
+    assert "single_archive" not in delivery_config