infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__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/tasks.py +4 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +6 -6
- infrahub/computed_attribute/gather.py +3 -4
- infrahub/computed_attribute/tasks.py +23 -6
- infrahub/config.py +8 -0
- infrahub/constants/enums.py +12 -0
- infrahub/core/account.py +5 -8
- infrahub/core/attribute.py +106 -108
- infrahub/core/branch/models.py +44 -71
- infrahub/core/branch/tasks.py +5 -3
- infrahub/core/changelog/diff.py +1 -20
- infrahub/core/changelog/models.py +0 -7
- infrahub/core/constants/__init__.py +17 -0
- infrahub/core/constants/database.py +0 -1
- infrahub/core/constants/schema.py +0 -1
- infrahub/core/convert_object_type/repository_conversion.py +3 -4
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/cardinality_one.py +1 -1
- infrahub/core/diff/merger/merger.py +27 -1
- infrahub/core/diff/merger/serializer.py +3 -10
- infrahub/core/diff/model/diff.py +1 -1
- infrahub/core/diff/query/merge.py +376 -135
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +2 -2
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/manager.py +132 -126
- infrahub/core/metadata/__init__.py +0 -0
- infrahub/core/metadata/interface.py +37 -0
- infrahub/core/metadata/model.py +31 -0
- infrahub/core/metadata/query/__init__.py +0 -0
- infrahub/core/metadata/query/node_metadata.py +301 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
- infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
- infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
- infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
- infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/attribute_add.py +17 -6
- infrahub/core/migrations/query/attribute_remove.py +19 -5
- infrahub/core/migrations/query/attribute_rename.py +21 -5
- infrahub/core/migrations/query/node_duplicate.py +19 -4
- infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
- infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +4 -1
- infrahub/core/migrations/schema/node_remove.py +24 -2
- infrahub/core/migrations/schema/tasks.py +4 -1
- infrahub/core/migrations/shared.py +13 -6
- infrahub/core/models.py +6 -6
- infrahub/core/node/__init__.py +156 -57
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/standard.py +100 -14
- infrahub/core/property.py +0 -1
- infrahub/core/protocols_base.py +6 -2
- infrahub/core/query/__init__.py +6 -7
- infrahub/core/query/attribute.py +161 -46
- infrahub/core/query/branch.py +57 -69
- infrahub/core/query/diff.py +4 -4
- infrahub/core/query/node.py +618 -180
- infrahub/core/query/relationship.py +449 -300
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +1 -28
- infrahub/core/schema/attribute_schema.py +17 -11
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +48 -76
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +0 -22
- infrahub/core/validators/attribute/kind.py +2 -5
- infrahub/core/validators/determiner.py +3 -3
- infrahub/database/__init__.py +3 -3
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/display_labels/tasks.py +12 -3
- infrahub/git/integrator.py +18 -18
- infrahub/git/tasks.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/initialization.py +11 -0
- infrahub/graphql/loaders/account.py +134 -0
- infrahub/graphql/loaders/node.py +5 -12
- infrahub/graphql/loaders/peers.py +5 -7
- infrahub/graphql/manager.py +158 -18
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/models.py +33 -3
- infrahub/graphql/mutations/account.py +5 -5
- infrahub/graphql/mutations/attribute.py +0 -2
- infrahub/graphql/mutations/branch.py +9 -5
- infrahub/graphql/mutations/computed_attribute.py +1 -1
- infrahub/graphql/mutations/display_label.py +1 -1
- infrahub/graphql/mutations/hfid.py +1 -1
- infrahub/graphql/mutations/ipam.py +4 -6
- infrahub/graphql/mutations/main.py +9 -4
- infrahub/graphql/mutations/profile.py +16 -22
- infrahub/graphql/mutations/proposed_change.py +4 -4
- infrahub/graphql/mutations/relationship.py +40 -10
- infrahub/graphql/mutations/repository.py +14 -12
- infrahub/graphql/mutations/schema.py +2 -2
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/resolvers/account_metadata.py +84 -0
- infrahub/graphql/resolvers/ipam.py +6 -8
- infrahub/graphql/resolvers/many_relationship.py +77 -35
- infrahub/graphql/resolvers/resolver.py +16 -12
- infrahub/graphql/resolvers/single_relationship.py +87 -23
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/__init__.py +0 -1
- infrahub/graphql/types/attribute.py +10 -5
- infrahub/graphql/types/branch.py +40 -53
- infrahub/graphql/types/enums.py +3 -0
- infrahub/graphql/types/metadata.py +28 -0
- infrahub/graphql/types/node.py +22 -2
- infrahub/graphql/types/relationship.py +10 -2
- infrahub/graphql/types/standard_node.py +4 -3
- infrahub/hfid/tasks.py +12 -3
- infrahub/profiles/gather.py +56 -0
- infrahub/profiles/mandatory_fields_checker.py +116 -0
- infrahub/profiles/models.py +66 -0
- infrahub/profiles/node_applier.py +153 -12
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/tasks/artifact.py +1 -0
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +1 -0
- infrahub/trigger/setup.py +3 -3
- infrahub/trigger/tasks.py +3 -0
- infrahub/validators/tasks.py +1 -0
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +1 -1
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/node/constants.py +1 -0
- infrahub_sdk/node/related_node.py +13 -4
- infrahub_sdk/node/relationship.py +8 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any, assert_never
|
|
4
4
|
|
|
5
|
+
from infrahub.constants.enums import OrderByField
|
|
5
6
|
from infrahub.core.query import Query, QueryType
|
|
6
7
|
from infrahub.exceptions import InitializationError
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from uuid import UUID
|
|
10
11
|
|
|
11
|
-
from infrahub.core.node.standard import StandardNode
|
|
12
|
+
from infrahub.core.node.standard import StandardNode, StandardNodeOrdering
|
|
12
13
|
from infrahub.database import InfrahubDatabase
|
|
13
14
|
|
|
14
15
|
|
|
@@ -135,11 +136,19 @@ class StandardNodeGetListQuery(Query):
|
|
|
135
136
|
raw_filter: str | None = None
|
|
136
137
|
|
|
137
138
|
def __init__(
|
|
138
|
-
self,
|
|
139
|
+
self,
|
|
140
|
+
node_class: StandardNode,
|
|
141
|
+
node_ordering: StandardNodeOrdering,
|
|
142
|
+
ids: list[str] | None = None,
|
|
143
|
+
node_name: str | None = None,
|
|
144
|
+
partial_match: bool = False,
|
|
145
|
+
**kwargs: Any,
|
|
139
146
|
) -> None:
|
|
140
147
|
self.ids = ids
|
|
141
148
|
self.node_name = node_name
|
|
142
149
|
self.node_class = node_class
|
|
150
|
+
self.partial_match = partial_match
|
|
151
|
+
self.node_ordering = node_ordering
|
|
143
152
|
|
|
144
153
|
super().__init__(**kwargs)
|
|
145
154
|
|
|
@@ -148,9 +157,12 @@ class StandardNodeGetListQuery(Query):
|
|
|
148
157
|
if self.ids:
|
|
149
158
|
filters.append("n.uuid in $ids_value")
|
|
150
159
|
self.params["ids_value"] = self.ids
|
|
151
|
-
if self.node_name:
|
|
160
|
+
if self.node_name and not self.partial_match:
|
|
152
161
|
filters.append("n.name = $name")
|
|
153
162
|
self.params["name"] = self.node_name
|
|
163
|
+
if self.node_name and self.partial_match:
|
|
164
|
+
filters.append("toLower(toString(n.name)) CONTAINS toLower(toString($name))")
|
|
165
|
+
self.params["name"] = self.node_name
|
|
154
166
|
if self.raw_filter:
|
|
155
167
|
filters.append(self.raw_filter)
|
|
156
168
|
|
|
@@ -169,4 +181,12 @@ class StandardNodeGetListQuery(Query):
|
|
|
169
181
|
self.add_to_query(query)
|
|
170
182
|
|
|
171
183
|
self.return_labels = ["n"]
|
|
172
|
-
self.order_by
|
|
184
|
+
match self.node_ordering.order_by:
|
|
185
|
+
case OrderByField.ID:
|
|
186
|
+
self.order_by = [f"{db.get_id_function_name()}(n)"]
|
|
187
|
+
case OrderByField.CREATED_AT:
|
|
188
|
+
self.order_by = [f"n.created_at {self.node_ordering.direction.value}"]
|
|
189
|
+
case OrderByField.UPDATED_AT:
|
|
190
|
+
self.order_by = [f"n.updated_at {self.node_ordering.direction.value}"]
|
|
191
|
+
case _:
|
|
192
|
+
assert_never(self.node_ordering.order_by)
|
infrahub/core/query/utils.py
CHANGED
|
@@ -5,16 +5,14 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from neo4j.graph import Node as Neo4jNode
|
|
9
|
-
|
|
10
8
|
from infrahub.core.branch import Branch
|
|
11
9
|
from infrahub.database import InfrahubDatabase
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
def find_node_schema(
|
|
15
|
-
db: InfrahubDatabase,
|
|
13
|
+
db: InfrahubDatabase, branch: Branch | str, labels: list[str], duplicate: bool = False
|
|
16
14
|
) -> NodeSchema | ProfileSchema | TemplateSchema | None:
|
|
17
|
-
for label in
|
|
15
|
+
for label in labels:
|
|
18
16
|
if db.schema.has(name=label, branch=branch):
|
|
19
17
|
schema = db.schema.get(name=label, branch=branch, duplicate=duplicate)
|
|
20
18
|
if isinstance(schema, NodeSchema | ProfileSchema | TemplateSchema):
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from infrahub.core import registry
|
|
6
|
+
from infrahub.core.constants import MetadataOptions
|
|
7
|
+
from infrahub.core.manager import NodeManager
|
|
8
|
+
from infrahub.core.schema import NodeSchema, ProfileSchema
|
|
9
|
+
from infrahub.exceptions import ValidationError
|
|
10
|
+
|
|
11
|
+
from .interface import RelationshipManagerConstraintInterface
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.core.node import Node
|
|
16
|
+
from infrahub.core.relationship.model import Relationship, RelationshipManager
|
|
17
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
18
|
+
from infrahub.database import InfrahubDatabase
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RelationshipProfileRemovalConstraint(RelationshipManagerConstraintInterface):
|
|
22
|
+
"""Constraint that validates removing profiles from a node doesn't violate required relationships.
|
|
23
|
+
|
|
24
|
+
This runs in two cases:
|
|
25
|
+
1. When a node's `profiles` relationship is changed.
|
|
26
|
+
2. When a profile's `related_nodes` relationship is changed.
|
|
27
|
+
|
|
28
|
+
In both cases, it will perform checks only if peers are being removed from the relationship being changed.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
|
|
32
|
+
self.db = db
|
|
33
|
+
self.branch = branch
|
|
34
|
+
self.schema_branch = registry.schema.get_schema_branch(branch.name if branch else registry.default_branch)
|
|
35
|
+
|
|
36
|
+
def _get_required_attributes_names(self, schema: NodeSchema) -> set[str]:
|
|
37
|
+
attr_names: set[str] = set()
|
|
38
|
+
for attr_schema in schema.attributes:
|
|
39
|
+
if attr_schema.support_profiles and not attr_schema.optional:
|
|
40
|
+
attr_names.add(attr_schema.name)
|
|
41
|
+
return attr_names
|
|
42
|
+
|
|
43
|
+
def _get_required_relationship_names(self, schema: NodeSchema) -> set[str]:
|
|
44
|
+
rel_names: set[str] = set()
|
|
45
|
+
for rel_schema in schema.relationships:
|
|
46
|
+
if rel_schema.support_profiles and not rel_schema.optional:
|
|
47
|
+
rel_names.add(rel_schema.name)
|
|
48
|
+
return rel_names
|
|
49
|
+
|
|
50
|
+
async def _validate_profile_removal(
|
|
51
|
+
self, node: Node, profile_id: str, required_attr_names: set[str], required_rel_names: set[str]
|
|
52
|
+
) -> None:
|
|
53
|
+
for attr_name in required_attr_names:
|
|
54
|
+
attr = node.get_attribute(name=attr_name)
|
|
55
|
+
if attr.is_from_profile:
|
|
56
|
+
source = await attr.get_source(db=self.db)
|
|
57
|
+
if source and source.id == profile_id:
|
|
58
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
59
|
+
node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
|
|
60
|
+
raise ValidationError(
|
|
61
|
+
f"Cannot remove profile '{profile_id}' because {node_reference} "
|
|
62
|
+
f"inherits required attribute '{attr_name}' from this profile."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for rel_name in required_rel_names:
|
|
66
|
+
rel_manager = node.get_relationship(name=rel_name)
|
|
67
|
+
|
|
68
|
+
relationships: list[Relationship] = await rel_manager.get_relationships(db=self.db)
|
|
69
|
+
for rel in relationships:
|
|
70
|
+
if rel.is_from_profile:
|
|
71
|
+
source = await rel.get_source(db=self.db)
|
|
72
|
+
if source and source.id == profile_id:
|
|
73
|
+
node_display_label = await node.get_display_label(db=self.db)
|
|
74
|
+
node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
|
|
75
|
+
raise ValidationError(
|
|
76
|
+
f"Cannot remove profile '{profile_id}' because {node_reference} "
|
|
77
|
+
f"inherits required relationship '{rel_name}' from this profile."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def _check_node_profiles_removal(
|
|
81
|
+
self, relm: RelationshipManager, node_schema: NodeSchema, node: Node
|
|
82
|
+
) -> None:
|
|
83
|
+
required_attr_names = self._get_required_attributes_names(schema=node_schema)
|
|
84
|
+
required_rel_names = self._get_required_relationship_names(schema=node_schema)
|
|
85
|
+
if not required_attr_names and not required_rel_names:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
|
|
89
|
+
if not relm_update_details.peer_ids_present_database_only:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if required_attr_names:
|
|
93
|
+
# Required to get source for attributes
|
|
94
|
+
node = await NodeManager.get_one(
|
|
95
|
+
db=self.db, branch=self.branch, id=node.get_id(), include_metadata=MetadataOptions.SOURCE
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for profile_id in relm_update_details.peer_ids_present_database_only:
|
|
99
|
+
await self._validate_profile_removal(
|
|
100
|
+
node=node,
|
|
101
|
+
profile_id=profile_id,
|
|
102
|
+
required_attr_names=required_attr_names,
|
|
103
|
+
required_rel_names=required_rel_names,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def _check_profile_related_nodes_removal(
|
|
107
|
+
self, relm: RelationshipManager, profile_schema: ProfileSchema, profile: Node
|
|
108
|
+
) -> None:
|
|
109
|
+
relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
|
|
110
|
+
if not relm_update_details.peer_ids_present_database_only:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
target_kind = profile_schema.get_relationship(name="related_nodes").peer
|
|
114
|
+
target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
|
|
115
|
+
|
|
116
|
+
required_attr_names = self._get_required_attributes_names(schema=target_schema)
|
|
117
|
+
required_rel_names = self._get_required_relationship_names(schema=target_schema)
|
|
118
|
+
if not required_attr_names and not required_rel_names:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
nodes = await NodeManager.get_many(
|
|
122
|
+
db=self.db,
|
|
123
|
+
branch=self.branch,
|
|
124
|
+
ids=relm_update_details.peer_ids_present_database_only,
|
|
125
|
+
include_metadata=MetadataOptions.SOURCE,
|
|
126
|
+
)
|
|
127
|
+
for node in nodes.values():
|
|
128
|
+
await self._validate_profile_removal(
|
|
129
|
+
node=node,
|
|
130
|
+
profile_id=profile.get_id(),
|
|
131
|
+
required_attr_names=required_attr_names,
|
|
132
|
+
required_rel_names=required_rel_names,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None:
|
|
136
|
+
if relm.name == "profiles" and isinstance(node_schema, NodeSchema):
|
|
137
|
+
await self._check_node_profiles_removal(relm=relm, node_schema=node_schema, node=node)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
if relm.name == "related_nodes" and isinstance(node_schema, ProfileSchema):
|
|
141
|
+
await self._check_profile_related_nodes_removal(relm=relm, profile_schema=node_schema, profile=node)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
async def validate_profile_deletion(self, profile: Node, profile_schema: ProfileSchema) -> None:
|
|
145
|
+
related_nodes_rels = await profile.related_nodes.get_relationships(db=self.db) # type: ignore[attr-defined]
|
|
146
|
+
related_node_ids = [rel.peer_id for rel in related_nodes_rels if rel.peer_id]
|
|
147
|
+
|
|
148
|
+
if not related_node_ids:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
target_kind = profile_schema.get_relationship(name="related_nodes").peer
|
|
152
|
+
target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
|
|
153
|
+
|
|
154
|
+
required_attr_names = self._get_required_attributes_names(schema=target_schema)
|
|
155
|
+
required_rel_names = self._get_required_relationship_names(schema=target_schema)
|
|
156
|
+
if not required_attr_names and not required_rel_names:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
nodes = await NodeManager.get_many(
|
|
160
|
+
db=self.db, branch=self.branch, ids=related_node_ids, include_metadata=MetadataOptions.SOURCE
|
|
161
|
+
)
|
|
162
|
+
for node in nodes.values():
|
|
163
|
+
await self._validate_profile_removal(
|
|
164
|
+
node=node,
|
|
165
|
+
profile_id=profile.get_id(),
|
|
166
|
+
required_attr_names=required_attr_names,
|
|
167
|
+
required_rel_names=required_rel_names,
|
|
168
|
+
)
|