Skip to content

Commit 115832f

Browse files
authored
Start of subscriptions client tests (#590)
* Start of subscriptions client tests More or less the same inputs as the CLI tests. Resolves #518 * Drop filter/lambda for generator expression A little test tweaking too, * Hoist placeholder implementation of create_subscription up and out It's now at the module root. Something more like the production code remains. * Finish hoisting * Raise APIError on simulated server errors * Rename PlaceholderSubscriptionsClient to SubscriptionsClient Still not connected to the API servers, saving that for next.
1 parent 0c80534 commit 115832f

File tree

4 files changed

+322
-110
lines changed

4 files changed

+322
-110
lines changed

planet/cli/subscriptions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from planet import Session
88
from planet.cli.cmds import coro, translate_exceptions
99
from planet.cli.io import echo_json
10-
from planet.clients.subscriptions import (PlaceholderSubscriptionsClient as
11-
SubscriptionsClient)
10+
from planet.clients.subscriptions import SubscriptionsClient
1211

1312

1413
@click.group()

planet/clients/subscriptions.py

Lines changed: 137 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,111 @@
44
from typing import AsyncIterator, Dict, Optional, Set
55
import uuid
66

7-
from planet.exceptions import ClientError
7+
from planet.exceptions import APIError
88

99
# Collections of fake subscriptions and results for testing. Tests will
1010
# monkeypatch these attributes.
11-
_fake_subs: Optional[Dict[str, dict]] = None
12-
_fake_sub_results: Optional[Dict[str, list]] = None
11+
_fake_subs: Dict[str, dict] = {}
12+
_fake_sub_results: Dict[str, list] = {}
1313

1414

15-
class PlaceholderSubscriptionsClient:
16-
"""A placeholder client.
15+
async def _server_subscriptions_post(request):
16+
missing_keys = {'name', 'delivery', 'source'} - request.keys()
17+
if missing_keys:
18+
raise RuntimeError(f"Request lacks required members: {missing_keys!r}")
1719

18-
This class and its methods are derived from tests of a skeleton
19-
Subscriptions CLI. It is evolving into the real API client.
20+
id = str(uuid.uuid4())
21+
_fake_subs[id] = request
22+
sub = _fake_subs[id].copy()
23+
sub.update(id=id)
24+
return sub
25+
26+
27+
async def _server_subscriptions_get(status=None, limit=None):
28+
select_subs = (dict(**sub, id=sub_id) for sub_id,
29+
sub in _fake_subs.items()
30+
if not status or sub['status'] in status)
31+
filtered_subs = itertools.islice(select_subs, limit)
32+
for sub in filtered_subs:
33+
yield sub
34+
35+
36+
async def _server_subscriptions_id_cancel_post(subscription_id):
37+
sub = _fake_subs.pop(subscription_id)
38+
sub.update(id=subscription_id)
39+
return sub
40+
41+
42+
async def _server_subscriptions_id_put(subscription_id, request):
43+
_fake_subs[subscription_id].update(**request)
44+
sub = _fake_subs[subscription_id].copy()
45+
sub.update(id=subscription_id)
46+
return sub
47+
48+
49+
async def _server_subscriptions_id_get(subscription_id):
50+
sub = _fake_subs[subscription_id].copy()
51+
sub.update(id=subscription_id)
52+
return sub
53+
54+
55+
async def _server_subscriptions_id_results_get(subscription_id,
56+
status=None,
57+
limit=None):
58+
select_results = (result for result in _fake_sub_results[subscription_id]
59+
if not status or result['status'] in status)
60+
filtered_results = itertools.islice(select_results, limit)
61+
for result in filtered_results:
62+
yield result
63+
64+
65+
class SubscriptionsClient:
66+
"""The Planet Subscriptions API client.
67+
68+
TODO: make requests to the production API. Currently, the backend
69+
is fake and exists at the top of this class's module.
2070
2171
"""
2272

2373
def __init__(self, session=None) -> None:
2474
self._session = session
2575

2676
async def list_subscriptions(self,
27-
status: Set[str] = None,
77+
status: Optional[Set[str]] = None,
2878
limit: int = 100) -> AsyncIterator[dict]:
2979
"""Get account subscriptions with optional filtering.
3080
31-
The name of this method is based on the API's method name. This
32-
method provides iteration over subcriptions, it does not return
33-
a list.
81+
Note:
82+
The name of this method is based on the API's method name. This
83+
method provides iteration over subcriptions, it does not return
84+
a list.
3485
3586
Args:
3687
status (Set[str]): pass subscriptions with status in this
3788
set, filter out subscriptions with status not in this
3889
set.
3990
limit (int): limit the number of subscriptions in the
4091
results.
92+
TODO: user_id
4193
4294
Yields:
4395
dict: a description of a subscription.
4496
4597
Raises:
46-
ClientError
98+
APIError: on an API server error.
99+
ClientError: on a client error.
47100
48101
"""
49-
# Temporary marker for behavior of module with unpatched state.
50-
if _fake_subs is None:
51-
raise NotImplementedError
52-
53-
if status:
54-
select_subs = (dict(**sub, id=sub_id) for sub_id,
55-
sub in _fake_subs.items()
56-
if sub['status'] in status)
57-
else:
58-
select_subs = (
59-
dict(**sub, id=sub_id) for sub_id, sub in _fake_subs.items())
60-
61-
filtered_subs = itertools.islice(select_subs, limit)
62-
63-
for sub in filtered_subs:
64-
yield sub
102+
try:
103+
# TODO: replace with httpx request.
104+
async for sub in _server_subscriptions_get(status=status,
105+
limit=limit):
106+
yield sub
107+
except Exception as server_error:
108+
# TODO: remove "from server_error" clause. It's useful
109+
# during development but may invite users to depend on the
110+
# value of server_error.
111+
raise APIError("Subscription failure") from server_error
65112

66113
async def create_subscription(self, request: dict) -> dict:
67114
"""Create a Subscription.
@@ -73,23 +120,20 @@ async def create_subscription(self, request: dict) -> dict:
73120
dict: description of created subscription.
74121
75122
Raises:
76-
ClientError
123+
APIError: on an API server error.
124+
ClientError: on a client error.
77125
78126
"""
79-
# Temporary marker for behavior of module with unpatched state.
80-
if _fake_subs is None:
81-
raise NotImplementedError
82-
83-
missing_keys = {'name', 'delivery', 'source'} - request.keys()
84-
if missing_keys:
85-
raise ClientError(
86-
f"Request lacks required members: {missing_keys!r}")
87-
88-
id = str(uuid.uuid4())
89-
_fake_subs[id] = request
90-
sub = _fake_subs[id].copy()
91-
sub.update(id=id)
92-
return sub
127+
try:
128+
# TODO: replace with httpx request.
129+
sub = await _server_subscriptions_post(request)
130+
except Exception as server_error:
131+
# TODO: remove "from server_error" clause. It's useful
132+
# during development but may invite users to depend on the
133+
# value of server_error.
134+
raise APIError("Subscription failure") from server_error
135+
else:
136+
return sub
93137

94138
async def cancel_subscription(self, subscription_id: str) -> dict:
95139
"""Cancel a Subscription.
@@ -101,20 +145,20 @@ async def cancel_subscription(self, subscription_id: str) -> dict:
101145
dict: description of cancelled subscription.
102146
103147
Raises:
104-
ClientError
148+
APIError: on an API server error.
149+
ClientError: on a client error.
105150
106151
"""
107-
# Temporary marker for behavior of module with unpatched state.
108-
if _fake_subs is None:
109-
raise NotImplementedError
110-
111152
try:
112-
sub = _fake_subs.pop(subscription_id)
113-
except KeyError:
114-
raise ClientError(f"No such subscription: {subscription_id!r}")
115-
116-
sub.update(id=subscription_id)
117-
return sub
153+
# TODO: replace with httpx request.
154+
sub = await _server_subscriptions_id_cancel_post(subscription_id)
155+
except Exception as server_error:
156+
# TODO: remove "from server_error" clause. It's useful
157+
# during development but may invite users to depend on the
158+
# value of server_error.
159+
raise APIError("Subscription failure.") from server_error
160+
else:
161+
return sub
118162

119163
async def update_subscription(self, subscription_id: str,
120164
request: dict) -> dict:
@@ -128,21 +172,20 @@ async def update_subscription(self, subscription_id: str,
128172
dict: description of the updated subscription.
129173
130174
Raises:
131-
ClientError
175+
APIError: on an API server error.
176+
ClientError: on a client error.
132177
133178
"""
134-
# Temporary marker for behavior of module with unpatched state.
135-
if _fake_subs is None:
136-
raise NotImplementedError
137-
138179
try:
139-
_fake_subs[subscription_id].update(**request)
140-
sub = _fake_subs[subscription_id].copy()
141-
except KeyError:
142-
raise ClientError(f"No such subscription: {subscription_id!r}")
143-
144-
sub.update(id=subscription_id)
145-
return sub
180+
# TODO: replace with httpx request.
181+
sub = await _server_subscriptions_id_put(subscription_id, request)
182+
except Exception as server_error:
183+
# TODO: remove "from server_error" clause. It's useful
184+
# during development but may invite users to depend on the
185+
# value of server_error.
186+
raise APIError("Subscription failure.") from server_error
187+
else:
188+
return sub
146189

147190
async def get_subscription(self, subscription_id: str) -> dict:
148191
"""Get a description of a Subscription.
@@ -154,61 +197,55 @@ async def get_subscription(self, subscription_id: str) -> dict:
154197
dict: description of the subscription.
155198
156199
Raises:
157-
ClientError
200+
APIError: on an API server error.
201+
ClientError: on a client error.
158202
159203
"""
160-
# Temporary marker for behavior of module with unpatched state.
161-
if _fake_subs is None:
162-
raise NotImplementedError
163-
164204
try:
165-
sub = _fake_subs[subscription_id].copy()
166-
except KeyError:
167-
raise ClientError(f"No such subscription: {subscription_id!r}")
168-
169-
sub.update(id=subscription_id)
170-
return sub
205+
# TODO: replace with httpx request.
206+
sub = await _server_subscriptions_id_get(subscription_id)
207+
except Exception as server_error:
208+
# TODO: remove "from server_error" clause. It's useful
209+
# during development but may invite users to depend on the
210+
# value of server_error.
211+
raise APIError("Subscription failure.") from server_error
212+
else:
213+
return sub
171214

172215
async def get_results(self,
173216
subscription_id: str,
174-
status: Set[str] = None,
217+
status: Optional[Set[str]] = None,
175218
limit: int = 100) -> AsyncIterator[dict]:
176219
"""Get Results of a Subscription.
177220
178-
The name of this method is based on the API's method name. This
179-
method provides iteration over results, it does not get a
180-
single result description or return a list of descriptions.
221+
Note:
222+
The name of this method is based on the API's method name. This
223+
method provides iteration over results, it does not get a
224+
single result description or return a list of descriptions.
181225
182226
Args:
183227
subscription_id (str): id of a subscription.
184228
status (Set[str]): pass result with status in this set,
185229
filter out results with status not in this set.
186230
limit (int): limit the number of subscriptions in the
187231
results.
232+
TODO: created, updated, completed, user_id
188233
189234
Yields:
190235
dict: description of a subscription results.
191236
192237
Raises:
193-
ClientError
238+
APIError: on an API server error.
239+
ClientError: on a client error.
194240
195241
"""
196-
# Temporary marker for behavior of module with unpatched state.
197-
if _fake_sub_results is None:
198-
raise NotImplementedError
199-
200242
try:
201-
if status:
202-
select_results = (
203-
result for result in _fake_sub_results[subscription_id]
204-
if result['status'] in status)
205-
else:
206-
select_results = (
207-
result for result in _fake_sub_results[subscription_id])
208-
209-
filtered_results = itertools.islice(select_results, limit)
210-
except KeyError:
211-
raise ClientError(f"No such subscription: {subscription_id!r}")
212-
213-
for result in filtered_results:
214-
yield result
243+
# TODO: replace with httpx request.
244+
async for result in _server_subscriptions_id_results_get(
245+
subscription_id, status=status, limit=limit):
246+
yield result
247+
except Exception as server_error:
248+
# TODO: remove "from server_error" clause. It's useful
249+
# during development but may invite users to depend on the
250+
# value of server_error.
251+
raise APIError("Subscription failure.") from server_error

0 commit comments

Comments
 (0)