infrahub-server 1.2.10__py3-none-any.whl → 1.3.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/constants.py +86 -0
- infrahub/actions/gather.py +114 -0
- infrahub/actions/models.py +241 -0
- infrahub/actions/parsers.py +104 -0
- infrahub/actions/schema.py +382 -0
- infrahub/actions/tasks.py +126 -0
- infrahub/actions/triggers.py +21 -0
- infrahub/cli/db.py +1 -2
- infrahub/config.py +9 -0
- infrahub/core/account.py +24 -47
- infrahub/core/attribute.py +10 -12
- infrahub/core/constants/infrahubkind.py +8 -0
- infrahub/core/constraint/node/runner.py +1 -1
- infrahub/core/convert_object_type/__init__.py +0 -0
- infrahub/core/convert_object_type/conversion.py +122 -0
- infrahub/core/convert_object_type/schema_mapping.py +56 -0
- infrahub/core/diff/query/all_conflicts.py +1 -5
- infrahub/core/diff/query/artifact.py +10 -20
- infrahub/core/diff/query/diff_get.py +3 -6
- infrahub/core/diff/query/field_summary.py +2 -4
- infrahub/core/diff/query/merge.py +70 -123
- infrahub/core/diff/query/save.py +20 -32
- infrahub/core/diff/query/summary_counts_enricher.py +34 -54
- infrahub/core/diff/query_parser.py +5 -1
- infrahub/core/diff/tasks.py +3 -3
- infrahub/core/manager.py +14 -11
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
- infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
- infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
- infrahub/core/migrations/query/attribute_add.py +1 -2
- infrahub/core/migrations/query/attribute_rename.py +3 -6
- infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
- infrahub/core/migrations/query/node_duplicate.py +3 -6
- infrahub/core/migrations/query/relationship_duplicate.py +3 -6
- infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
- infrahub/core/migrations/schema/node_remove.py +3 -6
- infrahub/core/models.py +29 -2
- infrahub/core/node/__init__.py +18 -4
- infrahub/core/node/create.py +211 -0
- infrahub/core/protocols.py +51 -0
- infrahub/core/protocols_base.py +3 -0
- infrahub/core/query/__init__.py +2 -2
- infrahub/core/query/diff.py +26 -32
- infrahub/core/query/ipam.py +10 -20
- infrahub/core/query/node.py +28 -46
- infrahub/core/query/relationship.py +51 -28
- infrahub/core/query/resource_manager.py +1 -2
- infrahub/core/query/subquery.py +2 -4
- infrahub/core/relationship/model.py +3 -0
- infrahub/core/schema/__init__.py +2 -1
- infrahub/core/schema/attribute_parameters.py +36 -0
- infrahub/core/schema/attribute_schema.py +83 -8
- infrahub/core/schema/basenode_schema.py +25 -1
- infrahub/core/schema/definitions/core/__init__.py +21 -0
- infrahub/core/schema/definitions/internal.py +13 -3
- infrahub/core/schema/generated/attribute_schema.py +9 -3
- infrahub/core/schema/schema_branch.py +12 -7
- infrahub/core/validators/__init__.py +5 -1
- infrahub/core/validators/attribute/choices.py +1 -2
- infrahub/core/validators/attribute/enum.py +1 -2
- infrahub/core/validators/attribute/kind.py +1 -2
- infrahub/core/validators/attribute/length.py +13 -6
- infrahub/core/validators/attribute/optional.py +1 -2
- infrahub/core/validators/attribute/regex.py +5 -5
- infrahub/core/validators/attribute/unique.py +1 -3
- infrahub/core/validators/determiner.py +18 -2
- infrahub/core/validators/enum.py +7 -0
- infrahub/core/validators/node/hierarchy.py +3 -6
- infrahub/core/validators/query.py +1 -3
- infrahub/core/validators/relationship/count.py +6 -12
- infrahub/core/validators/relationship/optional.py +2 -4
- infrahub/core/validators/relationship/peer.py +3 -8
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +5 -9
- infrahub/database/__init__.py +1 -3
- infrahub/events/group_action.py +1 -0
- infrahub/graphql/analyzer.py +139 -18
- infrahub/graphql/app.py +1 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/peers.py +1 -1
- infrahub/graphql/manager.py +4 -0
- infrahub/graphql/mutations/action.py +164 -0
- infrahub/graphql/mutations/convert_object_type.py +62 -0
- infrahub/graphql/mutations/main.py +24 -175
- infrahub/graphql/mutations/proposed_change.py +21 -18
- infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
- infrahub/graphql/queries/relationship.py +1 -1
- infrahub/graphql/resolvers/many_relationship.py +4 -4
- infrahub/graphql/resolvers/resolver.py +4 -4
- infrahub/graphql/resolvers/single_relationship.py +2 -2
- infrahub/graphql/schema.py +6 -0
- infrahub/graphql/subscription/graphql_query.py +2 -2
- infrahub/graphql/types/branch.py +1 -1
- infrahub/menu/menu.py +31 -0
- infrahub/message_bus/messages/__init__.py +0 -10
- infrahub/message_bus/operations/__init__.py +0 -8
- infrahub/message_bus/operations/refresh/registry.py +1 -1
- infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
- infrahub/patch/queries/delete_duplicated_edges.py +5 -10
- infrahub/prefect_server/models.py +1 -19
- infrahub/proposed_change/models.py +68 -3
- infrahub/proposed_change/tasks.py +907 -30
- infrahub/task_manager/models.py +10 -6
- infrahub/telemetry/database.py +1 -1
- infrahub/telemetry/tasks.py +1 -1
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +18 -2
- infrahub/trigger/tasks.py +3 -1
- infrahub/workflows/catalogue.py +76 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +2 -2
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +121 -118
- infrahub_testcontainers/container.py +0 -1
- infrahub_testcontainers/docker-compose.test.yml +1 -1
- infrahub_testcontainers/helpers.py +8 -2
- infrahub/message_bus/messages/check_generator_run.py +0 -26
- infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
- infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
- infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
- infrahub/message_bus/operations/check/__init__.py +0 -3
- infrahub/message_bus/operations/check/generator.py +0 -156
- infrahub/message_bus/operations/finalize/__init__.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +0 -133
- infrahub/message_bus/operations/requests/__init__.py +0 -9
- infrahub/message_bus/operations/requests/generator_definition.py +0 -140
- infrahub/message_bus/operations/requests/proposed_change.py +0 -629
- /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.10.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -220,8 +220,7 @@ class NumberPoolGetUsed(Query):
|
|
|
220
220
|
|
|
221
221
|
query = """
|
|
222
222
|
MATCH (pool:%(number_pool)s { uuid: $pool_id })
|
|
223
|
-
CALL {
|
|
224
|
-
WITH pool
|
|
223
|
+
CALL (pool) {
|
|
225
224
|
MATCH (pool)-[res:IS_RESERVED]->(av:AttributeValue)<-[hv:HAS_VALUE]-(attr:Attribute)
|
|
226
225
|
WHERE
|
|
227
226
|
attr.name = $attribute_name
|
infrahub/core/query/subquery.py
CHANGED
|
@@ -61,7 +61,7 @@ async def build_subquery_filter(
|
|
|
61
61
|
where_str = " AND ".join(field_where)
|
|
62
62
|
branch_level_str = "reduce(br_lvl = 0, r in relationships(path) | br_lvl + r.branch_level)"
|
|
63
63
|
froms_str = db.render_list_comprehension(items="relationships(path)", item_name="from")
|
|
64
|
-
to_return = f"{
|
|
64
|
+
to_return = f"{prefix}"
|
|
65
65
|
with_extra = ""
|
|
66
66
|
final_with_extra = ""
|
|
67
67
|
is_isnull = filter_name == "isnull"
|
|
@@ -82,7 +82,6 @@ async def build_subquery_filter(
|
|
|
82
82
|
elif field is not None and field.is_attribute:
|
|
83
83
|
is_active_filter = "(latest_node_details[2]).value = 'NULL'"
|
|
84
84
|
query = f"""
|
|
85
|
-
WITH {node_alias}
|
|
86
85
|
{match} path = {filter_str}
|
|
87
86
|
WHERE {where_str}
|
|
88
87
|
WITH
|
|
@@ -94,7 +93,7 @@ async def build_subquery_filter(
|
|
|
94
93
|
ORDER BY branch_level DESC, froms[-1] DESC, froms[-2] DESC
|
|
95
94
|
WITH head(collect([is_active, {node_alias}{with_extra}])) AS latest_node_details
|
|
96
95
|
WHERE {is_active_filter}
|
|
97
|
-
WITH latest_node_details[1] AS {
|
|
96
|
+
WITH latest_node_details[1] AS {prefix}{final_with_extra}
|
|
98
97
|
RETURN {to_return}
|
|
99
98
|
"""
|
|
100
99
|
return query, params, prefix
|
|
@@ -174,7 +173,6 @@ async def build_subquery_order(
|
|
|
174
173
|
to_return_str_parts.append(f"CASE WHEN is_active = TRUE THEN {expression} ELSE NULL END AS {alias}")
|
|
175
174
|
to_return_str = ", ".join(to_return_str_parts)
|
|
176
175
|
query = f"""
|
|
177
|
-
WITH {node_alias}
|
|
178
176
|
OPTIONAL MATCH path = {filter_str}
|
|
179
177
|
WHERE {where_str}
|
|
180
178
|
WITH {with_str_to_alias}
|
infrahub/core/schema/__init__.py
CHANGED
|
@@ -21,7 +21,8 @@ from .profile_schema import ProfileSchema
|
|
|
21
21
|
from .relationship_schema import RelationshipSchema
|
|
22
22
|
from .template_schema import TemplateSchema
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
NonGenericSchemaTypes: TypeAlias = NodeSchema | ProfileSchema | TemplateSchema
|
|
25
|
+
MainSchemaTypes: TypeAlias = NonGenericSchemaTypes | GenericSchema
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# -----------------------------------------------------
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from infrahub.core.constants.schema import UpdateSupport
|
|
6
|
+
from infrahub.core.models import HashableModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_attribute_parameters_class_for_kind(kind: str) -> type[AttributeParameters]:
|
|
10
|
+
return {
|
|
11
|
+
"Text": TextAttributeParameters,
|
|
12
|
+
"TextArea": TextAttributeParameters,
|
|
13
|
+
}.get(kind, AttributeParameters)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AttributeParameters(HashableModel):
|
|
17
|
+
class Config:
|
|
18
|
+
extra = "forbid"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TextAttributeParameters(AttributeParameters):
|
|
22
|
+
regex: str | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Regular expression that attribute value must match if defined",
|
|
25
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
26
|
+
)
|
|
27
|
+
min_length: int | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Set a minimum number of characters allowed.",
|
|
30
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
31
|
+
)
|
|
32
|
+
max_length: int | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="Set a maximum number of characters allowed.",
|
|
35
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
36
|
+
)
|
|
@@ -2,15 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
6
6
|
|
|
7
|
-
from pydantic import field_validator, model_validator
|
|
7
|
+
from pydantic import Field, ValidationInfo, field_validator, model_validator
|
|
8
8
|
|
|
9
9
|
from infrahub import config
|
|
10
|
+
from infrahub.core.constants.schema import UpdateSupport
|
|
10
11
|
from infrahub.core.enums import generate_python_enum
|
|
11
12
|
from infrahub.core.query.attribute import default_attribute_query_filter
|
|
12
13
|
from infrahub.types import ATTRIBUTE_KIND_LABELS, ATTRIBUTE_TYPES
|
|
13
14
|
|
|
15
|
+
from .attribute_parameters import AttributeParameters, TextAttributeParameters, get_attribute_parameters_class_for_kind
|
|
14
16
|
from .generated.attribute_schema import GeneratedAttributeSchema
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
@@ -21,6 +23,14 @@ if TYPE_CHECKING:
|
|
|
21
23
|
from infrahub.database import InfrahubDatabase
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def get_attribute_schema_class_for_kind(kind: str) -> type[AttributeSchema]:
|
|
27
|
+
attribute_schema_class_by_kind: dict[str, type[AttributeSchema]] = {
|
|
28
|
+
"Text": TextAttributeSchema,
|
|
29
|
+
"TextArea": TextAttributeSchema,
|
|
30
|
+
}
|
|
31
|
+
return attribute_schema_class_by_kind.get(kind, AttributeSchema)
|
|
32
|
+
|
|
33
|
+
|
|
24
34
|
class AttributeSchema(GeneratedAttributeSchema):
|
|
25
35
|
_sort_by: list[str] = ["name"]
|
|
26
36
|
_enum_class: type[enum.Enum] | None = None
|
|
@@ -53,16 +63,36 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
53
63
|
|
|
54
64
|
@model_validator(mode="before")
|
|
55
65
|
@classmethod
|
|
56
|
-
def validate_dropdown_choices(cls, values:
|
|
66
|
+
def validate_dropdown_choices(cls, values: Any) -> Any:
|
|
57
67
|
"""Validate that choices are defined for a dropdown but not for other kinds."""
|
|
58
|
-
if values
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
if isinstance(values, dict):
|
|
69
|
+
kind = values.get("kind")
|
|
70
|
+
choices = values.get("choices")
|
|
71
|
+
elif isinstance(values, AttributeSchema):
|
|
72
|
+
kind = values.kind
|
|
73
|
+
choices = values.choices
|
|
74
|
+
else:
|
|
75
|
+
return values
|
|
76
|
+
if kind != "Dropdown" and choices:
|
|
77
|
+
raise ValueError(f"Can only specify 'choices' for kind=Dropdown: {kind}")
|
|
78
|
+
|
|
79
|
+
if kind == "Dropdown" and not choices:
|
|
62
80
|
raise ValueError("The property 'choices' is required for kind=Dropdown")
|
|
63
81
|
|
|
64
82
|
return values
|
|
65
83
|
|
|
84
|
+
@field_validator("parameters", mode="before")
|
|
85
|
+
@classmethod
|
|
86
|
+
def set_parameters_type(cls, value: Any, info: ValidationInfo) -> Any:
|
|
87
|
+
"""Override parameters class if using base AttributeParameters class and should be using a subclass"""
|
|
88
|
+
kind = info.data["kind"]
|
|
89
|
+
expected_parameters_class = get_attribute_parameters_class_for_kind(kind=kind)
|
|
90
|
+
if value is None:
|
|
91
|
+
return expected_parameters_class()
|
|
92
|
+
if not isinstance(value, expected_parameters_class) and isinstance(value, AttributeParameters):
|
|
93
|
+
return expected_parameters_class(**value.model_dump())
|
|
94
|
+
return value
|
|
95
|
+
|
|
66
96
|
def get_class(self) -> type[BaseAttribute]:
|
|
67
97
|
return ATTRIBUTE_TYPES[self.kind].get_infrahub_class()
|
|
68
98
|
|
|
@@ -106,7 +136,7 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
106
136
|
|
|
107
137
|
def to_node(self) -> dict[str, Any]:
|
|
108
138
|
fields_to_exclude = {"id", "state", "filters"}
|
|
109
|
-
fields_to_json = {"computed_attribute"}
|
|
139
|
+
fields_to_json = {"computed_attribute", "parameters"}
|
|
110
140
|
data = self.model_dump(exclude=fields_to_exclude | fields_to_json)
|
|
111
141
|
|
|
112
142
|
for field_name in fields_to_json:
|
|
@@ -117,6 +147,15 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
117
147
|
|
|
118
148
|
return data
|
|
119
149
|
|
|
150
|
+
def get_regex(self) -> str | None:
|
|
151
|
+
return self.regex
|
|
152
|
+
|
|
153
|
+
def get_min_length(self) -> int | None:
|
|
154
|
+
return self.min_length
|
|
155
|
+
|
|
156
|
+
def get_max_length(self) -> int | None:
|
|
157
|
+
return self.max_length
|
|
158
|
+
|
|
120
159
|
async def get_query_filter(
|
|
121
160
|
self,
|
|
122
161
|
name: str,
|
|
@@ -144,3 +183,39 @@ class AttributeSchema(GeneratedAttributeSchema):
|
|
|
144
183
|
partial_match=partial_match,
|
|
145
184
|
support_profiles=support_profiles,
|
|
146
185
|
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TextAttributeSchema(AttributeSchema):
|
|
189
|
+
parameters: TextAttributeParameters = Field(
|
|
190
|
+
default_factory=TextAttributeParameters,
|
|
191
|
+
description="Extra parameters specific to text attributes",
|
|
192
|
+
json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@model_validator(mode="after")
|
|
196
|
+
def reconcile_parameters(self) -> Self:
|
|
197
|
+
if self.regex != self.parameters.regex:
|
|
198
|
+
final_regex = self.parameters.regex or self.regex
|
|
199
|
+
if not final_regex: # falsy parameters.regex override falsy regex
|
|
200
|
+
final_regex = self.parameters.regex
|
|
201
|
+
self.regex = self.parameters.regex = final_regex
|
|
202
|
+
if self.min_length != self.parameters.min_length:
|
|
203
|
+
final_min_length = self.parameters.min_length or self.min_length
|
|
204
|
+
if not final_min_length: # falsy parameters.min_length override falsy min_length
|
|
205
|
+
final_min_length = self.parameters.min_length
|
|
206
|
+
self.min_length = self.parameters.min_length = final_min_length
|
|
207
|
+
if self.max_length != self.parameters.max_length:
|
|
208
|
+
final_max_length = self.parameters.max_length or self.max_length
|
|
209
|
+
if not final_max_length: # falsy parameters.max_length override falsy max_length
|
|
210
|
+
final_max_length = self.parameters.max_length
|
|
211
|
+
self.max_length = self.parameters.max_length = final_max_length
|
|
212
|
+
return self
|
|
213
|
+
|
|
214
|
+
def get_regex(self) -> str | None:
|
|
215
|
+
return self.parameters.regex
|
|
216
|
+
|
|
217
|
+
def get_min_length(self) -> int | None:
|
|
218
|
+
return self.parameters.min_length
|
|
219
|
+
|
|
220
|
+
def get_max_length(self) -> int | None:
|
|
221
|
+
return self.parameters.max_length
|
|
@@ -13,7 +13,7 @@ from pydantic import field_validator
|
|
|
13
13
|
from infrahub.core.constants import RelationshipCardinality, RelationshipKind
|
|
14
14
|
from infrahub.core.models import HashableModel, HashableModelDiff
|
|
15
15
|
|
|
16
|
-
from .attribute_schema import AttributeSchema
|
|
16
|
+
from .attribute_schema import AttributeSchema, get_attribute_schema_class_for_kind
|
|
17
17
|
from .generated.base_node_schema import GeneratedBaseNodeSchema
|
|
18
18
|
from .relationship_schema import RelationshipSchema
|
|
19
19
|
|
|
@@ -74,6 +74,30 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
|
|
|
74
74
|
Be careful hash generated from hash() have a salt by default and they will not be the same across run"""
|
|
75
75
|
return hash(self.get_hash())
|
|
76
76
|
|
|
77
|
+
@field_validator("attributes", mode="before")
|
|
78
|
+
@classmethod
|
|
79
|
+
def set_attribute_type(cls, raw_attributes: Any) -> Any:
|
|
80
|
+
if not isinstance(raw_attributes, list):
|
|
81
|
+
return raw_attributes
|
|
82
|
+
attribute_schemas_with_types: list[Any] = []
|
|
83
|
+
for raw_attr in raw_attributes:
|
|
84
|
+
if not isinstance(raw_attr, (dict, AttributeSchema)):
|
|
85
|
+
attribute_schemas_with_types.append(raw_attr)
|
|
86
|
+
continue
|
|
87
|
+
if isinstance(raw_attr, dict):
|
|
88
|
+
kind = raw_attr.get("kind")
|
|
89
|
+
attribute_type_class = get_attribute_schema_class_for_kind(kind=kind)
|
|
90
|
+
attribute_schemas_with_types.append(attribute_type_class(**raw_attr))
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
expected_attr_schema_class = get_attribute_schema_class_for_kind(kind=raw_attr.kind)
|
|
94
|
+
if not isinstance(raw_attr, expected_attr_schema_class):
|
|
95
|
+
final_attr = expected_attr_schema_class(**raw_attr.model_dump())
|
|
96
|
+
else:
|
|
97
|
+
final_attr = raw_attr
|
|
98
|
+
attribute_schemas_with_types.append(final_attr)
|
|
99
|
+
return attribute_schemas_with_types
|
|
100
|
+
|
|
77
101
|
def to_dict(self) -> dict:
|
|
78
102
|
data = self.model_dump(
|
|
79
103
|
exclude_unset=True, exclude_none=True, exclude_defaults=True, exclude={"attributes", "relationships"}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from infrahub.actions.schema import (
|
|
4
|
+
core_action,
|
|
5
|
+
core_generator_action,
|
|
6
|
+
core_group_action,
|
|
7
|
+
core_group_trigger_rule,
|
|
8
|
+
core_node_trigger_attribute_match,
|
|
9
|
+
core_node_trigger_match,
|
|
10
|
+
core_node_trigger_relationship_match,
|
|
11
|
+
core_node_trigger_rule,
|
|
12
|
+
core_trigger_rule,
|
|
13
|
+
)
|
|
14
|
+
|
|
3
15
|
from ...generic_schema import GenericSchema
|
|
4
16
|
from ...node_schema import NodeSchema
|
|
5
17
|
from .account import (
|
|
@@ -63,6 +75,9 @@ from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
|
|
|
63
75
|
|
|
64
76
|
core_models_mixed: dict[str, list] = {
|
|
65
77
|
"generics": [
|
|
78
|
+
core_action,
|
|
79
|
+
core_trigger_rule,
|
|
80
|
+
core_node_trigger_match,
|
|
66
81
|
core_node,
|
|
67
82
|
lineage_owner,
|
|
68
83
|
core_profile_schema_definition,
|
|
@@ -90,12 +105,18 @@ core_models_mixed: dict[str, list] = {
|
|
|
90
105
|
],
|
|
91
106
|
"nodes": [
|
|
92
107
|
menu_item,
|
|
108
|
+
core_group_action,
|
|
93
109
|
core_standard_group,
|
|
94
110
|
core_generator_group,
|
|
95
111
|
core_graphql_query_group,
|
|
96
112
|
builtin_tag,
|
|
97
113
|
core_account,
|
|
98
114
|
core_account_token,
|
|
115
|
+
core_generator_action,
|
|
116
|
+
core_group_trigger_rule,
|
|
117
|
+
core_node_trigger_rule,
|
|
118
|
+
core_node_trigger_attribute_match,
|
|
119
|
+
core_node_trigger_relationship_match,
|
|
99
120
|
core_password_credential,
|
|
100
121
|
core_refresh_token,
|
|
101
122
|
core_proposed_change,
|
|
@@ -31,6 +31,7 @@ from infrahub.core.constants import (
|
|
|
31
31
|
RelationshipKind,
|
|
32
32
|
UpdateSupport,
|
|
33
33
|
)
|
|
34
|
+
from infrahub.core.schema.attribute_parameters import AttributeParameters
|
|
34
35
|
from infrahub.core.schema.attribute_schema import AttributeSchema
|
|
35
36
|
from infrahub.core.schema.computed_attribute import ComputedAttribute
|
|
36
37
|
from infrahub.core.schema.dropdown import DropdownChoice
|
|
@@ -506,21 +507,21 @@ attribute_schema = SchemaNode(
|
|
|
506
507
|
SchemaAttribute(
|
|
507
508
|
name="regex",
|
|
508
509
|
kind="Text",
|
|
509
|
-
description="Regex uses to limit the characters allowed in for the attributes.",
|
|
510
|
+
description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
|
|
510
511
|
optional=True,
|
|
511
512
|
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
512
513
|
),
|
|
513
514
|
SchemaAttribute(
|
|
514
515
|
name="max_length",
|
|
515
516
|
kind="Number",
|
|
516
|
-
description="Set a maximum number of characters allowed for a given attribute.",
|
|
517
|
+
description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
|
|
517
518
|
optional=True,
|
|
518
519
|
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
519
520
|
),
|
|
520
521
|
SchemaAttribute(
|
|
521
522
|
name="min_length",
|
|
522
523
|
kind="Number",
|
|
523
|
-
description="Set a minimum number of characters allowed for a given attribute.",
|
|
524
|
+
description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
|
|
524
525
|
optional=True,
|
|
525
526
|
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
526
527
|
),
|
|
@@ -617,6 +618,15 @@ attribute_schema = SchemaNode(
|
|
|
617
618
|
optional=True,
|
|
618
619
|
extra={"update": UpdateSupport.ALLOWED},
|
|
619
620
|
),
|
|
621
|
+
SchemaAttribute(
|
|
622
|
+
name="parameters",
|
|
623
|
+
kind="JSON",
|
|
624
|
+
internal_kind=AttributeParameters,
|
|
625
|
+
optional=True,
|
|
626
|
+
description="Extra parameters specific to this kind of attribute",
|
|
627
|
+
extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
|
|
628
|
+
default_factory="AttributeParameters",
|
|
629
|
+
),
|
|
620
630
|
SchemaAttribute(
|
|
621
631
|
name="deprecation",
|
|
622
632
|
kind="Text",
|
|
@@ -8,6 +8,7 @@ from pydantic import Field
|
|
|
8
8
|
|
|
9
9
|
from infrahub.core.constants import AllowOverrideType, BranchSupportType, HashableModelState
|
|
10
10
|
from infrahub.core.models import HashableModel
|
|
11
|
+
from infrahub.core.schema.attribute_parameters import AttributeParameters # noqa: TC001
|
|
11
12
|
from infrahub.core.schema.computed_attribute import ComputedAttribute # noqa: TC001
|
|
12
13
|
from infrahub.core.schema.dropdown import DropdownChoice # noqa: TC001
|
|
13
14
|
|
|
@@ -44,17 +45,17 @@ class GeneratedAttributeSchema(HashableModel):
|
|
|
44
45
|
)
|
|
45
46
|
regex: str | None = Field(
|
|
46
47
|
default=None,
|
|
47
|
-
description="Regex uses to limit the characters allowed in for the attributes.",
|
|
48
|
+
description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)",
|
|
48
49
|
json_schema_extra={"update": "validate_constraint"},
|
|
49
50
|
)
|
|
50
51
|
max_length: int | None = Field(
|
|
51
52
|
default=None,
|
|
52
|
-
description="Set a maximum number of characters allowed for a given attribute.",
|
|
53
|
+
description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)",
|
|
53
54
|
json_schema_extra={"update": "validate_constraint"},
|
|
54
55
|
)
|
|
55
56
|
min_length: int | None = Field(
|
|
56
57
|
default=None,
|
|
57
|
-
description="Set a minimum number of characters allowed for a given attribute.",
|
|
58
|
+
description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)",
|
|
58
59
|
json_schema_extra={"update": "validate_constraint"},
|
|
59
60
|
)
|
|
60
61
|
label: str | None = Field(
|
|
@@ -112,6 +113,11 @@ class GeneratedAttributeSchema(HashableModel):
|
|
|
112
113
|
description="Type of allowed override for the attribute.",
|
|
113
114
|
json_schema_extra={"update": "allowed"},
|
|
114
115
|
)
|
|
116
|
+
parameters: AttributeParameters = Field(
|
|
117
|
+
default_factory=AttributeParameters,
|
|
118
|
+
description="Extra parameters specific to this kind of attribute",
|
|
119
|
+
json_schema_extra={"update": "validate_constraint"},
|
|
120
|
+
)
|
|
115
121
|
deprecation: str | None = Field(
|
|
116
122
|
default=None,
|
|
117
123
|
description="Mark attribute as deprecated and provide a user-friendly message to display",
|
|
@@ -49,6 +49,7 @@ from infrahub.core.schema import (
|
|
|
49
49
|
SchemaRoot,
|
|
50
50
|
TemplateSchema,
|
|
51
51
|
)
|
|
52
|
+
from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
|
|
52
53
|
from infrahub.core.schema.definitions.core import core_profile_schema_definition
|
|
53
54
|
from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
|
|
54
55
|
from infrahub.exceptions import SchemaNotFoundError, ValidationError
|
|
@@ -1136,7 +1137,7 @@ class SchemaBranch:
|
|
|
1136
1137
|
self.set(name=name, schema=node)
|
|
1137
1138
|
|
|
1138
1139
|
def process_labels(self) -> None:
|
|
1139
|
-
def check_if_need_to_update_label(node) -> bool:
|
|
1140
|
+
def check_if_need_to_update_label(node: MainSchemaTypes) -> bool:
|
|
1140
1141
|
if not node.label:
|
|
1141
1142
|
return True
|
|
1142
1143
|
for item in node.relationships + node.attributes:
|
|
@@ -1812,12 +1813,14 @@ class SchemaBranch:
|
|
|
1812
1813
|
def generate_profile_from_node(self, node: NodeSchema) -> ProfileSchema:
|
|
1813
1814
|
core_profile_schema = self.get(name=InfrahubKind.PROFILE, duplicate=False)
|
|
1814
1815
|
core_name_attr = core_profile_schema.get_attribute(name="profile_name")
|
|
1815
|
-
|
|
1816
|
+
name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
|
|
1817
|
+
profile_name_attr = name_attr_schema_class(
|
|
1816
1818
|
**core_name_attr.model_dump(exclude=["id", "inherited"]),
|
|
1817
1819
|
)
|
|
1818
1820
|
profile_name_attr.branch = node.branch
|
|
1819
1821
|
core_priority_attr = core_profile_schema.get_attribute(name="profile_priority")
|
|
1820
|
-
|
|
1822
|
+
priority_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_priority_attr.kind)
|
|
1823
|
+
profile_priority_attr = priority_attr_schema_class(
|
|
1821
1824
|
**core_priority_attr.model_dump(exclude=["id", "inherited"]),
|
|
1822
1825
|
)
|
|
1823
1826
|
profile_priority_attr.branch = node.branch
|
|
@@ -1848,8 +1851,8 @@ class SchemaBranch:
|
|
|
1848
1851
|
for node_attr in node.attributes:
|
|
1849
1852
|
if node_attr.read_only or node_attr.optional is False:
|
|
1850
1853
|
continue
|
|
1851
|
-
|
|
1852
|
-
attr =
|
|
1854
|
+
attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
|
|
1855
|
+
attr = attr_schema_class(
|
|
1853
1856
|
optional=True,
|
|
1854
1857
|
**node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "default_value", "inherited"]),
|
|
1855
1858
|
)
|
|
@@ -1973,7 +1976,8 @@ class SchemaBranch:
|
|
|
1973
1976
|
else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
|
|
1974
1977
|
)
|
|
1975
1978
|
core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
|
|
1976
|
-
|
|
1979
|
+
name_attr_schema_class = get_attribute_schema_class_for_kind(kind=core_name_attr.kind)
|
|
1980
|
+
template_name_attr = name_attr_schema_class(
|
|
1977
1981
|
**core_name_attr.model_dump(exclude=["id", "inherited"]),
|
|
1978
1982
|
)
|
|
1979
1983
|
template_name_attr.branch = node.branch
|
|
@@ -2033,7 +2037,8 @@ class SchemaBranch:
|
|
|
2033
2037
|
if node_attr.unique or node_attr.read_only:
|
|
2034
2038
|
continue
|
|
2035
2039
|
|
|
2036
|
-
|
|
2040
|
+
attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
|
|
2041
|
+
attr = attr_schema_class(
|
|
2037
2042
|
optional=node_attr.optional if is_autogenerated_subtemplate else True,
|
|
2038
2043
|
**node_attr.model_dump(exclude=["id", "unique", "optional", "read_only"]),
|
|
2039
2044
|
)
|
|
@@ -5,6 +5,7 @@ from .attribute.length import AttributeLengthChecker
|
|
|
5
5
|
from .attribute.optional import AttributeOptionalChecker
|
|
6
6
|
from .attribute.regex import AttributeRegexChecker
|
|
7
7
|
from .attribute.unique import AttributeUniquenessChecker
|
|
8
|
+
from .enum import ConstraintIdentifier
|
|
8
9
|
from .interface import ConstraintCheckerInterface
|
|
9
10
|
from .node.attribute import NodeAttributeAddChecker
|
|
10
11
|
from .node.generate_profile import NodeGenerateProfileChecker
|
|
@@ -17,11 +18,14 @@ from .relationship.peer import RelationshipPeerChecker
|
|
|
17
18
|
from .uniqueness.checker import UniquenessChecker
|
|
18
19
|
|
|
19
20
|
CONSTRAINT_VALIDATOR_MAP: dict[str, type[ConstraintCheckerInterface] | None] = {
|
|
21
|
+
"attribute.kind.update": AttributeKindChecker,
|
|
20
22
|
"attribute.regex.update": AttributeRegexChecker,
|
|
23
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value: AttributeRegexChecker,
|
|
21
24
|
"attribute.enum.update": AttributeEnumChecker,
|
|
22
|
-
"attribute.kind.update": AttributeKindChecker,
|
|
23
25
|
"attribute.min_length.update": AttributeLengthChecker,
|
|
24
26
|
"attribute.max_length.update": AttributeLengthChecker,
|
|
27
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value: AttributeLengthChecker,
|
|
28
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value: AttributeLengthChecker,
|
|
25
29
|
"attribute.unique.update": AttributeUniquenessChecker,
|
|
26
30
|
"attribute.optional.update": AttributeOptionalChecker,
|
|
27
31
|
"attribute.choices.update": AttributeChoicesChecker,
|
|
@@ -31,8 +31,7 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
31
31
|
|
|
32
32
|
query = """
|
|
33
33
|
MATCH p = (n:%(node_kind)s)
|
|
34
|
-
CALL {
|
|
35
|
-
WITH n
|
|
34
|
+
CALL (n) {
|
|
36
35
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
37
36
|
WHERE all(
|
|
38
37
|
r in relationships(path)
|
|
@@ -30,8 +30,7 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
30
30
|
self.params["null_value"] = NULL_VALUE
|
|
31
31
|
query = """
|
|
32
32
|
MATCH (n:%(node_kind)s)
|
|
33
|
-
CALL {
|
|
34
|
-
WITH n
|
|
33
|
+
CALL (n) {
|
|
35
34
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
36
35
|
WHERE all(
|
|
37
36
|
r in relationships(path)
|
|
@@ -37,8 +37,7 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
37
37
|
|
|
38
38
|
query = """
|
|
39
39
|
MATCH p = (n:%(node_kind)s)
|
|
40
|
-
CALL {
|
|
41
|
-
WITH n
|
|
40
|
+
CALL (n) {
|
|
42
41
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
43
42
|
WHERE all(
|
|
44
43
|
r in relationships(path)
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
4
4
|
|
|
5
5
|
from infrahub.core.constants import PathType
|
|
6
6
|
from infrahub.core.path import DataPath, GroupedDataPaths
|
|
7
|
+
from infrahub.core.validators.enum import ConstraintIdentifier
|
|
7
8
|
|
|
8
9
|
from ..interface import ConstraintCheckerInterface
|
|
9
10
|
from ..shared import AttributeSchemaValidatorQuery
|
|
@@ -23,13 +24,12 @@ class AttributeLengthUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
23
24
|
self.params.update(branch_params)
|
|
24
25
|
|
|
25
26
|
self.params["attr_name"] = self.attribute_schema.name
|
|
26
|
-
self.params["min_length"] = self.attribute_schema.
|
|
27
|
-
self.params["max_length"] = self.attribute_schema.
|
|
27
|
+
self.params["min_length"] = self.attribute_schema.get_min_length()
|
|
28
|
+
self.params["max_length"] = self.attribute_schema.get_max_length()
|
|
28
29
|
|
|
29
30
|
query = """
|
|
30
31
|
MATCH (n:%(node_kind)s)
|
|
31
|
-
CALL {
|
|
32
|
-
WITH n
|
|
32
|
+
CALL (n) {
|
|
33
33
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
34
34
|
WHERE all(
|
|
35
35
|
r in relationships(path)
|
|
@@ -79,14 +79,21 @@ class AttributeLengthChecker(ConstraintCheckerInterface):
|
|
|
79
79
|
return "attribute.length.update"
|
|
80
80
|
|
|
81
81
|
def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
|
|
82
|
-
return request.constraint_name in (
|
|
82
|
+
return request.constraint_name in (
|
|
83
|
+
"attribute.min_length.update",
|
|
84
|
+
"attribute.max_length.update",
|
|
85
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MIN_LENGTH_UPDATE.value,
|
|
86
|
+
ConstraintIdentifier.ATTRIBUTE_PARAMETERS_MAX_LENGTH_UPDATE.value,
|
|
87
|
+
)
|
|
83
88
|
|
|
84
89
|
async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
|
|
85
90
|
grouped_data_paths_list: list[GroupedDataPaths] = []
|
|
86
91
|
if not request.schema_path.field_name:
|
|
87
92
|
raise ValueError("field_name is not defined")
|
|
88
93
|
attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
|
|
89
|
-
|
|
94
|
+
min_length = attribute_schema.get_min_length()
|
|
95
|
+
max_length = attribute_schema.get_max_length()
|
|
96
|
+
if min_length is None and max_length is None:
|
|
90
97
|
return grouped_data_paths_list
|
|
91
98
|
|
|
92
99
|
for query_class in self.query_classes:
|
|
@@ -27,8 +27,7 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
27
27
|
|
|
28
28
|
query = """
|
|
29
29
|
MATCH (n:%(node_kind)s)
|
|
30
|
-
CALL {
|
|
31
|
-
WITH n
|
|
30
|
+
CALL (n) {
|
|
32
31
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
33
32
|
WHERE all(
|
|
34
33
|
r in relationships(path)
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
4
4
|
|
|
5
5
|
from infrahub.core.constants import NULL_VALUE, PathType
|
|
6
6
|
from infrahub.core.path import DataPath, GroupedDataPaths
|
|
7
|
+
from infrahub.core.validators.enum import ConstraintIdentifier
|
|
7
8
|
|
|
8
9
|
from ..interface import ConstraintCheckerInterface
|
|
9
10
|
from ..shared import AttributeSchemaValidatorQuery
|
|
@@ -23,12 +24,11 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
23
24
|
self.params.update(branch_params)
|
|
24
25
|
|
|
25
26
|
self.params["attr_name"] = self.attribute_schema.name
|
|
26
|
-
self.params["attr_value_regex"] = self.attribute_schema.
|
|
27
|
+
self.params["attr_value_regex"] = self.attribute_schema.get_regex()
|
|
27
28
|
self.params["null_value"] = NULL_VALUE
|
|
28
29
|
query = """
|
|
29
30
|
MATCH p = (n:%(node_kind)s)
|
|
30
|
-
CALL {
|
|
31
|
-
WITH n
|
|
31
|
+
CALL (n) {
|
|
32
32
|
MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
|
|
33
33
|
WHERE all(
|
|
34
34
|
r in relationships(path)
|
|
@@ -79,14 +79,14 @@ class AttributeRegexChecker(ConstraintCheckerInterface):
|
|
|
79
79
|
return "attribute.regex.update"
|
|
80
80
|
|
|
81
81
|
def supports(self, request: SchemaConstraintValidatorRequest) -> bool:
|
|
82
|
-
return request.constraint_name
|
|
82
|
+
return request.constraint_name in (self.name, ConstraintIdentifier.ATTRIBUTE_PARAMETERS_REGEX_UPDATE.value)
|
|
83
83
|
|
|
84
84
|
async def check(self, request: SchemaConstraintValidatorRequest) -> list[GroupedDataPaths]:
|
|
85
85
|
grouped_data_paths_list: list[GroupedDataPaths] = []
|
|
86
86
|
if not request.schema_path.field_name:
|
|
87
87
|
raise ValueError("field_name is not defined")
|
|
88
88
|
attribute_schema = request.node_schema.get_attribute(name=request.schema_path.field_name)
|
|
89
|
-
if not attribute_schema.
|
|
89
|
+
if not attribute_schema.get_regex():
|
|
90
90
|
return grouped_data_paths_list
|
|
91
91
|
|
|
92
92
|
for query_class in self.query_classes:
|
|
@@ -31,12 +31,10 @@ class AttributeUniqueUpdateValidatorQuery(AttributeSchemaValidatorQuery):
|
|
|
31
31
|
query = """
|
|
32
32
|
MATCH (potential_node:Node)
|
|
33
33
|
WHERE $node_kind IN LABELS(potential_node)
|
|
34
|
-
CALL {
|
|
35
|
-
WITH potential_node
|
|
34
|
+
CALL (potential_node) {
|
|
36
35
|
MATCH potential_path = (potential_node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name })-[potential_value_relationship:HAS_VALUE]-(potential_value:AttributeValue)
|
|
37
36
|
WHERE all(r IN relationships(potential_path) WHERE (%(branch_filter)s))
|
|
38
37
|
WITH
|
|
39
|
-
potential_node,
|
|
40
38
|
potential_value,
|
|
41
39
|
potential_value_relationship,
|
|
42
40
|
potential_path,
|