1313 get_common_deps ,
1414 get_content_type ,
1515)
16- from middleware .api .business_logic import BusinessLogic
16+ from middleware .api .business_logic import BusinessLogic , ConflictError
17+ from middleware .api .business_logic .exceptions import DuplicateArcInHarvestError
1718from middleware .api .document_store .harvest_document import HarvestDocument
1819from middleware .shared .api_models .v3 import models as v3_models
1920
@@ -88,42 +89,77 @@ async def get_harvest(
8889 return _map_harvest (harvest )
8990
9091
91- @router .post ("/{harvest_id}/complete" , response_model = v3_models .HarvestResponse )
92+ @router .post ("/{harvest_id}/complete" , response_model = v3_models .HarvestResponse , deprecated = True )
9293async def complete_harvest ( # noqa: PLR0913, PLR0917
9394 request : Request ,
9495 harvest_id : str ,
9596 bl : Annotated [BusinessLogic , Depends (get_business_logic )],
9697 deps : Annotated [CommonApiDependencies , Depends (get_common_deps )],
9798 client_id : Annotated [str | None , Depends (get_client_id )],
9899) -> v3_models .HarvestResponse :
99- """Mark a harvest as completed."""
100+ """Mark a harvest as completed. [DEPRECATED].
101+
102+ Use ``PATCH /v3/harvests/{harvest_id}`` with ``status=COMPLETED`` instead.
103+ """
100104 # Fetch once — used for both RDI auth and passed to complete_harvest (C2).
101105 harvest = await bl .harvest_manager .get_harvest (harvest_id )
102106 if not harvest :
103107 raise HTTPException (status_code = HTTPStatus .NOT_FOUND , detail = "Harvest not found" )
104108
105109 await deps .validate_rdi_authorized (harvest .rdi , request )
106110
107- harvest = await bl .harvest_manager .complete_harvest (harvest_id , client_id = client_id , pre_fetched = harvest )
111+ harvest = await bl .harvest_manager .complete_harvest (harvest , client_id = client_id )
108112 return _map_harvest (harvest )
109113
110114
111- @router .delete ("/{harvest_id}" , status_code = HTTPStatus .NO_CONTENT )
115+ @router .patch ("/{harvest_id}" , response_model = v3_models .HarvestResponse )
116+ async def patch_harvest_status ( # noqa: PLR0913, PLR0917
117+ request : Request ,
118+ harvest_id : str ,
119+ request_body : v3_models .PatchHarvestRequest ,
120+ bl : Annotated [BusinessLogic , Depends (get_business_logic )],
121+ deps : Annotated [CommonApiDependencies , Depends (get_common_deps )],
122+ client_id : Annotated [str | None , Depends (get_client_id )],
123+ _content_type : Annotated [None , Depends (get_content_type )],
124+ ) -> v3_models .HarvestResponse :
125+ """Transition a harvest run to a terminal status (COMPLETED, CANCELLED, or FAILED).
126+
127+ Only a ``RUNNING`` harvest may be transitioned; any other current status returns
128+ 409 Conflict.
129+ """
130+ harvest = await bl .harvest_manager .get_harvest (harvest_id )
131+ if not harvest :
132+ raise HTTPException (status_code = HTTPStatus .NOT_FOUND , detail = "Harvest not found" )
133+
134+ await deps .validate_rdi_authorized (harvest .rdi , request )
135+
136+ try :
137+ harvest = await bl .harvest_manager .transition_harvest (harvest , request_body .status , client_id = client_id )
138+ except ConflictError as exc :
139+ raise HTTPException (status_code = HTTPStatus .CONFLICT , detail = str (exc )) from exc
140+
141+ return _map_harvest (harvest )
142+
143+
144+ @router .delete ("/{harvest_id}" , status_code = HTTPStatus .NO_CONTENT , deprecated = True )
112145async def cancel_harvest (
113146 request : Request ,
114147 harvest_id : str ,
115148 bl : Annotated [BusinessLogic , Depends (get_business_logic )],
116149 deps : Annotated [CommonApiDependencies , Depends (get_common_deps )],
117150 client_id : Annotated [str | None , Depends (get_client_id )],
118151) -> None :
119- """Cancel a harvest run."""
152+ """Cancel a harvest run. [DEPRECATED].
153+
154+ Use ``PATCH /v3/harvests/{harvest_id}`` with ``status=CANCELLED`` instead.
155+ """
120156 # Fetch once — used for both RDI auth and passed to cancel_harvest (C2).
121157 harvest = await bl .harvest_manager .get_harvest (harvest_id )
122158 if not harvest :
123159 raise HTTPException (status_code = HTTPStatus .NOT_FOUND , detail = "Harvest not found" )
124160
125161 await deps .validate_rdi_authorized (harvest .rdi , request )
126- await bl .harvest_manager .cancel_harvest (harvest_id , client_id = client_id , pre_fetched = harvest )
162+ await bl .harvest_manager .cancel_harvest (harvest , client_id = client_id )
127163
128164
129165@router .post ("/{harvest_id}/arcs" , response_model = v3_models .ArcResponse )
@@ -146,7 +182,10 @@ async def submit_arc_in_harvest( # noqa: PLR0913, PLR0917
146182 rdi = harvest .rdi
147183 await deps .validate_rdi_authorized (rdi , request )
148184
149- result = await bl .create_or_update_arc (rdi , request_body .arc , client_id , harvest_id = harvest_id )
185+ try :
186+ result = await bl .create_or_update_arc (rdi , request_body .arc , client_id , harvest_id = harvest_id )
187+ except DuplicateArcInHarvestError as exc :
188+ raise HTTPException (status_code = HTTPStatus .CONFLICT , detail = str (exc )) from exc
150189
151190 arc_id = result .arc .id
152191 metadata = await bl .get_metadata (arc_id )
0 commit comments