Repository: planetlabs/planet-client-python
Pull request #1079 (2024-11-28 05:08:55)

Add filtering/sorting parameters to the subscriptions & orders list commands
**Related Issue(s):**

Closes [DND-1610](https://hello.planet.com/jira/browse/DND-1610)


**Proposed Changes:**

For inclusion in changelog (if applicable):

- Add parameters to the subscriptions list command: `--created`, `--start-time`, `--end-time`, `--updated`, `--hosting`, `--name`, `--name-contains`, and `--sort-by`.
- Add parameters to the orders list command: `--source-type`, `--name`, `--name-contains`, `--created-on`, `--last-modified`, `--hosting`, and `--sort-by`.

**PR Checklist:**

- [x] This PR is as small and focused as possible
- [x] If this PR includes proposed changes for inclusion in the changelog, the title of this PR summarizes those changes and is ready for inclusion in the Changelog.
- [x] I have updated docstrings for function changes and docs in the 'docs' folder for user interface / behavior changes
- [x] This PR does not break any examples or I have updated them


Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index 0993ad3..9ed7271 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,9 @@
+2.12.0 (2024-12-06)
+- Add parameters to the subscriptions list command: --created, --start-time,
+  --end-time, --updated, --hosting, --name, --name-contains, and --sort-by.
+- Add parameters to the orders list command: --source-type, --name,
+  --name-contains, --created-on, --last-modified, --hosting, and --sort-by.
+
 2.11.0 (2024-10-21)
 
 Added:
diff --git a/docs/cli/cli-orders.md b/docs/cli/cli-orders.md
index ba11f07..3ae33a8 100644
--- a/docs/cli/cli-orders.md
+++ b/docs/cli/cli-orders.md
@@ -15,9 +15,10 @@ then check out our [CLI Concepts](cli-intro.md) guide.
 
 ## Core Workflows
 
-### See Recent Orders
+### List Orders
 
-You can use the `list` command to show your recent orders:
+You can use the `list` command to see details of existing orders. By default they will be sorted
+by most recently created.
 
 ```sh
 planet orders list
@@ -26,24 +27,10 @@ planet orders list
 If you’ve not placed any orders with Explorer, the CLI or the API directly, then the results
 of this call will be blank, so you may want to try out some of the create order commands below.
 
-Sometimes that list gets too long, so you can put a limit on how many are returned:
+Sometimes that list gets too long, so you can put a limit on how many are returned using the
+`--limit` parameter.
 
-```sh
-planet orders list --limit 5
-```
-
-You can also filter orders by their `state`, which can be useful to just see orders in 
-progress:
-
-```sh
-planet orders list --state running
-```
-
-The other options are queued, failed, success, partial and cancelled.
-
-Note you can also get a nice list online as well, at https://www.planet.com/account/#/orders
-
-### Recent Orders with Formatting
+#### Formatting
 
 You can also print the list of orders more nicely:
 
@@ -71,10 +58,13 @@ planet orders list | jq -rs '.[] | "\(.id) \(.created_on) \(.state) \(.name)"'
 
 You can customize which fields you want to show by changing the values.
 
-### Number of recent orders
+A list of your orders can also be viewed in a UI at https://www.planet.com/account/#/orders
 
-You can use jq to process the output for more insight, like 
-get a count of how many recent orders you’ve done. 
+#### Number of existing orders
+
+You can use `jq` to process the output for more insight, like get a count of how many existing
+orders you have. Orders are automatically deleted 90 days after completion, so this number
+roughly is equivalent to the number of orders created in the last 90 days.
 
 ```sh
 planet orders list | jq -s length
@@ -83,6 +73,65 @@ planet orders list | jq -s length
 This uses `-s` to collect the output into a single array, and `length` then tells the
 length of the array.
 
+#### Filtering
+
+The `list` command supports filtering on a variety of fields:
+* `--state`: Filter by order state. Options include `queued`, `failed`, `success`, `partial` and `cancelled`.
+* `--source-type`: Filter by source type. Options include `scenes`, `basemaps`, or `all`. The default is `all`.
+* `--name`: Filter by name.
+* `--name-contains`: Only return orders with a name that contains the provided string.
+* `--created-on`: Filter on the order creation time or an interval of creation times.
+* `--last-modified`: Filter on the order's last modified time or an interval of last modified times.
+* `--hosting`: Filter on orders containing a hosting location (e.g. SentinelHub). Accepted values are `true` or `false`.
+
+Datetime args (`--created-on` and `--last-modified`) can either be a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.
+* A date-time: `2018-02-12T23:20:50Z`
+* A closed interval: `2018-02-12T00:00:00Z/2018-03-18T12:31:12Z`
+* Open intervals: `2018-02-12T00:00:00Z/..` or `../2018-03-18T12:31:12Z`
+
+To list orders in progress:
+```sh
+planet orders list --state running
+```
+
+To list orders with the `scenes` source type:
+```sh
+planet orders list --source-type scenes
+```
+
+To list orders that were created in 2024:
+```sh
+planet orders list --created-on 2024-01-01T00:00:00Z/2025-01-01T00:00:00Z
+```
+
+To list orders with a hosting location:
+```sh
+planet orders list --hosting true
+```
+
+To list orders with the name `my location xyz`:
+```sh
+planet orders list --name "my location xyz"
+```
+
+To list orders with a name containing `xyz`:
+```sh
+planet orders list --name-contains xyz
+```
+
+#### Sorting
+
+The `list` command also supports sorting the orders on one or more fields: `name`, `created_on`, `state`, and `last_modified`. The sort direction can be specified by appending ` ASC` or ` DESC` to the field name (default is ascending).
+
+When multiple fields are specified, the sort order is applied in the order the fields are listed.
+
+Sorting examples:
+* `--sort-by name`: sorts orders by name alphabetically.
+* `--sort-by "created_on DESC"`: sorts orders by most recently created.
+* `--sort-by "last_modified DESC"`: sorts subscriptions by most recently modified.
+* `--sort-by "state ASC"`: sorts orders by state alphabetically.
+* `--sort-by "name,last_modified DESC"`: sorts subscriptions by name first, and most recently modified second.
+
 ### Info on an Order
 
 For a bit more information about an order, including the location of any downloads, use
diff --git a/docs/cli/cli-subscriptions.md b/docs/cli/cli-subscriptions.md
index 4c42eec..92caea3 100644
--- a/docs/cli/cli-subscriptions.md
+++ b/docs/cli/cli-subscriptions.md
@@ -117,8 +117,7 @@ To create a new subscription with the CLI, use the `create` command and the json
 planet subscriptions create my-subscription.json
 ```
 
-!!!note "Note"
-    The above command assumes that you’ve saved the subscriptions JSON as `my-subscription.json` and that you’ve replaced the delivery information with your own bucket and credentials.
+**Note:** The above command assumes that you’ve saved the subscriptions JSON as `my-subscription.json` and that you’ve replaced the delivery information with your own bucket and credentials.
 
 ### List Subscriptions
 
@@ -134,23 +133,62 @@ parameter higher, or you can set it to 0 and there will be no limit.
 You can get nicer formatting with `--pretty` or pipe it into `jq`, just like the other Planet
 CLI’s.
 
-The `list` command supports filtering by the status of the subscription:
-
+#### Filtering
+
+The `list` command supports filtering on a variety of fields:
+* `--created`: Filter on the subscription creation time or an interval of creation times.
+* `--end-time`: Filter on the subscription end time or an interval of end times.
+* `--hosting`: Filter on subscriptions containing a hosting location (e.g. SentinelHub). Accepted values are `true` or `false`.
+* `--name-contains`: Filter on subscriptions with a name that contains the provided string.
+* `--name`: Filter on subscriptions with a specific name
+* `--source-type`: Filter by the source type of the subscription. For the full list of available source types, see [Subscription Source Types](https://developers.planet.com/docs/subscriptions/source/#subscription-source-types). Multiple source type args are allowed.
+* `--start-time`: Filter on the subscription start time or an interval of start times.
+* `--status`: Filter on the status of the subscription. Status options include `running`, `cancelled`, `preparing`, `pending`, `completed`, `suspended`, and `failed`. Multiple status args are allowed.
+* `--updated`: Filter on the subscription update time or an interval of updated times.
+
+Datetime args (`--created`, `end-time`, `--start-time`, and `--updated`) can either be a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.
+* A date-time: `2018-02-12T23:20:50Z`
+* A closed interval: `2018-02-12T00:00:00Z/2018-03-18T12:31:12Z`
+* Open intervals: `2018-02-12T00:00:00Z/..` or `../2018-03-18T12:31:12Z`
+
+To list currently active subscriptions:
 ```sh
 planet subscriptions list --status running
 ```
 
-gives you just the currently active subscriptions. The other available statuses are:
-`cancelled`, `preparing`, `pending`, `completed`, `suspended`, and `failed`.
+To list subscriptions with the `catalog` source type:
+```sh
+planet subscriptions list --source-type catalog
+```
 
-The `list` command also supports filtering by the source type of the subscription:
+To list subscriptions that were created in 2024:
+```sh
+planet subscriptions list --created 2024-01-01T00:00:00Z/2025-01-01T00:00:00Z
+```
 
+To list subscriptions with an end time after Jan 1, 2025:
 ```sh
-planet subscriptions list --source-type catalog
+planet subscriptions list --end-time 2025-01-01T00:00:00Z/..
 ```
 
-only gives you subscriptions with the `catalog` source type. For the full list of available source types, 
-see [Subscription Source Types](https://developers.planet.com/docs/subscriptions/source/#subscription-source-types).
+To list subscriptions with a hosting location:
+```sh
+planet subscriptions list --hosting true
+```
+
+#### Sorting
+
+The `list` command also supports sorting the subscriptions on one or more fields: `name`, `created`, `updated`, `start_time`, and `end_time`. The sort direction can be specified by appending ` ASC` or ` DESC` to the field name (default is ascending).
+
+When multiple fields are specified, the sort order is applied in the order the fields are listed.
+
+Sorting examples:
+* `--sort-by name`: sorts subscriptions by name alphabetically.
+* `--sort-by "created DESC"`: sorts subscriptions by most recently created.
+* `--sort-by "updated DESC"`: sorts subscriptions by most recently updated.
+* `--sort-by "start_time ASC"`: sorts subscriptions by start time (earliest to latest)
+* `--sort-by "end_time DESC"`: sorts subscriptions by end time (latest to earliest)
+* `--sort-by "name,start_time DESC"`: sorts subscriptions by name ascending first, and start time descending second.
 
 ### Get Subscription
 
diff --git a/planet/cli/orders.py b/planet/cli/orders.py
index db7734e..5d6e55d 100644
--- a/planet/cli/orders.py
+++ b/planet/cli/orders.py
@@ -54,22 +54,72 @@ def orders(ctx, base_url):
 @translate_exceptions
 @coro
 @click.option('--state',
-              help='Filter orders to given state.',
+              help='Filter by state.',
               type=click.Choice(planet.clients.orders.ORDER_STATE_SEQUENCE,
                                 case_sensitive=False))
+@click.option(
+    '--source-type',
+    default="all",
+    help="""Filter by source type. See documentation for all available types.
+    Default is all.""")
+@click.option('--name', help="filter by name")
+@click.option(
+    '--name-contains',
+    help="only return orders with a name that contains the provided string.")
+@click.option(
+    '--created-on',
+    help="Filter by creation time or interval. See documentation for examples."
+)
+@click.option(
+    '--last-modified',
+    help="Filter by creation time or interval. See documentation for examples."
+)
+@click.option(
+    '--hosting',
+    type=click.BOOL,
+    help="Filter by presence of hosting block (e.g. SentinelHub hosting).")
+@click.option(
+    '--sort-by',
+    help="""fields to sort orders by. Multiple fields can be specified,
+    separated by commas. The sort direction can be specified by appending
+    ' ASC' or ' DESC' to the field name. The default sort direction is
+    ascending. When multiple fields are specified, the sort order is applied
+    in the order the fields are listed.
+
+    Supported fields: [name, created_on, state, last_modified].
+
+    Example: 'name ASC,created_on DESC'""")
 @limit
 @pretty
-async def list(ctx, state, limit, pretty):
+async def list(ctx,
+               state,
+               source_type,
+               name,
+               name_contains,
+               created_on,
+               last_modified,
+               hosting,
+               sort_by,
+               limit,
+               pretty):
     """List orders
 
     This command prints a sequence of the returned order descriptions,
     optionally pretty-printed.
 
-    Order descriptions are sorted by creation date with the last created order
+    By default, order descriptions are sorted by creation date with the last created order
     returned first.
     """
     async with orders_client(ctx) as cl:
-        async for o in cl.list_orders(state=state, limit=limit):
+        async for o in cl.list_orders(state=state,
+                                      source_type=source_type,
+                                      name=name,
+                                      name__contains=name_contains,
+                                      created_on=created_on,
+                                      last_modified=last_modified,
+                                      hosting=hosting,
+                                      sort_by=sort_by,
+                                      limit=limit):
             echo_json(o, pretty)
 
 
diff --git a/planet/cli/subscriptions.py b/planet/cli/subscriptions.py
index 1620549..f2521f8 100644
--- a/planet/cli/subscriptions.py
+++ b/planet/cli/subscriptions.py
@@ -64,6 +64,32 @@ def subscriptions(ctx, base_url):
 # command function as "list_subscriptions".
 @subscriptions.command(name="list")  # type: ignore
 @pretty
+@click.option(
+    '--created',
+    help="""Filter subscriptions by creation time or interval. See documentation
+    for examples.""")
+@click.option(
+    '--end-time',
+    help="""Filter subscriptions by end time or interval. See documentation
+    for examples.""")
+@click.option(
+    '--hosting',
+    type=click.BOOL,
+    help="""Filter subscriptions by presence of hosting block (e.g. SentinelHub
+    hosting).""")
+@click.option(
+    '--name-contains',
+    help="""only return subscriptions with a name that contains the provided
+    string.""")
+@click.option('--name', help="filter by name")
+@click.option(
+    '--source-type',
+    help="""Filter subscriptions by source type. See documentation for all
+    available types. Default is all.""")
+@click.option(
+    '--start-time',
+    help="""Filter subscriptions by start time or interval. See documentation
+    for examples.""")
 @click.option(
     '--status',
     type=click.Choice([
@@ -76,23 +102,52 @@ def subscriptions(ctx, base_url):
         "failed"
     ]),
     multiple=True,
-    default=None,
     help="Select subscriptions in one or more states. Default is all.")
-@click.option(
-    '--source-type',
-    default=None,
-    help="Filter subscriptions by source type. See documentation for all "
-    "available types. Default is all.")
+@click.option('--sort-by',
+              help="""fields to sort subscriptions by. Multiple fields can be
+    specified, separated by commas. The sort direction can be specified by
+    appending ' ASC' or ' DESC' to the field name. The default sort
+    direction is ascending. When multiple fields are specified, the sort
+    order is applied in the order the fields are listed.
+
+    Supported fields: [name, created, updated, start_time, end_time].
+
+    Example: 'name ASC,created DESC'""")
+@click.option('--updated',
+              help="""Filter subscriptions by update time or interval. See
+    documentation
+    for examples.""")
 @limit
 @click.pass_context
 @translate_exceptions
 @coro
-async def list_subscriptions_cmd(ctx, status, source_type, limit, pretty):
+async def list_subscriptions_cmd(ctx,
+                                 created,
+                                 end_time,
+                                 hosting,
+                                 name_contains,
+                                 name,
+                                 source_type,
+                                 start_time,
+                                 status,
+                                 sort_by,
+                                 updated,
+                                 limit,
+                                 pretty):
     """Prints a sequence of JSON-encoded Subscription descriptions."""
     async with subscriptions_client(ctx) as client:
-        async for sub in client.list_subscriptions(status=status,
-                                                   source_type=source_type,
-                                                   limit=limit):
+        async for sub in client.list_subscriptions(
+                created=created,
+                end_time=end_time,
+                hosting=hosting,
+                name__contains=name_contains,
+                name=name,
+                source_type=source_type,
+                start_time=start_time,
+                status=status,
+                sort_by=sort_by,
+                updated=updated,
+                limit=limit):
             echo_json(sub, pretty)
 
 
diff --git a/planet/clients/orders.py b/planet/clients/orders.py
index 92d7464..1926ac8 100644
--- a/planet/clients/orders.py
+++ b/planet/clients/orders.py
@@ -16,7 +16,7 @@
 import asyncio
 import logging
 import time
-from typing import AsyncIterator, Callable, List, Optional
+from typing import AsyncIterator, Callable, List, Optional, Sequence, Union, Dict
 import uuid
 import json
 import hashlib
@@ -464,10 +464,17 @@ class OrdersClient:
 
     async def list_orders(self,
                           state: Optional[str] = None,
+                          source_type: Optional[str] = None,
+                          name: Optional[str] = None,
+                          name__contains: Optional[str] = None,
+                          created_on: Optional[str] = None,
+                          last_modified: Optional[str] = None,
+                          hosting: Optional[bool] = None,
+                          sort_by: Optional[str] = None,
                           limit: int = 100) -> AsyncIterator[dict]:
         """Iterate over the list of stored orders.
 
-        Order descriptions are sorted by creation date with the last created
+        By default, order descriptions are sorted by creation date with the last created
         order returned first.
 
         Note:
@@ -476,10 +483,38 @@ class OrdersClient:
             single result description or return a list of descriptions.
 
         Parameters:
-            state: Filter orders to given state.
-            limit: Maximum number of results to return. When set to 0, no
+            state (str): filter by state.
+            source_type (str): filter by source type.
+            name (str): filter by name.
+            name__contains (str): only include orders with names containing this string.
+            created_on (str): filter by creation date-time or interval.
+            last_modified (str): filter by last modified date-time or interval.
+            hosting (bool): only return orders that contain a hosting block
+                (e.g. SentinelHub hosting).
+            sort_by (str): fields to sort orders by. Multiple fields can be specified,
+                separated by commas. The sort direction can be specified by appending
+                ' ASC' or ' DESC' to the field name. The default sort direction is
+                ascending. When multiple fields are specified, the sort order is applied
+                in the order the fields are listed.
+
+                Supported fields: name, created_on, state, last_modified
+
+                Examples:
+                 * "name"
+                 * "name DESC"
+                 * "name,state DESC,last_modified"
+            limit (int): maximum number of results to return. When set to 0, no
                 maximum is applied.
 
+        Datetime args (created_on and last_modified) can either be a date-time or an
+        interval, open or closed. Date and time expressions adhere to RFC 3339. Open
+        intervals are expressed using double-dots.
+
+        Examples:
+            * A date-time: "2018-02-12T23:20:50Z"
+            * A closed interval: "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"
+            * Open intervals: "2018-02-12T00:00:00Z/.." or "../2018-03-18T12:31:12Z"
+
         Yields:
             Description of an order.
 
@@ -488,8 +523,23 @@ class OrdersClient:
             planet.exceptions.ClientError: If state is not valid.
         """
         url = self._orders_url()
-        params = {"source_type": "all"}
-
+        params: Dict[str, Union[str, Sequence[str], bool]] = {}
+        if source_type is not None:
+            params["source_type"] = source_type
+        else:
+            params["source_type"] = "all"
+        if name is not None:
+            params["name"] = name
+        if name__contains is not None:
+            params["name__contains"] = name__contains
+        if created_on is not None:
+            params["created_on"] = created_on
+        if last_modified is not None:
+            params["last_modified"] = last_modified
+        if hosting is not None:
+            params["hosting"] = hosting
+        if sort_by is not None:
+            params["sort_by"] = sort_by
         if state:
             if state not in ORDER_STATE_SEQUENCE:
                 raise exceptions.ClientError(
diff --git a/planet/clients/subscriptions.py b/planet/clients/subscriptions.py
index 0cdf2c4..0382528 100644
--- a/planet/clients/subscriptions.py
+++ b/planet/clients/subscriptions.py
@@ -60,10 +60,18 @@ class SubscriptionsClient:
             self._base_url = self._base_url[:-1]
 
     async def list_subscriptions(self,
-                                 status: Optional[Sequence[str]] = None,
+                                 created: Optional[str] = None,
+                                 end_time: Optional[str] = None,
+                                 hosting: Optional[bool] = None,
+                                 name__contains: Optional[str] = None,
+                                 name: Optional[str] = None,
                                  source_type: Optional[str] = None,
+                                 start_time: Optional[str] = None,
+                                 status: Optional[Sequence[str]] = None,
+                                 sort_by: Optional[str] = None,
+                                 updated: Optional[str] = None,
                                  limit: int = 100) -> AsyncIterator[dict]:
-        """Iterate over list of account subscriptions with optional filtering.
+        """Iterate over list of account subscriptions with optional filtering and sorting.
 
         Note:
             The name of this method is based on the API's method name.
@@ -71,14 +79,43 @@ class SubscriptionsClient:
             not return a list.
 
         Args:
-            status (Set[str]): pass subscriptions with status in this
-                set, filter out subscriptions with status not in this
-                set.
-            source_type (str): Product source type filter, to return only subscriptions with the matching source type
+            created (str): filter by created time or interval.
+            end_time (str): filter by end time or interval.
+            hosting (bool): only return subscriptions that contain a
+                hosting block (e.g. SentinelHub hosting).
+            name__contains (str): only return subscriptions with a
+                name that contains the given string.
+            name (str): filter by name.
+            source_type (str): filter by source type.
+            start_time (str): filter by start time or interval.
+            status (Set[str]): include subscriptions with a status in this set.
+            sort_by (str): fields to sort subscriptions by. Multiple
+                fields can be specified, separated by commas. The sort
+                direction can be specified by appending ' ASC' or '
+                DESC' to the field name. The default sort direction is
+                ascending. When multiple fields are specified, the sort
+                order is applied in the order the fields are listed.
+
+                Supported fields: name, created, updated, start_time, end_time
+
+                Examples:
+                 * "name"
+                 * "name DESC"
+                 * "name,end_time DESC,start_time"
+            updated (str): filter by updated time or interval.
             limit (int): limit the number of subscriptions in the
                 results. When set to 0, no maximum is applied.
             TODO: user_id
 
+        Datetime args (created, end_time, start_time, updated) can either be a
+        date-time or an interval, open or closed. Date and time expressions adhere
+        to RFC 3339. Open intervals are expressed using double-dots.
+
+        Examples:
+            * A date-time: "2018-02-12T23:20:50Z"
+            * A closed interval: "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"
+            * Open intervals: "2018-02-12T00:00:00Z/.." or "../2018-03-18T12:31:12Z"
+
         Yields:
             dict: a description of a subscription.
 
@@ -91,11 +128,27 @@ class SubscriptionsClient:
             """Navigates pages of messages about subscriptions."""
             ITEMS_KEY = 'subscriptions'
 
-        params: Dict[str, Union[str, Sequence[str]]] = {
-            'status': [val for val in status or {}]
-        }
+        params: Dict[str, Union[str, Sequence[str], bool]] = {}
+        if created is not None:
+            params['created'] = created
+        if end_time is not None:
+            params['end_time'] = end_time
+        if hosting is not None:
+            params['hosting'] = hosting
+        if name__contains is not None:
+            params['name__contains'] = name__contains
+        if name is not None:
+            params['name'] = name
         if source_type is not None:
-            params['source_type'] = source_type  # type: ignore
+            params['source_type'] = source_type
+        if start_time is not None:
+            params['start_time'] = start_time
+        if status is not None:
+            params['status'] = [val for val in status]
+        if sort_by is not None:
+            params['sort_by'] = sort_by
+        if updated is not None:
+            params['updated'] = updated
 
         try:
             response = await self._session.request(method='GET',
diff --git a/setup.cfg b/setup.cfg
index 896717a..49dc643 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -25,4 +25,4 @@ based_on_style = pep8
 split_all_top_level_comma_separated_values=true
 
 [flake8]
-ignore = E126,E501,W50
\ No newline at end of file
+ignore = E121,E126,E501,W50
\ No newline at end of file


Test patch:
diff --git a/tests/integration/test_orders_api.py b/tests/integration/test_orders_api.py
index 3470436..0257bf5 100644
--- a/tests/integration/test_orders_api.py
+++ b/tests/integration/test_orders_api.py
@@ -121,8 +121,8 @@ async def test_list_orders_basic(order_descriptions, session):
 
 @respx.mock
 @pytest.mark.anyio
-async def test_list_orders_state_success(order_descriptions, session):
-    list_url = TEST_ORDERS_URL + '?source_type=all&state=failed'
+async def test_list_orders_filtering_and_sorting(order_descriptions, session):
+    list_url = TEST_ORDERS_URL + '?source_type=all&state=failed&name=my_order_xyz&name__contains=xyz&created_on=2018-02-12T00:00:00Z/..&last_modified=../2018-03-18T12:31:12Z&hosting=true&sort_by=name DESC'
 
     order1, order2, _ = order_descriptions
 
@@ -136,10 +136,15 @@ async def test_list_orders_state_success(order_descriptions, session):
 
     cl = OrdersClient(session, base_url=TEST_URL)
 
-    # if the value of state doesn't get sent as a url parameter,
+    # if the value of each arg doesn't get sent as a url parameter,
     # the mock will fail and this test will fail
-    assert [order1,
-            order2] == [o async for o in cl.list_orders(state='failed')]
+    assert [order1, order2] == [
+        o async for o in cl.list_orders(
+            state='failed', name='my_order_xyz', name__contains='xyz',
+            created_on='2018-02-12T00:00:00Z/..',
+            last_modified='../2018-03-18T12:31:12Z', hosting=True,
+            sort_by='name DESC')
+    ]
 
 
 @pytest.mark.anyio
diff --git a/tests/integration/test_orders_cli.py b/tests/integration/test_orders_cli.py
index c873877..7b759df 100644
--- a/tests/integration/test_orders_cli.py
+++ b/tests/integration/test_orders_cli.py
@@ -84,8 +84,8 @@ def test_cli_orders_list_empty(invoke):
 
 
 @respx.mock
-def test_cli_orders_list_state(invoke, order_descriptions):
-    list_url = TEST_ORDERS_URL + '?source_type=all&state=failed'
+def test_cli_orders_list_filtering_and_sorting(invoke, order_descriptions):
+    list_url = TEST_ORDERS_URL + '?source_type=all&state=failed&name=my_order_xyz&name__contains=xyz&created_on=2018-02-12T00:00:00Z/..&last_modified=../2018-03-18T12:31:12Z&hosting=true&sort_by=name DESC'
 
     order1, order2, _ = order_descriptions
 
@@ -97,9 +97,25 @@ def test_cli_orders_list_state(invoke, order_descriptions):
     mock_resp = httpx.Response(HTTPStatus.OK, json=page1_response)
     respx.get(list_url).return_value = mock_resp
 
-    # if the value of state doesn't get sent as a url parameter,
+    # if the value of each arg doesn't get sent as a url parameter,
     # the mock will fail and this test will fail
-    result = invoke(['list', '--state', 'failed'])
+    result = invoke([
+        'list',
+        '--state',
+        'failed',
+        '--name',
+        'my_order_xyz',
+        '--name-contains',
+        'xyz',
+        '--created-on',
+        '2018-02-12T00:00:00Z/..',
+        '--last-modified',
+        '../2018-03-18T12:31:12Z',
+        '--hosting',
+        'true',
+        '--sort-by',
+        'name DESC'
+    ])
     assert result.exit_code == 0
     sequence = '\n'.join([json.dumps(o) for o in [order1, order2]])
     assert result.output == sequence + '\n'
diff --git a/tests/integration/test_subscriptions_api.py b/tests/integration/test_subscriptions_api.py
index d237b63..2b1c2f9 100644
--- a/tests/integration/test_subscriptions_api.py
+++ b/tests/integration/test_subscriptions_api.py
@@ -28,7 +28,7 @@ failing_api_mock.route(M(url__startswith=TEST_URL)).mock(
     }))
 
 
-def result_pages(status=None, size=40):
+def subscription_pages(status=None, size=40):
     """Helper for creating fake subscriptions listing pages."""
     all_subs = [{'id': str(i), 'status': 'running'} for i in range(1, 101)]
     select_subs = (sub for sub in all_subs
@@ -54,7 +54,7 @@ api_mock = respx.mock(assert_all_called=False)
 api_mock.route(M(url=TEST_URL),
                M(params__contains={'status': 'running'})).mock(side_effect=[
                    Response(200, json=page)
-                   for page in result_pages(status={'running'}, size=40)
+                   for page in subscription_pages(status={'running'}, size=40)
                ])
 
 # 2. Request for status: failed. Response has a single empty page.
@@ -71,7 +71,7 @@ api_mock.route(
     M(url=TEST_URL),
     M(params__contains={'source_type': 'catalog'})).mock(side_effect=[
         Response(200, json=page)
-        for page in result_pages(status={'running'}, size=40)
+        for page in subscription_pages(status={'running'}, size=40)
     ])
 
 # 5. source_type: soil_water_content requested. Response has a single empty page.
@@ -79,9 +79,32 @@ api_mock.route(M(url=TEST_URL),
                M(params__contains={'source_type': 'soil_water_content'})).mock(
                    side_effect=[Response(200, json={'subscriptions': []})])
 
-# 6. No status or source_type requested. Response is the same as for 1.
-api_mock.route(M(url=TEST_URL)).mock(
-    side_effect=[Response(200, json=page) for page in result_pages(size=40)])
+# 6. All other parameters are used. Response has 2 subscriptions.
+# The response is unrealistic here, but we are just testing the query parameter handling.
+api_mock.route(
+    M(url=TEST_URL),
+    M(
+        params__contains={
+            'name': 'test xyz',
+            'name__contains': 'xyz',
+            'created': '2018-02-12T00:00:00Z/..',
+            'updated': '../2018-03-18T12:31:12Z',
+            'start_time': '2018-01-01T00:00:00Z',
+            'end_time': '2022-01-01T00:00:00Z/2024-01-01T00:00:00Z',
+            'hosting': 'true',
+            'sort_by': 'name DESC',
+        })).mock(side_effect=[
+            Response(200, json={'subscriptions': [{
+                'id': 1
+            }, {
+                'id': 2
+            }]})
+        ])
+
+# 7. No status or source_type requested. Response is the same as for 1.
+api_mock.route(M(url=TEST_URL)).mock(side_effect=[
+    Response(200, json=page) for page in subscription_pages(size=40)
+])
 
 
 # The "creation", "update", and "cancel" mock APIs return submitted
@@ -213,6 +236,22 @@ async def test_list_subscriptions_source_type_success(
         ]) == count
 
 
+@pytest.mark.anyio
+@api_mock
+async def test_list_subscriptions_filtering_and_sorting():
+    async with Session() as session:
+        client = SubscriptionsClient(session, base_url=TEST_URL)
+        assert len([
+            sub async for sub in client.list_subscriptions(
+                name='test xyz', name__contains='xyz',
+                created='2018-02-12T00:00:00Z/..',
+                updated='../2018-03-18T12:31:12Z',
+                start_time='2018-01-01T00:00:00Z',
+                end_time='2022-01-01T00:00:00Z/2024-01-01T00:00:00Z',
+                hosting=True, sort_by='name DESC')
+        ]) == 2
+
+
 @pytest.mark.anyio
 @failing_api_mock
 async def test_create_subscription_failure():
diff --git a/tests/integration/test_subscriptions_cli.py b/tests/integration/test_subscriptions_cli.py
index 50bd822..c10f854 100644
--- a/tests/integration/test_subscriptions_cli.py
+++ b/tests/integration/test_subscriptions_cli.py
@@ -51,13 +51,24 @@ def invoke():
     return _invoke
 
 
-@pytest.mark.parametrize('options,expected_count',
-                         [(['--status=running'], 100), ([], 100),
-                          (['--source-type=catalog'], 100),
-                          (['--source-type=soil_water_content'], 0),
-                          (['--limit=1', '--status=running'], 1),
-                          (['--limit=2', '--pretty', '--status=running'], 2),
-                          (['--limit=1', '--status=preparing'], 0)])
+@pytest.mark.parametrize(
+    'options,expected_count',
+    [(['--status=running'], 100), ([], 100), (['--source-type=catalog'], 100),
+     (['--source-type=soil_water_content'], 0),
+     (['--limit=1', '--status=running'], 1),
+     (['--limit=2', '--pretty', '--status=running'], 2),
+     (['--limit=1', '--status=preparing'], 0),
+     ([
+         '--name=test xyz',
+         '--name-contains=xyz',
+         '--created=2018-02-12T00:00:00Z/..',
+         '--updated=../2018-03-18T12:31:12Z',
+         '--start-time=2018-01-01T00:00:00Z',
+         '--end-time=2022-01-01T00:00:00Z/2024-01-01T00:00:00Z',
+         '--hosting=true',
+         '--sort-by=name DESC'
+     ],
+      2)])
 @api_mock
 # Remember, parameters come before fixtures in the function definition.
 def test_subscriptions_list_options(invoke, options, expected_count):