infrahub-server 1.6.3__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/actions/tasks.py +4 -2
- infrahub/api/exceptions.py +2 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +54 -28
- 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 +4 -4
- 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/branch_differ.py +1 -1
- infrahub/core/diff/conflict_transferer.py +1 -1
- infrahub/core/diff/data_check_synchronizer.py +4 -3
- infrahub/core/diff/enricher/cardinality_one.py +2 -2
- infrahub/core/diff/enricher/hierarchy.py +1 -1
- infrahub/core/diff/enricher/labels.py +1 -1
- infrahub/core/diff/merger/merger.py +28 -2
- 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/diff/repository/repository.py +3 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +3 -3
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/ipam/reconciler.py +8 -6
- infrahub/core/ipam/utilization.py +8 -15
- infrahub/core/manager.py +133 -152
- infrahub/core/merge.py +1 -1
- 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/m012_convert_account_generic.py +12 -12
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
- 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/m041_deleted_dup_edges.py +1 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/__init__.py +2 -2
- 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/query/schema_attribute_update.py +1 -1
- infrahub/core/migrations/schema/attribute_kind_update.py +21 -2
- infrahub/core/migrations/schema/attribute_name_update.py +1 -1
- infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
- infrahub/core/migrations/schema/node_kind_update.py +1 -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 +157 -58
- infrahub/core/node/base.py +9 -5
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/delete_validator.py +1 -1
- infrahub/core/node/standard.py +100 -14
- infrahub/core/order.py +30 -0
- infrahub/core/property.py +0 -1
- infrahub/core/protocols.py +1 -0
- infrahub/core/protocols_base.py +10 -2
- infrahub/core/query/__init__.py +5 -3
- infrahub/core/query/attribute.py +164 -49
- infrahub/core/query/branch.py +58 -70
- infrahub/core/query/delete.py +1 -1
- infrahub/core/query/diff.py +7 -7
- infrahub/core/query/ipam.py +104 -43
- infrahub/core/query/node.py +1072 -281
- infrahub/core/query/relationship.py +531 -325
- infrahub/core/query/resource_manager.py +107 -18
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/constraints/peer_kind.py +1 -1
- infrahub/core/relationship/constraints/peer_parent.py +1 -1
- infrahub/core/relationship/constraints/peer_relatives.py +1 -1
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_schema.py +2 -2
- infrahub/core/schema/basenode_schema.py +3 -0
- infrahub/core/schema/definitions/core/__init__.py +8 -2
- infrahub/core/schema/definitions/core/account.py +10 -10
- infrahub/core/schema/definitions/core/artifact.py +14 -8
- infrahub/core/schema/definitions/core/check.py +10 -4
- infrahub/core/schema/definitions/core/generator.py +26 -6
- infrahub/core/schema/definitions/core/graphql_query.py +1 -1
- infrahub/core/schema/definitions/core/group.py +9 -2
- infrahub/core/schema/definitions/core/ipam.py +80 -10
- infrahub/core/schema/definitions/core/menu.py +41 -7
- infrahub/core/schema/definitions/core/permission.py +16 -2
- infrahub/core/schema/definitions/core/profile.py +16 -2
- infrahub/core/schema/definitions/core/propose_change.py +24 -4
- infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
- infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
- infrahub/core/schema/definitions/core/repository.py +10 -0
- infrahub/core/schema/definitions/core/resource_pool.py +8 -1
- infrahub/core/schema/definitions/core/template.py +19 -2
- infrahub/core/schema/definitions/core/transform.py +11 -5
- infrahub/core/schema/definitions/core/webhook.py +27 -9
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +48 -10
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +3 -25
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/attribute/choices.py +1 -1
- infrahub/core/validators/attribute/enum.py +1 -1
- infrahub/core/validators/attribute/kind.py +1 -1
- infrahub/core/validators/attribute/length.py +1 -1
- infrahub/core/validators/attribute/min_max.py +1 -1
- infrahub/core/validators/attribute/number_pool.py +1 -1
- infrahub/core/validators/attribute/optional.py +1 -1
- infrahub/core/validators/attribute/regex.py +1 -1
- infrahub/core/validators/determiner.py +3 -3
- infrahub/core/validators/node/attribute.py +1 -1
- infrahub/core/validators/node/relationship.py +1 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/database/__init__.py +4 -4
- 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/git/utils.py +1 -1
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/field_extractor.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 +175 -21
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/mutations/account.py +6 -6
- 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/order.py +14 -0
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/resource_manager.py +25 -24
- 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 +59 -14
- 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 +12 -7
- infrahub/hfid/tasks.py +12 -3
- infrahub/lock.py +7 -0
- infrahub/menu/repository.py +1 -1
- infrahub/patch/queries/base.py +1 -1
- infrahub/pools/number.py +1 -8
- 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 +154 -13
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/action_checker.py +1 -1
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/services/__init__.py +1 -1
- infrahub/services/adapters/cache/nats.py +1 -1
- infrahub/services/adapters/cache/redis.py +7 -0
- 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/gather.py +1 -1
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +23 -7
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/analyzer.py +2 -2
- infrahub_sdk/branch.py +12 -39
- infrahub_sdk/checks.py +4 -4
- infrahub_sdk/client.py +36 -0
- infrahub_sdk/ctl/cli_commands.py +2 -1
- infrahub_sdk/ctl/graphql.py +15 -4
- infrahub_sdk/ctl/utils.py +2 -2
- infrahub_sdk/enums.py +6 -0
- infrahub_sdk/graphql/renderers.py +21 -0
- infrahub_sdk/graphql/utils.py +85 -0
- infrahub_sdk/node/attribute.py +12 -2
- infrahub_sdk/node/constants.py +12 -0
- infrahub_sdk/node/metadata.py +69 -0
- infrahub_sdk/node/node.py +65 -14
- infrahub_sdk/node/property.py +3 -0
- infrahub_sdk/node/related_node.py +37 -5
- infrahub_sdk/node/relationship.py +18 -1
- infrahub_sdk/operation.py +2 -2
- infrahub_sdk/schema/repository.py +1 -2
- infrahub_sdk/transforms.py +2 -2
- infrahub_sdk/types.py +18 -2
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +249 -228
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- infrahub_testcontainers/models.py +3 -3
- infrahub_testcontainers/performance_test.py +1 -1
- infrahub/graphql/models.py +0 -6
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
infrahub/core/node/standard.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
from dataclasses import dataclass, field
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Optional, Union, get_args, get_origin
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
8
9
|
import ujson
|
|
9
10
|
from infrahub_sdk.uuidt import UUIDT
|
|
10
|
-
from pydantic import BaseModel
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator
|
|
11
12
|
|
|
12
|
-
from infrahub.
|
|
13
|
+
from infrahub.constants.enums import OrderByField, OrderDirection
|
|
14
|
+
from infrahub.core.constants import NULL_VALUE, SYSTEM_USER_ID, InfrahubKind
|
|
13
15
|
from infrahub.core.query.standard_node import (
|
|
14
16
|
StandardNodeCreateQuery,
|
|
15
17
|
StandardNodeDeleteQuery,
|
|
@@ -18,6 +20,7 @@ from infrahub.core.query.standard_node import (
|
|
|
18
20
|
StandardNodeQuery,
|
|
19
21
|
StandardNodeUpdateQuery,
|
|
20
22
|
)
|
|
23
|
+
from infrahub.core.timestamp import Timestamp, current_timestamp
|
|
21
24
|
from infrahub.exceptions import Error, InitializationError
|
|
22
25
|
|
|
23
26
|
if TYPE_CHECKING:
|
|
@@ -29,9 +32,25 @@ if TYPE_CHECKING:
|
|
|
29
32
|
from infrahub.database import InfrahubDatabase
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class StandardNodeOrdering:
|
|
37
|
+
order_by: OrderByField = field(default=OrderByField.ID)
|
|
38
|
+
direction: OrderDirection = field(default=OrderDirection.ASC)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class StandardNodeQueryFields:
|
|
43
|
+
node: dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
node_metadata: dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
class StandardNode(BaseModel):
|
|
33
48
|
id: Optional[str] = None
|
|
34
49
|
uuid: Optional[UUID] = None
|
|
50
|
+
created_at: Optional[str] = Field(default=None, validate_default=True)
|
|
51
|
+
created_by: str = Field(default=SYSTEM_USER_ID)
|
|
52
|
+
updated_by: Optional[str] = Field(default=None)
|
|
53
|
+
updated_at: Optional[str] = Field(default=None, validate_default=True)
|
|
35
54
|
|
|
36
55
|
_query: type[StandardNodeQuery] = StandardNodeCreateQuery
|
|
37
56
|
_exclude_attrs: list[str] = ["id", "uuid", "_query"]
|
|
@@ -45,6 +64,11 @@ class StandardNode(BaseModel):
|
|
|
45
64
|
raise ValueError("id isn't defined yet")
|
|
46
65
|
return self.id
|
|
47
66
|
|
|
67
|
+
@field_validator("created_at", mode="before")
|
|
68
|
+
@classmethod
|
|
69
|
+
def set_created_at(cls, value: str) -> str:
|
|
70
|
+
return Timestamp(value).to_string()
|
|
71
|
+
|
|
48
72
|
@staticmethod
|
|
49
73
|
def guess_field_type(field: FieldInfo) -> Any:
|
|
50
74
|
"""Return the type of a Pydantic model field.
|
|
@@ -68,7 +92,14 @@ class StandardNode(BaseModel):
|
|
|
68
92
|
|
|
69
93
|
raise InitializationError("The root node has not been initialized with a uuid")
|
|
70
94
|
|
|
71
|
-
async def
|
|
95
|
+
async def to_graphql_flat(self, fields: dict) -> dict:
|
|
96
|
+
"""Returns the GraphQL representation of the object with only top-level fields.
|
|
97
|
+
|
|
98
|
+
This method does not handle nested fields and is only used for the old `Branch` query which
|
|
99
|
+
will be deprecated in the future and replaced by the `InfrahubBranch` query.
|
|
100
|
+
It's also used for the old style of Branch mutations that will be deprecated in the future,
|
|
101
|
+
when we introduce InfrahubBranch muations for consistency.
|
|
102
|
+
"""
|
|
72
103
|
response: dict[str, Any] = {"id": self.uuid}
|
|
73
104
|
|
|
74
105
|
for field_name in fields.keys():
|
|
@@ -77,21 +108,71 @@ class StandardNode(BaseModel):
|
|
|
77
108
|
if field_name == "__typename":
|
|
78
109
|
response[field_name] = self.get_type()
|
|
79
110
|
continue
|
|
80
|
-
field = getattr(self, field_name)
|
|
111
|
+
field = getattr(self, field_name, None)
|
|
81
112
|
if field is None:
|
|
82
113
|
response[field_name] = None
|
|
83
114
|
continue
|
|
115
|
+
if isinstance(fields.get(field_name), dict):
|
|
116
|
+
result = {}
|
|
117
|
+
for nested_field in fields.get(field_name, {}).keys():
|
|
118
|
+
if nested_field == "value":
|
|
119
|
+
result[nested_field] = field
|
|
120
|
+
continue
|
|
121
|
+
response[field_name] = result
|
|
122
|
+
continue
|
|
123
|
+
|
|
84
124
|
response[field_name] = field
|
|
85
125
|
|
|
86
126
|
return response
|
|
87
127
|
|
|
88
|
-
async def
|
|
128
|
+
async def to_graphql(self, fields: StandardNodeQueryFields) -> dict:
|
|
129
|
+
node_response: dict[str, Any] = {}
|
|
130
|
+
meta_response: dict[str, Any] = {}
|
|
131
|
+
|
|
132
|
+
for field_name in fields.node.keys():
|
|
133
|
+
if field_name == "id":
|
|
134
|
+
node_response["id"] = self.uuid
|
|
135
|
+
continue
|
|
136
|
+
if field_name == "__typename":
|
|
137
|
+
node_response[field_name] = self.get_type()
|
|
138
|
+
continue
|
|
139
|
+
field = getattr(self, field_name, None)
|
|
140
|
+
if field is None:
|
|
141
|
+
node_response[field_name] = None
|
|
142
|
+
continue
|
|
143
|
+
if isinstance(fields.node.get(field_name), dict):
|
|
144
|
+
result = {}
|
|
145
|
+
for nested_field in fields.node.get(field_name, {}).keys():
|
|
146
|
+
if nested_field == "value":
|
|
147
|
+
result[nested_field] = field
|
|
148
|
+
continue
|
|
149
|
+
node_response[field_name] = result
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
node_response[field_name] = field
|
|
153
|
+
|
|
154
|
+
for field_name in fields.node_metadata.keys():
|
|
155
|
+
match field_name:
|
|
156
|
+
case "created_at":
|
|
157
|
+
meta_response["created_at"] = Timestamp(self.created_at).to_datetime() if self.created_at else None
|
|
158
|
+
case "updated_at":
|
|
159
|
+
meta_response["updated_at"] = Timestamp(self.updated_at).to_datetime() if self.updated_at else None
|
|
160
|
+
case "created_by":
|
|
161
|
+
if self.created_by and self.created_by != SYSTEM_USER_ID:
|
|
162
|
+
meta_response["created_by"] = {"id": self.created_by, "__kind__": InfrahubKind.ACCOUNT}
|
|
163
|
+
case "updated_by":
|
|
164
|
+
if self.updated_by and self.updated_by != SYSTEM_USER_ID:
|
|
165
|
+
meta_response["updated_by"] = {"id": self.updated_by, "__kind__": InfrahubKind.ACCOUNT}
|
|
166
|
+
|
|
167
|
+
return {"node": node_response, "node_metadata": meta_response}
|
|
168
|
+
|
|
169
|
+
async def save(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
89
170
|
"""Create or Update the Node in the database."""
|
|
90
171
|
|
|
91
172
|
if self.id:
|
|
92
|
-
return await self.update(db=db)
|
|
173
|
+
return await self.update(db=db, user_id=user_id)
|
|
93
174
|
|
|
94
|
-
return await self.create(db=db)
|
|
175
|
+
return await self.create(db=db, user_id=user_id)
|
|
95
176
|
|
|
96
177
|
async def delete(self, db: InfrahubDatabase) -> None:
|
|
97
178
|
"""Delete the Node in the database."""
|
|
@@ -99,9 +180,11 @@ class StandardNode(BaseModel):
|
|
|
99
180
|
query: Query = await StandardNodeDeleteQuery.init(db=db, node=self)
|
|
100
181
|
await query.execute(db=db)
|
|
101
182
|
|
|
102
|
-
async def create(self, db: InfrahubDatabase) -> bool:
|
|
183
|
+
async def create(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
103
184
|
"""Create a new node in the database."""
|
|
104
|
-
|
|
185
|
+
self.created_by = user_id
|
|
186
|
+
self.updated_by = self.created_by
|
|
187
|
+
self.updated_at = self.created_at
|
|
105
188
|
query: Query = await self._query.init(db=db, node=self)
|
|
106
189
|
await query.execute(db=db)
|
|
107
190
|
|
|
@@ -115,9 +198,10 @@ class StandardNode(BaseModel):
|
|
|
115
198
|
|
|
116
199
|
return True
|
|
117
200
|
|
|
118
|
-
async def update(self, db: InfrahubDatabase) -> bool:
|
|
201
|
+
async def update(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
|
|
119
202
|
"""Update the node in the database if needed."""
|
|
120
|
-
|
|
203
|
+
self.updated_by = user_id
|
|
204
|
+
self.updated_at = current_timestamp()
|
|
121
205
|
query: Query = await StandardNodeUpdateQuery.init(db=db, node=self)
|
|
122
206
|
await query.execute(db=db)
|
|
123
207
|
result = query.get_result()
|
|
@@ -187,7 +271,7 @@ class StandardNode(BaseModel):
|
|
|
187
271
|
else:
|
|
188
272
|
data["uuid"] = str(self.uuid)
|
|
189
273
|
|
|
190
|
-
for attr_name,
|
|
274
|
+
for attr_name, field_info in self.__class__.model_fields.items():
|
|
191
275
|
if attr_name in self._exclude_attrs:
|
|
192
276
|
continue
|
|
193
277
|
|
|
@@ -195,7 +279,7 @@ class StandardNode(BaseModel):
|
|
|
195
279
|
if isinstance(attr_value, Enum):
|
|
196
280
|
attr_value = attr_value.value
|
|
197
281
|
|
|
198
|
-
field_type = self.guess_field_type(
|
|
282
|
+
field_type = self.guess_field_type(field_info)
|
|
199
283
|
|
|
200
284
|
if attr_value is None:
|
|
201
285
|
data[attr_name] = NULL_VALUE
|
|
@@ -219,10 +303,12 @@ class StandardNode(BaseModel):
|
|
|
219
303
|
limit: int = 1000,
|
|
220
304
|
ids: list[str] | None = None,
|
|
221
305
|
name: str | None = None,
|
|
306
|
+
node_ordering: StandardNodeOrdering | None = None,
|
|
222
307
|
**kwargs: dict[str, Any],
|
|
223
308
|
) -> list[Self]:
|
|
309
|
+
node_ordering = node_ordering or StandardNodeOrdering()
|
|
224
310
|
query: Query = await StandardNodeGetListQuery.init(
|
|
225
|
-
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
|
|
311
|
+
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, node_ordering=node_ordering, **kwargs
|
|
226
312
|
)
|
|
227
313
|
await query.execute(db=db)
|
|
228
314
|
|
infrahub/core/order.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
from infrahub.constants.enums import OrderDirection # noqa: TC001
|
|
8
|
+
from infrahub.exceptions import ValidationError
|
|
9
|
+
|
|
10
|
+
# Metadata field name constants
|
|
11
|
+
METADATA_CREATED_AT = "created_at"
|
|
12
|
+
METADATA_CREATED_BY = "created_by"
|
|
13
|
+
METADATA_UPDATED_AT = "updated_at"
|
|
14
|
+
METADATA_UPDATED_BY = "updated_by"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NodeMetaOrder(BaseModel):
|
|
18
|
+
created_at: OrderDirection | None = None
|
|
19
|
+
updated_at: OrderDirection | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OrderModel(BaseModel):
|
|
23
|
+
disable: bool | None = None
|
|
24
|
+
node_metadata: NodeMetaOrder | None = None
|
|
25
|
+
|
|
26
|
+
@model_validator(mode="after")
|
|
27
|
+
def validate_metadata(self) -> Self:
|
|
28
|
+
if self.node_metadata and self.node_metadata.created_at and self.node_metadata.updated_at:
|
|
29
|
+
raise ValidationError("Cannot order by both created_at and updated_at simultaneously.")
|
|
30
|
+
return self
|
infrahub/core/property.py
CHANGED
infrahub/core/protocols.py
CHANGED
infrahub/core/protocols_base.py
CHANGED
|
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
|
4
4
|
|
|
5
5
|
from typing_extensions import Self
|
|
6
6
|
|
|
7
|
+
from infrahub.core.constants import SYSTEM_USER_ID
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from neo4j import AsyncResult, AsyncSession, AsyncTransaction, Record
|
|
9
11
|
|
|
@@ -82,8 +84,10 @@ class CoreNode(Protocol):
|
|
|
82
84
|
at: Timestamp | str | None = None,
|
|
83
85
|
) -> Self: ...
|
|
84
86
|
async def new(self, db: InfrahubDatabase, id: str | None = None, **kwargs: Any) -> Self: ...
|
|
85
|
-
async def save(self, db: InfrahubDatabase, at: Timestamp | None = None) -> Self: ...
|
|
86
|
-
async def delete(
|
|
87
|
+
async def save(self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID) -> Self: ...
|
|
88
|
+
async def delete(
|
|
89
|
+
self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID
|
|
90
|
+
) -> None: ...
|
|
87
91
|
async def load(
|
|
88
92
|
self,
|
|
89
93
|
db: InfrahubDatabase,
|
|
@@ -102,3 +106,7 @@ class CoreNode(Protocol):
|
|
|
102
106
|
) -> dict: ...
|
|
103
107
|
async def render_display_label(self, db: InfrahubDatabase | None = None) -> str: ...
|
|
104
108
|
async def from_graphql(self, data: dict, db: InfrahubDatabase) -> bool: ...
|
|
109
|
+
def _get_created_at(self) -> Timestamp | None: ...
|
|
110
|
+
def _get_created_by(self) -> str | None: ...
|
|
111
|
+
def _get_updated_at(self) -> Timestamp | None: ...
|
|
112
|
+
def _get_updated_by(self) -> str | None: ...
|
infrahub/core/query/__init__.py
CHANGED
|
@@ -12,7 +12,7 @@ from neo4j.graph import Relationship as Neo4jRelationship
|
|
|
12
12
|
from opentelemetry import trace
|
|
13
13
|
|
|
14
14
|
from infrahub import config
|
|
15
|
-
from infrahub.core.constants import PermissionLevel
|
|
15
|
+
from infrahub.core.constants import SYSTEM_USER_ID, PermissionLevel
|
|
16
16
|
from infrahub.core.timestamp import Timestamp
|
|
17
17
|
from infrahub.exceptions import QueryError
|
|
18
18
|
|
|
@@ -159,7 +159,7 @@ def cleanup_return_labels(labels: list[str]) -> list[str]:
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
class QueryResult:
|
|
162
|
-
def __init__(self, data: list[Neo4jNode | Neo4jRelationship | list[Neo4jNode]], labels: list[str]):
|
|
162
|
+
def __init__(self, data: list[Neo4jNode | Neo4jRelationship | list[Neo4jNode]], labels: list[str]) -> None:
|
|
163
163
|
self.data = data
|
|
164
164
|
self.labels = labels
|
|
165
165
|
self.branch_score: int = 0
|
|
@@ -351,7 +351,8 @@ class Query:
|
|
|
351
351
|
offset: int | None = None,
|
|
352
352
|
order_by: list[str] | None = None,
|
|
353
353
|
branch_agnostic: bool = False,
|
|
354
|
-
|
|
354
|
+
user_id: str = SYSTEM_USER_ID,
|
|
355
|
+
) -> None:
|
|
355
356
|
if branch:
|
|
356
357
|
self.branch = branch
|
|
357
358
|
|
|
@@ -366,6 +367,7 @@ class Query:
|
|
|
366
367
|
self.limit = limit
|
|
367
368
|
self.offset = offset
|
|
368
369
|
self.order_by = order_by
|
|
370
|
+
self.user_id = user_id
|
|
369
371
|
|
|
370
372
|
# Initialize internal variables
|
|
371
373
|
self.params: dict = {}
|
infrahub/core/query/attribute.py
CHANGED
|
@@ -31,7 +31,7 @@ class AttributeQuery(Query):
|
|
|
31
31
|
at: Timestamp | str | None = None,
|
|
32
32
|
branch: Branch | None = None,
|
|
33
33
|
**kwargs: Any,
|
|
34
|
-
):
|
|
34
|
+
) -> None:
|
|
35
35
|
self.attr = attr
|
|
36
36
|
self.attr_id = attr_id or attr.db_id
|
|
37
37
|
|
|
@@ -48,13 +48,14 @@ class AttributeQuery(Query):
|
|
|
48
48
|
class AttributeUpdateValueQuery(AttributeQuery):
|
|
49
49
|
name = "attribute_update_value"
|
|
50
50
|
type: QueryType = QueryType.WRITE
|
|
51
|
-
|
|
52
|
-
raise_error_if_empty: bool =
|
|
51
|
+
insert_return: bool = False
|
|
52
|
+
raise_error_if_empty: bool = False
|
|
53
53
|
|
|
54
54
|
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
55
55
|
at = self.at or self.attr.at
|
|
56
56
|
|
|
57
57
|
self.params["attr_uuid"] = self.attr.id
|
|
58
|
+
self.params["user_id"] = self.user_id
|
|
58
59
|
self.params["branch"] = self.branch.name
|
|
59
60
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
60
61
|
self.params["at"] = at.to_string()
|
|
@@ -73,29 +74,46 @@ class AttributeUpdateValueQuery(AttributeQuery):
|
|
|
73
74
|
labels.append(GraphAttributeIPNetworkNode.get_default_label())
|
|
74
75
|
|
|
75
76
|
query = """
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
78
|
+
MERGE (av:%(labels)s { %(props)s } )
|
|
79
|
+
WITH av, a
|
|
80
|
+
LIMIT 1
|
|
81
|
+
// ----------
|
|
82
|
+
// find the existing HAS_VALUE edge, if it exists, and set the to time and user_id
|
|
83
|
+
// ---------
|
|
84
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
|
|
85
|
+
WHERE existing_active_r.to IS NULL
|
|
86
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
87
|
+
WITH av, a
|
|
88
|
+
LIMIT 1
|
|
89
|
+
// ----------
|
|
90
|
+
// create the new HAS_VALUE edge
|
|
91
|
+
// ---------
|
|
92
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(av)
|
|
93
|
+
// ----------
|
|
94
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
95
|
+
// ---------
|
|
96
|
+
WITH a
|
|
97
|
+
WHERE $branch_level = 1
|
|
98
|
+
LIMIT 1
|
|
99
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
81
100
|
""" % {"rel_label": self.attr._rel_to_value_label, "labels": ":".join(labels), "props": ", ".join(prop_list)}
|
|
82
101
|
|
|
83
102
|
self.add_to_query(query)
|
|
84
|
-
self.return_labels = ["a", "av", "r"]
|
|
85
103
|
|
|
86
104
|
|
|
87
105
|
class AttributeUpdateFlagQuery(AttributeQuery):
|
|
88
106
|
name = "attribute_update_flag"
|
|
89
107
|
type: QueryType = QueryType.WRITE
|
|
90
|
-
|
|
91
|
-
raise_error_if_empty: bool =
|
|
108
|
+
insert_return: bool = False
|
|
109
|
+
raise_error_if_empty: bool = False
|
|
92
110
|
|
|
93
111
|
def __init__(
|
|
94
112
|
self,
|
|
95
113
|
flag_name: str,
|
|
96
114
|
**kwargs: Any,
|
|
97
115
|
) -> None:
|
|
98
|
-
SUPPORTED_FLAGS = ["
|
|
116
|
+
SUPPORTED_FLAGS = ["is_protected"]
|
|
99
117
|
|
|
100
118
|
if flag_name not in SUPPORTED_FLAGS:
|
|
101
119
|
raise ValueError(f"Only {SUPPORTED_FLAGS} are supported for now.")
|
|
@@ -108,6 +126,7 @@ class AttributeUpdateFlagQuery(AttributeQuery):
|
|
|
108
126
|
at = self.at or self.attr.at
|
|
109
127
|
|
|
110
128
|
self.params["attr_uuid"] = self.attr.id
|
|
129
|
+
self.params["user_id"] = self.user_id
|
|
111
130
|
self.params["branch"] = self.branch.name
|
|
112
131
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
113
132
|
self.params["at"] = at.to_string()
|
|
@@ -115,27 +134,44 @@ class AttributeUpdateFlagQuery(AttributeQuery):
|
|
|
115
134
|
self.params["flag_type"] = self.attr.get_kind()
|
|
116
135
|
|
|
117
136
|
query = """
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
138
|
+
MERGE (flag:Boolean { value: $flag_value })
|
|
139
|
+
WITH flag, a
|
|
140
|
+
LIMIT 1
|
|
141
|
+
// ----------
|
|
142
|
+
// find the existing property edge, if it exists, and set the to time and user_id
|
|
143
|
+
// ---------
|
|
144
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(flag_type)s { branch: $branch, status: "active" }]->()
|
|
145
|
+
WHERE existing_active_r.to IS NULL
|
|
146
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
147
|
+
// ----------
|
|
148
|
+
// create the new property edge
|
|
149
|
+
// ---------
|
|
150
|
+
WITH a, flag
|
|
151
|
+
CREATE (a)-[r:%(flag_type)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(flag)
|
|
152
|
+
// ----------
|
|
153
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
154
|
+
// ---------
|
|
155
|
+
WITH a
|
|
156
|
+
WHERE $branch_level = 1
|
|
157
|
+
LIMIT 1
|
|
158
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
159
|
+
""" % {"flag_type": self.flag_name.upper()}
|
|
123
160
|
self.add_to_query(query)
|
|
124
|
-
self.return_labels = ["a", "flag", "r"]
|
|
125
161
|
|
|
126
162
|
|
|
127
163
|
class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
128
164
|
name = "attribute_update_node_property"
|
|
129
165
|
type: QueryType = QueryType.WRITE
|
|
130
|
-
|
|
131
|
-
raise_error_if_empty: bool =
|
|
166
|
+
insert_return: bool = False
|
|
167
|
+
raise_error_if_empty: bool = False
|
|
132
168
|
|
|
133
169
|
def __init__(
|
|
134
170
|
self,
|
|
135
171
|
prop_name: str,
|
|
136
172
|
prop_id: str | None = None,
|
|
137
173
|
**kwargs: Any,
|
|
138
|
-
):
|
|
174
|
+
) -> None:
|
|
139
175
|
self.prop_name = prop_name
|
|
140
176
|
self.prop_id = prop_id
|
|
141
177
|
|
|
@@ -147,6 +183,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
147
183
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
|
|
148
184
|
self.params.update(branch_params)
|
|
149
185
|
self.params["attr_uuid"] = self.attr.id
|
|
186
|
+
self.params["user_id"] = self.user_id
|
|
150
187
|
self.params["branch"] = self.branch.name
|
|
151
188
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
152
189
|
self.params["at"] = at.to_string()
|
|
@@ -176,13 +213,29 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
|
|
|
176
213
|
self.add_to_query(node_query)
|
|
177
214
|
|
|
178
215
|
attr_query = """
|
|
179
|
-
|
|
180
|
-
|
|
216
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
217
|
+
// ----------
|
|
218
|
+
// find the existing property edge, if it exists, and set the to time and user_id
|
|
219
|
+
// ---------
|
|
220
|
+
OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
|
|
221
|
+
WHERE existing_active_r.to IS NULL
|
|
222
|
+
SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
|
|
223
|
+
// ----------
|
|
224
|
+
// create the new property edge
|
|
225
|
+
// ---------
|
|
226
|
+
WITH a, np
|
|
227
|
+
LIMIT 1
|
|
228
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(np)
|
|
229
|
+
// ----------
|
|
230
|
+
// update the Attribute node with the new timestamp and user id if we are on the default or global branch
|
|
231
|
+
// ---------
|
|
232
|
+
WITH a
|
|
233
|
+
WHERE $branch_level = 1
|
|
234
|
+
LIMIT 1
|
|
235
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
181
236
|
""" % {"rel_label": rel_label}
|
|
182
237
|
self.add_to_query(attr_query)
|
|
183
238
|
|
|
184
|
-
self.return_labels = ["a", "np", "r"]
|
|
185
|
-
|
|
186
239
|
|
|
187
240
|
class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
188
241
|
name = "attribute_clear_node_property"
|
|
@@ -192,11 +245,9 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
|
192
245
|
def __init__(
|
|
193
246
|
self,
|
|
194
247
|
prop_name: str,
|
|
195
|
-
prop_id: str | None = None,
|
|
196
248
|
**kwargs: Any,
|
|
197
|
-
):
|
|
249
|
+
) -> None:
|
|
198
250
|
self.prop_name = prop_name
|
|
199
|
-
self.prop_id = prop_id
|
|
200
251
|
|
|
201
252
|
super().__init__(**kwargs)
|
|
202
253
|
|
|
@@ -206,15 +257,14 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
|
|
|
206
257
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
|
|
207
258
|
self.params.update(branch_params)
|
|
208
259
|
self.params["attr_uuid"] = self.attr.id
|
|
260
|
+
self.params["user_id"] = self.user_id
|
|
209
261
|
self.params["branch"] = self.branch.name
|
|
210
262
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
211
263
|
self.params["at"] = at.to_string()
|
|
212
|
-
self.params["prop_name"] = self.prop_name
|
|
213
|
-
self.params["prop_id"] = self.prop_id
|
|
214
264
|
|
|
215
265
|
rel_label = f"HAS_{self.prop_name.upper()}"
|
|
216
266
|
query = """
|
|
217
|
-
MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node
|
|
267
|
+
MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node)
|
|
218
268
|
WITH DISTINCT a, np
|
|
219
269
|
CALL (a, np) {
|
|
220
270
|
MATCH (a)-[r:%(rel_label)s]->(np)
|
|
@@ -228,42 +278,107 @@ WHERE property_edge.status = "active"
|
|
|
228
278
|
CALL (property_edge) {
|
|
229
279
|
WITH property_edge
|
|
230
280
|
WHERE property_edge.branch = $branch
|
|
231
|
-
SET property_edge.to = $at
|
|
281
|
+
SET property_edge.to = $at, property_edge.to_user_id = $user_id
|
|
232
282
|
}
|
|
233
283
|
CALL (a, np, property_edge) {
|
|
234
284
|
WITH property_edge
|
|
235
285
|
WHERE property_edge.branch_level < $branch_level
|
|
236
|
-
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at }]->(np)
|
|
286
|
+
CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]->(np)
|
|
287
|
+
}
|
|
288
|
+
CALL (a) {
|
|
289
|
+
WITH a
|
|
290
|
+
WHERE $branch_level = 1
|
|
291
|
+
LIMIT 1
|
|
292
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
237
293
|
}
|
|
238
294
|
""" % {"branch_filter": branch_filter, "rel_label": rel_label}
|
|
239
295
|
self.add_to_query(query)
|
|
240
296
|
|
|
241
297
|
|
|
242
|
-
class
|
|
243
|
-
name = "
|
|
244
|
-
type: QueryType = QueryType.
|
|
298
|
+
class AttributeDeleteQuery(AttributeQuery):
|
|
299
|
+
name = "attribute_delete"
|
|
300
|
+
type: QueryType = QueryType.WRITE
|
|
301
|
+
insert_return: bool = False
|
|
245
302
|
|
|
246
303
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
247
304
|
self.params["attr_uuid"] = self.attr.id
|
|
248
|
-
self.params["
|
|
249
|
-
|
|
305
|
+
self.params["user_id"] = self.user_id
|
|
306
|
+
self.params["branch"] = self.branch.name
|
|
307
|
+
self.params["branch_level"] = self.branch.hierarchy_level
|
|
308
|
+
self.params["branched_from"] = self.branch.get_branched_from()
|
|
250
309
|
self.params["at"] = self.at.to_string()
|
|
251
310
|
|
|
252
|
-
|
|
253
|
-
self.params.update(
|
|
311
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
|
|
312
|
+
self.params.update(branch_params)
|
|
254
313
|
|
|
255
|
-
query =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
314
|
+
query = """
|
|
315
|
+
MATCH (a:Attribute { uuid: $attr_uuid })
|
|
316
|
+
CALL (a) {
|
|
317
|
+
WITH a
|
|
318
|
+
WHERE $branch_level = 1
|
|
319
|
+
LIMIT 1
|
|
320
|
+
SET a.updated_at = $at, a.updated_by = $user_id
|
|
321
|
+
}
|
|
263
322
|
|
|
323
|
+
UNWIND [
|
|
324
|
+
["HAS_ATTRIBUTE", "in"],
|
|
325
|
+
["HAS_VALUE", "out"],
|
|
326
|
+
["IS_PROTECTED", "out"],
|
|
327
|
+
["HAS_SOURCE", "out"],
|
|
328
|
+
["HAS_OWNER", "out"]
|
|
329
|
+
] AS edge_details
|
|
330
|
+
WITH a, edge_details[0] AS property_type, edge_details[1] AS direction
|
|
331
|
+
CALL (a, property_type, direction) {
|
|
332
|
+
MATCH (a)-[r]-(attr_peer)
|
|
333
|
+
WHERE type(r) = property_type
|
|
334
|
+
AND (
|
|
335
|
+
(direction = "in" AND startNode(r) = attr_peer)
|
|
336
|
+
OR (direction = "out" AND startNode(r) = a)
|
|
337
|
+
)
|
|
338
|
+
AND %(branch_filter)s
|
|
339
|
+
RETURN r AS property_edge, attr_peer
|
|
340
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
341
|
+
LIMIT 1
|
|
342
|
+
}
|
|
343
|
+
CALL (property_edge) {
|
|
344
|
+
WITH property_edge
|
|
345
|
+
WHERE property_edge.status = "active"
|
|
346
|
+
AND property_edge.branch = $branch
|
|
347
|
+
AND property_edge.to IS NULL
|
|
348
|
+
SET property_edge.to = $at, property_edge.to_user_id = $user_id
|
|
349
|
+
}
|
|
350
|
+
WITH a, property_edge, property_type, attr_peer, direction
|
|
351
|
+
CALL (a, property_type, attr_peer, direction) {
|
|
352
|
+
WITH direction
|
|
353
|
+
WHERE direction = "out"
|
|
354
|
+
CREATE (a)
|
|
355
|
+
-[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
|
|
356
|
+
->(attr_peer)
|
|
357
|
+
}
|
|
358
|
+
CALL (a, property_type, attr_peer, direction) {
|
|
359
|
+
WITH direction
|
|
360
|
+
WHERE direction = "in"
|
|
361
|
+
CREATE (a)
|
|
362
|
+
<-[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
|
|
363
|
+
-(attr_peer)
|
|
364
|
+
}
|
|
365
|
+
WITH CASE
|
|
366
|
+
WHEN property_type = "HAS_VALUE" THEN attr_peer.value
|
|
367
|
+
ELSE NULL
|
|
368
|
+
END AS property_value
|
|
369
|
+
WITH property_value
|
|
370
|
+
RETURN property_value
|
|
371
|
+
ORDER BY property_value ASC
|
|
372
|
+
LIMIT 1
|
|
373
|
+
""" % {"branch_filter": branch_filter}
|
|
264
374
|
self.add_to_query(query)
|
|
375
|
+
self.return_labels = ["property_value"]
|
|
265
376
|
|
|
266
|
-
|
|
377
|
+
def get_previous_property_value(self) -> Any:
|
|
378
|
+
result = self.get_result()
|
|
379
|
+
if result:
|
|
380
|
+
return result.get(label="property_value")
|
|
381
|
+
return None
|
|
267
382
|
|
|
268
383
|
|
|
269
384
|
async def default_attribute_query_filter(
|