1+ from  typing  import  AsyncGenerator 
2+ 
13from  infrahub .core .constants  import  DiffAction 
24from  infrahub .core .constants .database  import  DatabaseEdgeType 
35from  infrahub .core .schema .schema_branch  import  SchemaBranch 
6+ from  infrahub .types  import  ATTRIBUTE_PYTHON_TYPES 
47
58from  ..model .path  import  (
69    ConflictSelection ,
710    EnrichedDiffAttribute ,
811    EnrichedDiffConflict ,
12+     EnrichedDiffProperty ,
913    EnrichedDiffRoot ,
1014    EnrichedDiffSingleRelationship ,
1115)
12- from  .model  import  AttributeMergeDict , NodeMergeDict , RelationshipMergeDict 
16+ from  .model  import  (
17+     AttributeMergeDict ,
18+     AttributePropertyMergeDict ,
19+     NodeMergeDict ,
20+     PropertyMergeDict ,
21+     RelationshipMergeDict ,
22+     RelationshipPropertyMergeDict ,
23+ )
24+ 
25+ Primitives  =  str  |  bool  |  int  |  float 
1326
1427
1528class  DiffMergeSerializer :
16-     def  __init__ (self , schema_branch : SchemaBranch ) ->  None :
29+     def  __init__ (self , schema_branch : SchemaBranch ,  max_batch_size :  int ) ->  None :
1730        self .schema_branch  =  schema_branch 
31+         self .max_batch_size  =  max_batch_size 
1832        self ._relationship_id_cache : dict [tuple [str , str ], str ] =  {}
33+         self ._attribute_type_cache : dict [tuple [str , str ], type ] =  {}
34+ 
35+     def  _reset_caches (self ) ->  None :
36+         self ._relationship_id_cache  =  {}
37+         self ._attribute_type_cache  =  {}
1938
2039    def  _get_action (self , action : DiffAction , conflict : EnrichedDiffConflict  |  None ) ->  DiffAction :
2140        if  not  conflict :
@@ -29,32 +48,75 @@ def _get_action(self, action: DiffAction, conflict: EnrichedDiffConflict | None)
2948    def  _to_action_str (self , action : DiffAction ) ->  str :
3049        return  str (action .value ).upper ()
3150
32-     def  _get_relationship_identifier (self , schema_kind_str : str , relationship_name : str ) ->  str :
33-         cache_key  =  (schema_kind_str , relationship_name )
51+     def  _get_relationship_identifier (self , schema_kind : str , relationship_name : str ) ->  str :
52+         cache_key  =  (schema_kind , relationship_name )
3453        if  cache_key  in  self ._relationship_id_cache :
3554            return  self ._relationship_id_cache [cache_key ]
36-         node_schema  =  self .schema_branch .get (name = schema_kind_str , duplicate = False )
55+         node_schema  =  self .schema_branch .get (name = schema_kind , duplicate = False )
3756        relationship_schema  =  node_schema .get_relationship (name = relationship_name )
3857        relationship_identifier  =  relationship_schema .get_identifier ()
3958        self ._relationship_id_cache [cache_key ] =  relationship_identifier 
4059        return  relationship_identifier 
4160
42-     async  def  serialize (self , diff : EnrichedDiffRoot ) ->  list [NodeMergeDict ]:
61+     def  _get_property_type_for_attribute_value (self , schema_kind : str , attribute_name : str ) ->  type :
62+         cache_key  =  (schema_kind , attribute_name )
63+         if  cache_key  in  self ._attribute_type_cache :
64+             return  self ._attribute_type_cache [cache_key ]
65+         node_schema  =  self .schema_branch .get (name = schema_kind , duplicate = False )
66+         attribute_schema  =  node_schema .get_attribute (name = attribute_name )
67+         python_type  =  ATTRIBUTE_PYTHON_TYPES [attribute_schema .kind ]
68+         final_python_type : type  =  str 
69+         if  python_type  in  (str , int , float , bool ):
70+             final_python_type  =  python_type 
71+         self ._attribute_type_cache [cache_key ] =  final_python_type 
72+         return  final_python_type 
73+ 
74+     def  _convert_property_value (
75+         self , property_type : DatabaseEdgeType , raw_value : str  |  None , value_type : type  |  None  =  None 
76+     ) ->  Primitives  |  None :
77+         if  raw_value  is  None :
78+             if  property_type  is  DatabaseEdgeType .HAS_VALUE :
79+                 return  "NULL" 
80+             return  None 
81+         # peer IDs are strings 
82+         if  property_type  in  (DatabaseEdgeType .HAS_OWNER , DatabaseEdgeType .HAS_SOURCE , DatabaseEdgeType .IS_RELATED ):
83+             return  raw_value 
84+         # these are boolean 
85+         if  property_type  in  (DatabaseEdgeType .IS_VISIBLE , DatabaseEdgeType .IS_PROTECTED ):
86+             return  raw_value .lower () ==  "true" 
87+         # this must be HAS_VALUE 
88+         if  value_type :
89+             return  value_type (raw_value )
90+         return  raw_value 
91+ 
92+     async  def  serialize_diff (
93+         self , diff : EnrichedDiffRoot 
94+     ) ->  AsyncGenerator [
95+         tuple [list [NodeMergeDict ], list [AttributePropertyMergeDict  |  RelationshipPropertyMergeDict ]], None 
96+     ]:
97+         self ._reset_caches ()
4398        serialized_node_diffs  =  []
99+         serialized_property_diffs : list [AttributePropertyMergeDict  |  RelationshipPropertyMergeDict ] =  []
44100        for  node  in  diff .nodes :
45101            node_action  =  self ._get_action (action = node .action , conflict = node .conflict )
46-             attribute_diffs  =  [self ._serialize_attribute (attribute_diff = attr_diff ) for  attr_diff  in  node .attributes ]
102+             attribute_diffs  =  []
103+             for  attr_diff  in  node .attributes :
104+                 attribute_diff , attribute_property_diff  =  self ._serialize_attribute (
105+                     attribute_diff = attr_diff , node_uuid = node .uuid , node_kind = node .kind 
106+                 )
107+                 attribute_diffs .append (attribute_diff )
108+                 serialized_property_diffs .append (attribute_property_diff )
47109            relationship_diffs  =  []
48110            for  rel_diff  in  node .relationships :
49111                relationship_identifier  =  self ._get_relationship_identifier (
50-                     schema_kind_str = node .kind , relationship_name = rel_diff .name 
112+                     schema_kind = node .kind , relationship_name = rel_diff .name 
51113                )
52114                for  relationship_element_diff  in  rel_diff .relationships :
53-                     relationship_diffs .extend (
54-                         self ._serialize_relationship_element (
55-                             relationship_diff = relationship_element_diff , relationship_identifier = relationship_identifier 
56-                         )
115+                     element_diffs , relationship_property_diffs  =  self ._serialize_relationship_element (
116+                         relationship_diff = relationship_element_diff , relationship_identifier = relationship_identifier 
57117                    )
118+                     relationship_diffs .extend (element_diffs )
119+                     serialized_property_diffs .extend (relationship_property_diffs )
58120            serialized_node_diffs .append (
59121                NodeMergeDict (
60122                    uuid = node .uuid ,
@@ -63,41 +125,82 @@ async def serialize(self, diff: EnrichedDiffRoot) -> list[NodeMergeDict]:
63125                    relationships = relationship_diffs ,
64126                )
65127            )
66-         return  serialized_node_diffs 
128+             if  len (serialized_node_diffs ) ==  self .max_batch_size :
129+                 yield  (serialized_node_diffs , serialized_property_diffs )
130+                 serialized_node_diffs , serialized_property_diffs  =  [], []
131+         yield  (serialized_node_diffs , serialized_property_diffs )
132+ 
133+     def  _get_property_actions_and_values (
134+         self , property_diff : EnrichedDiffProperty , python_value_type : type 
135+     ) ->  list [tuple [DiffAction , Primitives ]]:
136+         action  =  property_diff .action 
137+         new_value  =  property_diff .new_value 
138+         if  property_diff .conflict  and  property_diff .conflict .selected_branch  is  ConflictSelection .BASE_BRANCH :
139+             action  =  property_diff .conflict .base_branch_action 
140+             if  property_diff .conflict .base_branch_value :
141+                 new_value  =  property_diff .conflict .base_branch_value 
142+         actions  =  [action ]
143+         if  property_diff .action  is  DiffAction .UPDATED :
144+             actions  =  [DiffAction .ADDED , DiffAction .REMOVED ]
145+         actions_and_values : list [tuple [DiffAction , Primitives ]] =  []
146+         for  action  in  actions :
147+             if  action  not  in DiffAction .ADDED , DiffAction .REMOVED ):
148+                 continue 
149+             if  action  is  DiffAction .ADDED :
150+                 raw_value  =  new_value 
151+             else :
152+                 raw_value  =  property_diff .previous_value 
153+             final_value  =  self ._convert_property_value (
154+                 property_type = property_diff .property_type , raw_value = raw_value , value_type = python_value_type 
155+             )
156+             if  final_value :
157+                 actions_and_values .append ((action , final_value ))
158+         return  actions_and_values 
67159
68-     def  _serialize_attribute (self , attribute_diff : EnrichedDiffAttribute ) ->  AttributeMergeDict :
69-         return  AttributeMergeDict (
160+     def  _serialize_attribute (
161+         self , attribute_diff : EnrichedDiffAttribute , node_uuid : str , node_kind : str 
162+     ) ->  tuple [AttributeMergeDict , AttributePropertyMergeDict ]:
163+         prop_dicts : list [PropertyMergeDict ] =  []
164+         python_type  =  self ._get_property_type_for_attribute_value (
165+             schema_kind = node_kind , attribute_name = attribute_diff .name 
166+         )
167+         for  property_diff  in  attribute_diff .properties :
168+             actions_and_values  =  self ._get_property_actions_and_values (
169+                 property_diff = property_diff , python_value_type = python_type 
170+             )
171+             for  action , value  in  actions_and_values :
172+                 prop_dicts .append (
173+                     PropertyMergeDict (
174+                         property_type = property_diff .property_type .value ,
175+                         action = self ._to_action_str (action = action ),
176+                         value = value ,
177+                     )
178+                 )
179+         attr_dict  =  AttributeMergeDict (
70180            name = attribute_diff .name ,
71181            action = self ._to_action_str (action = attribute_diff .action ),
72182        )
183+         attr_prop_dict  =  AttributePropertyMergeDict (
184+             node_uuid = node_uuid , attribute_name = attribute_diff .name , properties = prop_dicts 
185+         )
186+         return  attr_dict , attr_prop_dict 
73187
74188    def  _serialize_relationship_element (
75189        self , relationship_diff : EnrichedDiffSingleRelationship , relationship_identifier : str 
76-     ) ->  list [RelationshipMergeDict ]:
190+     ) ->  tuple [ list [RelationshipMergeDict ],  list [ RelationshipPropertyMergeDict ] ]:
77191        relationship_dicts  =  []
78192        for  property_diff  in  relationship_diff .properties :
79193            if  property_diff .property_type  is  not DatabaseEdgeType .IS_RELATED :
80194                continue 
81-             action  =  property_diff .action 
82-             new_value  =  relationship_diff .peer_id 
83-             if  property_diff .conflict  and  property_diff .conflict .selected_branch  is  ConflictSelection .BASE_BRANCH :
84-                 action  =  property_diff .conflict .base_branch_action 
85-                 if  property_diff .conflict .base_branch_value :
86-                     new_value  =  property_diff .conflict .base_branch_value 
87-             actions  =  [action ]
88-             if  property_diff .action  is  DiffAction .UPDATED :
89-                 actions  =  [DiffAction .ADDED , DiffAction .REMOVED ]
90-             actions_and_values : list [tuple [DiffAction , str ]] =  []
91-             for  action  in  actions :
92-                 if  action  is  DiffAction .ADDED :
93-                     actions_and_values .append ((action , new_value ))
94-                 elif  action  is  DiffAction .REMOVED  and  property_diff .previous_value :
95-                     actions_and_values .append ((action , property_diff .previous_value ))
96- 
97-         for  action , value  in  actions_and_values :
98-             relationship_dicts .append (
99-                 RelationshipMergeDict (
100-                     peer_id = value , name = relationship_identifier , action = self ._to_action_str (action = action )
101-                 )
195+             # TODO: next PR will add properties to this method 
196+             actions_and_values  =  self ._get_property_actions_and_values (
197+                 property_diff = property_diff , python_value_type = str 
102198            )
103-         return  relationship_dicts 
199+ 
200+             for  action , value  in  actions_and_values :
201+                 relationship_dicts .append (
202+                     RelationshipMergeDict (
203+                         peer_id = str (value ), name = relationship_identifier , action = self ._to_action_str (action = action )
204+                     )
205+                 )
206+         return  relationship_dicts , []
0 commit comments