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

Commit e2572dc

Browse files
committed
feat: convert backend protocols to callables
This change is in preparation for making product opportunity searching and ordering optional and for introducing optional asynchronous opportunity searching (which will require state). A few notable changes to the test suite: - The `backends` module has been removed from the `tests` directory. Its contents were never actually being used. The contents in `conftest` took precedence. - The in-memory storage that was attached to the MockProductBackend class is replaced with a FastAPI lifespan for storing state.
1 parent 0a5d877 commit e2572dc

File tree

12 files changed

+271
-297
lines changed

12 files changed

+271
-297
lines changed

src/stapi_fastapi/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from .backends import ProductBackend, RootBackend
1+
from .backends.product_backend import CreateOrder, SearchOpportunities
2+
from .backends.root_backend import GetOrder, GetOrders, GetOrderStatuses
23
from .models import (
34
Link,
45
OpportunityProperties,
@@ -9,13 +10,16 @@
910
from .routers import ProductRouter, RootRouter
1011

1112
__all__ = [
13+
"CreateOrder",
14+
"GetOrder",
15+
"GetOrders",
16+
"GetOrderStatuses",
1217
"Link",
1318
"OpportunityProperties",
1419
"Product",
15-
"ProductBackend",
1620
"ProductRouter",
1721
"Provider",
1822
"ProviderRole",
19-
"RootBackend",
2023
"RootRouter",
24+
"SearchOpportunities",
2125
]
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from .product_backend import ProductBackend
2-
from .root_backend import RootBackend
1+
from .product_backend import CreateOrder, SearchOpportunities
2+
from .root_backend import GetOrder, GetOrders, GetOrderStatuses
33

44
__all__ = [
5-
"ProductBackend",
6-
"RootBackend",
5+
"CreateOrder",
6+
"GetOrder",
7+
"GetOrders",
8+
"GetOrderStatuses",
9+
"SearchOpportunities",
710
]
Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Protocol
3+
from typing import Any, Callable, Coroutine
44

55
from fastapi import Request
66
from returns.result import ResultE
@@ -9,30 +9,42 @@
99
from stapi_fastapi.models.order import Order, OrderPayload
1010
from stapi_fastapi.routers.product_router import ProductRouter
1111

12-
13-
class ProductBackend(Protocol): # pragma: nocover
14-
async def search_opportunities(
15-
self,
16-
product_router: ProductRouter,
17-
search: OpportunityRequest,
18-
request: Request,
19-
) -> ResultE[list[Opportunity]]:
20-
"""
21-
Search for ordering opportunities for the given search parameters.
22-
23-
Backends must validate search constraints and return
24-
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
25-
"""
26-
27-
async def create_order(
28-
self,
29-
product_router: ProductRouter,
30-
search: OrderPayload,
31-
request: Request,
32-
) -> ResultE[Order]:
33-
"""
34-
Create a new order.
35-
36-
Backends must validate order payload and return
37-
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
38-
"""
12+
SearchOpportunities = Callable[
13+
[ProductRouter, OpportunityRequest, Request],
14+
Coroutine[Any, Any, ResultE[list[Opportunity]]],
15+
]
16+
"""
17+
Type alias for an async function that searches for ordering opportunities for the given
18+
search parameters.
19+
20+
Args:
21+
product_router (ProductRouter): The product router.
22+
search (OpportunityRequest): The search parameters.
23+
request (Request): FastAPI's Request object.
24+
25+
Returns:
26+
- Should return returns.result.Success[list[Opportunity]]
27+
- Returning returns.result.Failure[Exception] will result in a 500.
28+
29+
Backends must validate search constraints and return
30+
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
31+
"""
32+
33+
CreateOrder = Callable[
34+
[ProductRouter, OrderPayload, Request], Coroutine[Any, Any, ResultE[Order]]
35+
]
36+
"""
37+
Type alias for an async function that creates a new order.
38+
39+
Args:
40+
product_router (ProductRouter): The product router.
41+
payload (OrderPayload): The order payload.
42+
request (Request): FastAPI's Request object.
43+
44+
Returns:
45+
- Should return returns.result.Success[Order]
46+
- Returning returns.result.Failure[Exception] will result in a 500.
47+
48+
Backends must validate order payload and return
49+
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
50+
"""
Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Protocol
1+
from typing import Any, Callable, Coroutine, TypeVar
22

33
from fastapi import Request
44
from returns.maybe import Maybe
@@ -10,38 +10,48 @@
1010
OrderStatus,
1111
)
1212

