infrahub-server 1.2.11__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. infrahub/actions/constants.py +130 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +243 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +393 -0
  6. infrahub/actions/tasks.py +119 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/branch/__init__.py +0 -0
  9. infrahub/branch/tasks.py +29 -0
  10. infrahub/branch/triggers.py +22 -0
  11. infrahub/cli/db.py +3 -4
  12. infrahub/computed_attribute/gather.py +3 -1
  13. infrahub/computed_attribute/tasks.py +23 -29
  14. infrahub/core/account.py +24 -47
  15. infrahub/core/attribute.py +13 -15
  16. infrahub/core/constants/__init__.py +10 -0
  17. infrahub/core/constants/database.py +1 -0
  18. infrahub/core/constants/infrahubkind.py +9 -0
  19. infrahub/core/constraint/node/runner.py +3 -1
  20. infrahub/core/convert_object_type/__init__.py +0 -0
  21. infrahub/core/convert_object_type/conversion.py +124 -0
  22. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  23. infrahub/core/diff/coordinator.py +8 -1
  24. infrahub/core/diff/query/all_conflicts.py +1 -5
  25. infrahub/core/diff/query/artifact.py +10 -20
  26. infrahub/core/diff/query/delete_query.py +8 -4
  27. infrahub/core/diff/query/diff_get.py +3 -6
  28. infrahub/core/diff/query/field_specifiers.py +1 -1
  29. infrahub/core/diff/query/field_summary.py +2 -4
  30. infrahub/core/diff/query/merge.py +72 -125
  31. infrahub/core/diff/query/save.py +83 -68
  32. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  33. infrahub/core/diff/query/time_range_query.py +0 -1
  34. infrahub/core/diff/repository/repository.py +4 -0
  35. infrahub/core/graph/__init__.py +1 -1
  36. infrahub/core/manager.py +14 -11
  37. infrahub/core/migrations/graph/__init__.py +6 -0
  38. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  39. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  41. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  42. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  43. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  44. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  45. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  46. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  47. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  48. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  49. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
  50. infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
  51. infrahub/core/migrations/query/attribute_add.py +14 -11
  52. infrahub/core/migrations/query/attribute_rename.py +6 -11
  53. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  54. infrahub/core/migrations/query/node_duplicate.py +19 -21
  55. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  56. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  57. infrahub/core/migrations/schema/node_remove.py +19 -20
  58. infrahub/core/models.py +29 -2
  59. infrahub/core/node/__init__.py +131 -28
  60. infrahub/core/node/base.py +1 -1
  61. infrahub/core/node/create.py +211 -0
  62. infrahub/core/node/resource_manager/number_pool.py +31 -5
  63. infrahub/core/node/standard.py +6 -1
  64. infrahub/core/path.py +15 -1
  65. infrahub/core/protocols.py +57 -0
  66. infrahub/core/protocols_base.py +3 -0
  67. infrahub/core/query/__init__.py +2 -2
  68. infrahub/core/query/delete.py +3 -3
  69. infrahub/core/query/diff.py +19 -32
  70. infrahub/core/query/ipam.py +10 -20
  71. infrahub/core/query/node.py +29 -47
  72. infrahub/core/query/relationship.py +55 -34
  73. infrahub/core/query/resource_manager.py +1 -2
  74. infrahub/core/query/standard_node.py +19 -5
  75. infrahub/core/query/subquery.py +2 -4
  76. infrahub/core/relationship/constraints/count.py +10 -9
  77. infrahub/core/relationship/constraints/interface.py +2 -1
  78. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  79. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  80. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  81. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  82. infrahub/core/relationship/model.py +4 -1
  83. infrahub/core/schema/__init__.py +2 -1
  84. infrahub/core/schema/attribute_parameters.py +160 -0
  85. infrahub/core/schema/attribute_schema.py +130 -7
  86. infrahub/core/schema/basenode_schema.py +27 -3
  87. infrahub/core/schema/definitions/core/__init__.py +29 -1
  88. infrahub/core/schema/definitions/core/group.py +45 -0
  89. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  90. infrahub/core/schema/definitions/internal.py +43 -5
  91. infrahub/core/schema/generated/attribute_schema.py +16 -3
  92. infrahub/core/schema/generated/relationship_schema.py +11 -1
  93. infrahub/core/schema/manager.py +7 -2
  94. infrahub/core/schema/schema_branch.py +109 -12
  95. infrahub/core/validators/__init__.py +15 -2
  96. infrahub/core/validators/attribute/choices.py +1 -3
  97. infrahub/core/validators/attribute/enum.py +1 -3
  98. infrahub/core/validators/attribute/kind.py +1 -3
  99. infrahub/core/validators/attribute/length.py +13 -7
  100. infrahub/core/validators/attribute/min_max.py +118 -0
  101. infrahub/core/validators/attribute/number_pool.py +106 -0
  102. infrahub/core/validators/attribute/optional.py +1 -4
  103. infrahub/core/validators/attribute/regex.py +5 -6
  104. infrahub/core/validators/attribute/unique.py +1 -3
  105. infrahub/core/validators/determiner.py +18 -2
  106. infrahub/core/validators/enum.py +12 -0
  107. infrahub/core/validators/node/hierarchy.py +3 -6
  108. infrahub/core/validators/query.py +1 -3
  109. infrahub/core/validators/relationship/count.py +6 -12
  110. infrahub/core/validators/relationship/optional.py +2 -4
  111. infrahub/core/validators/relationship/peer.py +177 -12
  112. infrahub/core/validators/tasks.py +1 -1
  113. infrahub/core/validators/uniqueness/query.py +5 -9
  114. infrahub/database/__init__.py +12 -4
  115. infrahub/database/validation.py +100 -0
  116. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  117. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  118. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  119. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  120. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  121. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  122. infrahub/dependencies/registry.py +4 -0
  123. infrahub/events/group_action.py +1 -0
  124. infrahub/events/models.py +1 -1
  125. infrahub/git/base.py +5 -3
  126. infrahub/git/integrator.py +96 -5
  127. infrahub/git/tasks.py +1 -0
  128. infrahub/graphql/analyzer.py +139 -18
  129. infrahub/graphql/manager.py +4 -0
  130. infrahub/graphql/mutations/action.py +164 -0
  131. infrahub/graphql/mutations/convert_object_type.py +71 -0
  132. infrahub/graphql/mutations/main.py +25 -176
  133. infrahub/graphql/mutations/proposed_change.py +20 -17
  134. infrahub/graphql/mutations/relationship.py +32 -0
  135. infrahub/graphql/mutations/resource_manager.py +63 -7
  136. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  137. infrahub/graphql/queries/resource_manager.py +7 -1
  138. infrahub/graphql/resolvers/many_relationship.py +1 -1
  139. infrahub/graphql/resolvers/resolver.py +2 -2
  140. infrahub/graphql/resolvers/single_relationship.py +1 -1
  141. infrahub/graphql/schema.py +6 -0
  142. infrahub/menu/menu.py +34 -2
  143. infrahub/message_bus/messages/__init__.py +0 -10
  144. infrahub/message_bus/operations/__init__.py +0 -8
  145. infrahub/message_bus/operations/refresh/registry.py +4 -7
  146. infrahub/patch/queries/delete_duplicated_edges.py +45 -39
  147. infrahub/pools/models.py +14 -0
  148. infrahub/pools/number.py +5 -3
  149. infrahub/pools/registration.py +22 -0
  150. infrahub/pools/tasks.py +126 -0
  151. infrahub/prefect_server/models.py +1 -19
  152. infrahub/proposed_change/models.py +68 -3
  153. infrahub/proposed_change/tasks.py +911 -34
  154. infrahub/schema/__init__.py +0 -0
  155. infrahub/schema/tasks.py +27 -0
  156. infrahub/schema/triggers.py +23 -0
  157. infrahub/task_manager/models.py +10 -6
  158. infrahub/trigger/catalogue.py +6 -0
  159. infrahub/trigger/models.py +23 -6
  160. infrahub/trigger/setup.py +26 -2
  161. infrahub/trigger/tasks.py +4 -2
  162. infrahub/types.py +6 -0
  163. infrahub/webhook/tasks.py +6 -9
  164. infrahub/workflows/catalogue.py +103 -1
  165. infrahub_sdk/client.py +43 -10
  166. infrahub_sdk/ctl/generator.py +4 -4
  167. infrahub_sdk/ctl/repository.py +1 -1
  168. infrahub_sdk/node/__init__.py +39 -0
  169. infrahub_sdk/node/attribute.py +122 -0
  170. infrahub_sdk/node/constants.py +21 -0
  171. infrahub_sdk/{node.py → node/node.py} +158 -803
  172. infrahub_sdk/node/parsers.py +15 -0
  173. infrahub_sdk/node/property.py +24 -0
  174. infrahub_sdk/node/related_node.py +266 -0
  175. infrahub_sdk/node/relationship.py +302 -0
  176. infrahub_sdk/protocols.py +112 -0
  177. infrahub_sdk/protocols_base.py +34 -2
  178. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  179. infrahub_sdk/query_groups.py +17 -5
  180. infrahub_sdk/schema/main.py +1 -0
  181. infrahub_sdk/schema/repository.py +16 -0
  182. infrahub_sdk/spec/object.py +1 -1
  183. infrahub_sdk/store.py +1 -1
  184. infrahub_sdk/testing/schemas/car_person.py +1 -0
  185. infrahub_sdk/utils.py +7 -20
  186. infrahub_sdk/yaml.py +6 -5
  187. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +5 -5
  188. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +197 -168
  189. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +1 -1
  190. infrahub_testcontainers/container.py +239 -65
  191. infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
  192. infrahub_testcontainers/docker-compose.test.yml +2 -1
  193. infrahub_testcontainers/helpers.py +23 -3
  194. infrahub_testcontainers/plugin.py +9 -0
  195. infrahub/message_bus/messages/check_generator_run.py +0 -26
  196. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  197. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  198. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  199. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  200. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  201. infrahub/message_bus/operations/check/__init__.py +0 -3
  202. infrahub/message_bus/operations/check/generator.py +0 -156
  203. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  204. infrahub/message_bus/operations/finalize/validator.py +0 -133
  205. infrahub/message_bus/operations/requests/__init__.py +0 -9
  206. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  207. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  208. infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -109
  209. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  210. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  211. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,11 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ RepositoryObjects,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
