infrahub-server 1.2.12__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/constants.py +130 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +243 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +393 -0
- infrahub/actions/tasks.py +119 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/branch/__init__.py +0 -0
- infrahub/branch/tasks.py +29 -0
- infrahub/branch/triggers.py +22 -0
- infrahub/cli/db.py +3 -4
- infrahub/computed_attribute/gather.py +3 -1
- infrahub/computed_attribute/tasks.py +23 -29
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +13 -15
- infrahub/core/constants/__init__.py +10 -0
- infrahub/core/constants/infrahubkind.py +9 -0
- infrahub/core/constraint/node/runner.py +3 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +124 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/coordinator.py +8 -1
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/delete_query.py +8 -4
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/merge.py +72 -125
- infrahub/core/diff/query/save.py +28 -43
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query/time_range_query.py +0 -1
- infrahub/core/diff/repository/repository.py +4 -0
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
- infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
- 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/m023_deduplicate_cardinality_one_relationships.py +2 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +30 -48
- infrahub/core/migrations/graph/m030_illegal_edges.py +1 -2
- infrahub/core/migrations/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +6 -11
- infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
- infrahub/core/migrations/query/node_duplicate.py +19 -21
- infrahub/core/migrations/query/relationship_duplicate.py +19 -18
- infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
- infrahub/core/migrations/schema/node_remove.py +19 -20
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +131 -28
- infrahub/core/node/base.py +1 -1
- infrahub/core/node/create.py +211 -0
- infrahub/core/node/resource_manager/number_pool.py +31 -5
- infrahub/core/node/standard.py +6 -1
- infrahub/core/path.py +15 -1
- infrahub/core/protocols.py +57 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/delete.py +3 -3
- infrahub/core/query/diff.py +19 -32
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +29 -47
- infrahub/core/query/relationship.py +55 -34
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/standard_node.py +19 -5
- infrahub/core/query/subquery.py +2 -4
- infrahub/core/relationship/constraints/count.py +10 -9
- infrahub/core/relationship/constraints/interface.py +2 -1
- infrahub/core/relationship/constraints/peer_kind.py +2 -1
- infrahub/core/relationship/constraints/peer_parent.py +56 -0
- infrahub/core/relationship/constraints/peer_relatives.py +72 -0
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/relationship/model.py +4 -1
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +160 -0
- infrahub/core/schema/attribute_schema.py +130 -7
- infrahub/core/schema/basenode_schema.py +27 -3
- infrahub/core/schema/definitions/core/__init__.py +29 -1
- infrahub/core/schema/definitions/core/group.py +45 -0
- infrahub/core/schema/definitions/core/resource_pool.py +9 -0
- infrahub/core/schema/definitions/internal.py +43 -5
- infrahub/core/schema/generated/attribute_schema.py +16 -3
- infrahub/core/schema/generated/relationship_schema.py +11 -1
- infrahub/core/schema/manager.py +7 -2
- infrahub/core/schema/schema_branch.py +104 -9
- infrahub/core/validators/__init__.py +15 -2
- infrahub/core/validators/attribute/choices.py +1 -3
- infrahub/core/validators/attribute/enum.py +1 -3
- infrahub/core/validators/attribute/kind.py +1 -3
- infrahub/core/validators/attribute/length.py +13 -7
- infrahub/core/validators/attribute/min_max.py +118 -0
- infrahub/core/validators/attribute/number_pool.py +106 -0
- infrahub/core/validators/attribute/optional.py +1 -4
- infrahub/core/validators/attribute/regex.py +5 -6
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +12 -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 +177 -12
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +5 -9
- infrahub/database/__init__.py +12 -4
- infrahub/database/validation.py +1 -2
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
- infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
- infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
- infrahub/dependencies/builder/diff/deserializer.py +1 -1
- infrahub/dependencies/registry.py +4 -0
- infrahub/events/group_action.py +1 -0
- infrahub/events/models.py +1 -1
- infrahub/git/base.py +5 -3
- infrahub/git/integrator.py +96 -5
- infrahub/git/tasks.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +71 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +20 -17
- infrahub/graphql/mutations/relationship.py +32 -0
- infrahub/graphql/mutations/resource_manager.py +63 -7
- infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
- infrahub/graphql/queries/resource_manager.py +7 -1
- infrahub/graphql/resolvers/many_relationship.py +1 -1
- infrahub/graphql/resolvers/resolver.py +2 -2
- infrahub/graphql/resolvers/single_relationship.py +1 -1
- infrahub/graphql/schema.py +6 -0
- infrahub/menu/menu.py +34 -2
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +10 -15
- infrahub/pools/models.py +14 -0
- infrahub/pools/number.py +5 -3
- infrahub/pools/registration.py +22 -0
- infrahub/pools/tasks.py +126 -0
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +911 -34
- infrahub/schema/__init__.py +0 -0
- infrahub/schema/tasks.py +27 -0
- infrahub/schema/triggers.py +23 -0
- infrahub/task_manager/models.py +10 -6
- infrahub/trigger/catalogue.py +6 -0
- infrahub/trigger/models.py +23 -6
- infrahub/trigger/setup.py +26 -2
- infrahub/trigger/tasks.py +4 -2
- infrahub/types.py +6 -0
- infrahub/webhook/tasks.py +4 -8
- infrahub/workflows/catalogue.py +103 -1
- infrahub_sdk/client.py +43 -10
- infrahub_sdk/ctl/generator.py +4 -4
- infrahub_sdk/ctl/repository.py +1 -1
- infrahub_sdk/node/__init__.py +39 -0
- infrahub_sdk/node/attribute.py +122 -0
- infrahub_sdk/node/constants.py +21 -0
- infrahub_sdk/{node.py → node/node.py} +158 -803
- infrahub_sdk/node/parsers.py +15 -0
- infrahub_sdk/node/property.py +24 -0
- infrahub_sdk/node/related_node.py +266 -0
- infrahub_sdk/node/relationship.py +302 -0
- infrahub_sdk/protocols.py +112 -0
- infrahub_sdk/protocols_base.py +34 -2
- infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
- infrahub_sdk/query_groups.py +17 -5
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/schema/repository.py +16 -0
- infrahub_sdk/spec/object.py +1 -1
- infrahub_sdk/store.py +1 -1
- infrahub_sdk/testing/schemas/car_person.py +1 -0
- infrahub_sdk/utils.py +7 -20
- infrahub_sdk/yaml.py +6 -5
- {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +3 -3
- {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +192 -166
- infrahub_testcontainers/container.py +0 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- infrahub_testcontainers/helpers.py +8 -2
- 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.12.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -47,7 +47,6 @@ class RelationshipDuplicateQuery(Query):
|
|
|
47
47
|
@staticmethod
|
|
48
48
|
def _render_sub_query_per_rel_type(rel_name: str, rel_type: str, direction: GraphRelDirection) -> str:
|
|
49
49
|
subquery = [
|
|
50
|
-
f"WITH peer_node, {rel_name}, active_rel, new_rel",
|
|
51
50
|
f"WITH peer_node, {rel_name}, active_rel, new_rel",
|
|
52
51
|
f'WHERE type({rel_name}) = "{rel_type}"',
|
|
53
52
|
]
|
|
@@ -61,28 +60,32 @@ class RelationshipDuplicateQuery(Query):
|
|
|
61
60
|
return "\n".join(subquery)
|
|
62
61
|
|
|
63
62
|
@classmethod
|
|
64
|
-
def _render_sub_query_out(cls) -> str:
|
|
63
|
+
def _render_sub_query_out(cls) -> tuple[str, str]:
|
|
64
|
+
rel_name = "rel_outband"
|
|
65
|
+
sub_query_out_args = f"peer_node, {rel_name}, active_rel, new_rel"
|
|
65
66
|
sub_queries_out = [
|
|
66
67
|
cls._render_sub_query_per_rel_type(
|
|
67
|
-
rel_name=
|
|
68
|
+
rel_name=rel_name, rel_type=rel_type, direction=GraphRelDirection.OUTBOUND
|
|
68
69
|
)
|
|
69
70
|
for rel_type, rel_def in GraphRelationshipRelationships.model_fields.items()
|
|
70
71
|
if rel_def.default.direction in [GraphRelDirection.OUTBOUND, GraphRelDirection.EITHER]
|
|
71
72
|
]
|
|
72
73
|
sub_query_out = "\nUNION\n".join(sub_queries_out)
|
|
73
|
-
return sub_query_out
|
|
74
|
+
return sub_query_out, sub_query_out_args
|
|
74
75
|
|
|
75
76
|
@classmethod
|
|
76
|
-
def _render_sub_query_in(cls) -> str:
|
|
77
|
+
def _render_sub_query_in(cls) -> tuple[str, str]:
|
|
78
|
+
rel_name = "rel_inband"
|
|
79
|
+
sub_query_in_args = f"peer_node, {rel_name}, active_rel, new_rel"
|
|
77
80
|
sub_queries_in = [
|
|
78
81
|
cls._render_sub_query_per_rel_type(
|
|
79
|
-
rel_name=
|
|
82
|
+
rel_name=rel_name, rel_type=rel_type, direction=GraphRelDirection.INBOUND
|
|
80
83
|
)
|
|
81
84
|
for rel_type, rel_def in GraphRelationshipRelationships.model_fields.items()
|
|
82
85
|
if rel_def.default.direction in [GraphRelDirection.INBOUND, GraphRelDirection.EITHER]
|
|
83
86
|
]
|
|
84
87
|
sub_query_in = "\nUNION\n".join(sub_queries_in)
|
|
85
|
-
return sub_query_in
|
|
88
|
+
return sub_query_in, sub_query_in_args
|
|
86
89
|
|
|
87
90
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
88
91
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
|
|
@@ -109,15 +112,13 @@ class RelationshipDuplicateQuery(Query):
|
|
|
109
112
|
"from": self.at.to_string(),
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
sub_query_out = self._render_sub_query_out()
|
|
113
|
-
sub_query_in = self._render_sub_query_in()
|
|
115
|
+
sub_query_out, sub_query_out_args = self._render_sub_query_out()
|
|
116
|
+
sub_query_in, sub_query_in_args = self._render_sub_query_in()
|
|
114
117
|
|
|
115
118
|
self.add_to_query(self.render_match())
|
|
116
119
|
|
|
117
|
-
# ruff: noqa: E501
|
|
118
120
|
query = """
|
|
119
|
-
CALL {
|
|
120
|
-
WITH source, rel, destination
|
|
121
|
+
CALL (source, rel, destination) {
|
|
121
122
|
MATCH path = (source)-[r1:IS_RELATED]-(rel)-[r2:IS_RELATED]-(destination)
|
|
122
123
|
WHERE all(r IN relationships(path) WHERE %(branch_filter)s)
|
|
123
124
|
RETURN rel as rel1, r1 as r11, r2 as r12
|
|
@@ -130,8 +131,7 @@ class RelationshipDuplicateQuery(Query):
|
|
|
130
131
|
WITH DISTINCT(active_rel) as active_rel, new_rel
|
|
131
132
|
// Process Inbound Relationship
|
|
132
133
|
MATCH (active_rel)<-[]-(peer)
|
|
133
|
-
CALL {
|
|
134
|
-
WITH active_rel, peer
|
|
134
|
+
CALL (active_rel, peer) {
|
|
135
135
|
MATCH (active_rel)<-[r]-(peer)
|
|
136
136
|
WHERE %(branch_filter)s
|
|
137
137
|
RETURN active_rel as n1, r as rel_inband1, peer as p1
|
|
@@ -140,7 +140,7 @@ class RelationshipDuplicateQuery(Query):
|
|
|
140
140
|
}
|
|
141
141
|
WITH n1 as active_rel, rel_inband1 as rel_inband, p1 as peer_node, new_rel
|
|
142
142
|
WHERE rel_inband.status = "active"
|
|
143
|
-
CALL {
|
|
143
|
+
CALL (%(sub_query_in_args)s) {
|
|
144
144
|
%(sub_query_in)s
|
|
145
145
|
}
|
|
146
146
|
WITH p2 as peer_node, rel_inband, active_rel, new_rel
|
|
@@ -150,8 +150,7 @@ class RelationshipDuplicateQuery(Query):
|
|
|
150
150
|
WITH DISTINCT(active_rel) as active_rel, new_rel
|
|
151
151
|
// Process Outbound Relationship
|
|
152
152
|
MATCH (active_rel)-[]->(peer)
|
|
153
|
-
CALL {
|
|
154
|
-
WITH active_rel, peer
|
|
153
|
+
CALL (active_rel, peer) {
|
|
155
154
|
MATCH (active_rel)-[r]->(peer)
|
|
156
155
|
WHERE %(branch_filter)s
|
|
157
156
|
RETURN active_rel as n1, r as rel_outband1, peer as p1
|
|
@@ -160,7 +159,7 @@ class RelationshipDuplicateQuery(Query):
|
|
|
160
159
|
}
|
|
161
160
|
WITH n1 as active_rel, rel_outband1 as rel_outband, p1 as peer_node, new_rel
|
|
162
161
|
WHERE rel_outband.status = "active"
|
|
163
|
-
CALL {
|
|
162
|
+
CALL (%(sub_query_out_args)s) {
|
|
164
163
|
%(sub_query_out)s
|
|
165
164
|
}
|
|
166
165
|
WITH p2 as peer_node, rel_outband, active_rel, new_rel
|
|
@@ -172,5 +171,7 @@ class RelationshipDuplicateQuery(Query):
|
|
|
172
171
|
"branch_filter": branch_filter,
|
|
173
172
|
"sub_query_out": sub_query_out,
|
|
174
173
|
"sub_query_in": sub_query_in,
|
|
174
|
+
"sub_query_in_args": sub_query_in_args,
|
|
175
|
+
"sub_query_out_args": sub_query_out_args,
|
|
175
176
|
}
|
|
176
177
|
self.add_to_query(query)
|
|
@@ -50,7 +50,6 @@ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
|
|
|
50
50
|
|
|
51
51
|
def render_sub_query_per_rel_type(rel_type: str, rel_def: FieldInfo) -> str:
|
|
52
52
|
subquery = [
|
|
53
|
-
"WITH peer_node, rb, active_attr",
|
|
54
53
|
"WITH peer_node, rb, active_attr",
|
|
55
54
|
f'WHERE type(rb) = "{rel_type}"',
|
|
56
55
|
]
|
|
@@ -75,8 +74,7 @@ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
|
|
|
75
74
|
MATCH (node:%(node_kind)s)
|
|
76
75
|
WHERE (size($kinds_to_ignore) = 0 OR NOT any(l IN labels(node) WHERE l IN $kinds_to_ignore))
|
|
77
76
|
AND exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name }))
|
|
78
|
-
CALL {
|
|
79
|
-
WITH node
|
|
77
|
+
CALL (node) {
|
|
80
78
|
MATCH (root:Root)<-[r:IS_PART_OF]-(node)
|
|
81
79
|
WHERE %(branch_filter)s
|
|
82
80
|
RETURN node as n1, r as r1
|
|
@@ -86,8 +84,7 @@ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
|
|
|
86
84
|
WITH n1 as active_node, r1 as rb
|
|
87
85
|
WHERE rb.status = "active"
|
|
88
86
|
// Find all the attributes that need to be updated
|
|
89
|
-
CALL {
|
|
90
|
-
WITH active_node
|
|
87
|
+
CALL (active_node) {
|
|
91
88
|
MATCH (active_node)-[r:HAS_ATTRIBUTE]-(attr:Attribute { name: $attr_name })
|
|
92
89
|
WHERE %(branch_filter)s
|
|
93
90
|
RETURN active_node as n1, r as r1, attr as attr1
|
|
@@ -98,8 +95,7 @@ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
|
|
|
98
95
|
WHERE rb.status = "active"
|
|
99
96
|
WITH active_attr
|
|
100
97
|
MATCH (active_attr)-[]-(peer)
|
|
101
|
-
CALL {
|
|
102
|
-
WITH active_attr, peer
|
|
98
|
+
CALL (active_attr, peer) {
|
|
103
99
|
MATCH (active_attr)-[r]-(peer)
|
|
104
100
|
WHERE %(branch_filter)s
|
|
105
101
|
RETURN active_attr as a1, r as r1, peer as p1
|
|
@@ -108,7 +104,7 @@ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
|
|
|
108
104
|
}
|
|
109
105
|
WITH a1 as active_attr, r1 as rb, p1 as peer_node
|
|
110
106
|
WHERE rb.status = "active"
|
|
111
|
-
CALL {
|
|
107
|
+
CALL (peer_node, rb, active_attr) {
|
|
112
108
|
%(sub_query_all)s
|
|
113
109
|
}
|
|
114
110
|
WITH p2 as peer_node, rb, active_attr
|
|
@@ -21,7 +21,6 @@ class NodeRemoveMigrationBaseQuery(MigrationQuery):
|
|
|
21
21
|
rel_def: FieldInfo,
|
|
22
22
|
) -> str:
|
|
23
23
|
subquery = [
|
|
24
|
-
f"WITH peer_node, {rel_name}, active_node",
|
|
25
24
|
f"WITH peer_node, {rel_name}, active_node",
|
|
26
25
|
f'WHERE type({rel_name}) = "{rel_type}"',
|
|
27
26
|
]
|
|
@@ -59,12 +58,10 @@ class NodeRemoveMigrationBaseQuery(MigrationQuery):
|
|
|
59
58
|
|
|
60
59
|
node_remove_query = self.render_node_remove_query(branch_filter=branch_filter)
|
|
61
60
|
|
|
62
|
-
# ruff: noqa: E501
|
|
63
61
|
query = """
|
|
64
62
|
// Find all the active nodes
|
|
65
63
|
MATCH (node:%(node_kind)s)
|
|
66
|
-
CALL {
|
|
67
|
-
WITH node
|
|
64
|
+
CALL (node) {
|
|
68
65
|
MATCH (root:Root)<-[r:IS_PART_OF]-(node)
|
|
69
66
|
WHERE %(branch_filter)s
|
|
70
67
|
RETURN node as n1, r as r1
|
|
@@ -91,13 +88,12 @@ class NodeRemoveMigrationQueryIn(NodeRemoveMigrationBaseQuery):
|
|
|
91
88
|
insert_return: bool = False
|
|
92
89
|
|
|
93
90
|
def render_node_remove_query(self, branch_filter: str) -> str:
|
|
94
|
-
sub_query = self.render_sub_query_in()
|
|
91
|
+
sub_query, sub_query_args = self.render_sub_query_in()
|
|
95
92
|
query = """
|
|
96
93
|
// Process Inbound Relationship
|
|
97
94
|
WITH active_node
|
|
98
95
|
MATCH (active_node)<-[]-(peer)
|
|
99
|
-
CALL {
|
|
100
|
-
WITH active_node, peer
|
|
96
|
+
CALL (active_node, peer) {
|
|
101
97
|
MATCH (active_node)-[r]->(peer)
|
|
102
98
|
WHERE %(branch_filter)s
|
|
103
99
|
RETURN active_node as n1, r as rel_inband1, peer as p1
|
|
@@ -106,27 +102,29 @@ class NodeRemoveMigrationQueryIn(NodeRemoveMigrationBaseQuery):
|
|
|
106
102
|
}
|
|
107
103
|
WITH n1 as active_node, rel_inband1 as rel_inband, p1 as peer_node
|
|
108
104
|
WHERE rel_inband.status = "active"
|
|
109
|
-
CALL {
|
|
105
|
+
CALL (%(sub_query_args)s) {
|
|
110
106
|
%(sub_query)s
|
|
111
107
|
}
|
|
112
108
|
WITH p2 as peer_node, rel_inband, active_node
|
|
113
109
|
FOREACH (i in CASE WHEN rel_inband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
|
|
114
110
|
SET rel_inband.to = $current_time
|
|
115
111
|
)
|
|
116
|
-
""" % {"sub_query": sub_query, "branch_filter": branch_filter}
|
|
112
|
+
""" % {"sub_query": sub_query, "sub_query_args": sub_query_args, "branch_filter": branch_filter}
|
|
117
113
|
return query
|
|
118
114
|
|
|
119
|
-
def render_sub_query_in(self) -> str:
|
|
115
|
+
def render_sub_query_in(self) -> tuple[str, str]:
|
|
116
|
+
rel_name = "rel_inband"
|
|
117
|
+
sub_query_in_args = f"peer_node, {rel_name}, active_node"
|
|
120
118
|
sub_queries_in = [
|
|
121
119
|
self.render_sub_query_per_rel_type(
|
|
122
|
-
rel_name=
|
|
120
|
+
rel_name=rel_name,
|
|
123
121
|
rel_type=rel_type,
|
|
124
122
|
rel_def=rel_def,
|
|
125
123
|
)
|
|
126
124
|
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
|
|
127
125
|
]
|
|
128
126
|
sub_query_in = "\nUNION\n".join(sub_queries_in)
|
|
129
|
-
return sub_query_in
|
|
127
|
+
return sub_query_in, sub_query_in_args
|
|
130
128
|
|
|
131
129
|
def get_nbr_migrations_executed(self) -> int:
|
|
132
130
|
return 0
|
|
@@ -137,13 +135,12 @@ class NodeRemoveMigrationQueryOut(NodeRemoveMigrationBaseQuery):
|
|
|
137
135
|
insert_return: bool = False
|
|
138
136
|
|
|
139
137
|
def render_node_remove_query(self, branch_filter: str) -> str:
|
|
140
|
-
sub_query = self.render_sub_query_out()
|
|
138
|
+
sub_query, sub_query_args = self.render_sub_query_out()
|
|
141
139
|
query = """
|
|
142
140
|
// Process Outbound Relationship
|
|
143
141
|
WITH active_node
|
|
144
142
|
MATCH (active_node)-[]->(peer)
|
|
145
|
-
CALL {
|
|
146
|
-
WITH active_node, peer
|
|
143
|
+
CALL (active_node, peer) {
|
|
147
144
|
MATCH (active_node)-[r]->(peer)
|
|
148
145
|
WHERE %(branch_filter)s
|
|
149
146
|
RETURN active_node as n1, r as rel_outband1, peer as p1
|
|
@@ -152,27 +149,29 @@ class NodeRemoveMigrationQueryOut(NodeRemoveMigrationBaseQuery):
|
|
|
152
149
|
}
|
|
153
150
|
WITH n1 as active_node, rel_outband1 as rel_outband, p1 as peer_node
|
|
154
151
|
WHERE rel_outband.status = "active"
|
|
155
|
-
CALL {
|
|
152
|
+
CALL (%(sub_query_args)s) {
|
|
156
153
|
%(sub_query)s
|
|
157
154
|
}
|
|
158
155
|
FOREACH (i in CASE WHEN rel_outband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
|
|
159
156
|
SET rel_outband.to = $current_time
|
|
160
157
|
)
|
|
161
|
-
""" % {"sub_query": sub_query, "branch_filter": branch_filter}
|
|
158
|
+
""" % {"sub_query": sub_query, "sub_query_args": sub_query_args, "branch_filter": branch_filter}
|
|
162
159
|
|
|
163
160
|
return query
|
|
164
161
|
|
|
165
|
-
def render_sub_query_out(self) -> str:
|
|
162
|
+
def render_sub_query_out(self) -> tuple[str, str]:
|
|
163
|
+
rel_name = "rel_outband"
|
|
164
|
+
sub_query_out_args = f"peer_node, {rel_name}, active_node"
|
|
166
165
|
sub_queries_out = [
|
|
167
166
|
self.render_sub_query_per_rel_type(
|
|
168
|
-
rel_name=
|
|
167
|
+
rel_name=rel_name,
|
|
169
168
|
rel_type=rel_type,
|
|
170
169
|
rel_def=rel_def,
|
|
171
170
|
)
|
|
172
171
|
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
|
|
173
172
|
]
|
|
174
173
|
sub_query_out = "\nUNION\n".join(sub_queries_out)
|
|
175
|
-
return sub_query_out
|
|
174
|
+
return sub_query_out, sub_query_out_args
|
|
176
175
|
|
|
177
176
|
def get_nbr_migrations_executed(self) -> int:
|
|
178
177
|
return self.num_of_results
|
infrahub/core/models.py
CHANGED
|
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
21
21
|
|
|
22
22
|
GENERIC_ATTRIBUTES_TO_IGNORE = ["namespace", "name", "branch"]
|
|
23
|
+
PROPERTY_NAMES_TO_IGNORE = ["regex", "min_length", "max_length"]
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class NodeKind(BaseModel):
|
|
@@ -252,11 +253,37 @@ class SchemaUpdateValidationResult(BaseModel):
|
|
|
252
253
|
if not sub_field_diff:
|
|
253
254
|
raise ValueError("sub_field_diff must be defined, unexpected situation")
|
|
254
255
|
|
|
255
|
-
for prop_name in sub_field_diff.changed:
|
|
256
|
+
for prop_name, prop_diff in sub_field_diff.changed.items():
|
|
257
|
+
if prop_name in PROPERTY_NAMES_TO_IGNORE:
|
|
258
|
+
continue
|
|
259
|
+
|
|
256
260
|
field_info = field.model_fields[prop_name]
|
|
257
261
|
field_update = str(field_info.json_schema_extra.get("update")) # type: ignore[union-attr]
|
|
258
262
|
|
|
259
|
-
|
|
263
|
+
if isinstance(prop_diff, HashableModelDiff):
|
|
264
|
+
for param_field_name in prop_diff.changed:
|
|
265
|
+
# override field_update if this field has its own json_schema_extra.update
|
|
266
|
+
try:
|
|
267
|
+
prop_field = getattr(field, prop_name)
|
|
268
|
+
param_field_info = prop_field.model_fields[param_field_name]
|
|
269
|
+
param_field_update = str(param_field_info.json_schema_extra.get("update"))
|
|
270
|
+
except (AttributeError, KeyError):
|
|
271
|
+
param_field_update = None
|
|
272
|
+
|
|
273
|
+
schema_path = SchemaPath(
|
|
274
|
+
schema_kind=schema.kind,
|
|
275
|
+
path_type=path_type,
|
|
276
|
+
field_name=field_name,
|
|
277
|
+
property_name=f"{prop_name}.{param_field_name}",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
self._process_field(
|
|
281
|
+
schema_path=schema_path,
|
|
282
|
+
field_update=param_field_update or field_update,
|
|
283
|
+
)
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
schema_path = SchemaPath(
|
|
260
287
|
schema_kind=schema.kind,
|
|
261
288
|
path_type=path_type,
|
|
262
289
|
field_name=field_name,
|
infrahub/core/node/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from infrahub_sdk.template import Jinja2Template
|
|
|
7
7
|
from infrahub_sdk.utils import is_valid_uuid
|
|
8
8
|
from infrahub_sdk.uuidt import UUIDT
|
|
9
9
|
|
|
10
|
+
from infrahub import lock
|
|
10
11
|
from infrahub.core import registry
|
|
11
12
|
from infrahub.core.changelog.models import NodeChangelog
|
|
12
13
|
from infrahub.core.constants import (
|
|
@@ -16,15 +17,25 @@ from infrahub.core.constants import (
|
|
|
16
17
|
BranchSupportType,
|
|
17
18
|
ComputedAttributeKind,
|
|
18
19
|
InfrahubKind,
|
|
20
|
+
NumberPoolType,
|
|
19
21
|
RelationshipCardinality,
|
|
20
22
|
RelationshipKind,
|
|
21
23
|
)
|
|
22
24
|
from infrahub.core.constants.schema import SchemaElementPathType
|
|
23
25
|
from infrahub.core.protocols import CoreNumberPool, CoreObjectTemplate
|
|
24
26
|
from infrahub.core.query.node import NodeCheckIDQuery, NodeCreateAllQuery, NodeDeleteQuery, NodeGetListQuery
|
|
25
|
-
from infrahub.core.schema import
|
|
27
|
+
from infrahub.core.schema import (
|
|
28
|
+
AttributeSchema,
|
|
29
|
+
NodeSchema,
|
|
30
|
+
NonGenericSchemaTypes,
|
|
31
|
+
ProfileSchema,
|
|
32
|
+
RelationshipSchema,
|
|
33
|
+
TemplateSchema,
|
|
34
|
+
)
|
|
35
|
+
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
26
36
|
from infrahub.core.timestamp import Timestamp
|
|
27
37
|
from infrahub.exceptions import InitializationError, NodeNotFoundError, PoolExhaustedError, ValidationError
|
|
38
|
+
from infrahub.pools.models import NumberPoolLockDefinition
|
|
28
39
|
from infrahub.types import ATTRIBUTE_TYPES
|
|
29
40
|
|
|
30
41
|
from ...graphql.constants import KIND_GRAPHQL_FIELD_NAME
|
|
@@ -66,7 +77,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
66
77
|
_meta.default_filter = default_filter
|
|
67
78
|
super().__init_subclass_with_meta__(_meta=_meta, **options)
|
|
68
79
|
|
|
69
|
-
def get_schema(self) ->
|
|
80
|
+
def get_schema(self) -> NonGenericSchemaTypes:
|
|
70
81
|
return self._schema
|
|
71
82
|
|
|
72
83
|
def get_kind(self) -> str:
|
|
@@ -247,6 +258,12 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
247
258
|
within the create code.
|
|
248
259
|
"""
|
|
249
260
|
|
|
261
|
+
number_pool_parameters: NumberPoolParameters | None = None
|
|
262
|
+
if attribute.schema.kind == "NumberPool" and isinstance(attribute.schema.parameters, NumberPoolParameters):
|
|
263
|
+
attribute.from_pool = {"id": attribute.schema.parameters.number_pool_id}
|
|
264
|
+
attribute.is_default = False
|
|
265
|
+
number_pool_parameters = attribute.schema.parameters
|
|
266
|
+
|
|
250
267
|
if not attribute.from_pool:
|
|
251
268
|
return
|
|
252
269
|
|
|
@@ -255,19 +272,25 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
255
272
|
db=db, id=attribute.from_pool["id"], kind=CoreNumberPool
|
|
256
273
|
)
|
|
257
274
|
except NodeNotFoundError:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
275
|
+
if number_pool_parameters:
|
|
276
|
+
number_pool = await self._fetch_or_create_number_pool(
|
|
277
|
+
db=db, attribute=attribute, number_pool_parameters=number_pool_parameters
|
|
261
278
|
)
|
|
262
|
-
|
|
263
|
-
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
errors.append(
|
|
282
|
+
ValidationError(
|
|
283
|
+
{f"{attribute.name}.from_pool": f"The pool requested {attribute.from_pool} was not found."}
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
return
|
|
264
287
|
|
|
265
288
|
if (
|
|
266
289
|
number_pool.node.value in [self._schema.kind] + self._schema.inherit_from
|
|
267
290
|
and number_pool.node_attribute.value == attribute.name
|
|
268
291
|
):
|
|
269
292
|
try:
|
|
270
|
-
next_free = await number_pool.get_resource(db=db, branch=self._branch, node=self)
|
|
293
|
+
next_free = await number_pool.get_resource(db=db, branch=self._branch, node=self, attribute=attribute)
|
|
271
294
|
except PoolExhaustedError:
|
|
272
295
|
errors.append(
|
|
273
296
|
ValidationError({f"{attribute.name}.from_pool": f"The pool {number_pool.node.value} is exhausted."})
|
|
@@ -285,6 +308,50 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
285
308
|
)
|
|
286
309
|
)
|
|
287
310
|
|
|
311
|
+
async def _fetch_or_create_number_pool(
|
|
312
|
+
self, db: InfrahubDatabase, attribute: BaseAttribute, number_pool_parameters: NumberPoolParameters
|
|
313
|
+
) -> CoreNumberPool:
|
|
314
|
+
number_pool_from_db: CoreNumberPool | None = None
|
|
315
|
+
lock_definition = NumberPoolLockDefinition(pool_id=str(number_pool_parameters.number_pool_id))
|
|
316
|
+
async with lock.registry.get(
|
|
317
|
+
name=lock_definition.lock_name, namespace=lock_definition.namespace_name, local=False
|
|
318
|
+
):
|
|
319
|
+
try:
|
|
320
|
+
number_pool_from_db = await registry.manager.get_one_by_id_or_default_filter(
|
|
321
|
+
db=db, id=str(number_pool_parameters.number_pool_id), kind=CoreNumberPool
|
|
322
|
+
)
|
|
323
|
+
except NodeNotFoundError:
|
|
324
|
+
schema = db.schema.get_node_schema(name="CoreNumberPool", duplicate=False)
|
|
325
|
+
|
|
326
|
+
pool_node = self._schema.kind
|
|
327
|
+
schema_attribute = self._schema.get_attribute(attribute.schema.name)
|
|
328
|
+
if schema_attribute.inherited:
|
|
329
|
+
for generic_name in self._schema.inherit_from:
|
|
330
|
+
generic_node = db.schema.get_generic_schema(name=generic_name, duplicate=False)
|
|
331
|
+
if attribute.schema.name in generic_node.attribute_names:
|
|
332
|
+
pool_node = generic_node.kind
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
number_pool = await Node.init(db=db, schema=schema, branch=self._branch)
|
|
336
|
+
await number_pool.new(
|
|
337
|
+
db=db,
|
|
338
|
+
id=number_pool_parameters.number_pool_id,
|
|
339
|
+
name=f"{pool_node}.{attribute.schema.name} [{number_pool_parameters.number_pool_id}]",
|
|
340
|
+
node=pool_node,
|
|
341
|
+
node_attribute=attribute.schema.name,
|
|
342
|
+
start_range=number_pool_parameters.start_range,
|
|
343
|
+
end_range=number_pool_parameters.end_range,
|
|
344
|
+
pool_type=NumberPoolType.SCHEMA.value,
|
|
345
|
+
)
|
|
346
|
+
await number_pool.save(db=db)
|
|
347
|
+
|
|
348
|
+
# Do a lookup of the number pool to get the correct mapped type from the registry
|
|
349
|
+
# without this we don't get access to the .get_resource() method.
|
|
350
|
+
created_pool: CoreNumberPool = number_pool_from_db or await registry.manager.get_one_by_id_or_default_filter(
|
|
351
|
+
db=db, id=number_pool.id, kind=CoreNumberPool
|
|
352
|
+
)
|
|
353
|
+
return created_pool
|
|
354
|
+
|
|
288
355
|
async def handle_object_template(self, fields: dict, db: InfrahubDatabase, errors: list) -> None:
|
|
289
356
|
"""Fill the `fields` parameters with values from an object template if one is in use."""
|
|
290
357
|
object_template_field = fields.get(OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
@@ -369,6 +436,9 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
369
436
|
self._computed_jinja2_attributes.append(mandatory_attr)
|
|
370
437
|
continue
|
|
371
438
|
|
|
439
|
+
if mandatory_attribute.kind == "NumberPool":
|
|
440
|
+
continue
|
|
441
|
+
|
|
372
442
|
errors.append(
|
|
373
443
|
ValidationError({mandatory_attr: f"{mandatory_attr} is mandatory for {self.get_kind()}"})
|
|
374
444
|
)
|
|
@@ -385,6 +455,21 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
385
455
|
# -------------------------------------------
|
|
386
456
|
# Generate Attribute and Relationship and assign them
|
|
387
457
|
# -------------------------------------------
|
|
458
|
+
errors.extend(await self._process_fields_relationships(fields=fields, db=db))
|
|
459
|
+
errors.extend(await self._process_fields_attributes(fields=fields, db=db))
|
|
460
|
+
|
|
461
|
+
if errors:
|
|
462
|
+
raise ValidationError(errors)
|
|
463
|
+
|
|
464
|
+
# Check if any post processor have been defined
|
|
465
|
+
# A processor can be used for example to assigne a default value
|
|
466
|
+
for name in self._attributes + self._relationships:
|
|
467
|
+
if hasattr(self, f"process_{name}"):
|
|
468
|
+
await getattr(self, f"process_{name}")(db=db)
|
|
469
|
+
|
|
470
|
+
async def _process_fields_relationships(self, fields: dict, db: InfrahubDatabase) -> list[ValidationError]:
|
|
471
|
+
errors: list[ValidationError] = []
|
|
472
|
+
|
|
388
473
|
for rel_schema in self._schema.relationships:
|
|
389
474
|
self._relationships.append(rel_schema.name)
|
|
390
475
|
|
|
@@ -406,6 +491,11 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
406
491
|
except ValidationError as exc:
|
|
407
492
|
errors.append(exc)
|
|
408
493
|
|
|
494
|
+
return errors
|
|
495
|
+
|
|
496
|
+
async def _process_fields_attributes(self, fields: dict, db: InfrahubDatabase) -> list[ValidationError]:
|
|
497
|
+
errors: list[ValidationError] = []
|
|
498
|
+
|
|
409
499
|
for attr_schema in self._schema.attributes:
|
|
410
500
|
self._attributes.append(attr_schema.name)
|
|
411
501
|
if not self._existing and attr_schema.name in self._computed_jinja2_attributes:
|
|
@@ -434,14 +524,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
434
524
|
except ValidationError as exc:
|
|
435
525
|
errors.append(exc)
|
|
436
526
|
|
|
437
|
-
|
|
438
|
-
raise ValidationError(errors)
|
|
439
|
-
|
|
440
|
-
# Check if any post processor have been defined
|
|
441
|
-
# A processor can be used for example to assigne a default value
|
|
442
|
-
for name in self._attributes + self._relationships:
|
|
443
|
-
if hasattr(self, f"process_{name}"):
|
|
444
|
-
await getattr(self, f"process_{name}")(db=db)
|
|
527
|
+
return errors
|
|
445
528
|
|
|
446
529
|
async def _process_macros(self, db: InfrahubDatabase) -> None:
|
|
447
530
|
schema_branch = db.schema.get_schema_branch(self._branch.name)
|
|
@@ -474,17 +557,21 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
474
557
|
relationship_attribute: RelationshipManager = getattr(
|
|
475
558
|
self, attribute_path.active_relationship_schema.name
|
|
476
559
|
)
|
|
477
|
-
peer
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
560
|
+
if peer := await relationship_attribute.get_peer(db=db, raise_on_error=False):
|
|
561
|
+
related_node = await registry.manager.get_one_by_id_or_default_filter(
|
|
562
|
+
db=db,
|
|
563
|
+
id=peer.id,
|
|
564
|
+
kind=attribute_path.active_relationship_schema.peer,
|
|
565
|
+
branch=self._branch.name,
|
|
566
|
+
)
|
|
482
567
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
568
|
+
attribute: BaseAttribute = getattr(
|
|
569
|
+
getattr(related_node, attribute_path.active_attribute_schema.name),
|
|
570
|
+
attribute_path.active_attribute_property_name,
|
|
571
|
+
)
|
|
572
|
+
variables[variable] = attribute
|
|
573
|
+
else:
|
|
574
|
+
variables[variable] = None
|
|
488
575
|
|
|
489
576
|
elif attribute_path.is_type_attribute:
|
|
490
577
|
attribute = getattr(
|
|
@@ -872,9 +959,11 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
872
959
|
if relationship.kind == RelationshipKind.PARENT:
|
|
873
960
|
return relationship.name
|
|
874
961
|
|
|
875
|
-
async def get_object_template(self, db: InfrahubDatabase) ->
|
|
962
|
+
async def get_object_template(self, db: InfrahubDatabase) -> CoreObjectTemplate | None:
|
|
876
963
|
object_template: RelationshipManager = getattr(self, OBJECT_TEMPLATE_RELATIONSHIP_NAME, None)
|
|
877
|
-
return
|
|
964
|
+
return (
|
|
965
|
+
await object_template.get_peer(db=db, peer_type=CoreObjectTemplate) if object_template is not None else None
|
|
966
|
+
)
|
|
878
967
|
|
|
879
968
|
def get_relationships(
|
|
880
969
|
self, kind: RelationshipKind, exclude: Sequence[str] | None = None
|
|
@@ -888,3 +977,17 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
888
977
|
for relationship in self.get_schema().relationships
|
|
889
978
|
if relationship.name not in exclude and relationship.kind == kind
|
|
890
979
|
]
|
|
980
|
+
|
|
981
|
+
def validate_relationships(self) -> None:
|
|
982
|
+
for name in self._relationships:
|
|
983
|
+
relm: RelationshipManager = getattr(self, name)
|
|
984
|
+
relm.validate()
|
|
985
|
+
|
|
986
|
+
async def get_parent_relationship_peer(self, db: InfrahubDatabase, name: str) -> Node | None:
|
|
987
|
+
"""When a node has a parent relationship of a given name, this method returns the peer of that relationship."""
|
|
988
|
+
relationship = self.get_schema().get_relationship(name=name)
|
|
989
|
+
if relationship.kind != RelationshipKind.PARENT:
|
|
990
|
+
raise ValueError(f"Relationship '{name}' is not of kind 'parent'")
|
|
991
|
+
|
|
992
|
+
relm: RelationshipManager = getattr(self, name)
|
|
993
|
+
return await relm.get_peer(db=db)
|
infrahub/core/node/base.py
CHANGED
|
@@ -52,7 +52,7 @@ class BaseNodeOptions(BaseOptions):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class ObjectNodeMeta(BaseNodeMeta):
|
|
55
|
-
def __new__(mcs, name_, bases, namespace, **options):
|
|
55
|
+
def __new__(mcs, name_, bases, namespace, **options):
|
|
56
56
|
# Note: it's safe to pass options as keyword arguments as they are still type-checked by NodeOptions.
|
|
57
57
|
|
|
58
58
|
# We create this type, to then overload it with the dataclass attrs
|