Repository: planetlabs/planet-client-python

Issue:
Revise `planet orders request` arguments
The arguments for `planet orders request` originally were all flags. @jreiberkyle proposed a change in https://github.com/planetlabs/planet-client-python/pull/616 to go to all positional arguments, and then in #680 we got a sort of half way there with positional arguments for items and bundles, but not id or name. 

@jreiberkyle and I discussed at length and concluded the way forward is to do a single positional argument of `id`, with the rest as flags. The hope is that we could implement some of the ideas in https://github.com/planetlabs/planet-client-python/issues/523 where the only required argument would be `id`. But if that doesn't work having a single positional argument feels much better than having 4, where you'd have to remember the exact order, or 0, where it's all flags so a lot more to type out. This also seems generally compatible with the other commands in the CLI, where most have one or two positional arguments, which seems to be a general best practice for CLI's.

Discussion:

Patch:
diff --git a/docs/cli/cli-orders.md b/docs/cli/cli-orders.md
index 444994f..ab47780 100644
--- a/docs/cli/cli-orders.md
+++ b/docs/cli/cli-orders.md
@@ -107,19 +107,19 @@ If you don't have access to PlanetScope data then replace PSScene with SkySatCol
 Then make the following call:
 
 ```console
-planet orders request PSScene visual --name 'My First Order' --id 20220605_124027_64_242b 
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'My First Order' 20220605_124027_64_242b
 ```
 
 Running the above command should output the JSON needed to create an order:
 
 ```json
-{"name": "My First Order", "products": [{"item_ids": ["20220605_124027_64_242b"], "item_type": "PSScene", "product_bundle": "analytic_sr_udm2"}]}
+{"name": "My First Order", "products": [{"item_ids": ["20220605_124027_64_242b"], "item_type": "PSScene", "product_bundle": "analytic_sr_udm2"}], "metadata": {"stac": {}}}
 ```
 
 You can also use `jq` here to make it a bit more readable:
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name 'My First Order' --id 20220605_124027_64_242b | jq
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'My First Order' 20220605_124027_64_242b | jq
 ```
 
 ```json
@@ -133,7 +133,10 @@ planet orders request PSScene analytic_sr_udm2 --name 'My First Order' --id 2022
       "item_type": "PSScene",
       "product_bundle": "analytic_sr_udm2"
     }
-  ]
+  ],
+  "metadata": {
+    "stac": {}
+  }
 }
 ```
 
@@ -143,7 +146,7 @@ The above command just prints out the necessary JSON to create an order. To actu
 save the output into a file:
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name "My First Order" --id 20220605_124027_64_242b \
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name "My First Order" 20220605_124027_64_242b \
  > request-1.json
 ```
 
@@ -200,8 +203,8 @@ passing the output of the `orders request` command directly to be the input of t
 command:
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name 'Two Item Order' \
---id 20220605_124027_64_242b,20220605_124025_34_242b | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Two Item Order' \
+20220605_124027_64_242b,20220605_124025_34_242b | planet orders create -
 ```
 
 The Planet CLI is designed to work well with piping, as it aims at small commands that can be 
@@ -357,8 +360,8 @@ You can move that geometry to your current directory and use the following comma
 tweak the geometry.geojson to refer to where you downloaded it.
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --clip geometry.geojson --name clipped-geom \
- --id 20220605_124027_64_242b | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --clip geometry.geojson --name clipped-geom \
+ 20220605_124027_64_242b | planet orders create - 
 ```
 
 ### Additional Tools
