infrahub-server 1.2.9rc0__py3-none-any.whl → 1.3.0a0__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/actions/constants.py +86 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +241 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +382 -0
- infrahub/actions/tasks.py +126 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/cli/db.py +1 -2
- infrahub/computed_attribute/models.py +13 -0
- infrahub/computed_attribute/tasks.py +48 -26
- infrahub/config.py +9 -0
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +53 -14
- infrahub/core/branch/models.py +8 -9
- infrahub/core/branch/tasks.py +0 -2
- infrahub/core/constants/infrahubkind.py +8 -0
- infrahub/core/constraint/node/runner.py +1 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +122 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/calculator.py +65 -11
- infrahub/core/diff/combiner.py +38 -31
- infrahub/core/diff/coordinator.py +44 -28
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/hierarchy.py +36 -27
- infrahub/core/diff/ipam_diff_parser.py +5 -4
- infrahub/core/diff/merger/merger.py +46 -16
- infrahub/core/diff/merger/serializer.py +1 -0
- infrahub/core/diff/model/field_specifiers_map.py +64 -0
- infrahub/core/diff/model/path.py +58 -58
- infrahub/core/diff/parent_node_adder.py +14 -16
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/drop_nodes.py +42 -0
- infrahub/core/diff/query/field_specifiers.py +8 -7
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/filters.py +15 -1
- infrahub/core/diff/query/merge.py +284 -101
- infrahub/core/diff/query/save.py +26 -34
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query_parser.py +55 -65
- infrahub/core/diff/repository/deserializer.py +38 -24
- infrahub/core/diff/repository/repository.py +31 -12
- infrahub/core/diff/tasks.py +3 -3
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
- infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
- infrahub/core/migrations/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +3 -6
- infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
- infrahub/core/migrations/query/node_duplicate.py +3 -6
- infrahub/core/migrations/query/relationship_duplicate.py +3 -6
- infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
- infrahub/core/migrations/schema/node_remove.py +3 -6
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +18 -4
- infrahub/core/node/create.py +211 -0
- infrahub/core/protocols.py +51 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/branch.py +27 -17
- infrahub/core/query/diff.py +186 -81
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +65 -49
- infrahub/core/query/relationship.py +156 -58
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/subquery.py +4 -6
- infrahub/core/relationship/model.py +4 -1
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +36 -0
- infrahub/core/schema/attribute_schema.py +83 -8
- infrahub/core/schema/basenode_schema.py +25 -1
- infrahub/core/schema/definitions/core/__init__.py +21 -0
- infrahub/core/schema/definitions/internal.py +13 -3
- infrahub/core/schema/generated/attribute_schema.py +9 -3
- infrahub/core/schema/schema_branch.py +15 -7
- infrahub/core/validators/__init__.py +5 -1
- infrahub/core/validators/attribute/choices.py +1 -2
- infrahub/core/validators/attribute/enum.py +1 -2
- infrahub/core/validators/attribute/kind.py +1 -2
- infrahub/core/validators/attribute/length.py +13 -6
- infrahub/core/validators/attribute/optional.py +1 -2
- infrahub/core/validators/attribute/regex.py +5 -5
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +7 -0
- infrahub/core/validators/node/hierarchy.py +3 -6
- infrahub/core/validators/query.py +1 -3
- infrahub/core/validators/relationship/count.py +6 -12
- infrahub/core/validators/relationship/optional.py +2 -4
- infrahub/core/validators/relationship/peer.py +3 -8
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +12 -9
- infrahub/database/__init__.py +1 -3
- infrahub/events/group_action.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/app.py +1 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/peers.py +1 -1
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +62 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +21 -18
- infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
- infrahub/graphql/queries/diff/tree.py +2 -1
- infrahub/graphql/queries/relationship.py +1 -1
- infrahub/graphql/resolvers/many_relationship.py +4 -4
- infrahub/graphql/resolvers/resolver.py +4 -4
- infrahub/graphql/resolvers/single_relationship.py +2 -2
- infrahub/graphql/schema.py +6 -0
- infrahub/graphql/subscription/graphql_query.py +2 -2
- infrahub/graphql/types/branch.py +1 -1
- infrahub/menu/menu.py +31 -0
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +1 -1
- infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +5 -10
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +907 -30
- infrahub/task_manager/models.py +10 -6
- infrahub/telemetry/database.py +1 -1
- infrahub/telemetry/tasks.py +1 -1
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +29 -3
- infrahub/trigger/setup.py +51 -15
- infrahub/trigger/tasks.py +4 -5
- infrahub/types.py +1 -1
- infrahub/webhook/models.py +2 -1
- infrahub/workflows/catalogue.py +85 -0
- infrahub/workflows/initialization.py +1 -3
- infrahub_sdk/timestamp.py +2 -2
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +4 -4
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +153 -146
- infrahub_testcontainers/container.py +0 -1
- infrahub_testcontainers/docker-compose.test.yml +4 -4
- infrahub_testcontainers/helpers.py +8 -2
- infrahub_testcontainers/performance_test.py +6 -3
- infrahub/message_bus/messages/check_generator_run.py +0 -26
- infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
- infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
- infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
- infrahub/message_bus/operations/check/__init__.py +0 -3
- infrahub/message_bus/operations/check/generator.py +0 -156
- infrahub/message_bus/operations/finalize/__init__.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +0 -133
- infrahub/message_bus/operations/requests/__init__.py +0 -9
- infrahub/message_bus/operations/requests/generator_definition.py +0 -140
- infrahub/message_bus/operations/requests/proposed_change.py +0 -629
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -73,9 +73,18 @@ class RelationshipPeerData:
|
|
|
73
73
|
source_id: UUID
|
|
74
74
|
"""UUID of the Source Node."""
|
|
75
75
|
|
|
76
|
+
source_db_id: str
|
|
77
|
+
"""Internal DB ID of the Source Node."""
|
|
78
|
+
|
|
79
|
+
source_kind: str
|
|
80
|
+
"""Kind of the Source Node."""
|
|
81
|
+
|
|
76
82
|
peer_id: UUID
|
|
77
83
|
"""UUID of the Peer Node."""
|
|
78
84
|
|
|
85
|
+
peer_db_id: str
|
|
86
|
+
"""Internal DB ID of the Peer Node."""
|
|
87
|
+
|
|
79
88
|
peer_kind: str
|
|
80
89
|
"""Kind of the Peer Node."""
|
|
81
90
|
|
|
@@ -85,9 +94,6 @@ class RelationshipPeerData:
|
|
|
85
94
|
rel_node_id: UUID | None = None
|
|
86
95
|
"""UUID of the Relationship Node."""
|
|
87
96
|
|
|
88
|
-
peer_db_id: str | None = None
|
|
89
|
-
"""Internal DB ID of the Peer Node."""
|
|
90
|
-
|
|
91
97
|
rel_node_db_id: str | None = None
|
|
92
98
|
"""Internal DB ID of the Relationship Node."""
|
|
93
99
|
|
|
@@ -196,6 +202,63 @@ class RelationshipQuery(Query):
|
|
|
196
202
|
rel_prop_dict["hierarchy"] = self.schema.hierarchical
|
|
197
203
|
return rel_prop_dict
|
|
198
204
|
|
|
205
|
+
def add_source_match_to_query(self, source_branch: Branch) -> None:
|
|
206
|
+
self.params["source_id"] = self.source_id or self.source.get_id()
|
|
207
|
+
if source_branch.is_global or source_branch.is_default:
|
|
208
|
+
source_query_match = """
|
|
209
|
+
MATCH (s:Node { uuid: $source_id })
|
|
210
|
+
OPTIONAL MATCH (s)-[delete_edge:IS_PART_OF {status: "deleted", branch: $source_branch}]->(:Root)
|
|
211
|
+
WHERE delete_edge.from <= $at
|
|
212
|
+
WITH *, s WHERE delete_edge IS NULL
|
|
213
|
+
"""
|
|
214
|
+
self.params["source_branch"] = source_branch.name
|
|
215
|
+
source_filter, source_filter_params = source_branch.get_query_filter_path(
|
|
216
|
+
at=self.at, variable_name="r", params_prefix="src_"
|
|
217
|
+
)
|
|
218
|
+
source_query_match = """
|
|
219
|
+
MATCH (s:Node { uuid: $source_id })
|
|
220
|
+
CALL {
|
|
221
|
+
WITH s
|
|
222
|
+
MATCH (s)-[r:IS_PART_OF]->(:Root)
|
|
223
|
+
WHERE %(source_filter)s
|
|
224
|
+
RETURN r.status = "active" AS s_is_active
|
|
225
|
+
ORDER BY r.from DESC
|
|
226
|
+
LIMIT 1
|
|
227
|
+
}
|
|
228
|
+
WITH *, s WHERE s_is_active = TRUE
|
|
229
|
+
""" % {"source_filter": source_filter}
|
|
230
|
+
self.params.update(source_filter_params)
|
|
231
|
+
self.add_to_query(source_query_match)
|
|
232
|
+
|
|
233
|
+
def add_dest_match_to_query(self, destination_branch: Branch, destination_id: str) -> None:
|
|
234
|
+
self.params["destination_id"] = destination_id
|
|
235
|
+
if destination_branch.is_global or destination_branch.is_default:
|
|
236
|
+
destination_query_match = """
|
|
237
|
+
MATCH (d:Node { uuid: $destination_id })
|
|
238
|
+
OPTIONAL MATCH (d)-[delete_edge:IS_PART_OF {status: "deleted", branch: $destination_branch}]->(:Root)
|
|
239
|
+
WHERE delete_edge.from <= $at
|
|
240
|
+
WITH *, d WHERE delete_edge IS NULL
|
|
241
|
+
"""
|
|
242
|
+
self.params["destination_branch"] = destination_branch.name
|
|
243
|
+
else:
|
|
244
|
+
destination_filter, destination_filter_params = destination_branch.get_query_filter_path(
|
|
245
|
+
at=self.at, variable_name="r", params_prefix="dst_"
|
|
246
|
+
)
|
|
247
|
+
destination_query_match = """
|
|
248
|
+
MATCH (d:Node { uuid: $destination_id })
|
|
249
|
+
CALL {
|
|
250
|
+
WITH d
|
|
251
|
+
MATCH (d)-[r:IS_PART_OF]->(:Root)
|
|
252
|
+
WHERE %(destination_filter)s
|
|
253
|
+
RETURN r.status = "active" AS d_is_active
|
|
254
|
+
ORDER BY r.from DESC
|
|
255
|
+
LIMIT 1
|
|
256
|
+
}
|
|
257
|
+
WITH *, d WHERE d_is_active = TRUE
|
|
258
|
+
""" % {"destination_filter": destination_filter}
|
|
259
|
+
self.params.update(destination_filter_params)
|
|
260
|
+
self.add_to_query(destination_query_match)
|
|
261
|
+
|
|
199
262
|
|
|
200
263
|
class RelationshipCreateQuery(RelationshipQuery):
|
|
201
264
|
name = "relationship_create"
|
|
@@ -214,8 +277,6 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
214
277
|
super().__init__(destination=destination, destination_id=destination_id, **kwargs)
|
|
215
278
|
|
|
216
279
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
217
|
-
self.params["source_id"] = self.source_id
|
|
218
|
-
self.params["destination_id"] = self.destination_id
|
|
219
280
|
self.params["name"] = self.schema.identifier
|
|
220
281
|
self.params["branch_support"] = self.schema.branch.value
|
|
221
282
|
|
|
@@ -228,12 +289,11 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
228
289
|
self.params["is_protected"] = self.rel.is_protected
|
|
229
290
|
self.params["is_visible"] = self.rel.is_visible
|
|
230
291
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
292
|
+
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
293
|
+
self.add_dest_match_to_query(
|
|
294
|
+
destination_branch=self.destination.get_branch_based_on_support_type(),
|
|
295
|
+
destination_id=self.destination_id or self.destination.get_id(),
|
|
296
|
+
)
|
|
237
297
|
self.query_add_all_node_property_match()
|
|
238
298
|
|
|
239
299
|
self.params["rel_prop"] = self.get_relationship_properties_dict(status=RelationshipStatus.ACTIVE)
|
|
@@ -378,7 +438,6 @@ class RelationshipDataDeleteQuery(RelationshipQuery):
|
|
|
378
438
|
|
|
379
439
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
380
440
|
self.params["source_id"] = self.source_id
|
|
381
|
-
self.params["destination_id"] = self.data.peer_id
|
|
382
441
|
self.params["rel_node_id"] = self.data.rel_node_id
|
|
383
442
|
self.params["name"] = self.schema.identifier
|
|
384
443
|
self.params["branch"] = self.branch.name
|
|
@@ -388,9 +447,10 @@ class RelationshipDataDeleteQuery(RelationshipQuery):
|
|
|
388
447
|
# -----------------------------------------------------------------------
|
|
389
448
|
# Match all nodes, including properties
|
|
390
449
|
# -----------------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
452
|
+
self.add_dest_match_to_query(destination_branch=self.branch, destination_id=self.data.peer_id)
|
|
391
453
|
query = """
|
|
392
|
-
MATCH (s:Node { uuid: $source_id })
|
|
393
|
-
MATCH (d:Node { uuid: $destination_id })
|
|
394
454
|
MATCH (rl:Relationship { uuid: $rel_node_id })
|
|
395
455
|
"""
|
|
396
456
|
self.add_to_query(query)
|
|
@@ -442,8 +502,6 @@ class RelationshipDeleteQuery(RelationshipQuery):
|
|
|
442
502
|
|
|
443
503
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
444
504
|
rel_filter, rel_params = self.branch.get_query_filter_path(at=self.at, variable_name="edge")
|
|
445
|
-
self.params["source_id"] = self.source_id
|
|
446
|
-
self.params["destination_id"] = self.destination_id
|
|
447
505
|
self.params["rel_id"] = self.rel.id
|
|
448
506
|
self.params["branch"] = self.branch.name
|
|
449
507
|
self.params["rel_prop"] = self.get_relationship_properties_dict(status=RelationshipStatus.DELETED)
|
|
@@ -454,15 +512,19 @@ class RelationshipDeleteQuery(RelationshipQuery):
|
|
|
454
512
|
r1 = f"{arrows.left.start}[r1:{self.rel_type} $rel_prop ]{arrows.left.end}"
|
|
455
513
|
r2 = f"{arrows.right.start}[r2:{self.rel_type} $rel_prop ]{arrows.right.end}"
|
|
456
514
|
|
|
515
|
+
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
516
|
+
self.add_dest_match_to_query(
|
|
517
|
+
destination_branch=self.destination.get_branch_based_on_support_type(),
|
|
518
|
+
destination_id=self.destination_id or self.destination.get_id(),
|
|
519
|
+
)
|
|
457
520
|
query = """
|
|
458
|
-
MATCH (s
|
|
459
|
-
WITH s, rl, d
|
|
521
|
+
MATCH (s)-[:IS_RELATED]-(rl:Relationship {uuid: $rel_id})-[:IS_RELATED]-(d)
|
|
522
|
+
WITH DISTINCT s, rl, d
|
|
460
523
|
LIMIT 1
|
|
461
524
|
CREATE (s)%(r1)s(rl)
|
|
462
525
|
CREATE (rl)%(r2)s(d)
|
|
463
526
|
WITH rl
|
|
464
|
-
CALL {
|
|
465
|
-
WITH rl
|
|
527
|
+
CALL (rl) {
|
|
466
528
|
MATCH (rl)-[edge:IS_VISIBLE]->(visible)
|
|
467
529
|
WHERE %(rel_filter)s AND edge.status = "active"
|
|
468
530
|
WITH rl, edge, visible
|
|
@@ -473,8 +535,7 @@ class RelationshipDeleteQuery(RelationshipQuery):
|
|
|
473
535
|
WHERE edge.branch = $branch
|
|
474
536
|
SET edge.to = $at
|
|
475
537
|
}
|
|
476
|
-
CALL {
|
|
477
|
-
WITH rl
|
|
538
|
+
CALL (rl) {
|
|
478
539
|
MATCH (rl)-[edge:IS_PROTECTED]->(protected)
|
|
479
540
|
WHERE %(rel_filter)s AND edge.status = "active"
|
|
480
541
|
WITH rl, edge, protected
|
|
@@ -485,8 +546,7 @@ class RelationshipDeleteQuery(RelationshipQuery):
|
|
|
485
546
|
WHERE edge.branch = $branch
|
|
486
547
|
SET edge.to = $at
|
|
487
548
|
}
|
|
488
|
-
CALL {
|
|
489
|
-
WITH rl
|
|
549
|
+
CALL (rl) {
|
|
490
550
|
MATCH (rl)-[edge:HAS_OWNER]->(owner_node)
|
|
491
551
|
WHERE %(rel_filter)s AND edge.status = "active"
|
|
492
552
|
WITH rl, edge, owner_node
|
|
@@ -497,8 +557,7 @@ class RelationshipDeleteQuery(RelationshipQuery):
|
|
|
497
557
|
WHERE edge.branch = $branch
|
|
498
558
|
SET edge.to = $at
|
|
499
559
|
}
|
|
500
|
-
CALL {
|
|
501
|
-
WITH rl
|
|
560
|
+
CALL (rl) {
|
|
502
561
|
MATCH (rl)-[edge:HAS_SOURCE]->(source_node)
|
|
503
562
|
WHERE %(rel_filter)s AND edge.status = "active"
|
|
504
563
|
WITH rl, edge, source_node
|
|
@@ -593,8 +652,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
593
652
|
MATCH (source_node:Node)%(arrow_left_start)s[:IS_RELATED]%(arrow_left_end)s(rl:Relationship { name: $rel_identifier })
|
|
594
653
|
WHERE source_node.uuid IN $source_ids
|
|
595
654
|
WITH DISTINCT source_node, rl
|
|
596
|
-
CALL {
|
|
597
|
-
WITH rl, source_node
|
|
655
|
+
CALL (rl, source_node) {
|
|
598
656
|
MATCH path = (source_node)%(path)s(peer:Node)
|
|
599
657
|
WHERE
|
|
600
658
|
$source_kind IN LABELS(source_node) AND
|
|
@@ -663,22 +721,19 @@ class RelationshipGetPeerQuery(Query):
|
|
|
663
721
|
with_str = ", ".join(
|
|
664
722
|
[f"{subquery_result_name} as {label}" if label == "peer" else label for label in self.return_labels]
|
|
665
723
|
)
|
|
666
|
-
self.add_subquery(subquery=subquery, with_clause=with_str)
|
|
667
|
-
|
|
724
|
+
self.add_subquery(subquery=subquery, node_alias="peer", with_clause=with_str)
|
|
668
725
|
# ----------------------------------------------------------------------------
|
|
669
726
|
# QUERY Properties
|
|
670
727
|
# ----------------------------------------------------------------------------
|
|
671
728
|
query = """
|
|
672
|
-
CALL {
|
|
673
|
-
WITH rl
|
|
729
|
+
CALL (rl) {
|
|
674
730
|
MATCH (rl)-[r:IS_VISIBLE]-(is_visible)
|
|
675
731
|
WHERE %(branch_filter)s
|
|
676
732
|
RETURN r AS rel_is_visible, is_visible
|
|
677
733
|
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
678
734
|
LIMIT 1
|
|
679
735
|
}
|
|
680
|
-
CALL {
|
|
681
|
-
WITH rl
|
|
736
|
+
CALL (rl) {
|
|
682
737
|
MATCH (rl)-[r:IS_PROTECTED]-(is_protected)
|
|
683
738
|
WHERE %(branch_filter)s
|
|
684
739
|
RETURN r AS rel_is_protected, is_protected
|
|
@@ -695,8 +750,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
695
750
|
# We must query them one by one otherwise the second one won't return
|
|
696
751
|
for node_prop in ["source", "owner"]:
|
|
697
752
|
query = """
|
|
698
|
-
CALL {
|
|
699
|
-
WITH rl
|
|
753
|
+
CALL (rl) {
|
|
700
754
|
OPTIONAL MATCH (rl)-[r:HAS_%(node_prop_type)s]-(%(node_prop)s)
|
|
701
755
|
WHERE %(branch_filter)s
|
|
702
756
|
RETURN r AS rel_%(node_prop)s, %(node_prop)s
|
|
@@ -737,7 +791,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
737
791
|
self.order_by.append(subquery_result_name)
|
|
738
792
|
self.params.update(subquery_params)
|
|
739
793
|
|
|
740
|
-
self.add_subquery(subquery=subquery)
|
|
794
|
+
self.add_subquery(subquery=subquery, node_alias="peer")
|
|
741
795
|
|
|
742
796
|
order_cnt += 1
|
|
743
797
|
|
|
@@ -752,10 +806,15 @@ class RelationshipGetPeerQuery(Query):
|
|
|
752
806
|
def get_peers(self) -> Generator[RelationshipPeerData, None, None]:
|
|
753
807
|
for result in self.get_results_group_by(("peer", "uuid"), ("source_node", "uuid")):
|
|
754
808
|
rels = result.get("rels")
|
|
809
|
+
source_node = result.get_node("source_node")
|
|
810
|
+
peer_node = result.get_node("peer")
|
|
755
811
|
data = RelationshipPeerData(
|
|
756
|
-
source_id=
|
|
757
|
-
|
|
758
|
-
|
|
812
|
+
source_id=source_node.get("uuid"),
|
|
813
|
+
source_db_id=source_node.element_id,
|
|
814
|
+
source_kind=source_node.get("kind"),
|
|
815
|
+
peer_id=peer_node.get("uuid"),
|
|
816
|
+
peer_db_id=peer_node.element_id,
|
|
817
|
+
peer_kind=peer_node.get("kind"),
|
|
759
818
|
rel_node_db_id=result.get("rl").element_id,
|
|
760
819
|
rel_node_id=result.get("rl").get("uuid"),
|
|
761
820
|
updated_at=rels[0]["from"],
|
|
@@ -793,8 +852,6 @@ class RelationshipGetQuery(RelationshipQuery):
|
|
|
793
852
|
type: QueryType = QueryType.READ
|
|
794
853
|
|
|
795
854
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
796
|
-
self.params["source_id"] = self.source_id
|
|
797
|
-
self.params["destination_id"] = self.destination_id
|
|
798
855
|
self.params["name"] = self.schema.identifier
|
|
799
856
|
self.params["branch"] = self.branch.name
|
|
800
857
|
|
|
@@ -808,9 +865,12 @@ class RelationshipGetQuery(RelationshipQuery):
|
|
|
808
865
|
r1 = f"{arrows.left.start}[r1:{self.rel.rel_type}]{arrows.left.end}"
|
|
809
866
|
r2 = f"{arrows.right.start}[r2:{self.rel.rel_type}]{arrows.right.end}"
|
|
810
867
|
|
|
868
|
+
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
869
|
+
self.add_dest_match_to_query(
|
|
870
|
+
destination_branch=self.destination.get_branch_based_on_support_type(),
|
|
871
|
+
destination_id=self.destination_id or self.destination.get_id(),
|
|
872
|
+
)
|
|
811
873
|
query = """
|
|
812
|
-
MATCH (s:Node { uuid: $source_id })
|
|
813
|
-
MATCH (d:Node { uuid: $destination_id })
|
|
814
874
|
MATCH (s)%s(rl:Relationship { name: $name })%s(d)
|
|
815
875
|
WHERE %s
|
|
816
876
|
""" % (
|
|
@@ -870,8 +930,7 @@ class RelationshipGetByIdentifierQuery(Query):
|
|
|
870
930
|
query = """
|
|
871
931
|
MATCH (rl:Relationship)
|
|
872
932
|
WHERE rl.name IN $identifiers
|
|
873
|
-
CALL {
|
|
874
|
-
WITH rl
|
|
933
|
+
CALL (rl) {
|
|
875
934
|
MATCH (src:Node)-[r1:IS_RELATED]-(rl:Relationship)-[r2:IS_RELATED]-(dst:Node)
|
|
876
935
|
WHERE (size($full_identifiers) = 0 OR [src.kind, rl.name, dst.kind] in $full_identifiers)
|
|
877
936
|
AND NOT src.namespace IN $excluded_namespaces
|
|
@@ -934,8 +993,7 @@ class RelationshipCountPerNodeQuery(Query):
|
|
|
934
993
|
query = """
|
|
935
994
|
MATCH (peer_node:Node)%(path)s(rl:Relationship { name: $rel_identifier })
|
|
936
995
|
WHERE peer_node.uuid IN $peer_ids AND %(branch_filter)s
|
|
937
|
-
CALL {
|
|
938
|
-
WITH rl
|
|
996
|
+
CALL (rl) {
|
|
939
997
|
MATCH path = (peer_node:Node)%(path)s(rl)
|
|
940
998
|
WHERE peer_node.uuid IN $peer_ids AND %(branch_filter)s
|
|
941
999
|
RETURN peer_node as peer, r as r1
|
|
@@ -1013,8 +1071,7 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1013
1071
|
for arrow_left, arrow_right in (("<-", "-"), ("-", "->")):
|
|
1014
1072
|
for edge_type in edge_types:
|
|
1015
1073
|
sub_query = """
|
|
1016
|
-
CALL {
|
|
1017
|
-
WITH rl
|
|
1074
|
+
CALL (rl) {
|
|
1018
1075
|
MATCH (rl)%(arrow_left)s[active_edge:%(edge_type)s]%(arrow_right)s(n)
|
|
1019
1076
|
WHERE %(active_rel_filter)s AND active_edge.status ="active"
|
|
1020
1077
|
CREATE (rl)%(arrow_left)s[deleted_edge:%(edge_type)s $rel_prop]%(arrow_right)s(n)
|
|
@@ -1034,10 +1091,13 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1034
1091
|
|
|
1035
1092
|
# We only want to return uuid/kind of `Node` connected through `IS_RELATED` edges.
|
|
1036
1093
|
query += """
|
|
1037
|
-
CALL {
|
|
1038
|
-
WITH rl
|
|
1094
|
+
CALL (rl) {
|
|
1039
1095
|
MATCH (rl)-[active_edge:IS_RELATED]->(n)
|
|
1040
|
-
WHERE %(active_rel_filter)s
|
|
1096
|
+
WHERE %(active_rel_filter)s
|
|
1097
|
+
WITH rl, active_edge, n
|
|
1098
|
+
ORDER BY %(id_func)s(rl), %(id_func)s(n), active_edge.from DESC
|
|
1099
|
+
WITH rl, n, head(collect(active_edge)) AS active_edge
|
|
1100
|
+
WHERE active_edge.status = "active"
|
|
1041
1101
|
CREATE (rl)-[deleted_edge:IS_RELATED $rel_prop]->(n)
|
|
1042
1102
|
SET deleted_edge.hierarchy = active_edge.hierarchy
|
|
1043
1103
|
WITH rl, active_edge, n
|
|
@@ -1050,10 +1110,13 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1050
1110
|
"outbound" as rel_direction
|
|
1051
1111
|
|
|
1052
1112
|
UNION
|
|
1053
|
-
|
|
1054
1113
|
WITH rl
|
|
1055
1114
|
MATCH (rl)<-[active_edge:IS_RELATED]-(n)
|
|
1056
|
-
WHERE %(active_rel_filter)s
|
|
1115
|
+
WHERE %(active_rel_filter)s
|
|
1116
|
+
WITH rl, active_edge, n
|
|
1117
|
+
ORDER BY %(id_func)s(rl), %(id_func)s(n), active_edge.from DESC
|
|
1118
|
+
WITH rl, n, head(collect(active_edge)) AS active_edge
|
|
1119
|
+
WHERE active_edge.status = "active"
|
|
1057
1120
|
CREATE (rl)<-[deleted_edge:IS_RELATED $rel_prop]-(n)
|
|
1058
1121
|
SET deleted_edge.hierarchy = active_edge.hierarchy
|
|
1059
1122
|
WITH rl, active_edge, n
|
|
@@ -1066,9 +1129,7 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1066
1129
|
"inbound" as rel_direction
|
|
1067
1130
|
}
|
|
1068
1131
|
RETURN DISTINCT uuid, kind, rel_identifier, rel_direction
|
|
1069
|
-
""" % {
|
|
1070
|
-
"active_rel_filter": active_rel_filter,
|
|
1071
|
-
}
|
|
1132
|
+
""" % {"active_rel_filter": active_rel_filter, "id_func": db.get_id_function_name()}
|
|
1072
1133
|
|
|
1073
1134
|
self.add_to_query(query)
|
|
1074
1135
|
|
|
@@ -1118,3 +1179,40 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1118
1179
|
changelog_mapper.delete_relationship(peer_id=peer_uuid, peer_kind=kind, rel_schema=deleted_rel_schema)
|
|
1119
1180
|
|
|
1120
1181
|
return [changelog_mapper.changelog for changelog_mapper in rel_identifier_to_changelog_mapper.values()]
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
class GetAllPeersIds(Query):
|
|
1185
|
+
"""
|
|
1186
|
+
Return all peers ids connected to input node. Some peers can be excluded using `exclude_identifiers`.
|
|
1187
|
+
"""
|
|
1188
|
+
|
|
1189
|
+
name = "get_peers_ids"
|
|
1190
|
+
type: QueryType = QueryType.READ
|
|
1191
|
+
insert_return = False
|
|
1192
|
+
|
|
1193
|
+
def __init__(self, node_id: str, exclude_identifiers: list[str], **kwargs):
|
|
1194
|
+
self.node_id = node_id
|
|
1195
|
+
self.exclude_identifiers = exclude_identifiers
|
|
1196
|
+
super().__init__(**kwargs)
|
|
1197
|
+
|
|
1198
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
1199
|
+
self.params["source_id"] = kwargs["node_id"]
|
|
1200
|
+
self.params["branch"] = self.branch.name
|
|
1201
|
+
self.params["exclude_identifiers"] = self.exclude_identifiers
|
|
1202
|
+
|
|
1203
|
+
active_rel_filter, rel_params = self.branch.get_query_filter_path(
|
|
1204
|
+
at=self.at, variable_name="e1", branch_agnostic=self.branch_agnostic
|
|
1205
|
+
)
|
|
1206
|
+
self.params.update(rel_params)
|
|
1207
|
+
|
|
1208
|
+
query = """
|
|
1209
|
+
MATCH (node:Node { uuid: $source_id })-[e1:IS_RELATED]-(rl:Relationship)-[e2:IS_RELATED]-(peer:Node)
|
|
1210
|
+
WHERE %(active_rel_filter)s AND peer.uuid <> node.uuid AND NOT (rl.name IN $exclude_identifiers)
|
|
1211
|
+
WITH DISTINCT(peer.uuid) as uuid
|
|
1212
|
+
RETURN uuid
|
|
1213
|
+
""" % {"active_rel_filter": active_rel_filter}
|
|
1214
|
+
|
|
1215
|
+
self.add_to_query(query)
|
|
1216
|
+
|
|
1217
|
+
def get_peers_uuids(self) -> list[str]:
|
|
1218
|
+
return [row.data["uuid"] for row in self.results] # type: ignore
|
|
@@ -220,8 +220,7 @@ class NumberPoolGetUsed(Query):
|
|
|
220
220
|
|
|
221
221
|
query = """
|
|
222
222
|
MATCH (pool:%(number_pool)s { uuid: $pool_id })
|
|
223
|
-
CALL {
|
|
224
|
-
WITH pool
|
|
223
|
+
CALL (pool) {
|
|
225
224
|
MATCH (pool)-[res:IS_RESERVED]->(av:AttributeValue)<-[hv:HAS_VALUE]-(attr:Attribute)
|
|
226
225
|
WHERE
|
|
227
226
|
attr.name = $attribute_name
|
infrahub/core/query/subquery.py
CHANGED
|
@@ -57,11 +57,11 @@ async def build_subquery_filter(
|
|
|
57
57
|
params.update(field_params)
|
|
58
58
|
|
|
59
59
|
field_where.append("all(r IN relationships(path) WHERE (%s))" % branch_filter)
|
|
60
|
-
filter_str = f"({node_alias})" + "".join([str(item) for item in field_filter])
|
|
60
|
+
filter_str = f"({node_alias}:Node {{uuid: {node_alias}.uuid}})" + "".join([str(item) for item in field_filter])
|
|
61
61
|
where_str = " AND ".join(field_where)
|
|
62
62
|
branch_level_str = "reduce(br_lvl = 0, r in relationships(path) | br_lvl + r.branch_level)"
|
|
63
63
|
froms_str = db.render_list_comprehension(items="relationships(path)", item_name="from")
|
|
64
|
-
to_return = f"{
|
|
64
|
+
to_return = f"{prefix}"
|
|
65
65
|
with_extra = ""
|
|
66
66
|
final_with_extra = ""
|
|
67
67
|
is_isnull = filter_name == "isnull"
|
|
@@ -82,7 +82,6 @@ async def build_subquery_filter(
|
|
|
82
82
|
elif field is not None and field.is_attribute:
|
|
83
83
|
is_active_filter = "(latest_node_details[2]).value = 'NULL'"
|
|
84
84
|
query = f"""
|
|
85
|
-
WITH {node_alias}
|
|
86
85
|
{match} path = {filter_str}
|
|
87
86
|
WHERE {where_str}
|
|
88
87
|
WITH
|
|
@@ -94,7 +93,7 @@ async def build_subquery_filter(
|
|
|
94
93
|
ORDER BY branch_level DESC, froms[-1] DESC, froms[-2] DESC
|
|
95
94
|
WITH head(collect([is_active, {node_alias}{with_extra}])) AS latest_node_details
|
|
96
95
|
WHERE {is_active_filter}
|
|
97
|
-
WITH latest_node_details[1] AS {
|
|
96
|
+
WITH latest_node_details[1] AS {prefix}{final_with_extra}
|
|
98
97
|
RETURN {to_return}
|
|
99
98
|
"""
|
|
100
99
|
return query, params, prefix
|
|
@@ -138,7 +137,7 @@ async def build_subquery_order(
|
|
|
138
137
|
field_filter[-1].name = "last"
|
|
139
138
|
|
|
140
139
|
field_where.append("all(r IN relationships(path) WHERE (%s))" % branch_filter)
|
|
141
|
-
filter_str = f"({node_alias})" + "".join([str(item) for item in field_filter])
|
|
140
|
+
filter_str = f"({node_alias}:Node {{uuid: {node_alias}.uuid}})" + "".join([str(item) for item in field_filter])
|
|
142
141
|
where_str = " AND ".join(field_where)
|
|
143
142
|
branch_level_str = "reduce(br_lvl = 0, r in relationships(path) | br_lvl + r.branch_level)"
|
|
144
143
|
froms_str = db.render_list_comprehension(items="relationships(path)", item_name="from")
|
|
@@ -174,7 +173,6 @@ async def build_subquery_order(
|
|
|
174
173
|
to_return_str_parts.append(f"CASE WHEN is_active = TRUE THEN {expression} ELSE NULL END AS {alias}")
|
|
175
174
|
to_return_str = ", ".join(to_return_str_parts)
|
|
176
175
|
query = f"""
|
|
177
|
-
WITH {node_alias}
|
|
178
176
|
OPTIONAL MATCH path = {filter_str}
|
|
179
177
|
WHERE {where_str}
|
|
180
178
|
WITH {with_str_to_alias}
|
|
@@ -416,7 +416,7 @@ class Relationship(FlagPropertyMixin, NodePropertyMixin):
|
|
|
416
416
|
await update_relationships_to(rel_ids_to_update, to=delete_at, db=db)
|
|
417
417
|
|
|
418
418
|
delete_query = await RelationshipDeleteQuery.init(
|
|
419
|
-
db=db, rel=self,
|
|
419
|
+
db=db, rel=self, source=node, destination=peer, branch=branch, at=delete_at
|
|
420
420
|
)
|
|
421
421
|
await delete_query.execute(db=db)
|
|
422
422
|
|
|
@@ -789,6 +789,9 @@ class RelationshipManager:
|
|
|
789
789
|
|
|
790
790
|
return len(self._relationships)
|
|
791
791
|
|
|
792
|
+
def validate(self) -> None:
|
|
793
|
+
self._relationships.validate()
|
|
794
|
+
|
|
792
795
|
@overload
|
|
793
796
|
async def get_peer(
|
|
794
797
|
self,
|
infrahub/core/schema/__init__.py
CHANGED
|
@@ -21,7 +21,8 @@ from .profile_schema import ProfileSchema
|
|
|
21
21
|
from .relationship_schema import RelationshipSchema
|
|
22
22
|
from .template_schema import TemplateSchema
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
NonGenericSchemaTypes: TypeAlias = NodeSchema | ProfileSchema | TemplateSchema
|
|
25
|
+
MainSchemaTypes: TypeAlias = NonGenericSchemaTypes | GenericSchema
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# -----------------------------------------------------
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from infrahub.core.constants.schema import UpdateSupport
|
|
6
|
+
from infrahub.core.models import HashableModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_attribute_parameters_class_for_kind(kind: str) -> type[AttributeParameters]:
|
|
10
|
+
return {
|
|
11
|
+
"Text": TextAttributeParameters,
|
|
12
|
+
"TextArea": TextAttributeParameters,
|
|
13
|
+
}.get(kind, AttributeParameters)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AttributeParameters(HashableModel):
|
|
17
|
+
class Config:
|
|
18
|
+
extra = "forbid"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TextAttributeParameters(AttributeParameters):
|
|
22
|
+
regex: str | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Regular expression that attribute value must match if defined",
|
|
25
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
26
|
+
)
|
|
27
|
+
min_length: int | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Set a minimum number of characters allowed.",
|
|
30
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
31
|
+
)
|
|
32
|
+
max_length: int | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="Set a maximum number of characters allowed.",
|
|
35
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
36
|
+
)
|
|
@@ -2,15 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
6
6
|
|
|
7
|
-
from pydantic import field_validator, model_validator
|
|
7
|
+
from pydantic import Field, ValidationInfo, field_validator, model_validator
|
|
8
8
|
|
|
9
9
|
from infrahub import config
|
|
10
|
+
from infrahub.core.constants.schema import UpdateSupport
|
|
10
11
|
from infrahub.core.enums import generate_python_enum
|
|
11
12
|
from infrahub.core.query.attribute import default_attribute_query_filter
|
|
12
13
|
from infrahub.types import ATTRIBUTE_KIND_LABELS, ATTRIBUTE_TYPES
|
|
13
14
|
|
|
15
|
+
from .attribute_parameters import AttributeParameters, TextAttributeParameters, get_attribute_parameters_class_for_kind
|
|
14
16
|
from .generated.attribute_schema import GeneratedAttributeSchema
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
@@ -21,6 +23,14 @@ if TYPE_CHECKING:
|
|
|
21
23
|
from infrahub.database import InfrahubDatabase
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def get_attribute_schema_class_for_kind(kind: str) -> type[AttributeSchema]:
|
|
27
|
+
attribute_schema_class_by_kind: dict[str, type[AttributeSchema]] = {
|
|
28
|
+
"Text": TextAttributeSchema,
|
|
29
|
+
"TextArea": TextAttributeSchema,
|
|
30
|
+
}
|
|
31
|
+
return attribute_schema_class_by_kind.get(kind, AttributeSchema)
|
|
32
|
+
|
|
33
|
+
|
|
24
34
|
class AttributeSchema(GeneratedAttributeSchema):
|
|
25
35
|
_sort_by: list[str] = ["name"]
|
|
26
36
|
_enum_class: type[enum.Enum] | None = None
|
|
@@ -53,16 +63,36 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
53
63
|
|
|
54
64
|
@model_validator(mode="before")
|
|
55
65
|
@classmethod
|
|
56
|
-
def validate_dropdown_choices(cls, values:
|
|
66
|
+
def validate_dropdown_choices(cls, values: Any) -> Any:
|
|
57
67
|
"""Validate that choices are defined for a dropdown but not for other kinds."""
|
|
58
|
-
if values
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
if isinstance(values, dict):
|
|
69
|
+
kind = values.get("kind")
|
|
70
|
+
choices = values.get("choices")
|
|
71
|
+
elif isinstance(values, AttributeSchema):
|
|
72
|
+
kind = values.kind
|
|
73
|
+
choices = values.choices
|
|
74
|
+
else:
|
|
75
|
+
return values
|
|
76
|
+
if kind != "Dropdown" and choices:
|
|
77
|
+
raise ValueError(f"Can only specify 'choices' for kind=Dropdown: {kind}")
|
|
78
|
+
|
|
79
|
+
if kind == "Dropdown" and not choices:
|
|
62
80
|
raise ValueError("The property 'choices' is required for kind=Dropdown")
|
|
63
81
|
|
|
64
82
|
return values
|
|
65
83
|
|
|
84
|
+
@field_validator("parameters", mode="before")
|
|
85
|
+
@classmethod
|
|
86
|
+
def set_parameters_type(cls, value: Any, info: ValidationInfo) -> Any:
|
|
87
|
+
"""Override parameters class if using base AttributeParameters class and should be using a subclass"""
|
|
88
|
+
kind = info.data["kind"]
|
|
89
|
+
expected_parameters_class = get_attribute_parameters_class_for_kind(kind=kind)
|
|
90
|
+
if value is None:
|
|
91
|
+
return expected_parameters_class()
|
|
92
|
+
if not isinstance(value, expected_parameters_class) and isinstance(value, AttributeParameters):
|
|
93
|
+
return expected_parameters_class(**value.model_dump())
|
|
94
|
+
return value
|
|
95
|
+
|
|
66
96
|
def get_class(self) -> type[BaseAttribute]:
|
|
67
97
|
return ATTRIBUTE_TYPES[self.kind].get_infrahub_class()
|
|
68
98
|
|
|
@@ -106,7 +136,7 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
106
136
|
|
|
107
137
|
def to_node(self) -> dict[str, Any]:
|
|
108
138
|
fields_to_exclude = {"id", "state", "filters"}
|
|
109
|
-
fields_to_json = {"computed_attribute"}
|
|
139
|
+
fields_to_json = {"computed_attribute", "parameters"}
|
|
110
140
|
data = self.model_dump(exclude=fields_to_exclude | fields_to_json)
|
|
111
141
|
|
|
112
142
|
for field_name in fields_to_json:
|
|
@@ -117,6 +147,15 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
117
147
|
|
|
118
148
|
return data
|
|
119
149
|
|
|
150
|
+
def get_regex(self) -> str | None:
|
|
151
|
+
return self.regex
|
|
152
|
+
|
|
153
|
+
def get_min_length(self) -> int | None:
|
|
154
|
+
return self.min_length
|
|
155
|
+
|
|
156
|
+
def get_max_length(self) -> int | None:
|
|
157
|
+
return self.max_length
|
|
158
|
+
|
|
120
159
|
async def get_query_filter(
|
|
121
160
|
self,
|
|
122
161
|
name: str,
|
|
@@ -144,3 +183,39 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
144
183
|
partial_match=partial_match,
|
|
145
184
|
support_profiles=support_profiles,
|
|
146
185
|
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TextAttributeSchema(AttributeSchema):
|
|
189
|
+
parameters: TextAttributeParameters = Field(
|
|
190
|
+
default_factory=TextAttributeParameters,
|
|
191
|
+
description="Extra parameters specific to text attributes",
|
|
192
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@model_validator(mode="after")
|
|
196
|
+
def reconcile_parameters(self) -> Self:
|
|
197
|
+
if self.regex != self.parameters.regex:
|
|
198
|
+
final_regex = self.parameters.regex or self.regex
|
|
199
|
+
if not final_regex: # falsy parameters.regex override falsy regex
|
|
200
|
+
final_regex = self.parameters.regex
|
|
201
|
+
self.regex = self.parameters.regex = final_regex
|
|
202
|
+
if self.min_length != self.parameters.min_length:
|
|
203
|
+
final_min_length = self.parameters.min_length or self.min_length
|
|
204
|
+
if not final_min_length: # falsy parameters.min_length override falsy min_length
|
|
205
|
+
final_min_length = self.parameters.min_length
|
|
206
|
+
self.min_length = self.parameters.min_length = final_min_length
|
|
207
|
+
if self.max_length != self.parameters.max_length:
|
|
208
|
+
final_max_length = self.parameters.max_length or self.max_length
|
|
209
|
+
if not final_max_length: # falsy parameters.max_length override falsy max_length
|
|
210
|
+
final_max_length = self.parameters.max_length
|
|
211
|
+
self.max_length = self.parameters.max_length = final_max_length
|
|
212
|
+
return self
|
|
213
|
+
|
|
214
|
+
def get_regex(self) -> str | None:
|
|
215
|
+
return self.parameters.regex
|
|
216
|
+
|
|
217
|
+
def get_min_length(self) -> int | None:
|
|
218
|
+
return self.parameters.min_length
|
|
219
|
+
|
|
220
|
+
def get_max_length(self) -> int | None:
|
|
221
|
+
return self.parameters.max_length
|