infrahub-server 1.2.11__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/database.py +1 -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 +83 -68
- 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/graph/__init__.py +1 -1
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/__init__.py +6 -0
- 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 +662 -0
- infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
- infrahub/core/migrations/query/attribute_add.py +14 -11
- 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 +109 -12
- 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 +100 -0
- 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 +25 -176
- 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 +4 -7
- infrahub/patch/queries/delete_duplicated_edges.py +45 -39
- 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 +6 -9
- 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.11.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +5 -5
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +197 -168
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +1 -1
- infrahub_testcontainers/container.py +239 -65
- infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
- infrahub_testcontainers/docker-compose.test.yml +2 -1
- infrahub_testcontainers/helpers.py +23 -3
- infrahub_testcontainers/plugin.py +9 -0
- 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/patch/queries/consolidate_duplicated_nodes.py +0 -109
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,709 +1,31 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import ipaddress
|
|
4
|
-
import re
|
|
5
3
|
from collections.abc import Iterable
|
|
6
4
|
from copy import copy
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from ..constants import InfrahubClientMode
|
|
8
|
+
from ..exceptions import FeatureNotSupportedError, NodeNotFoundError, ResourceNotDefinedError, SchemaNotFoundError
|
|
9
|
+
from ..graphql import Mutation, Query
|
|
10
|
+
from ..schema import GenericSchemaAPI, RelationshipCardinality, RelationshipKind
|
|
11
|
+
from ..utils import compare_lists, generate_short_id, get_flat_value
|
|
12
|
+
from .attribute import Attribute
|
|
13
|
+
from .constants import (
|
|
14
|
+
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
15
|
+
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
16
|
+
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
17
|
+
PROPERTIES_OBJECT,
|
|
15
18
|
)
|
|
16
|
-
from .
|
|
17
|
-
from .
|
|
18
|
-
from .utils import compare_lists, generate_short_id, get_flat_value
|
|
19
|
-
from .uuidt import UUIDT
|
|
19
|
+
from .related_node import RelatedNode, RelatedNodeBase, RelatedNodeSync
|
|
20
|
+
from .relationship import RelationshipManager, RelationshipManagerBase, RelationshipManagerSync
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from typing_extensions import Self
|
|
23
24
|
|
|
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}
|
|
76
|
-
|
|
77
|
-
self._properties_flag = PROPERTIES_FLAG
|
|
78
|
-
self._properties_object = PROPERTIES_OBJECT
|
|
79
|
-
self._properties = self._properties_flag + self._properties_object
|
|
80
|
-
|
|
81
|
-
self._read_only = ["updated_at", "is_inherited"]
|
|
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]
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def value(self) -> Any:
|
|
113
|
-
return self._value
|
|
114
|
-
|
|
115
|
-
@value.setter
|
|
116
|
-
def value(self, value: Any) -> None:
|
|
117
|
-
self._value = value
|
|
118
|
-
self.value_has_been_mutated = True
|
|
119
|
-
|
|
120
|
-
def _generate_input_data(self) -> dict | None:
|
|
121
|
-
data: dict[str, Any] = {}
|
|
122
|
-
variables: dict[str, Any] = {}
|
|
123
|
-
|
|
124
|
-
if self.value is None:
|
|
125
|
-
return data
|
|
126
|
-
|
|
127
|
-
if isinstance(self.value, str):
|
|
128
|
-
if SAFE_VALUE.match(self.value):
|
|
129
|
-
data["value"] = self.value
|
|
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}
|
|
138
|
-
else:
|
|
139
|
-
data["value"] = self.value
|
|
140
|
-
|
|
141
|
-
for prop_name in self._properties_flag:
|
|
142
|
-
if getattr(self, prop_name) is not None:
|
|
143
|
-
data[prop_name] = getattr(self, prop_name)
|
|
144
|
-
|
|
145
|
-
for prop_name in self._properties_object:
|
|
146
|
-
if getattr(self, prop_name) is not None:
|
|
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)
|
|
536
|
-
)
|
|
537
|
-
elif isinstance(data, dict) and "edges" in data:
|
|
538
|
-
for item in data["edges"]:
|
|
539
|
-
self.peers.append(
|
|
540
|
-
RelatedNode(name=name, client=self.client, branch=self.branch, schema=schema, data=item)
|
|
541
|
-
)
|
|
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
|
-
|
|
596
|
-
elif node_to_remove.hfid and node_to_remove.hfid in self.peer_hfids:
|
|
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
|
|
25
|
+
from ..client import InfrahubClient, InfrahubClientSync
|
|
26
|
+
from ..context import RequestContext
|
|
27
|
+
from ..schema import MainSchemaTypesAPI
|
|
28
|
+
from ..types import Order
|
|
707
29
|
|
|
708
30
|
|
|
709
31
|
class InfrahubNodeBase:
|
|
@@ -720,6 +42,7 @@ class InfrahubNodeBase:
|
|
|
720
42
|
self._data = data
|
|
721
43
|
self._branch = branch
|
|
722
44
|
self._existing: bool = True
|
|
45
|
+
self._attribute_data: dict[str, Attribute] = {}
|
|
723
46
|
|
|
724
47
|
# Generate a unique ID only to be used inside the SDK
|
|
725
48
|
# The format if this ID is purposely different from the ID used by the API
|
|
@@ -814,12 +137,18 @@ class InfrahubNodeBase:
|
|
|
814
137
|
def _init_attributes(self, data: dict | None = None) -> None:
|
|
815
138
|
for attr_schema in self._schema.attributes:
|
|
816
139
|
attr_data = data.get(attr_schema.name, None) if isinstance(data, dict) else None
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
attr_schema.name,
|
|
820
|
-
Attribute(name=attr_schema.name, schema=attr_schema, data=attr_data),
|
|
140
|
+
self._attribute_data[attr_schema.name] = Attribute(
|
|
141
|
+
name=attr_schema.name, schema=attr_schema, data=attr_data
|
|
821
142
|
)
|
|
822
143
|
|
|
144
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
145
|
+
"""Set values for attributes that exist or revert to normal behaviour"""
|
|
146
|
+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
|
|
147
|
+
self._attribute_data[name].value = value
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
super().__setattr__(name, value)
|
|
151
|
+
|
|
823
152
|
def _get_request_context(self, request_context: RequestContext | None = None) -> dict[str, Any] | None:
|
|
824
153
|
if request_context:
|
|
825
154
|
return request_context.model_dump(exclude_none=True)
|
|
@@ -1121,6 +450,12 @@ class InfrahubNodeBase:
|
|
|
1121
450
|
}}
|
|
1122
451
|
"""
|
|
1123
452
|
|
|
453
|
+
def _get_attribute(self, name: str) -> Attribute:
|
|
454
|
+
if name in self._attribute_data:
|
|
455
|
+
return self._attribute_data[name]
|
|
456
|
+
|
|
457
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have an attribute for {name}")
|
|
458
|
+
|
|
1124
459
|
|
|
1125
460
|
class InfrahubNode(InfrahubNodeBase):
|
|
1126
461
|
"""Represents a Infrahub node in an asynchronous context."""
|
|
@@ -1140,11 +475,13 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1140
475
|
data: Optional data to initialize the node.
|
|
1141
476
|
"""
|
|
1142
477
|
self._client = client
|
|
1143
|
-
self.__class__ = type(f"{schema.kind}InfrahubNode", (self.__class__,), {})
|
|
1144
478
|
|
|
1145
479
|
if isinstance(data, dict) and isinstance(data.get("node"), dict):
|
|
1146
480
|
data = data.get("node")
|
|
1147
481
|
|
|
482
|
+
self._relationship_cardinality_many_data: dict[str, RelationshipManager] = {}
|
|
483
|
+
self._relationship_cardinality_one_data: dict[str, RelatedNode] = {}
|
|
484
|
+
|
|
1148
485
|
super().__init__(schema=schema, branch=branch or client.default_branch, data=data)
|
|
1149
486
|
|
|
1150
487
|
@classmethod
|
|
@@ -1169,27 +506,46 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1169
506
|
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
|
|
1170
507
|
|
|
1171
508
|
if rel_schema.cardinality == "one":
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
self.__class__,
|
|
1175
|
-
rel_schema.name,
|
|
1176
|
-
generate_relationship_property(name=rel_schema.name, node=self),
|
|
509
|
+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNode(
|
|
510
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
|
|
1177
511
|
)
|
|
1178
|
-
setattr(self, rel_schema.name, rel_data)
|
|
1179
512
|
else:
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
branch=self._branch,
|
|
1188
|
-
schema=rel_schema,
|
|
1189
|
-
data=rel_data,
|
|
1190
|
-
),
|
|
513
|
+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManager(
|
|
514
|
+
name=rel_schema.name,
|
|
515
|
+
client=self._client,
|
|
516
|
+
node=self,
|
|
517
|
+
branch=self._branch,
|
|
518
|
+
schema=rel_schema,
|
|
519
|
+
data=rel_data,
|
|
1191
520
|
)
|
|
1192
521
|
|
|
522
|
+
def __getattr__(self, name: str) -> Attribute | RelationshipManager | RelatedNode:
|
|
523
|
+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
|
|
524
|
+
return self._attribute_data[name]
|
|
525
|
+
if "_relationship_cardinality_many_data" in self.__dict__ and name in self._relationship_cardinality_many_data:
|
|
526
|
+
return self._relationship_cardinality_many_data[name]
|
|
527
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
528
|
+
return self._relationship_cardinality_one_data[name]
|
|
529
|
+
|
|
530
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
531
|
+
|
|
532
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
533
|
+
"""Set values for relationship names that exist or revert to normal behaviour"""
|
|
534
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
535
|
+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
|
|
536
|
+
if not rel_schemas:
|
|
537
|
+
raise SchemaNotFoundError(
|
|
538
|
+
identifier=self._schema.kind,
|
|
539
|
+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
|
|
540
|
+
)
|
|
541
|
+
rel_schema = rel_schemas[0]
|
|
542
|
+
self._relationship_cardinality_one_data[name] = RelatedNode(
|
|
543
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
|
|
544
|
+
)
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
super().__setattr__(name, value)
|
|
548
|
+
|
|
1193
549
|
async def generate(self, nodes: list[str] | None = None) -> None:
|
|
1194
550
|
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1195
551
|
|
|
@@ -1202,14 +558,14 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1202
558
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1203
559
|
|
|
1204
560
|
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1205
|
-
await artifact.definition.fetch()
|
|
1206
|
-
await artifact.definition.peer.generate([artifact.id])
|
|
561
|
+
await artifact._get_relationship_one(name="definition").fetch()
|
|
562
|
+
await artifact._get_relationship_one(name="definition").peer.generate([artifact.id])
|
|
1207
563
|
|
|
1208
564
|
async def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1209
565
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1210
566
|
|
|
1211
567
|
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1212
|
-
content = await self._client.object_store.get(identifier=artifact.storage_id.value)
|
|
568
|
+
content = await self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
1213
569
|
return content
|
|
1214
570
|
|
|
1215
571
|
async def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
@@ -1652,6 +1008,27 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1652
1008
|
return [edge["node"] for edge in response[graphql_query_name]["edges"]]
|
|
1653
1009
|
return []
|
|
1654
1010
|
|
|
1011
|
+
def _get_relationship_many(self, name: str) -> RelationshipManager:
|
|
1012
|
+
if name in self._relationship_cardinality_many_data:
|
|
1013
|
+
return self._relationship_cardinality_many_data[name]
|
|
1014
|
+
|
|
1015
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=many relationship for {name}")
|
|
1016
|
+
|
|
1017
|
+
def _get_relationship_one(self, name: str) -> RelatedNode:
|
|
1018
|
+
if name in self._relationship_cardinality_one_data:
|
|
1019
|
+
return self._relationship_cardinality_one_data[name]
|
|
1020
|
+
|
|
1021
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=one relationship for {name}")
|
|
1022
|
+
|
|
1023
|
+
def __dir__(self) -> Iterable[str]:
|
|
1024
|
+
base = list(super().__dir__())
|
|
1025
|
+
return sorted(
|
|
1026
|
+
base
|
|
1027
|
+
+ list(self._attribute_data.keys())
|
|
1028
|
+
+ list(self._relationship_cardinality_many_data.keys())
|
|
1029
|
+
+ list(self._relationship_cardinality_one_data.keys())
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1655
1032
|
|
|
1656
1033
|
class InfrahubNodeSync(InfrahubNodeBase):
|
|
1657
1034
|
"""Represents a Infrahub node in a synchronous context."""
|
|
@@ -1670,12 +1047,14 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1670
1047
|
branch (Optional[str]): The branch where the node resides.
|
|
1671
1048
|
data (Optional[dict]): Optional data to initialize the node.
|
|
1672
1049
|
"""
|
|
1673
|
-
self.__class__ = type(f"{schema.kind}InfrahubNodeSync", (self.__class__,), {})
|
|
1674
1050
|
self._client = client
|
|
1675
1051
|
|
|
1676
1052
|
if isinstance(data, dict) and isinstance(data.get("node"), dict):
|
|
1677
1053
|
data = data.get("node")
|
|
1678
1054
|
|
|
1055
|
+
self._relationship_cardinality_many_data: dict[str, RelationshipManagerSync] = {}
|
|
1056
|
+
self._relationship_cardinality_one_data: dict[str, RelatedNodeSync] = {}
|
|
1057
|
+
|
|
1679
1058
|
super().__init__(schema=schema, branch=branch or client.default_branch, data=data)
|
|
1680
1059
|
|
|
1681
1060
|
@classmethod
|
|
@@ -1700,26 +1079,46 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1700
1079
|
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
|
|
1701
1080
|
|
|
1702
1081
|
if rel_schema.cardinality == "one":
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
self.__class__,
|
|
1706
|
-
rel_schema.name,
|
|
1707
|
-
generate_relationship_property(name=rel_schema.name, node=self),
|
|
1082
|
+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNodeSync(
|
|
1083
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
|
|
1708
1084
|
)
|
|
1709
|
-
|
|
1085
|
+
|
|
1710
1086
|
else:
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1087
|
+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManagerSync(
|
|
1088
|
+
name=rel_schema.name,
|
|
1089
|
+
client=self._client,
|
|
1090
|
+
node=self,
|
|
1091
|
+
branch=self._branch,
|
|
1092
|
+
schema=rel_schema,
|
|
1093
|
+
data=rel_data,
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
def __getattr__(self, name: str) -> Attribute | RelationshipManagerSync | RelatedNodeSync:
|
|
1097
|
+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
|
|
1098
|
+
return self._attribute_data[name]
|
|
1099
|
+
if "_relationship_cardinality_many_data" in self.__dict__ and name in self._relationship_cardinality_many_data:
|
|
1100
|
+
return self._relationship_cardinality_many_data[name]
|
|
1101
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
1102
|
+
return self._relationship_cardinality_one_data[name]
|
|
1103
|
+
|
|
1104
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
1105
|
+
|
|
1106
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
1107
|
+
"""Set values for relationship names that exist or revert to normal behaviour"""
|
|
1108
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
1109
|
+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
|
|
1110
|
+
if not rel_schemas:
|
|
1111
|
+
raise SchemaNotFoundError(
|
|
1112
|
+
identifier=self._schema.kind,
|
|
1113
|
+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
|
|
1722
1114
|
)
|
|
1115
|
+
rel_schema = rel_schemas[0]
|
|
1116
|
+
self._relationship_cardinality_one_data[name] = RelatedNodeSync(
|
|
1117
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
|
|
1118
|
+
)
|
|
1119
|
+
return
|
|
1120
|
+
|
|
1121
|
+
super().__setattr__(name, value)
|
|
1723
1122
|
|
|
1724
1123
|
def generate(self, nodes: list[str] | None = None) -> None:
|
|
1725
1124
|
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
@@ -1731,13 +1130,13 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1731
1130
|
def artifact_generate(self, name: str) -> None:
|
|
1732
1131
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1733
1132
|
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1734
|
-
artifact.definition.fetch()
|
|
1735
|
-
artifact.definition.peer.generate([artifact.id])
|
|
1133
|
+
artifact._get_relationship_one(name="definition").fetch()
|
|
1134
|
+
artifact._get_relationship_one(name="definition").peer.generate([artifact.id])
|
|
1736
1135
|
|
|
1737
1136
|
def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1738
1137
|
self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1739
1138
|
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1740
|
-
content = self._client.object_store.get(identifier=artifact.storage_id.value)
|
|
1139
|
+
content = self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
1741
1140
|
return content
|
|
1742
1141
|
|
|
1743
1142
|
def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
@@ -2180,67 +1579,23 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
2180
1579
|
return [edge["node"] for edge in response[graphql_query_name]["edges"]]
|
|
2181
1580
|
return []
|
|
2182
1581
|
|
|
1582
|
+
def _get_relationship_many(self, name: str) -> RelationshipManager | RelationshipManagerSync:
|
|
1583
|
+
if name in self._relationship_cardinality_many_data:
|
|
1584
|
+
return self._relationship_cardinality_many_data[name]
|
|
2183
1585
|
|
|
2184
|
-
|
|
2185
|
-
"""Represents a property of a node, typically used for metadata like display labels."""
|
|
1586
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=many relationship for {name}")
|
|
2186
1587
|
|
|
2187
|
-
def
|
|
2188
|
-
|
|
2189
|
-
|
|
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.
|
|
1588
|
+
def _get_relationship_one(self, name: str) -> RelatedNode | RelatedNodeSync:
|
|
1589
|
+
if name in self._relationship_cardinality_one_data:
|
|
1590
|
+
return self._relationship_cardinality_one_data[name]
|
|
2213
1591
|
|
|
2214
|
-
|
|
2215
|
-
A property object for managing the relationship.
|
|
1592
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=one relationship for {name}")
|
|
2216
1593
|
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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)
|
|
1594
|
+
def __dir__(self) -> Iterable[str]:
|
|
1595
|
+
base = list(super().__dir__())
|
|
1596
|
+
return sorted(
|
|
1597
|
+
base
|
|
1598
|
+
+ list(self._attribute_data.keys())
|
|
1599
|
+
+ list(self._relationship_cardinality_many_data.keys())
|
|
1600
|
+
+ list(self._relationship_cardinality_one_data.keys())
|
|
1601
|
+
)
|