8
+ from infrahub.core.schema.dropdown import DropdownChoice
7
9
 
8
10
  from ...attribute_schema import AttributeSchema as Attr
9
11
  from ...generic_schema import GenericSchema
@@ -80,6 +82,7 @@ core_generator_group = NodeSchema(
80
82
  generate_profile=False,
81
83
  )
82
84
 
85
+
83
86
  core_graphql_query_group = NodeSchema(
84
87
  name="GraphQLQueryGroup",
85
88
  namespace="Core",
@@ -106,3 +109,45 @@ core_graphql_query_group = NodeSchema(
106
109
  ),
107
110
  ],
108
111
  )
112
+
113
+
114
+ core_repository_group = NodeSchema(
115
+ name="RepositoryGroup",
116
+ namespace="Core",
117
+ description="Group of nodes associated with a given repository.",
118
+ include_in_menu=False,
119
+ icon="mdi:account-group",
120
+ label="Repository Group",
121
+ default_filter="name__value",
122
+ order_by=["name__value"],
123
+ display_labels=["name__value"],
124
+ branch=BranchSupportType.LOCAL,
125
+ inherit_from=[InfrahubKind.GENERICGROUP],
126
+ generate_profile=False,
127
+ attributes=[
128
+ Attr(
129
+ name="content",
130
+ kind="Dropdown",
131
+ description="Type of data to load, can be either `object` or `menu`",
132
+ choices=[
133
+ DropdownChoice(
134
+ name=RepositoryObjects.OBJECT.value,
135
+ label="Objects",
136
+ ),
137
+ DropdownChoice(
138
+ name=RepositoryObjects.MENU.value,
139
+ label="Menus",
140
+ ),
141
+ ],
142
+ optional=False,
143
+ ),
144
+ ],
145
+ relationships=[
146
+ Rel(
147
+ name="repository",
148
+ peer=InfrahubKind.GENERICREPOSITORY,
149
+ optional=False,
150
+ cardinality=Cardinality.ONE,
151
+ ),
152
+ ],
153
+ )
@@ -1,6 +1,7 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ NumberPoolType,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
@@ -186,5 +187,13 @@ core_number_pool = NodeSchema(
186
187
  Attr(
187
188
  name="end_range", kind="Number", optional=False, description="The end range for the pool", order_weight=6000
188
189
  ),
190
+ Attr(
191
+ name="pool_type",
192
+ kind="Text",
193
+ description="Defines how this number pool was created",
194
+ default_value=NumberPoolType.USER.value,
195
+ enum=NumberPoolType.available_types(),
196
+ read_only=True,
197
+ ),
189
198
  ],
