Repository: planetlabs/planet-client-python

Issue:
Change `search-update` to be consistent with new search.
I just realized the `search-update` should also be updated for consistency. Elsewhere (#887) I mentioned `planet data stats` could also use an update. I think that one is 'nice to have', but probably not essential. But `search-update` seems like it really should have the same behavior. I'm only trying it out now - I had thought it might just take JSON instead of having arguments, but just realized it has the arguments, and it'd be weird to have them different.

_Originally posted by @cholmes in https://github.com/planetlabs/planet-client-python/issues/883#issuecomment-1478903549_

Note I'd see an ideal `search-update` not requiring all the parameters - if you left them off then it'd assume that you want to keep things the same. So like if you just wanted to update the `name` then you could leave the other values blank. That seems like it'd probably be a complicated implementation, since we'd need to get the JSON and edit the right bits - just putting it here as I think that'd be the ideal experience. But this function is very little used, so not really worth optimizing.
            

Discussion:

Patch:
diff --git a/docs/cli/cli-intro.md b/docs/cli/cli-intro.md
index 91bb052..6100876 100644
--- a/docs/cli/cli-intro.md
+++ b/docs/cli/cli-intro.md
@@ -173,7 +173,7 @@ represent GeoJSON features, the JSON blob is a GeoJSON FeatureCollection.
 Otherwise, the JSON blob is a list of the individual results.
 
 ```console
-planet data search PSScene | planet collect -
+$ planet data search PSScene | planet collect -
 ```
 
 This gives you a fully compliant GeoJSON FeatureCollection, which is 
diff --git a/docs/cli/cli-orders.md b/docs/cli/cli-orders.md
index bf5049f..ab47780 100644
--- a/docs/cli/cli-orders.md
+++ b/docs/cli/cli-orders.md
@@ -36,7 +36,7 @@ You can also filter orders by their `state`, which can be useful to just see ord
 progress:
 
 ```console
-planet orders list --state running
+planet orders --state running
 ```
 
 The other options are queued, failed, success, partial and cancelled.
@@ -100,7 +100,7 @@ To create an order you need a name, a [bundle](https://developers.planet.com/api
 First lets get the ID of an item you have download access to, using the Data API: 
 
 ```
-planet data filter | planet data search PSScene --limit 1 --filter - | jq -r .id 
+planet data filter | planet data search PSScene --limit 1 - | jq -r .id
 ```
 
 If you don't have access to PlanetScope data then replace PSScene with SkySatCollect.
@@ -515,7 +515,7 @@ One cool little trick is that you can even stream in the JSON directly with `cur
 
 ```console
 curl -s https://raw.githubusercontent.com/planetlabs/planet-client-python/main/docs/cli/request-json/tools-clip-composite.json \
-| planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Streaming Clip & Composite' --no-stac \
+| 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 - 
 ```
 
@@ -550,7 +550,7 @@ STAC metadata by default, as the STAC files are small and often more useful than
 You can easily turn off STAC output request with the `--no-stac` command:
 
 ```
-planet orders request --item-type PSScene --bundle visual --name 'No STAC' --no-stac 20220605_124027_64_242b
+planet orders request PSScene visual --name 'No STAC' --no-stac --id 20220605_124027_64_242b 
 ```
 
 Currently this needs to be done for any 'composite' operation, as STAC output from composites is not yet 
@@ -672,7 +672,7 @@ image that was published:
 
 ```console
 planet orders request --item-type SkySatCollect --bundle analytic --name 'SkySat Latest' \
- `planet data filter | planet data search SkySatCollect --sort 'acquired desc' --limit 1 --filter - | jq -r .id` \
+ `planet data filter | planet data search SkySatCollect --sort 'acquired desc' --limit 1 - | jq -r .id` \
 | planet orders create - 
 ```
 
@@ -681,7 +681,7 @@ 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 --filter - | jq -r .id | tr '\n' , | sed 's/.$//'`
+search PSScene --limit 5 - | jq -r .id | tr '\n' , | sed 's/.$//'`
 planet orders request --item-type PSScene --bundle analytic_sr_udm2 --name 'Clipped Scenes'  \
  $ids --clip geometry.geojson | planet orders create -
 ```
diff --git a/docs/cli/cli-tips-tricks.md b/docs/cli/cli-tips-tricks.md
index bae4fcd..4da63c3 100644
--- a/docs/cli/cli-tips-tricks.md
+++ b/docs/cli/cli-tips-tricks.md
@@ -301,11 +301,9 @@ planet orders list | jq -rs '.[3] | "\(.id) \(.created_on) \(.name) \(.state)"'
 
 * Use jq to show just orders that have a given item type, like just skysat.
 
-```console
-planet orders list | jq -rs '.[] | "\(.id) \(.created_on) \(.state) \(.products[0].item_type)"'
-```
+planet orders list | jq -rs '.[] | "\(.id) \(.created_on) \(.state) \(.products[0].item_type)"'  will show the item type
 
-will show the item type https://gist.github.com/ipbastola/2c955d8bf2e96f9b1077b15f995bdae3 has ideas for contains, but haven't got it right yet
+https://gist.github.com/ipbastola/2c955d8bf2e96f9b1077b15f995bdae3 has ideas for contains, but haven't got it right yet
 
 * use jq to get the id of the an order by it's name
 
diff --git a/planet/cli/data.py b/planet/cli/data.py
index 2591b32..4853889 100644
--- a/planet/cli/data.py
+++ b/planet/cli/data.py
@@ -418,27 +418,19 @@ async def search_run(ctx, search_id, sort, limit, pretty):
 @click.argument("item_types",
                 type=types.CommaSeparatedString(),
                 callback=check_item_types)
-@click.option(
-    '--filter',
-    type=types.JSON(),
-    required=True,
-    help="""Filter to apply to search. Can be a json string, filename,
-         or '-' for stdin.""")
-@click.option('--interval',
-              type=click.Choice(STATS_INTERVAL),
-              required=True,
-              help='The size of the histogram date buckets.')
-async def stats(ctx, item_types, filter, interval):
+@click.argument('interval', type=click.Choice(STATS_INTERVAL))
+@click.argument("filter", type=types.JSON())
+async def stats(ctx, item_types, interval, filter):
     """Get a bucketed histogram of items matching the filter.
 
     This function returns a bucketed histogram of results based on the
-    item_types, interval, and filter specified.
+    item_types, interval, and json filter specified (using file or stdin).
 
     """
     async with data_client(ctx) as cl:
         items = await cl.get_stats(item_types=item_types,
-                                   search_filter=filter,
-                                   interval=interval)
+                                   interval=interval,
+                                   search_filter=filter)
         echo_json(items)
 
 
@@ -476,20 +468,28 @@ async def search_delete(ctx, search_id):
 @translate_exceptions
 @coro
 @click.argument('search_id')
-@click.argument('name')
 @click.argument("item_types",
                 type=types.CommaSeparatedString(),
                 callback=check_item_types)
-@click.argument('filter', type=types.JSON())
+@click.option(
+    '--filter',
+    type=types.JSON(),
+    required=True,
+    help="""Filter to apply to search. Can be a json string, filename,
+         or '-' for stdin.""")
+@click.option('--name',
+              type=str,
+              required=True,
+              help='Name of the saved search.')
 @click.option('--daily-email',
               is_flag=True,
               help='Send a daily email when new results are added.')
 @pretty
 async def search_update(ctx,
                         search_id,
-                        name,
                         item_types,
                         filter,
+                        name,
                         daily_email,
                         pretty):
     """Update a saved search with the given search request.
@@ -499,9 +499,9 @@ async def search_update(ctx,
     """
     async with data_client(ctx) as cl:
         items = await cl.update_search(search_id,
-                                       name,
                                        item_types,
                                        filter,
+                                       name,
                                        daily_email)
         echo_json(items, pretty)
 


Test patch:
diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py
index 3bddc70..76909e8 100644
--- a/tests/integration/test_data_cli.py
+++ b/tests/integration/test_data_cli.py
@@ -771,18 +771,16 @@ def test_data_stats_invalid_filter(invoke, filter):
     interval = "hour"
     item_type = 'PSScene'
     runner = CliRunner()
-    result = invoke(
-        ["stats", item_type, f'--interval={interval}', f'--filter={filter}'],
-        runner=runner)
+    result = invoke(["stats", item_type, interval, filter], runner=runner)
     assert result.exit_code == 2
 
 
 @respx.mock
 @pytest.mark.parametrize("item_types",
                          ['PSScene', 'SkySatScene', 'PSScene, SkySatScene'])
-@pytest.mark.parametrize("interval, exit_code", [(None, 2), ('hou', 2),
+@pytest.mark.parametrize("interval, exit_code", [(None, 1), ('hou', 2),
                                                  ('hour', 0)])
-def test_data_stats_interval(invoke, item_types, interval, exit_code):
+def test_data_stats_invalid_interval(invoke, item_types, interval, exit_code):
     """Test for planet data stats. Test with multiple item_types.
     Test should succeed with valid interval, and fail with invalid interval."""
     filter = {
@@ -799,11 +797,10 @@ def test_data_stats_interval(invoke, item_types, interval, exit_code):
                                }]})
     respx.post(TEST_STATS_URL).return_value = mock_resp
 
