Skip to content

Commit 2177989

Browse files
authored
Add support for new subscriptions summary endpoints (#1098)
* add support for new subscriptions summary endpoints * fix summary cli tests * split client function into two separate for different return structs
1 parent 65b7792 commit 2177989

File tree

6 files changed

+277
-1
lines changed

6 files changed

+277
-1
lines changed

docs/cli/cli-subscriptions.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,4 +555,19 @@ planet subscriptions request \
555555
```
556556

557557
For more information on Sentinel Hub hosting, see the [Subscriptions API documentation](https://developers.planet.com/docs/subscriptions/delivery/#delivery-to-sentinel-hub-collection) and the [Linking Planet User to Sentinel Hub User
558-
](https://support.planet.com/hc/en-us/articles/16550358397469-Linking-Planet-User-to-Sentinel-Hub-User) support post.
558+
](https://support.planet.com/hc/en-us/articles/16550358397469-Linking-Planet-User-to-Sentinel-Hub-User) support post.
559+
560+
561+
#### Summaries
562+
563+
You can get two types of summaries using the CLI.
564+
565+
The first is a summary of all subscriptions created by the user, totaled by status.
566+
```sh
567+
planet subscriptions summarize
568+
```
569+
570+
The second is a summary of _results_ for a specified subscription, totaled by status.
571+
```sh
572+
planet subscriptions summarize --subscription-id=cb817760-1f07-4ee7-bba6-bcac5346343f
573+
```

planet/cli/subscriptions.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,24 @@ def item_types(ctx):
526526
click.echo("Valid item types:")
527527
for it in get_item_types():
528528
click.echo(f"- {it}")
529+
530+
531+
@subscriptions.command() # type: ignore
532+
@click.option(
533+
'--subscription-id',
534+
default=None,
535+
help="""Optionally supply a subscription ID to summarize result counts
536+
by status. If omitted, the summary will be generated for all
537+
subscriptions the requester has created by status.""")
538+
@pretty
539+
@click.pass_context
540+
@translate_exceptions
541+
@coro
542+
async def summarize(ctx, subscription_id, pretty):
543+
"""Summarize the status of all subscriptions or the status of results for a single subscription"""
544+
async with subscriptions_client(ctx) as client:
545+
if subscription_id:
546+
summary = await client.get_subscription_summary(subscription_id)
547+
else:
548+
summary = await client.get_summary()
549+
echo_json(summary, pretty)

planet/clients/subscriptions.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,57 @@ async def get_results_csv(
407407
async with self._session.stream('GET', url, params=params) as response:
408408
async for line in response.aiter_lines():
409409
yield line
410+
411+
async def get_summary(self) -> dict:
412+
"""Summarize the status of all subscriptions via GET.
413+
414+
Returns:
415+
dict: subscription totals by status.
416+
417+
Raises:
418+
APIError: on an API server error.
419+
ClientError: on a client error.
420+
"""
421+
url = f'{self._base_url}/summary'
422+
423+
try:
424+
resp = await self._session.request(method='GET', url=url)
425+
# Forward APIError. We don't strictly need this clause, but it
426+
# makes our intent clear.
427+
except APIError:
428+
raise
429+
except ClientError: # pragma: no cover
430+
raise
431+
else:
432+
summary = resp.json()
433+
return summary
434+
435+
async def get_subscription_summary(
436+
self,
437+
subscription_id: str,
438+
) -> dict:
439+
"""Summarize the status of results for a single subscription via GET.
440+
441+
Args
442+
subscription_id (str): ID of the subscription to summarize.
443+
444+
Returns:
445+
dict: result totals for the provided subscription by status.
446+
447+
Raises:
448+
APIError: on an API server error.
449+
ClientError: on a client error.
450+
"""
451+
url = f'{self._base_url}/{subscription_id}/summary'
452+
453+
try:
454+
resp = await self._session.request(method='GET', url=url)
455+
# Forward APIError. We don't strictly need this clause, but it
456+
# makes our intent clear.
457+
except APIError:
458+
raise
459+
except ClientError: # pragma: no cover
460+
raise
461+
else:
462+
summary = resp.json()
463+
return summary

planet/sync/subscriptions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,31 @@ def get_results_csv(
280280
yield self._client._call_sync(results.__anext__())
281281
except StopAsyncIteration:
282282
pass
283+
284+
def get_summary(self) -> Dict[str, Any]:
285+
"""Summarize the status of all subscriptions via GET.
286+
287+
Returns:
288+
dict: subscription totals by status.
289+
290+
Raises:
291+
APIError: on an API server error.
292+
ClientError: on a client error.
293+
"""
294+
return self._client._call_sync(self._client.get_summary())
295+
296+
def get_subscription_summary(self, subscription_id: str) -> Dict[str, Any]:
297+
"""Summarize the status of results for a single subscription via GET.
298+
299+
Args
300+
subscription_id (str): ID of the subscription to summarize.
301+
302+
Returns:
303+
dict: result totals for the provided subscription by status.
304+
305+
Raises:
306+
APIError: on an API server error.
307+
ClientError: on a client error.
308+
"""
309+
return self._client._call_sync(
310+
self._client.get_subscription_summary(subscription_id))

tests/integration/test_subscriptions_api.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,40 @@ def modify_response(request):
146146
'source': 'test'
147147
}))
148148

149+
summary_mock = respx.mock()
150+
summary_mock.route(
151+
M(url=f'{TEST_URL}/summary'),
152+
method='GET').mock(return_value=Response(200,
153+
json={
154+
"subscriptions": {
155+
"preparing": 0,
156+
"pending": 0,
157+
"running": 0,
158+
"completed": 0,
159+
"cancelled": 0,
160+
"suspended": 0,
161+
"failed": 0
162+
}
163+
}))
164+
165+
sub_summary_mock = respx.mock()
166+
sub_summary_mock.route(
167+
M(url=f'{TEST_URL}/test/summary'),
168+
method='GET').mock(return_value=Response(200,
169+
json={
170+
"results": {
171+
"created": 0,
172+
"queued": 0,
173+
"processing": 0,
174+
"failed": 0,
175+
"cancelled": 0,
176+
"success": 0
177+
},
178+
"subscription": {
179+
"status": "pending"
180+
}
181+
}))
182+
149183

150184
def result_pages(status=None, size=40):
151185
"""Helper for creating fake result listing pages."""
@@ -526,3 +560,85 @@ async def test_list_subscriptions_cycle_break():
526560
async with Session() as session:
527561
client = SubscriptionsClient(session, base_url=TEST_URL)
528562
_ = [sub async for sub in client.list_subscriptions()]
563+
564+
565+
@pytest.mark.anyio
566+
@summary_mock
567+
async def test_get_summary():
568+
"""Summary fetched, has expected totals, async."""
569+
async with Session() as session:
570+
client = SubscriptionsClient(session, base_url=TEST_URL)
571+
summary = await client.get_summary()
572+
assert summary == {
573+
"subscriptions": {
574+
"preparing": 0,
575+
"pending": 0,
576+
"running": 0,
577+
"completed": 0,
578+
"cancelled": 0,
579+
"suspended": 0,
580+
"failed": 0
581+
}
582+
}
583+
584+
585+
@summary_mock
586+
def test_get_summary_sync():
587+
"""Summary fetched, has expected totals, sync."""
588+
pl = Planet()
589+
pl.subscriptions._client._base_url = TEST_URL
590+
summary = pl.subscriptions.get_summary()
591+
assert summary == {
592+
"subscriptions": {
593+
"preparing": 0,
594+
"pending": 0,
595+
"running": 0,
596+
"completed": 0,
597+
"cancelled": 0,
598+
"suspended": 0,
599+
"failed": 0
600+
}
601+
}
602+
603+
604+
@pytest.mark.anyio
605+
@sub_summary_mock
606+
async def test_get_sub_summary():
607+
"""Subscription summary fetched, has expected totals, async."""
608+
async with Session() as session:
609+
client = SubscriptionsClient(session, base_url=TEST_URL)
610+
summary = await client.get_subscription_summary("test")
611+
assert summary == {
612+
"results": {
613+
"created": 0,
614+
"queued": 0,
615+
"processing": 0,
616+
"failed": 0,
617+
"cancelled": 0,
618+
"success": 0
619+
},
620+
"subscription": {
621+
"status": "pending"
622+
}
623+
}
624+
625+
626+
@sub_summary_mock
627+
def test_get_sub_summary_sync():
628+
"""Subscription summary fetched, has expected totals, sync."""
629+
pl = Planet()
630+
pl.subscriptions._client._base_url = TEST_URL
631+
summary = pl.subscriptions.get_subscription_summary("test")
632+
assert summary == {
633+
"results": {
634+
"created": 0,
635+
"queued": 0,
636+
"processing": 0,
637+
"failed": 0,
638+
"cancelled": 0,
639+
"success": 0
640+
},
641+
"subscription": {
642+
"status": "pending"
643+
}
644+
}

tests/integration/test_subscriptions_cli.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
get_mock,
3030
patch_mock,
3131
res_api_mock,
32+
sub_summary_mock,
33+
summary_mock,
3234
update_mock,
3335
TEST_URL)
3436

@@ -523,3 +525,43 @@ def test_item_types(invoke, mock_bundles):
523525
for item_type in expected_item_types:
524526
assert item_type in result.output
525527
assert result.exit_code == 0
528+
529+
530+
@summary_mock
531+
def test_summarize_subs(invoke):
532+
result = invoke(['summarize'])
533+
534+
assert result.exit_code == 0 # success.
535+
summary = json.loads(result.output)
536+
assert summary == {
537+
"subscriptions": {
538+
"preparing": 0,
539+
"pending": 0,
540+
"running": 0,
541+
"completed": 0,
542+
"cancelled": 0,
543+
"suspended": 0,
544+
"failed": 0
545+
}
546+
}
547+
548+
549+
@sub_summary_mock
550+
def test_summarize_results(invoke):
551+
result = invoke(['summarize', '--subscription-id=test'])
552+
553+
assert result.exit_code == 0 # success.
554+
summary = json.loads(result.output)
555+
assert summary == {
556+
"results": {
557+
"created": 0,
558+
"queued": 0,
559+
"processing": 0,
560+
"failed": 0,
561+
"cancelled": 0,
562+
"success": 0
563+
},
564+
"subscription": {
565+
"status": "pending"
566+
}
567+
}

0 commit comments

Comments
 (0)