190
199
  )
@@ -31,6 +31,12 @@ from infrahub.core.constants import (
31
31
  RelationshipKind,
32
32
  UpdateSupport,
33
33
  )
34
+ from infrahub.core.schema.attribute_parameters import (
35
+ AttributeParameters,
36
+ NumberAttributeParameters,
37
+ NumberPoolParameters,
38
+ TextAttributeParameters,
39
+ )
34
40
  from infrahub.core.schema.attribute_schema import AttributeSchema
35
41
  from infrahub.core.schema.computed_attribute import ComputedAttribute
36
42
  from infrahub.core.schema.dropdown import DropdownChoice
@@ -47,7 +53,7 @@ class SchemaAttribute(BaseModel):
47
53
  kind: str
48
54
  description: str
49
55
  extra: ExtraField
50
- internal_kind: type[Any] | GenericAlias | None = None
56
+ internal_kind: type[Any] | GenericAlias | list[type[Any]] | None = None
51
57
  regex: str | None = None
52
58
  unique: bool | None = None
53
59
  optional: bool | None = None
@@ -76,7 +82,7 @@ class SchemaAttribute(BaseModel):
76
82
 
77
83
  @property
78
84
  def optional_in_model(self) -> bool:
79
- if self.optional and self.default_value is None and self.default_factory is None or self.default_to_none:
85
+ if (self.optional and self.default_value is None and self.default_factory is None) or self.default_to_none:
80
86
  return True
