Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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 number Diff line number Diff 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 number Diff line number Diff line change
@@ -1 +1 @@
version = "0.0.2b3"
version = "0.0.2b4"
4 changes: 2 additions & 2 deletions python-sdk/exospherehost/runtime.py
Original file line number Diff line number Diff 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"

def _get_register_endpoint(self):
"""
Expand Down
5 changes: 4 additions & 1 deletion python-sdk/exospherehost/signals.py
Original file line number Diff line number Diff 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:
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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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}")


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 number Diff line number Diff 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:
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
})
)

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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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

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