13+
GetOrders = Callable[[Request], Coroutine[Any, Any, ResultE[OrderCollection]]]
14+
"""
15+
Type alias for an async function that returns a list of existing Orders.
1316
14-
class RootBackend[T: OrderStatus](Protocol): # pragma: nocover
15-
async def get_orders(self, request: Request) -> ResultE[OrderCollection]:
16-
"""
17-
Return a list of existing orders.
18-
"""
19-
...
17+
Args:
18+
request (Request): FastAPI's Request object.
2019
21-
async def get_order(self, order_id: str, request: Request) -> ResultE[Maybe[Order]]:
22-
"""
23-
Get details for order with `order_id`.
20+
Returns:
21+
- Should return returns.result.Success[OrderCollection]
22+
- Returning returns.result.Failure[Exception] will result in a 500.
23+
"""
2424

25-
Should return returns.results.Success[Order] if order is found.
25+
GetOrder = Callable[[str, Request], Coroutine[Any, Any, ResultE[Maybe[Order]]]]
26+
"""
27+
Type alias for an async function that gets details for the order with `order_id`.
2628
27-
Should return returns.results.Failure[returns.maybe.Nothing] if the order is
28-
not found or if access is denied.
29+
Args:
30+
order_id (str): The order ID.
31+
request (Request): FastAPI's Request object.
2932
30-
A Failure[Exception] will result in a 500.
31-
"""
32-
...
33+
Returns:
34+
- Should return returns.result.Success[Order] if order is found.
35+
- Should return returns.result.Failure[returns.maybe.Nothing] if the order is not
36+
found or if access is denied.
37+
- Returning returns.result.Failure[Exception] will result in a 500.
38+
"""
3339

34-
async def get_order_statuses(
35-
self, order_id: str, request: Request
36-
) -> ResultE[list[T]]:
37-
"""
38-
Get statuses for order with `order_id`.
3940

40-
Should return returns.results.Success[list[OrderStatus]] if order is found.
41+
T = TypeVar("T", bound=OrderStatus)
4142

42-
Should return returns.results.Failure[Exception] if the order is
43-
not found or if access is denied.
4443

45-
A Failure[Exception] will result in a 500.
46-
"""
47-
...
44+
GetOrderStatuses = Callable[[str, Request], Coroutine[Any, Any, ResultE[list[T]]]]
45+
"""
46+
Type alias for an async function that gets statuses for the order with `order_id`.
47+
48+
Args:
49+
order_id (str): The order ID.
50+
request (Request): FastAPI's Request object.
51+
52+
Returns:
53+
- Should return returns.result.Success[list[OrderStatus]] if order is found.
54+
- Should return returns.result.Failure[Exception] if the order is not found or if
55+
access is denied.
56+
- Returning returns.result.Failure[Exception] will result in a 500.
57+
"""

src/stapi_fastapi/models/product.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
from stapi_fastapi.models.shared import Link
1111

1212
if TYPE_CHECKING:
13-
from stapi_fastapi.backends.product_backend import ProductBackend
13+
from stapi_fastapi.backends.product_backend import (
14+
CreateOrder,
15+
SearchOpportunities,
16+
)
1417

1518

1619
type Constraints = BaseModel
@@ -50,26 +53,33 @@ class Product(BaseModel):
5053
_constraints: type[Constraints]
5154
_opportunity_properties: type[OpportunityProperties]
5255
_order_parameters: type[OrderParameters]
53-
_backend: ProductBackend
56+
_create_order: CreateOrder
57+
_search_opportunities: SearchOpportunities
5458