81
87
 
82
88
  return False
@@ -93,6 +99,9 @@ class SchemaAttribute(BaseModel):
93
99
  if isinstance(self.internal_kind, GenericAlias):
94
100
  return str(self.internal_kind)
95
101
 
102
+ if isinstance(self.internal_kind, list):
103
+ return " | ".join([internal_kind.__name__ for internal_kind in self.internal_kind])
104
+
96
105
  if self.internal_kind and self.kind == "List":
97
106
  return f"list[{self.internal_kind.__name__}]"
98
107
 
@@ -506,21 +515,21 @@ attribute_schema = SchemaNode(
506
515
  SchemaAttribute(
507
516
  name="regex",
508
517
  kind="Text",
509
- description="Regex uses to limit the characters allowed in for the attributes.",
518
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
510
519
  optional=True,
511
520
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
512
521
  ),
513
522
  SchemaAttribute(
514
523
  name="max_length",
515
524
  kind="Number",
516
- description="Set a maximum number of characters allowed for a given attribute.",
525
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
517
526
  optional=True,
518
527
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
519
528
  ),
520
529
  SchemaAttribute(
521
530
  name="min_length",
522
531
  kind="Number",
523
- description="Set a minimum number of characters allowed for a given attribute.",
532
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
524
533
  optional=True,
525
534
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
526
535
  ),
@@ -617,6 +626,20 @@ attribute_schema = SchemaNode(
617
626
  optional=True,
618
627
  extra={"update": UpdateSupport.ALLOWED},
619
628
  ),
629
+ SchemaAttribute(
630
+ name="parameters",
631
+ kind="JSON",
632
+ internal_kind=[
633
+ AttributeParameters,
634
+ TextAttributeParameters,
635
+ NumberAttributeParameters,
636
+ NumberPoolParameters,
637
+ ],
638
+ optional=True,
639
+ description="Extra parameters specific to this kind of attribute",
640
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
641
+ default_factory="AttributeParameters",
642
+ ),
620
643
  SchemaAttribute(
621
644
  name="deprecation",
622
645
  kind="Text",
@@ -731,6 +754,21 @@ relationship_schema = SchemaNode(
731
754
  optional=True,
732
755
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
733
756
  ),
757
+ SchemaAttribute(
758
+ name="common_parent",
759
+ kind="Text",
760
+ optional=True,
761
+ description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.",
762
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
763
+ ),
764
+ SchemaAttribute(
765
+ name="common_relatives",
766
+ kind="List",
767
+ internal_kind=str,
768
+ optional=True,
769
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
770
+ extra={"update": UpdateSupport.ALLOWED},
771
+ ),
734
772
  SchemaAttribute(
735
773
  name="order_weight",
736
774
  kind="Number",
@@ -8,6 +8,12 @@ from pydantic import Field
8
8
 
9
9
  from infrahub.core.constants import AllowOverrideType, BranchSupportType, HashableModelState
10
10
  from infrahub.core.models import HashableModel
11
+ from infrahub.core.schema.attribute_parameters import (
12
+ AttributeParameters,
13
+ NumberAttributeParameters,
14
+ NumberPoolParameters,
15
+ TextAttributeParameters,
16
+ )
11
17
  from infrahub.core.schema.computed_attribute import ComputedAttribute # noqa: TC001
12
18
  from infrahub.core.schema.dropdown import DropdownChoice # noqa: TC001
13
19
 
@@ -44,17 +50,17 @@ class GeneratedAttributeSchema(HashableModel):
44
50
  )
45
51
  regex: str | None = Field(
46
52
  default=None,
47
- description="Regex uses to limit the characters allowed in for the attributes.",
53
+ description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
48
54
  json_schema_extra={"update": "validate_constraint"},
49
55
  )
50
56
  max_length: int | None = Field(
51
57
  default=None,
52
- description="Set a maximum number of characters allowed for a given attribute.",
58
+ description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
53
59
  json_schema_extra={"update": "validate_constraint"},
54
60
  )
55
61
  min_length: int | None = Field(
56
62
  default=None,
57
- description="Set a minimum number of characters allowed for a given attribute.",
63
+ description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
58
64
  json_schema_extra={"update": "validate_constraint"},
59
65
  )
60
66
  label: str | None = Field(
@@ -112,6 +118,13 @@ class GeneratedAttributeSchema(HashableModel):
112
118
  description="Type of allowed override for the attribute.",
113
119
  json_schema_extra={"update": "allowed"},
114
120
  )
121
+ parameters: AttributeParameters | TextAttributeParameters | NumberAttributeParameters | NumberPoolParameters = (
122
+ Field(
123
+ default_factory=AttributeParameters,
124
+ description="Extra parameters specific to this kind of attribute",
125
+ json_schema_extra={"update": "validate_constraint"},
126
+ )
127
+ )
115
128
  deprecation: str | None = Field(
116
129
  default=None,
117
130
  description="Mark attribute as deprecated and provide a user-friendly message to display",
@@ -12,7 +12,7 @@ from infrahub.core.constants import (
12
12
  RelationshipDeleteBehavior,
13
13
  RelationshipDirection,
14
14
  RelationshipKind,
15
- ) # noqa: TC001
15
+ )
16
16
  from infrahub.core.models import HashableModel
17
17
 
18
18
 
@@ -73,6 +73,16 @@ class GeneratedRelationshipSchema(HashableModel):
73
73
  description="Defines the maximum objects allowed on the other side of the relationship.",
74
74
  json_schema_extra={"update": "validate_constraint"},
75
75
  )
76
+ common_parent: str | None = Field(
77
+ default=None,
78
+ description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.",
79
+ json_schema_extra={"update": "validate_constraint"},
80
+ )
81
+ common_relatives: list[str] | None = Field(
82
+ default=None,
83
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
84
+ json_schema_extra={"update": "allowed"},
85
+ )
76
86
  order_weight: int | None = Field(
77
87
  default=None,
78
88
  description="Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first.",
@@ -471,7 +471,7 @@ class SchemaManager(NodeManager):
471
471
  if diff_attributes:
472
472
  for item in node.local_attributes:
473
473
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
474
- if item.name in diff_attributes.added or item.name in diff_attributes.changed and item.id is None:
474
+ if item.name in diff_attributes.added or (item.name in diff_attributes.changed and item.id is None):
475
475
  created_item = await self.create_attribute_in_db(
476
476
  schema=attribute_schema, item=item, branch=branch, db=db, parent=obj
477
477
  )
@@ -491,7 +491,9 @@ class SchemaManager(NodeManager):
491
491
  if diff_relationships:
492
492
  for item in node.local_relationships:
493
493
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
494
- if item.name in diff_relationships.added or item.name in diff_relationships.changed and item.id is None:
494
+ if item.name in diff_relationships.added or (
495
+ item.name in diff_relationships.changed and item.id is None
496
+ ):
495
497
  created_rel = await self.create_relationship_in_db(
496
498
  schema=relationship_schema, item=item, branch=branch, db=db, parent=obj
497
499
  )
@@ -764,3 +766,6 @@ class SchemaManager(NodeManager):
764
766
  del self._cache[hash_key]
765
767
 
766
768
  return removed_branches
769
+
770
+ def get_branches(self) -> list[str]:
771
+ return list(self._branches.keys())
@@ -5,6 +5,7 @@ import hashlib
5
5
  from collections import defaultdict
6
6
  from itertools import chain, combinations
7
7
  from typing import Any
8
+ from uuid import uuid4
8
9
 
9
10
  from infrahub_sdk.template import Jinja2Template
10
11
  from infrahub_sdk.template.exceptions import JinjaTemplateError, JinjaTemplateOperationViolationError
@@ -49,6 +50,8 @@ from infrahub.core.schema import (
49
50
  SchemaRoot,
50
51
  TemplateSchema,
51
52
  )
53
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters
54
+ from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
52
55
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
53
56
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
54
57
  from infrahub.exceptions import SchemaNotFoundError, ValidationError
@@ -88,7 +91,7 @@ class SchemaBranch:
88
91
  self.templates = data.get("templates", {})
89
92
 
90
93
  @classmethod
91
- def validate(cls, data: Any) -> Self: # noqa: ARG003
94
+ def validate(cls, data: Any) -> Self:
92
95
  if isinstance(data, cls):
93
96
  return data
94
97
  if isinstance(data, dict):
@@ -450,7 +453,7 @@ class SchemaBranch:
450
453
  if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
451
454
  return node.generate_fields_for_display_label()
452
455
 
453
- fields: dict[str, str | None | dict[str, None]] = {}
456
+ fields: dict[str, str | dict[str, None] | None] = {}
454
457
  if isinstance(node, GenericSchema):
455
458
  for child_node_name in node.used_by:
456
459
  child_node = self.get(name=child_node_name, duplicate=False)
@@ -517,6 +520,7 @@ class SchemaBranch:
517
520
  self.validate_names()
518
521
  self.validate_kinds()
519
522
  self.validate_computed_attributes()
523
+ self.validate_attribute_parameters()
520
524
  self.validate_default_values()
521
525
  self.validate_count_against_cardinality()
522
526
  self.validate_identifiers()
@@ -971,6 +975,28 @@ class SchemaBranch:
971
975
  ):
972
976
  raise ValueError(f"{node.kind}: {rel.name} isn't allowed as a relationship name.")
973
977
 
978
+ def _validate_common_parent(self, node: NodeSchema, rel: RelationshipSchema) -> None:
979
+ if not rel.common_parent:
980
+ return
981
+
982
+ peer_schema = self.get(name=rel.peer, duplicate=False)
983
+ if not node.has_parent_relationship:
984
+ raise ValueError(
985
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but node does not have a parent relationship"
986
+ )
987
+
988
+ try:
989
+ parent_rel = peer_schema.get_relationship(name=rel.common_parent)
990
+ except ValueError as exc:
991
+ raise ValueError(
992
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but '{rel.peer}.{rel.common_parent}' does not exist"
993
+ ) from exc
994
+
995
+ if parent_rel.kind != RelationshipKind.PARENT:
996
+ raise ValueError(
997
+ f"{node.kind}: Relationship {rel.name!r} defines 'common_parent' but '{rel.peer}.{rel.common_parent} is not of kind 'parent'"
998
+ )
999
+
974
1000
  def validate_kinds(self) -> None:
975
1001
  for name in list(self.nodes.keys()):
976
1002
  node = self.get_node(name=name, duplicate=False)
@@ -994,6 +1020,71 @@ class SchemaBranch:
994
1020
  f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
995
1021
  ) from None
