1414 Response ,
1515 status ,
1616)
17+ from fastapi .responses import JSONResponse
1718from geojson_pydantic .geometries import Geometry
1819from returns .maybe import Maybe , Some
1920from 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
5455class 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