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

Commit fd17110

Browse files
committed
fix: revert back to a clunky, but functional, opportunity search endpoint
1 parent f2b18e0 commit fd17110

2 files changed

Lines changed: 56 additions & 17 deletions

File tree

src/stapi_fastapi/models/opportunity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class OpportunitySearchStatus(BaseModel):
5959
class OpportunitySearchRecord(BaseModel):
6060
id: str
6161
product_id: str
62-
request: OpportunityRequest
62+
opportunity_request: OpportunityRequest
6363
status: OpportunitySearchStatus
6464
links: list[Link] = Field(default_factory=list)
6565

src/stapi_fastapi/routers/product_router.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Response,
1515
status,
1616
)
17+
from fastapi.responses import JSONResponse
1718
from geojson_pydantic.geometries import Geometry
1819
from returns.maybe import Maybe, Some
1920
from returns.result import Failure, Success
@@ -48,7 +49,7 @@ def get_preference(prefer: str | None = Header(None)) -> str | None:
4849
detail=f"Invalid Prefer header value: {prefer}",
4950
)
5051

51-
return prefer
52+
return Prefer(prefer)
5253

5354

5455
class ProductRouter(APIRouter):
@@ -129,7 +130,10 @@ async def _create_order(
129130
tags=["Products"],
130131
)
131132

132-
if product.supports_opportunity_search:
133+
if (
134+
product.supports_opportunity_search
135+
or root_router.supports_async_opportunity_search
136+
):
133137
self.add_api_route(
134138
path="/opportunities",
135139
endpoint=self.search_opportunities,
@@ -141,21 +145,17 @@ async def _create_order(
141145
Geometry,
142146
self.product.opportunity_properties, # type: ignore
143147
],
148+
responses={
149+
201: {
150+
"model": OpportunitySearchRecord,
151+
"content": {TYPE_JSON: {}},
152+
}
153+
},
144154
summary="Search Opportunities for the product",
145155
tags=["Products"],
146156
)
147157

148158
if root_router.supports_async_opportunity_search:
149-
self.add_api_route(
150-
path="/opportunities",
151-
endpoint=self.search_opportunities_async,
152-
name=f"{self.root_router.name}:{self.product.id}:search-opportunities",
153-
methods=["POST"],
154-
status_code=status.HTTP_201_CREATED,
155-
summary="Search Opportunities for the product",
156-
tags=["Products"],
157-
)
158-
159159
self.add_api_route(
160160
path="/opportunities/{opportunity_collection_id}",
161161
endpoint=self.get_opportunity_collection,
@@ -216,18 +216,19 @@ def get_product(self: Self, request: Request) -> Product:
216216
],
217217
)
218218

219-
async def search_opportunities(
219+
async def search_opportunities( # noqa: C901
220220
self: Self,
221221
search: OpportunityRequest,
222222
request: Request,
223-
response: GeoJSONResponse,
224-
prefer: str | None = Depends(get_preference),
223+
response: Response,
225224
next: Annotated[str | None, Body()] = None,
226225
limit: Annotated[int, Body()] = 10,
227-
) -> OpportunityCollection:
226+
prefer: str | None = Depends(get_preference),
227+
) -> OpportunityCollection | Response:
228228
"""
229229
Explore the opportunities available for a particular set of constraints
230230
"""
231+
# synchronous opportunities search
231232
if (
232233
not self.root_router.supports_async_opportunity_search
233234
or prefer is Prefer.wait
@@ -268,6 +269,44 @@ async def search_opportunities(
268269

269270
return OpportunityCollection(features=features, links=links)
270271

272+
# asynchronous opportunities search
273+
if (
274+
prefer is None
275+
or prefer is Prefer.respond_async
276+
or (prefer is Prefer.wait and not self.product.supports_opportunity_search)
277+
):
278+
match await self.product._search_opportunities_async(self, search, request):
279+
case Success(search_record):
280+
self.root_router.add_opportunity_search_record_self_link(
281+
search_record, request
282+
)
283+
headers = {}
284+
headers["Location"] = str(
285+
self.root_router.generate_opportunity_search_record_href(
286+
request, search_record.id
287+
)
288+
)
289+
if prefer is not None:
290+
headers["Preference-Applied"] = "respond-async"
291+
return JSONResponse(
292+
status_code=201,
293+
content=search_record.model_dump(mode="json"),
294+
headers=headers,
295+
)
296+
case Failure(e) if isinstance(e, ConstraintsException):
297+
raise e
298+
case Failure(e):
299+
logger.error(
300+
"An error occurred while initiating an asynchronous opportunity search: %s",
301+
traceback.format_exception(e),
302+
)
303+
raise HTTPException(
304+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
305+
detail="Error initiating an asynchronous opportunity search",
306+
)
307+
case y:
308+
raise AssertionError(f"Expected code to be unreachable: {y}")
309+
271310
raise AssertionError("Expected code to be unreachable")
272311

273312
async def search_opportunities_async(

0 commit comments

Comments
 (0)