Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
# Ignore temp directory and temp files at repository root
/temp*
!/temp/.gitkeep
2 changes: 1 addition & 1 deletion python-sdk/exospherehost/_version.py
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
version = "0.0.2b3"
version = "0.0.2b4"
Comment thread
NiveditJain marked this conversation as resolved.
4 changes: 2 additions & 2 deletions python-sdk/exospherehost/runtime.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,13 +141,13 @@ def _get_executed_endpoint(self, state_id: str):
"""
Construct the endpoint URL for notifying executed states.
"""
return f"{self._state_manager_uri}/{str(self._state_manager_version)}/namespace/{self._namespace}/states/{state_id}/executed"
return f"{self._state_manager_uri}/{str(self._state_manager_version)}/namespace/{self._namespace}/state/{state_id}/executed"

def _get_errored_endpoint(self, state_id: str):
"""
Construct the endpoint URL for notifying errored states.
"""
return f"{self._state_manager_uri}/{str(self._state_manager_version)}/namespace/{self._namespace}/states/{state_id}/errored"
return f"{self._state_manager_uri}/{str(self._state_manager_version)}/namespace/{self._namespace}/state/{state_id}/errored"

Comment thread
coderabbitai[bot] marked this conversation as resolved.
def _get_register_endpoint(self):
"""
Expand Down
5 changes: 4 additions & 1 deletion python-sdk/exospherehost/signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,8 +27,11 @@ async def send(self, endpoint: str, key: str):
Raises:
Exception: If the HTTP request fails (status code != 200).
"""
body = {
"data": self.data
}
async with ClientSession() as session:
async with session.post(endpoint, json=self.data, headers={"x-api-key": key}) as response:
async with session.post(endpoint, json=body, headers={"x-api-key": key}) as response:
if response.status != 200:
Comment thread
NiveditJain marked this conversation as resolved.
raise Exception(f"Failed to send prune signal to {endpoint}")

Expand Down
4 changes: 2 additions & 2 deletions python-sdk/tests/test_runtime_comprehensive.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -192,13 +192,13 @@ def test_get_enque_endpoint(self, runtime_config):
def test_get_executed_endpoint(self, runtime_config):
runtime = Runtime(**runtime_config)
endpoint = runtime._get_executed_endpoint("state123")
expected = "http://localhost:8080/v1/namespace/test_namespace/states/state123/executed"
expected = "http://localhost:8080/v1/namespace/test_namespace/state/state123/executed"
assert endpoint == expected

def test_get_errored_endpoint(self, runtime_config):
runtime = Runtime(**runtime_config)
endpoint = runtime._get_errored_endpoint("state123")
expected = "http://localhost:8080/v1/namespace/test_namespace/states/state123/errored"
expected = "http://localhost:8080/v1/namespace/test_namespace/state/state123/errored"
assert endpoint == expected

def test_get_register_endpoint(self, runtime_config):
Expand Down
4 changes: 2 additions & 2 deletions python-sdk/tests/test_signals_and_runtime_functions.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,7 +95,7 @@ async def test_prune_signal_send_success(self):
# Verify the request was made correctly
mock_session.post.assert_called_once_with(
"http://test-endpoint/prune",
json=data,
json={"data": data},
headers={"x-api-key": "test-api-key"}
)

Expand DownExpand Up@@ -270,7 +270,7 @@ async def test_signal_handling_direct(self):
# Verify prune endpoint was called correctly
mock_session.post.assert_called_once_with(
runtime._get_prune_endpoint("test-state"),
json={"reason": "direct_test"},
json={"data": {"reason": "direct_test"}},
headers={"x-api-key": "test-key"}
)

Expand Down
29 changes: 26 additions & 3 deletions state-manager/app/controller/trigger_graph.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@
from app.models.db.run import Run
from app.models.db.graph_template_model import GraphTemplate
from app.models.node_template_model import NodeTemplate
from app.models.dependent_string import DependentString
import uuid