@@ -406,8 +409,8 @@ Example: `tools.json`
 Ordering two scenes is easy, just add another id:
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name 'Two Scenes' \
- --id 20220605_124027_64_242b,20220605_124025_34_242b | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Two Scenes' \
+ 20220605_124027_64_242b,20220605_124025_34_242b | planet orders create - 
 ```
 
 And then you can composite them together, using the 'tools' json. You can 
@@ -426,8 +429,8 @@ Once you've got it saved you call the `--tools` flag to refer to the JSON file,
 can pipe that to `orders create`.
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name 'Two Scenes Composited' \
---id 20220605_124027_64_242b,20220605_124025_34_242b --no-stac --tools tools-composite.json | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Two Scenes Composited' \
+ 20220605_124027_64_242b,20220605_124025_34_242b --no-stac --tools tools-composite.json | planet orders create - 
 ```
 
 Note that we add the `--no-stac` option as [STAC Metadata](#stac-metadata) is not yet supported by the composite 
@@ -452,8 +455,8 @@ as COG in the file format tool.
 The following command just shows the output with [tools-cog.json](https://raw.githubusercontent.com/planetlabs/planet-client-python/main/docs/cli/request-json/tools-cog.json):
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --name 'COG Order' \
- --id 20220605_124027_64_242b,20220605_124025_34_242b --tools tools-cog.json
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'COG Order' \
+ 20220605_124027_64_242b,20220605_124025_34_242b --tools tools-cog.json
 ```
 
 As shown above you can also pipe that output directly in to `orders create`. 
@@ -504,16 +507,16 @@ so you can just use the [following json](https://raw.githubusercontent.com/plane
 ```
 
 ```console
-planet orders request PSScene analytic_sr_udm2 --no-stac --name 'Two Scenes Clipped and Composited' \
- --id 20220605_124027_64_242b,20220605_124025_34_242b --tools tools-clip-composite.json | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --no-stac --name 'Two Scenes Clipped and Composited' \
+ 20220605_124027_64_242b,20220605_124025_34_242b --tools tools-clip-composite.json | planet orders create -
 ```
 
 One cool little trick is that you can even stream in the JSON directly with `curl`, piping it into the request:
 
 ```console
 curl -s https://raw.githubusercontent.com/planetlabs/planet-client-python/main/docs/cli/request-json/tools-clip-composite.json \
-| planet orders request PSScene analytic_sr_udm2 --name 'Streaming Clip & Composite' \
- --id 20220605_124027_64_242b,20220605_124025_34_242b --tools - | planet orders create - 
+| planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Streaming Clip & Composite' \
+ 20220605_124027_64_242b,20220605_124025_34_242b --tools - | planet orders create - 
 ```
 
 ### Harmonize
@@ -533,7 +536,7 @@ The harmonize tool allows you to compare data to different generations of satell
 You may create an order request by calling [`tools-harmonize.json`](https://raw.githubusercontent.com/planetlabs/planet-client-python/main/docs/cli/request-json/tools-harmonize.json) with `--tools`.
 
 ```console
-planet orders request psscene analytic_sr_udm2 --name 'Harmonized data' --id 20200925_161029_69_2223 --tools tools-harmonize.json
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Harmonized data' 20200925_161029_69_2223 --tools tools-harmonize.json
 ```
 
 ## More options
@@ -668,9 +671,9 @@ image that was published:
 
 
 ```console
-planet orders request SkySatCollect analytic --name 'SkySat Latest' \
---id `planet data filter | planet data search SkySatCollect --sort 'acquired desc' --limit 1 - | jq -r .id` \
-| planet orders create -
+planet orders request --item-type SkySatCollect --bundle analytic --name 'SkySat Latest' \
+ `planet data filter | planet data search SkySatCollect --sort 'acquired desc' --limit 1 - | jq -r .id` \
+| planet orders create - 
 ```
 
 Or get the 5 latest cloud free images in an area and create an order that clips to that area, using 
@@ -679,8 +682,8 @@ Or get the 5 latest cloud free images in an area and create an order that clips
 ```console
 ids=`planet data filter --geom geometry.geojson --range clear_percent gt 90 | planet data \
 search PSScene --limit 5 - | jq -r .id | tr '\n' , | sed 's/.$//'`
-planet orders request PSScene analytic_sr_udm2 --name 'Clipped Scenes'  \
---id $ids --clip geometry.geojson | planet orders create -
+planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Clipped Scenes'  \
+ $ids --clip geometry.geojson | planet orders create -
 ```
 
 This one uses some advanced unix capabilities like `sed` and `tr`, along with unix variables, so more
diff --git a/planet/cli/orders.py b/planet/cli/orders.py
index b126a4d..998168e 100644
--- a/planet/cli/orders.py
+++ b/planet/cli/orders.py
@@ -225,22 +225,21 @@ async def create(ctx, request: str, pretty):
 @click.pass_context
 @translate_exceptions
 @coro
-@click.argument('item_type',
-                metavar='ITEM_TYPE',
-                type=click.Choice(planet.specs.get_item_types(),
-                                  case_sensitive=False))
-@click.argument('bundle',
-                metavar='BUNDLE',
-                type=click.Choice(planet.specs.get_product_bundles(),
-                                  case_sensitive=False))
+@click.argument('ids', metavar='IDS', type=types.CommaSeparatedString())
+@click.option('--item-type',
+              required=True,
+              help='Item type for requested item ids.',
+              type=click.Choice(planet.specs.get_item_types(),
+                                case_sensitive=False))
+@click.option('--bundle',
+              required=True,
+              help='Asset type for the item.',
+              type=click.Choice(planet.specs.get_product_bundles(),
+                                case_sensitive=False))
 @click.option('--name',
               required=True,
               help='Order name. Does not need to be unique.',
               type=click.STRING)
-@click.option('--id',
-              help='One or more comma-separated item IDs.',
-              type=types.CommaSeparatedString(),
-              required=True)
 @click.option('--clip',
               type=types.JSON(),
               help="""Clip feature GeoJSON. Can be a json string, filename,
@@ -270,7 +269,7 @@ async def request(ctx,
                   item_type,
                   bundle,
                   name,
-                  id,
+                  ids,
                   clip,
                   tools,
                   email,
@@ -280,11 +279,13 @@ async def request(ctx,
     """Generate an order request.
 
     This command provides support for building an order description used
-    in creating an order. It outputs the order request, optionally pretty-
-    printed.
+    in creating an order. It outputs the order request, optionally
+    pretty-printed.
+
+    IDs is one or more comma-separated item IDs.
     """
     try:
-        product = planet.order_request.product(id, bundle, item_type)
+        product = planet.order_request.product(ids, bundle, item_type)
     except planet.specs.SpecificationException as e:
         raise click.BadParameter(e)
 


Test patch:
diff --git a/tests/integration/test_orders_cli.py b/tests/integration/test_orders_cli.py
index 16c7a9f..4cfda87 100644
--- a/tests/integration/test_orders_cli.py
+++ b/tests/integration/test_orders_cli.py
@@ -461,10 +461,10 @@ def test_cli_orders_request_basic_success(expected_ids,
                                           stac_json):
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        f'--id={id_string}',
+        id_string,
     ])
     assert not result.exception
 
@@ -485,45 +485,44 @@ def test_cli_orders_request_basic_success(expected_ids,
 def test_cli_orders_request_item_type_invalid(invoke):
     result = invoke([
         'request',
-        'invalid'
-        'analytic',
+        '--item-type=invalid'
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
     ])
     assert result.exit_code == 2
-    error_msg = "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE"
-    assert error_msg in result.output
 
 
 def test_cli_orders_request_product_bundle_invalid(invoke):
     result = invoke([
         'request',
-        'PSScene'
-        'invalid',
+        '--item-type=PSScene'
+        '--bundle=invalid',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
     ])
     assert result.exit_code == 2
-    error_msg = "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE"
-    assert error_msg in result.output
 
 
 def test_cli_orders_request_product_bundle_incompatible(invoke):
     result = invoke([
         'request',
-        'PSScene',
-        'analytic',
+        '--item-type=PSScene',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
     ])
     assert result.exit_code == 2
-    error_msg = "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE"
-    assert error_msg in result.output
 
 
 def test_cli_orders_request_id_empty(invoke):
-    result = invoke(
-        ['request', 'PSOrthoTile', 'analytic', '--name=test', '--id='])
+    result = invoke([
+        'request',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
+        '--name=test',
+        ''
+    ])
     assert result.exit_code == 2
     assert 'Entry cannot be an empty string.' in result.output
 
@@ -541,10 +540,10 @@ def test_cli_orders_request_clip_success(geom_fixture,
 
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         f'--clip={json.dumps(geom)}',
     ])
     assert result.exit_code == 0
@@ -571,10 +570,10 @@ def test_cli_orders_request_clip_success(geom_fixture,
 def test_cli_orders_request_clip_invalid_geometry(invoke, point_geom_geojson):
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         f'--clip={json.dumps(point_geom_geojson)}'
     ])
     assert result.exit_code == 2
@@ -588,10 +587,10 @@ def test_cli_orders_request_both_clip_and_tools(invoke, geom_geojson):
     # option values are valid json
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         f'--clip={json.dumps(geom_geojson)}',
         f'--tools={json.dumps(geom_geojson)}'
     ])
@@ -613,10 +612,10 @@ def test_cli_orders_request_cloudconfig(invoke, stac_json):
 
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         f'--cloudconfig={json.dumps(config_json)}',
     ])
     assert result.exit_code == 0
@@ -640,10 +639,10 @@ def test_cli_orders_request_cloudconfig(invoke, stac_json):
 def test_cli_orders_request_email(invoke, stac_json):
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         '--email'
     ])
     assert result.exit_code == 0
@@ -671,10 +670,10 @@ def test_cli_orders_request_tools(invoke, geom_geojson, stac_json):
 
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         f'--tools={json.dumps(tools_json)}'
     ])
 
@@ -699,10 +698,10 @@ def test_cli_orders_request_no_stac(invoke):
 
     result = invoke([
         'request',
-        'PSOrthoTile',
-        'analytic',
+        '--item-type=PSOrthoTile',
+        '--bundle=analytic',
         '--name=test',
-        '--id=4500474_2133707_2021-05-20_2419',
+        '4500474_2133707_2021-05-20_2419',
         '--no-stac'
     ])