Skip to content

Commit b8be6a6

Browse files
authored
orders + subscriptions: support user_id query param (#1188)
* orders: support user_id query param * subscriptions: support user_id query param
1 parent a2b0930 commit b8be6a6

File tree

12 files changed

+154
-22
lines changed

12 files changed

+154
-22
lines changed

docs/cli/cli-orders.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ The `list` command supports filtering on a variety of fields:
8484
* `--last-modified`: Filter on the order's last modified time or an interval of last modified times.
8585
* `--hosting`: Filter on orders containing a hosting location (e.g. SentinelHub). Accepted values are `true` or `false`.
8686
* `--destination-ref`: Filter on orders created with the provided destination reference.
87+
* `--user-id`: Filter by user ID. Only available to organization admins. Accepts "all" or a specific user ID.
8788

8889
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.
8990
* A date-time: `2018-02-12T23:20:50Z`
@@ -120,6 +121,16 @@ To list orders with a name containing `xyz`:
120121
planet orders list --name-contains xyz
121122
```
122123

124+
To list orders for all users in your organization (organization admin only):
125+
```sh
126+
planet orders list --user-id all
127+
```
128+
129+
To list orders for a specific user ID (organization admin only):
130+
```sh
131+
planet orders list --user-id 12345
132+
```
133+
123134
#### Sorting
124135

125136
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).

docs/cli/cli-subscriptions.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ The `list` command supports filtering on a variety of fields:
145145
* `--status`: Filter on the status of the subscription. Status options include `running`, `cancelled`, `preparing`, `pending`, `completed`, `suspended`, and `failed`. Multiple status args are allowed.
146146
* `--updated`: Filter on the subscription update time or an interval of updated times.
147147
* `--destination-ref`: Filter on subscriptions created with the provided destination reference.
148+
* `--user-id`: Filter by user ID. Only available to organization admins. Accepts "all" or a specific user ID.
148149

149150
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.
150151
* A date-time: `2018-02-12T23:20:50Z`
@@ -171,6 +172,16 @@ To list subscriptions with an end time after Jan 1, 2025:
171172
planet subscriptions list --end-time 2025-01-01T00:00:00Z/..
172173
```
173174

175+
To list subscriptions for all users in your organization (organization admin only):
176+
```sh
177+
planet subscriptions list --user-id all
178+
```
179+
180+
To list subscriptions for a specific user ID (organization admin only):
181+
```sh
182+
planet subscriptions list --user-id 12345
183+
```
184+
174185
To list subscriptions with a hosting location:
175186
```sh
176187
planet subscriptions list --hosting true

planet/cli/orders.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ def orders(ctx, base_url):
135135
@click.option(
136136
'--destination-ref',
137137
help="Filter by orders created with the provided destination reference.")
138+
@click.option('--user-id',
139+
help="Filter by user ID. Accepts 'all' or a specific user ID.")
138140
@limit
139141
@pretty
140142
async def list(ctx,
@@ -147,6 +149,7 @@ async def list(ctx,
147149
hosting,
148150
sort_by,
149151
destination_ref,
152+
user_id,
150153
limit,
151154
pretty):
152155
"""List orders
@@ -167,6 +170,7 @@ async def list(ctx,
167170
hosting=hosting,
168171
sort_by=sort_by,
169172
destination_ref=destination_ref,
173+
user_id=user_id,
170174
limit=limit):
171175
echo_json(o, pretty)
172176

planet/cli/subscriptions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ def subscriptions(ctx, base_url):
110110
'--destination-ref',
111111
help="Filter subscriptions created with the provided destination reference."
112112
)
113+
@click.option('--user-id',
114+
help="Filter by user ID. Accepts 'all' or a specific user ID.")
113115
@limit
114116
@click.option('--page-size',
115117
type=click.INT,
@@ -130,6 +132,7 @@ async def list_subscriptions_cmd(ctx,
130132
updated,
131133
limit,
132134
destination_ref,
135+
user_id,
133136
page_size,
134137
pretty):
135138
"""Prints a sequence of JSON-encoded Subscription descriptions."""
@@ -146,7 +149,8 @@ async def list_subscriptions_cmd(ctx,
146149
'sort_by': sort_by,
147150
'updated': updated,
148151
'limit': limit,
149-
'destination_ref': destination_ref
152+
'destination_ref': destination_ref,
153+
'user_id': user_id
150154
}
151155
if page_size is not None:
152156
list_subscriptions_kwargs['page_size'] = page_size

planet/clients/orders.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import asyncio
1717
import logging
1818
import time
19-
from typing import AsyncIterator, Callable, Dict, List, Optional, Sequence, TypeVar, Union
19+
from typing import Any, AsyncIterator, Callable, Dict, List, Optional, TypeVar, Union
2020
import uuid
2121
import json
2222
import hashlib
@@ -474,7 +474,8 @@ async def list_orders(
474474
last_modified: Optional[str] = None,
475475
hosting: Optional[bool] = None,
476476
destination_ref: Optional[str] = None,
477-
sort_by: Optional[str] = None) -> AsyncIterator[dict]:
477+
sort_by: Optional[str] = None,
478+
user_id: Optional[Union[str, int]] = None) -> AsyncIterator[dict]:
478479
"""Iterate over the list of stored orders.
479480
480481
By default, order descriptions are sorted by creation date with the last created
@@ -510,6 +511,8 @@ async def list_orders(
510511
* "name"
511512
* "name DESC"
512513
* "name,state DESC,last_modified"
514+
user_id (str or int): filter by user ID. Only available to organization admins.
515+
Accepts "all" or a specific user ID.
513516
514517
Datetime args (created_on and last_modified) can either be a date-time or an
515518
interval, open or closed. Date and time expressions adhere to RFC 3339. Open
@@ -528,7 +531,7 @@ async def list_orders(
528531
planet.exceptions.ClientError: If state is not valid.
529532
"""
530533
url = self._orders_url()
531-
params: Dict[str, Union[str, Sequence[str], bool]] = {}
534+
params: Dict[str, Any] = {}
532535
if source_type is not None:
533536
params["source_type"] = source_type
534537
else:
@@ -547,6 +550,8 @@ async def list_orders(
547550
params["sort_by"] = sort_by
548551
if destination_ref is not None:
549552
params["destination_ref"] = destination_ref
553+
if user_id is not None:
554+
params["user_id"] = user_id
550555
if state:
551556
if state not in ORDER_STATE_SEQUENCE:
552557
raise exceptions.ClientError(

planet/clients/subscriptions.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Planet Subscriptions API Python client."""
22

33
import logging
4-
from typing import Any, AsyncIterator, Dict, Optional, Sequence, TypeVar, List
4+
from typing import Any, AsyncIterator, Dict, Optional, Sequence, TypeVar, List, Union
55

66
from typing_extensions import Literal
77

@@ -71,6 +71,7 @@ async def list_subscriptions(self,
7171
sort_by: Optional[str] = None,
7272
updated: Optional[str] = None,
7373
destination_ref: Optional[str] = None,
74+
user_id: Optional[Union[str, int]] = None,
7475
page_size: int = 500) -> AsyncIterator[dict]:
7576
"""Iterate over list of account subscriptions with optional filtering.
7677
@@ -108,6 +109,8 @@ async def list_subscriptions(self,
108109
updated (str): filter by updated time or interval.
109110
destination_ref (str): filter by subscriptions created with the
110111
provided destination reference.
112+
user_id (str or int): filter by user ID. Only available to organization admins.
113+
Accepts "all" or a specific user ID.
111114
page_size (int): number of subscriptions to return per page.
112115
113116
Datetime args (created, end_time, start_time, updated) can either be a
@@ -127,8 +130,6 @@ async def list_subscriptions(self,
127130
ClientError: on a client error.
128131
"""
129132

130-
# TODO from old doc string, which breaks strict document checking:
131-
# Add Parameter user_id
132133
class _SubscriptionsPager(Paged):
133134
"""Navigates pages of messages about subscriptions."""
134135
ITEMS_KEY = 'subscriptions'
@@ -156,6 +157,8 @@ class _SubscriptionsPager(Paged):
156157
params['updated'] = updated
157158
if destination_ref is not None:
158159
params['destination_ref'] = destination_ref
160+
if user_id is not None:
161+
params['user_id'] = user_id
159162

160163
params['page_size'] = page_size
161164

planet/sync/orders.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# License for the specific language governing permissions and limitations under
1414
# the License.
1515
"""Functionality for interacting with the orders api"""
16-
from typing import Any, Callable, Dict, Iterator, List, Optional
16+
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
1717

1818
from pathlib import Path
1919
from ..http import Session
@@ -252,17 +252,19 @@ def wait(self,
252252
return self._client._call_sync(
253253
self._client.wait(order_id, state, delay, max_attempts, callback))
254254

255-
def list_orders(self,
256-
state: Optional[str] = None,
257-
limit: int = 100,
258-
source_type: Optional[str] = None,
259-
name: Optional[str] = None,
260-
name__contains: Optional[str] = None,
261-
created_on: Optional[str] = None,
262-
last_modified: Optional[str] = None,
263-
hosting: Optional[bool] = None,
264-
destination_ref: Optional[str] = None,
265-
sort_by: Optional[str] = None) -> Iterator[dict]:
255+
def list_orders(
256+
self,
257+
state: Optional[str] = None,
258+
limit: int = 100,
259+
source_type: Optional[str] = None,
260+
name: Optional[str] = None,
261+
name__contains: Optional[str] = None,
262+
created_on: Optional[str] = None,
263+
last_modified: Optional[str] = None,
264+
hosting: Optional[bool] = None,
265+
destination_ref: Optional[str] = None,
266+
sort_by: Optional[str] = None,
267+
user_id: Optional[Union[str, int]] = None) -> Iterator[dict]:
266268
"""Iterate over the list of stored orders.
267269
268270
By default, order descriptions are sorted by creation date with the last created
@@ -296,6 +298,8 @@ def list_orders(self,
296298
* "name"
297299
* "name DESC"
298300
* "name,state DESC,last_modified"
301+
user_id (str or int): filter by user ID. Only available to organization admins.
302+
Accepts "all" or a specific user ID.
299303
limit (int): maximum number of results to return. When set to 0, no
300304
maximum is applied.
301305
@@ -320,4 +324,5 @@ def list_orders(self,
320324
last_modified,
321325
hosting,
322326
destination_ref,
323-
sort_by))
327+
sort_by,
328+
user_id))

planet/sync/subscriptions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def list_subscriptions(self,
4747
sort_by: Optional[str] = None,
4848
updated: Optional[str] = None,
4949
destination_ref: Optional[str] = None,
50+
user_id: Optional[Union[str, int]] = None,
5051
page_size: int = 500) -> Iterator[dict]:
5152
"""Iterate over list of account subscriptions with optional filtering.
5253
@@ -82,10 +83,11 @@ def list_subscriptions(self,
8283
updated (str): filter by updated time or interval.
8384
destination_ref (str): filter by subscriptions created with the
8485
provided destination reference.
86+
user_id (str or int): filter by user ID. Only available to organization admins.
87+
Accepts "all" or a specific user ID.
8588
limit (int): limit the number of subscriptions in the
8689
results. When set to 0, no maximum is applied.
8790
page_size (int): number of subscriptions to return per page.
88-
TODO: user_id
8991
9092
Datetime args (created, end_time, start_time, updated) can either be a
9193
date-time or an interval, open or closed. Date and time expressions adhere
@@ -117,6 +119,7 @@ def list_subscriptions(self,
117119
sort_by,
118120
updated,
119121
destination_ref,
122+
user_id,
120123
page_size))
121124

122125
def create_subscription(self, request: Dict) -> Dict:

tests/integration/test_orders_api.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,56 @@ async def test_list_orders_filtering_and_sorting(order_descriptions, session):
171171
]
172172

173173

174+
@respx.mock
175+
@pytest.mark.anyio
176+
@pytest.mark.parametrize("user_id", ["all", "123", 456])
177+
async def test_list_orders_user_id_filtering(order_descriptions,
178+
session,
179+
user_id):
180+
"""Test user_id parameter filtering for organization admins."""
181+
list_url = TEST_ORDERS_URL + f'?source_type=all&user_id={user_id}'
182+
183+
order1, order2, _ = order_descriptions
184+
185+
page1_response = {
186+
"_links": {
187+
"_self": "string"
188+
}, "orders": [order1, order2]
189+
}
190+
mock_resp = httpx.Response(HTTPStatus.OK, json=page1_response)
191+
respx.get(list_url).return_value = mock_resp
192+
193+
cl = OrdersClient(session, base_url=TEST_URL)
194+
195+
# if the value of user_id doesn't get sent as a url parameter,
196+
# the mock will fail and this test will fail
197+
assert [order1,
198+
order2] == [o async for o in cl.list_orders(user_id=user_id)]
199+
200+
201+
@respx.mock
202+
def test_list_orders_user_id_sync(order_descriptions, session):
203+
"""Test sync client user_id parameter for organization admins."""
204+
list_url = TEST_ORDERS_URL + '?source_type=all&user_id=all'
205+
206+
order1, order2, _ = order_descriptions
207+
208+
page1_response = {
209+
"_links": {
210+
"_self": "string"
211+
}, "orders": [order1, order2]
212+
}
213+
mock_resp = httpx.Response(HTTPStatus.OK, json=page1_response)
214+
respx.get(list_url).return_value = mock_resp
215+
216+
pl = Planet()
217+
pl.orders._client._base_url = TEST_URL
218+
219+
# if the value of user_id doesn't get sent as a url parameter,
220+
# the mock will fail and this test will fail
221+
assert [order1, order2] == list(pl.orders.list_orders(user_id='all'))
222+
223+
174224
@respx.mock
175225
def test_list_orders_state_success_sync(order_descriptions, session):
176226
list_url = TEST_ORDERS_URL + '?source_type=all&state=failed'

tests/integration/test_orders_cli.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,30 @@ def test_cli_orders_list_filtering_and_sorting(invoke, order_descriptions):
121121
assert result.output == sequence + '\n'
122122

123123

124+
@respx.mock
125+
@pytest.mark.parametrize("user_id", ["all", "123"])
126+
def test_cli_orders_list_user_id(invoke, order_descriptions, user_id):
127+
"""Test CLI user_id parameter for organization admins."""
128+
list_url = TEST_ORDERS_URL + f'?source_type=all&user_id={user_id}'
129+
130+
order1, order2, _ = order_descriptions
131+
132+
page1_response = {
133+
"_links": {
134+
"_self": "string"
135+
}, "orders": [order1, order2]
136+
}
137+
mock_resp = httpx.Response(HTTPStatus.OK, json=page1_response)
138+
respx.get(list_url).return_value = mock_resp
139+
140+
# if the value of user_id doesn't get sent as a url parameter,
141+
# the mock will fail and this test will fail
142+
result = invoke(['list', '--user-id', user_id])
143+
assert result.exit_code == 0
144+
sequence = '\n'.join([json.dumps(o) for o in [order1, order2]])
145+
assert result.output == sequence + '\n'
146+
147+
124148
@respx.mock
125149
@pytest.mark.parametrize("limit,limited_list_length", [(None, 100), (0, 102),
126150
(1, 1)])

0 commit comments

Comments
 (0)