Skip to content

Commit f199b60

Browse files
committed
Enhance validation in NodeTemplate and GraphTemplate models
- Introduced field validators for various attributes in NodeTemplate to ensure non-empty values and uniqueness for node names, identifiers, next nodes, and unites. - Added validation for name and namespace in GraphTemplate to prevent empty values. - Implemented model-level validation to ensure node identifiers are unique and that next node identifiers exist within the graph. - Removed outdated verification functions from verify_graph.py to streamline the validation process. These changes improve data integrity and validation consistency across the models.
1 parent f194648 commit f199b60

3 files changed

Lines changed: 100 additions & 24 deletions

File tree

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

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import asyncio
44

55
from .base import BaseDatabaseModel
6-
from pydantic import Field, field_validator, PrivateAttr
7-
from typing import Optional, List
6+
from pydantic import Field, field_validator, PrivateAttr, model_validator
7+
from typing import Optional, List, Self
88
from ..graph_template_validation_status import GraphTemplateValidationStatus
99
from ..node_template_model import NodeTemplate
1010
from pymongo import IndexModel
@@ -41,6 +41,20 @@ def get_node_by_identifier(self, identifier: str) -> NodeTemplate | None:
4141
assert self._node_by_identifier is not None
4242
return self._node_by_identifier.get(identifier)
4343

44+
@field_validator('name')
45+
@classmethod
46+
def validate_name(cls, v: str) -> str:
47+
if v == "" or v is None:
48+
raise ValueError("Name cannot be empty")
49+
return v
50+
51+
@field_validator('namespace')
52+
@classmethod
53+
def validate_namespace(cls, v: str) -> str:
54+
if v == "" or v is None:
55+
raise ValueError("Namespace cannot be empty")
56+
return v
57+
4458
@field_validator('secrets')
4559
@classmethod
4660
def validate_secrets(cls, v: Dict[str, str]) -> Dict[str, str]:
@@ -55,6 +69,43 @@ def validate_secrets(cls, v: Dict[str, str]) -> Dict[str, str]:
5569

5670
return v
5771

72+
@model_validator(mode='after')
73+
def validate_nodes(self) -> Self:
74+
for node in self.nodes:
75+
if node.namespace != self.namespace:
76+
raise ValueError(f"Node namespace {node.namespace} does not match graph namespace {self.namespace}")
77+
return self
78+
79+
@field_validator('nodes')
80+
@classmethod
81+
def validate_unique_identifiers(cls, v: List[NodeTemplate]) -> List[NodeTemplate]:
82+
identifiers = set()
83+
errors = []
84+
for node in v:
85+
if node.identifier in identifiers:
86+
errors.append(f"Node identifier {node.identifier} is not unique")
87+
identifiers.add(node.identifier)
88+
if errors:
89+
raise ValueError("\n".join(errors))
90+
return v
91+
92+
@field_validator('nodes')
93+
@classmethod
94+
def validate_next_nodes_identifiers_exist(cls, v: List[NodeTemplate]) -> List[NodeTemplate]:
95+
identifiers = set()
96+
for node in v:
97+
identifiers.add(node.identifier)
98+
99+
errors = []
100+
for node in v:
101+
if node.next_nodes:
102+
for next_node in node.next_nodes:
103+
if next_node not in identifiers:
104+
errors.append(f"Node identifier {next_node} does not exist in the graph")
105+
if errors:
106+
raise ValueError("\n".join(errors))
107+
return v
108+
58109
@classmethod
59110
def _validate_secret_value(cls, secret_value: str) -> None:
60111
# Check minimum length for AES-GCM encrypted string
@@ -70,7 +121,6 @@ def _validate_secret_value(cls, secret_value: str) -> None:
70121
except Exception:
71122
raise ValueError("Value is not valid URL-safe base64 encoded")
72123

73-
74124
def set_secrets(self, secrets: Dict[str, str]) -> "GraphTemplate":
75125
self.secrets = {secret_name: get_encrypter().encrypt(secret_value) for secret_name, secret_value in secrets.items()}
76126
return self

state-manager/app/models/node_template_model.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pydantic import Field, BaseModel
1+
from pydantic import Field, BaseModel, field_validator
22
from typing import Any, Optional, List
33

44