996
1022
 
1023
+ self._validate_common_parent(node=node, rel=rel)
1024
+
1025
+ if rel.common_relatives:
1026
+ peer_schema = self.get(name=rel.peer, duplicate=False)
1027
+ for common_relatives_rel_name in rel.common_relatives:
1028
+ if common_relatives_rel_name not in peer_schema.relationship_names:
1029
+ raise ValueError(
1030
+ f"{node.kind}: Relationship {rel.name!r} set 'common_relatives' with invalid relationship from '{rel.peer}'"
1031
+ ) from None
1032
+
1033
+ def validate_attribute_parameters(self) -> None:
1034
+ for name in self.generics.keys():
1035
+ generic_schema = self.get_generic(name=name, duplicate=False)
1036
+ for attribute in generic_schema.attributes:
1037
+ if (
1038
+ attribute.kind == "NumberPool"
1039
+ and isinstance(attribute.parameters, NumberPoolParameters)
1040
+ and not attribute.parameters.number_pool_id
1041
+ ):
1042
+ attribute.parameters.number_pool_id = str(uuid4())
1043
+
1044
+ for name in self.nodes.keys():
1045
+ node_schema = self.get_node(name=name, duplicate=False)
1046
+ for attribute in node_schema.attributes:
1047
+ if attribute.kind == "NumberPool" and isinstance(attribute.parameters, NumberPoolParameters):
1048
+ self._validate_number_pool_parameters(
1049
+ node_schema=node_schema, attribute=attribute, number_pool_parameters=attribute.parameters
1050
+ )
1051
+
1052
+ def _validate_number_pool_parameters(
1053
+ self, node_schema: NodeSchema, attribute: AttributeSchema, number_pool_parameters: NumberPoolParameters
1054
+ ) -> None:
1055
+ if attribute.optional:
1056
+ raise ValidationError(f"{node_schema.kind}.{attribute.name} is a NumberPool it can't be optional")
1057
+
1058
+ if not attribute.read_only:
1059
+ raise ValidationError(
1060
+ f"{node_schema.kind}.{attribute.name} is a NumberPool it has to be a read_only attribute"
1061
+ )
1062
+
1063
+ if attribute.inherited and not number_pool_parameters.number_pool_id:
1064
+ generics_with_attribute = []
1065
+ for generic_name in node_schema.inherit_from:
1066
+ generic_schema = self.get_generic(name=generic_name, duplicate=False)
1067
+ if attribute.name in generic_schema.attribute_names:
1068
+ generic_attribute = generic_schema.get_attribute(name=attribute.name)
1069
+ generics_with_attribute.append(generic_schema)
1070
+ if isinstance(generic_attribute.parameters, NumberPoolParameters):
1071
+ number_pool_parameters.number_pool_id = generic_attribute.parameters.number_pool_id
1072
+
1073
+ if len(generics_with_attribute) > 1:
1074
+ raise ValidationError(
1075
+ f"{node_schema.kind}.{attribute.name} is a NumberPool inherited from more than one generic"
1076
+ )
1077
+ elif not attribute.inherited:
1078
+ for generic_name in node_schema.inherit_from:
1079
+ generic_schema = self.get_generic(name=generic_name, duplicate=False)
1080
+ if attribute.name in generic_schema.attribute_names:
1081
+ raise ValidationError(
1082
+ f"Overriding '{node_schema.kind}.{attribute.name}' NumberPool attribute from generic '{generic_name}' is not supported"
1083
+ )
1084
+
1085
+ if not number_pool_parameters.number_pool_id:
1086
+ number_pool_parameters.number_pool_id = str(uuid4())
1087
+
997
1088
  def validate_computed_attributes(self) -> None:
