Skip to content

Commit cf1f6f3

Browse files
authored
Add executed and errored state APIs with corresponding models and controllers (#107)
1 parent 3155020 commit cf1f6f3

7 files changed

Lines changed: 136 additions & 4 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from app.models.errored_models import ErroredRequestModel, ErroredResponseModel
2+
from bson import ObjectId
3+
from fastapi import HTTPException, status
4+
5+
from app.models.db.state import State
6+
from app.models.state_status_enum import StateStatusEnum
7+
from app.singletons.logs_manager import LogsManager
8+
9+
logger = LogsManager().get_logger()
10+
11+
async def errored_state(namespace_name: str, state_id: ObjectId, body: ErroredRequestModel, x_exosphere_request_id: str) -> ErroredResponseModel:
12+
13+
try:
14+
logger.info(f"Errored 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)
17+
if not state:
18+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="State not found")
19+
20+
if state.status != StateStatusEnum.QUEUED and state.status != StateStatusEnum.EXECUTED:
21+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="State is not queued or executed")
22+
23+
if state.status == StateStatusEnum.EXECUTED:
24+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="State is already executed")
25+
26+
await State.find_one(State.id == state_id).set(
27+
{"status": StateStatusEnum.ERRORED, "error": body.error}
28+
)
29+
30+
return ErroredResponseModel(status=StateStatusEnum.ERRORED)
31+
32+
except Exception as e:
33+
logger.error(f"Error errored state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id, error=e)
34+
raise e
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from app.models.executed_models import ExecutedRequestModel, ExecutedResponseModel
2+
from bson import ObjectId
3+
from fastapi import HTTPException, status
4+
5+
from app.models.db.state import State
6+
from app.models.state_status_enum import StateStatusEnum
7+
from app.singletons.logs_manager import LogsManager
8+
9+
logger = LogsManager().get_logger()
10+
11+
async def executed_state(namespace_name: str, state_id: ObjectId, body: ExecutedRequestModel, x_exosphere_request_id: str) -> ExecutedResponseModel:
12+
13+
try:
14+
logger.info(f"Executed 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)
17+
if not state:
18+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="State not found")
19+
20+
if state.status != StateStatusEnum.QUEUED:
21+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="State is not queued")
22+
23+
await State.find_one(State.id == state_id).set(
24+
{"status": StateStatusEnum.EXECUTED, "outputs": body.outputs}
25+
)
26+
27+
return ExecutedResponseModel(status=StateStatusEnum.EXECUTED)
28+
29+
except Exception as e:
30+
logger.error(f"Error executing state {state_id} for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id, error=e)
31+
raise e

state-manager/app/models/db/namespace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
class Namespace(BaseDatabaseModel):
77

8-
name: Indexed(str, unique=True) = Field(..., description="Name of the namespace")
8+
name: Indexed(str, unique=True) = Field(..., description="Name of the namespace") # type: ignore
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .base import BaseDatabaseModel
22
from ..state_status_enum import StateStatusEnum
33
from pydantic import Field
4-
from typing import Any
4+
from typing import Any, Optional
55

66

77
class State(BaseDatabaseModel):
@@ -10,4 +10,5 @@ class State(BaseDatabaseModel):
1010
namespace_name: str = Field(..., description="Name of the namespace of the state")
1111
status: StateStatusEnum = Field(..., description="Status of the state")
1212
inputs: dict[str, Any] = Field(..., description="Inputs of the state")
13-
outputs: dict[str, Any] = Field(..., description="Outputs of the state")
13+
outputs: dict[str, Any] = Field(..., description="Outputs of the state")
14+
error: Optional[str] = Field(None, description="Error message")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydantic import BaseModel, Field
2+
from .state_status_enum import StateStatusEnum
3+
4+
5+
class ErroredRequestModel(BaseModel):
6+
error: str = Field(..., description="Error message")
7+
8+
9+
class ErroredResponseModel(BaseModel):
10+
status: StateStatusEnum = Field(..., description="Status of the state")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydantic import BaseModel, Field
2+
from typing import Any
3+
from .state_status_enum import StateStatusEnum
4+
5+
class ExecutedRequestModel(BaseModel):
6+
outputs: dict[str, Any] = Field(..., description="Outputs of the state")
7+
8+
9+
class ExecutedResponseModel(BaseModel):
10+
status: StateStatusEnum = Field(..., description="Status of the state")

state-manager/app/routes.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from fastapi import APIRouter, status, Request, Depends, HTTPException
22
from uuid import uuid4
3+
from bson import ObjectId
34

45
from app.utils.check_secret import check_api_key
56
from app.singletons.logs_manager import LogsManager
@@ -11,6 +12,13 @@
1112
from .models.create_models import CreateRequestModel, CreateResponseModel
1213
from .controller.create_states import create_states
1314

15+
from .models.executed_models import ExecutedRequestModel, ExecutedResponseModel
16+
from .controller.executed_state import executed_state
17+
18+
from .models.errored_models import ErroredRequestModel, ErroredResponseModel
19+
from .controller.errored_state import errored_state
20+
21+
1422

1523
logger = LogsManager().get_logger()
1624

@@ -52,4 +60,42 @@ async def create_state(namespace_name: str, body: CreateRequestModel, request: R
5260
logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
5361
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key")
5462

55-
return await create_states(namespace_name, body, x_exosphere_request_id)
63+
return await create_states(namespace_name, body, x_exosphere_request_id)
64+
65+
66+
@router.post(
67+
"/{state_id}/executed",
68+
response_model=ExecutedResponseModel,
69+
status_code=status.HTTP_200_OK,
70+
response_description="State executed successfully"
71+
)
72+
async def executed_state_route(namespace_name: str, state_id: str, body: ExecutedRequestModel, request: Request, api_key: str = Depends(check_api_key)):
73+
74+
x_exosphere_request_id = getattr(request.state, "x_exosphere_request_id", str(uuid4()))
75+
76+
if api_key:
77+
logger.info(f"API key is valid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
78+
else:
79+
logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
80+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key")
81+
82+
return await executed_state(namespace_name, ObjectId(state_id), body, x_exosphere_request_id)
83+
84+
85+
@router.post(
86+
"/{state_id}/errored",
87+
response_model=ErroredResponseModel,
88+
status_code=status.HTTP_200_OK,
89+
response_description="State errored successfully"
90+
)
91+
async def errored_state_route(namespace_name: str, state_id: str, body: ErroredRequestModel, request: Request, api_key: str = Depends(check_api_key)):
92+
93+
x_exosphere_request_id = getattr(request.state, "x_exosphere_request_id", str(uuid4()))
94+
95+
if api_key:
96+
logger.info(f"API key is valid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
97+
else:
98+
logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id)
99+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key")
100+
101+
return await errored_state(namespace_name, ObjectId(state_id), body, x_exosphere_request_id)

0 commit comments

Comments
 (0)