Skip to content
This repository was archived by the owner on Apr 2, 2025. It is now read-only.

Commit 3a2d9c1

Browse files
feat: small fixes based on PR feedback. Creating more link creation methods to clean up endpoint business logic. tests: small tweaks to test based on PR feedback
1 parent c148b7d commit 3a2d9c1

File tree

6 files changed

+62
-94
lines changed

6 files changed

+62
-94
lines changed

src/stapi_fastapi/backends/root_backend.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ async def get_orders(
1515
self, request: Request, next: str | None, limit: int
1616
) -> ResultE[tuple[list[Order], Maybe[str]]]:
1717
"""
18-
Return a list of existing orders and pagination token if applicable
19-
No pagination will return empty string for token
18+
Return a list of existing orders and pagination token if applicable.
2019
"""
2120
...
2221

@@ -26,8 +25,8 @@ async def get_order(self, order_id: str, request: Request) -> ResultE[Maybe[Orde
2625
2726
Should return returns.results.Success[Order] if order is found.
2827
29-
Should return returns.results.Failure[returns.maybe.Nothing] if the order is
30-
not found or if access is denied.
28+
Should return returns.results.Failure[returns.maybe.Nothing] if the
29+
order is not found or if access is denied.
3130
3231
A Failure[Exception] will result in a 500.
3332
"""
@@ -37,7 +36,7 @@ async def get_order_statuses(
3736
self, order_id: str, request: Request, next: str | None, limit: int
3837
) -> ResultE[tuple[list[T], Maybe[str]]]:
3938
"""
40-
Get statuses for order with `order_id`.
39+
Get statuses for order with `order_id` and return pagination token if applicable
4140
4241
Should return returns.results.Success[list[OrderStatus]] if order is found.
4342

src/stapi_fastapi/routers/product_router.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -176,22 +176,12 @@ async def search_opportunities(
176176
self, search, request, next, limit
177177
):
178178
case Success((features, Some(pagination_token))):
179-
links.append(self.order_link(request, "create-order"))
180-
body = search.model_dump()
179+
links.append(self.order_link(request))
180+
body = search.model_dump(mode="json")
181181
body["next"] = pagination_token
182-
links.append(
183-
Link(
184-
href=str(
185-
request.url.remove_query_params(keys=["next", "limit"])
186-
),
187-
rel="next",
188-
type=TYPE_JSON,
189-
method="POST",
190-
body=body,
191-
)
192-
)
182+
links.append(self.pagination_link(request, body))
193183
case Success((features, Nothing)): # noqa: F841
194-
links.append(self.order_link(request, "create-order"))
184+
links.append(self.order_link(request))
195185
case Failure(e) if isinstance(e, ConstraintsException):
196186
raise e
197187
case Failure(e):
@@ -249,14 +239,23 @@ async def create_order(
249239
case x:
250240
raise AssertionError(f"Expected code to be unreachable {x}")
251241

252-
def order_link(self, request: Request, suffix: str):
242+
def order_link(self, request: Request):
253243
return Link(
254244
href=str(
255245
request.url_for(
256-
f"{self.root_router.name}:{self.product.id}:{suffix}",
246+
f"{self.root_router.name}:{self.product.id}:create-order",
257247
),
258248
),
259249
rel="create-order",
260250
type=TYPE_JSON,
261251
method="POST",
262252
)
253+
254+
def pagination_link(self, request: Request, body: dict[str, str | dict]):
255+
return Link(
256+
href=str(request.url.remove_query_params(keys=["next", "limit"])),
257+
rel="next",
258+
type=TYPE_JSON,
259+
method="POST",
260+
body=body,
261+
)

src/stapi_fastapi/routers/root_router.py

Lines changed: 32 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def __init__(
4242
self.conformances = conformances
4343
self.openapi_endpoint_name = openapi_endpoint_name
4444
self.docs_endpoint_name = docs_endpoint_name
45-
self.product_ids: list = []
45+
self.product_ids: list[str] = []
4646

4747
# A dict is used to track the product routers so we can ensure
4848
# idempotentcy in case a product is added multiple times, and also to
@@ -164,15 +164,7 @@ def get_products(
164164
),
165165
]
166166
if end > 0 and end < len(self.product_ids):
167-
links.append(
168-
Link(
169-
href=str(
170-
request.url.include_query_params(next=self.product_ids[end]),
171-
),
172-
rel="next",
173-
type=TYPE_JSON,
174-
)
175-
)
167+
links.append(self.pagination_link(request, self.product_ids[end]))
176168
return ProductsCollection(
177169
products=[
178170
self.product_routers[product_id].get_product(request)
@@ -184,36 +176,26 @@ def get_products(
184176
async def get_orders(
185177
self, request: Request, next: str | None = None, limit: int = 10
186178
) -> OrderCollection:
187-
# links: list[Link] = []
179+
links: list[Link] = []
188180
match await self.backend.get_orders(request, next, limit):
189181
case Success((orders, Some(pagination_token))):
190182
for order in orders:
191-
order.links.append(self.order_link(request, "get-order", order))
192-
links = [
193-
Link(
194-
href=str(
195-
request.url.include_query_params(next=pagination_token)
196-
),
197-
rel="next",
198-
type=TYPE_JSON,
199-
)
200-
]
183+
order.links.append(self.order_link(request, order))
184+
links.append(self.pagination_link(request, pagination_token))
201185
case Success((orders, Nothing)): # noqa: F841
202186
for order in orders:
203-
order.links.append(self.order_link(request, "get-order", order))
204-
links = []
187+
order.links.append(self.order_link(request, order))
188+
case Failure(ValueError()):
189+
raise NotFoundException(detail="Error finding pagination token")
205190
case Failure(e):
206191
logger.error(
207192
"An error occurred while retrieving orders: %s",
208193
traceback.format_exception(e),
209194
)
210-
if isinstance(e, ValueError):
211-
raise NotFoundException(detail="Error finding pagination token")
212-
else:
213-
raise HTTPException(
214-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
215-
detail="Error finding Orders",
216-
)
195+
raise HTTPException(
196+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
197+
detail="Error finding Orders",
198+
)
217199
case _:
218200
raise AssertionError("Expected code to be unreachable")
219201
return OrderCollection(features=orders, links=links)
@@ -251,34 +233,21 @@ async def get_order_statuses(
251233
links: list[Link] = []
252234
match await self.backend.get_order_statuses(order_id, request, next, limit):
253235
case Success((statuses, Some(pagination_token))):
254-
links.append(
255-
self.order_statuses_link(request, "list-order-statuses", order_id)
256-
)
257-
links.append(
258-
Link(
259-
href=str(
260-
request.url.include_query_params(next=pagination_token)
261-
),
262-
rel="next",
263-
type=TYPE_JSON,
264-
)
265-
)
236+
links.append(self.order_statuses_link(request, order_id))
237+
links.append(self.pagination_link(request, pagination_token))
266238
case Success((statuses, Nothing)): # noqa: F841
267-
links.append(
268-
self.order_statuses_link(request, "list-order-statuses", order_id)
269-
)
239+
links.append(self.order_statuses_link(request, order_id))
240+
case Failure(KeyError()):
241+
raise NotFoundException("Error finding pagination token")
270242
case Failure(e):
271243
logger.error(
272244
"An error occurred while retrieving order statuses: %s",
273245
traceback.format_exception(e),
274246
)
275-
if isinstance(e, KeyError):
276-
raise NotFoundException(detail="Error finding pagination token")
277-
else:
278-
raise HTTPException(
279-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
280-
detail="Error finding Order Statuses",
281-
)
247+
raise HTTPException(
248+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
249+
detail="Error finding Order Statuses",
250+
)
282251
case _:
283252
raise AssertionError("Expected code to be unreachable")
284253
return OrderStatuses(statuses=statuses, links=links)
@@ -314,21 +283,28 @@ def add_order_links(self, order: Order, request: Request):
314283
),
315284
)
316285

317-
def order_link(self, request: Request, link_suffix: str, order: Order):
286+
def order_link(self, request: Request, order: Order):
318287
return Link(
319-
href=str(request.url_for(f"{self.name}:{link_suffix}", order_id=order.id)),
288+
href=str(request.url_for(f"{self.name}:get-order", order_id=order.id)),
320289
rel="self",
321290
type=TYPE_JSON,
322291
)
323292

324-
def order_statuses_link(self, request: Request, link_suffix: str, order_id: str):
293+
def order_statuses_link(self, request: Request, order_id: str):
325294
return Link(
326295
href=str(
327296
request.url_for(
328-
f"{self.name}:{link_suffix}",
297+
f"{self.name}:list-order-statuses",
329298
order_id=order_id,
330299
)
331300
),
332301
rel="self",
333302
type=TYPE_JSON,
334303
)
304+
305+
def pagination_link(self, request: Request, pagination_token: str):
306+
return Link(
307+
href=str(request.url.include_query_params(next=pagination_token)),
308+
rel="next",
309+
type=TYPE_JSON,
310+
)

tests/conftest.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import copy
21
from collections.abc import Iterator
32
from typing import Any, Callable
43
from urllib.parse import urljoin
@@ -173,38 +172,35 @@ def pagination_tester(
173172

174173
assert len(resp_body[target]) <= limit
175174
retrieved.extend(resp_body[target])
176-
next_token = next(
177-
(d["href"] for d in resp_body["links"] if d["rel"] == "next"), None
178-
)
175+
next_url = next((d["href"] for d in resp_body["links"] if d["rel"] == "next"), None)
179176

180-
while next_token:
181-
url = copy.deepcopy(next_token)
177+
while next_url:
178+
url = next_url
182179
if method == "POST":
183-
next_token = next(
180+
next_url = next(
184181
(d["body"]["next"] for d in resp_body["links"] if d["rel"] == "next"),
185182
)
186183

187-
res = make_request(stapi_client, url, method, body, next_token, limit)
184+
res = make_request(stapi_client, url, method, body, next_url, limit)
188185
assert res.status_code == status.HTTP_200_OK
189186
assert len(resp_body[target]) <= limit
190187
resp_body = res.json()
191188
retrieved.extend(resp_body[target])
192189

193190
# get url w/ query params for next call if exists, and POST body if necessary
194191
if resp_body["links"]:
195-
next_token = next(
192+
next_url = next(
196193
(d["href"] for d in resp_body["links"] if d["rel"] == "next"), None
197194
)
198195
body = next(
199196
(d.get("body") for d in resp_body["links"] if d.get("body")),
200197
None,
201198
)
202199
else:
203-
next_token = None
200+
next_url = None
204201

205202
assert len(retrieved) == len(expected_returns)
206203
assert retrieved == expected_returns
207-
# assert retrieved[:2] == expected_returns[:2]
208204

209205

210206
def make_request(

tests/test_opportunity.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
@pytest.fixture
1919
def mock_test_spotlight_opportunities() -> list[Opportunity]:
2020
"""Fixture to create mock data for Opportunities for `test-spotlight-1`."""
21-
now = datetime.now(timezone.utc) # Use timezone-aware datetime
22-
start = now
21+
start = datetime.now(timezone.utc) # Use timezone-aware datetime
2322
end = start + timedelta(days=5)
2423

2524
# Create a list of mock opportunities for the given product
@@ -60,10 +59,9 @@ def test_search_opportunities_response(
6059
product_backend._opportunities = mock_test_spotlight_opportunities
6160

6261
now = datetime.now(UTC)
63-
start = now
64-
end = start + timedelta(days=5)
62+
end = now + timedelta(days=5)
6563
format = "%Y-%m-%dT%H:%M:%S.%f%z"
66-
start_string = rfc3339_strftime(start, format)
64+
start_string = rfc3339_strftime(now, format)
6765
end_string = rfc3339_strftime(end, format)
6866

6967
request_payload = {

tests/test_product.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_product_order_parameters_response(
6868

6969

7070
@pytest.mark.parametrize("limit", [0, 1, 2, 4])
71-
def test_product_pagination(
71+
def test_get_products_pagination(
7272
limit: int,
7373
stapi_client: TestClient,
7474
mock_product_test_spotlight,

0 commit comments

Comments
 (0)