infrahub-server 1.3.1__py3-none-any.whl → 1.3.2__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.
- infrahub/core/diff/calculator.py +4 -1
- infrahub/core/diff/query_parser.py +23 -32
- infrahub/core/node/constraints/grouped_uniqueness.py +88 -132
- infrahub/core/validators/uniqueness/model.py +17 -0
- infrahub/core/validators/uniqueness/query.py +212 -1
- infrahub/graphql/mutations/main.py +18 -2
- infrahub_sdk/node/node.py +22 -10
- infrahub_sdk/node/related_node.py +7 -0
- {infrahub_server-1.3.1.dist-info → infrahub_server-1.3.2.dist-info}/METADATA +1 -1
- {infrahub_server-1.3.1.dist-info → infrahub_server-1.3.2.dist-info}/RECORD +13 -13
- {infrahub_server-1.3.1.dist-info → infrahub_server-1.3.2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.1.dist-info → infrahub_server-1.3.2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.1.dist-info → infrahub_server-1.3.2.dist-info}/entry_points.txt +0 -0
infrahub/core/diff/calculator.py
CHANGED
|
@@ -181,8 +181,11 @@ class DiffCalculator:
|
|
|
181
181
|
log.info("Diff property-level calculation queries for branch complete")
|
|
182
182
|
|
|
183
183
|
if base_branch.name != diff_branch.name:
|
|
184
|
-
current_node_field_specifiers = diff_parser.get_current_node_field_specifiers()
|
|
185
184
|
new_node_field_specifiers = diff_parser.get_new_node_field_specifiers()
|
|
185
|
+
current_node_field_specifiers = None
|
|
186
|
+
if previous_node_specifiers is not None:
|
|
187
|
+
current_node_field_specifiers = previous_node_specifiers - new_node_field_specifiers
|
|
188
|
+
|
|
186
189
|
base_calculation_request = DiffCalculationRequest(
|
|
187
190
|
base_branch=base_branch,
|
|
188
191
|
diff_branch=base_branch,
|
|
@@ -486,6 +486,7 @@ class DiffQueryParser:
|
|
|
486
486
|
self._previous_node_field_specifiers = previous_node_field_specifiers or NodeFieldSpecifierMap()
|
|
487
487
|
self._new_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
488
488
|
self._current_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
489
|
+
self._diff_node_field_specifiers: NodeFieldSpecifierMap = NodeFieldSpecifierMap()
|
|
489
490
|
|
|
490
491
|
def get_branches(self) -> set[str]:
|
|
491
492
|
return set(self._final_diff_root_by_branch.keys())
|
|
@@ -497,33 +498,17 @@ class DiffQueryParser:
|
|
|
497
498
|
return self._final_diff_root_by_branch[branch]
|
|
498
499
|
return DiffRoot(from_time=self.from_time, to_time=self.to_time, uuid=str(uuid4()), branch=branch, nodes=[])
|
|
499
500
|
|
|
500
|
-
def get_diff_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
501
|
-
node_field_specifiers_map = NodeFieldSpecifierMap()
|
|
502
|
-
if self.diff_branch_name not in self._diff_root_by_branch:
|
|
503
|
-
return node_field_specifiers_map
|
|
504
|
-
diff_root = self._diff_root_by_branch[self.diff_branch_name]
|
|
505
|
-
for node in diff_root.nodes_by_identifier.values():
|
|
506
|
-
for attribute_name in node.attributes_by_name:
|
|
507
|
-
node_field_specifiers_map.add_entry(node_uuid=node.uuid, kind=node.kind, field_name=attribute_name)
|
|
508
|
-
for relationship_diff in node.relationships_by_identifier.values():
|
|
509
|
-
node_field_specifiers_map.add_entry(
|
|
510
|
-
node_uuid=node.uuid, kind=node.kind, field_name=relationship_diff.identifier
|
|
511
|
-
)
|
|
512
|
-
return node_field_specifiers_map
|
|
513
|
-
|
|
514
501
|
def get_new_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
self.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return
|
|
524
|
-
|
|
525
|
-
self._current_node_field_specifiers = self._previous_node_field_specifiers - new_node_field_specifiers
|
|
526
|
-
return self._current_node_field_specifiers
|
|
502
|
+
return self._diff_node_field_specifiers - self._previous_node_field_specifiers
|
|
503
|
+
|
|
504
|
+
def is_new_node_field_specifier(self, node_uuid: str, kind: str, field_name: str) -> bool:
|
|
505
|
+
if not self._diff_node_field_specifiers.has_entry(node_uuid=node_uuid, kind=kind, field_name=field_name):
|
|
506
|
+
return False
|
|
507
|
+
if self._previous_node_field_specifiers and self._previous_node_field_specifiers.has_entry(
|
|
508
|
+
node_uuid=node_uuid, kind=kind, field_name=field_name
|
|
509
|
+
):
|
|
510
|
+
return False
|
|
511
|
+
return True
|
|
527
512
|
|
|
528
513
|
def read_result(self, query_result: QueryResult) -> None:
|
|
529
514
|
try:
|
|
@@ -533,8 +518,6 @@ class DiffQueryParser:
|
|
|
533
518
|
return
|
|
534
519
|
database_path = DatabasePath.from_cypher_path(cypher_path=path)
|
|
535
520
|
self._parse_path(database_path=database_path)
|
|
536
|
-
self._current_node_field_specifiers = None
|
|
537
|
-
self._new_node_field_specifiers = None
|
|
538
521
|
|
|
539
522
|
def parse(self, include_unchanged: bool = False) -> None:
|
|
540
523
|
self._new_node_field_specifiers = None
|
|
@@ -617,11 +600,15 @@ class DiffQueryParser:
|
|
|
617
600
|
branch_name = database_path.deepest_branch
|
|
618
601
|
from_time = self.from_time
|
|
619
602
|
if branch_name == self.base_branch_name:
|
|
620
|
-
|
|
621
|
-
if new_node_field_specifiers.has_entry(
|
|
603
|
+
if self.is_new_node_field_specifier(
|
|
622
604
|
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
623
605
|
):
|
|
624
606
|
from_time = self.diff_branched_from_time
|
|
607
|
+
else:
|
|
608
|
+
# Add to diff node field specifiers if this is the diff branch
|
|
609
|
+
self._diff_node_field_specifiers.add_entry(
|
|
610
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
611
|
+
)
|
|
625
612
|
if attribute_name not in diff_node.attributes_by_name:
|
|
626
613
|
diff_node.attributes_by_name[attribute_name] = DiffAttributeIntermediate(
|
|
627
614
|
uuid=database_path.attribute_id,
|
|
@@ -663,11 +650,15 @@ class DiffQueryParser:
|
|
|
663
650
|
branch_name = database_path.deepest_branch
|
|
664
651
|
from_time = self.from_time
|
|
665
652
|
if branch_name == self.base_branch_name:
|
|
666
|
-
|
|
667
|
-
if new_node_field_specifiers.has_entry(
|
|
653
|
+
if self.is_new_node_field_specifier(
|
|
668
654
|
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
669
655
|
):
|
|
670
656
|
from_time = self.diff_branched_from_time
|
|
657
|
+
else:
|
|
658
|
+
# Add to diff node field specifiers if this is the diff branch
|
|
659
|
+
self._diff_node_field_specifiers.add_entry(
|
|
660
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
661
|
+
)
|
|
671
662
|
diff_relationship = DiffRelationshipIntermediate(
|
|
672
663
|
name=relationship_schema.name,
|
|
673
664
|
cardinality=relationship_schema.cardinality,
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from infrahub.core import registry
|
|
6
6
|
from infrahub.core.constants import NULL_VALUE
|
|
7
|
-
from infrahub.core.schema import (
|
|
8
|
-
MainSchemaTypes,
|
|
9
|
-
SchemaAttributePath,
|
|
10
|
-
SchemaAttributePathValue,
|
|
11
|
-
)
|
|
12
7
|
from infrahub.core.schema.basenode_schema import (
|
|
13
|
-
SchemaUniquenessConstraintPath,
|
|
14
8
|
UniquenessConstraintType,
|
|
15
9
|
UniquenessConstraintViolation,
|
|
16
10
|
)
|
|
17
|
-
from infrahub.core.validators.uniqueness.index import UniquenessQueryResultsIndex
|
|
18
11
|
from infrahub.core.validators.uniqueness.model import (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
NodeUniquenessQueryRequestValued,
|
|
13
|
+
QueryAttributePathValued,
|
|
14
|
+
QueryRelationshipPathValued,
|
|
22
15
|
)
|
|
23
|
-
from infrahub.core.validators.uniqueness.query import
|
|
16
|
+
from infrahub.core.validators.uniqueness.query import UniquenessValidationQuery
|
|
24
17
|
from infrahub.exceptions import HFIDViolatedError, ValidationError
|
|
25
18
|
|
|
26
19
|
from .interface import NodeConstraintInterface
|
|
@@ -28,8 +21,11 @@ from .interface import NodeConstraintInterface
|
|
|
28
21
|
if TYPE_CHECKING:
|
|
29
22
|
from infrahub.core.branch import Branch
|
|
30
23
|
from infrahub.core.node import Node
|
|
31
|
-
from infrahub.core.query import QueryResult
|
|
32
24
|
from infrahub.core.relationship.model import RelationshipManager
|
|
25
|
+
from infrahub.core.schema import (
|
|
26
|
+
MainSchemaTypes,
|
|
27
|
+
SchemaAttributePath,
|
|
28
|
+
)
|
|
33
29
|
from infrahub.core.timestamp import Timestamp
|
|
34
30
|
from infrahub.database import InfrahubDatabase
|
|
35
31
|
|
|
@@ -40,72 +36,38 @@ class NodeGroupedUniquenessConstraint(NodeConstraintInterface):
|
|
|
40
36
|
self.branch = branch
|
|
41
37
|
self.schema_branch = registry.schema.get_schema_branch(branch.name)
|
|
42
38
|
|
|
43
|
-
async def
|
|
44
|
-
self,
|
|
45
|
-
updated_node: Node,
|
|
46
|
-
node_schema: MainSchemaTypes,
|
|
47
|
-
uniqueness_constraint_paths: list[SchemaUniquenessConstraintPath],
|
|
48
|
-
filters: list[str] | None = None,
|
|
49
|
-
) -> NodeUniquenessQueryRequest:
|
|
50
|
-
query_request = NodeUniquenessQueryRequest(kind=node_schema.kind)
|
|
51
|
-
for uniqueness_constraint_path in uniqueness_constraint_paths:
|
|
52
|
-
include_in_query = not filters
|
|
53
|
-
query_relationship_paths: set[QueryRelationshipAttributePath] = set()
|
|
54
|
-
query_attribute_paths: set[QueryAttributePath] = set()
|
|
55
|
-
for attribute_path in uniqueness_constraint_path.attributes_paths:
|
|
56
|
-
if attribute_path.related_schema and attribute_path.relationship_schema:
|
|
57
|
-
if filters and attribute_path.relationship_schema.name in filters:
|
|
58
|
-
include_in_query = True
|
|
59
|
-
|
|
60
|
-
relationship_manager: RelationshipManager = getattr(
|
|
61
|
-
updated_node, attribute_path.relationship_schema.name
|
|
62
|
-
)
|
|
63
|
-
related_node = await relationship_manager.get_peer(db=self.db)
|
|
64
|
-
related_node_id = related_node.get_id() if related_node else None
|
|
65
|
-
query_relationship_paths.add(
|
|
66
|
-
QueryRelationshipAttributePath(
|
|
67
|
-
identifier=attribute_path.relationship_schema.get_identifier(),
|
|
68
|
-
value=related_node_id,
|
|
69
|
-
)
|
|
70
|
-
)
|
|
71
|
-
continue
|
|
72
|
-
if attribute_path.attribute_schema:
|
|
73
|
-
if filters and attribute_path.attribute_schema.name in filters:
|
|
74
|
-
include_in_query = True
|
|
75
|
-
attribute_name = attribute_path.attribute_schema.name
|
|
76
|
-
attribute = getattr(updated_node, attribute_name)
|
|
77
|
-
if attribute.is_enum and attribute.value:
|
|
78
|
-
attribute_value = attribute.value.value
|
|
79
|
-
else:
|
|
80
|
-
attribute_value = attribute.value
|
|
81
|
-
if attribute_value is None:
|
|
82
|
-
attribute_value = NULL_VALUE
|
|
83
|
-
query_attribute_paths.add(
|
|
84
|
-
QueryAttributePath(
|
|
85
|
-
attribute_name=attribute_name,
|
|
86
|
-
property_name=attribute_path.attribute_property_name or "value",
|
|
87
|
-
value=attribute_value,
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
if include_in_query:
|
|
91
|
-
query_request.relationship_attribute_paths |= query_relationship_paths
|
|
92
|
-
query_request.unique_attribute_paths |= query_attribute_paths
|
|
93
|
-
return query_request
|
|
94
|
-
|
|
95
|
-
async def _get_node_attribute_path_values(
|
|
39
|
+
async def _get_unique_valued_paths(
|
|
96
40
|
self,
|
|
97
41
|
updated_node: Node,
|
|
98
42
|
path_group: list[SchemaAttributePath],
|
|
99
|
-
|
|
100
|
-
|
|
43
|
+
filters: list[str],
|
|
44
|
+
) -> list[QueryAttributePathValued | QueryRelationshipPathValued]:
|
|
45
|
+
# if filters are provided, we need to check if the path group is relevant to the filters
|
|
46
|
+
if filters:
|
|
47
|
+
field_names: list[str] = []
|
|
48
|
+
for schema_attribute_path in path_group:
|
|
49
|
+
if schema_attribute_path.relationship_schema:
|
|
50
|
+
field_names.append(schema_attribute_path.relationship_schema.name)
|
|
51
|
+
elif schema_attribute_path.attribute_schema:
|
|
52
|
+
field_names.append(schema_attribute_path.attribute_schema.name)
|
|
53
|
+
|
|
54
|
+
if not set(field_names) & set(filters):
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
valued_paths: list[QueryAttributePathValued | QueryRelationshipPathValued] = []
|
|
101
58
|
for schema_attribute_path in path_group:
|
|
102
59
|
if schema_attribute_path.relationship_schema:
|
|
103
60
|
relationship_name = schema_attribute_path.relationship_schema.name
|
|
104
61
|
relationship_manager: RelationshipManager = getattr(updated_node, relationship_name)
|
|
105
62
|
related_node = await relationship_manager.get_peer(db=self.db)
|
|
106
63
|
related_node_id = related_node.get_id() if related_node else None
|
|
107
|
-
|
|
108
|
-
|
|
64
|
+
valued_paths.append(
|
|
65
|
+
QueryRelationshipPathValued(
|
|
66
|
+
relationship_schema=schema_attribute_path.relationship_schema,
|
|
67
|
+
peer_id=related_node_id,
|
|
68
|
+
attribute_name=None,
|
|
69
|
+
attribute_value=None,
|
|
70
|
+
)
|
|
109
71
|
)
|
|
110
72
|
elif schema_attribute_path.attribute_schema:
|
|
111
73
|
attribute_name = schema_attribute_path.attribute_schema.name
|
|
@@ -115,86 +77,79 @@ class NodeGroupedUniquenessConstraint(NodeConstraintInterface):
|
|
|
115
77
|
attribute_value = attribute_value.value
|
|
116
78
|
elif attribute_value is None:
|
|
117
79
|
attribute_value = NULL_VALUE
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
80
|
+
valued_paths.append(
|
|
81
|
+
QueryAttributePathValued(
|
|
82
|
+
attribute_name=attribute_name,
|
|
121
83
|
value=attribute_value,
|
|
122
84
|
)
|
|
123
85
|
)
|
|
124
|
-
return
|
|
86
|
+
return valued_paths
|
|
125
87
|
|
|
126
|
-
async def
|
|
88
|
+
async def _get_single_schema_violations(
|
|
127
89
|
self,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
90
|
+
node: Node,
|
|
91
|
+
node_schema: MainSchemaTypes,
|
|
92
|
+
filters: list[str],
|
|
93
|
+
at: Timestamp | None = None,
|
|
131
94
|
) -> list[UniquenessConstraintViolation]:
|
|
132
|
-
|
|
133
|
-
|
|
95
|
+
schema_branch = self.db.schema.get_schema_branch(name=self.branch.name)
|
|
96
|
+
|
|
97
|
+
uniqueness_constraint_paths = node_schema.get_unique_constraint_schema_attribute_paths(
|
|
98
|
+
schema_branch=schema_branch
|
|
134
99
|
)
|
|
135
|
-
|
|
100
|
+
|
|
101
|
+
violations: list[UniquenessConstraintViolation] = []
|
|
136
102
|
for uniqueness_constraint_path in uniqueness_constraint_paths:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
103
|
+
valued_paths = await self._get_unique_valued_paths(
|
|
104
|
+
updated_node=node,
|
|
105
|
+
path_group=uniqueness_constraint_path.attributes_paths,
|
|
106
|
+
filters=filters,
|
|
140
107
|
)
|
|
141
108
|
|
|
142
|
-
|
|
143
|
-
if any(sapv.value is None for sapv in schema_attribute_path_values):
|
|
109
|
+
if not valued_paths:
|
|
144
110
|
continue
|
|
145
111
|
|
|
146
|
-
|
|
147
|
-
|
|
112
|
+
# Create the valued query request for this constraint
|
|
113
|
+
valued_query_request = NodeUniquenessQueryRequestValued(
|
|
114
|
+
kind=node_schema.kind,
|
|
115
|
+
unique_valued_paths=valued_paths,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Execute the query
|
|
119
|
+
query = await UniquenessValidationQuery.init(
|
|
120
|
+
db=self.db,
|
|
121
|
+
branch=self.branch,
|
|
122
|
+
at=at,
|
|
123
|
+
query_request=valued_query_request,
|
|
124
|
+
node_ids_to_exclude=[node.get_id()],
|
|
125
|
+
)
|
|
126
|
+
await query.execute(db=self.db)
|
|
127
|
+
|
|
128
|
+
# Get violation nodes from the query results
|
|
129
|
+
violation_nodes = query.get_violation_nodes()
|
|
130
|
+
if not violation_nodes:
|
|
148
131
|
continue
|
|
149
132
|
|
|
133
|
+
# Create violation object
|
|
150
134
|
uniqueness_constraint_fields = []
|
|
151
|
-
for
|
|
152
|
-
if
|
|
153
|
-
uniqueness_constraint_fields.append(
|
|
154
|
-
elif
|
|
155
|
-
uniqueness_constraint_fields.append(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
135
|
+
for valued_path in valued_paths:
|
|
136
|
+
if isinstance(valued_path, QueryRelationshipPathValued):
|
|
137
|
+
uniqueness_constraint_fields.append(valued_path.relationship_schema.name)
|
|
138
|
+
elif isinstance(valued_path, QueryAttributePathValued):
|
|
139
|
+
uniqueness_constraint_fields.append(valued_path.attribute_name)
|
|
140
|
+
|
|
141
|
+
matching_node_ids = {node_id for node_id, _ in violation_nodes}
|
|
142
|
+
if matching_node_ids:
|
|
143
|
+
violations.append(
|
|
144
|
+
UniquenessConstraintViolation(
|
|
145
|
+
nodes_ids=matching_node_ids,
|
|
146
|
+
fields=uniqueness_constraint_fields,
|
|
147
|
+
typ=uniqueness_constraint_path.typ,
|
|
148
|
+
)
|
|
162
149
|
)
|
|
163
|
-
)
|
|
164
150
|
|
|
165
151
|
return violations
|
|
166
152
|
|
|
167
|
-
async def _get_single_schema_violations(
|
|
168
|
-
self,
|
|
169
|
-
node: Node,
|
|
170
|
-
node_schema: MainSchemaTypes,
|
|
171
|
-
at: Timestamp | None = None,
|
|
172
|
-
filters: list[str] | None = None,
|
|
173
|
-
) -> list[UniquenessConstraintViolation]:
|
|
174
|
-
schema_branch = self.db.schema.get_schema_branch(name=self.branch.name)
|
|
175
|
-
|
|
176
|
-
uniqueness_constraint_paths = node_schema.get_unique_constraint_schema_attribute_paths(
|
|
177
|
-
schema_branch=schema_branch
|
|
178
|
-
)
|
|
179
|
-
query_request = await self._build_query_request(
|
|
180
|
-
updated_node=node,
|
|
181
|
-
node_schema=node_schema,
|
|
182
|
-
uniqueness_constraint_paths=uniqueness_constraint_paths,
|
|
183
|
-
filters=filters,
|
|
184
|
-
)
|
|
185
|
-
if not query_request:
|
|
186
|
-
return []
|
|
187
|
-
|
|
188
|
-
query = await NodeUniqueAttributeConstraintQuery.init(
|
|
189
|
-
db=self.db, branch=self.branch, at=at, query_request=query_request, min_count_required=0
|
|
190
|
-
)
|
|
191
|
-
await query.execute(db=self.db)
|
|
192
|
-
return await self._get_violations(
|
|
193
|
-
updated_node=node,
|
|
194
|
-
uniqueness_constraint_paths=uniqueness_constraint_paths,
|
|
195
|
-
query_results=query.get_results(),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
153
|
async def check(self, node: Node, at: Timestamp | None = None, filters: list[str] | None = None) -> None:
|
|
199
154
|
def _frozen_constraints(schema: MainSchemaTypes) -> frozenset[frozenset[str]]:
|
|
200
155
|
if not schema.uniqueness_constraints:
|
|
@@ -218,7 +173,8 @@ class NodeGroupedUniquenessConstraint(NodeConstraintInterface):
|
|
|
218
173
|
if include_node_schema:
|
|
219
174
|
schemas_to_check.append(node_schema)
|
|
220
175
|
|
|
221
|
-
violations = []
|
|
176
|
+
violations: list[UniquenessConstraintViolation] = []
|
|
177
|
+
|
|
222
178
|
for schema in schemas_to_check:
|
|
223
179
|
schema_filters = list(filters) if filters is not None else []
|
|
224
180
|
for attr_schema in schema.attributes:
|
|
@@ -59,6 +59,23 @@ class NodeUniquenessQueryRequest(BaseModel):
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
class QueryRelationshipPathValued(BaseModel):
|
|
63
|
+
relationship_schema: RelationshipSchema
|
|
64
|
+
peer_id: str | None
|
|
65
|
+
attribute_name: str | None
|
|
66
|
+
attribute_value: str | bool | int | float | None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class QueryAttributePathValued(BaseModel):
|
|
70
|
+
attribute_name: str
|
|
71
|
+
value: str | bool | int | float
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class NodeUniquenessQueryRequestValued(BaseModel):
|
|
75
|
+
kind: str
|
|
76
|
+
unique_valued_paths: list[QueryAttributePathValued | QueryRelationshipPathValued]
|
|
77
|
+
|
|
78
|
+
|
|
62
79
|
class NonUniqueRelatedAttribute(BaseModel):
|
|
63
80
|
relationship: RelationshipSchema
|
|
64
81
|
attribute_name: str
|
|
@@ -5,10 +5,12 @@ from typing import TYPE_CHECKING, Any
|
|
|
5
5
|
from infrahub.core.constants.relationship_label import RELATIONSHIP_TO_VALUE_LABEL
|
|
6
6
|
from infrahub.core.query import Query, QueryType
|
|
7
7
|
|
|
8
|
+
from .model import QueryAttributePathValued, QueryRelationshipPathValued
|
|
9
|
+
|
|
8
10
|
if TYPE_CHECKING:
|
|
9
11
|
from infrahub.database import InfrahubDatabase
|
|
10
12
|
|
|
11
|
-
from .model import NodeUniquenessQueryRequest
|
|
13
|
+
from .model import NodeUniquenessQueryRequest, NodeUniquenessQueryRequestValued
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class NodeUniqueAttributeConstraintQuery(Query):
|
|
@@ -244,3 +246,212 @@ class NodeUniqueAttributeConstraintQuery(Query):
|
|
|
244
246
|
"attr_value",
|
|
245
247
|
"relationship_identifier",
|
|
246
248
|
]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class UniquenessValidationQuery(Query):
|
|
252
|
+
name = "uniqueness_constraint_validation"
|
|
253
|
+
type = QueryType.READ
|
|
254
|
+
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
query_request: NodeUniquenessQueryRequestValued,
|
|
258
|
+
node_ids_to_exclude: list[str] | None = None,
|
|
259
|
+
**kwargs: Any,
|
|
260
|
+
) -> None:
|
|
261
|
+
self.query_request = query_request
|
|
262
|
+
self.node_ids_to_exclude = node_ids_to_exclude
|
|
263
|
+
super().__init__(**kwargs)
|
|
264
|
+
|
|
265
|
+
def _build_attr_subquery(
|
|
266
|
+
self, node_kind: str, attr_path: QueryAttributePathValued, index: int, branch_filter: str, is_first_query: bool
|
|
267
|
+
) -> tuple[str, dict[str, str | int | float | bool]]:
|
|
268
|
+
attr_name_var = f"attr_name_{index}"
|
|
269
|
+
attr_value_var = f"attr_value_{index}"
|
|
270
|
+
if is_first_query:
|
|
271
|
+
first_query_filter = "WHERE $node_ids_to_exclude IS NULL OR NOT node.uuid IN $node_ids_to_exclude"
|
|
272
|
+
else:
|
|
273
|
+
first_query_filter = ""
|
|
274
|
+
attribute_query = """
|
|
275
|
+
MATCH (node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})
|
|
276
|
+
%(first_query_filter)s
|
|
277
|
+
WITH DISTINCT node
|
|
278
|
+
CALL (node) {
|
|
279
|
+
MATCH (node)-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})
|
|
280
|
+
WHERE %(branch_filter)s
|
|
281
|
+
WITH attr, r.status = "active" AS is_active
|
|
282
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
283
|
+
WITH attr, is_active
|
|
284
|
+
LIMIT 1
|
|
285
|
+
WITH attr, is_active
|
|
286
|
+
WHERE is_active = TRUE
|
|
287
|
+
MATCH (attr)-[r:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})
|
|
288
|
+
WHERE %(branch_filter)s
|
|
289
|
+
WITH r
|
|
290
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
291
|
+
LIMIT 1
|
|
292
|
+
WITH r
|
|
293
|
+
WHERE r.status = "active"
|
|
294
|
+
RETURN 1 AS is_match_%(index)s
|
|
295
|
+
}
|
|
296
|
+
""" % {
|
|
297
|
+
"first_query_filter": first_query_filter,
|
|
298
|
+
"node_kind": node_kind,
|
|
299
|
+
"attr_name_var": attr_name_var,
|
|
300
|
+
"attr_value_var": attr_value_var,
|
|
301
|
+
"branch_filter": branch_filter,
|
|
302
|
+
"index": index,
|
|
303
|
+
}
|
|
304
|
+
params: dict[str, str | int | float | bool] = {
|
|
305
|
+
attr_name_var: attr_path.attribute_name,
|
|
306
|
+
attr_value_var: attr_path.value,
|
|
307
|
+
}
|
|
308
|
+
return attribute_query, params
|
|
309
|
+
|
|
310
|
+
def _build_rel_subquery(
|
|
311
|
+
self,
|
|
312
|
+
node_kind: str,
|
|
313
|
+
rel_path: QueryRelationshipPathValued,
|
|
314
|
+
index: int,
|
|
315
|
+
branch_filter: str,
|
|
316
|
+
is_first_query: bool,
|
|
317
|
+
) -> tuple[str, dict[str, str | int | float | bool]]:
|
|
318
|
+
params: dict[str, str | int | float | bool] = {}
|
|
319
|
+
rel_attr_query = ""
|
|
320
|
+
rel_attr_match = ""
|
|
321
|
+
if rel_path.attribute_name and rel_path.attribute_value:
|
|
322
|
+
attr_name_var = f"attr_name_{index}"
|
|
323
|
+
attr_value_var = f"attr_value_{index}"
|
|
324
|
+
rel_attr_query = """
|
|
325
|
+
MATCH (peer)-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})
|
|
326
|
+
WHERE %(branch_filter)s
|
|
327
|
+
WITH attr, r.status = "active" AS is_active
|
|
328
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
329
|
+
WITH attr, is_active
|
|
330
|
+
LIMIT 1
|
|
331
|
+
WITH attr, is_active
|
|
332
|
+
WHERE is_active = TRUE
|
|
333
|
+
MATCH (attr)-[r:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})
|
|
334
|
+
WHERE %(branch_filter)s
|
|
335
|
+
WITH r
|
|
336
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
337
|
+
LIMIT 1
|
|
338
|
+
WITH r
|
|
339
|
+
WHERE r.status = "active"
|
|
340
|
+
""" % {"attr_name_var": attr_name_var, "attr_value_var": attr_value_var, "branch_filter": branch_filter}
|
|
341
|
+
rel_attr_match = (
|
|
342
|
+
"-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})"
|
|
343
|
+
% {
|
|
344
|
+
"attr_name_var": attr_name_var,
|
|
345
|
+
"attr_value_var": attr_value_var,
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
params[attr_name_var] = rel_path.attribute_name
|
|
349
|
+
params[attr_value_var] = rel_path.attribute_value
|
|
350
|
+
query_arrows = rel_path.relationship_schema.get_query_arrows()
|
|
351
|
+
rel_name_var = f"rel_name_{index}"
|
|
352
|
+
# long path MATCH is required to hit an index on the peer or AttributeValue of the peer
|
|
353
|
+
first_match = (
|
|
354
|
+
"MATCH (node:%(node_kind)s)%(lstart)s[:IS_RELATED]%(lend)s(:Relationship {name: $%(rel_name_var)s})%(rstart)s[:IS_RELATED]%(rend)s"
|
|
355
|
+
% {
|
|
356
|
+
"node_kind": node_kind,
|
|
357
|
+
"lstart": query_arrows.left.start,
|
|
358
|
+
"lend": query_arrows.left.end,
|
|
359
|
+
"rstart": query_arrows.right.start,
|
|
360
|
+
"rend": query_arrows.right.end,
|
|
361
|
+
"rel_name_var": rel_name_var,
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
peer_where = f"WHERE {branch_filter}"
|
|
365
|
+
if rel_path.peer_id:
|
|
366
|
+
peer_id_var = f"peer_id_{index}"
|
|
367
|
+
peer_where += f" AND peer.uuid = ${peer_id_var}"
|
|
368
|
+
params[peer_id_var] = rel_path.peer_id
|
|
369
|
+
first_match += "(:Node {uuid: $%(peer_id_var)s})" % {"peer_id_var": peer_id_var}
|
|
370
|
+
else:
|
|
371
|
+
peer_where += " AND peer.uuid <> node.uuid"
|
|
372
|
+
first_match += "(:Node)"
|
|
373
|
+
if rel_attr_match:
|
|
374
|
+
first_match += rel_attr_match
|
|
375
|
+
if is_first_query:
|
|
376
|
+
first_query_filter = "WHERE $node_ids_to_exclude IS NULL OR NOT node.uuid IN $node_ids_to_exclude"
|
|
377
|
+
else:
|
|
378
|
+
first_query_filter = ""
|
|
379
|
+
relationship_query = f"""
|
|
380
|
+
{first_match}
|
|
381
|
+
{first_query_filter}
|
|
382
|
+
WITH DISTINCT node
|
|
383
|
+
"""
|
|
384
|
+
relationship_query += """
|
|
385
|
+
CALL (node) {
|
|
386
|
+
MATCH (node)%(lstart)s[r:IS_RELATED]%(lend)s(rel:Relationship {name: $%(rel_name_var)s})
|
|
387
|
+
WHERE %(branch_filter)s
|
|
388
|
+
WITH rel, r.status = "active" AS is_active
|
|
389
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
390
|
+
WITH rel, is_active
|
|
391
|
+
LIMIT 1
|
|
392
|
+
WITH rel, is_active
|
|
393
|
+
WHERE is_active = TRUE
|
|
394
|
+
MATCH (rel)%(rstart)s[r:IS_RELATED]%(rend)s(peer:Node)
|
|
395
|
+
%(peer_where)s
|
|
396
|
+
WITH peer, r.status = "active" AS is_active
|
|
397
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
398
|
+
WITH peer, is_active
|
|
399
|
+
LIMIT 1
|
|
400
|
+
WITH peer, is_active
|
|
401
|
+
WHERE is_active = TRUE
|
|
402
|
+
%(rel_attr_query)s
|
|
403
|
+
RETURN 1 AS is_match_%(index)s
|
|
404
|
+
LIMIT 1
|
|
405
|
+
}
|
|
406
|
+
""" % {
|
|
407
|
+
"rel_name_var": rel_name_var,
|
|
408
|
+
"lstart": query_arrows.left.start,
|
|
409
|
+
"lend": query_arrows.left.end,
|
|
410
|
+
"rstart": query_arrows.right.start,
|
|
411
|
+
"rend": query_arrows.right.end,
|
|
412
|
+
"peer_where": peer_where,
|
|
413
|
+
"rel_attr_query": rel_attr_query,
|
|
414
|
+
"branch_filter": branch_filter,
|
|
415
|
+
"index": index,
|
|
416
|
+
}
|
|
417
|
+
params[rel_name_var] = rel_path.relationship_schema.get_identifier()
|
|
418
|
+
return relationship_query, params
|
|
419
|
+
|
|
420
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
421
|
+
self.params["node_ids_to_exclude"] = self.node_ids_to_exclude
|
|
422
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string(), is_isolated=False)
|
|
423
|
+
self.params.update(branch_params)
|
|
424
|
+
|
|
425
|
+
subqueries = []
|
|
426
|
+
for index, schema_path in enumerate(self.query_request.unique_valued_paths):
|
|
427
|
+
is_first_query = index == 0
|
|
428
|
+
if isinstance(schema_path, QueryAttributePathValued):
|
|
429
|
+
subquery, params = self._build_attr_subquery(
|
|
430
|
+
node_kind=self.query_request.kind,
|
|
431
|
+
attr_path=schema_path,
|
|
432
|
+
index=index,
|
|
433
|
+
branch_filter=branch_filter,
|
|
434
|
+
is_first_query=is_first_query,
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
subquery, params = self._build_rel_subquery(
|
|
438
|
+
node_kind=self.query_request.kind,
|
|
439
|
+
rel_path=schema_path,
|
|
440
|
+
index=index,
|
|
441
|
+
branch_filter=branch_filter,
|
|
442
|
+
is_first_query=is_first_query,
|
|
443
|
+
)
|
|
444
|
+
subqueries.append(subquery)
|
|
445
|
+
self.params.update(params)
|
|
446
|
+
|
|
447
|
+
full_query = "\n".join(subqueries)
|
|
448
|
+
self.add_to_query(full_query)
|
|
449
|
+
self.return_labels = ["node.uuid AS node_uuid", "node.kind AS node_kind"]
|
|
450
|
+
|
|
451
|
+
def get_violation_nodes(self) -> list[tuple[str, str]]:
|
|
452
|
+
violation_tuples = []
|
|
453
|
+
for result in self.results:
|
|
454
|
+
violation_tuples.append(
|
|
455
|
+
(result.get_as_type("node_uuid", return_type=str), result.get_as_type("node_kind", return_type=str))
|
|
456
|
+
)
|
|
457
|
+
return violation_tuples
|
|
@@ -25,7 +25,7 @@ from infrahub.core.timestamp import Timestamp
|
|
|
25
25
|
from infrahub.database import retry_db_transaction
|
|
26
26
|
from infrahub.dependencies.registry import get_component_registry
|
|
27
27
|
from infrahub.events.generator import generate_node_mutation_events
|
|
28
|
-
from infrahub.exceptions import HFIDViolatedError, InitializationError
|
|
28
|
+
from infrahub.exceptions import HFIDViolatedError, InitializationError, NodeNotFoundError
|
|
29
29
|
from infrahub.graphql.context import apply_external_context
|
|
30
30
|
from infrahub.lock import InfrahubMultiLock, build_object_lock_name
|
|
31
31
|
from infrahub.log import get_log_data, get_logger
|
|
@@ -384,7 +384,23 @@ class InfrahubMutationMixin:
|
|
|
384
384
|
if len(exc.matching_nodes_ids) > 1:
|
|
385
385
|
raise RuntimeError(f"Multiple {schema_name} nodes have the same hfid") from exc
|
|
386
386
|
node_id = list(exc.matching_nodes_ids)[0]
|
|
387
|
-
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
node = await NodeManager.get_one(
|
|
390
|
+
db=db, id=node_id, kind=schema_name, branch=branch, raise_on_error=True
|
|
391
|
+
)
|
|
392
|
+
except NodeNotFoundError as exc:
|
|
393
|
+
if branch.is_default:
|
|
394
|
+
raise
|
|
395
|
+
raise NodeNotFoundError(
|
|
396
|
+
node_type=exc.node_type,
|
|
397
|
+
identifier=exc.identifier,
|
|
398
|
+
branch_name=branch.name,
|
|
399
|
+
message=(
|
|
400
|
+
f"Node {exc.identifier} / {exc.node_type} uses this human-friendly ID, but does not exist on"
|
|
401
|
+
f" this branch. Please rebase this branch to access {exc.identifier} / {exc.node_type}"
|
|
402
|
+
),
|
|
403
|
+
) from exc
|
|
388
404
|
updated_obj, mutation = await cls._call_mutate_update(
|
|
389
405
|
info=info,
|
|
390
406
|
data=data,
|
infrahub_sdk/node/node.py
CHANGED
|
@@ -507,11 +507,17 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
507
507
|
|
|
508
508
|
if rel_schema.cardinality == "one":
|
|
509
509
|
if isinstance(rel_data, RelatedNode):
|
|
510
|
-
peer_id_data: dict[str, Any] = {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
510
|
+
peer_id_data: dict[str, Any] = {
|
|
511
|
+
key: value
|
|
512
|
+
for key, value in (
|
|
513
|
+
("id", rel_data.id),
|
|
514
|
+
("hfid", rel_data.hfid),
|
|
515
|
+
("__typename", rel_data.typename),
|
|
516
|
+
("kind", rel_data.kind),
|
|
517
|
+
("display_label", rel_data.display_label),
|
|
518
|
+
)
|
|
519
|
+
if value is not None
|
|
520
|
+
}
|
|
515
521
|
if peer_id_data:
|
|
516
522
|
rel_data = peer_id_data
|
|
517
523
|
else:
|
|
@@ -1090,11 +1096,17 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1090
1096
|
|
|
1091
1097
|
if rel_schema.cardinality == "one":
|
|
1092
1098
|
if isinstance(rel_data, RelatedNodeSync):
|
|
1093
|
-
peer_id_data: dict[str, Any] = {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1099
|
+
peer_id_data: dict[str, Any] = {
|
|
1100
|
+
key: value
|
|
1101
|
+
for key, value in (
|
|
1102
|
+
("id", rel_data.id),
|
|
1103
|
+
("hfid", rel_data.hfid),
|
|
1104
|
+
("__typename", rel_data.typename),
|
|
1105
|
+
("kind", rel_data.kind),
|
|
1106
|
+
("display_label", rel_data.display_label),
|
|
1107
|
+
)
|
|
1108
|
+
if value is not None
|
|
1109
|
+
}
|
|
1098
1110
|
if peer_id_data:
|
|
1099
1111
|
rel_data = peer_id_data
|
|
1100
1112
|
else:
|
|
@@ -39,6 +39,7 @@ class RelatedNodeBase:
|
|
|
39
39
|
self._hfid: list[str] | None = None
|
|
40
40
|
self._display_label: str | None = None
|
|
41
41
|
self._typename: str | None = None
|
|
42
|
+
self._kind: str | None = None
|
|
42
43
|
|
|
43
44
|
if isinstance(data, (CoreNodeBase)):
|
|
44
45
|
self._peer = data
|
|
@@ -118,6 +119,12 @@ class RelatedNodeBase:
|
|
|
118
119
|
return self._peer.typename
|
|
119
120
|
return self._typename
|
|
120
121
|
|
|
122
|
+
@property
|
|
123
|
+
def kind(self) -> str | None:
|
|
124
|
+
if self._peer:
|
|
125
|
+
return self._peer.get_kind()
|
|
126
|
+
return self._kind
|
|
127
|
+
|
|
121
128
|
def _generate_input_data(self, allocate_from_pool: bool = False) -> dict[str, Any]:
|
|
122
129
|
data: dict[str, Any] = {}
|
|
123
130
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: infrahub-server
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: Infrahub is taking a new approach to Infrastructure Management by providing a new generation of datastore to organize and control all the data that defines how an infrastructure should run.
|
|
5
5
|
License: AGPL-3.0-only
|
|
6
6
|
Author: OpsMill
|
|
@@ -80,7 +80,7 @@ infrahub/core/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
80
80
|
infrahub/core/diff/artifacts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
81
|
infrahub/core/diff/artifacts/calculator.py,sha256=qk1DspB3bkKeWJFesLbmziCALVnbRadjrez1kn_IZWU,4435
|
|
82
82
|
infrahub/core/diff/branch_differ.py,sha256=62TRs3tGb4brQqCaVoI2iMIiPnny3_0_e9j-Mq-AXx4,7752
|
|
83
|
-
infrahub/core/diff/calculator.py,sha256=
|
|
83
|
+
infrahub/core/diff/calculator.py,sha256=1ure9OGmoopCLMyjIK4r6zmLJ4eAswEI7yB0WgOkgx0,10088
|
|
84
84
|
infrahub/core/diff/combiner.py,sha256=qL4WQsphB2sVnncgskSG_QcJBqBHjaK0vWU_apeTn-E,23508
|
|
85
85
|
infrahub/core/diff/conflict_transferer.py,sha256=LZCuS9Dbr4yBf-bd3RF-9cPnaOvVWiU3KBmmwxbRZl0,3968
|
|
86
86
|
infrahub/core/diff/conflicts_enricher.py,sha256=x6qiZOXO2A3BQ2Fm78apJ4WA7HLzPO84JomJfcyuyDg,12552
|
|
@@ -127,7 +127,7 @@ infrahub/core/diff/query/save.py,sha256=xBKWpWfRWfaP7g523xKMK82ogg0AfVQTTMeyz8oe
|
|
|
127
127
|
infrahub/core/diff/query/summary_counts_enricher.py,sha256=HuMeQfa2Ce0qFmGTSfUV-LncauEsBDhdDcs1QpZOETA,9957
|
|
128
128
|
infrahub/core/diff/query/time_range_query.py,sha256=Xv9_Y_UJ45UsqfxosoAxXMY47-EpO6fHNIqdwFpysBQ,2976
|
|
129
129
|
infrahub/core/diff/query/update_conflict_query.py,sha256=kQkFazz88wnApr8UU_qb0ruzhmrhWiqhbErukSAMhLA,1212
|
|
130
|
-
infrahub/core/diff/query_parser.py,sha256
|
|
130
|
+
infrahub/core/diff/query_parser.py,sha256=-uz4Fd2briFx7447UGii4RBSJjP_LnV0WiEBOjh_chc,36536
|
|
131
131
|
infrahub/core/diff/repository/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
132
|
infrahub/core/diff/repository/deserializer.py,sha256=bhN9ao8HxqKyRz273QGLNV9z9_SS4EQnM9JoY5ptx78,21337
|
|
133
133
|
infrahub/core/diff/repository/repository.py,sha256=x3QP9VmBVYBOVtf3IZUyzXqCd8sSfmHTqVoYlAOdGao,26006
|
|
@@ -206,7 +206,7 @@ infrahub/core/node/__init__.py,sha256=4of1tA26-EV8FOubg-rhBFU9wkJ0BBipePnnvx1vi_
|
|
|
206
206
|
infrahub/core/node/base.py,sha256=BAowVRCK_WC50yXym1kCyUppJDJnrODGU5uoj1s0Yd4,2564
|
|
207
207
|
infrahub/core/node/constraints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
208
208
|
infrahub/core/node/constraints/attribute_uniqueness.py,sha256=9MThTmuqZ7RgK71ZZARlw1k1x3ARn1U67g2_Gatd6rE,2099
|
|
209
|
-
infrahub/core/node/constraints/grouped_uniqueness.py,sha256=
|
|
209
|
+
infrahub/core/node/constraints/grouped_uniqueness.py,sha256=F5pmnXVuQNlVmdZY5FRxSGK4gGi1BK1IRgw4emCTlLw,9506
|
|
210
210
|
infrahub/core/node/constraints/interface.py,sha256=fwB32pRLxteQyKRArqekQ0RXlrDkyzp7Vmq03vSpUEo,291
|
|
211
211
|
infrahub/core/node/create.py,sha256=1mAFaMLqRmuONIwL549JQLFbOpEbP3rBQEb1D2VArcc,7752
|
|
212
212
|
infrahub/core/node/delete_validator.py,sha256=mj_HQXkTeP_A3po65-R5bCJnDM9CmFFmcUQIxwPlofc,10559
|
|
@@ -334,8 +334,8 @@ infrahub/core/validators/tasks.py,sha256=oaV1rOFiGOkbZYjpK2d5UTj_LFJAghSMKbO7w5f
|
|
|
334
334
|
infrahub/core/validators/uniqueness/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
335
335
|
infrahub/core/validators/uniqueness/checker.py,sha256=RpiLpIjbdkwwjivry-vjEkVim6ZoC-t2H5Bal7ngASQ,10375
|
|
336
336
|
infrahub/core/validators/uniqueness/index.py,sha256=Jw1o-UVinQquNduZ5vCCzt8GUfIEdVzBo-1XyRti8F8,5068
|
|
337
|
-
infrahub/core/validators/uniqueness/model.py,sha256=
|
|
338
|
-
infrahub/core/validators/uniqueness/query.py,sha256=
|
|
337
|
+
infrahub/core/validators/uniqueness/model.py,sha256=3MXrE9lLMI1B2TXxMS2Eb59uOI_Q4havuluoI8fy4EE,5593
|
|
338
|
+
infrahub/core/validators/uniqueness/query.py,sha256=3qJfoxwau3303BCkjJwPngotQYjTRuewqTQvcPzsICE,20103
|
|
339
339
|
infrahub/database/__init__.py,sha256=sRME_Cm74b5MsKyqMLRpAv3E38Vw2H1yv20JjSBb-Vg,20836
|
|
340
340
|
infrahub/database/index.py,sha256=ATLqw9Grqbq7haGGm14VSEPmcPniid--YATiffo4sA0,1676
|
|
341
341
|
infrahub/database/memgraph.py,sha256=Fg3xHP9s0MiBBmMvcEmsJvuIUSq8U_XCS362HDE9d1s,1742
|
|
@@ -471,7 +471,7 @@ infrahub/graphql/mutations/diff_conflict.py,sha256=JngQfyKXCVlmtlqQ_VyabmrOEDOEK
|
|
|
471
471
|
infrahub/graphql/mutations/generator.py,sha256=Ulw4whZm8Gc8lJjwfUFoFSsR0cOUliFKl87Oca4B9O0,3579
|
|
472
472
|
infrahub/graphql/mutations/graphql_query.py,sha256=mp_O2byChneCihUrEAFEiIAgJ1gW9WrgtwPetUQmkJw,3562
|
|
473
473
|
infrahub/graphql/mutations/ipam.py,sha256=wIN8OcTNCHVy32YgatWZi2Of-snFYBd4wlxOAJvE-AY,15961
|
|
474
|
-
infrahub/graphql/mutations/main.py,sha256=
|
|
474
|
+
infrahub/graphql/mutations/main.py,sha256=GgETTp-KKyVTnvD8GO7hp1oWIq-eOJ7E_WL-wXUP5Us,20392
|
|
475
475
|
infrahub/graphql/mutations/menu.py,sha256=u2UbOA-TFDRcZRGFkgYTmpGxN2IAUgOvJXd7SnsufyI,3708
|
|
476
476
|
infrahub/graphql/mutations/models.py,sha256=ilkSLr8OxVO9v3Ra_uDyUTJT9qPOmdPMqQbuWIydJMo,264
|
|
477
477
|
infrahub/graphql/mutations/node_getter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -719,10 +719,10 @@ infrahub_sdk/jinja2.py,sha256=lTfV9E_P5gApaX6RW9M8U8oixQi-0H3U8wcs8fdGVaU,1150
|
|
|
719
719
|
infrahub_sdk/node/__init__.py,sha256=clAUZ9lNVPFguelR5Sg9PzklAZruTKEm2xk-BaO68l8,1262
|
|
720
720
|
infrahub_sdk/node/attribute.py,sha256=oEY1qxip8ETEx9Q33NhSQo013zmzrmpVIFzSkEMUY8M,4547
|
|
721
721
|
infrahub_sdk/node/constants.py,sha256=TJO4uxvv7sc3FjoLdQdV7Ccymqz8AqxDenARst8awb4,775
|
|
722
|
-
infrahub_sdk/node/node.py,sha256=
|
|
722
|
+
infrahub_sdk/node/node.py,sha256=34rIDO7ZdA8uJpsuBbPDDZ5GSUKkL1nlnFBvoM5bDRo,70674
|
|
723
723
|
infrahub_sdk/node/parsers.py,sha256=sLDdT6neoYSZIjOCmq8Bgd0LK8FFoasjvJLuSz0whSU,543
|
|
724
724
|
infrahub_sdk/node/property.py,sha256=8Mjkc8bp3kLlHyllwxDJlpJTuOA1ciMgY8mtH3dFVLM,728
|
|
725
|
-
infrahub_sdk/node/related_node.py,sha256=
|
|
725
|
+
infrahub_sdk/node/related_node.py,sha256=fPMnZ83OZnnbimaPC14MdE3lR-kumAA6hbOhRlo1gms,10093
|
|
726
726
|
infrahub_sdk/node/relationship.py,sha256=ax9BfYFEfzvUmVxiC1RrhtnpV9ZPuuvQFN_DNRGUHLU,11911
|
|
727
727
|
infrahub_sdk/object_store.py,sha256=d-EDnxPpw_7BsbjbGbH50rjt-1-Ojj2zNrhFansP5hA,4299
|
|
728
728
|
infrahub_sdk/operation.py,sha256=hsbZSjLbLsqvjZg5R5x_bOxxlteXJAk0fQy3mLrZhn4,2730
|
|
@@ -801,8 +801,8 @@ infrahub_testcontainers/models.py,sha256=ASYyvl7d_WQz_i7y8-3iab9hwwmCl3OCJavqVbe
|
|
|
801
801
|
infrahub_testcontainers/performance_test.py,sha256=hvwiy6tc_lWniYqGkqfOXVGAmA_IV15VOZqbiD9ezno,6149
|
|
802
802
|
infrahub_testcontainers/plugin.py,sha256=I3RuZQ0dARyKHuqCf0y1Yj731P2Mwf3BJUehRJKeWrs,5645
|
|
803
803
|
infrahub_testcontainers/prometheus.yml,sha256=610xQEyj3xuVJMzPkC4m1fRnCrjGpiRBrXA2ytCLa54,599
|
|
804
|
-
infrahub_server-1.3.
|
|
805
|
-
infrahub_server-1.3.
|
|
806
|
-
infrahub_server-1.3.
|
|
807
|
-
infrahub_server-1.3.
|
|
808
|
-
infrahub_server-1.3.
|
|
804
|
+
infrahub_server-1.3.2.dist-info/LICENSE.txt,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
|
|
805
|
+
infrahub_server-1.3.2.dist-info/METADATA,sha256=ryVIpJ8RWv_Brr02affO2F-ZGjjAuz0ahV-OGLaIr7U,8205
|
|
806
|
+
infrahub_server-1.3.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
807
|
+
infrahub_server-1.3.2.dist-info/entry_points.txt,sha256=UXIeFWDsrV-4IllNvUEd6KieYGzQfn9paga2YyABOQI,393
|
|
808
|
+
infrahub_server-1.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|