Skip to content

Commit 0f92411

Browse files
authored
feat: add manual retry state apis (#405)
* feat: add manual retry state functionality - Introduced a new API endpoint for manual state retries, allowing users to trigger retries for specific states. - Implemented request and response models for manual retry operations. - Added error handling for duplicate retry states and invalid API keys. - Enhanced logging for better traceability of retry operations. This feature improves the system's ability to manage state retries effectively. * fix: correct import path for manual retry state and update route definition - Fixed the import path for the manual retry state controller in routes.py. - Updated the route definition for manual retry to include a leading slash for consistency. - Added a new controller file for manual retry state functionality, implementing the logic for handling manual retries. - Introduced unit tests for the manual retry request and response models, ensuring validation and functionality. - Enhanced unit tests for the manual retry state route, covering various scenarios including valid and invalid API keys. These changes improve the structure and reliability of the manual retry feature. * fix: refine manual retry state error handling and query logic - Updated the query for fetching the state to include the namespace name, ensuring accurate state retrieval. - Changed the HTTP status code for duplicate retry state errors from 400 to 409 to better reflect the conflict nature of the error. - Simplified exception handling by removing the unnecessary re-raise of the caught exception. These changes enhance the reliability and clarity of the manual retry state functionality. * feat: add unit tests for manual retry state functionality - Introduced a new test file for the manual retry state, covering various scenarios including successful state creation, error handling for not found states, and duplicate key errors. - Enhanced tests to verify logging, database error handling, and preservation of original state fields during retries. - Updated the README to include the new test file and detailed coverage of the manual retry state functionality. These changes improve the test coverage and reliability of the manual retry state feature.
1 parent 8e5337e commit 0f92411

8 files changed

Lines changed: 944 additions & 4 deletions

File tree

state-manager/.coverage

-68 KB
Binary file not shown.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from pymongo.errors import DuplicateKeyError
2+
from app.models.manual_retry import ManualRetryRequestModel, ManualRetryResponseModel
3+
from beanie import PydanticObjectId
4+
from app.singletons.logs_manager import LogsManager
5+
from app.models.state_status_enum import StateStatusEnum
6+
from fastapi import HTTPException, status
7+
from app.models.db.state import State
8+
9+
10+
logger = LogsManager().get_logger()
11+
12+
async def manual_retry_state(namespace_name: str, state_id: PydanticObjectId, body: ManualRetryRequestModel, x_exosphere_request_id: str):
13+
try:
14+
logger.info(f"Manual retry state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
15+
16+
state = await State.find_one(State.id == state_id, State.namespace_name == namespace_name)
17+
if not state:
18+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="State not found")
19+
20+
try:
21+
retry_state = State(
22+
node_name=state.node_name,
23+
namespace_name=state.namespace_name,
24+
identifier=state.identifier,
25+
graph_name=state.graph_name,
26+
run_id=state.run_id,
27+
status=StateStatusEnum.CREATED,
28+
inputs=state.inputs,
29+
outputs={},
30+
error=None,
31+
parents=state.parents,
32+
does_unites=state.does_unites,
33+
fanout_id=body.fanout_id # this will ensure that multiple unwanted retries are not formed because of index in database
34+
)
35+
retry_state = await retry_state.insert()
36+
logger.info(f"Retry state {retry_state.id} created for state {state_id}", x_exosphere_request_id=x_exosphere_request_id)
37+
38+
state.status = StateStatusEnum.RETRY_CREATED
39+
await state.save()
40+
41+
return ManualRetryResponseModel(id=str(retry_state.id), status=retry_state.status)
42+
except DuplicateKeyError:
43+
logger.info(f"Duplicate retry state detected for state {state_id}. A retry state with the same unique key already exists.", x_exosphere_request_id=x_exosphere_request_id)
44+
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Duplicate retry state detected")
45+
46+
47+
except Exception as _:
48+
logger.error(f"Error manual retry state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
49+
raise
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pydantic import BaseModel, Field
2+
from .state_status_enum import StateStatusEnum
3+
4+
5+
class ManualRetryRequestModel(BaseModel):
6+
fanout_id: str = Field(..., description="Fanout ID of the state")
7+
8+
9+
class ManualRetryResponseModel(BaseModel):
10+
id: str = Field(..., description="ID of the state")
11+
status: StateStatusEnum = Field(..., description="Status of the state")

state-manager/app/routes.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
from .models.signal_models import ReEnqueueAfterRequestModel
5151
from .controller.re_queue_after_signal import re_queue_after_signal
5252

53+
# manual_retry
54+
from .models.manual_retry import ManualRetryRequestModel, ManualRetryResponseModel
55+
from .controller.manual_retry_state import manual_retry_state
56+
5357

5458
logger = LogsManager().get_logger()
5559

@@ -176,6 +180,24 @@ async def re_enqueue_after_state_route(namespace_name: str, state_id: str, body:
176180

177181
return await re_queue_after_signal(namespace_name, PydanticObjectId(state_id), body, x_exosphere_request_id)
178182

183+
@router.post(
184+
"/state/{state_id}/manual-retry",
185+
response_model=ManualRetryResponseModel,
186+
status_code=status.HTTP_200_OK,
187+
response_description="State manual retry successfully",
188+
tags=["state"]
189+
)
190+
async def manual_retry_state_route(namespace_name: str, state_id: str, body: ManualRetryRequestModel, request: Request, api_key: str = Depends(check_api_key)):
191+
x_exosphere_request_id = getattr(request.state, "x_exosphere_request_id", str(uuid4()))
192+
193+
if api_key:
194+
logger.info(f"API key is valid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
195+
else:
196+
logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
197+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key")
198+
199+
return await manual_retry_state(namespace_name, PydanticObjectId(state_id), body, x_exosphere_request_id)
200+
179201

180202
@router.put(
181203
"/graph/{graph_name}",

state-manager/tests/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ tests/
1414
│ ├── test_errored_state.py
1515
│ ├── test_get_graph_template.py
1616
│ ├── test_get_secrets.py
17+
│ ├── test_manual_retry_state.py
1718
│ ├── test_register_nodes.py
1819
│ └── test_upsert_graph_template.py
1920
└── README.md
@@ -80,7 +81,21 @@ The unit tests cover all controller functions in the state-manager:
8081
- ✅ Complex schema handling
8182
- ✅ Database error handling
8283

83-
### 8. `upsert_graph_template.py`
84+
### 8. `manual_retry_state.py`
85+
- ✅ Successful manual retry state creation
86+
- ✅ State not found scenarios
87+
- ✅ Duplicate retry state detection (DuplicateKeyError)
88+
- ✅ Different fanout_id handling
89+
- ✅ Complex inputs and multiple parents preservation
90+
- ✅ Database errors during state lookup
91+
- ✅ Database errors during state save
92+
- ✅ Database errors during retry state insert
93+
- ✅ Empty inputs and parents handling
94+
- ✅ Namespace mismatch scenarios
95+
- ✅ Field preservation and reset logic
96+
- ✅ Logging verification
97+
98+
### 9. `upsert_graph_template.py`
8499
- ✅ Existing template updates
85100
- ✅ New template creation
86101
- ✅ Empty nodes handling

0 commit comments

Comments
 (0)