66from  pydantic  import  BaseModel , Field 
77
88from  ..exceptions  import  ObjectValidationError , ValidationError 
9- from  ..schema  import  RelationshipSchema 
9+ from  ..schema  import  GenericSchemaAPI ,  RelationshipKind ,  RelationshipSchema 
1010from  ..yaml  import  InfrahubFile , InfrahubFileKind 
1111
1212if  TYPE_CHECKING :
@@ -59,6 +59,11 @@ def is_bidirectional(self) -> bool:
5959    def  is_mandatory (self ) ->  bool :
6060        if  not  self .peer_rel :
6161            return  False 
62+         # For hierarchical node, currently the relationship to the parent is always optional in the schema even if it's mandatory 
63+         # In order to build the tree from top to bottom, we need to consider it as mandatory 
64+         # While it should technically work bottom-up, it created some unexpected behavior while loading the menu 
65+         if  self .peer_rel .cardinality  ==  "one"  and  self .peer_rel .kind  ==  RelationshipKind .HIERARCHY :
66+             return  True 
6267        return  not  self .peer_rel .optional 
6368
6469    @property  
@@ -168,14 +173,28 @@ async def validate_format(self, client: InfrahubClient, branch: str | None = Non
168173        schema  =  await  client .schema .get (kind = self .kind , branch = branch )
169174        for  idx , item  in  enumerate (self .data ):
170175            errors .extend (
171-                 await  self .validate_object (client = client , position = [idx  +  1 ], schema = schema , data = item , branch = branch )
176+                 await  self .validate_object (
177+                     client = client ,
178+                     position = [idx  +  1 ],
179+                     schema = schema ,
180+                     data = item ,
181+                     branch = branch ,
182+                     default_schema_kind = self .kind ,
183+                 )
172184            )
173185        return  errors 
174186
175187    async  def  process (self , client : InfrahubClient , branch : str  |  None  =  None ) ->  None :
176188        schema  =  await  client .schema .get (kind = self .kind , branch = branch )
177189        for  idx , item  in  enumerate (self .data ):
178-             await  self .create_node (client = client , schema = schema , data = item , position = [idx  +  1 ], branch = branch )
190+             await  self .create_node (
191+                 client = client ,
192+                 schema = schema ,
193+                 data = item ,
194+                 position = [idx  +  1 ],
195+                 branch = branch ,
196+                 default_schema_kind = self .kind ,
197+             )
179198
180199    @classmethod  
181200    async  def  validate_object (
@@ -186,6 +205,7 @@ async def validate_object(
186205        position : list [int  |  str ],
187206        context : dict  |  None  =  None ,
188207        branch : str  |  None  =  None ,
208+         default_schema_kind : str  |  None  =  None ,
189209    ) ->  list [ObjectValidationError ]:
190210        errors : list [ObjectValidationError ] =  []
191211        context  =  context .copy () if  context  else  {}
@@ -234,6 +254,7 @@ async def validate_object(
234254                        data = value ,
235255                        context = context ,
236256                        branch = branch ,
257+                         default_schema_kind = default_schema_kind ,
237258                    )
238259                )
239260
@@ -248,6 +269,7 @@ async def validate_related_nodes(
248269        data : dict  |  list [dict ],
249270        context : dict  |  None  =  None ,
250271        branch : str  |  None  =  None ,
272+         default_schema_kind : str  |  None  =  None ,
251273    ) ->  list [ObjectValidationError ]:
252274        context  =  context .copy () if  context  else  {}
253275        errors : list [ObjectValidationError ] =  []
@@ -260,7 +282,9 @@ async def validate_related_nodes(
260282
261283        if  isinstance (data , dict ) and  rel_info .format  ==  RelationshipDataFormat .ONE_OBJ :
262284            peer_kind  =  data .get ("kind" ) or  rel_info .peer_kind 
263-             peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
285+             peer_schema  =  await  cls .get_peer_schema (
286+                 client = client , peer_kind = peer_kind , branch = branch , default_schema_kind = default_schema_kind 
287+             )
264288
265289            rel_info .find_matching_relationship (peer_schema = peer_schema )
266290            context .update (rel_info .get_context (value = "placeholder" ))
@@ -273,13 +297,16 @@ async def validate_related_nodes(
273297                    data = data ["data" ],
274298                    context = context ,
275299                    branch = branch ,
300+                     default_schema_kind = default_schema_kind ,
276301                )
277302            )
278303            return  errors 
279304
280305        if  isinstance (data , dict ) and  rel_info .format  ==  RelationshipDataFormat .MANY_OBJ_DICT_LIST :
281306            peer_kind  =  data .get ("kind" ) or  rel_info .peer_kind 
282-             peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
307+             peer_schema  =  await  cls .get_peer_schema (
308+                 client = client , peer_kind = peer_kind , branch = branch , default_schema_kind = default_schema_kind 
309+             )
283310
284311            rel_info .find_matching_relationship (peer_schema = peer_schema )
285312            context .update (rel_info .get_context (value = "placeholder" ))
@@ -294,6 +321,7 @@ async def validate_related_nodes(
294321                        data = peer_data ,
295322                        context = context ,
296323                        branch = branch ,
324+                         default_schema_kind = default_schema_kind ,
297325                    )
298326                )
299327            return  errors 
@@ -302,7 +330,9 @@ async def validate_related_nodes(
302330            for  idx , item  in  enumerate (data ):
303331                context ["list_index" ] =  idx 
304332                peer_kind  =  item .get ("kind" ) or  rel_info .peer_kind 
305-                 peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
333+                 peer_schema  =  await  cls .get_peer_schema (
334+                     client = client , peer_kind = peer_kind , branch = branch , default_schema_kind = default_schema_kind 
335+                 )
306336
307337                rel_info .find_matching_relationship (peer_schema = peer_schema )
308338                context .update (rel_info .get_context (value = "placeholder" ))
@@ -315,6 +345,7 @@ async def validate_related_nodes(
315345                        data = item ["data" ],
316346                        context = context ,
317347                        branch = branch ,
348+                         default_schema_kind = default_schema_kind ,
318349                    )
319350                )
320351            return  errors 
@@ -345,7 +376,13 @@ async def create_node(
345376        context  =  context .copy () if  context  else  {}
346377
347378        errors  =  await  cls .validate_object (
348-             client = client , position = position , schema = schema , data = data , context = context , branch = branch 
379+             client = client ,
380+             position = position ,
381+             schema = schema ,
382+             data = data ,
383+             context = context ,
384+             branch = branch ,
385+             default_schema_kind = default_schema_kind ,
349386        )
350387        if  errors :
351388            messages  =  [str (error ) for  error  in  errors ]
@@ -480,7 +517,9 @@ async def create_related_nodes(
480517
481518        if  isinstance (data , dict ) and  rel_info .format  ==  RelationshipDataFormat .MANY_OBJ_DICT_LIST :
482519            peer_kind  =  data .get ("kind" ) or  rel_info .peer_kind 
483-             peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
520+             peer_schema  =  await  cls .get_peer_schema (
521+                 client = client , peer_kind = peer_kind , branch = branch , default_schema_kind = default_schema_kind 
522+             )
484523
485524            if  parent_node :
486525                rel_info .find_matching_relationship (peer_schema = peer_schema )
@@ -506,7 +545,9 @@ async def create_related_nodes(
506545                context ["list_index" ] =  idx 
507546
508547                peer_kind  =  item .get ("kind" ) or  rel_info .peer_kind 
509-                 peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
548+                 peer_schema  =  await  cls .get_peer_schema (
549+                     client = client , peer_kind = peer_kind , branch = branch , default_schema_kind = default_schema_kind 
550+                 )
510551
511552                if  parent_node :
512553                    rel_info .find_matching_relationship (peer_schema = peer_schema )
@@ -529,6 +570,23 @@ async def create_related_nodes(
529570            f"Relationship { rel_info .rel_schema .name } { rel_info .rel_schema .cardinality } { type (data )}  
530571        )
531572
573+     @classmethod  
574+     async  def  get_peer_schema (
575+         cls , client : InfrahubClient , peer_kind : str , branch : str  |  None  =  None , default_schema_kind : str  |  None  =  None 
576+     ) ->  MainSchemaTypesAPI :
577+         peer_schema  =  await  client .schema .get (kind = peer_kind , branch = branch )
578+         if  not  isinstance (peer_schema , GenericSchemaAPI ):
579+             return  peer_schema 
580+ 
581+         if  not  default_schema_kind :
582+             raise  ValueError (f"Found a peer schema as a generic { peer_kind }  )
583+ 
584+         # if the initial peer_kind was a generic, we try the default_schema_kind 
585+         peer_schema  =  await  client .schema .get (kind = default_schema_kind , branch = branch )
586+         if  isinstance (peer_schema , GenericSchemaAPI ):
587+             raise  ValueError (f"Default schema kind { default_schema_kind }  )
588+         return  peer_schema 
589+ 
532590
533591class  ObjectFile (InfrahubFile ):
534592    _spec : InfrahubObjectFileData  |  None  =  None 
0 commit comments