44from  typing  import  TYPE_CHECKING , Any 
55
66from  ..constants  import  InfrahubClientMode 
7- from  ..exceptions  import  (
8-     FeatureNotSupportedError ,
9-     NodeNotFoundError ,
10- )
7+ from  ..exceptions  import  FeatureNotSupportedError , NodeNotFoundError , SchemaNotFoundError 
118from  ..graphql  import  Mutation , Query 
129from  ..schema  import  GenericSchemaAPI , RelationshipCardinality , RelationshipKind 
1310from  ..utils  import  compare_lists , generate_short_id , get_flat_value 
3027    from  ..types  import  Order 
3128
3229
33- def  generate_relationship_property (node : InfrahubNode  |  InfrahubNodeSync , name : str ) ->  property :
34-     """Generates a property that stores values under a private non-public name. 
35- 
36-     Args: 
37-         node (Union[InfrahubNode, InfrahubNodeSync]): The node instance. 
38-         name (str): The name of the relationship property. 
39- 
40-     Returns: 
41-         A property object for managing the relationship. 
42- 
43-     """ 
44-     internal_name  =  "_"  +  name .lower ()
45-     external_name  =  name 
46- 
47-     def  prop_getter (self : InfrahubNodeBase ) ->  Any :
48-         return  getattr (self , internal_name )
49- 
50-     def  prop_setter (self : InfrahubNodeBase , value : Any ) ->  None :
51-         if  isinstance (value , RelatedNodeBase ) or  value  is  None :
52-             setattr (self , internal_name , value )
53-         else :
54-             schema  =  [rel  for  rel  in  self ._schema .relationships  if  rel .name  ==  external_name ][0 ]
55-             if  isinstance (node , InfrahubNode ):
56-                 setattr (
57-                     self ,
58-                     internal_name ,
59-                     RelatedNode (
60-                         name = external_name , branch = node ._branch , client = node ._client , schema = schema , data = value 
61-                     ),
62-                 )
63-             else :
64-                 setattr (
65-                     self ,
66-                     internal_name ,
67-                     RelatedNodeSync (
68-                         name = external_name , branch = node ._branch , client = node ._client , schema = schema , data = value 
69-                     ),
70-                 )
71- 
72-     return  property (prop_getter , prop_setter )
73- 
74- 
7530class  InfrahubNodeBase :
7631    """Base class for InfrahubNode and InfrahubNodeSync""" 
7732
@@ -86,6 +41,9 @@ def __init__(self, schema: MainSchemaTypesAPI, branch: str, data: dict | None =
8641        self ._data  =  data 
8742        self ._branch  =  branch 
8843        self ._existing : bool  =  True 
44+         self ._attribute_data : dict [str , Attribute ] =  {}
45+         self ._relationship_cardinality_many_data : dict [str , RelationshipManager  |  RelationshipManagerSync ] =  {}
46+         self ._relationship_cardinality_one_data : dict [str , RelatedNode  |  RelatedNodeSync ] =  {}
8947
9048        # Generate a unique ID only to be used inside the SDK 
9149        # The format if this ID is purposely different from the ID used by the API 
@@ -180,12 +138,30 @@ def hfid_str(self) -> str | None:
180138    def  _init_attributes (self , data : dict  |  None  =  None ) ->  None :
181139        for  attr_schema  in  self ._schema .attributes :
182140            attr_data  =  data .get (attr_schema .name , None ) if  isinstance (data , dict ) else  None 
183-             setattr (
184-                 self ,
185-                 attr_schema .name ,
186-                 Attribute (name = attr_schema .name , schema = attr_schema , data = attr_data ),
141+             self ._attribute_data [attr_schema .name ] =  Attribute (
142+                 name = attr_schema .name , schema = attr_schema , data = attr_data 
187143            )
188144
145+     def  __getattr__ (
146+         self , name : str 
147+     ) ->  Attribute  |  RelationshipManager  |  RelationshipManagerSync  |  RelatedNode  |  RelatedNodeSync :
148+         if  "_attribute_data"  in  self .__dict__  and  name  in  self ._attribute_data :
149+             return  self ._attribute_data [name ]
150+         if  "_relationship_cardinality_many_data"  in  self .__dict__  and  name  in  self ._relationship_cardinality_many_data :
151+             return  self ._relationship_cardinality_many_data [name ]
152+         if  "_relationship_cardinality_one_data"  in  self .__dict__  and  name  in  self ._relationship_cardinality_one_data :
153+             return  self ._relationship_cardinality_one_data [name ]
154+ 
155+         raise  AttributeError (f"'{ self .__class__ .__name__ } { name }  )
156+ 
157+     def  __setattr__ (self , name : str , value : Any ) ->  None :
158+         """Set values for attributes that exist or revert to normal behaviour""" 
159+         if  "_attribute_data"  in  self .__dict__  and  name  in  self ._attribute_data :
160+             self ._attribute_data [name ].value  =  value 
161+             return 
162+ 
163+         super ().__setattr__ (name , value )
164+ 
189165    def  _get_request_context (self , request_context : RequestContext  |  None  =  None ) ->  dict [str , Any ] |  None :
190166        if  request_context :
191167            return  request_context .model_dump (exclude_none = True )
@@ -506,7 +482,6 @@ def __init__(
506482            data: Optional data to initialize the node. 
507483        """ 
508484        self ._client  =  client 
509-         self .__class__  =  type (f"{ schema .kind }  , (self .__class__ ,), {})
510485
511486        if  isinstance (data , dict ) and  isinstance (data .get ("node" ), dict ):
512487            data  =  data .get ("node" )
@@ -535,27 +510,36 @@ def _init_relationships(self, data: dict | None = None) -> None:
535510            rel_data  =  data .get (rel_schema .name , None ) if  isinstance (data , dict ) else  None 
536511
537512            if  rel_schema .cardinality  ==  "one" :
538-                 setattr (self , f"_{ rel_schema .name }  , None )
539-                 setattr (
540-                     self .__class__ ,
541-                     rel_schema .name ,
542-                     generate_relationship_property (name = rel_schema .name , node = self ),
513+                 self ._relationship_cardinality_one_data [rel_schema .name ] =  RelatedNode (
514+                     name = rel_schema .name , branch = self ._branch , client = self ._client , schema = rel_schema , data = rel_data 
543515                )
544-                 setattr (self , rel_schema .name , rel_data )
545516            else :
546-                 setattr (
547-                     self ,
548-                     rel_schema .name ,
549-                     RelationshipManager (
550-                         name = rel_schema .name ,
551-                         client = self ._client ,
552-                         node = self ,
553-                         branch = self ._branch ,
554-                         schema = rel_schema ,
555-                         data = rel_data ,
556-                     ),
517+                 self ._relationship_cardinality_many_data [rel_schema .name ] =  RelationshipManager (
518+                     name = rel_schema .name ,
519+                     client = self ._client ,
520+                     node = self ,
521+                     branch = self ._branch ,
522+                     schema = rel_schema ,
523+                     data = rel_data ,
557524                )
558525
526+     def  __setattr__ (self , name : str , value : Any ) ->  None :
527+         """Set values for relationship names that exist or revert to normal behaviour""" 
528+         if  "_relationship_cardinality_one_data"  in  self .__dict__  and  name  in  self ._relationship_cardinality_one_data :
529+             rel_schemas  =  [rel_schema  for  rel_schema  in  self ._schema .relationships  if  rel_schema .name  ==  name ]
530+             if  not  rel_schemas :
531+                 raise  SchemaNotFoundError (
532+                     identifier = self ._schema .kind ,
533+                     message = f"Unable to find relationship schema for '{ name } { self ._schema .kind }  ,
534+                 )
535+             rel_schema  =  rel_schemas [0 ]
536+             self ._relationship_cardinality_one_data [name ] =  RelatedNode (
537+                 name = rel_schema .name , branch = self ._branch , client = self ._client , schema = rel_schema , data = value 
538+             )
539+             return 
540+ 
541+         super ().__setattr__ (name , value )
542+ 
559543    async  def  generate (self , nodes : list [str ] |  None  =  None ) ->  None :
560544        self ._validate_artifact_definition_support (ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE )
561545
@@ -1036,7 +1020,6 @@ def __init__(
10361020            branch (Optional[str]): The branch where the node resides. 
10371021            data (Optional[dict]): Optional data to initialize the node. 
10381022        """ 
1039-         self .__class__  =  type (f"{ schema .kind }  , (self .__class__ ,), {})
10401023        self ._client  =  client 
10411024
10421025        if  isinstance (data , dict ) and  isinstance (data .get ("node" ), dict ):
@@ -1066,26 +1049,36 @@ def _init_relationships(self, data: dict | None = None) -> None:
10661049            rel_data  =  data .get (rel_schema .name , None ) if  isinstance (data , dict ) else  None 
10671050
10681051            if  rel_schema .cardinality  ==  "one" :
1069-                 setattr (self , f"_{ rel_schema .name }  , None )
1070-                 setattr (
1071-                     self .__class__ ,
1072-                     rel_schema .name ,
1073-                     generate_relationship_property (name = rel_schema .name , node = self ),
1052+                 self ._relationship_cardinality_one_data [rel_schema .name ] =  RelatedNodeSync (
1053+                     name = rel_schema .name , branch = self ._branch , client = self ._client , schema = rel_schema , data = rel_data 
10741054                )
1075-                  setattr ( self ,  rel_schema . name ,  rel_data ) 
1055+ 
10761056            else :
1077-                 setattr (
1078-                     self ,
1079-                     rel_schema .name ,
1080-                     RelationshipManagerSync (
1081-                         name = rel_schema .name ,
1082-                         client = self ._client ,
1083-                         node = self ,
1084-                         branch = self ._branch ,
1085-                         schema = rel_schema ,
1086-                         data = rel_data ,
1087-                     ),
1057+                 self ._relationship_cardinality_many_data [rel_schema .name ] =  RelationshipManagerSync (
1058+                     name = rel_schema .name ,
1059+                     client = self ._client ,
1060+                     node = self ,
1061+                     branch = self ._branch ,
1062+                     schema = rel_schema ,
1063+                     data = rel_data ,
1064+                 )
1065+ 
1066+     def  __setattr__ (self , name : str , value : Any ) ->  None :
1067+         """Set values for relationship names that exist or revert to normal behaviour""" 
1068+         if  "_relationship_cardinality_one_data"  in  self .__dict__  and  name  in  self ._relationship_cardinality_one_data :
1069+             rel_schemas  =  [rel_schema  for  rel_schema  in  self ._schema .relationships  if  rel_schema .name  ==  name ]
1070+             if  not  rel_schemas :
1071+                 raise  SchemaNotFoundError (
1072+                     identifier = self ._schema .kind ,
1073+                     message = f"Unable to find relationship schema for '{ name } { self ._schema .kind }  ,
10881074                )
1075+             rel_schema  =  rel_schemas [0 ]
1076+             self ._relationship_cardinality_one_data [name ] =  RelatedNodeSync (
1077+                 name = rel_schema .name , branch = self ._branch , client = self ._client , schema = rel_schema , data = value 
1078+             )
1079+             return 
1080+ 
1081+         super ().__setattr__ (name , value )
10891082
10901083    def  generate (self , nodes : list [str ] |  None  =  None ) ->  None :
10911084        self ._validate_artifact_definition_support (ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE )
0 commit comments