infrahub-server 1.3.0b5__py3-none-any.whl → 1.3.1__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 +36 -79
- infrahub/actions/schema.py +2 -0
- infrahub/cli/db.py +7 -5
- infrahub/cli/upgrade.py +6 -1
- infrahub/core/attribute.py +5 -0
- infrahub/core/constraint/node/runner.py +3 -1
- infrahub/core/convert_object_type/conversion.py +2 -0
- infrahub/core/diff/coordinator.py +8 -1
- infrahub/core/diff/query/delete_query.py +8 -4
- infrahub/core/diff/query/field_specifiers.py +1 -1
- infrahub/core/diff/query/merge.py +2 -2
- infrahub/core/diff/repository/repository.py +4 -0
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
- 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/m023_deduplicate_cardinality_one_relationships.py +2 -2
- infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +2 -2
- infrahub/core/migrations/graph/m031_check_number_attributes.py +102 -0
- infrahub/core/migrations/query/attribute_rename.py +1 -1
- infrahub/core/node/__init__.py +70 -37
- infrahub/core/path.py +14 -0
- infrahub/core/query/delete.py +3 -3
- 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 +1 -1
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/schema/attribute_parameters.py +12 -5
- infrahub/core/schema/basenode_schema.py +107 -1
- infrahub/core/schema/definitions/internal.py +8 -1
- infrahub/core/schema/generated/relationship_schema.py +6 -1
- infrahub/core/schema/schema_branch.py +53 -13
- infrahub/core/validators/__init__.py +2 -1
- infrahub/core/validators/attribute/min_max.py +7 -2
- infrahub/core/validators/relationship/peer.py +174 -4
- infrahub/database/__init__.py +0 -1
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
- infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
- infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/git/tasks.py +1 -0
- infrahub/graphql/app.py +5 -1
- infrahub/graphql/mutations/convert_object_type.py +16 -7
- infrahub/graphql/mutations/relationship.py +32 -0
- infrahub/graphql/queries/convert_object_type_mapping.py +3 -5
- infrahub/message_bus/operations/refresh/registry.py +3 -6
- infrahub/pools/models.py +14 -0
- infrahub/pools/tasks.py +71 -1
- infrahub/services/adapters/message_bus/nats.py +5 -1
- infrahub/services/scheduler.py +5 -1
- infrahub_sdk/ctl/generator.py +4 -4
- infrahub_sdk/ctl/repository.py +1 -1
- infrahub_sdk/node/__init__.py +2 -0
- infrahub_sdk/node/node.py +166 -93
- infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
- infrahub_sdk/query_groups.py +4 -3
- infrahub_sdk/utils.py +7 -20
- infrahub_sdk/yaml.py +6 -5
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/METADATA +2 -2
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/RECORD +68 -63
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.0b5.dist-info → infrahub_server-1.3.1.dist-info}/entry_points.txt +0 -0
infrahub/pools/tasks.py
CHANGED
|
@@ -3,12 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
from prefect import flow
|
|
4
4
|
from prefect.logging import get_run_logger
|
|
5
5
|
|
|
6
|
+
from infrahub import lock
|
|
6
7
|
from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
|
|
7
|
-
from infrahub.core.constants import NumberPoolType
|
|
8
|
+
from infrahub.core.constants import InfrahubKind, NumberPoolType
|
|
8
9
|
from infrahub.core.manager import NodeManager
|
|
10
|
+
from infrahub.core.node import Node
|
|
9
11
|
from infrahub.core.protocols import CoreNumberPool
|
|
10
12
|
from infrahub.core.registry import registry
|
|
11
13
|
from infrahub.core.schema.attribute_parameters import NumberPoolParameters
|
|
14
|
+
from infrahub.exceptions import NodeNotFoundError
|
|
15
|
+
from infrahub.pools.models import NumberPoolLockDefinition
|
|
12
16
|
from infrahub.pools.registration import get_branches_with_schema_number_pool
|
|
13
17
|
from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
|
|
14
18
|
|
|
@@ -54,3 +58,69 @@ async def validate_schema_number_pools(
|
|
|
54
58
|
elif not defined_on_branches:
|
|
55
59
|
log.info(f"Deleting number pool (id={schema_number_pool.id}) as it is no longer defined in the schema")
|
|
56
60
|
await schema_number_pool.delete(db=service.database)
|
|
61
|
+
|
|
62
|
+
existing_pool_ids = [pool.id for pool in schema_number_pools]
|
|
63
|
+
for registry_branch in registry.schema.get_branches():
|
|
64
|
+
schema_branch = service.database.schema.get_schema_branch(name=registry_branch)
|
|
65
|
+
|
|
66
|
+
for generic_name in schema_branch.generic_names:
|
|
67
|
+
generic_node = schema_branch.get_generic(name=generic_name, duplicate=False)
|
|
68
|
+
for attribute_name in generic_node.attribute_names:
|
|
69
|
+
attribute = generic_node.get_attribute(name=attribute_name)
|
|
70
|
+
if isinstance(attribute.parameters, NumberPoolParameters) and attribute.parameters.number_pool_id:
|
|
71
|
+
if attribute.parameters.number_pool_id not in existing_pool_ids:
|
|
72
|
+
await _create_number_pool(
|
|
73
|
+
service=service,
|
|
74
|
+
number_pool_id=attribute.parameters.number_pool_id,
|
|
75
|
+
pool_node=generic_node.kind,
|
|
76
|
+
pool_attribute=attribute_name,
|
|
77
|
+
start_range=attribute.parameters.start_range,
|
|
78
|
+
end_range=attribute.parameters.end_range,
|
|
79
|
+
)
|
|
80
|
+
existing_pool_ids.append(attribute.parameters.number_pool_id)
|
|
81
|
+
|
|
82
|
+
for node_name in schema_branch.node_names:
|
|
83
|
+
node = schema_branch.get_node(name=node_name, duplicate=False)
|
|
84
|
+
for attribute_name in node.attribute_names:
|
|
85
|
+
attribute = node.get_attribute(name=attribute_name)
|
|
86
|
+
if isinstance(attribute.parameters, NumberPoolParameters) and attribute.parameters.number_pool_id:
|
|
87
|
+
if attribute.parameters.number_pool_id not in existing_pool_ids:
|
|
88
|
+
await _create_number_pool(
|
|
89
|
+
service=service,
|
|
90
|
+
number_pool_id=attribute.parameters.number_pool_id,
|
|
91
|
+
pool_node=node.kind,
|
|
92
|
+
pool_attribute=attribute_name,
|
|
93
|
+
start_range=attribute.parameters.start_range,
|
|
94
|
+
end_range=attribute.parameters.end_range,
|
|
95
|
+
)
|
|
96
|
+
existing_pool_ids.append(attribute.parameters.number_pool_id)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def _create_number_pool(
|
|
100
|
+
service: InfrahubServices,
|
|
101
|
+
number_pool_id: str,
|
|
102
|
+
pool_node: str,
|
|
103
|
+
pool_attribute: str,
|
|
104
|
+
start_range: int,
|
|
105
|
+
end_range: int,
|
|
106
|
+
) -> None:
|
|
107
|
+
lock_definition = NumberPoolLockDefinition(pool_id=number_pool_id)
|
|
108
|
+
async with lock.registry.get(name=lock_definition.lock_name, namespace=lock_definition.namespace_name, local=False):
|
|
109
|
+
async with service.database.start_session() as dbs:
|
|
110
|
+
try:
|
|
111
|
+
await registry.manager.get_one_by_id_or_default_filter(
|
|
112
|
+
db=dbs, id=str(number_pool_id), kind=CoreNumberPool
|
|
113
|
+
)
|
|
114
|
+
except NodeNotFoundError:
|
|
115
|
+
number_pool = await Node.init(db=dbs, schema=InfrahubKind.NUMBERPOOL, branch=registry.default_branch)
|
|
116
|
+
await number_pool.new(
|
|
117
|
+
db=dbs,
|
|
118
|
+
id=number_pool_id,
|
|
119
|
+
name=f"{pool_node}.{pool_attribute} [{number_pool_id}]",
|
|
120
|
+
node=pool_node,
|
|
121
|
+
node_attribute=pool_attribute,
|
|
122
|
+
start_range=start_range,
|
|
123
|
+
end_range=end_range,
|
|
124
|
+
pool_type=NumberPoolType.SCHEMA.value,
|
|
125
|
+
)
|
|
126
|
+
await number_pool.save(db=dbs)
|
|
@@ -26,6 +26,8 @@ if TYPE_CHECKING:
|
|
|
26
26
|
MessageFunction = Callable[[InfrahubMessage], Awaitable[None]]
|
|
27
27
|
ResponseClass = TypeVar("ResponseClass")
|
|
28
28
|
|
|
29
|
+
publish_tasks = set()
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
async def _add_request_id(message: InfrahubMessage) -> None:
|
|
31
33
|
log_data = get_log_data()
|
|
@@ -223,7 +225,9 @@ class NATSMessageBus(InfrahubMessageBus):
|
|
|
223
225
|
# Delayed retries are directly handled in the callback using Nack
|
|
224
226
|
return
|
|
225
227
|
# Use asyncio task for delayed publish since NATS does not support that out of the box
|
|
226
|
-
asyncio.create_task(self._publish_with_delay(message, routing_key, delay))
|
|
228
|
+
task = asyncio.create_task(self._publish_with_delay(message, routing_key, delay))
|
|
229
|
+
publish_tasks.add(task)
|
|
230
|
+
task.add_done_callback(publish_tasks.discard)
|
|
227
231
|
return
|
|
228
232
|
|
|
229
233
|
for enricher in self.message_enrichers:
|
infrahub/services/scheduler.py
CHANGED
|
@@ -16,6 +16,8 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
log = get_logger()
|
|
18
18
|
|
|
19
|
+
background_tasks = set()
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
@dataclass
|
|
21
23
|
class Schedule:
|
|
@@ -56,7 +58,9 @@ class InfrahubScheduler:
|
|
|
56
58
|
|
|
57
59
|
async def start_schedule(self) -> None:
|
|
58
60
|
for schedule in self.schedules:
|
|
59
|
-
asyncio.create_task(self.run_schedule(schedule=schedule), name=f"scheduled_task_{schedule.name}")
|
|
61
|
+
task = asyncio.create_task(self.run_schedule(schedule=schedule), name=f"scheduled_task_{schedule.name}")
|
|
62
|
+
background_tasks.add(task)
|
|
63
|
+
task.add_done_callback(background_tasks.discard)
|
|
60
64
|
|
|
61
65
|
async def shutdown(self) -> None:
|
|
62
66
|
self.running = False
|
infrahub_sdk/ctl/generator.py
CHANGED
|
@@ -74,20 +74,20 @@ async def run(
|
|
|
74
74
|
targets = await client.get(
|
|
75
75
|
kind="CoreGroup", branch=branch, include=["members"], name__value=generator_config.targets
|
|
76
76
|
)
|
|
77
|
-
await targets.members.fetch()
|
|
77
|
+
await targets._get_relationship_many(name="members").fetch()
|
|
78
78
|
|
|
79
|
-
if not targets.members.peers:
|
|
79
|
+
if not targets._get_relationship_many(name="members").peers:
|
|
80
80
|
console.print(
|
|
81
81
|
f"[red]No members found within '{generator_config.targets}', not running generator '{generator_name}'"
|
|
82
82
|
)
|
|
83
83
|
return
|
|
84
84
|
|
|
85
|
-
for member in targets.members.peers:
|
|
85
|
+
for member in targets._get_relationship_many(name="members").peers:
|
|
86
86
|
check_parameter = {}
|
|
87
87
|
if identifier:
|
|
88
88
|
attribute = getattr(member.peer, identifier)
|
|
89
89
|
check_parameter = {identifier: attribute.value}
|
|
90
|
-
params = {"name": member.peer.name.value}
|
|
90
|
+
params = {"name": member.peer._get_attribute(name="name").value}
|
|
91
91
|
generator = generator_class(
|
|
92
92
|
query=generator_config.query,
|
|
93
93
|
client=client,
|
infrahub_sdk/ctl/repository.py
CHANGED
|
@@ -45,7 +45,7 @@ def get_repository_config(repo_config_file: Path) -> InfrahubRepositoryConfig:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def load_repository_config_file(repo_config_file: Path) -> dict:
|
|
48
|
-
yaml_data = read_file(
|
|
48
|
+
yaml_data = read_file(file_path=repo_config_file)
|
|
49
49
|
|
|
50
50
|
try:
|
|
51
51
|
data = yaml.safe_load(yaml_data)
|
infrahub_sdk/node/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from .attribute import Attribute
|
|
3
4
|
from .constants import (
|
|
4
5
|
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
5
6
|
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
|
|
@@ -25,6 +26,7 @@ __all__ = [
|
|
|
25
26
|
"PROPERTIES_FLAG",
|
|
26
27
|
"PROPERTIES_OBJECT",
|
|
27
28
|
"SAFE_VALUE",
|
|
29
|
+
"Attribute",
|
|
28
30
|
"InfrahubNode",
|
|
29
31
|
"InfrahubNodeBase",
|
|
30
32
|
"InfrahubNodeSync",
|
infrahub_sdk/node/node.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from copy import copy
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
6
7
|
from ..constants import InfrahubClientMode
|
|
7
|
-
from ..exceptions import
|
|
8
|
-
FeatureNotSupportedError,
|
|
9
|
-
NodeNotFoundError,
|
|
10
|
-
)
|
|
8
|
+
from ..exceptions import FeatureNotSupportedError, NodeNotFoundError, ResourceNotDefinedError, SchemaNotFoundError
|
|
11
9
|
from ..graphql import Mutation, Query
|
|
12
10
|
from ..schema import GenericSchemaAPI, RelationshipCardinality, RelationshipKind
|
|
13
11
|
from ..utils import compare_lists, generate_short_id, get_flat_value
|
|
@@ -30,48 +28,6 @@ if TYPE_CHECKING:
|
|
|
30
28
|
from ..types import Order
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
def generate_relationship_property(node: InfrahubNode | InfrahubNodeSync, name: str) -> property:
|
|
34
|
-
"""Generates a property that stores values under a private non-public name.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
node (Union[InfrahubNode, InfrahubNodeSync]): The node instance.
|
|
38
|
-
name (str): The name of the relationship property.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
A property object for managing the relationship.
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
internal_name = "_" + name.lower()
|
|
45
|
-
external_name = name
|
|
46
|
-
|
|
47
|
-
def prop_getter(self: InfrahubNodeBase) -> Any:
|
|
48
|
-
return getattr(self, internal_name)
|
|
49
|
-
|
|
50
|
-
def prop_setter(self: InfrahubNodeBase, value: Any) -> None:
|
|
51
|
-
if isinstance(value, RelatedNodeBase) or value is None:
|
|
52
|
-
setattr(self, internal_name, value)
|
|
53
|
-
else:
|
|
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
|
-
),
|
|
62
|
-
)
|
|
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
|
-
),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
return property(prop_getter, prop_setter)
|
|
73
|
-
|
|
74
|
-
|
|
75
31
|
class InfrahubNodeBase:
|
|
76
32
|
"""Base class for InfrahubNode and InfrahubNodeSync"""
|
|
77
33
|
|
|
@@ -86,6 +42,7 @@ class InfrahubNodeBase:
|
|
|
86
42
|
self._data = data
|
|
87
43
|
self._branch = branch
|
|
88
44
|
self._existing: bool = True
|
|
45
|
+
self._attribute_data: dict[str, Attribute] = {}
|
|
89
46
|
|
|
90
47
|
# Generate a unique ID only to be used inside the SDK
|
|
91
48
|
# The format if this ID is purposely different from the ID used by the API
|
|
@@ -180,12 +137,18 @@ class InfrahubNodeBase:
|
|
|
180
137
|
def _init_attributes(self, data: dict | None = None) -> None:
|
|
181
138
|
for attr_schema in self._schema.attributes:
|
|
182
139
|
attr_data = data.get(attr_schema.name, None) if isinstance(data, dict) else None
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
attr_schema.name,
|
|
186
|
-
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
|
|
187
142
|
)
|
|
188
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
|
+
|
|
189
152
|
def _get_request_context(self, request_context: RequestContext | None = None) -> dict[str, Any] | None:
|
|
190
153
|
if request_context:
|
|
191
154
|
return request_context.model_dump(exclude_none=True)
|
|
@@ -487,6 +450,12 @@ class InfrahubNodeBase:
|
|
|
487
450
|
}}
|
|
488
451
|
"""
|
|
489
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
|
+
|
|
490
459
|
|
|
491
460
|
class InfrahubNode(InfrahubNodeBase):
|
|
492
461
|
"""Represents a Infrahub node in an asynchronous context."""
|
|
@@ -506,11 +475,13 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
506
475
|
data: Optional data to initialize the node.
|
|
507
476
|
"""
|
|
508
477
|
self._client = client
|
|
509
|
-
self.__class__ = type(f"{schema.kind}InfrahubNode", (self.__class__,), {})
|
|
510
478
|
|
|
511
479
|
if isinstance(data, dict) and isinstance(data.get("node"), dict):
|
|
512
480
|
data = data.get("node")
|
|
513
481
|
|
|
482
|
+
self._relationship_cardinality_many_data: dict[str, RelationshipManager] = {}
|
|
483
|
+
self._relationship_cardinality_one_data: dict[str, RelatedNode] = {}
|
|
484
|
+
|
|
514
485
|
super().__init__(schema=schema, branch=branch or client.default_branch, data=data)
|
|
515
486
|
|
|
516
487
|
@classmethod
|
|
@@ -530,31 +501,60 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
530
501
|
|
|
531
502
|
return cls(client=client, schema=schema, branch=branch, data=cls._strip_alias(data))
|
|
532
503
|
|
|
533
|
-
def _init_relationships(self, data: dict | None = None) -> None:
|
|
504
|
+
def _init_relationships(self, data: dict | RelatedNode | None = None) -> None:
|
|
534
505
|
for rel_schema in self._schema.relationships:
|
|
535
506
|
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
|
|
536
507
|
|
|
537
508
|
if rel_schema.cardinality == "one":
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
509
|
+
if isinstance(rel_data, RelatedNode):
|
|
510
|
+
peer_id_data: dict[str, Any] = {}
|
|
511
|
+
if rel_data.id:
|
|
512
|
+
peer_id_data["id"] = rel_data.id
|
|
513
|
+
if rel_data.hfid:
|
|
514
|
+
peer_id_data["hfid"] = rel_data.hfid
|
|
515
|
+
if peer_id_data:
|
|
516
|
+
rel_data = peer_id_data
|
|
517
|
+
else:
|
|
518
|
+
rel_data = None
|
|
519
|
+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNode(
|
|
520
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
|
|
543
521
|
)
|
|
544
|
-
setattr(self, rel_schema.name, rel_data)
|
|
545
522
|
else:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
523
|
+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManager(
|
|
524
|
+
name=rel_schema.name,
|
|
525
|
+
client=self._client,
|
|
526
|
+
node=self,
|
|
527
|
+
branch=self._branch,
|
|
528
|
+
schema=rel_schema,
|
|
529
|
+
data=rel_data,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
def __getattr__(self, name: str) -> Attribute | RelationshipManager | RelatedNode:
|
|
533
|
+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
|
|
534
|
+
return self._attribute_data[name]
|
|
535
|
+
if "_relationship_cardinality_many_data" in self.__dict__ and name in self._relationship_cardinality_many_data:
|
|
536
|
+
return self._relationship_cardinality_many_data[name]
|
|
537
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
538
|
+
return self._relationship_cardinality_one_data[name]
|
|
539
|
+
|
|
540
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
541
|
+
|
|
542
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
543
|
+
"""Set values for relationship names that exist or revert to normal behaviour"""
|
|
544
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
545
|
+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
|
|
546
|
+
if not rel_schemas:
|
|
547
|
+
raise SchemaNotFoundError(
|
|
548
|
+
identifier=self._schema.kind,
|
|
549
|
+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
|
|
557
550
|
)
|
|
551
|
+
rel_schema = rel_schemas[0]
|
|
552
|
+
self._relationship_cardinality_one_data[name] = RelatedNode(
|
|
553
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
|
|
554
|
+
)
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
super().__setattr__(name, value)
|
|
558
558
|
|
|
559
559
|
async def generate(self, nodes: list[str] | None = None) -> None:
|
|
560
560
|
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
@@ -568,14 +568,14 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
568
568
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
569
569
|
|
|
570
570
|
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
571
|
-
await artifact.definition.fetch()
|
|
572
|
-
await artifact.definition.peer.generate([artifact.id])
|
|
571
|
+
await artifact._get_relationship_one(name="definition").fetch()
|
|
572
|
+
await artifact._get_relationship_one(name="definition").peer.generate([artifact.id])
|
|
573
573
|
|
|
574
574
|
async def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
575
575
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
576
576
|
|
|
577
577
|
artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
578
|
-
content = await self._client.object_store.get(identifier=artifact.storage_id.value)
|
|
578
|
+
content = await self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
579
579
|
return content
|
|
580
580
|
|
|
581
581
|
async def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
@@ -1018,6 +1018,27 @@ class InfrahubNode(InfrahubNodeBase):
|
|
|
1018
1018
|
return [edge["node"] for edge in response[graphql_query_name]["edges"]]
|
|
1019
1019
|
return []
|
|
1020
1020
|
|
|
1021
|
+
def _get_relationship_many(self, name: str) -> RelationshipManager:
|
|
1022
|
+
if name in self._relationship_cardinality_many_data:
|
|
1023
|
+
return self._relationship_cardinality_many_data[name]
|
|
1024
|
+
|
|
1025
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=many relationship for {name}")
|
|
1026
|
+
|
|
1027
|
+
def _get_relationship_one(self, name: str) -> RelatedNode:
|
|
1028
|
+
if name in self._relationship_cardinality_one_data:
|
|
1029
|
+
return self._relationship_cardinality_one_data[name]
|
|
1030
|
+
|
|
1031
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=one relationship for {name}")
|
|
1032
|
+
|
|
1033
|
+
def __dir__(self) -> Iterable[str]:
|
|
1034
|
+
base = list(super().__dir__())
|
|
1035
|
+
return sorted(
|
|
1036
|
+
base
|
|
1037
|
+
+ list(self._attribute_data.keys())
|
|
1038
|
+
+ list(self._relationship_cardinality_many_data.keys())
|
|
1039
|
+
+ list(self._relationship_cardinality_one_data.keys())
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1021
1042
|
|
|
1022
1043
|
class InfrahubNodeSync(InfrahubNodeBase):
|
|
1023
1044
|
"""Represents a Infrahub node in a synchronous context."""
|
|
@@ -1036,12 +1057,14 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1036
1057
|
branch (Optional[str]): The branch where the node resides.
|
|
1037
1058
|
data (Optional[dict]): Optional data to initialize the node.
|
|
1038
1059
|
"""
|
|
1039
|
-
self.__class__ = type(f"{schema.kind}InfrahubNodeSync", (self.__class__,), {})
|
|
1040
1060
|
self._client = client
|
|
1041
1061
|
|
|
1042
1062
|
if isinstance(data, dict) and isinstance(data.get("node"), dict):
|
|
1043
1063
|
data = data.get("node")
|
|
1044
1064
|
|
|
1065
|
+
self._relationship_cardinality_many_data: dict[str, RelationshipManagerSync] = {}
|
|
1066
|
+
self._relationship_cardinality_one_data: dict[str, RelatedNodeSync] = {}
|
|
1067
|
+
|
|
1045
1068
|
super().__init__(schema=schema, branch=branch or client.default_branch, data=data)
|
|
1046
1069
|
|
|
1047
1070
|
@classmethod
|
|
@@ -1066,27 +1089,56 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1066
1089
|
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
|
|
1067
1090
|
|
|
1068
1091
|
if rel_schema.cardinality == "one":
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1092
|
+
if isinstance(rel_data, RelatedNodeSync):
|
|
1093
|
+
peer_id_data: dict[str, Any] = {}
|
|
1094
|
+
if rel_data.id:
|
|
1095
|
+
peer_id_data["id"] = rel_data.id
|
|
1096
|
+
if rel_data.hfid:
|
|
1097
|
+
peer_id_data["hfid"] = rel_data.hfid
|
|
1098
|
+
if peer_id_data:
|
|
1099
|
+
rel_data = peer_id_data
|
|
1100
|
+
else:
|
|
1101
|
+
rel_data = None
|
|
1102
|
+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNodeSync(
|
|
1103
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
|
|
1074
1104
|
)
|
|
1075
|
-
setattr(self, rel_schema.name, rel_data)
|
|
1076
1105
|
else:
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
branch=self._branch,
|
|
1085
|
-
schema=rel_schema,
|
|
1086
|
-
data=rel_data,
|
|
1087
|
-
),
|
|
1106
|
+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManagerSync(
|
|
1107
|
+
name=rel_schema.name,
|
|
1108
|
+
client=self._client,
|
|
1109
|
+
node=self,
|
|
1110
|
+
branch=self._branch,
|
|
1111
|
+
schema=rel_schema,
|
|
1112
|
+
data=rel_data,
|
|
1088
1113
|
)
|
|
1089
1114
|
|
|
1115
|
+
def __getattr__(self, name: str) -> Attribute | RelationshipManagerSync | RelatedNodeSync:
|
|
1116
|
+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
|
|
1117
|
+
return self._attribute_data[name]
|
|
1118
|
+
if "_relationship_cardinality_many_data" in self.__dict__ and name in self._relationship_cardinality_many_data:
|
|
1119
|
+
return self._relationship_cardinality_many_data[name]
|
|
1120
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
1121
|
+
return self._relationship_cardinality_one_data[name]
|
|
1122
|
+
|
|
1123
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
|
1124
|
+
|
|
1125
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
1126
|
+
"""Set values for relationship names that exist or revert to normal behaviour"""
|
|
1127
|
+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
|
|
1128
|
+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
|
|
1129
|
+
if not rel_schemas:
|
|
1130
|
+
raise SchemaNotFoundError(
|
|
1131
|
+
identifier=self._schema.kind,
|
|
1132
|
+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
|
|
1133
|
+
)
|
|
1134
|
+
rel_schema = rel_schemas[0]
|
|
1135
|
+
self._relationship_cardinality_one_data[name] = RelatedNodeSync(
|
|
1136
|
+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
|
|
1137
|
+
)
|
|
1138
|
+
return
|
|
1139
|
+
|
|
1140
|
+
super().__setattr__(name, value)
|
|
1141
|
+
|
|
1090
1142
|
def generate(self, nodes: list[str] | None = None) -> None:
|
|
1091
1143
|
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1092
1144
|
nodes = nodes or []
|
|
@@ -1097,13 +1149,13 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1097
1149
|
def artifact_generate(self, name: str) -> None:
|
|
1098
1150
|
self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1099
1151
|
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1100
|
-
artifact.definition.fetch()
|
|
1101
|
-
artifact.definition.peer.generate([artifact.id])
|
|
1152
|
+
artifact._get_relationship_one(name="definition").fetch()
|
|
1153
|
+
artifact._get_relationship_one(name="definition").peer.generate([artifact.id])
|
|
1102
1154
|
|
|
1103
1155
|
def artifact_fetch(self, name: str) -> str | dict[str, Any]:
|
|
1104
1156
|
self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
|
|
1105
1157
|
artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
|
|
1106
|
-
content = self._client.object_store.get(identifier=artifact.storage_id.value)
|
|
1158
|
+
content = self._client.object_store.get(identifier=artifact._get_attribute(name="storage_id").value)
|
|
1107
1159
|
return content
|
|
1108
1160
|
|
|
1109
1161
|
def delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None:
|
|
@@ -1545,3 +1597,24 @@ class InfrahubNodeSync(InfrahubNodeBase):
|
|
|
1545
1597
|
if response[graphql_query_name].get("count", 0):
|
|
1546
1598
|
return [edge["node"] for edge in response[graphql_query_name]["edges"]]
|
|
1547
1599
|
return []
|
|
1600
|
+
|
|
1601
|
+
def _get_relationship_many(self, name: str) -> RelationshipManager | RelationshipManagerSync:
|
|
1602
|
+
if name in self._relationship_cardinality_many_data:
|
|
1603
|
+
return self._relationship_cardinality_many_data[name]
|
|
1604
|
+
|
|
1605
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=many relationship for {name}")
|
|
1606
|
+
|
|
1607
|
+
def _get_relationship_one(self, name: str) -> RelatedNode | RelatedNodeSync:
|
|
1608
|
+
if name in self._relationship_cardinality_one_data:
|
|
1609
|
+
return self._relationship_cardinality_one_data[name]
|
|
1610
|
+
|
|
1611
|
+
raise ResourceNotDefinedError(message=f"The node doesn't have a cardinality=one relationship for {name}")
|
|
1612
|
+
|
|
1613
|
+
def __dir__(self) -> Iterable[str]:
|
|
1614
|
+
base = list(super().__dir__())
|
|
1615
|
+
return sorted(
|
|
1616
|
+
base
|
|
1617
|
+
+ list(self._attribute_data.keys())
|
|
1618
|
+
+ list(self._relationship_cardinality_many_data.keys())
|
|
1619
|
+
+ list(self._relationship_cardinality_one_data.keys())
|
|
1620
|
+
)
|
|
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
import ujson
|
|
8
8
|
from httpx import HTTPStatusError
|
|
9
9
|
|
|
10
|
+
from ...node import InfrahubNode
|
|
10
11
|
from ..exceptions import OutputMatchError, PythonTransformDefinitionError
|
|
11
12
|
from ..models import InfrahubTestExpectedResult
|
|
12
13
|
from .base import InfrahubItem
|
|
@@ -41,7 +42,7 @@ class InfrahubPythonTransformItem(InfrahubItem):
|
|
|
41
42
|
)
|
|
42
43
|
client = self.session.infrahub_client # type: ignore[attr-defined]
|
|
43
44
|
# TODO: Look into seeing how a transform class may use the branch, but set as a empty string for the time being to keep current behaviour
|
|
44
|
-
self.transform_instance = transform_class(branch="", client=client)
|
|
45
|
+
self.transform_instance = transform_class(branch="", client=client, infrahub_node=InfrahubNode)
|
|
45
46
|
|
|
46
47
|
def run_transform(self, variables: dict[str, Any]) -> Any:
|
|
47
48
|
self.instantiate_transform()
|
infrahub_sdk/query_groups.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Sequence
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
from .constants import InfrahubClientMode
|
|
@@ -19,7 +20,7 @@ class InfrahubGroupContextBase:
|
|
|
19
20
|
self.related_node_ids: list[str] = []
|
|
20
21
|
self.related_group_ids: list[str] = []
|
|
21
22
|
self.unused_member_ids: list[str] | None = None
|
|
22
|
-
self.previous_members:
|
|
23
|
+
self.previous_members: Sequence[RelatedNodeBase] | None = None
|
|
23
24
|
self.previous_children: list[RelatedNodeBase] | None = None
|
|
24
25
|
self.identifier: str | None = None
|
|
25
26
|
self.params: dict[str, str] = {}
|
|
@@ -101,7 +102,7 @@ class InfrahubGroupContext(InfrahubGroupContextBase):
|
|
|
101
102
|
if not store_peers:
|
|
102
103
|
return group
|
|
103
104
|
|
|
104
|
-
self.previous_members = group.members.peers
|
|
105
|
+
self.previous_members = group._get_relationship_many(name="members").peers
|
|
105
106
|
return group
|
|
106
107
|
|
|
107
108
|
async def delete_unused(self) -> None:
|
|
@@ -195,7 +196,7 @@ class InfrahubGroupContextSync(InfrahubGroupContextBase):
|
|
|
195
196
|
if not store_peers:
|
|
196
197
|
return group
|
|
197
198
|
|
|
198
|
-
self.previous_members = group.members.peers
|
|
199
|
+
self.previous_members = group._get_relationship_many(name="members").peers
|
|
199
200
|
return group
|
|
200
201
|
|
|
201
202
|
def delete_unused(self) -> None:
|
infrahub_sdk/utils.py
CHANGED
|
@@ -240,21 +240,6 @@ def is_valid_url(url: str) -> bool:
|
|
|
240
240
|
return False
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
def find_files(extension: str | list[str], directory: str | Path = ".") -> list[Path]:
|
|
244
|
-
files: list[Path] = []
|
|
245
|
-
|
|
246
|
-
if isinstance(extension, str):
|
|
247
|
-
extension = [extension]
|
|
248
|
-
if isinstance(directory, str):
|
|
249
|
-
directory = Path(directory)
|
|
250
|
-
|
|
251
|
-
for ext in extension:
|
|
252
|
-
files.extend(list(directory.glob(f"**/*.{ext}")))
|
|
253
|
-
files.extend(list(directory.glob(f"**/.*.{ext}")))
|
|
254
|
-
|
|
255
|
-
return files
|
|
256
|
-
|
|
257
|
-
|
|
258
243
|
def get_branch(branch: str | None = None, directory: str | Path = ".") -> str:
|
|
259
244
|
"""If branch isn't provide, return the name of the local Git branch."""
|
|
260
245
|
if branch:
|
|
@@ -351,14 +336,16 @@ def write_to_file(path: Path, value: Any) -> bool:
|
|
|
351
336
|
return written is not None
|
|
352
337
|
|
|
353
338
|
|
|
354
|
-
def read_file(
|
|
355
|
-
if not
|
|
356
|
-
raise FileNotValidError(name=str(
|
|
339
|
+
def read_file(file_path: Path) -> str:
|
|
340
|
+
if not file_path.is_file():
|
|
341
|
+
raise FileNotValidError(name=str(file_path.name), message=f"{file_path.name}: not found at {file_path.parent}")
|
|
357
342
|
try:
|
|
358
|
-
with Path.open(
|
|
343
|
+
with Path.open(file_path, encoding="utf-8") as fobj:
|
|
359
344
|
return fobj.read()
|
|
360
345
|
except UnicodeDecodeError as exc:
|
|
361
|
-
raise FileNotValidError(
|
|
346
|
+
raise FileNotValidError(
|
|
347
|
+
name=str(file_path.name), message=f"Unable to read {file_path.name} with utf-8 encoding"
|
|
348
|
+
) from exc
|
|
362
349
|
|
|
363
350
|
|
|
364
351
|
def get_user_permissions(data: list[dict]) -> dict:
|