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__ } ' object has no attribute '{ 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 } InfrahubNode" , (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 } ' on { 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 } InfrahubNodeSync" , (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 } ' on { 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