infrahub-server 1.2.12__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 (205) 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/infrahubkind.py +9 -0
  18. infrahub/core/constraint/node/runner.py +3 -1
  19. infrahub/core/convert_object_type/__init__.py +0 -0
  20. infrahub/core/convert_object_type/conversion.py +124 -0
  21. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  22. infrahub/core/diff/coordinator.py +8 -1
  23. infrahub/core/diff/query/all_conflicts.py +1 -5
  24. infrahub/core/diff/query/artifact.py +10 -20
  25. infrahub/core/diff/query/delete_query.py +8 -4
  26. infrahub/core/diff/query/diff_get.py +3 -6
  27. infrahub/core/diff/query/field_specifiers.py +1 -1
  28. infrahub/core/diff/query/field_summary.py +2 -4
  29. infrahub/core/diff/query/merge.py +72 -125
  30. infrahub/core/diff/query/save.py +28 -43
  31. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  32. infrahub/core/diff/query/time_range_query.py +0 -1
  33. infrahub/core/diff/repository/repository.py +4 -0
  34. infrahub/core/manager.py +14 -11
  35. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  36. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  37. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  38. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  39. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  40. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  43. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  44. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  45. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  46. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +30 -48
  47. infrahub/core/migrations/graph/m030_illegal_edges.py +1 -2
  48. infrahub/core/migrations/query/attribute_add.py +1 -2
  49. infrahub/core/migrations/query/attribute_rename.py +6 -11
  50. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  51. infrahub/core/migrations/query/node_duplicate.py +19 -21
  52. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  53. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  54. infrahub/core/migrations/schema/node_remove.py +19 -20
  55. infrahub/core/models.py +29 -2
  56. infrahub/core/node/__init__.py +131 -28
  57. infrahub/core/node/base.py +1 -1
  58. infrahub/core/node/create.py +211 -0
  59. infrahub/core/node/resource_manager/number_pool.py +31 -5
  60. infrahub/core/node/standard.py +6 -1
  61. infrahub/core/path.py +15 -1
  62. infrahub/core/protocols.py +57 -0
  63. infrahub/core/protocols_base.py +3 -0
  64. infrahub/core/query/__init__.py +2 -2
  65. infrahub/core/query/delete.py +3 -3
  66. infrahub/core/query/diff.py +19 -32
  67. infrahub/core/query/ipam.py +10 -20
  68. infrahub/core/query/node.py +29 -47
  69. infrahub/core/query/relationship.py +55 -34
  70. infrahub/core/query/resource_manager.py +1 -2
  71. infrahub/core/query/standard_node.py +19 -5
  72. infrahub/core/query/subquery.py +2 -4
  73. infrahub/core/relationship/constraints/count.py +10 -9
  74. infrahub/core/relationship/constraints/interface.py +2 -1
  75. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  76. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  77. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  78. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  79. infrahub/core/relationship/model.py +4 -1
  80. infrahub/core/schema/__init__.py +2 -1
  81. infrahub/core/schema/attribute_parameters.py +160 -0
  82. infrahub/core/schema/attribute_schema.py +130 -7
  83. infrahub/core/schema/basenode_schema.py +27 -3
  84. infrahub/core/schema/definitions/core/__init__.py +29 -1
  85. infrahub/core/schema/definitions/core/group.py +45 -0
  86. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  87. infrahub/core/schema/definitions/internal.py +43 -5
  88. infrahub/core/schema/generated/attribute_schema.py +16 -3
  89. infrahub/core/schema/generated/relationship_schema.py +11 -1
  90. infrahub/core/schema/manager.py +7 -2
  91. infrahub/core/schema/schema_branch.py +104 -9
  92. infrahub/core/validators/__init__.py +15 -2
  93. infrahub/core/validators/attribute/choices.py +1 -3
  94. infrahub/core/validators/attribute/enum.py +1 -3
  95. infrahub/core/validators/attribute/kind.py +1 -3
  96. infrahub/core/validators/attribute/length.py +13 -7
  97. infrahub/core/validators/attribute/min_max.py +118 -0
  98. infrahub/core/validators/attribute/number_pool.py +106 -0
  99. infrahub/core/validators/attribute/optional.py +1 -4
  100. infrahub/core/validators/attribute/regex.py +5 -6
  101. infrahub/core/validators/attribute/unique.py +1 -3
  102. infrahub/core/validators/determiner.py +18 -2
  103. infrahub/core/validators/enum.py +12 -0
  104. infrahub/core/validators/node/hierarchy.py +3 -6
  105. infrahub/core/validators/query.py +1 -3
  106. infrahub/core/validators/relationship/count.py +6 -12
  107. infrahub/core/validators/relationship/optional.py +2 -4
  108. infrahub/core/validators/relationship/peer.py +177 -12
  109. infrahub/core/validators/tasks.py +1 -1
  110. infrahub/core/validators/uniqueness/query.py +5 -9
  111. infrahub/database/__init__.py +12 -4
  112. infrahub/database/validation.py +1 -2
  113. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  114. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  115. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  116. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  117. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  118. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  119. infrahub/dependencies/registry.py +4 -0
  120. infrahub/events/group_action.py +1 -0
  121. infrahub/events/models.py +1 -1
  122. infrahub/git/base.py +5 -3
  123. infrahub/git/integrator.py +96 -5
  124. infrahub/git/tasks.py +1 -0
  125. infrahub/graphql/analyzer.py +139 -18
  126. infrahub/graphql/manager.py +4 -0
  127. infrahub/graphql/mutations/action.py +164 -0
  128. infrahub/graphql/mutations/convert_object_type.py +71 -0
  129. infrahub/graphql/mutations/main.py +24 -175
  130. infrahub/graphql/mutations/proposed_change.py +20 -17
  131. infrahub/graphql/mutations/relationship.py +32 -0
  132. infrahub/graphql/mutations/resource_manager.py +63 -7
  133. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  134. infrahub/graphql/queries/resource_manager.py +7 -1
  135. infrahub/graphql/resolvers/many_relationship.py +1 -1
  136. infrahub/graphql/resolvers/resolver.py +2 -2
  137. infrahub/graphql/resolvers/single_relationship.py +1 -1
  138. infrahub/graphql/schema.py +6 -0
  139. infrahub/menu/menu.py +34 -2
  140. infrahub/message_bus/messages/__init__.py +0 -10
  141. infrahub/message_bus/operations/__init__.py +0 -8
  142. infrahub/message_bus/operations/refresh/registry.py +3 -6
  143. infrahub/patch/queries/delete_duplicated_edges.py +10 -15
  144. infrahub/pools/models.py +14 -0
  145. infrahub/pools/number.py +5 -3
  146. infrahub/pools/registration.py +22 -0
  147. infrahub/pools/tasks.py +126 -0
  148. infrahub/prefect_server/models.py +1 -19
  149. infrahub/proposed_change/models.py +68 -3
  150. infrahub/proposed_change/tasks.py +911 -34
  151. infrahub/schema/__init__.py +0 -0
  152. infrahub/schema/tasks.py +27 -0
  153. infrahub/schema/triggers.py +23 -0
  154. infrahub/task_manager/models.py +10 -6
  155. infrahub/trigger/catalogue.py +6 -0
  156. infrahub/trigger/models.py +23 -6
  157. infrahub/trigger/setup.py +26 -2
  158. infrahub/trigger/tasks.py +4 -2
  159. infrahub/types.py +6 -0
  160. infrahub/webhook/tasks.py +4 -8
  161. infrahub/workflows/catalogue.py +103 -1
  162. infrahub_sdk/client.py +43 -10
  163. infrahub_sdk/ctl/generator.py +4 -4
  164. infrahub_sdk/ctl/repository.py +1 -1
  165. infrahub_sdk/node/__init__.py +39 -0
  166. infrahub_sdk/node/attribute.py +122 -0
  167. infrahub_sdk/node/constants.py +21 -0
  168. infrahub_sdk/{node.py → node/node.py} +158 -803
  169. infrahub_sdk/node/parsers.py +15 -0
  170. infrahub_sdk/node/property.py +24 -0
  171. infrahub_sdk/node/related_node.py +266 -0
  172. infrahub_sdk/node/relationship.py +302 -0
  173. infrahub_sdk/protocols.py +112 -0
  174. infrahub_sdk/protocols_base.py +34 -2
  175. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  176. infrahub_sdk/query_groups.py +17 -5
  177. infrahub_sdk/schema/main.py +1 -0
  178. infrahub_sdk/schema/repository.py +16 -0
  179. infrahub_sdk/spec/object.py +1 -1
  180. infrahub_sdk/store.py +1 -1
  181. infrahub_sdk/testing/schemas/car_person.py +1 -0
  182. infrahub_sdk/utils.py +7 -20
  183. infrahub_sdk/yaml.py +6 -5
  184. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +3 -3
  185. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +192 -166
  186. infrahub_testcontainers/container.py +0 -1
  187. infrahub_testcontainers/docker-compose.test.yml +1 -1
  188. infrahub_testcontainers/helpers.py +8 -2
  189. infrahub/message_bus/messages/check_generator_run.py +0 -26
  190. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  191. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  192. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  193. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  194. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  195. infrahub/message_bus/operations/check/__init__.py +0 -3
  196. infrahub/message_bus/operations/check/generator.py +0 -156
  197. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  198. infrahub/message_bus/operations/finalize/validator.py +0 -133
  199. infrahub/message_bus/operations/requests/__init__.py +0 -9
  200. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  201. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  202. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  203. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  204. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +0 -0
  205. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -2,17 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from infrahub.core.constants import PathType