logger = LogsManager().get_logger()
Expand DownExpand Up@@ -41,6 +42,30 @@ async def trigger_graph(namespace_name: str, graph_name: str, body: TriggerGraph

if not graph_template.is_valid():
raise HTTPException(status_code=400, detail="Graph template is not valid")

root = graph_template.get_root_node()
inputs = construct_inputs(root, body.inputs)

try:
for field, value in inputs.items():
dependent_string = DependentString.create_dependent_string(value)

for dependent in dependent_string.dependents.values():
if dependent.identifier != "store":
raise HTTPException(status_code=400, detail=f"Root node can have only store identifier as dependent but got {dependent.identifier}")
elif dependent.field not in body.store:
if dependent.field in graph_template.store_config.default_values.keys():
dependent_string.set_value(dependent.identifier, dependent.field, graph_template.store_config.default_values[dependent.field])
else:
raise HTTPException(status_code=400, detail=f"Dependent {dependent.field} not found in store for root node {root.identifier}")
else:
dependent_string.set_value(dependent.identifier, dependent.field, body.store[dependent.field])

inputs[field] = dependent_string.generate_string()

except Exception as e:
raise HTTPException(status_code=400, detail=f"Invalid input: {e}")

Comment thread
NiveditJain marked this conversation as resolved.

check_required_store_keys(graph_template, body.store)

Expand All@@ -64,16 +89,14 @@ async def trigger_graph(namespace_name: str, graph_name: str, body: TriggerGraph
if len(new_stores) > 0:
await Store.insert_many(new_stores)

root = graph_template.get_root_node()

new_state = State(
node_name=root.node_name,
namespace_name=namespace_name,
identifier=root.identifier,
graph_name=graph_name,
run_id=run_id,
status=StateStatusEnum.CREATED,
inputs=construct_inputs(root, body.inputs),
inputs=inputs,
outputs={},
error=None
)
Expand Down
8 changes: 5 additions & 3 deletions state-manager/app/controller/upsert_graph_template.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse
GraphTemplate.name == graph_name,
GraphTemplate.namespace == namespace_name
)

try:
if graph_template:
Comment thread
NiveditJain marked this conversation as resolved.
logger.info(
Expand All@@ -28,7 +28,8 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse
GraphTemplate.nodes: body.nodes, # type: ignore
GraphTemplate.validation_status: GraphTemplateValidationStatus.PENDING, # type: ignore
GraphTemplate.validation_errors: [], # type: ignore
GraphTemplate.retry_policy: body.retry_policy # type: ignore
GraphTemplate.retry_policy: body.retry_policy, # type: ignore
GraphTemplate.store_config: body.store_config # type: ignore
})
Comment thread
NiveditJain marked this conversation as resolved.
)

Expand All@@ -46,7 +47,8 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse
nodes=body.nodes,
validation_status=GraphTemplateValidationStatus.PENDING,
validation_errors=[],
retry_policy=body.retry_policy
retry_policy=body.retry_policy,
store_config=body.store_config
).set_secrets(body.secrets)
)
except ValueError as e:
Expand Down
8 changes: 4 additions & 4 deletions state-manager/app/routes.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,7 +92,7 @@ async def trigger_graph_route(namespace_name: str, graph_name: str, body: Trigge
return await trigger_graph(namespace_name, graph_name, body, x_exosphere_request_id)

@router.post(
"/states/{state_id}/executed",
"/state/{state_id}/executed",
response_model=ExecutedResponseModel,
status_code=status.HTTP_200_OK,
response_description="State executed successfully",
Expand All@@ -112,7 +112,7 @@ async def executed_state_route(namespace_name: str, state_id: str, body: Execute


@router.post(
"/states/{state_id}/errored",
"/state/{state_id}/errored",
response_model=ErroredResponseModel,
status_code=status.HTTP_200_OK,
response_description="State errored successfully",
Expand All@@ -132,7 +132,7 @@ async def errored_state_route(namespace_name: str, state_id: str, body: ErroredR


@router.post(
"/states/{state_id}/prune",
"/state/{state_id}/prune",
response_model=SignalResponseModel,
status_code=status.HTTP_200_OK,
response_description="State pruned successfully",
Expand All@@ -151,7 +151,7 @@ async def prune_state_route(namespace_name: str, state_id: str, body: PruneReque


@router.post(
"/states/{state_id}/re-enqueue-after",
"/state/{state_id}/re-enqueue-after",
response_model=SignalResponseModel,
status_code=status.HTTP_200_OK,
response_description="State re-enqueued successfully",
Expand Down
2 changes: 2 additions & 0 deletions state-manager/app/tasks/create_next_states.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,8 @@ def validate_dependencies(next_state_node_template: NodeTemplate, next_state_inp
dependency_string = DependentString.create_dependent_string(next_state_node_template.inputs[field_name])

for dependent in dependency_string.dependents.values():
if dependent.identifier == "store":
continue
# 2) For each placeholder, verify the identifier is either current or present in parents
if dependent.identifier != identifier and dependent.identifier not in parents:
raise KeyError(f"Identifier '{dependent.identifier}' not found in parents for template '{next_state_node_template.identifier}'")
Expand Down
5 changes: 4 additions & 1 deletion state-manager/app/tasks/verify_graph.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,7 +71,10 @@ async def verify_inputs(graph_template: GraphTemplate, registered_nodes: list[Re
for dependent_string in dependent_strings:
identifier_field_pairs = dependent_string.get_identifier_field()
for identifier, field in identifier_field_pairs:


if identifier == "store":
continue

Comment thread
NiveditJain marked this conversation as resolved.
temp_node = graph_template.get_node_by_identifier(identifier)
if temp_node is None:
errors.append(f"Node {identifier} does not exist in the graph template")
Expand Down
Loading