998
1089
  self.computed_attributes = ComputedAttributes()
999
1090
  for name in self.nodes.keys():
@@ -1136,7 +1227,7 @@ class SchemaBranch:
1136
1227
  self.set(name=name, schema=node)
1137
1228
 
1138
1229
  def process_labels(self) -> None:
1139
- def check_if_need_to_update_label(node) -> bool:
1230
+ def check_if_need_to_update_label(node: MainSchemaTypes) -> bool:
1140
1231
  if not node.label:
1141
1232
  return True
1142
1233
  for item in node.relationships + node.attributes:
@@ -1812,12 +1903,14 @@ class SchemaBranch:
1812
1903
  def generate_profile_from_node(self, node: NodeSchema) -> ProfileSchema:
1813
1904
  core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=False)
1814
1905
  core_name_attr = core_profile_schema.get_attribute(name="profile_name")
1815
- profile_name_attr = AttributeSchema(
1906
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
1907
+ profile_name_attr = name_attr_schema_class(
1816
1908
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1817
1909
  )
1818
1910
  profile_name_attr.branch = node.branch
1819
1911
  core_priority_attr = core_profile_schema.get_attribute(name="profile_priority")
1820
- profile_priority_attr = AttributeSchema(
1912
+ priority_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_priority_attr.kind)
1913
+ profile_priority_attr = priority_attr_schema_class(
1821
1914
  **core_priority_attr.model_dump(exclude=["id", "inherited"]),
1822
1915
  )
1823
1916
  profile_priority_attr.branch = node.branch
@@ -1848,8 +1941,8 @@ class SchemaBranch:
1848
1941
  for node_attr in node.attributes:
1849
1942
  if node_attr.read_only or node_attr.optional is False:
1850
1943
  continue
1851
-
1852
- attr = AttributeSchema(
1944
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
1945
+ attr = attr_schema_class(
1853
1946
  optional=True,
1854
1947
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "default_value", "inherited"]),
1855
1948
  )
