From 7e25e256237e94e7681a625ed1cb3683676f01bf Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 13:47:04 +0530 Subject: [PATCH 1/9] creating api to create init state --- state-manager/app/controller/create_states.py | 28 ++++++++++++++++--- state-manager/app/models/create_models.py | 5 ++-- state-manager/app/models/db/state.py | 1 + state-manager/app/routes.py | 6 ++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/state-manager/app/controller/create_states.py b/state-manager/app/controller/create_states.py index 442f0638..7e7cfc65 100644 --- a/state-manager/app/controller/create_states.py +++ b/state-manager/app/controller/create_states.py @@ -1,24 +1,44 @@ +from fastapi import HTTPException + from app.singletons.logs_manager import LogsManager from app.models.create_models import CreateRequestModel, CreateResponseModel, ResponseStateModel from app.models.state_status_enum import StateStatusEnum from app.models.db.state import State +from app.models.db.graph_template_model import GraphTemplate +from app.models.node_template_model import NodeTemplate from beanie.operators import In from bson import ObjectId logger = LogsManager().get_logger() -async def create_states(namespace_name: str, body: CreateRequestModel, x_exosphere_request_id: str) -> CreateResponseModel: + +def get_node_template(graph_template: GraphTemplate, identifier: str) -> NodeTemplate: + for node in graph_template.nodes: + if node.identifier == identifier: + return node + raise HTTPException(status_code=404, detail="Node template not found") + + +async def create_states(namespace_name: str, graph_name: str, body: CreateRequestModel, x_exosphere_request_id: str) -> CreateResponseModel: try: states = [] logger.info(f"Creating states for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id) + + graph_template = await GraphTemplate.find_one(GraphTemplate.name == graph_name, GraphTemplate.namespace == namespace_name) + if not graph_template: + raise HTTPException(status_code=404, detail="Graph template not found") for state in body.states: + + node_template = get_node_template(graph_template, state.identifier) + states.append( State( - node_name=state.node_name, - namespace_name=namespace_name, - graph_name=state.graph_name, + identifier=state.identifier, + node_name=node_template.node_name, + namespace_name=node_template.namespace, + graph_name=graph_name, status=StateStatusEnum.CREATED, inputs=state.inputs, outputs={}, diff --git a/state-manager/app/models/create_models.py b/state-manager/app/models/create_models.py index ba672e0f..d9cba1c4 100644 --- a/state-manager/app/models/create_models.py +++ b/state-manager/app/models/create_models.py @@ -1,3 +1,4 @@ +from xml.dom.minidom import Identified from pydantic import BaseModel, Field from typing import Any from .state_status_enum import StateStatusEnum @@ -5,14 +6,14 @@ class RequestStateModel(BaseModel): - node_name: str = Field(..., description="Name of the node of the state") - graph_name: str = Field(..., description="Name of the graph template for this state") + identifier: str = Field(..., description="Name of the node of the state") inputs: dict[str, Any] = Field(..., description="Inputs of the state") class ResponseStateModel(BaseModel): state_id: str = Field(..., description="ID of the state") node_name: str = Field(..., description="Name of the node of the state") + identifier: str = Field(..., description="Identifier of the node for which state is created") graph_name: str = Field(..., description="Name of the graph template for this state") inputs: dict[str, Any] = Field(..., description="Inputs of the state") created_at: datetime = Field(..., description="Date and time when the state was created") diff --git a/state-manager/app/models/db/state.py b/state-manager/app/models/db/state.py index c0e687c5..d55df68a 100644 --- a/state-manager/app/models/db/state.py +++ b/state-manager/app/models/db/state.py @@ -8,6 +8,7 @@ class State(BaseDatabaseModel): node_name: str = Field(..., description="Name of the node of the state") namespace_name: str = Field(..., description="Name of the namespace of the state") + identifier: str = Field(..., description="Identifier of the node for which state is created") graph_name: str = Field(..., description="Name of the graph template for this state") status: StateStatusEnum = Field(..., description="Status of the state") inputs: dict[str, Any] = Field(..., description="Inputs of the state") diff --git a/state-manager/app/routes.py b/state-manager/app/routes.py index 70fbef13..f532687c 100644 --- a/state-manager/app/routes.py +++ b/state-manager/app/routes.py @@ -55,13 +55,13 @@ async def enqueue_state(namespace_name: str, body: EnqueueRequestModel, request: @router.post( - "/states/create", + "/graph/{graph_name}/states/create", response_model=CreateResponseModel, status_code=status.HTTP_200_OK, response_description="States created successfully", tags=["state"] ) -async def create_state(namespace_name: str, body: CreateRequestModel, request: Request, api_key: str = Depends(check_api_key)): +async def create_state(namespace_name: str, graph_name: str, body: CreateRequestModel, request: Request, api_key: str = Depends(check_api_key)): x_exosphere_request_id = getattr(request.state, "x_exosphere_request_id", str(uuid4())) @@ -71,7 +71,7 @@ async def create_state(namespace_name: str, body: CreateRequestModel, request: R logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key") - return await create_states(namespace_name, body, x_exosphere_request_id) + return await create_states(namespace_name, graph_name, body, x_exosphere_request_id) @router.post( From 2cb57efb7d8441ee34ab28ef28e1d8c716fd2a13 Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 13:50:37 +0530 Subject: [PATCH 2/9] fixed ruff checks --- state-manager/app/models/create_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/state-manager/app/models/create_models.py b/state-manager/app/models/create_models.py index d9cba1c4..e2c93503 100644 --- a/state-manager/app/models/create_models.py +++ b/state-manager/app/models/create_models.py @@ -1,4 +1,3 @@ -from xml.dom.minidom import Identified from pydantic import BaseModel, Field from typing import Any from .state_status_enum import StateStatusEnum From fa3d5986570933845b7b0ffb3e71d3bccadc71ae Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 15:30:49 +0530 Subject: [PATCH 3/9] resolved issues by coderabbit --- state-manager/app/controller/create_states.py | 8 ++++---- state-manager/app/controller/enqueue_states.py | 1 + state-manager/app/models/create_models.py | 2 +- state-manager/app/models/db/graph_template_model.py | 13 +++++++++++++ state-manager/app/models/enqueue_response.py | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/state-manager/app/controller/create_states.py b/state-manager/app/controller/create_states.py index 7e7cfc65..435825e7 100644 --- a/state-manager/app/controller/create_states.py +++ b/state-manager/app/controller/create_states.py @@ -14,10 +14,10 @@ def get_node_template(graph_template: GraphTemplate, identifier: str) -> NodeTemplate: - for node in graph_template.nodes: - if node.identifier == identifier: - return node - raise HTTPException(status_code=404, detail="Node template not found") + node = graph_template.get_node_by_identifier(identifier) + if not node: + raise HTTPException(status_code=404, detail="Node template not found") + return node async def create_states(namespace_name: str, graph_name: str, body: CreateRequestModel, x_exosphere_request_id: str) -> CreateResponseModel: diff --git a/state-manager/app/controller/enqueue_states.py b/state-manager/app/controller/enqueue_states.py index e028d44a..e7379020 100644 --- a/state-manager/app/controller/enqueue_states.py +++ b/state-manager/app/controller/enqueue_states.py @@ -40,6 +40,7 @@ async def enqueue_states(namespace_name: str, body: EnqueueRequestModel, x_exosp StateModel( state_id=str(state.id), node_name=state.node_name, + identifier=state.identifier, inputs=state.inputs, created_at=state.created_at ) diff --git a/state-manager/app/models/create_models.py b/state-manager/app/models/create_models.py index e2c93503..063dbf39 100644 --- a/state-manager/app/models/create_models.py +++ b/state-manager/app/models/create_models.py @@ -5,7 +5,7 @@ class RequestStateModel(BaseModel): - identifier: str = Field(..., description="Name of the node of the state") + identifier: str = Field(..., description="Unique identifier of the node template within the graph template") inputs: dict[str, Any] = Field(..., description="Inputs of the state") diff --git a/state-manager/app/models/db/graph_template_model.py b/state-manager/app/models/db/graph_template_model.py index 21f9c839..e68c6c7a 100644 --- a/state-manager/app/models/db/graph_template_model.py +++ b/state-manager/app/models/db/graph_template_model.py @@ -9,6 +9,7 @@ from typing import Dict from app.utils.encrypter import get_encrypter + class GraphTemplate(BaseDatabaseModel): name: str = Field(..., description="Name of the graph") namespace: str = Field(..., description="Namespace of the graph") @@ -26,6 +27,18 @@ class Settings: ) ] + @property + def node_identifier_map(self) -> Dict[str, NodeTemplate]: + """Create a dictionary mapping node identifiers to nodes for O(1) lookup.""" + return {node.identifier: node for node in self.nodes} + + def get_node_by_identifier(self, identifier: str) -> NodeTemplate | None: + """Get a node by its identifier using O(1) dictionary lookup.""" + node_map = self.node_identifier_map + if identifier not in node_map: + return None + return node_map[identifier] + @field_validator('secrets') @classmethod def validate_secrets(cls, v: Dict[str, str]) -> Dict[str, str]: diff --git a/state-manager/app/models/enqueue_response.py b/state-manager/app/models/enqueue_response.py index 65b46aaa..13150dc8 100644 --- a/state-manager/app/models/enqueue_response.py +++ b/state-manager/app/models/enqueue_response.py @@ -6,6 +6,7 @@ class StateModel(BaseModel): state_id: str = Field(..., description="ID of the state") node_name: str = Field(..., description="Name of the node of the state") + identifier: str = Field(..., description="Identifier of the node for which state is created") inputs: dict[str, Any] = Field(..., description="Inputs of the state") created_at: datetime = Field(..., description="Date and time when the state was created") From 7ebda8395771e13270838d7153182a5a597dcc29 Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 15:37:14 +0530 Subject: [PATCH 4/9] ingore @coderabbitai for now --- state-manager/app/models/db/graph_template_model.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/state-manager/app/models/db/graph_template_model.py b/state-manager/app/models/db/graph_template_model.py index e68c6c7a..ee3d8d35 100644 --- a/state-manager/app/models/db/graph_template_model.py +++ b/state-manager/app/models/db/graph_template_model.py @@ -27,17 +27,12 @@ class Settings: ) ] - @property - def node_identifier_map(self) -> Dict[str, NodeTemplate]: - """Create a dictionary mapping node identifiers to nodes for O(1) lookup.""" - return {node.identifier: node for node in self.nodes} - def get_node_by_identifier(self, identifier: str) -> NodeTemplate | None: """Get a node by its identifier using O(1) dictionary lookup.""" - node_map = self.node_identifier_map - if identifier not in node_map: - return None - return node_map[identifier] + for node in self.nodes: + if node.identifier == identifier: + return node + return None @field_validator('secrets') @classmethod From 417ece9089e40865497c8ac3fdf605704154510b Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 17:02:37 +0530 Subject: [PATCH 5/9] adding background tasks for VERIFY graph template --- state-manager/app/controller/upsert_graph_template.py | 7 ++++++- state-manager/app/routes.py | 6 +++--- state-manager/app/tasks/__init__.py | 0 state-manager/app/tasks/verify_graph.py | 4 ++++ 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 state-manager/app/tasks/__init__.py create mode 100644 state-manager/app/tasks/verify_graph.py diff --git a/state-manager/app/controller/upsert_graph_template.py b/state-manager/app/controller/upsert_graph_template.py index ea956ded..1fabb381 100644 --- a/state-manager/app/controller/upsert_graph_template.py +++ b/state-manager/app/controller/upsert_graph_template.py @@ -2,11 +2,14 @@ from app.models.graph_models import UpsertGraphTemplateRequest, UpsertGraphTemplateResponse from app.models.db.graph_template_model import GraphTemplate from app.models.graph_template_validation_status import GraphTemplateValidationStatus +from app.tasks.verify_graph import verify_graph + +from fastapi import BackgroundTasks from beanie.operators import Set logger = LogsManager().get_logger() -async def upsert_graph_template(namespace_name: str, graph_name: str, body: UpsertGraphTemplateRequest, x_exosphere_request_id: str) -> UpsertGraphTemplateResponse: +async def upsert_graph_template(namespace_name: str, graph_name: str, body: UpsertGraphTemplateRequest, x_exosphere_request_id: str, background_tasks: BackgroundTasks) -> UpsertGraphTemplateResponse: try: graph_template = await GraphTemplate.find_one( GraphTemplate.name == graph_name, @@ -43,6 +46,8 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse ).set_secrets(body.secrets) ) + background_tasks.add_task(verify_graph, graph_template) + return UpsertGraphTemplateResponse( nodes=graph_template.nodes, validation_status=graph_template.validation_status, diff --git a/state-manager/app/routes.py b/state-manager/app/routes.py index f532687c..c39bc4fc 100644 --- a/state-manager/app/routes.py +++ b/state-manager/app/routes.py @@ -27,7 +27,7 @@ from .models.secrets_response import SecretsResponseModel from .controller.get_secrets import get_secrets - +from fastapi import BackgroundTasks logger = LogsManager().get_logger() @@ -121,7 +121,7 @@ async def errored_state_route(namespace_name: str, state_id: str, body: ErroredR response_description="Graph template upserted successfully", tags=["graph"] ) -async def upsert_graph_template(namespace_name: str, graph_name: str, body: UpsertGraphTemplateRequest, request: Request, api_key: str = Depends(check_api_key)): +async def upsert_graph_template(namespace_name: str, graph_name: str, body: UpsertGraphTemplateRequest, request: Request, background_tasks: BackgroundTasks, api_key: str = Depends(check_api_key)): x_exosphere_request_id = getattr(request.state, "x_exosphere_request_id", str(uuid4())) if api_key: @@ -130,7 +130,7 @@ async def upsert_graph_template(namespace_name: str, graph_name: str, body: Upse logger.error(f"API key is invalid for namespace {namespace_name}", x_exosphere_request_id=x_exosphere_request_id) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key") - return await upsert_graph_template_controller(namespace_name, graph_name, body, x_exosphere_request_id) + return await upsert_graph_template_controller(namespace_name, graph_name, body, x_exosphere_request_id, background_tasks) @router.put( diff --git a/state-manager/app/tasks/__init__.py b/state-manager/app/tasks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/state-manager/app/tasks/verify_graph.py b/state-manager/app/tasks/verify_graph.py new file mode 100644 index 00000000..64f71b5a --- /dev/null +++ b/state-manager/app/tasks/verify_graph.py @@ -0,0 +1,4 @@ +from app.models.db.graph_template_model import GraphTemplate + +async def verify_graph(graph_template: GraphTemplate): + pass \ No newline at end of file From 554cc208395231cd936b61b1a701996630ef3641 Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 17:33:59 +0530 Subject: [PATCH 6/9] added more validations --- state-manager/app/controller/create_states.py | 2 +- state-manager/app/tasks/verify_graph.py | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/state-manager/app/controller/create_states.py b/state-manager/app/controller/create_states.py index 435825e7..77d5cc81 100644 --- a/state-manager/app/controller/create_states.py +++ b/state-manager/app/controller/create_states.py @@ -56,7 +56,7 @@ async def create_states(namespace_name: str, graph_name: str, body: CreateReques return CreateResponseModel( status=StateStatusEnum.CREATED, - states=[ResponseStateModel(state_id=str(state.id), node_name=state.node_name, graph_name=state.graph_name, inputs=state.inputs, created_at=state.created_at) for state in newStates] + states=[ResponseStateModel(state_id=str(state.id), identifier=state.identifier, node_name=state.node_name, graph_name=state.graph_name, inputs=state.inputs, created_at=state.created_at) for state in newStates] ) except Exception as e: diff --git a/state-manager/app/tasks/verify_graph.py b/state-manager/app/tasks/verify_graph.py index 64f71b5a..c5d5eff7 100644 --- a/state-manager/app/tasks/verify_graph.py +++ b/state-manager/app/tasks/verify_graph.py @@ -1,4 +1,55 @@ from app.models.db.graph_template_model import GraphTemplate +from app.models.graph_template_validation_status import GraphTemplateValidationStatus +from app.models.db.graph_template_model import NodeTemplate +from app.models.db.registered_node import RegisteredNode +from beanie.operators import In + +async def verify_nodes_names(nodes: list[NodeTemplate], errors: list[str]): + errors = [] + for node in nodes: + if node.node_name is None or node.node_name == "": + errors.append(f"Node {node.identifier} has no name") + +async def verify_nodes_namespace(nodes: list[NodeTemplate], graph_namespace: str, errors: list[str]): + for node in nodes: + if node.namespace != graph_namespace and node.namespace != "exospherehost": + errors.append(f"Node {node.identifier} has a namespace that does not match the graph namespace or uses exospherehost universal namespace") + +async def verify_node_exists(nodes: list[NodeTemplate], graph_namespace: str, errors: list[str]): + graph_namespace_node_names = [ + node.node_name for node in nodes if node.namespace == graph_namespace + ] + graph_namespace_database_nodes = await RegisteredNode.find( + In(RegisteredNode.name, graph_namespace_node_names), + RegisteredNode.namespace == graph_namespace + ).to_list() + exospherehost_node_names = [ + node.name for node in graph_namespace_database_nodes if node.namespace == "exospherehost" + ] + exospherehost_database_nodes = await RegisteredNode.find( + In(RegisteredNode.name, exospherehost_node_names), + RegisteredNode.namespace == "exospherehost" + ).to_list() + + template_nodes = set([(node.node_name, node.namespace) for node in nodes]) + database_nodes = set([(node.name, node.namespace) for node in graph_namespace_database_nodes + exospherehost_database_nodes]) + + nodes_not_found = template_nodes - database_nodes + + for node in nodes_not_found: + errors.append(f"Node {node[0]} in namespace {node[1]} does not exist.") async def verify_graph(graph_template: GraphTemplate): - pass \ No newline at end of file + errors = [] + await verify_nodes_names(graph_template.nodes, errors) + await verify_nodes_namespace(graph_template.nodes, graph_template.namespace, errors) + await verify_node_exists(graph_template.nodes, graph_template.namespace, errors) + + if errors: + graph_template.validation_status = GraphTemplateValidationStatus.INVALID + graph_template.validation_errors = errors + await graph_template.save() + return + + graph_template.validation_status = GraphTemplateValidationStatus.VALID + await graph_template.save() \ No newline at end of file From 4151aff659306a1698104477356acce2dbbb92d0 Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 19:30:48 +0530 Subject: [PATCH 7/9] adding validations for identifier --- .../app/models/node_template_model.py | 4 ++-- state-manager/app/tasks/verify_graph.py | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/state-manager/app/models/node_template_model.py b/state-manager/app/models/node_template_model.py index 970662b7..acd2f037 100644 --- a/state-manager/app/models/node_template_model.py +++ b/state-manager/app/models/node_template_model.py @@ -1,5 +1,5 @@ from pydantic import Field, BaseModel -from typing import Any, Optional, List +from typing import Any, Optional, List, Set class NodeTemplate(BaseModel): @@ -8,4 +8,4 @@ class NodeTemplate(BaseModel): identifier: str = Field(..., description="Identifier of the node") inputs: dict[str, Any] = Field(..., description="Inputs of the node") store: dict[str, Any] = Field(..., description="Upsert data to store object for the node") - next_nodes: Optional[List[str]] = Field(None, description="Next nodes to execute") \ No newline at end of file + next_nodes: Optional[Set[str]] = Field(None, description="Next nodes to execute") \ No newline at end of file diff --git a/state-manager/app/tasks/verify_graph.py b/state-manager/app/tasks/verify_graph.py index c5d5eff7..1eebe077 100644 --- a/state-manager/app/tasks/verify_graph.py +++ b/state-manager/app/tasks/verify_graph.py @@ -39,11 +39,32 @@ async def verify_node_exists(nodes: list[NodeTemplate], graph_namespace: str, er for node in nodes_not_found: errors.append(f"Node {node[0]} in namespace {node[1]} does not exist.") +async def verify_node_identifiers(nodes: list[NodeTemplate], errors: list[str]): + identities = set() + + for node in nodes: + if node.identifier is None or node.identifier == "": + errors.append(f"Node {node.node_name} in namespace {node.namespace} has no identifier") + continue + if node.identifier in identities: + errors.append(f"Node {node.node_name} has a duplicate identifier {node.identifier} in namespace {node.namespace}") + continue + else: + identities.add(node.identifier) + + for node in nodes: + if node.next_nodes is None: + continue + for next_node in node.next_nodes: + if next_node not in identities: + errors.append(f"Node {node.node_name} in namespace {node.namespace} has a next node {next_node} that does not exist in the graph") + async def verify_graph(graph_template: GraphTemplate): errors = [] await verify_nodes_names(graph_template.nodes, errors) await verify_nodes_namespace(graph_template.nodes, graph_template.namespace, errors) await verify_node_exists(graph_template.nodes, graph_template.namespace, errors) + await verify_node_identifiers(graph_template.nodes, errors) if errors: graph_template.validation_status = GraphTemplateValidationStatus.INVALID @@ -52,4 +73,5 @@ async def verify_graph(graph_template: GraphTemplate): return graph_template.validation_status = GraphTemplateValidationStatus.VALID + graph_template.validation_errors = None await graph_template.save() \ No newline at end of file From 33c5997b8b9333f195e201ad3a880ea05fbfb30e Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 19:38:46 +0530 Subject: [PATCH 8/9] fixed errors as pointed by coderabbit --- state-manager/app/models/node_template_model.py | 4 ++-- state-manager/app/tasks/verify_graph.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/state-manager/app/models/node_template_model.py b/state-manager/app/models/node_template_model.py index acd2f037..970662b7 100644 --- a/state-manager/app/models/node_template_model.py +++ b/state-manager/app/models/node_template_model.py @@ -1,5 +1,5 @@ from pydantic import Field, BaseModel -from typing import Any, Optional, List, Set +from typing import Any, Optional, List class NodeTemplate(BaseModel): @@ -8,4 +8,4 @@ class NodeTemplate(BaseModel): identifier: str = Field(..., description="Identifier of the node") inputs: dict[str, Any] = Field(..., description="Inputs of the node") store: dict[str, Any] = Field(..., description="Upsert data to store object for the node") - next_nodes: Optional[Set[str]] = Field(None, description="Next nodes to execute") \ No newline at end of file + next_nodes: Optional[List[str]] = Field(None, description="Next nodes to execute") \ No newline at end of file diff --git a/state-manager/app/tasks/verify_graph.py b/state-manager/app/tasks/verify_graph.py index 1eebe077..59139d41 100644 --- a/state-manager/app/tasks/verify_graph.py +++ b/state-manager/app/tasks/verify_graph.py @@ -5,7 +5,6 @@ from beanie.operators import In async def verify_nodes_names(nodes: list[NodeTemplate], errors: list[str]): - errors = [] for node in nodes: if node.node_name is None or node.node_name == "": errors.append(f"Node {node.identifier} has no name") @@ -24,7 +23,7 @@ async def verify_node_exists(nodes: list[NodeTemplate], graph_namespace: str, er RegisteredNode.namespace == graph_namespace ).to_list() exospherehost_node_names = [ - node.name for node in graph_namespace_database_nodes if node.namespace == "exospherehost" + node.node_name for node in nodes if node.namespace == "exospherehost" ] exospherehost_database_nodes = await RegisteredNode.find( In(RegisteredNode.name, exospherehost_node_names), From 4b81fd4b1d800e05c67370ae95f3713a6bfb672e Mon Sep 17 00:00:00 2001 From: NiveditJain Date: Wed, 6 Aug 2025 19:54:47 +0530 Subject: [PATCH 9/9] fixxed comments by coderabbit --- state-manager/app/routes.py | 3 +- state-manager/app/tasks/verify_graph.py | 63 ++++++++++++++++--------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/state-manager/app/routes.py b/state-manager/app/routes.py index c39bc4fc..659256d9 100644 --- a/state-manager/app/routes.py +++ b/state-manager/app/routes.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, status, Request, Depends, HTTPException +from fastapi import APIRouter, status, Request, Depends, HTTPException, BackgroundTasks from uuid import uuid4 from bson import ObjectId @@ -27,7 +27,6 @@ from .models.secrets_response import SecretsResponseModel from .controller.get_secrets import get_secrets -from fastapi import BackgroundTasks logger = LogsManager().get_logger() diff --git a/state-manager/app/tasks/verify_graph.py b/state-manager/app/tasks/verify_graph.py index 59139d41..3ca70d0e 100644 --- a/state-manager/app/tasks/verify_graph.py +++ b/state-manager/app/tasks/verify_graph.py @@ -1,9 +1,11 @@ -from app.models.db.graph_template_model import GraphTemplate +from app.models.db.graph_template_model import GraphTemplate, NodeTemplate from app.models.graph_template_validation_status import GraphTemplateValidationStatus -from app.models.db.graph_template_model import NodeTemplate from app.models.db.registered_node import RegisteredNode +from app.singletons.logs_manager import LogsManager from beanie.operators import In +logger = LogsManager().get_logger() + async def verify_nodes_names(nodes: list[NodeTemplate], errors: list[str]): for node in nodes: if node.node_name is None or node.node_name == "": @@ -12,7 +14,7 @@ async def verify_nodes_names(nodes: list[NodeTemplate], errors: list[str]): async def verify_nodes_namespace(nodes: list[NodeTemplate], graph_namespace: str, errors: list[str]): for node in nodes: if node.namespace != graph_namespace and node.namespace != "exospherehost": - errors.append(f"Node {node.identifier} has a namespace that does not match the graph namespace or uses exospherehost universal namespace") + errors.append(f"Node {node.identifier} has invalid namespace '{node.namespace}'. Must match graph namespace '{graph_namespace}' or use universal namespace 'exospherehost'") async def verify_node_exists(nodes: list[NodeTemplate], graph_namespace: str, errors: list[str]): graph_namespace_node_names = [ @@ -39,38 +41,53 @@ async def verify_node_exists(nodes: list[NodeTemplate], graph_namespace: str, er errors.append(f"Node {node[0]} in namespace {node[1]} does not exist.") async def verify_node_identifiers(nodes: list[NodeTemplate], errors: list[str]): - identities = set() + identifier_to_nodes = {} + # First pass: collect all nodes by identifier for node in nodes: if node.identifier is None or node.identifier == "": errors.append(f"Node {node.node_name} in namespace {node.namespace} has no identifier") continue - if node.identifier in identities: - errors.append(f"Node {node.node_name} has a duplicate identifier {node.identifier} in namespace {node.namespace}") - continue - else: - identities.add(node.identifier) + + if node.identifier not in identifier_to_nodes: + identifier_to_nodes[node.identifier] = [] + identifier_to_nodes[node.identifier].append(node) + + # Check for duplicates and report all nodes sharing the same identifier + for identifier, nodes_with_identifier in identifier_to_nodes.items(): + if len(nodes_with_identifier) > 1: + node_list = ", ".join([f"{node.node_name} in namespace {node.namespace}" for node in nodes_with_identifier]) + errors.append(f"Duplicate identifier '{identifier}' found in nodes: {node_list}") + # Check next_nodes references using the valid identifiers + valid_identifiers = set(identifier_to_nodes.keys()) for node in nodes: if node.next_nodes is None: continue for next_node in node.next_nodes: - if next_node not in identities: + if next_node not in valid_identifiers: errors.append(f"Node {node.node_name} in namespace {node.namespace} has a next node {next_node} that does not exist in the graph") async def verify_graph(graph_template: GraphTemplate): - errors = [] - await verify_nodes_names(graph_template.nodes, errors) - await verify_nodes_namespace(graph_template.nodes, graph_template.namespace, errors) - await verify_node_exists(graph_template.nodes, graph_template.namespace, errors) - await verify_node_identifiers(graph_template.nodes, errors) + try: + errors = [] + await verify_nodes_names(graph_template.nodes, errors) + await verify_nodes_namespace(graph_template.nodes, graph_template.namespace, errors) + await verify_node_exists(graph_template.nodes, graph_template.namespace, errors) + await verify_node_identifiers(graph_template.nodes, errors) - if errors: - graph_template.validation_status = GraphTemplateValidationStatus.INVALID - graph_template.validation_errors = errors + if errors: + graph_template.validation_status = GraphTemplateValidationStatus.INVALID + graph_template.validation_errors = errors + await graph_template.save() + return + + graph_template.validation_status = GraphTemplateValidationStatus.VALID + graph_template.validation_errors = None await graph_template.save() - return - - graph_template.validation_status = GraphTemplateValidationStatus.VALID - graph_template.validation_errors = None - await graph_template.save() \ No newline at end of file + + except Exception as e: + logger.error(f"Exception during graph validation for graph template {graph_template.id}: {str(e)}", exc_info=True) + graph_template.validation_status = GraphTemplateValidationStatus.INVALID + graph_template.validation_errors = [f"Validation failed due to unexpected error: {str(e)}"] + await graph_template.save() \ No newline at end of file