5
+ from infrahub import config
6
+ from infrahub.core.constants import PathType, RelationshipKind
6
7
  from infrahub.core.path import DataPath, GroupedDataPaths
7
8
  from infrahub.core.schema import GenericSchema
8
9
 
9
10
  from ..interface import ConstraintCheckerInterface
10
- from ..shared import (
11
- RelationshipSchemaValidatorQuery,
12
- )
11
+ from ..shared import RelationshipSchemaValidatorQuery
13
12
 
14
13
  if TYPE_CHECKING:
15
14
  from infrahub.core.branch import Branch
15
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
16
16
  from infrahub.database import InfrahubDatabase
17
17
 
18
18
  from ..model import SchemaConstraintValidatorRequest
@@ -36,8 +36,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
36
36
  # ruff: noqa: E501
37
37
  query = """
38
38
  MATCH (n:%(node_kind)s)
39
- CALL {
40
- WITH n
39
+ CALL (n) {
41
40
  MATCH path = (root:Root)<-[rroot:IS_PART_OF]-(n)
42
41
  WHERE all(r in relationships(path) WHERE %(branch_filter)s)
43
42
  RETURN path as full_path, n as active_node
@@ -45,10 +44,8 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
45
44
  LIMIT 1
46
45
  }
47
46
  WITH full_path, active_node
48
- WITH full_path, active_node
49
47
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
50
- CALL {
51
- WITH active_node
48
+ CALL (active_node) {
52
49
  MATCH path = (active_node)-[rrel1:IS_RELATED]-(rel:Relationship { name: $relationship_id })-[rrel2:IS_RELATED]-(peer:Node)
53
50
  WHERE all(
54
51
  r in relationships(path)
@@ -68,8 +65,7 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
68
65
  collect([branch_level_sum, from_times, active_relationship_count, relationship_path, deepest_branch_name]) as enriched_paths,
69
66
  start_node,
70
67
  peer_node
71
- CALL {
72
- WITH enriched_paths, peer_node
68
+ CALL (enriched_paths, peer_node) {
73
69
  UNWIND enriched_paths as path_to_check
74
70
  RETURN path_to_check[3] as current_path, path_to_check[4] as branch_name, peer_node as current_peer
75
71
  ORDER BY
@@ -80,7 +76,6 @@ class RelationshipPeerUpdateValidatorQuery(RelationshipSchemaValidatorQuery):
80
76
  LIMIT 1
81
77
  }
82
78
  WITH start_node, current_peer, branch_name, current_path
83
- WITH start_node, current_peer, branch_name, current_path
84
79
  WHERE all(r in relationships(current_path) WHERE r.status = "active")
85
80
  AND NOT any(label IN LABELS(current_peer) WHERE label IN $allowed_peer_kinds)
86
81
  """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
