infrahub-server 1.2.11__py3-none-any.whl → 1.3.0b1__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/core/account.py +24 -47
- infrahub/core/attribute.py +13 -15
- infrahub/core/constants/__init__.py +5 -0
- infrahub/core/constants/infrahubkind.py +9 -0
- 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/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/field_summary.py +2 -4
- infrahub/core/diff/query/merge.py +70 -123
- infrahub/core/diff/query/save.py +20 -32
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/manager.py +14 -11
- 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/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +5 -10
- 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 -17
- infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
- infrahub/core/migrations/schema/node_remove.py +19 -19
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +90 -18
- 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/protocols.py +56 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/diff.py +19 -32
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +28 -46
- infrahub/core/query/relationship.py +53 -32
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/subquery.py +2 -4
- infrahub/core/relationship/model.py +3 -0
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +160 -0
- infrahub/core/schema/attribute_schema.py +111 -8
- infrahub/core/schema/basenode_schema.py +25 -1
- infrahub/core/schema/definitions/core/__init__.py +29 -1
- infrahub/core/schema/definitions/core/group.py +45 -0
- infrahub/core/schema/definitions/internal.py +27 -4
- infrahub/core/schema/generated/attribute_schema.py +16 -3
- infrahub/core/schema/manager.py +3 -0
- infrahub/core/schema/schema_branch.py +67 -7
- infrahub/core/validators/__init__.py +13 -1
- 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 +3 -8
- infrahub/core/validators/uniqueness/query.py +5 -9
- infrahub/database/__init__.py +11 -2
- infrahub/events/group_action.py +1 -0
- infrahub/git/base.py +5 -3
- infrahub/git/integrator.py +102 -3
- 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 +62 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +20 -17
- infrahub/graphql/mutations/resource_manager.py +62 -6
- infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
- infrahub/graphql/queries/resource_manager.py +7 -1
- infrahub/graphql/schema.py +6 -0
- infrahub/menu/menu.py +31 -0
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +5 -10
- infrahub/pools/number.py +5 -3
- 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/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +18 -2
- infrahub/trigger/tasks.py +3 -1
- infrahub/types.py +6 -0
- infrahub/workflows/catalogue.py +76 -0
- infrahub_sdk/client.py +43 -10
- 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} +50 -749
- 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/query_groups.py +13 -2
- 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_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/METADATA +4 -4
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/RECORD +134 -122
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/WHEEL +1 -1
- 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.11.dist-info → infrahub_server-1.3.0b1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,709 +1,75 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import ipaddress
|
|
4
|
-
import re
|
|
5
|
-
from collections.abc import Iterable
|
|
6
3
|
from copy import copy
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
8
5
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
Error,
|
|
6
|
+
from ..constants import InfrahubClientMode
|
|
7
|
+
from ..exceptions import (
|
|
12
8
|
FeatureNotSupportedError,
|
|
13
9
|
NodeNotFoundError,
|
|
14
|
-
UninitializedError,
|
|
15
10
|
)
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from .
|
|
11
|
+
from ..graphql import Mutation, Query
|
|
12
|
+
from ..schema import GenericSchemaAPI, RelationshipCardinality, RelationshipKind
|
|
13
|
+
from ..utils import compare_lists, generate_short_id, get_flat_value
|
|
14
|
+
from .attribute import Attribute
|
|
15
|
+
from .constants import (
|
|
16
|
+
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
17
|
+
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
18
|
+
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
19
|
+
PROPERTIES_OBJECT,
|
|
20
|
+
)
|
|
21
|
+
from .related_node import RelatedNode, RelatedNodeBase, RelatedNodeSync
|
|
22
|
+
from .relationship import RelationshipManager, RelationshipManagerBase, RelationshipManagerSync
|
|
20
23
|
|
|
21
24
|
if TYPE_CHECKING:
|
|
22
25
|
from typing_extensions import Self
|
|
23
26
|
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
PROPERTIES_FLAG = ["is_visible", "is_protected"]
|
|
31
|
-
PROPERTIES_OBJECT = ["source", "owner"]
|
|
32
|
-
SAFE_VALUE = re.compile(r"(^[\. /:a-zA-Z0-9_-]+$)|(^$)")
|
|
33
|
-
|
|
34
|
-
IP_TYPES = Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network]
|
|
35
|
-
|
|
36
|
-
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
37
|
-
"calling artifact_fetch is only supported for nodes that are Artifact Definition target"
|
|
38
|
-
)
|
|
39
|
-
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
40
|
-
"calling artifact_generate is only supported for nodes that are Artifact Definition targets"
|
|
41
|
-
)
|
|
42
|
-
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
|
|
43
|
-
"calling generate is only supported for CoreArtifactDefinition nodes"
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
HFID_STR_SEPARATOR = "__"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def parse_human_friendly_id(hfid: str | list[str]) -> tuple[str | None, list[str]]:
|
|
50
|
-
"""Parse a human friendly ID into a kind and an identifier."""
|
|
51
|
-
if isinstance(hfid, str):
|
|
52
|
-
hfid_parts = hfid.split(HFID_STR_SEPARATOR)
|
|
53
|
-
if len(hfid_parts) == 1:
|
|
54
|
-
return None, hfid_parts
|
|
55
|
-
return hfid_parts[0], hfid_parts[1:]
|
|
56
|
-
if isinstance(hfid, list):
|
|
57
|
-
return None, hfid
|
|
58
|
-
raise ValueError(f"Invalid human friendly ID: {hfid}")
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class Attribute:
|
|
62
|
-
"""Represents an attribute of a Node, including its schema, value, and properties."""
|
|
63
|
-
|
|
64
|
-
def __init__(self, name: str, schema: AttributeSchemaAPI, data: Any | dict):
|
|
65
|
-
"""
|
|
66
|
-
Args:
|
|
67
|
-
name (str): The name of the attribute.
|
|
68
|
-
schema (AttributeSchema): The schema defining the attribute.
|
|
69
|
-
data (Union[Any, dict]): The data for the attribute, either in raw form or as a dictionary.
|
|
70
|
-
"""
|
|
71
|
-
self.name = name
|
|
72
|
-
self._schema = schema
|
|
73
|
-
|
|
74
|
-
if not isinstance(data, dict) or "value" not in data.keys():
|
|
75
|
-
data = {"value": data}
|
|
27
|
+
from ..client import InfrahubClient, InfrahubClientSync
|
|
28
|
+
from ..context import RequestContext
|
|
29
|
+
from ..schema import MainSchemaTypesAPI
|
|
30
|
+
from ..types import Order
|
|
76
31
|
|
|
77
|
-
self._properties_flag = PROPERTIES_FLAG
|
|
78
|
-
self._properties_object = PROPERTIES_OBJECT
|
|
79
|
-
self._properties = self._properties_flag + self._properties_object
|
|
80
32
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.id: str | None = data.get("id", None)
|
|
84
|
-
|
|
85
|
-
self._value: Any | None = data.get("value", None)
|
|
86
|
-
self.value_has_been_mutated = False
|
|
87
|
-
self.is_default: bool | None = data.get("is_default", None)
|
|
88
|
-
self.is_from_profile: bool | None = data.get("is_from_profile", None)
|
|
89
|
-
|
|
90
|
-
if self._value:
|
|
91
|
-
value_mapper: dict[str, Callable] = {
|
|
92
|
-
"IPHost": ipaddress.ip_interface,
|
|
93
|
-
"IPNetwork": ipaddress.ip_network,
|
|
94
|
-
}
|
|
95
|
-
mapper = value_mapper.get(schema.kind, lambda value: value)
|
|
96
|
-
self._value = mapper(data.get("value"))
|
|
97
|
-
|
|
98
|
-
self.is_inherited: bool | None = data.get("is_inherited", None)
|
|
99
|
-
self.updated_at: str | None = data.get("updated_at", None)
|
|
100
|
-
|
|
101
|
-
self.is_visible: bool | None = data.get("is_visible", None)
|
|
102
|
-
self.is_protected: bool | None = data.get("is_protected", None)
|
|
103
|
-
|
|
104
|
-
self.source: NodeProperty | None = None
|
|
105
|
-
self.owner: NodeProperty | None = None
|
|
106
|
-
|
|
107
|
-
for prop_name in self._properties_object:
|
|
108
|
-
if data.get(prop_name):
|
|
109
|
-
setattr(self, prop_name, NodeProperty(data=data.get(prop_name))) # type: ignore[arg-type]
|
|
33
|
+
def generate_relationship_property(node: InfrahubNode | InfrahubNodeSync, name: str) -> property:
|
|
34
|
+
"""Generates a property that stores values under a private non-public name.
|
|
110
35
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
36
|
+
Args:
|
|
37
|
+
node (Union[InfrahubNode, InfrahubNodeSync]): The node instance.
|
|
38
|
+
name (str): The name of the relationship property.
|
|
114
39
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
self._value = value
|
|
118
|
-
self.value_has_been_mutated = True
|
|
40
|
+
Returns:
|
|
41
|
+
A property object for managing the relationship.
|
|
119
42
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
43
|
+
"""
|
|
44
|
+
internal_name = "_" + name.lower()
|
|
45
|
+
external_name = name
|
|
123
46
|
|
|
124
|
-
|
|
125
|
-
|
|
47
|
+
def prop_getter(self: InfrahubNodeBase) -> Any:
|
|
48
|
+
return getattr(self, internal_name)
|
|
126
49
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
else:
|
|
131
|
-
var_name = f"value_{UUIDT.new().hex}"
|
|
132
|
-
variables[var_name] = self.value
|
|
133
|
-
data["value"] = f"${var_name}"
|
|
134
|
-
elif isinstance(self.value, get_args(IP_TYPES)):
|
|
135
|
-
data["value"] = self.value.with_prefixlen
|
|
136
|
-
elif isinstance(self.value, InfrahubNodeBase) and self.value.is_resource_pool():
|
|
137
|
-
data["from_pool"] = {"id": self.value.id}
|
|
50
|
+
def prop_setter(self: InfrahubNodeBase, value: Any) -> None:
|
|
51
|
+
if isinstance(value, RelatedNodeBase) or value is None:
|
|
52
|
+
setattr(self, internal_name, value)
|
|
138
53
|
else:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
data[prop_name] = getattr(self, prop_name)._generate_input_data()
|
|
148
|
-
|
|
149
|
-
return {"data": data, "variables": variables}
|
|
150
|
-
|
|
151
|
-
def _generate_query_data(self, property: bool = False) -> dict | None:
|
|
152
|
-
data: dict[str, Any] = {"value": None}
|
|
153
|
-
|
|
154
|
-
if property:
|
|
155
|
-
data.update({"is_default": None, "is_from_profile": None})
|
|
156
|
-
|
|
157
|
-
for prop_name in self._properties_flag:
|
|
158
|
-
data[prop_name] = None
|
|
159
|
-
for prop_name in self._properties_object:
|
|
160
|
-
data[prop_name] = {"id": None, "display_label": None, "__typename": None}
|
|
161
|
-
|
|
162
|
-
return data
|
|
163
|
-
|
|
164
|
-
def _generate_mutation_query(self) -> dict[str, Any]:
|
|
165
|
-
if isinstance(self.value, InfrahubNodeBase) and self.value.is_resource_pool():
|
|
166
|
-
# If it points to a pool, ask for the value of the pool allocated resource
|
|
167
|
-
return {self.name: {"value": None}}
|
|
168
|
-
return {}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class RelatedNodeBase:
|
|
172
|
-
"""Base class for representing a related node in a relationship."""
|
|
173
|
-
|
|
174
|
-
def __init__(self, branch: str, schema: RelationshipSchemaAPI, data: Any | dict, name: str | None = None):
|
|
175
|
-
"""
|
|
176
|
-
Args:
|
|
177
|
-
branch (str): The branch where the related node resides.
|
|
178
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
179
|
-
data (Union[Any, dict]): Data representing the related node.
|
|
180
|
-
name (Optional[str]): The name of the related node.
|
|
181
|
-
"""
|
|
182
|
-
self.schema = schema
|
|
183
|
-
self.name = name
|
|
184
|
-
|
|
185
|
-
self._branch = branch
|
|
186
|
-
|
|
187
|
-
self._properties_flag = PROPERTIES_FLAG
|
|
188
|
-
self._properties_object = PROPERTIES_OBJECT
|
|
189
|
-
self._properties = self._properties_flag + self._properties_object
|
|
190
|
-
|
|
191
|
-
self._peer = None
|
|
192
|
-
self._id: str | None = None
|
|
193
|
-
self._hfid: list[str] | None = None
|
|
194
|
-
self._display_label: str | None = None
|
|
195
|
-
self._typename: str | None = None
|
|
196
|
-
|
|
197
|
-
if isinstance(data, (InfrahubNode, InfrahubNodeSync)):
|
|
198
|
-
self._peer = data
|
|
199
|
-
for prop in self._properties:
|
|
200
|
-
setattr(self, prop, None)
|
|
201
|
-
elif isinstance(data, list):
|
|
202
|
-
data = {"hfid": data}
|
|
203
|
-
elif not isinstance(data, dict):
|
|
204
|
-
data = {"id": data}
|
|
205
|
-
|
|
206
|
-
if isinstance(data, dict):
|
|
207
|
-
# To support both with and without pagination, we split data into node_data and properties_data
|
|
208
|
-
# We should probably clean that once we'll remove the code without pagination.
|
|
209
|
-
node_data = data.get("node", data)
|
|
210
|
-
properties_data = data.get("properties", data)
|
|
211
|
-
|
|
212
|
-
if node_data:
|
|
213
|
-
self._id = node_data.get("id", None)
|
|
214
|
-
self._hfid = node_data.get("hfid", None)
|
|
215
|
-
self._kind = node_data.get("kind", None)
|
|
216
|
-
self._display_label = node_data.get("display_label", None)
|
|
217
|
-
self._typename = node_data.get("__typename", None)
|
|
218
|
-
|
|
219
|
-
self.updated_at: str | None = data.get("updated_at", data.get("_relation__updated_at", None))
|
|
220
|
-
|
|
221
|
-
# FIXME, we won't need that once we are only supporting paginated results
|
|
222
|
-
if self._typename and self._typename.startswith("Related"):
|
|
223
|
-
self._typename = self._typename[7:]
|
|
224
|
-
|
|
225
|
-
for prop in self._properties:
|
|
226
|
-
prop_data = properties_data.get(prop, properties_data.get(f"_relation__{prop}", None))
|
|
227
|
-
if prop_data and isinstance(prop_data, dict) and "id" in prop_data:
|
|
228
|
-
setattr(self, prop, prop_data["id"])
|
|
229
|
-
elif prop_data and isinstance(prop_data, (str, bool)):
|
|
230
|
-
setattr(self, prop, prop_data)
|
|
231
|
-
else:
|
|
232
|
-
setattr(self, prop, None)
|
|
233
|
-
|
|
234
|
-
@property
|
|
235
|
-
def id(self) -> str | None:
|
|
236
|
-
if self._peer:
|
|
237
|
-
return self._peer.id
|
|
238
|
-
return self._id
|
|
239
|
-
|
|
240
|
-
@property
|
|
241
|
-
def hfid(self) -> list[Any] | None:
|
|
242
|
-
if self._peer:
|
|
243
|
-
return self._peer.hfid
|
|
244
|
-
return self._hfid
|
|
245
|
-
|
|
246
|
-
@property
|
|
247
|
-
def hfid_str(self) -> str | None:
|
|
248
|
-
if self._peer and self.hfid:
|
|
249
|
-
return self._peer.get_human_friendly_id_as_string(include_kind=True)
|
|
250
|
-
return None
|
|
251
|
-
|
|
252
|
-
@property
|
|
253
|
-
def is_resource_pool(self) -> bool:
|
|
254
|
-
if self._peer:
|
|
255
|
-
return self._peer.is_resource_pool()
|
|
256
|
-
return False
|
|
257
|
-
|
|
258
|
-
@property
|
|
259
|
-
def initialized(self) -> bool:
|
|
260
|
-
return bool(self.id) or bool(self.hfid)
|
|
261
|
-
|
|
262
|
-
@property
|
|
263
|
-
def display_label(self) -> str | None:
|
|
264
|
-
if self._peer:
|
|
265
|
-
return self._peer.display_label
|
|
266
|
-
return self._display_label
|
|
267
|
-
|
|
268
|
-
@property
|
|
269
|
-
def typename(self) -> str | None:
|
|
270
|
-
if self._peer:
|
|
271
|
-
return self._peer.typename
|
|
272
|
-
return self._typename
|
|
273
|
-
|
|
274
|
-
def _generate_input_data(self, allocate_from_pool: bool = False) -> dict[str, Any]:
|
|
275
|
-
data: dict[str, Any] = {}
|
|
276
|
-
|
|
277
|
-
if self.is_resource_pool and allocate_from_pool:
|
|
278
|
-
return {"from_pool": {"id": self.id}}
|
|
279
|
-
|
|
280
|
-
if self.id is not None:
|
|
281
|
-
data["id"] = self.id
|
|
282
|
-
elif self.hfid is not None:
|
|
283
|
-
data["hfid"] = self.hfid
|
|
284
|
-
if self._kind is not None:
|
|
285
|
-
data["kind"] = self._kind
|
|
286
|
-
|
|
287
|
-
for prop_name in self._properties:
|
|
288
|
-
if getattr(self, prop_name) is not None:
|
|
289
|
-
data[f"_relation__{prop_name}"] = getattr(self, prop_name)
|
|
290
|
-
|
|
291
|
-
return data
|
|
292
|
-
|
|
293
|
-
def _generate_mutation_query(self) -> dict[str, Any]:
|
|
294
|
-
if self.name and self.is_resource_pool:
|
|
295
|
-
# If a related node points to a pool, ask for the ID of the pool allocated resource
|
|
296
|
-
return {self.name: {"node": {"id": None, "display_label": None, "__typename": None}}}
|
|
297
|
-
return {}
|
|
298
|
-
|
|
299
|
-
@classmethod
|
|
300
|
-
def _generate_query_data(cls, peer_data: dict[str, Any] | None = None, property: bool = False) -> dict:
|
|
301
|
-
"""Generates the basic structure of a GraphQL query for a single relationship.
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
peer_data (dict[str, Union[Any, Dict]], optional): Additional data to be included in the query for the node.
|
|
305
|
-
This is used to add extra fields when prefetching related node data.
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
Dict: A dictionary representing the basic structure of a GraphQL query, including the node's ID, display label,
|
|
309
|
-
and typename. The method also includes additional properties and any peer_data provided.
|
|
310
|
-
"""
|
|
311
|
-
data: dict[str, Any] = {"node": {"id": None, "hfid": None, "display_label": None, "__typename": None}}
|
|
312
|
-
properties: dict[str, Any] = {}
|
|
313
|
-
|
|
314
|
-
if property:
|
|
315
|
-
for prop_name in PROPERTIES_FLAG:
|
|
316
|
-
properties[prop_name] = None
|
|
317
|
-
for prop_name in PROPERTIES_OBJECT:
|
|
318
|
-
properties[prop_name] = {"id": None, "display_label": None, "__typename": None}
|
|
319
|
-
|
|
320
|
-
if properties:
|
|
321
|
-
data["properties"] = properties
|
|
322
|
-
if peer_data:
|
|
323
|
-
data["node"].update(peer_data)
|
|
324
|
-
|
|
325
|
-
return data
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
class RelatedNode(RelatedNodeBase):
|
|
329
|
-
"""Represents a RelatedNodeBase in an asynchronous context."""
|
|
330
|
-
|
|
331
|
-
def __init__(
|
|
332
|
-
self,
|
|
333
|
-
client: InfrahubClient,
|
|
334
|
-
branch: str,
|
|
335
|
-
schema: RelationshipSchemaAPI,
|
|
336
|
-
data: Any | dict,
|
|
337
|
-
name: str | None = None,
|
|
338
|
-
):
|
|
339
|
-
"""
|
|
340
|
-
Args:
|
|
341
|
-
client (InfrahubClient): The client used to interact with the backend asynchronously.
|
|
342
|
-
branch (str): The branch where the related node resides.
|
|
343
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
344
|
-
data (Union[Any, dict]): Data representing the related node.
|
|
345
|
-
name (Optional[str]): The name of the related node.
|
|
346
|
-
"""
|
|
347
|
-
self._client = client
|
|
348
|
-
super().__init__(branch=branch, schema=schema, data=data, name=name)
|
|
349
|
-
|
|
350
|
-
async def fetch(self, timeout: int | None = None) -> None:
|
|
351
|
-
if not self.id or not self.typename:
|
|
352
|
-
raise Error("Unable to fetch the peer, id and/or typename are not defined")
|
|
353
|
-
|
|
354
|
-
self._peer = await self._client.get(
|
|
355
|
-
kind=self.typename, id=self.id, populate_store=True, branch=self._branch, timeout=timeout
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
@property
|
|
359
|
-
def peer(self) -> InfrahubNode:
|
|
360
|
-
return self.get()
|
|
361
|
-
|
|
362
|
-
def get(self) -> InfrahubNode:
|
|
363
|
-
if self._peer:
|
|
364
|
-
return self._peer # type: ignore[return-value]
|
|
365
|
-
|
|
366
|
-
if self.id and self.typename:
|
|
367
|
-
return self._client.store.get(key=self.id, kind=self.typename, branch=self._branch) # type: ignore[return-value]
|
|
368
|
-
|
|
369
|
-
if self.hfid_str:
|
|
370
|
-
return self._client.store.get(key=self.hfid_str, branch=self._branch) # type: ignore[return-value]
|
|
371
|
-
|
|
372
|
-
raise ValueError("Node must have at least one identifier (ID or HFID) to query it.")
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
class RelatedNodeSync(RelatedNodeBase):
|
|
376
|
-
"""Represents a related node in a synchronous context."""
|
|
377
|
-
|
|
378
|
-
def __init__(
|
|
379
|
-
self,
|
|
380
|
-
client: InfrahubClientSync,
|
|
381
|
-
branch: str,
|
|
382
|
-
schema: RelationshipSchemaAPI,
|
|
383
|
-
data: Any | dict,
|
|
384
|
-
name: str | None = None,
|
|
385
|
-
):
|
|
386
|
-
"""
|
|
387
|
-
Args:
|
|
388
|
-
client (InfrahubClientSync): The client used to interact with the backend synchronously.
|
|
389
|
-
branch (str): The branch where the related node resides.
|
|
390
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
391
|
-
data (Union[Any, dict]): Data representing the related node.
|
|
392
|
-
name (Optional[str]): The name of the related node.
|
|
393
|
-
"""
|
|
394
|
-
self._client = client
|
|
395
|
-
super().__init__(branch=branch, schema=schema, data=data, name=name)
|
|
396
|
-
|
|
397
|
-
def fetch(self, timeout: int | None = None) -> None:
|
|
398
|
-
if not self.id or not self.typename:
|
|
399
|
-
raise Error("Unable to fetch the peer, id and/or typename are not defined")
|
|
400
|
-
|
|
401
|
-
self._peer = self._client.get(
|
|
402
|
-
kind=self.typename, id=self.id, populate_store=True, branch=self._branch, timeout=timeout
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
@property
|
|
406
|
-
def peer(self) -> InfrahubNodeSync:
|
|
407
|
-
return self.get()
|
|
408
|
-
|
|
409
|
-
def get(self) -> InfrahubNodeSync:
|
|
410
|
-
if self._peer:
|
|
411
|
-
return self._peer # type: ignore[return-value]
|
|
412
|
-
|
|
413
|
-
if self.id and self.typename:
|
|
414
|
-
return self._client.store.get(key=self.id, kind=self.typename, branch=self._branch) # type: ignore[return-value]
|
|
415
|
-
|
|
416
|
-
if self.hfid_str:
|
|
417
|
-
return self._client.store.get(key=self.hfid_str, branch=self._branch) # type: ignore[return-value]
|
|
418
|
-
|
|
419
|
-
raise ValueError("Node must have at least one identifier (ID or HFID) to query it.")
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
class RelationshipManagerBase:
|
|
423
|
-
"""Base class for RelationshipManager and RelationshipManagerSync"""
|
|
424
|
-
|
|
425
|
-
def __init__(self, name: str, branch: str, schema: RelationshipSchemaAPI):
|
|
426
|
-
"""
|
|
427
|
-
Args:
|
|
428
|
-
name (str): The name of the relationship.
|
|
429
|
-
branch (str): The branch where the relationship resides.
|
|
430
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
431
|
-
"""
|
|
432
|
-
self.initialized: bool = False
|
|
433
|
-
self._has_update: bool = False
|
|
434
|
-
self.name = name
|
|
435
|
-
self.schema = schema
|
|
436
|
-
self.branch = branch
|
|
437
|
-
|
|
438
|
-
self._properties_flag = PROPERTIES_FLAG
|
|
439
|
-
self._properties_object = PROPERTIES_OBJECT
|
|
440
|
-
self._properties = self._properties_flag + self._properties_object
|
|
441
|
-
|
|
442
|
-
self.peers: list[RelatedNode | RelatedNodeSync] = []
|
|
443
|
-
|
|
444
|
-
@property
|
|
445
|
-
def peer_ids(self) -> list[str]:
|
|
446
|
-
return [peer.id for peer in self.peers if peer.id]
|
|
447
|
-
|
|
448
|
-
@property
|
|
449
|
-
def peer_hfids(self) -> list[list[Any]]:
|
|
450
|
-
return [peer.hfid for peer in self.peers if peer.hfid]
|
|
451
|
-
|
|
452
|
-
@property
|
|
453
|
-
def peer_hfids_str(self) -> list[str]:
|
|
454
|
-
return [peer.hfid_str for peer in self.peers if peer.hfid_str]
|
|
455
|
-
|
|
456
|
-
@property
|
|
457
|
-
def has_update(self) -> bool:
|
|
458
|
-
return self._has_update
|
|
459
|
-
|
|
460
|
-
def _generate_input_data(self, allocate_from_pool: bool = False) -> list[dict]:
|
|
461
|
-
return [peer._generate_input_data(allocate_from_pool=allocate_from_pool) for peer in self.peers]
|
|
462
|
-
|
|
463
|
-
def _generate_mutation_query(self) -> dict[str, Any]:
|
|
464
|
-
# Does nothing for now
|
|
465
|
-
return {}
|
|
466
|
-
|
|
467
|
-
@classmethod
|
|
468
|
-
def _generate_query_data(cls, peer_data: dict[str, Any] | None = None, property: bool = False) -> dict:
|
|
469
|
-
"""Generates the basic structure of a GraphQL query for relationships with multiple nodes.
|
|
470
|
-
|
|
471
|
-
Args:
|
|
472
|
-
peer_data (dict[str, Union[Any, Dict]], optional): Additional data to be included in the query for each node.
|
|
473
|
-
This is used to add extra fields when prefetching related node data in many-to-many relationships.
|
|
474
|
-
|
|
475
|
-
Returns:
|
|
476
|
-
Dict: A dictionary representing the basic structure of a GraphQL query for multiple related nodes.
|
|
477
|
-
It includes count, edges, and node information (ID, display label, and typename), along with additional properties
|
|
478
|
-
and any peer_data provided.
|
|
479
|
-
"""
|
|
480
|
-
data: dict[str, Any] = {
|
|
481
|
-
"count": None,
|
|
482
|
-
"edges": {"node": {"id": None, "hfid": None, "display_label": None, "__typename": None}},
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
properties: dict[str, Any] = {}
|
|
486
|
-
if property:
|
|
487
|
-
for prop_name in PROPERTIES_FLAG:
|
|
488
|
-
properties[prop_name] = None
|
|
489
|
-
for prop_name in PROPERTIES_OBJECT:
|
|
490
|
-
properties[prop_name] = {"id": None, "display_label": None, "__typename": None}
|
|
491
|
-
|
|
492
|
-
if properties:
|
|
493
|
-
data["edges"]["properties"] = properties
|
|
494
|
-
if peer_data:
|
|
495
|
-
data["edges"]["node"].update(peer_data)
|
|
496
|
-
|
|
497
|
-
return data
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
class RelationshipManager(RelationshipManagerBase):
|
|
501
|
-
"""Manages relationships of a node in an asynchronous context."""
|
|
502
|
-
|
|
503
|
-
def __init__(
|
|
504
|
-
self,
|
|
505
|
-
name: str,
|
|
506
|
-
client: InfrahubClient,
|
|
507
|
-
node: InfrahubNode,
|
|
508
|
-
branch: str,
|
|
509
|
-
schema: RelationshipSchemaAPI,
|
|
510
|
-
data: Any | dict,
|
|
511
|
-
):
|
|
512
|
-
"""
|
|
513
|
-
Args:
|
|
514
|
-
name (str): The name of the relationship.
|
|
515
|
-
client (InfrahubClient): The client used to interact with the backend.
|
|
516
|
-
node (InfrahubNode): The node to which the relationship belongs.
|
|
517
|
-
branch (str): The branch where the relationship resides.
|
|
518
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
519
|
-
data (Union[Any, dict]): Initial data for the relationships.
|
|
520
|
-
"""
|
|
521
|
-
self.client = client
|
|
522
|
-
self.node = node
|
|
523
|
-
|
|
524
|
-
super().__init__(name=name, schema=schema, branch=branch)
|
|
525
|
-
|
|
526
|
-
self.initialized = data is not None
|
|
527
|
-
self._has_update = False
|
|
528
|
-
|
|
529
|
-
if data is None:
|
|
530
|
-
return
|
|
531
|
-
|
|
532
|
-
if isinstance(data, list):
|
|
533
|
-
for item in data:
|
|
534
|
-
self.peers.append(
|
|
535
|
-
RelatedNode(name=name, client=self.client, branch=self.branch, schema=schema, data=item)
|
|
54
|
+
schema = [rel for rel in self._schema.relationships if rel.name == external_name][0]
|
|
55
|
+
if isinstance(node, InfrahubNode):
|
|
56
|
+
setattr(
|
|
57
|
+
self,
|
|
58
|
+
internal_name,
|
|
59
|
+
RelatedNode(
|
|
60
|
+
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
|
|
61
|
+
),
|
|
536
62
|
)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
63
|
+
else:
|
|
64
|
+
setattr(
|
|
65
|
+
self,
|
|
66
|
+
internal_name,
|
|
67
|
+
RelatedNodeSync(
|
|
68
|
+
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
|
|
69
|
+
),
|
|
541
70
|
)
|
|
542
|
-
else:
|
|
543
|
-
raise ValueError(f"Unexpected format for {name} found a {type(data)}, {data}")
|
|
544
|
-
|
|
545
|
-
def __getitem__(self, item: int) -> RelatedNode:
|
|
546
|
-
return self.peers[item] # type: ignore[return-value]
|
|
547
|
-
|
|
548
|
-
async def fetch(self) -> None:
|
|
549
|
-
if not self.initialized:
|
|
550
|
-
exclude = self.node._schema.relationship_names + self.node._schema.attribute_names
|
|
551
|
-
exclude.remove(self.schema.name)
|
|
552
|
-
node = await self.client.get(
|
|
553
|
-
kind=self.node._schema.kind,
|
|
554
|
-
id=self.node.id,
|
|
555
|
-
branch=self.branch,
|
|
556
|
-
include=[self.schema.name],
|
|
557
|
-
exclude=exclude,
|
|
558
|
-
)
|
|
559
|
-
rm = getattr(node, self.schema.name)
|
|
560
|
-
self.peers = rm.peers
|
|
561
|
-
self.initialized = True
|
|
562
|
-
|
|
563
|
-
for peer in self.peers:
|
|
564
|
-
await peer.fetch() # type: ignore[misc]
|
|
565
|
-
|
|
566
|
-
def add(self, data: str | RelatedNode | dict) -> None:
|
|
567
|
-
"""Add a new peer to this relationship."""
|
|
568
|
-
if not self.initialized:
|
|
569
|
-
raise UninitializedError("Must call fetch() on RelationshipManager before editing members")
|
|
570
|
-
new_node = RelatedNode(schema=self.schema, client=self.client, branch=self.branch, data=data)
|
|
571
|
-
|
|
572
|
-
if (new_node.id and new_node.id not in self.peer_ids) or (
|
|
573
|
-
new_node.hfid and new_node.hfid not in self.peer_hfids
|
|
574
|
-
):
|
|
575
|
-
self.peers.append(new_node)
|
|
576
|
-
self._has_update = True
|
|
577
|
-
|
|
578
|
-
def extend(self, data: Iterable[str | RelatedNode | dict]) -> None:
|
|
579
|
-
"""Add new peers to this relationship."""
|
|
580
|
-
for d in data:
|
|
581
|
-
self.add(d)
|
|
582
|
-
|
|
583
|
-
def remove(self, data: str | RelatedNode | dict) -> None:
|
|
584
|
-
if not self.initialized:
|
|
585
|
-
raise UninitializedError("Must call fetch() on RelationshipManager before editing members")
|
|
586
|
-
node_to_remove = RelatedNode(schema=self.schema, client=self.client, branch=self.branch, data=data)
|
|
587
|
-
|
|
588
|
-
if node_to_remove.id and node_to_remove.id in self.peer_ids:
|
|
589
|
-
idx = self.peer_ids.index(node_to_remove.id)
|
|
590
|
-
if self.peers[idx].id != node_to_remove.id:
|
|
591
|
-
raise IndexError(f"Unexpected situation, the node with the index {idx} should be {node_to_remove.id}")
|
|
592
|
-
|
|
593
|
-
self.peers.pop(idx)
|
|
594
|
-
self._has_update = True
|
|
595
71
|
|
|
596
|
-
|
|
597
|
-
idx = self.peer_hfids.index(node_to_remove.hfid)
|
|
598
|
-
if self.peers[idx].hfid != node_to_remove.hfid:
|
|
599
|
-
raise IndexError(f"Unexpected situation, the node with the index {idx} should be {node_to_remove.hfid}")
|
|
600
|
-
|
|
601
|
-
self.peers.pop(idx)
|
|
602
|
-
self._has_update = True
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
class RelationshipManagerSync(RelationshipManagerBase):
|
|
606
|
-
"""Manages relationships of a node in a synchronous context."""
|
|
607
|
-
|
|
608
|
-
def __init__(
|
|
609
|
-
self,
|
|
610
|
-
name: str,
|
|
611
|
-
client: InfrahubClientSync,
|
|
612
|
-
node: InfrahubNodeSync,
|
|
613
|
-
branch: str,
|
|
614
|
-
schema: RelationshipSchemaAPI,
|
|
615
|
-
data: Any | dict,
|
|
616
|
-
):
|
|
617
|
-
"""
|
|
618
|
-
Args:
|
|
619
|
-
name (str): The name of the relationship.
|
|
620
|
-
client (InfrahubClientSync): The client used to interact with the backend synchronously.
|
|
621
|
-
node (InfrahubNodeSync): The node to which the relationship belongs.
|
|
622
|
-
branch (str): The branch where the relationship resides.
|
|
623
|
-
schema (RelationshipSchema): The schema of the relationship.
|
|
624
|
-
data (Union[Any, dict]): Initial data for the relationships.
|
|
625
|
-
"""
|
|
626
|
-
self.client = client
|
|
627
|
-
self.node = node
|
|
628
|
-
|
|
629
|
-
super().__init__(name=name, schema=schema, branch=branch)
|
|
630
|
-
|
|
631
|
-
self.initialized = data is not None
|
|
632
|
-
self._has_update = False
|
|
633
|
-
|
|
634
|
-
if data is None:
|
|
635
|
-
return
|
|
636
|
-
|
|
637
|
-
if isinstance(data, list):
|
|
638
|
-
for item in data:
|
|
639
|
-
self.peers.append(
|
|
640
|
-
RelatedNodeSync(name=name, client=self.client, branch=self.branch, schema=schema, data=item)
|
|
641
|
-
)
|
|
642
|
-
elif isinstance(data, dict) and "edges" in data:
|
|
643
|
-
for item in data["edges"]:
|
|
644
|
-
self.peers.append(
|
|
645
|
-
RelatedNodeSync(name=name, client=self.client, branch=self.branch, schema=schema, data=item)
|
|
646
|
-
)
|
|
647
|
-
else:
|
|
648
|
-
raise ValueError(f"Unexpected format for {name} found a {type(data)}, {data}")
|
|
649
|
-
|
|
650
|
-
def __getitem__(self, item: int) -> RelatedNodeSync:
|
|
651
|
-
return self.peers[item] # type: ignore[return-value]
|
|
652
|
-
|
|
653
|
-
def fetch(self) -> None:
|
|
654
|
-
if not self.initialized:
|
|
655
|
-
exclude = self.node._schema.relationship_names + self.node._schema.attribute_names
|
|
656
|
-
exclude.remove(self.schema.name)
|
|
657
|
-
node = self.client.get(
|
|
658
|
-
kind=self.node._schema.kind,
|
|
659
|
-
id=self.node.id,
|
|
660
|
-
branch=self.branch,
|
|
661
|
-
include=[self.schema.name],
|
|
662
|
-
exclude=exclude,
|
|
663
|
-
)
|
|
664
|
-
rm = getattr(node, self.schema.name)
|
|
665
|
-
self.peers = rm.peers
|
|
666
|
-
self.initialized = True
|
|
667
|
-
|
|
668
|
-
for peer in self.peers:
|
|
669
|
-
peer.fetch()
|
|
670
|
-
|
|
671
|
-
def add(self, data: str | RelatedNodeSync | dict) -> None:
|
|
672
|
-
"""Add a new peer to this relationship."""
|
|
673
|
-
if not self.initialized:
|
|
674
|
-
raise UninitializedError("Must call fetch() on RelationshipManager before editing members")
|
|
675
|
-
new_node = RelatedNodeSync(schema=self.schema, client=self.client, branch=self.branch, data=data)
|
|
676
|
-
|
|
677
|
-
if (new_node.id and new_node.id not in self.peer_ids) or (
|
|
678
|
-
new_node.hfid and new_node.hfid not in self.peer_hfids
|
|
679
|
-
):
|
|
680
|
-
self.peers.append(new_node)
|
|
681
|
-
self._has_update = True
|
|
682
|
-
|
|
683
|
-
def extend(self, data: Iterable[str | RelatedNodeSync | dict]) -> None:
|
|
684
|
-
"""Add new peers to this relationship."""
|
|
685
|
-
for d in data:
|
|
686
|
-
self.add(d)
|
|
687
|
-
|
|
688
|
-
def remove(self, data: str | RelatedNodeSync | dict) -> None:
|
|
689
|
-
if not self.initialized:
|
|
690
|
-
raise UninitializedError("Must call fetch() on RelationshipManager before editing members")
|
|
691
|
-
node_to_remove = RelatedNodeSync(schema=self.schema, client=self.client, branch=self.branch, data=data)
|
|
692
|
-
|
|
693
|
-
if node_to_remove.id and node_to_remove.id in self.peer_ids:
|
|
694
|
-
idx = self.peer_ids.index(node_to_remove.id)
|
|
695
|
-
if self.peers[idx].id != node_to_remove.id:
|
|
696
|
-
raise IndexError(f"Unexpected situation, the node with the index {idx} should be {node_to_remove.id}")
|
|
697
|
-
self.peers.pop(idx)
|
|
698
|
-
self._has_update = True
|
|
699
|
-
|
|
700
|
-
elif node_to_remove.hfid and node_to_remove.hfid in self.peer_hfids:
|
|
701
|
-
idx = self.peer_hfids.index(node_to_remove.hfid)
|
|
702
|
-
if self.peers[idx].hfid != node_to_remove.hfid:
|
|
703
|
-
raise IndexError(f"Unexpected situation, the node with the index {idx} should be {node_to_remove.hfid}")
|
|
704
|
-
|
|
705
|
-
self.peers.pop(idx)
|
|
706
|
-
self._has_update = True
|
|
72
|
+
return property(prop_getter, prop_setter)
|
|
707
73
|
|
|
708
74
|
|
|
709
75
|
class InfrahubNodeBase:
|
|
@@ -2179,68 +1545,3 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
2179
1545
|
if response[graphql_query_name].get("count", 0):
|
|
2180
1546
|
return [edge["node"] for edge in response[graphql_query_name]["edges"]]
|
|
2181
1547
|
return []
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
class NodeProperty:
|
|
2185
|
-
"""Represents a property of a node, typically used for metadata like display labels."""
|
|
2186
|
-
|
|
2187
|
-
def __init__(self, data: dict | str):
|
|
2188
|
-
"""
|
|
2189
|
-
Args:
|
|
2190
|
-
data (Union[dict, str]): Data representing the node property.
|
|
2191
|
-
"""
|
|
2192
|
-
self.id = None
|
|
2193
|
-
self.display_label = None
|
|
2194
|
-
self.typename = None
|
|
2195
|
-
|
|
2196
|
-
if isinstance(data, str):
|
|
2197
|
-
self.id = data
|
|
2198
|
-
elif isinstance(data, dict):
|
|
2199
|
-
self.id = data.get("id", None)
|
|
2200
|
-
self.display_label = data.get("display_label", None)
|
|
2201
|
-
self.typename = data.get("__typename", None)
|
|
2202
|
-
|
|
2203
|
-
def _generate_input_data(self) -> str | None:
|
|
2204
|
-
return self.id
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
def generate_relationship_property(node: InfrahubNode | InfrahubNodeSync, name: str) -> property:
|
|
2208
|
-
"""Generates a property that stores values under a private non-public name.
|
|
2209
|
-
|
|
2210
|
-
Args:
|
|
2211
|
-
node (Union[InfrahubNode, InfrahubNodeSync]): The node instance.
|
|
2212
|
-
name (str): The name of the relationship property.
|
|
2213
|
-
|
|
2214
|
-
Returns:
|
|
2215
|
-
A property object for managing the relationship.
|
|
2216
|
-
|
|
2217
|
-
"""
|
|
2218
|
-
internal_name = "_" + name.lower()
|
|
2219
|
-
external_name = name
|
|
2220
|
-
|
|
2221
|
-
def prop_getter(self: InfrahubNodeBase) -> Any:
|
|
2222
|
-
return getattr(self, internal_name)
|
|
2223
|
-
|
|
2224
|
-
def prop_setter(self: InfrahubNodeBase, value: Any) -> None:
|
|
2225
|
-
if isinstance(value, RelatedNodeBase) or value is None:
|
|
2226
|
-
setattr(self, internal_name, value)
|
|
2227
|
-
else:
|
|
2228
|
-
schema = [rel for rel in self._schema.relationships if rel.name == external_name][0]
|
|
2229
|
-
if isinstance(node, InfrahubNode):
|
|
2230
|
-
setattr(
|
|
2231
|
-
self,
|
|
2232
|
-
internal_name,
|
|
2233
|
-
RelatedNode(
|
|
2234
|
-
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
|
|
2235
|
-
),
|
|
2236
|
-
)
|
|
2237
|
-
else:
|
|
2238
|
-
setattr(
|
|
2239
|
-
self,
|
|
2240
|
-
internal_name,
|
|
2241
|
-
RelatedNodeSync(
|
|
2242
|
-
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
|
|
2243
|
-
),
|
|
2244
|
-
)
|
|
2245
|
-
|
|
2246
|
-
return property(prop_getter, prop_setter)
|