1+ from pydantic import BaseModel , Field , field_validator
2+ from typing import Any , Optional , List
3+ from enum import Enum
4+
5+
6+ class UnitesStrategyEnum (str , Enum ):
7+ ALL_SUCCESS = "ALL_SUCCESS"
8+ ALL_DONE = "ALL_DONE"
9+
10+
11+ class UnitesModel (BaseModel ):
12+ identifier : str = Field (..., description = "Identifier of the node" )
13+ strategy : UnitesStrategyEnum = Field (default = UnitesStrategyEnum .ALL_SUCCESS , description = "Strategy of the unites" )
14+
15+
16+ class GraphNodeModel (BaseModel ):
17+ node_name : str = Field (..., description = "Name of the node" )
18+ namespace : str = Field (..., description = "Namespace of the node" )
19+ identifier : str = Field (..., description = "Identifier of the node" )
20+ inputs : dict [str , Any ] = Field (..., description = "Inputs of the node" )
21+ next_nodes : Optional [List [str ]] = Field (None , description = "Next nodes to execute" )
22+ unites : Optional [UnitesModel ] = Field (None , description = "Unites of the node" )
23+
24+ @field_validator ('node_name' )
25+ @classmethod
26+ def validate_node_name (cls , v : str ) -> str :
27+ trimmed_v = v .strip ()
28+ if trimmed_v == "" or trimmed_v is None :
29+ raise ValueError ("Node name cannot be empty" )
30+ return trimmed_v
31+
32+ @field_validator ('identifier' )
33+ @classmethod
34+ def validate_identifier (cls , v : str ) -> str :
35+ trimmed_v = v .strip ()
36+ if trimmed_v == "" or trimmed_v is None :
37+ raise ValueError ("Node identifier cannot be empty" )
38+ elif trimmed_v == "store" :
39+ raise ValueError ("Node identifier cannot be reserved word 'store'" )
40+ return trimmed_v
41+
42+ @field_validator ('next_nodes' )
43+ @classmethod
44+ def validate_next_nodes (cls , v : Optional [List [str ]]) -> Optional [List [str ]]:
45+ identifiers = set ()
46+ errors = []
47+ trimmed_v = []
48+
49+ if v is not None :
50+ for next_node_identifier in v :
51+ trimmed_next_node_identifier = next_node_identifier .strip ()
52+
53+ if trimmed_next_node_identifier == "" or trimmed_next_node_identifier is None :
54+ errors .append ("Next node identifier cannot be empty" )
55+ continue
56+
57+ if trimmed_next_node_identifier in identifiers :
58+ errors .append (f"Next node identifier { trimmed_next_node_identifier } is not unique" )
59+ continue
60+
61+ identifiers .add (trimmed_next_node_identifier )
62+ trimmed_v .append (trimmed_next_node_identifier )
63+ if errors :
64+ raise ValueError ("\n " .join (errors ))
65+ return trimmed_v
66+
67+ @field_validator ('unites' )
68+ @classmethod
69+ def validate_unites (cls , v : Optional [UnitesModel ]) -> Optional [UnitesModel ]:
70+ trimmed_v = v
71+ if v is not None :
72+ trimmed_v = UnitesModel (identifier = v .identifier .strip (), strategy = v .strategy )
73+ if trimmed_v .identifier == "" or trimmed_v .identifier is None :
74+ raise ValueError ("Unites identifier cannot be empty" )
75+ return trimmed_v
76+
77+
78+ class RetryStrategyEnum (str , Enum ):
79+ EXPONENTIAL = "EXPONENTIAL"
80+ EXPONENTIAL_FULL_JITTER = "EXPONENTIAL_FULL_JITTER"
81+ EXPONENTIAL_EQUAL_JITTER = "EXPONENTIAL_EQUAL_JITTER"
82+
83+ LINEAR = "LINEAR"
84+ LINEAR_FULL_JITTER = "LINEAR_FULL_JITTER"
85+ LINEAR_EQUAL_JITTER = "LINEAR_EQUAL_JITTER"
86+
87+ FIXED = "FIXED"
88+ FIXED_FULL_JITTER = "FIXED_FULL_JITTER"
89+ FIXED_EQUAL_JITTER = "FIXED_EQUAL_JITTER"
90+
91+
92+ class RetryPolicyModel (BaseModel ):
93+ max_retries : int = Field (default = 3 , description = "The maximum number of retries" , ge = 0 )
94+ strategy : RetryStrategyEnum = Field (default = RetryStrategyEnum .EXPONENTIAL , description = "The method of retry" )
95+ backoff_factor : int = Field (default = 2000 , description = "The backoff factor in milliseconds (default: 2000 = 2 seconds)" , gt = 0 )
96+ exponent : int = Field (default = 2 , description = "The exponent for the exponential retry strategy" , gt = 0 )
97+ max_delay : int | None = Field (default = None , description = "The maximum delay in milliseconds (no default limit when None)" , gt = 0 )
98+
99+
100+ class StoreConfigModel (BaseModel ):
101+ required_keys : list [str ] = Field (default_factory = list , description = "Required keys of the store" )
102+ default_values : dict [str , str ] = Field (default_factory = dict , description = "Default values of the store" )
103+
104+ @field_validator ("required_keys" )
105+ def validate_required_keys (cls , v : list [str ]) -> list [str ]:
106+ errors = []
107+ keys = set ()
108+ trimmed_keys = []
109+
110+ for key in v :
111+ trimmed_key = key .strip () if key is not None else ""
112+
113+ if trimmed_key == "" :
114+ errors .append ("Key cannot be empty or contain only whitespace" )
115+ continue
116+
117+ if '.' in trimmed_key :
118+ errors .append (f"Key '{ trimmed_key } ' cannot contain '.' character" )
119+ continue
120+
121+ if trimmed_key in keys :
122+ errors .append (f"Key '{ trimmed_key } ' is duplicated" )
123+ continue
124+
125+ keys .add (trimmed_key )
126+ trimmed_keys .append (trimmed_key )
127+
128+ if len (errors ) > 0 :
129+ raise ValueError ("\n " .join (errors ))
130+ return trimmed_keys
131+
132+ @field_validator ("default_values" )
133+ def validate_default_values (cls , v : dict [str , str ]) -> dict [str , str ]:
134+ errors = []
135+ keys = set ()
136+ normalized_dict = {}
137+
138+ for key , value in v .items ():
139+ trimmed_key = key .strip () if key is not None else ""
140+
141+ if trimmed_key == "" :
142+ errors .append ("Key cannot be empty or contain only whitespace" )
143+ continue
144+
145+ if '.' in trimmed_key :
146+ errors .append (f"Key '{ trimmed_key } ' cannot contain '.' character" )
147+ continue
148+
149+ if trimmed_key in keys :
150+ errors .append (f"Key '{ trimmed_key } ' is duplicated" )
151+ continue
152+
153+ keys .add (trimmed_key )
154+ normalized_dict [trimmed_key ] = str (value )
155+
156+ if len (errors ) > 0 :
157+ raise ValueError ("\n " .join (errors ))
158+ return normalized_dict
0 commit comments