@@ -107,6 +107,10 @@ def node_names(self) -> list[str]:
107107 def generic_names (self ) -> list [str ]:
108108 return list (self .generics .keys ())
109109
110+ @property
111+ def generic_names_without_templates (self ) -> list [str ]:
112+ return [g for g in self .generic_names if not g .startswith ("Template" )]
113+
110114 @property
111115 def profile_names (self ) -> list [str ]:
112116 return list (self .profiles .keys ())
@@ -115,10 +119,10 @@ def profile_names(self) -> list[str]:
115119 def template_names (self ) -> list [str ]:
116120 return list (self .templates .keys ())
117121
118- def get_all_kind_id_map (self , exclude_profiles : bool = False ) -> dict [str , str ]:
122+ def get_all_kind_id_map (self , nodes_and_generics_only : bool = False ) -> dict [str , str ]:
119123 kind_id_map = {}
120- if exclude_profiles :
121- names = self .node_names + self .generic_names
124+ if nodes_and_generics_only :
125+ names = self .node_names + self .generic_names_without_templates
122126 else :
123127 names = self .all_names
124128 for name in names :
@@ -182,8 +186,8 @@ def from_dict_schema_object(cls, data: dict) -> Self:
182186
183187 def diff (self , other : SchemaBranch ) -> SchemaDiff :
184188 # Identify the nodes or generics that have been added or removed
185- local_kind_id_map = self .get_all_kind_id_map (exclude_profiles = True )
186- other_kind_id_map = other .get_all_kind_id_map (exclude_profiles = True )
189+ local_kind_id_map = self .get_all_kind_id_map (nodes_and_generics_only = True )
190+ other_kind_id_map = other .get_all_kind_id_map (nodes_and_generics_only = True )
187191 clean_local_ids = [id for id in local_kind_id_map .values () if id is not None ]
188192 clean_other_ids = [id for id in other_kind_id_map .values () if id is not None ]
189193 shared_ids = intersection (list1 = clean_local_ids , list2 = clean_other_ids )
@@ -687,7 +691,7 @@ def validate_schema_path(
687691 return schema_attribute_path
688692
689693 def sync_uniqueness_constraints_and_unique_attributes (self ) -> None :
690- for name in self .generic_names + self .node_names :
694+ for name in self .generic_names_without_templates + self .node_names :
691695 node_schema = self .get (name = name , duplicate = False )
692696
693697 if not node_schema .unique_attributes and not node_schema .uniqueness_constraints :
@@ -802,7 +806,7 @@ def validate_default_filters(self) -> None:
802806 )
803807
804808 def validate_default_values (self ) -> None :
805- for name in self .generic_names + self .node_names :
809+ for name in self .generic_names_without_templates + self .node_names :
806810 node_schema = self .get (name = name , duplicate = False )
807811 for node_attr in node_schema .local_attributes :
808812 if node_attr .default_value is None :
@@ -822,7 +826,7 @@ def validate_default_values(self) -> None:
822826 ) from exc
823827
824828 def validate_human_friendly_id (self ) -> None :
825- for name in self .generic_names + self .node_names :
829+ for name in self .generic_names_without_templates + self .node_names :
826830 node_schema = self .get (name = name , duplicate = False )
827831 hf_attr_names = set ()
828832
@@ -843,7 +847,7 @@ def validate_human_friendly_id(self) -> None:
843847
844848 def validate_required_relationships (self ) -> None :
845849 reverse_dependency_map : dict [str , set [str ]] = {}
846- for name in self .node_names + self .generic_names :
850+ for name in self .node_names + self .generic_names_without_templates :
847851 node_schema = self .get (name = name , duplicate = False )
848852 for relationship_schema in node_schema .relationships :
849853 if relationship_schema .optional :
@@ -861,7 +865,7 @@ def validate_required_relationships(self) -> None:
861865 def validate_parent_component (self ) -> None :
862866 # {parent_kind: {component_kind_1, component_kind_2, ...}}
863867 dependency_map : dict [str , set [str ]] = defaultdict (set )
864- for name in self .generic_names + self .node_names :
868+ for name in self .generic_names_without_templates + self .node_names :
865869 node_schema = self .get (name = name , duplicate = False )
866870
867871 parent_relationships : list [RelationshipSchema ] = []
@@ -1147,7 +1151,7 @@ def process_relationships(self) -> None:
11471151 self .set (name = schema_to_update .kind , schema = schema_to_update )
11481152
11491153 def process_human_friendly_id (self ) -> None :
1150- for name in self .generic_names + self .node_names :
1154+ for name in self .generic_names_without_templates + self .node_names :
11511155 node = self .get (name = name , duplicate = False )
11521156
11531157 # If human_friendly_id IS NOT defined
@@ -1634,7 +1638,7 @@ def manage_profile_schemas(self) -> None:
16341638 self .set (name = core_profile_schema .kind , schema = core_profile_schema )
16351639
16361640 profile_schema_kinds = set ()
1637- for node_name in self .node_names + self .generic_names :
1641+ for node_name in self .node_names + self .generic_names_without_templates :
16381642 node = self .get (name = node_name , duplicate = False )
16391643 if (
16401644 node .namespace in RESTRICTED_NAMESPACES
@@ -1865,37 +1869,64 @@ def add_relationships_to_template(self, node: NodeSchema) -> None:
18651869 )
18661870 )
18671871
1868- def generate_object_template_from_node (self , node : NodeSchema ) -> TemplateSchema :
1872+ def generate_object_template_from_node (
1873+ self , node : NodeSchema | GenericSchema , need_templates : set [NodeSchema | GenericSchema ]
1874+ ) -> TemplateSchema | GenericSchema :
18691875 core_template_schema = self .get (name = InfrahubKind .OBJECTTEMPLATE , duplicate = False )
18701876 core_name_attr = core_template_schema .get_attribute (name = OBJECT_TEMPLATE_NAME_ATTR )
18711877 template_name_attr = AttributeSchema (
18721878 ** core_name_attr .model_dump (exclude = ["id" , "inherited" ]),
18731879 )
18741880 template_name_attr .branch = node .branch
18751881
1876- template = TemplateSchema (
1877- name = node .kind ,
1878- namespace = "Template" ,
1879- label = f"Object template { node .label } " ,
1880- description = f"Object template for { node .kind } " ,
1881- branch = node .branch ,
1882- include_in_menu = False ,
1883- display_labels = ["template_name__value" ],
1884- inherit_from = [InfrahubKind .LINEAGESOURCE , InfrahubKind .OBJECTTEMPLATE , InfrahubKind .NODE ],
1885- human_friendly_id = ["template_name__value" ],
1886- default_filter = "template_name__value" ,
1887- attributes = [template_name_attr ],
1888- relationships = [
1889- RelationshipSchema (
1890- name = "related_nodes" ,
1891- identifier = "node__objecttemplate" ,
1892- peer = node .kind ,
1893- kind = RelationshipKind .TEMPLATE ,
1894- cardinality = RelationshipCardinality .MANY ,
1895- branch = BranchSupportType .AWARE ,
1896- )
1897- ],
1898- )
1882+ template : TemplateSchema | GenericSchema
1883+ need_template_kinds = [n .kind for n in need_templates ]
1884+
1885+ if isinstance (node , GenericSchema ):
1886+ # When needing a template for a generic, we generate an empty shell mostly to make sure that schemas (including the GraphQL one) will
1887+ # look right. We don't really care about applying inheritance of fields as it was already processed and actual templates will have the
1888+ # correct attributes and relationships
1889+ template = GenericSchema (
1890+ name = node .kind ,
1891+ namespace = "Template" ,
1892+ label = f"Generic object template { node .label } " ,
1893+ description = f"Generic object template for generic { node .kind } " ,
1894+ generate_profile = False ,
1895+ branch = node .branch ,
1896+ include_in_menu = False ,
1897+ )
1898+
1899+ for used in node .used_by :
1900+ if used in need_template_kinds :
1901+ template .used_by .append (self ._get_object_template_kind (node_kind = used ))
1902+ else :
1903+ template = TemplateSchema (
1904+ name = node .kind ,
1905+ namespace = "Template" ,
1906+ label = f"Object template { node .label } " ,
1907+ description = f"Object template for { node .kind } " ,
1908+ branch = node .branch ,
1909+ include_in_menu = False ,
1910+ display_labels = ["template_name__value" ],
1911+ inherit_from = [InfrahubKind .LINEAGESOURCE , InfrahubKind .OBJECTTEMPLATE , InfrahubKind .NODE ],
1912+ human_friendly_id = ["template_name__value" ],
1913+ default_filter = "template_name__value" ,
1914+ attributes = [template_name_attr ],
1915+ relationships = [
1916+ RelationshipSchema (
1917+ name = "related_nodes" ,
1918+ identifier = "node__objecttemplate" ,
1919+ peer = node .kind ,
1920+ kind = RelationshipKind .TEMPLATE ,
1921+ cardinality = RelationshipCardinality .MANY ,
1922+ branch = BranchSupportType .AWARE ,
1923+ )
1924+ ],
1925+ )
1926+
1927+ for inherited in node .inherit_from :
1928+ if inherited in need_template_kinds :
1929+ template .inherit_from .append (self ._get_object_template_kind (node_kind = inherited ))
18991930
19001931 for node_attr in node .attributes :
19011932 if node_attr .unique :
@@ -1918,15 +1949,22 @@ def identify_required_object_templates(
19181949 identified .add (node_schema )
19191950
19201951 for relationship in node_schema .relationships :
1921- if relationship .peer in [
1922- InfrahubKind . GENERICGROUP ,
1923- InfrahubKind . PROFILE ,
1924- ] or relationship . kind not in [ RelationshipKind . COMPONENT , RelationshipKind . PARENT ] :
1952+ if relationship .peer in [InfrahubKind . GENERICGROUP , InfrahubKind . PROFILE ] or relationship . kind not in [
1953+ RelationshipKind . COMPONENT ,
1954+ RelationshipKind . PARENT ,
1955+ ]:
19251956 continue
19261957
19271958 peer_schema = self .get (name = relationship .peer , duplicate = False )
19281959 if not isinstance (peer_schema , NodeSchema | GenericSchema ) or peer_schema in identified :
19291960 continue
1961+ # In a context of a generic, we won't be able to create objects out of it, so any kind of nodes implementing the generic is a valid
1962+ # option, we therefore need to have a template for each of those nodes
1963+ if isinstance (peer_schema , GenericSchema ) and peer_schema .used_by :
1964+ for used_by in peer_schema .used_by :
1965+ identified |= self .identify_required_object_templates (
1966+ node_schema = self .get (name = used_by , duplicate = False ), identified = identified
1967+ )
19301968
19311969 identified |= self .identify_required_object_templates (node_schema = peer_schema , identified = identified )
19321970
@@ -1936,7 +1974,7 @@ def manage_object_template_schemas(self) -> None:
19361974 need_templates : set [NodeSchema | GenericSchema ] = set ()
19371975 template_schema_kinds : set [str ] = set ()
19381976
1939- for node_name in self .node_names + self .generic_names :
1977+ for node_name in self .node_names + self .generic_names_without_templates :
19401978 node = self .get (name = node_name , duplicate = False )
19411979
19421980 # Delete old object templates if schemas were removed
@@ -1955,7 +1993,7 @@ def manage_object_template_schemas(self) -> None:
19551993
19561994 # Generate templates with their attributes
19571995 for node in need_templates :
1958- template = self .generate_object_template_from_node (node = node )
1996+ template = self .generate_object_template_from_node (node = node , need_templates = need_templates )
19591997 self .set (name = template .kind , schema = template )
19601998 template_schema_kinds .add (template .kind )
19611999
0 commit comments