infrahub-server 1.2.7__py3-none-any.whl → 1.2.9__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 (85) hide show
  1. infrahub/api/transformation.py +1 -0
  2. infrahub/artifacts/models.py +4 -0
  3. infrahub/cli/db.py +15 -6
  4. infrahub/computed_attribute/tasks.py +34 -12
  5. infrahub/config.py +2 -1
  6. infrahub/constants/__init__.py +0 -0
  7. infrahub/core/branch/tasks.py +0 -2
  8. infrahub/core/constants/__init__.py +1 -0
  9. infrahub/core/diff/calculator.py +4 -3
  10. infrahub/core/diff/combiner.py +1 -2
  11. infrahub/core/diff/coordinator.py +44 -28
  12. infrahub/core/diff/data_check_synchronizer.py +3 -2
  13. infrahub/core/diff/enricher/hierarchy.py +38 -27
  14. infrahub/core/diff/ipam_diff_parser.py +5 -4
  15. infrahub/core/diff/merger/merger.py +20 -18
  16. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  17. infrahub/core/diff/model/path.py +55 -58
  18. infrahub/core/diff/parent_node_adder.py +14 -16
  19. infrahub/core/diff/query/drop_nodes.py +42 -0
  20. infrahub/core/diff/query/field_specifiers.py +8 -7
  21. infrahub/core/diff/query/filters.py +15 -1
  22. infrahub/core/diff/query/save.py +3 -0
  23. infrahub/core/diff/query_parser.py +49 -52
  24. infrahub/core/diff/repository/deserializer.py +36 -23
  25. infrahub/core/diff/repository/repository.py +31 -12
  26. infrahub/core/graph/__init__.py +1 -1
  27. infrahub/core/graph/index.py +3 -1
  28. infrahub/core/initialization.py +23 -7
  29. infrahub/core/manager.py +16 -5
  30. infrahub/core/migrations/graph/__init__.py +2 -0
  31. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +9 -8
  32. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  33. infrahub/core/protocols.py +1 -0
  34. infrahub/core/query/branch.py +27 -17
  35. infrahub/core/query/diff.py +65 -38
  36. infrahub/core/query/node.py +111 -33
  37. infrahub/core/query/relationship.py +17 -3
  38. infrahub/core/query/subquery.py +2 -2
  39. infrahub/core/schema/definitions/core/builtin.py +2 -4
  40. infrahub/core/schema/definitions/core/transform.py +1 -0
  41. infrahub/core/schema/schema_branch.py +3 -0
  42. infrahub/core/validators/aggregated_checker.py +2 -2
  43. infrahub/core/validators/uniqueness/query.py +30 -9
  44. infrahub/database/__init__.py +1 -16
  45. infrahub/database/index.py +1 -1
  46. infrahub/database/memgraph.py +1 -12
  47. infrahub/database/neo4j.py +1 -13
  48. infrahub/git/integrator.py +27 -3
  49. infrahub/git/models.py +4 -0
  50. infrahub/git/tasks.py +3 -0
  51. infrahub/git_credential/helper.py +2 -2
  52. infrahub/graphql/mutations/computed_attribute.py +5 -1
  53. infrahub/graphql/queries/diff/tree.py +2 -1
  54. infrahub/message_bus/operations/requests/proposed_change.py +6 -0
  55. infrahub/message_bus/types.py +3 -0
  56. infrahub/patch/queries/consolidate_duplicated_nodes.py +109 -0
  57. infrahub/patch/queries/delete_duplicated_edges.py +138 -0
  58. infrahub/proposed_change/tasks.py +1 -0
  59. infrahub/server.py +1 -3
  60. infrahub/transformations/models.py +3 -0
  61. infrahub/transformations/tasks.py +1 -0
  62. infrahub/trigger/models.py +11 -1
  63. infrahub/trigger/setup.py +38 -13
  64. infrahub/trigger/tasks.py +1 -4
  65. infrahub/webhook/models.py +3 -0
  66. infrahub/workflows/initialization.py +1 -3
  67. infrahub_sdk/client.py +4 -4
  68. infrahub_sdk/config.py +17 -0
  69. infrahub_sdk/ctl/cli_commands.py +7 -1
  70. infrahub_sdk/ctl/generator.py +2 -2
  71. infrahub_sdk/generator.py +12 -66
  72. infrahub_sdk/operation.py +80 -0
  73. infrahub_sdk/protocols.py +12 -0
  74. infrahub_sdk/recorder.py +3 -0
  75. infrahub_sdk/schema/repository.py +4 -0
  76. infrahub_sdk/transforms.py +15 -27
  77. {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/METADATA +2 -2
  78. {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/RECORD +84 -78
  79. infrahub_testcontainers/container.py +1 -0
  80. infrahub_testcontainers/docker-compose.test.yml +5 -1
  81. infrahub/database/manager.py +0 -15
  82. /infrahub/{database/constants.py → constants/database.py} +0 -0
  83. {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/LICENSE.txt +0 -0
  84. {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/WHEEL +0 -0
  85. {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,7 @@ from infrahub.log import get_logger
12
12
  from ..model.path import (
13
13
  CalculatedDiffs,
14
14
  EnrichedDiffRoot,
15
+ NodeIdentifier,
15
16
  )
16
17
  from ..parent_node_adder import DiffParentNodeAdder, ParentNodeAddRequest
17
18
  from .interface import DiffEnricherInterface
@@ -37,8 +38,8 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
37
38
 
38
39
  log.info("Beginning hierarchical diff enrichment...")
39
40
  self.parent_adder.initialize(enriched_diff_root=enriched_diff_root)
40
- node_rel_parent_map: dict[str, list[str]] = defaultdict(list)
41
- node_hierarchy_map: dict[str, list[str]] = defaultdict(list)
41
+ node_rel_parent_map: dict[str, list[NodeIdentifier]] = defaultdict(list)
42
+ node_hierarchy_map: dict[str, list[NodeIdentifier]] = defaultdict(list)
42
43
 
43
44
  for node in enriched_diff_root.nodes:
44
45
  schema_node = self.db.schema.get(
@@ -49,14 +50,14 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
49
50
  continue
50
51
 
51
52
  if schema_node.has_parent_relationship:
52
- node_rel_parent_map[node.kind].append(node.uuid)
53
+ node_rel_parent_map[node.kind].append(node.identifier)
53
54
  continue
54
55
 
55
56
  try:
56
57
  hierarchy_schema = schema_node.get_hierarchy_schema(
57
58
  db=self.db, branch=enriched_diff_root.diff_branch_name
58
59
  )
59
- node_hierarchy_map[hierarchy_schema.kind].append(node.uuid)
60
+ node_hierarchy_map[hierarchy_schema.kind].append(node.identifier)
60
61
  except ValueError:
61
62
  pass
62
63
 
@@ -67,21 +68,21 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
67
68
  async def _enrich_hierarchical_nodes(
68
69
  self,
69
70
  enriched_diff_root: EnrichedDiffRoot,
70
- node_map: dict[str, list[str]],
71
+ node_map: dict[str, list[NodeIdentifier]],
71
72
  ) -> None:
72
73
  diff_branch = registry.get_branch_from_registry(branch=enriched_diff_root.diff_branch_name)
73
74
 
74
75
  # Retrieve the ID of all ancestors
75
- for kind, node_ids in node_map.items():
76
- log.info(f"Beginning hierarchy enrichment for {kind} node, num_nodes={len(node_ids)}...")
76
+ for kind, node_identifiers in node_map.items():
77
+ log.info(f"Beginning hierarchy enrichment for {kind} node, num_nodes={len(node_identifiers)}...")
77
78
  hierarchy_schema = self.db.schema.get(
78
79
  name=kind, branch=enriched_diff_root.diff_branch_name, duplicate=False
79
80
  )
80
- for node_id in node_ids:
81
+ for node_identifier in node_identifiers:
81
82
  query = await NodeGetHierarchyQuery.init(
82
83
  db=self.db,
83
84
  direction=RelationshipHierarchyDirection.ANCESTORS,
84
- node_id=node_id,
85
+ node_id=node_identifier.uuid,
85
86
  node_schema=hierarchy_schema,
86
87
  branch=diff_branch,
87
88
  hierarchical_ordering=True,
@@ -93,15 +94,15 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
93
94
  if not ancestors:
94
95
  continue
95
96
 
96
- node = enriched_diff_root.get_node(node_uuid=node_id)
97
+ node = enriched_diff_root.get_node(node_identifier=node_identifier)
97
98
  parent_rel = hierarchy_schema.get_relationship(name="parent")
98
99
 
99
100
  current_node = node
100
101
  for ancestor in ancestors:
102
+ ancestor_identifier = NodeIdentifier(uuid=ancestor.uuid, kind=ancestor.kind, labels=ancestor.labels)
101
103
  parent_request = ParentNodeAddRequest(
102
- node_id=current_node.uuid,
103
- parent_id=str(ancestor.uuid),
104
- parent_kind=ancestor.kind,
104
+ node_identifier=current_node.identifier,
105
+ parent_identifier=ancestor_identifier,
105
106
  parent_label="",
106
107
  parent_rel_name=parent_rel.name,
107
108
  parent_rel_identifier=parent_rel.get_identifier(),
@@ -113,20 +114,20 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
113
114
  current_node = parent
114
115
 
115
116
  async def _enrich_nodes_with_parent(
116
- self, enriched_diff_root: EnrichedDiffRoot, node_map: dict[str, list[str]]
117
+ self, enriched_diff_root: EnrichedDiffRoot, node_map: dict[str, list[NodeIdentifier]]
117
118
  ) -> None:
118
119
  diff_branch = registry.get_branch_from_registry(branch=enriched_diff_root.diff_branch_name)
119
120
 
120
- parent_peers: dict[str, RelationshipPeerData] = {}
121
+ parent_peers: dict[NodeIdentifier, RelationshipPeerData] = {}
121
122
 
122
123
  # Prepare a map to capture all parents that also have a parent
123
- node_parent_with_parent_map: dict[str, list[str]] = defaultdict(list)
124
+ node_parent_with_parent_map: dict[str, list[NodeIdentifier]] = defaultdict(list)
124
125
 
125
126
  # TODO Not gonna implement it now but technically we could check the content of the node to see if the parent relationship is present
126
127
 
127
128
  # Query the UUID of the parent
128
- for kind, ids in node_map.items():
129
- log.info(f"Beginning parent enrichment for {kind} node, num_nodes={len(ids)}...")
129
+ for kind, node_identifiers in node_map.items():
130
+ log.info(f"Beginning parent enrichment for {kind} node, num_nodes={len(node_identifiers)}...")
130
131
  schema_node = self.db.schema.get(name=kind, branch=enriched_diff_root.diff_branch_name, duplicate=False)
131
132
 
132
133
  parent_rel = [rel for rel in schema_node.relationships if rel.kind == RelationshipKind.PARENT][0]
@@ -137,33 +138,43 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
137
138
  query = await RelationshipGetPeerQuery.init(
138
139
  db=self.db,
139
140
  branch=diff_branch,
140
- source_ids=ids,
141
+ source_ids=[ni.uuid for ni in node_identifiers],
141
142
  rel_type=DatabaseEdgeType.IS_RELATED.value,
142
143
  schema=parent_rel,
143
144
  )
144
145
  await query.execute(db=self.db)
145
146
 
146
147
  for peer in query.get_peers():
147
- parent_peers[str(peer.source_id)] = peer
148
+ source_identifier = NodeIdentifier(
149
+ uuid=str(peer.source_id), kind=peer.source_kind, labels=peer.source_labels
150
+ )
151
+ parent_peers[source_identifier] = peer
148
152
  if parent_schema.has_parent_relationship:
149
- node_parent_with_parent_map[parent_schema.kind].append(str(peer.peer_id))
153
+ peer_identifier = NodeIdentifier(
154
+ uuid=str(peer.peer_id), kind=peer.peer_kind, labels=peer.peer_labels
155
+ )
156
+ node_parent_with_parent_map[parent_schema.kind].append(peer_identifier)
150
157
 
151
158
  # Check if the parent are already present
152
159
  # If parent is already in the list of node we need to add a relationship
153
160
  # If parent is not in the list of node, we need to add it
154
- diff_node_map = enriched_diff_root.get_node_map(node_uuids=set(parent_peers.keys()))
155
- for node_id, peer_parent in parent_peers.items():
161
+ diff_node_map = enriched_diff_root.get_node_map(
162
+ node_uuids={source_identifier.uuid for source_identifier in parent_peers.keys()}
163
+ )
164
+ for node_identifier, peer_parent in parent_peers.items():
156
165
  # TODO check if we can optimize this part to avoid querying this multiple times
157
- node = diff_node_map[node_id]
166
+ node = diff_node_map[node_identifier]
158
167
  schema_node = self.db.schema.get(
159
168
  name=node.kind, branch=enriched_diff_root.diff_branch_name, duplicate=False
160
169
  )
161
170
  parent_rel = [rel for rel in schema_node.relationships if rel.kind == RelationshipKind.PARENT][0]
162
171
 
172
+ peer_identifier = NodeIdentifier(
173
+ uuid=str(peer_parent.peer_id), kind=peer_parent.peer_kind, labels=peer_parent.peer_labels
174
+ )
163
175
  parent_request = ParentNodeAddRequest(
164
- node_id=node.uuid,
165
- parent_id=str(peer_parent.peer_id),
166
- parent_kind=peer_parent.peer_kind,
176
+ node_identifier=node.identifier,
177
+ parent_identifier=peer_identifier,
167
178
  parent_label="",
168
179
  parent_rel_name=parent_rel.name,
169
180
  parent_rel_identifier=parent_rel.get_identifier(),
@@ -3,6 +3,7 @@ from dataclasses import dataclass
3
3
  from infrahub.core.constants import DiffAction
4
4
  from infrahub.core.constants.database import DatabaseEdgeType
5
5
  from infrahub.core.diff.model.path import BranchTrackingId
6
+ from infrahub.core.diff.query.filters import EnrichedDiffQueryFilters, IncExclActionFilterOptions, IncExclFilterOptions
6
7
  from infrahub.core.ipam.kinds_getter import IpamKindsGetter
7
8
  from infrahub.core.ipam.model import IpamNodeDetails
8
9
  from infrahub.core.manager import NodeManager
@@ -48,10 +49,10 @@ class IpamDiffParser:
48
49
  base_branch_name=target_branch_name,
49
50
  diff_branch_names=[source_branch_name],
50
51
  tracking_id=BranchTrackingId(name=source_branch_name),
51
- filters={
52
- "kind": {"includes": list(ip_address_kinds | ip_prefix_kinds)},
53
- "status": {"excludes": {DiffAction.UNCHANGED}},
54
- },
52
+ filters=EnrichedDiffQueryFilters(
53
+ kind=IncExclFilterOptions(includes=list(ip_address_kinds | ip_prefix_kinds)),
54
+ status=IncExclActionFilterOptions(excludes={DiffAction.UNCHANGED}),
55
+ ),
55
56
  )
56
57
  changed_node_details: list[ChangedIpamNodeDetails] = []
57
58
  for diff in enriched_diffs:
@@ -54,24 +54,26 @@ class DiffMerger:
54
54
  log.info(f"Diff {latest_diff.uuid} retrieved")
55
55
  batch_num = 0
56
56
  async for node_diff_dicts, property_diff_dicts in self.serializer.serialize_diff(diff=enriched_diff):
57
- log.info(f"Merging batch of nodes #{batch_num}")
58
- merge_query = await DiffMergeQuery.init(
59
- db=self.db,
60
- branch=self.source_branch,
61
- at=at,
62
- target_branch=self.destination_branch,
63
- node_diff_dicts=node_diff_dicts,
64
- )
65
- await merge_query.execute(db=self.db)
66
- log.info(f"Merging batch of properties #{batch_num}")
67
- merge_properties_query = await DiffMergePropertiesQuery.init(
68
- db=self.db,
69
- branch=self.source_branch,
70
- at=at,
71
- target_branch=self.destination_branch,
72
- property_diff_dicts=property_diff_dicts,
73
- )
74
- await merge_properties_query.execute(db=self.db)
57
+ if node_diff_dicts:
58
+ log.info(f"Merging batch of nodes #{batch_num}")
59
+ merge_query = await DiffMergeQuery.init(
60
+ db=self.db,
61
+ branch=self.source_branch,
62
+ at=at,
63
+ target_branch=self.destination_branch,
64
+ node_diff_dicts=node_diff_dicts,
65
+ )
66
+ await merge_query.execute(db=self.db)
67
+ if property_diff_dicts:
68
+ log.info(f"Merging batch of properties #{batch_num}")
69
+ merge_properties_query = await DiffMergePropertiesQuery.init(
70
+ db=self.db,
71
+ branch=self.source_branch,
72
+ at=at,
73
+ target_branch=self.destination_branch,
74
+ property_diff_dicts=property_diff_dicts,
75
+ )
76
+ await merge_properties_query.execute(db=self.db)
75
77
  log.info(f"Batch #{batch_num} merged")
76
78
  batch_num += 1
77
79
 
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class NodeFieldSpecifierMap:
5
+ def __init__(self) -> None:
6
+ # {uuid: {kind: {field_name, ...}}}
7
+ self._map: dict[str, dict[str, set[str]]] = {}
8
+
9
+ def __len__(self) -> int:
10
+ return len(self._map)
11
+
12
+ def __hash__(self) -> int:
13
+ full_node_hash_sum = 0
14
+ for node_uuid, node_dict in self._map.items():
15
+ node_kinds_hash_sum = 0
16
+ for kind, field_names in node_dict.items():
17
+ fields_hash = hash(frozenset(field_names))
18
+ node_kinds_hash_sum += hash(f"{hash(kind)}:{fields_hash}")
19
+ full_node_hash_sum += hash(f"{node_uuid}:{node_kinds_hash_sum}")
20
+ return hash(full_node_hash_sum)
21
+
22
+ def __eq__(self, other: object) -> bool:
23
+ if not isinstance(other, NodeFieldSpecifierMap):
24
+ return False
25
+ return self._map == other._map
26
+
27
+ def __sub__(self, other: NodeFieldSpecifierMap) -> NodeFieldSpecifierMap:
28
+ subtracted = NodeFieldSpecifierMap()
29
+ for node_uuid, node_dict in self._map.items():
30
+ if node_uuid not in other._map:
31
+ subtracted._map[node_uuid] = {**node_dict}
32
+ continue
33
+ subtracted_node_map = {}
34
+ for kind, field_names in node_dict.items():
35
+ subtracted_field_names = field_names - other._map[node_uuid].get(kind, set())
36
+ if not subtracted_field_names:
37
+ continue
38
+ subtracted_node_map[kind] = subtracted_field_names
39
+ if not subtracted_node_map:
40
+ continue
41
+ subtracted._map[node_uuid] = subtracted_node_map
42
+ return subtracted
43
+
44
+ def add_entry(self, node_uuid: str, kind: str, field_name: str) -> None:
45
+ if node_uuid not in self._map:
46
+ self._map[node_uuid] = {}
47
+ if kind not in self._map[node_uuid]:
48
+ self._map[node_uuid][kind] = set()
49
+ self._map[node_uuid][kind].add(field_name)
50
+
51
+ def has_entry(self, node_uuid: str, kind: str, field_name: str) -> bool:
52
+ return field_name in self._map.get(node_uuid, {}).get(kind, set())
53
+
54
+ def get_uuids_list(self) -> list[str]:
55
+ return list(self._map.keys())
56
+
57
+ def get_uuid_field_names_map(self) -> dict[str, list[str]]:
58
+ uuid_field_names_map: dict[str, list[str]] = {}
59
+ for node_uuid, node_dict in self._map.items():
60
+ field_names_set: set[str] = set()
61
+ for field_names in node_dict.values():
62
+ field_names_set |= field_names
63
+ uuid_field_names_map[node_uuid] = list(field_names_set)
64
+ return uuid_field_names_map
@@ -81,6 +81,21 @@ def deserialize_tracking_id(tracking_id_str: str) -> TrackingId:
81
81
  raise ValueError(f"{tracking_id_str} is not a valid TrackingId")
82
82
 
83
83
 
84
+ @dataclass
85
+ class NodeIdentifier:
86
+ """Uniquely identifying nodes that have had their kind or inheritance updated requires all of these fields"""
87
+
88
+ uuid: str
89
+ kind: str
90
+ labels: frozenset[str]
91
+
92
+ def __hash__(self) -> int:
93
+ return hash(f"{self.uuid}:{self.kind}:{hash(self.labels)}")
94
+
95
+ def __str__(self) -> str:
96
+ return f"{self.kind} '{self.uuid}' ({','.join(self.labels)})"
97
+
98
+
84
99
  @dataclass
85
100
  class NodeDiffFieldSummary:
86
101
  kind: str
@@ -307,8 +322,7 @@ class ParentNodeInfo:
307
322
 
308
323
  @dataclass
309
324
  class EnrichedDiffNode(BaseSummary):
310
- uuid: str
311
- kind: str
325
+ identifier: NodeIdentifier
312
326
  label: str
313
327
  path_identifier: str = field(default="", kw_only=True)
314
328
  changed_at: Timestamp | None = field(default=None, kw_only=True)
@@ -318,7 +332,15 @@ class EnrichedDiffNode(BaseSummary):
318
332
  relationships: set[EnrichedDiffRelationship] = field(default_factory=set)
319
333
 
320
334
  def __hash__(self) -> int:
321
- return hash(self.uuid)
335
+ return hash(self.identifier)
336
+
337
+ @property
338
+ def uuid(self) -> str:
339
+ return self.identifier.uuid
340
+
341
+ @property
342
+ def kind(self) -> str:
343
+ return self.identifier.kind
322
344
 
323
345
  @property
324
346
  def num_properties(self) -> int:
@@ -402,8 +424,7 @@ class EnrichedDiffNode(BaseSummary):
402
424
  @classmethod
403
425
  def from_calculated_node(cls, calculated_node: DiffNode) -> EnrichedDiffNode:
404
426
  return EnrichedDiffNode(
405
- uuid=calculated_node.uuid,
406
- kind=calculated_node.kind,
427
+ identifier=calculated_node.identifier,
407
428
  label="",
408
429
  changed_at=calculated_node.changed_at,
409
430
  action=calculated_node.action,
@@ -473,24 +494,24 @@ class EnrichedDiffRoot(EnrichedDiffRootMetadata):
473
494
  nodes_with_parent_uuids |= {child_n.uuid for child_n in r.nodes}
474
495
  return {node for node in self.nodes if node.uuid not in nodes_with_parent_uuids}
475
496
 
476
- def get_node(self, node_uuid: str) -> EnrichedDiffNode:
497
+ def get_node(self, node_identifier: NodeIdentifier) -> EnrichedDiffNode:
477
498
  for n in self.nodes:
478
- if n.uuid == node_uuid:
499
+ if n.identifier == node_identifier:
479
500
  return n
480
- raise ValueError(f"No node {node_uuid} in diff root")
501
+ raise ValueError(f"No node {node_identifier} in diff root")
481
502
 
482
- def has_node(self, node_uuid: str) -> bool:
503
+ def has_node(self, node_identifier: NodeIdentifier) -> bool:
483
504
  try:
484
- self.get_node(node_uuid=node_uuid)
505
+ self.get_node(node_identifier=node_identifier)
485
506
  return True
486
507
  except ValueError:
487
508
  return False
488
509
 
489
- def get_node_map(self, node_uuids: set[str] | None = None) -> dict[str, EnrichedDiffNode]:
510
+ def get_node_map(self, node_uuids: set[str] | None = None) -> dict[NodeIdentifier, EnrichedDiffNode]:
490
511
  node_map = {}
491
512
  for node in self.nodes:
492
- if node_uuids is None or node.uuid in node_uuids:
493
- node_map[node.uuid] = node
513
+ if node_uuids is None or node.identifier.uuid in node_uuids:
514
+ node_map[node.identifier] = node
494
515
  return node_map
495
516
 
496
517
  def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
@@ -522,49 +543,6 @@ class EnrichedDiffRoot(EnrichedDiffRootMetadata):
522
543
  nodes={EnrichedDiffNode.from_calculated_node(calculated_node=n) for n in calculated_diff.nodes},
523
544
  )
524
545
 
525
- def add_parent(
526
- self,
527
- node_id: str,
528
- parent_id: str,
529
- parent_kind: str,
530
- parent_label: str,
531
- parent_rel_name: str,
532
- parent_rel_identifier: str,
533
- parent_rel_cardinality: RelationshipCardinality,
534
- parent_rel_label: str = "",
535
- ) -> EnrichedDiffNode:
536
- node = self.get_node(node_uuid=node_id)
537
- if not self.has_node(node_uuid=parent_id):
538
- parent = EnrichedDiffNode(
539
- uuid=parent_id,
540
- kind=parent_kind,
541
- label=parent_label,
542
- action=DiffAction.UNCHANGED,
543
- changed_at=None,
544
- )
545
- self.nodes.add(parent)
546
-
547
- else:
548
- parent = self.get_node(node_uuid=parent_id)
549
-
550
- if node.has_relationship(name=parent_rel_name):
551
- rel = node.get_relationship(name=parent_rel_name)
552
- rel.nodes.add(parent)
553
- else:
554
- node.relationships.add(
555
- EnrichedDiffRelationship(
556
- name=parent_rel_name,
557
- identifier=parent_rel_identifier,
558
- label=parent_rel_label,
559
- cardinality=parent_rel_cardinality,
560
- changed_at=None,
561
- action=DiffAction.UNCHANGED,
562
- nodes={parent},
563
- )
564
- )
565
-
566
- return parent
567
-
568
546
 
569
547
  @dataclass
570
548
  class EnrichedDiffsMetadata:
@@ -650,6 +628,14 @@ class EnrichedDiffs(EnrichedDiffsMetadata):
650
628
  def branch_node_uuids(self) -> set[str]:
651
629
  return {n.uuid for n in self.diff_branch_diff.nodes}
652
630
 
631
+ @property
632
+ def base_node_identifiers(self) -> set[NodeIdentifier]:
633
+ return {n.identifier for n in self.base_branch_diff.nodes}
634
+
635
+ @property
636
+ def branch_node_identifiers(self) -> set[NodeIdentifier]:
637
+ return {n.identifier for n in self.diff_branch_diff.nodes}
638
+
653
639
 
654
640
  @dataclass
655
641
  class CalculatedDiffs:
@@ -697,13 +683,20 @@ class DiffRelationship:
697
683
 
698
684
  @dataclass
699
685
  class DiffNode:
700
- uuid: str
701
- kind: str
686
+ identifier: NodeIdentifier
702
687
  changed_at: Timestamp
703
688
  action: DiffAction
704
689
  attributes: list[DiffAttribute] = field(default_factory=list)
705
690
  relationships: list[DiffRelationship] = field(default_factory=list)
706
691
 
692
+ @property
693
+ def uuid(self) -> str:
694
+ return self.identifier.uuid
695
+
696
+ @property
697
+ def kind(self) -> str:
698
+ return self.identifier.kind
699
+
707
700
 
708
701
  @dataclass
709
702
  class DiffRoot:
@@ -780,6 +773,10 @@ class DatabasePath:
780
773
  def node_db_id(self) -> str:
781
774
  return self.node_node.element_id
782
775
 
776
+ @property
777
+ def node_labels(self) -> frozenset[str]:
778
+ return self.node_node.labels
779
+
783
780
  @property
784
781
  def node_kind(self) -> str:
785
782
  return str(self.node_node.get("kind"))
@@ -2,14 +2,13 @@ from dataclasses import dataclass, field
2
2
 
3
3
  from infrahub.core.constants import DiffAction, RelationshipCardinality
4
4
 
5
- from .model.path import EnrichedDiffNode, EnrichedDiffRelationship, EnrichedDiffRoot
5
+ from .model.path import EnrichedDiffNode, EnrichedDiffRelationship, EnrichedDiffRoot, NodeIdentifier
6
6
 
7
7
 
8
8
  @dataclass
9
9
  class ParentNodeAddRequest:
10
- node_id: str
11
- parent_id: str
12
- parent_kind: str
10
+ node_identifier: NodeIdentifier
11
+ parent_identifier: NodeIdentifier
13
12
  parent_label: str
14
13
  parent_rel_name: str
15
14
  parent_rel_identifier: str
@@ -20,7 +19,7 @@ class ParentNodeAddRequest:
20
19
  class DiffParentNodeAdder:
21
20
  def __init__(self) -> None:
22
21
  self._diff_root: EnrichedDiffRoot | None = None
23
- self._node_map: dict[str, EnrichedDiffNode] = {}
22
+ self._node_map: dict[NodeIdentifier, EnrichedDiffNode] = {}
24
23
 
25
24
  def initialize(self, enriched_diff_root: EnrichedDiffRoot) -> None:
26
25
  self._diff_root = enriched_diff_root
@@ -31,33 +30,32 @@ class DiffParentNodeAdder:
31
30
  raise RuntimeError("Must call initialize before using")
32
31
  return self._diff_root
33
32
 
34
- def get_node(self, node_uuid: str) -> EnrichedDiffNode:
35
- return self._node_map[node_uuid]
33
+ def get_node(self, identifier: NodeIdentifier) -> EnrichedDiffNode:
34
+ return self._node_map[identifier]
36
35
 
37
- def has_node(self, node_uuid: str) -> bool:
38
- return node_uuid in self._node_map
36
+ def has_node(self, identifier: NodeIdentifier) -> bool:
37
+ return identifier in self._node_map
39
38
 
40
39
  def add_node(self, node: EnrichedDiffNode) -> None:
41
- if node.uuid in self._node_map:
40
+ if node.identifier in self._node_map:
42
41
  return
43
- self._node_map[node.uuid] = node
42
+ self._node_map[node.identifier] = node
44
43
  self.get_root().nodes.add(node)
45
44
 
46
45
  def add_parent(self, parent_request: ParentNodeAddRequest) -> EnrichedDiffNode:
47
46
  if not self._diff_root:
48
47
  raise RuntimeError("Must call initialize before using")
49
- node = self.get_node(node_uuid=parent_request.node_id)
50
- if not self.has_node(node_uuid=parent_request.parent_id):
48
+ node = self.get_node(identifier=parent_request.node_identifier)
49
+ if not self.has_node(identifier=parent_request.parent_identifier):
51
50
  parent = EnrichedDiffNode(
52
- uuid=parent_request.parent_id,
53
- kind=parent_request.parent_kind,
51
+ identifier=parent_request.parent_identifier,
54
52
  label=parent_request.parent_label,
55
53
  action=DiffAction.UNCHANGED,
56
54
  changed_at=None,
57
55
  )
58
56
  self.add_node(parent)
59
57
  else:
60
- parent = self.get_node(node_uuid=parent_request.parent_id)
58
+ parent = self.get_node(identifier=parent_request.parent_identifier)
61
59
 
62
60
  try:
63
61
  rel = node.get_relationship(name=parent_request.parent_rel_name)
@@ -0,0 +1,42 @@
1
+ from typing import Any
2
+
3
+ from infrahub.core.diff.model.path import NodeIdentifier
4
+ from infrahub.core.query import Query, QueryType
5
+ from infrahub.database import InfrahubDatabase
6
+
7
+
8
+ class EnrichedDiffDropNodesQuery(Query):
9
+ name = "enriched_diff_drop_nodes"
10
+ type = QueryType.WRITE
11
+ insert_return = False
12
+
13
+ def __init__(self, enriched_diff_uuid: str, node_identifiers: list[NodeIdentifier], **kwargs: Any) -> None:
14
+ super().__init__(**kwargs)
15
+ self.enriched_diff_uuid = enriched_diff_uuid
16
+ self.node_identifiers = node_identifiers
17
+
18
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
19
+ self.params = {
20
+ "diff_root_uuid": self.enriched_diff_uuid,
21
+ "node_uuids": [ni.uuid for ni in self.node_identifiers],
22
+ "node_identifiers_map": {ni.uuid: ni.kind for ni in self.node_identifiers},
23
+ }
24
+ query = """
25
+ MATCH (d_root:DiffRoot {uuid: $diff_root_uuid})
26
+ MATCH (d_root)-[:DIFF_HAS_NODE]->(dn:DiffNode)
27
+ WHERE dn.uuid IN $node_uuids
28
+ AND dn.kind IN $node_identifiers_map[dn.uuid]
29
+ OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]-(da:DiffAttribute)
30
+ OPTIONAL MATCH (da)-[*]->(diff_thing)
31
+ DETACH DELETE diff_thing
32
+ DETACH DELETE da
33
+ WITH dn
34
+ OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship)
35
+ OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement)
36
+ OPTIONAL MATCH (dre)-[*]->(diff_thing)
37
+ DETACH DELETE diff_thing
38
+ DETACH DELETE dre
39
+ DETACH DELETE dr
40
+ DETACH DELETE dn
41
+ """
42
+ self.add_to_query(query=query)
@@ -18,20 +18,21 @@ class EnrichedDiffFieldSpecifiersQuery(Query):
18
18
  CALL {
19
19
  MATCH (root:DiffRoot {uuid: $diff_id})-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_ATTRIBUTE]->(attr:DiffAttribute)
20
20
  WHERE (root.is_merged IS NULL OR root.is_merged <> TRUE)
21
- RETURN node.uuid AS node_uuid, attr.name AS field_name
21
+ RETURN node.uuid AS node_uuid, node.kind AS node_kind, attr.name AS field_name
22
22
  UNION
23
23
  MATCH (root:DiffRoot {uuid: $diff_id})-[:DIFF_HAS_NODE]->(node:DiffNode)-[:DIFF_HAS_RELATIONSHIP]->(rel:DiffRelationship)
24
24
  WHERE (root.is_merged IS NULL OR root.is_merged <> TRUE)
25
- RETURN node.uuid AS node_uuid, rel.identifier AS field_name
25
+ RETURN node.uuid AS node_uuid, node.kind AS node_kind, rel.identifier AS field_name
26
26
  }
27
27
  """
28
28
  self.add_to_query(query=query)
29
- self.return_labels = ["node_uuid", "field_name"]
30
- self.order_by = ["node_uuid", "field_name"]
29
+ self.return_labels = ["node_uuid", "node_kind", "field_name"]
30
+ self.order_by = ["node_uuid", "node_kind", "field_name"]
31
31
 
32
- def get_node_field_specifier_tuples(self) -> Generator[tuple[str, str], None, None]:
32
+ def get_node_field_specifier_tuples(self) -> Generator[tuple[str, str, str], None, None]:
33
33
  for result in self.get_results():
34
34
  node_uuid = result.get_as_str("node_uuid")
35
+ node_kind = result.get_as_str("node_kind")
35
36
  field_name = result.get_as_str("field_name")
36
- if node_uuid and field_name:
37
- yield (node_uuid, field_name)
37
+ if node_uuid and node_kind and field_name:
38
+ yield (node_uuid, node_kind, field_name)