@@ -1957,7 +2050,11 @@ class SchemaBranch:
1957
2050
  )
1958
2051
 
1959
2052
  parent_hfid = f"{relationship.name}__template_name__value"
1960
- if relationship.kind == RelationshipKind.PARENT and parent_hfid not in template_schema.human_friendly_id:
2053
+ if (
2054
+ not isinstance(template_schema, GenericSchema)
2055
+ and relationship.kind == RelationshipKind.PARENT
2056
+ and parent_hfid not in template_schema.human_friendly_id
2057
+ ):
1961
2058
  template_schema.human_friendly_id = [parent_hfid] + template_schema.human_friendly_id
1962
2059
  template_schema.uniqueness_constraints[0].append(relationship.name)
1963
2060
 
@@ -1973,7 +2070,8 @@ class SchemaBranch:
1973
2070
  else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
1974
2071
  )
1975
2072
  core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
1976
- template_name_attr = AttributeSchema(
2073
+ name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
2074
+ template_name_attr = name_attr_schema_class(
1977
2075
  **core_name_attr.model_dump(exclude=["id", "inherited"]),
1978
2076
  )
1979
2077
  template_name_attr.branch = node.branch
@@ -1992,7 +2090,6 @@ class SchemaBranch:
1992
2090
  include_in_menu=False,
1993
2091
  display_labels=["template_name__value"],
1994
2092
  human_friendly_id=["template_name__value"],
1995
- uniqueness_constraints=[["template_name__value"]],
1996
2093
  attributes=[template_name_attr],
1997
2094
  )
1998
2095
 
@@ -2011,7 +2108,6 @@ class SchemaBranch:
2011
2108
  human_friendly_id=["template_name__value"],
2012
2109
  uniqueness_constraints=[["template_name__value"]],
2013
2110
  inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.NODE, core_template_schema.kind],
2014
- default_filter="template_name__value",
2015
2111
  attributes=[template_name_attr],
