Repository: planetlabs/planet-client-python

Issue:
Support sort in data CLI 
Right now the CLI doesn't support the 'sort' options. Let's get it in there.

Discussion:

Patch:
diff --git a/planet/cli/data.py b/planet/cli/data.py
index 2e4c633..c5dff65 100644
--- a/planet/cli/data.py
+++ b/planet/cli/data.py
@@ -18,6 +18,7 @@ from contextlib import asynccontextmanager
 import click
 
 from planet import data_filter, DataClient
+from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT
 
 from . import types
 from .cmds import coro, translate_exceptions
@@ -230,13 +231,15 @@ def filter(ctx,
 @coro
 @click.argument("item_types", type=types.CommaSeparatedString())
 @click.argument("filter", type=types.JSON(), default="-", required=False)
-@click.option('--name',
-              type=str,
-              default=False,
-              help=('Name of the saved search.'))
 @limit
+@click.option('--name', type=str, help='Name of the saved search.')
+@click.option('--sort',
+              type=click.Choice(SEARCH_SORT),
+              default=SEARCH_SORT_DEFAULT,
+              show_default=True,
+              help='Field and direction to order results by.')
 @pretty
-async def search_quick(ctx, item_types, filter, name, limit, pretty):
+async def search_quick(ctx, item_types, filter, limit, name, sort, pretty):
     """Execute a structured item search.
 
     This function outputs a series of GeoJSON descriptions, one for each of the
@@ -251,11 +254,11 @@ async def search_quick(ctx, item_types, filter, name, limit, pretty):
     parameter will be applied to the stored quick search.
     """
     async with data_client(ctx) as cl:
-        items = await cl.quick_search(name=name,
-                                      item_types=item_types,
-                                      search_filter=filter,
-                                      limit=limit,
-                                      sort=None)
+        items = await cl.quick_search(item_types,
+                                      filter,
+                                      name=name,
+                                      sort=sort,
+                                      limit=limit)
         async for item in items:
             echo_json(item, pretty)
 
diff --git a/planet/clients/data.py b/planet/clients/data.py
index 2b122e1..0197781 100644
--- a/planet/clients/data.py
+++ b/planet/clients/data.py
@@ -24,8 +24,14 @@ BASE_URL = f'{PLANET_BASE_URL}/data/v1/'
 SEARCHES_PATH = '/searches'
 STATS_PATH = '/stats'
 
+# TODO: get these values from the spec directly gh-619
 LIST_SORT_ORDER = ('created desc', 'created asc')
 LIST_SEARCH_TYPE = ('any', 'saved', 'quick')
+SEARCH_SORT = ('published desc',
+               'published asc',
+               'acquired desc',
+               'acquired asc')
+SEARCH_SORT_DEFAULT = 'published desc'
 STATS_INTERVAL = ('hour', 'day', 'week', 'month', 'year')
 
 WAIT_DELAY = 5
@@ -102,8 +108,8 @@ class DataClient:
         """Execute a quick search.
 
         Quick searches are saved for a short period of time (~month). The
-        `name` parameter of the search defaults to the search id if `name`
-        is not given.
+        `name` parameter of the search defaults to the id of the generated
+        search id if `name` is not specified.
 
         Example:
 
@@ -132,10 +138,8 @@ class DataClient:
         Parameters:
             item_types: The item types to include in the search.
             search_filter: Structured search criteria.
-            sort: Override default of 'published desc' for field and direction
-                to order results by. Specified as '<field> <direction>' where
-                direction is either 'desc' for descending direction or 'asc'
-                for ascending direction.
+            sort: Field and direction to order results by. Valid options are
+            given in SEARCH_SORT.
             name: The name of the saved search.
             limit: Maximum number of items to return.
 
@@ -157,8 +161,11 @@ class DataClient:
             request_json['name'] = name
 
         params = {}
-        if sort:
-            # TODO: validate sort
+        if sort and sort != SEARCH_SORT_DEFAULT:
+            sort = sort.lower()
+            if sort not in SEARCH_SORT:
+                raise exceptions.ClientError(
+                    f'{sort} must be one of {SEARCH_SORT}')
             params['sort'] = sort
 
         request = self._request(url,


Test patch:
diff --git a/tests/integration/test_data_api.py b/tests/integration/test_data_api.py
index a488165..3c02722 100644
--- a/tests/integration/test_data_api.py
+++ b/tests/integration/test_data_api.py
@@ -110,7 +110,7 @@ async def test_quick_search_sort(item_descriptions,
                                  search_response,
                                  session):
 
-    sort = 'created asc'
+    sort = 'acquired asc'
     quick_search_url = f'{TEST_URL}/quick-search?sort={sort}'
 
     item1, _, _ = item_descriptions
diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py
index 4e7e4ec..cf10c75 100644
--- a/tests/integration/test_data_cli.py
+++ b/tests/integration/test_data_cli.py
@@ -401,6 +401,51 @@ def test_data_search_quick_filter_success(invoke, item_types):
     assert len(result.output.strip().split('\n')) == 1  # we have 1 feature
 
 
+@respx.mock
+def test_data_search_quick_sort_success(invoke):
+    # this cannot be the default value or else the sort param will not be
+    # added to the url
+    sort = 'published asc'
+    search_url = f'{TEST_QUICKSEARCH_URL}?sort={sort}'
+
+    filter = {
+        "type": "DateRangeFilter",
+        "field_name": "acquired",
+        "config": {
+            "gt": "2019-12-31T00:00:00Z"
+        }
+    }
+
+    feature = {"key": "value"}
+    mock_resp = httpx.Response(HTTPStatus.OK, json={'features': [feature]})
+    respx.post(search_url).return_value = mock_resp
+
+    runner = CliRunner()
+    result = invoke(
+        ['search-quick', 'PSScene', json.dumps(filter), f'--sort={sort}'],
+        runner=runner)
+    assert result.exit_code == 0
+    assert json.loads(result.output) == feature
+
+
+@respx.mock
+def test_data_search_quick_sort_invalid(invoke):
+    filter = {
+        "type": "DateRangeFilter",
+        "field_name": "acquired",
+        "config": {
+            "gt": "2019-12-31T00:00:00Z"
+        }
+    }
+
+    runner = CliRunner()
+    result = invoke(
+        ['search-quick', 'PSScene', json.dumps(filter), '--sort=invalid'],
+        runner=runner)
+
+    assert result.exit_code == 2
+
+
 @respx.mock
 @pytest.mark.parametrize("limit,limited_list_length", [(None, 100), (0, 102),
                                                        (1, 1)])