5559
def __init__(
5660
self,
5761
*args,
58-
backend: ProductBackend,
62+
create_order: CreateOrder,
63+
search_opportunities: SearchOpportunities,
5964
constraints: type[Constraints],
6065
opportunity_properties: type[OpportunityProperties],
6166
order_parameters: type[OrderParameters],
6267
**kwargs,
6368
) -> None:
6469
super().__init__(*args, **kwargs)
65-
self._backend = backend
70+
self._create_order = create_order
71+
self._search_opportunities = search_opportunities
6672
self._constraints = constraints
6773
self._opportunity_properties = opportunity_properties
6874
self._order_parameters = order_parameters
6975

7076
@property
71-
def backend(self: Self) -> ProductBackend:
72-
return self._backend
77+
def create_order(self: Self) -> CreateOrder:
78+
return self._create_order
79+
80+
@property
81+
def search_opportunities(self: Self) -> SearchOpportunities:
82+
return self._search_opportunities
7383

7484
@property
7585
def constraints(self: Self) -> type[Constraints]:

src/stapi_fastapi/routers/product_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ async def search_opportunities(
166166
"""
167167
Explore the opportunities available for a particular set of constraints
168168
"""
169-
match await self.product.backend.search_opportunities(self, search, request):
169+
match await self.product.search_opportunities(self, search, request):
170170
case Success(features):
171171
return OpportunityCollection(
172172
features=features,
@@ -214,7 +214,7 @@ async def create_order(
214214
"""
215215
Create a new order.
216216
"""
217-
match await self.product.backend.create_order(
217+
match await self.product.create_order(
218218
self,
219219
payload,
220220
request,

src/stapi_fastapi/routers/root_router.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from returns.maybe import Maybe, Some
88
from returns.result import Failure, Success
99

10-
from stapi_fastapi.backends.root_backend import RootBackend
10+
from stapi_fastapi.backends.root_backend import GetOrder, GetOrders, GetOrderStatuses
1111
from stapi_fastapi.constants import TYPE_GEOJSON, TYPE_JSON
1212
from stapi_fastapi.exceptions import NotFoundException
1313
from stapi_fastapi.models.conformance import CORE, Conformance
@@ -28,7 +28,9 @@
2828
class RootRouter(APIRouter):
2929
def __init__(
3030
self,
31-
backend: RootBackend,
31+
get_orders: GetOrders,
32+
get_order: GetOrder,
33+
get_order_statuses: GetOrderStatuses,
3234
conformances: list[str] = [CORE],
3335
name: str = "root",
3436
openapi_endpoint_name: str = "openapi",
@@ -37,7 +39,9 @@ def __init__(
3739
**kwargs,
3840
) -> None:
3941
super().__init__(*args, **kwargs)
40-
self.backend = backend
42+
self._get_orders = get_orders
43+
self._get_order = get_order
44+
self._get_order_statuses = get_order_statuses
4145
self.name = name
4246
self.conformances = conformances
4347
self.openapi_endpoint_name = openapi_endpoint_name
@@ -153,7 +157,7 @@ def get_products(self, request: Request) -> ProductsCollection:
153157
)
154158

155159
async def get_orders(self, request: Request) -> OrderCollection:
156-
match await self.backend.get_orders(request):
160+
match await self._get_orders(request):
157161
case Success(orders):
158162
for order in orders:
159163
order.links.append(
@@ -184,7 +188,7 @@ async def get_order(self: Self, order_id: str, request: Request) -> Order:
184188
"""
185189
Get details for order with `order_id`.
186190
"""
187-
match await self.backend.get_order(order_id, request):
191+
match await self._get_order(order_id, request):
188192
case Success(Some(order)):
189193
self.add_order_links(order, request)
190194
return order
@@ -206,7 +210,7 @@ async def get_order(self: Self, order_id: str, request: Request) -> Order:
206210
async def get_order_statuses(
207211
self: Self, order_id: str, request: Request
208212
) -> OrderStatuses:
209-
match await self.backend.get_order_statuses(order_id, request):
213+
match await self._get_order_statuses(order_id, request):
210214
case Success(statuses):
211215
return OrderStatuses(
212216
statuses=statuses,

0 commit comments

Comments
 (0)