-
Notifications
You must be signed in to change notification settings - Fork 41
feat: add manual retry state apis #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
844f13a
3d68362
edde4f0
19b3a65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||
| from pymongo.errors import DuplicateKeyError | ||||||||||||||||||||||||||
| from app.models.manual_retry import ManualRetryRequestModel, ManualRetryResponseModel | ||||||||||||||||||||||||||
| from beanie import PydanticObjectId | ||||||||||||||||||||||||||
| from app.singletons.logs_manager import LogsManager | ||||||||||||||||||||||||||
| from app.models.state_status_enum import StateStatusEnum | ||||||||||||||||||||||||||
| from fastapi import HTTPException, status | ||||||||||||||||||||||||||
| from app.models.db.state import State | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| logger = LogsManager().get_logger() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| async def manual_retry_state(namespace_name: str, state_id: PydanticObjectId, body: ManualRetryRequestModel, x_exosphere_request_id: str): | ||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| logger.info(f"Manual retry state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| state = await State.find_one(State.id == state_id, State.namespace_name == namespace_name) | ||||||||||||||||||||||||||
| if not state: | ||||||||||||||||||||||||||
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="State not found") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| retry_state = State( | ||||||||||||||||||||||||||
| node_name=state.node_name, | ||||||||||||||||||||||||||
| namespace_name=state.namespace_name, | ||||||||||||||||||||||||||
| identifier=state.identifier, | ||||||||||||||||||||||||||
| graph_name=state.graph_name, | ||||||||||||||||||||||||||
| run_id=state.run_id, | ||||||||||||||||||||||||||
| status=StateStatusEnum.CREATED, | ||||||||||||||||||||||||||
| inputs=state.inputs, | ||||||||||||||||||||||||||
| outputs={}, | ||||||||||||||||||||||||||
| error=None, | ||||||||||||||||||||||||||
| parents=state.parents, | ||||||||||||||||||||||||||
| does_unites=state.does_unites, | ||||||||||||||||||||||||||
| fanout_id=body.fanout_id # this will ensure that multiple unwanted retries are not formed because of index in database | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| retry_state = await retry_state.insert() | ||||||||||||||||||||||||||
| logger.info(f"Retry state {retry_state.id} created for state {state_id}", x_exosphere_request_id=x_exosphere_request_id) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| state.status = StateStatusEnum.RETRY_CREATED | ||||||||||||||||||||||||||
| await state.save() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ManualRetryResponseModel(id=str(retry_state.id), status=retry_state.status) | ||||||||||||||||||||||||||
|
Comment on lines
+36
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick Consider atomicity between insert and original state update. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| except DuplicateKeyError: | ||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||
| raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Duplicate retry state detected") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| except Exception as _: | ||||||||||||||||||||||||||
| logger.error(f"Error manual retry state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id) | ||||||||||||||||||||||||||
| raise | ||||||||||||||||||||||||||
|
Comment on lines
+47
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t log HTTPException as server errors; use logger.exception for unexpected failures. Apply: - except Exception as _:
- logger.error(f"Error manual retry state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
- raise
+ except HTTPException:
+ # propagate expected HTTP errors without error severity logging
+ raise
+ except Exception:
+ logger.exception(
+ f"Error creating manual retry state {state_id} for namespace {namespace_name}",
+ x_exosphere_request_id=x_exosphere_request_id,
+ )
+ raise📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from pydantic import BaseModel, Field | ||
| from .state_status_enum import StateStatusEnum | ||
|
|
||
|
|
||
| class ManualRetryRequestModel(BaseModel): | ||
| fanout_id: str = Field(..., description="Fanout ID of the state") | ||
|
|
||
NiveditJain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| class ManualRetryResponseModel(BaseModel): | ||
| id: str = Field(..., description="ID of the state") | ||
| status: StateStatusEnum = Field(..., description="Status of the state") | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ tests/ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ ├── test_errored_state.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ ├── test_get_graph_template.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ ├── test_get_secrets.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ ├── test_manual_retry_state.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ ├── test_register_nodes.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| │ └── test_upsert_graph_template.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| └── README.md | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -80,7 +81,21 @@ The unit tests cover all controller functions in the state-manager: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Complex schema handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Database error handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 8. `upsert_graph_template.py` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 8. `manual_retry_state.py` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Successful manual retry state creation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ State not found scenarios | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Duplicate retry state detection (DuplicateKeyError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Different fanout_id handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Complex inputs and multiple parents preservation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Database errors during state lookup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Database errors during state save | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Database errors during retry state insert | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Empty inputs and parents handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Namespace mismatch scenarios | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Field preservation and reset logic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Logging verification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+84
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick Fix markdownlint errors around the new section. Apply: -### 8. `manual_retry_state.py`
+
+### 8. `manual_retry_state.py`
+
- ✅ Successful manual retry state creation
- ✅ State not found scenarios
- ✅ Duplicate retry state detection (DuplicateKeyError)
- ✅ Different fanout_id handling
- ✅ Complex inputs and multiple parents preservation
- ✅ Database errors during state lookup
- ✅ Database errors during state save
- ✅ Database errors during retry state insert
- ✅ Empty inputs and parents handling
- ✅ Namespace mismatch scenarios
- ✅ Field preservation and reset logic
- ✅ Logging verification
+📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.17.2)84-84: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 85-85: Lists should be surrounded by blank lines (MD032, blanks-around-lists) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### 9. `upsert_graph_template.py` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick Add a blank line before this heading. Apply: -### 9. `upsert_graph_template.py`
+
+### 9. `upsert_graph_template.py`📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.17.2)98-98: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Existing template updates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ New template creation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - ✅ Empty nodes handling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocker: fingerprint collision for does_unites states; set retry_count on the new retry state.
Without bumping retry_count, the retry state’s fingerprint can equal the original (unique index uniq_state_fingerprint_unites), causing DuplicateKeyError even on first retry. Also aligns with the unique (node, ns, graph, identifier, run_id, retry_count, fanout_id) index.
Apply:
retry_state = State( node_name=state.node_name, namespace_name=state.namespace_name, identifier=state.identifier, graph_name=state.graph_name, run_id=state.run_id, status=StateStatusEnum.CREATED, inputs=state.inputs, outputs={}, error=None, parents=state.parents, does_unites=state.does_unites, + retry_count=(getattr(state, "retry_count", 0) + 1), fanout_id=body.fanout_id # this will ensure that multiple unwanted retries are not formed because of index in database )📝 Committable suggestion
🤖 Prompt for AI Agents