-    args = ["stats", item_types, f'--filter={json.dumps(filter)}']
-    if interval:
-        args.append(f'--interval={interval}')
+    runner = CliRunner()
+    result = invoke(["stats", item_types, interval, json.dumps(filter)],
+                    runner=runner)
 
-    result = invoke(args)
     assert result.exit_code == exit_code
 
 
@@ -831,12 +828,9 @@ def test_data_stats_success(invoke, item_types, interval):
                                }]})
     respx.post(TEST_STATS_URL).return_value = mock_resp
 
-    result = invoke([
-        "stats",
-        item_types,
-        f'--interval={interval}',
-        f'--filter={json.dumps(filter)}'
-    ])
+    runner = CliRunner()
+    result = invoke(["stats", item_types, interval, json.dumps(filter)],
+                    runner=runner)
     assert result.exit_code == 0
 
 
@@ -906,9 +900,9 @@ def test_search_update_success(invoke,
     result = invoke([
         'search-update',
         search_id,
-        name,
         item_types,
-        json.dumps(search_filter)
+        f'--filter={json.dumps(search_filter)}',
+        f'--name={name}'
     ])
 
     assert not result.exception
@@ -927,9 +921,9 @@ def test_search_update_fail(invoke, search_id, search_filter):
     result = invoke([
         'search-update',
         search_id,
-        name,
         item_types,
-        json.dumps(search_filter)
+        f'--filter={json.dumps(search_filter)}',
+        f'--name={name}'
     ])
 
     assert result.output.startswith("Error")