@@ -12,4 +12,49 @@ class NodeTemplate(BaseModel):
1212
identifier: str = Field(..., description="Identifier of the node")
1313
inputs: dict[str, Any] = Field(..., description="Inputs of the node")
1414
next_nodes: Optional[List[str]] = Field(None, description="Next nodes to execute")
15-
unites: Optional[Unites] = Field(None, description="Unites of the node")
15+
unites: Optional[Unites] = Field(None, description="Unites of the node")
16+
17+
@field_validator('node_name')
18+
@classmethod
19+
def validate_node_name(cls, v: str) -> str:
20+
if v == "" or v is None:
21+
raise ValueError("Node name cannot be empty")
22+
return v
23+
24+
@field_validator('node_name')
25+
@classmethod
26+
def validate_node_name_unique(cls, v: str) -> str:
27+
if v == "" or v is None:
28+
raise ValueError("Node name cannot be empty")
29+
return v
30+
31+
@field_validator('identifier')
32+
@classmethod
33+
def validate_identifier_unique(cls, v: str) -> str:
34+
if v == "" or v is None:
35+
raise ValueError("Node identifier cannot be empty")
36+
return v
37+
38+
@field_validator('next_nodes')
39+
@classmethod
40+
def validate_next_nodes(cls, v: Optional[List[str]]) -> Optional[List[str]]:
41+
identifiers = set()
42+
errors = []
43+
if v is not None:
44+
for next_node_identifier in v:
45+
if next_node_identifier == "" or next_node_identifier is None:
46+
errors.append("Next node identifier cannot be empty")
47+
elif next_node_identifier in identifiers:
48+
errors.append(f"Next node identifier {next_node_identifier} is not unique")
49+
identifiers.add(next_node_identifier)
50+
if errors:
51+
raise ValueError("\n".join(errors))
52+
return v
53+
54+
@field_validator('unites')
55+
@classmethod
56+
def validate_unites(cls, v: Optional[Unites]) -> Optional[Unites]:
57+
if v is not None:
58+
if v.identifier == "" or v.identifier is None:
59+
raise ValueError("Unites identifier cannot be empty")
60+
return v

state-manager/app/tasks/verify_graph.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,6 @@
99

1010
logger = LogsManager().get_logger()
1111

12-
async def verify_nodes_names(nodes: list[NodeTemplate]) -> list[str]:
13-
errors = []
14-
for node in nodes:
15-
if node.node_name is None or node.node_name == "":
16-
errors.append(f"Node {node.identifier} has no name")
17-
return errors
18-
19-
async def verify_nodes_namespace(nodes: list[NodeTemplate], graph_namespace: str) -> list[str]:
20-
errors = []
21-
for node in nodes:
22-
if node.namespace != graph_namespace and node.namespace != "exospherehost":
23-
errors.append(f"Node {node.identifier} has invalid namespace '{node.namespace}'. Must match graph namespace '{graph_namespace}' or use universal namespace 'exospherehost'")
24-
return errors
25-
2612
async def verify_node_exists(nodes: list[NodeTemplate], database_nodes: list[RegisteredNode]) -> list[str]:
2713
errors = []
2814
template_nodes_set = set([(node.node_name, node.namespace) for node in nodes])
@@ -33,8 +19,6 @@ async def verify_node_exists(nodes: list[NodeTemplate], database_nodes: list[Reg
3319
for node in nodes_not_found:
3420
errors.append(f"Node {node[0]} in namespace {node[1]} does not exist.")
3521
return errors
36-
37-
async def verify_node_identifiers(nodes: list[NodeTemplate]) -> list[str]:
3822
errors = []
3923
identifier_to_nodes = {}
4024

@@ -252,10 +236,7 @@ async def verify_graph(graph_template: GraphTemplate):
252236
database_nodes = await get_database_nodes(graph_template.nodes, graph_template.namespace)
253237

254238
basic_verify_tasks = [
255-
verify_nodes_names(graph_template.nodes),
256-
verify_nodes_namespace(graph_template.nodes, graph_template.namespace),
257239
verify_node_exists(graph_template.nodes, database_nodes),
258-
verify_node_identifiers(graph_template.nodes),
259240
verify_secrets(graph_template, database_nodes)
260241
]
261242
errors.extend(await asyncio.gather(*basic_verify_tasks))

0 commit comments

Comments
 (0)