@@ -130,3 +125,173 @@ class RelationshipPeerChecker(ConstraintCheckerInterface):
130
125
  await query.execute(db=self.db)
131
126
  grouped_data_paths_list.append(await query.get_paths())
132
127
  return grouped_data_paths_list
128
+
129
+
130
+ class RelationshipPeerParentValidatorQuery(RelationshipSchemaValidatorQuery):
131
+ name = "relationship_constraints_peer_parent_validator"
132
+
133
+ def __init__(
134
+ self,
135
+ relationship: RelationshipSchema,
136
+ parent_relationship: RelationshipSchema,
137
+ peer_parent_relationship: RelationshipSchema,
138
+ **kwargs: Any,
139
+ ):
140
+ super().__init__(**kwargs)
141
+
142
+ self.relationship = relationship
143
+ self.parent_relationship = parent_relationship
144
+ self.peer_parent_relationship = peer_parent_relationship
145
+
146
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
147
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string(), is_isolated=False)
148
+ self.params.update(branch_params)
149
+ self.params["peer_relationship_id"] = self.relationship.identifier
150
+ self.params["parent_relationship_id"] = self.parent_relationship.identifier
151
+ self.params["peer_parent_relationship_id"] = self.peer_parent_relationship.identifier
152
+
153
+ parent_arrows = self.parent_relationship.get_query_arrows()
154
+ parent_match = (
155
+ "MATCH (active_node)%(lstart)s[r1:IS_RELATED]%(lend)s"
156
+ "(rel:Relationship { name: $parent_relationship_id })%(rstart)s[r2:IS_RELATED]%(rend)s(parent:Node)"
157
+ ) % {
158
+ "lstart": parent_arrows.left.start,
159
+ "lend": parent_arrows.left.end,
160
+ "rstart": parent_arrows.right.start,
161
+ "rend": parent_arrows.right.end,
162
+ }
163
+
164
+ peer_parent_arrows = self.relationship.get_query_arrows()
165
+ peer_match = (
166
+ "MATCH (active_node)%(lstart)s[r1:IS_RELATED]%(lend)s"
167
+ "(r:Relationship {name: $peer_relationship_id })%(rstart)s[r2:IS_RELATED]%(rend)s(peer:Node)"
168
+ ) % {
169
+ "lstart": peer_parent_arrows.left.start,
170
+ "lend": peer_parent_arrows.left.end,
171
+ "rstart": peer_parent_arrows.right.start,
172
+ "rend": peer_parent_arrows.right.end,
173
+ }
174
+
175
+ peer_parent_arrows = self.peer_parent_relationship.get_query_arrows()
176
+ peer_parent_match = (
177
+ "MATCH (peer:Node)%(lstart)s[r1:IS_RELATED]%(lend)s"
178
+ "(r:Relationship {name: $peer_parent_relationship_id})%(rstart)s[r2:IS_RELATED]%(rend)s(peer_parent:Node)"
179
+ ) % {
180
+ "lstart": peer_parent_arrows.left.start,
181
+ "lend": peer_parent_arrows.left.end,
182
+ "rstart": peer_parent_arrows.right.start,
183
+ "rend": peer_parent_arrows.right.end,
184
+ }
185
+
186
+ query = """
187
+ MATCH (n:%(node_kind)s)
188
+ CALL (n) {
189
+ MATCH path = (root:Root)<-[r:IS_PART_OF]-(n)
190
+ WHERE %(branch_filter)s
191
+ RETURN n as active_node, r.status = "active" AS is_active
192
+ ORDER BY r.branch_level DESC, r.from DESC
193
+ LIMIT 1
194
+ }
195
+ WITH active_node, is_active
196
+ WHERE is_active = TRUE
197
+ %(parent_match)s
198
+ WHERE all(r in [r1, r2] WHERE %(branch_filter)s AND r.status = "active")
199
+ CALL (active_node) {
200
+ %(peer_match)s
201
+ WITH DISTINCT active_node, peer
202
+ %(peer_match)s
203
+ WHERE all(r in [r1, r2] WHERE %(branch_filter)s)
204
+ WITH peer, r1.status = "active" AND r2.status = "active" AS is_active
205
+ ORDER BY peer.uuid, r1.branch_level DESC, r2.branch_level DESC, r1.from DESC, r2.from DESC, is_active DESC
206
+ WITH peer, head(collect(is_active)) AS is_active
207
+ WHERE is_active = TRUE
208
+ RETURN peer
209
+ }
210
+ CALL (peer) {
211
+ %(peer_parent_match)s
212
+ WHERE all(r IN [r1, r2] WHERE %(branch_filter)s)
213
+ WITH peer_parent, r1, r2, r1.status = "active" AND r2.status = "active" AS is_active
214
+ WITH peer_parent, r1.branch AS branch_name, is_active
215
+ ORDER BY r1.branch_level DESC, r2.branch_level DESC, r1.from DESC, r2.from DESC, is_active DESC
216
+ LIMIT 1
217
+ WITH peer_parent, branch_name
218
+ WHERE is_active = TRUE
219
+ RETURN peer_parent, branch_name
220
+ }
221
+ WITH DISTINCT active_node, parent, peer, peer_parent, branch_name
222
+ WHERE parent.uuid <> peer_parent.uuid
223
+ """ % {
224
+ "branch_filter": branch_filter,
225
+ "node_kind": self.node_schema.kind,
226
+ "parent_match": parent_match,
227
+ "peer_match": peer_match,
228
+ "peer_parent_match": peer_parent_match,
229
+ }
230
+
231
+ self.add_to_query(query)
232
+ self.return_labels = ["active_node.uuid", "parent.uuid", "peer.uuid", "peer_parent.uuid", "branch_name"]
233
+
234
+ async def get_paths(self) -> GroupedDataPaths:
235
+ grouped_data_paths = GroupedDataPaths()
236
+
237
+ for result in self.results:
238
+ grouped_data_paths.add_data_path(
239
+ DataPath(
240
+ branch=str(result.get("branch_name")),
241
+ path_type=PathType.RELATIONSHIP_ONE,
242
+ node_id=str(result.get("peer.uuid")),
243
+ field_name=self.peer_parent_relationship.name,
244
+ peer_id=str(result.get("peer_parent.uuid")),
245
+ kind=self.relationship.peer,
246
+ )
247
+ )
248
+
249
+ return grouped_data_paths
250
+
251
+
252
+ class RelationshipPeerParentChecker(ConstraintCheckerInterface):
253
+ query_classes = [RelationshipPeerParentValidatorQuery]
254
+
255
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
256
+ self.db = db
257
+ self.branch = branch
258
+
259
+ @property
260
+ def name(self) -> str:
261
+ return "relationship.common_parent.update"
262
+
263
+ def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
264
+ return request.constraint_name == self.name and config.SETTINGS.main.schema_strict_mode
265
+
266
+ async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
267
+ grouped_data_paths_list: list[GroupedDataPaths] = []
268
+
269
+ if not request.schema_path.field_name:
270
+ return grouped_data_paths_list
271
+
272
+ relationship = request.node_schema.get_relationship(name=request.schema_path.field_name)
273
+ if not relationship.common_parent:
274
+ # Should not happen if schema validation was done properly
275
+ return grouped_data_paths_list
276
+
277
+ parent_relationship = next(
278
+ iter(request.node_schema.get_relationships_of_kind(relationship_kinds=[RelationshipKind.PARENT]))
279
+ )
280
+ peer_parent_relationship = request.schema_branch.get(name=relationship.peer, duplicate=False).get_relationship(
281
+ name=relationship.common_parent
282
+ )
283
+
284
+ for query_class in self.query_classes:
285
+ query = await query_class.init(
286
+ db=self.db,
287
+ branch=self.branch,
288
+ node_schema=request.node_schema,
289
+ schema_path=request.schema_path,
290
+ relationship=relationship,
291
+ parent_relationship=parent_relationship,
292
+ peer_parent_relationship=peer_parent_relationship,
293
+ )
294
+ await query.execute(db=self.db)
295
+ grouped_data_paths_list.append(await query.get_paths())
296
+
297
+ return grouped_data_paths_list
@@ -9,7 +9,7 @@ from prefect.logging import get_run_logger
9
9
 