2016
2112
  relationships=[
2017
2113
  RelationshipSchema(
@@ -2033,7 +2129,8 @@ class SchemaBranch:
2033
2129
  if node_attr.unique or node_attr.read_only:
2034
2130
  continue
2035
2131
 
2036
- attr = AttributeSchema(
2132
+ attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
2133
+ attr = attr_schema_class(
2037
2134
  optional=node_attr.optional if is_autogenerated_subtemplate else True,
2038
2135
  **node_attr.model_dump(exclude=["id", "unique", "optional", "read_only"]),
2039
2136
  )
@@ -1,10 +1,14 @@
1
+ from infrahub.core.validators.attribute.min_max import AttributeNumberChecker
2
+
1
3
  from .attribute.choices import AttributeChoicesChecker
2
4
  from .attribute.enum import AttributeEnumChecker
3
5
  from .attribute.kind import AttributeKindChecker
4
6
  from .attribute.length import AttributeLengthChecker
7
+ from .attribute.number_pool import AttributeNumberPoolChecker
5
8
  from .attribute.optional import AttributeOptionalChecker
6
9
  from .attribute.regex import AttributeRegexChecker
7
10
  from .attribute.unique import AttributeUniquenessChecker
11
+ from .enum import ConstraintIdentifier
8
12
  from .interface import ConstraintCheckerInterface
9
13
  from .node.attribute import NodeAttributeAddChecker
10
14
  from .node.generate_profile import NodeGenerateProfileChecker
@@ -13,15 +17,23 @@ from .node.inherit_from import NodeInheritFromChecker
13
17
  from .node.relationship import NodeRelationshipAddChecker
14
18
  from .relationship.count import RelationshipCountChecker
15
19
  from .relationship.optional import RelationshipOptionalChecker
16
- from .relationship.peer import RelationshipPeerChecker
20
+ from .relationship.peer import RelationshipPeerChecker, RelationshipPeerParentChecker
17
21
  from .uniqueness.checker import UniquenessChecker
18
22
 
19
23
  CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
24
+ "attribute.kind.update": AttributeKindChecker,
20
25
  "attribute.regex.update": AttributeRegexChecker,
26
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value: AttributeRegexChecker,
21
27
  "attribute.enum.update": AttributeEnumChecker,
22
- "attribute.kind.update": AttributeKindChecker,
23
28
  "attribute.min_length.update": AttributeLengthChecker,
24
29
  "attribute.max_length.update": AttributeLengthChecker,
30
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
31
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value: AttributeLengthChecker,
32
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_VALUE_UPDATE.value: AttributeNumberChecker,
33
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_VALUE_UPDATE.value: AttributeNumberChecker,
34
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_EXCLUDED_VALUES_UPDATE.value: AttributeNumberChecker,
35
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_START_RANGE_UPDATE: AttributeNumberPoolChecker,
36
+ ConstraintIdentifier.ATTRIBUTE_PARAMETERS_END_RANGE_UPDATE: AttributeNumberPoolChecker,
25
37
  "attribute.unique.update": AttributeUniquenessChecker,
26
38
  "attribute.optional.update": AttributeOptionalChecker,
27
39
  "attribute.choices.update": AttributeChoicesChecker,
@@ -30,6 +42,7 @@ CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
30
42
  "relationship.optional.update": RelationshipOptionalChecker,
31
43
  "relationship.min_count.update": RelationshipCountChecker,
32
44
  "relationship.max_count.update": RelationshipCountChecker,
45
+ "relationship.common_parent.update": RelationshipPeerParentChecker,
33
46
  "node.inherit_from.update": NodeInheritFromChecker,
34
47
  "node.uniqueness_constraints.update": UniquenessChecker,
35
48
  "node.parent.update": NodeHierarchyChecker,
@@ -31,8 +31,7 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
31
31
 
32
32
  query = """
33
33
  MATCH p = (n:%(node_kind)s)
34
- CALL {
35
- WITH n
34
+ CALL (n) {
36
35
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
37
36
  WHERE all(
38
37
  r in relationships(path)
@@ -43,7 +42,6 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
43
42
  LIMIT 1
44
43
  }
45
44
  WITH full_path, node, attribute_value, value_relationship
46
- WITH full_path, node, attribute_value, value_relationship
47
45
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
48
46
  AND attribute_value IS NOT NULL
49
47
  AND attribute_value <> $null_value
@@ -30,8 +30,7 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
30
30
  self.params["null_value"] = NULL_VALUE
31
31
  query = """
32
32
  MATCH (n:%(node_kind)s)
33
- CALL {
34
- WITH n
33
+ CALL (n) {
35
34
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
36
35
  WHERE all(
37
36
  r in relationships(path)
@@ -42,7 +41,6 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
42
41
  LIMIT 1
43
42
  }
44
43
  WITH full_path, node, attribute_value, value_relationship
45
- WITH full_path, node, attribute_value, value_relationship
46
44
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
47
45
  AND attribute_value IS NOT NULL
48
46
  AND attribute_value <> $null_value
@@ -37,8 +37,7 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
37
37
 
38
38
  query = """
39
39
  MATCH p = (n:%(node_kind)s)
40
- CALL {
41
- WITH n
40
+ CALL (n) {
42
41
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
43
42
  WHERE all(
44
43
  r in relationships(path)
@@ -49,7 +48,6 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
49
48
  LIMIT 1
50
49
  }
51
50
  WITH full_path, node, attribute_value, value_relationship
52
- WITH full_path, node, attribute_value, value_relationship
53
51
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
54
52
  AND attribute_value IS NOT NULL
55
53
  AND attribute_value <> $null_value