10
10
  from infrahub.core.branch import Branch # noqa: TC001
11
11
  from infrahub.core.path import SchemaPath # noqa: TC001
12
- from infrahub.core.schema import GenericSchema, NodeSchema # noqa: TC001
12
+ from infrahub.core.schema import GenericSchema, NodeSchema
13
13
  from infrahub.core.validators.aggregated_checker import AggregatedConstraintChecker
14
14
  from infrahub.core.validators.model import (
15
15
  SchemaConstraintValidatorRequest,
@@ -158,12 +158,11 @@ class NodeUniqueAttributeConstraintQuery(Query):
158
158
  # ruff: noqa: E501
159
159
  query = """
160
160
  // get attributes for node and its relationships
161
- CALL {
161
+ CALL () {
162
162
  %(select_subqueries_str)s
163
163
  }
164
- CALL {
164
+ CALL (potential_path) {
165
165
  WITH potential_path
166
- WITH potential_path // workaround for neo4j not allowing WHERE in a WITH of a subquery
167
166
  // only the branches and times we care about
168
167
  WHERE all(
169
168
  r IN relationships(potential_path) WHERE (
@@ -183,8 +182,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
183
182
  start_node,
184
183
  rel_identifier,
185
184
  potential_attr
186
- CALL {
187
- WITH enriched_paths
185
+ CALL (enriched_paths) {
188
186
  UNWIND enriched_paths as path_to_check
189
187
  RETURN path_to_check[0] as current_path, path_to_check[4] as latest_value
190
188
  ORDER BY
@@ -194,16 +192,14 @@ class NodeUniqueAttributeConstraintQuery(Query):
194
192
  path_to_check[3] DESC
195
193
  LIMIT 1
196
194
  }
197
- CALL {
195
+ CALL (current_path) {
198
196
  // only active paths
199
197
  WITH current_path
200
- WITH current_path // workaround for neo4j not allowing WHERE in a WITH of a subquery
201
198
  WHERE all(r IN relationships(current_path) WHERE r.status = "active")
202
199
  RETURN current_path as active_path
203
200
  }
204
- CALL {
201
+ CALL (active_path) {
205
202
  // get deepest branch name
206
- WITH active_path
207
203
  UNWIND %(branch_name_and_level)s as branch_name_and_level
208
204
  RETURN branch_name_and_level[0] as branch_name
209
205
  ORDER BY branch_name_and_level[1] DESC
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
39
39
  from types import TracebackType
40
40
 
41
41
  from infrahub.core.branch import Branch
42
- from infrahub.core.schema import MainSchemaTypes, NodeSchema
42
+ from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema
43
43
  from infrahub.core.schema.schema_branch import SchemaBranch
44
44
 
45
45
  validated_database = {}
@@ -91,6 +91,15 @@ class DatabaseSchemaManager:
91
91
 
92
92
  raise ValueError("The selected node is not of type NodeSchema")
93
93
 
94
+ def get_generic_schema(
95
+ self, name: str, branch: Branch | str | None = None, duplicate: bool = True
96
+ ) -> GenericSchema:
97
+ schema = self.get(name=name, branch=branch, duplicate=duplicate)
98
+ if schema.is_generic_schema:
99
+ return schema
100
+
101
+ raise ValueError("The selected node is not of type GenericSchema")
102
+
94
103
  def set(self, name: str, schema: MainSchemaTypes, branch: str | None = None) -> int:
95
104
  branch_name = get_branch_name(branch=branch)
96
105
  if branch_name not in self._db._schemas:
@@ -284,7 +293,7 @@ class InfrahubDatabase:
284
293
  exc_type: type[BaseException] | None,
285
294
  exc_value: BaseException | None,
286
295
  traceback: TracebackType | None,
287
- ):
296
+ ) -> None:
288
297
  if self._mode == InfrahubDatabaseMode.SESSION:
289
298
  return await self._session.close()
290
299
 
@@ -330,7 +339,7 @@ class InfrahubDatabase:
330
339
  CONNECTION_POOL_USAGE.labels(self._driver._pool.address).set(float(connpool_usage))
331
340
 
332
341
  if config.SETTINGS.database.max_concurrent_queries:
333
- while connpool_usage > config.SETTINGS.database.max_concurrent_queries: # noqa: ASYNC110
342
+ while connpool_usage > config.SETTINGS.database.max_concurrent_queries:
334
343
  await asyncio.sleep(config.SETTINGS.database.max_concurrent_queries_delay)
335
344
  connpool_usage = self._driver._pool.in_use_connection_count(self._driver._pool.address)
336
345
 
@@ -489,7 +498,6 @@ async def get_db(retry: int = 0) -> AsyncDriver:
489
498
  trusted_certificates=trusted_certificates,
490
499
  notifications_disabled_categories=[
491
500
  NotificationDisabledCategory.UNRECOGNIZED,
492
- NotificationDisabledCategory.DEPRECATION, # TODO: Remove me with 1.3
493
501
  ],
494
502
  notifications_min_severity=NotificationMinimumSeverity.WARNING,
495
503
  )
@@ -65,8 +65,7 @@ WITH DISTINCT n, delete_branch, delete_time, added_e, peer AS added_peer
65
65
  // ------------
66
66
  // get the branched_from for the branch on which the node was deleted
67
67
  // ------------
68
- CALL {
69
- WITH added_e
68
+ CALL (added_e) {
70
69
  MATCH (b:Branch {name: added_e.branch})
71
70
  RETURN b.branched_from AS added_e_branched_from
72
71
  }
@@ -4,6 +4,8 @@ from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilder
4
4
  from ..node.grouped_uniqueness import NodeGroupedUniquenessConstraintDependency
5
5
  from ..relationship_manager.count import RelationshipCountConstraintDependency
6
6
  from ..relationship_manager.peer_kind import RelationshipPeerKindConstraintDependency
7
+ from ..relationship_manager.peer_parent import RelationshipPeerParentConstraintDependency
8
+ from ..relationship_manager.peer_relatives import RelationshipPeerRelativesConstraintDependency
7
9
  from ..relationship_manager.profiles_kind import RelationshipProfilesKindConstraintDependency
8
10
 
9
11
 
@@ -18,5 +20,7 @@ class NodeConstraintRunnerDependency(DependencyBuilder[NodeConstraintRunner]):
18
20
  RelationshipPeerKindConstraintDependency.build(context=context),
19
21
  RelationshipCountConstraintDependency.build(context=context),
20
22
  RelationshipProfilesKindConstraintDependency.build(context=context),
23
+ RelationshipPeerParentConstraintDependency.build(context=context),
24
+ RelationshipPeerRelativesConstraintDependency.build(context=context),
21
25
  ],
22
26
  )
@@ -0,0 +1,8 @@
1
+ from infrahub.core.relationship.constraints.peer_parent import RelationshipPeerParentConstraint
2
+ from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
3
+
4
+
5
+ class RelationshipPeerParentConstraintDependency(DependencyBuilder[RelationshipPeerParentConstraint]):
6
+ @classmethod
7
+ def build(cls, context: DependencyBuilderContext) -> RelationshipPeerParentConstraint:
8
+ return RelationshipPeerParentConstraint(db=context.db, branch=context.branch)
@@ -0,0 +1,8 @@
1
+ from infrahub.core.relationship.constraints.peer_relatives import RelationshipPeerRelativesConstraint
2
+ from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
3
+
4
+
5
+ class RelationshipPeerRelativesConstraintDependency(DependencyBuilder[RelationshipPeerRelativesConstraint]):
6
+ @classmethod
7
+ def build(cls, context: DependencyBuilderContext) -> RelationshipPeerRelativesConstraint:
8
+ return RelationshipPeerRelativesConstraint(db=context.db, branch=context.branch)
@@ -14,6 +14,7 @@ from .node_attribute import SchemaNodeAttributeAddConstraintDependency
14
14
  from .node_relationship import SchemaNodeRelationshipAddConstraintDependency
15
15
  from .relationship_count import SchemaRelationshipCountConstraintDependency
16
16
  from .relationship_optional import SchemaRelationshipOptionalConstraintDependency
17
+ from .relationship_peer import SchemaRelationshipPeerParentConstraintDependency
17
18
  from .uniqueness import SchemaUniquenessConstraintDependency
18
19
 
19
20
 
@@ -36,6 +37,7 @@ class AggregatedSchemaConstraintsDependency(DependencyBuilder[AggregatedConstrai
36
37
  SchemaAttributeKindConstraintDependency.build(context=context),
37
38
  SchemaNodeAttributeAddConstraintDependency.build(context=context),
38
39
  SchemaNodeRelationshipAddConstraintDependency.build(context=context),
40
+ SchemaRelationshipPeerParentConstraintDependency.build(context=context),
39
41
  ],
40
42
  db=context.db,
41
43
  branch=context.branch,
@@ -0,0 +1,8 @@
1
+ from infrahub.core.validators.relationship.peer import RelationshipPeerParentChecker
2
+ from infrahub.dependencies.interface import DependencyBuilder, DependencyBuilderContext
3
+
4
+
5
+ class SchemaRelationshipPeerParentConstraintDependency(DependencyBuilder[RelationshipPeerParentChecker]):
6
+ @classmethod
7
+ def build(cls, context: DependencyBuilderContext) -> RelationshipPeerParentChecker:
8
+ return RelationshipPeerParentChecker(db=context.db, branch=context.branch)
@@ -6,5 +6,5 @@ from .parent_node_adder import DiffParentNodeAdderDependency
6
6
 
7
7
  class DiffDeserializerDependency(DependencyBuilder[EnrichedDiffDeserializer]):
8
8
  @classmethod
9
- def build(cls, context: DependencyBuilderContext) -> EnrichedDiffDeserializer: # noqa: ARG003
9
+ def build(cls, context: DependencyBuilderContext) -> EnrichedDiffDeserializer:
10
10
  return EnrichedDiffDeserializer(parent_adder=DiffParentNodeAdderDependency.build(context=context))
@@ -3,6 +3,8 @@ from .builder.constraint.node.grouped_uniqueness import NodeGroupedUniquenessCon
3
3
  from .builder.constraint.node.uniqueness import NodeAttributeUniquenessConstraintDependency
4
4
  from .builder.constraint.relationship_manager.count import RelationshipCountConstraintDependency
5
5
  from .builder.constraint.relationship_manager.peer_kind import RelationshipPeerKindConstraintDependency
6
+ from .builder.constraint.relationship_manager.peer_parent import RelationshipPeerParentConstraintDependency
7
+ from .builder.constraint.relationship_manager.peer_relatives import RelationshipPeerRelativesConstraintDependency
6
8
  from .builder.constraint.relationship_manager.profiles_kind import RelationshipProfilesKindConstraintDependency
7
9
  from .builder.constraint.schema.aggregated import AggregatedSchemaConstraintsDependency
8
10
  from .builder.constraint.schema.attribute_regex import SchemaAttributeRegexConstraintDependency
@@ -37,6 +39,8 @@ def build_component_registry() -> ComponentDependencyRegistry:
37
39
  component_registry.track_dependency(RelationshipCountConstraintDependency)
38
40
  component_registry.track_dependency(RelationshipProfilesKindConstraintDependency)
39
41
  component_registry.track_dependency(RelationshipPeerKindConstraintDependency)
42
+ component_registry.track_dependency(RelationshipPeerParentConstraintDependency)
43
+ component_registry.track_dependency(RelationshipPeerRelativesConstraintDependency)
40
44
  component_registry.track_dependency(NodeConstraintRunnerDependency)
41
45
  component_registry.track_dependency(NodeDeleteValidatorDependency)
42
46
  component_registry.track_dependency(IpamKindsGetterDependency)
@@ -89,6 +89,7 @@ class GroupMutatedEvent(InfrahubEvent):
89
89
  "infrahub.node.id": self.node_id,
90
90
  "infrahub.node.action": self.action.value,
91
91
  "infrahub.node.root_id": self.node_id,
92
+ "infrahub.branch.name": self.meta.context.branch.name,
92
93
  }
93
94
 
94
95
 
infrahub/events/models.py CHANGED
@@ -8,7 +8,7 @@ from pydantic import BaseModel, Field, PrivateAttr, model_validator
8
8
 
9
9
  from infrahub import __version__
10
10
  from infrahub.auth import AccountSession, AuthType
11
- from infrahub.context import InfrahubContext # noqa: TC001
11
+ from infrahub.context import InfrahubContext
12
12
  from infrahub.core.branch import Branch # noqa: TC001
13
13
  from infrahub.message_bus import InfrahubMessage, Meta
14
14
  from infrahub.worker import WORKER_IDENTITY
infrahub/git/base.py CHANGED
@@ -162,6 +162,11 @@ class InfrahubRepositoryBase(BaseModel, ABC):
162
162
  infrahub_branch_name: str | None = Field(None, description="Infrahub branch on which to sync the remote repository")
163
163
  model_config = ConfigDict(arbitrary_types_allowed=True, ignored_types=(Flow, Task))
164
164
 
165
+ def get_client(self) -> InfrahubClient:
166
+ if self.client is None:
167
+ raise ValueError("Client is not set")
168
+ return self.client
169
+
165
170
  @property
166
171
  def sdk(self) -> InfrahubClient:
167
172
  if self.client:
@@ -445,9 +450,6 @@ class InfrahubRepositoryBase(BaseModel, ABC):
445
450
 
446
451
  return [Worktree.init(response) for response in responses]
447
452
 
448
- def get_client(self) -> InfrahubClient:
449
- return self.sdk
450
-
451
453
  def get_location(self) -> str:
452
454
  if self.location:
453
455
  return self.location
@@ -29,10 +29,12 @@ from infrahub_sdk.schema.repository import (
29
29
  InfrahubPythonTransformConfig,
30
30
  InfrahubRepositoryConfig,
31
31
  )
32
+ from infrahub_sdk.spec.menu import MenuFile
33
+ from infrahub_sdk.spec.object import ObjectFile
32
34
  from infrahub_sdk.template import Jinja2Template
33
35
  from infrahub_sdk.template.exceptions import JinjaTemplateError
34
36
  from infrahub_sdk.utils import compare_lists
35
- from infrahub_sdk.yaml import SchemaFile
37
+ from infrahub_sdk.yaml import InfrahubFile, SchemaFile
36
38
  from prefect import flow, task
37
39
  from prefect.cache_policies import NONE
38
40
  from prefect.logging import get_run_logger
@@ -40,7 +42,7 @@ from pydantic import BaseModel, Field
40
42
  from pydantic import ValidationError as PydanticValidationError
41
43
  from typing_extensions import Self
42
44
 
43
- from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositorySyncStatus
45
+ from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositoryObjects, RepositorySyncStatus
44
46
  from infrahub.core.registry import registry
45
47
  from infrahub.events.artifact_action import ArtifactCreatedEvent, ArtifactUpdatedEvent
46
48
  from infrahub.events.models import EventMeta
@@ -54,6 +56,7 @@ if TYPE_CHECKING:
54
56
  import types
55
57
 
56
58
  from infrahub_sdk.checks import InfrahubCheck
59
+ from infrahub_sdk.ctl.utils import YamlFileVar
57
60
  from infrahub_sdk.schema.repository import InfrahubRepositoryArtifactDefinitionConfig
58
61
  from infrahub_sdk.transforms import InfrahubTransform
59
62
 
@@ -159,7 +162,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
159
162
  async def ensure_location_is_defined(self) -> None:
160
163
  if self.location:
161
164
  return
162
- client = self.get_client()
165
+ client = self.sdk
163
166
  repo = await client.get(
164
167
  kind=CoreGenericRepository, name__value=self.name, exclude=["tags", "credential"], raise_when_missing=True
165
168
  )
@@ -179,16 +182,20 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
179
182
 
180
183
  config_file = await self.get_repository_config(branch_name=infrahub_branch_name, commit=commit) # type: ignore[misc]
181
184
  sync_status = RepositorySyncStatus.IN_SYNC if config_file else RepositorySyncStatus.ERROR_IMPORT
185
+
182
186
  error: Exception | None = None
183
187
 
184
188
  try:
185
189
  if config_file:
186
190
  await self.import_schema_files(branch_name=infrahub_branch_name, commit=commit, config_file=config_file) # type: ignore[misc]
187
-
188
191
  await self.import_all_graphql_query(
189
192
  branch_name=infrahub_branch_name, commit=commit, config_file=config_file
190
193
  ) # type: ignore[misc]
191
-
194
+ await self.import_objects(
195
+ branch_name=infrahub_branch_name,
196
+ commit=commit,
197
+ config_file=config_file,
198
+ ) # type: ignore[misc]
192
199
  await self.import_all_python_files( # type: ignore[call-overload]
193
200
  branch_name=infrahub_branch_name, commit=commit, config_file=config_file
194
201
  ) # type: ignore[misc]
@@ -815,6 +822,80 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
815
822
  log.info(f"TransformPython {transform_name!r} not found locally, deleting")
816
823
  await transform_definition_in_graph[transform_name].delete()
817
824
 
825
+ async def _load_yamlfile_from_disk(self, paths: list[Path], file_type: type[YamlFileVar]) -> list[YamlFileVar]:
826
+ data_files = file_type.load_from_disk(paths=paths)
827
+
828
+ for data_file in data_files:
829
+ if not data_file.valid or not data_file.content:
830
+ raise ValueError(f"{data_file.error_message} ({data_file.location})")
831
+
832
+ return data_files
833
+
834
+ async def _load_objects(
835
+ self,
836
+ paths: list[Path],
837
+ branch: str,
838
+ file_type: type[InfrahubFile],
839
+ ) -> None:
840
+ """Load one or multiple objects files into Infrahub."""
841
+
842
+ log = get_run_logger()
843
+ files = await self._load_yamlfile_from_disk(paths=paths, file_type=file_type)
844
+
845
+ for file in files:
846
+ await file.validate_format(client=self.sdk, branch=branch)
847
+ schema = await self.sdk.schema.get(kind=file.spec.kind, branch=branch)
848
+ if not schema.human_friendly_id and not schema.default_filter:
849
+ raise ValueError(
850
+ f"Schemas of objects or menus defined within {file.location} "
851
+ "should have a `human_friendly_id` defined to avoid creating duplicated objects."
852
+ )
853
+
854
+ for file in files:
855
+ log.info(f"Loading objects defined in {file.location}")
856
+ await file.process(client=self.sdk, branch=branch)
857
+
858
+ async def _import_file_paths(
859
+ self, branch_name: str, commit: str, files_pathes: list[Path], object_type: RepositoryObjects
860
+ ) -> None:
861
+ branch_wt = self.get_worktree(identifier=commit or branch_name)
862
+ file_pathes = [branch_wt.directory / file_path for file_path in files_pathes]
863
+
864
+ # We currently assume there can't be concurrent imports, but if so, we might need to clone the client before tracking here.
865
+ async with self.sdk.start_tracking(
866
+ identifier=f"group-repo-{object_type.value}-{self.id}",
867
+ delete_unused_nodes=True,
868
+ branch=branch_name,
869
+ group_type="CoreRepositoryGroup",
870
+ group_params={"content": object_type.value, "repository": str(self.id)},
871
+ ):
872
+ file_type = repo_object_type_to_file_type(object_type)
873
+ await self._load_objects(
874
+ paths=file_pathes,
875
+ branch=branch_name,
876
+ file_type=file_type,
877
+ )
878
+
879
+ @task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE) # type: ignore[arg-type]
880
+ async def import_objects(
881
+ self,
882
+ branch_name: str,
883
+ commit: str,
884
+ config_file: InfrahubRepositoryConfig,
885
+ ) -> None:
886
+ await self._import_file_paths(
887
+ branch_name=branch_name,
888
+ commit=commit,
889
+ files_pathes=config_file.objects,
890
+ object_type=RepositoryObjects.OBJECT,
891
+ )
892
+ await self._import_file_paths(
893
+ branch_name=branch_name,
894
+ commit=commit,
895
+ files_pathes=config_file.menus,
896
+ object_type=RepositoryObjects.MENU,
897
+ )
898
+
818
899
  @task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE) # type: ignore[arg-type]
819
900
  async def get_check_definition(
820
901
  self,
@@ -1342,3 +1423,13 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1342
1423
 
1343
1424
  await self.service.event.send(event=event)
1344
1425
  return ArtifactGenerateResult(changed=True, checksum=checksum, storage_id=storage_id, artifact_id=artifact.id)
1426
+
1427
+
1428
+ def repo_object_type_to_file_type(repo_object: RepositoryObjects) -> type[InfrahubFile]:
1429
+ match repo_object:
1430
+ case RepositoryObjects.OBJECT:
1431
+ return ObjectFile
1432
+ case RepositoryObjects.MENU:
1433
+ return MenuFile
1434
+ case _:
1435
+ raise ValueError(f"Unknown repository object type: {repo_object}")
infrahub/git/tasks.py CHANGED
@@ -696,6 +696,7 @@ async def trigger_internal_checks(
696
696
  if (
697
697
  existing_validator.typename == InfrahubKind.REPOSITORYVALIDATOR
698
698
  and existing_validator.repository.id == model.repository
699
+ and existing_validator.label.value == validator_name
699
700
  ):
700
701
  previous_validator = existing_validator
701
702