infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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.
Files changed (178) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/query.py +2 -0
  5. infrahub/api/schema.py +3 -0
  6. infrahub/auth.py +5 -5
  7. infrahub/cli/db.py +26 -2
  8. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  9. infrahub/config.py +7 -2
  10. infrahub/core/attribute.py +25 -22
  11. infrahub/core/branch/models.py +2 -2
  12. infrahub/core/branch/needs_rebase_status.py +11 -0
  13. infrahub/core/branch/tasks.py +4 -3
  14. infrahub/core/changelog/models.py +4 -12
  15. infrahub/core/constants/__init__.py +1 -0
  16. infrahub/core/constants/infrahubkind.py +1 -0
  17. infrahub/core/convert_object_type/object_conversion.py +201 -0
  18. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  19. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  20. infrahub/core/diff/model/path.py +4 -0
  21. infrahub/core/diff/payload_builder.py +1 -1
  22. infrahub/core/diff/query/artifact.py +1 -1
  23. infrahub/core/graph/__init__.py +1 -1
  24. infrahub/core/initialization.py +2 -2
  25. infrahub/core/ipam/utilization.py +1 -1
  26. infrahub/core/manager.py +9 -84
  27. infrahub/core/migrations/graph/__init__.py +6 -0
  28. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  29. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
  30. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
  31. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  32. infrahub/core/migrations/shared.py +5 -6
  33. infrahub/core/node/__init__.py +165 -42
  34. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  35. infrahub/core/node/create.py +67 -35
  36. infrahub/core/node/lock_utils.py +98 -0
  37. infrahub/core/node/node_property_attribute.py +230 -0
  38. infrahub/core/node/standard.py +1 -1
  39. infrahub/core/property.py +11 -0
  40. infrahub/core/protocols.py +8 -1
  41. infrahub/core/query/attribute.py +27 -15
  42. infrahub/core/query/node.py +61 -185
  43. infrahub/core/query/relationship.py +43 -26
  44. infrahub/core/query/subquery.py +0 -8
  45. infrahub/core/registry.py +2 -2
  46. infrahub/core/relationship/constraints/count.py +1 -1
  47. infrahub/core/relationship/model.py +60 -20
  48. infrahub/core/schema/attribute_schema.py +0 -2
  49. infrahub/core/schema/basenode_schema.py +42 -2
  50. infrahub/core/schema/definitions/core/__init__.py +2 -0
  51. infrahub/core/schema/definitions/core/generator.py +2 -0
  52. infrahub/core/schema/definitions/core/group.py +16 -2
  53. infrahub/core/schema/definitions/core/repository.py +7 -0
  54. infrahub/core/schema/definitions/internal.py +14 -1
  55. infrahub/core/schema/generated/base_node_schema.py +6 -1
  56. infrahub/core/schema/node_schema.py +5 -2
  57. infrahub/core/schema/relationship_schema.py +0 -1
  58. infrahub/core/schema/schema_branch.py +137 -2
  59. infrahub/core/schema/schema_branch_display.py +123 -0
  60. infrahub/core/schema/schema_branch_hfid.py +114 -0
  61. infrahub/core/validators/aggregated_checker.py +1 -1
  62. infrahub/core/validators/determiner.py +12 -1
  63. infrahub/core/validators/relationship/peer.py +1 -1
  64. infrahub/core/validators/tasks.py +1 -1
  65. infrahub/display_labels/__init__.py +0 -0
  66. infrahub/display_labels/gather.py +48 -0
  67. infrahub/display_labels/models.py +240 -0
  68. infrahub/display_labels/tasks.py +186 -0
  69. infrahub/display_labels/triggers.py +22 -0
  70. infrahub/events/group_action.py +1 -1
  71. infrahub/events/node_action.py +1 -1
  72. infrahub/generators/constants.py +7 -0
  73. infrahub/generators/models.py +38 -12
  74. infrahub/generators/tasks.py +34 -16
  75. infrahub/git/base.py +38 -1
  76. infrahub/git/integrator.py +22 -14
  77. infrahub/graphql/analyzer.py +1 -1
  78. infrahub/graphql/api/dependencies.py +2 -4
  79. infrahub/graphql/api/endpoints.py +2 -2
  80. infrahub/graphql/app.py +2 -4
  81. infrahub/graphql/initialization.py +2 -3
  82. infrahub/graphql/manager.py +212 -137
  83. infrahub/graphql/middleware.py +12 -0
  84. infrahub/graphql/mutations/branch.py +11 -0
  85. infrahub/graphql/mutations/computed_attribute.py +110 -3
  86. infrahub/graphql/mutations/convert_object_type.py +34 -13
  87. infrahub/graphql/mutations/display_label.py +111 -0
  88. infrahub/graphql/mutations/generator.py +25 -7
  89. infrahub/graphql/mutations/hfid.py +118 -0
  90. infrahub/graphql/mutations/ipam.py +21 -8
  91. infrahub/graphql/mutations/main.py +37 -153
  92. infrahub/graphql/mutations/profile.py +195 -0
  93. infrahub/graphql/mutations/proposed_change.py +2 -1
  94. infrahub/graphql/mutations/relationship.py +2 -2
  95. infrahub/graphql/mutations/repository.py +22 -83
  96. infrahub/graphql/mutations/resource_manager.py +2 -2
  97. infrahub/graphql/mutations/schema.py +5 -5
  98. infrahub/graphql/mutations/webhook.py +1 -1
  99. infrahub/graphql/queries/resource_manager.py +1 -1
  100. infrahub/graphql/registry.py +173 -0
  101. infrahub/graphql/resolvers/resolver.py +2 -0
  102. infrahub/graphql/schema.py +8 -1
  103. infrahub/groups/tasks.py +1 -1
  104. infrahub/hfid/__init__.py +0 -0
  105. infrahub/hfid/gather.py +48 -0
  106. infrahub/hfid/models.py +240 -0
  107. infrahub/hfid/tasks.py +185 -0
  108. infrahub/hfid/triggers.py +22 -0
  109. infrahub/lock.py +67 -30
  110. infrahub/locks/__init__.py +0 -0
  111. infrahub/locks/tasks.py +37 -0
  112. infrahub/middleware.py +26 -1
  113. infrahub/patch/plan_writer.py +2 -2
  114. infrahub/profiles/__init__.py +0 -0
  115. infrahub/profiles/node_applier.py +101 -0
  116. infrahub/profiles/queries/__init__.py +0 -0
  117. infrahub/profiles/queries/get_profile_data.py +99 -0
  118. infrahub/profiles/tasks.py +63 -0
  119. infrahub/proposed_change/tasks.py +10 -1
  120. infrahub/repositories/__init__.py +0 -0
  121. infrahub/repositories/create_repository.py +113 -0
  122. infrahub/server.py +16 -3
  123. infrahub/services/__init__.py +8 -5
  124. infrahub/tasks/registry.py +6 -4
  125. infrahub/trigger/catalogue.py +4 -0
  126. infrahub/trigger/models.py +2 -0
  127. infrahub/trigger/tasks.py +3 -0
  128. infrahub/webhook/models.py +1 -1
  129. infrahub/workflows/catalogue.py +110 -3
  130. infrahub/workflows/initialization.py +16 -0
  131. infrahub/workflows/models.py +17 -2
  132. infrahub_sdk/branch.py +5 -8
  133. infrahub_sdk/checks.py +1 -1
  134. infrahub_sdk/client.py +364 -84
  135. infrahub_sdk/convert_object_type.py +61 -0
  136. infrahub_sdk/ctl/check.py +2 -3
  137. infrahub_sdk/ctl/cli_commands.py +18 -12
  138. infrahub_sdk/ctl/config.py +8 -2
  139. infrahub_sdk/ctl/generator.py +6 -3
  140. infrahub_sdk/ctl/graphql.py +184 -0
  141. infrahub_sdk/ctl/repository.py +39 -1
  142. infrahub_sdk/ctl/schema.py +18 -3
  143. infrahub_sdk/ctl/utils.py +4 -0
  144. infrahub_sdk/ctl/validate.py +5 -3
  145. infrahub_sdk/diff.py +4 -5
  146. infrahub_sdk/exceptions.py +2 -0
  147. infrahub_sdk/generator.py +7 -1
  148. infrahub_sdk/graphql/__init__.py +12 -0
  149. infrahub_sdk/graphql/constants.py +1 -0
  150. infrahub_sdk/graphql/plugin.py +85 -0
  151. infrahub_sdk/graphql/query.py +77 -0
  152. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  153. infrahub_sdk/graphql/utils.py +40 -0
  154. infrahub_sdk/node/attribute.py +2 -0
  155. infrahub_sdk/node/node.py +28 -20
  156. infrahub_sdk/playback.py +1 -2
  157. infrahub_sdk/protocols.py +54 -6
  158. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  159. infrahub_sdk/pytest_plugin/utils.py +40 -0
  160. infrahub_sdk/repository.py +1 -2
  161. infrahub_sdk/schema/__init__.py +38 -0
  162. infrahub_sdk/schema/main.py +1 -0
  163. infrahub_sdk/schema/repository.py +8 -0
  164. infrahub_sdk/spec/object.py +120 -7
  165. infrahub_sdk/spec/range_expansion.py +118 -0
  166. infrahub_sdk/timestamp.py +18 -6
  167. infrahub_sdk/transforms.py +1 -1
  168. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
  169. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
  170. infrahub_testcontainers/container.py +1 -1
  171. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  172. infrahub_testcontainers/docker-compose.test.yml +1 -1
  173. infrahub_testcontainers/models.py +2 -2
  174. infrahub_testcontainers/performance_test.py +4 -4
  175. infrahub/core/convert_object_type/conversion.py +0 -134
  176. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
  177. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
  178. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
6
+
7
+ from infrahub_sdk.template import Jinja2Template
8
+
9
+ from infrahub.core.query.node import AttributeFromDB
10
+ from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
11
+
12
+ from ..attribute import BaseAttribute, ListAttributeOptional, StringOptional
13
+
14
+ if TYPE_CHECKING:
15
+ from infrahub.core.node import Node
16
+ from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
17
+ from infrahub.core.schema.attribute_schema import AttributeSchema
18
+ from infrahub.core.timestamp import Timestamp
19
+ from infrahub.database import InfrahubDatabase
20
+
21
+ T = TypeVar("T")
22
+
23
+
24
+ class NodePropertyAttribute(Generic[T]):
25
+ """A node property attribute is a construct that seats between a property and an attribute.
26
+
27
+ View it as a property, set at the node level but stored in the database as an attribute. It usually is something computed from other components of
28
+ a node, such as its attributes and its relationships.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ node_schema: NodeSchema | ProfileSchema | TemplateSchema,
34
+ template: T | None,
35
+ value: AttributeFromDB | T | None = None,
36
+ ) -> None:
37
+ self.node_schema = node_schema
38
+
39
+ self.node_attributes: list[str] = []
40
+ self.node_relationships: list[str] = []
41
+
42
+ self.template = template
43
+ self._value = value
44
+ self._manually_assigned = False
45
+
46
+ self.schema: AttributeSchema
47
+
48
+ self.analyze_variables()
49
+
50
+ def needs_update(self, fields: list[str] | None) -> bool:
51
+ """Tell if this node property attribute must be recomputed given a list of updated fields of a node."""
52
+ if self._manually_assigned or not fields:
53
+ return True
54
+ for field in fields:
55
+ if field in self.node_attributes or field in self.node_relationships:
56
+ return True
57
+
58
+ return False
59
+
60
+ @property
61
+ def attribute_value(self) -> AttributeFromDB | dict[str, T | None]:
62
+ if isinstance(self._value, AttributeFromDB):
63
+ return self._value
64
+ return {"value": self._value}
65
+
66
+ def set_value(self, value: T | None, manually_assigned: bool = False) -> None:
67
+ """Force the value of the node property attribute to the given one."""
68
+ if isinstance(self._value, AttributeFromDB):
69
+ self._value.value = value
70
+ else:
71
+ self._value = value
72
+
73
+ if manually_assigned:
74
+ self._manually_assigned = True
75
+
76
+ def get_value(self, node: Node, at: Timestamp) -> T | None:
77
+ if isinstance(self._value, AttributeFromDB):
78
+ attr = self.get_node_attribute(node=node, at=at)
79
+ return attr.value # type: ignore
80
+
81
+ return self._value
82
+
83
+ @abstractmethod
84
+ def analyze_variables(self) -> None: ...
85
+
86
+ @abstractmethod
87
+ async def compute(self, db: InfrahubDatabase, node: Node) -> None: ...
88
+
89
+ @abstractmethod
90
+ def get_node_attribute(self, node: Node, at: Timestamp) -> BaseAttribute: ...
91
+
92
+
93
+ class DisplayLabel(NodePropertyAttribute[str]):
94
+ def __init__(
95
+ self,
96
+ node_schema: NodeSchema | ProfileSchema | TemplateSchema,
97
+ template: str | None,
98
+ value: AttributeFromDB | str | None = None,
99
+ ) -> None:
100
+ super().__init__(node_schema=node_schema, template=template, value=value)
101
+
102
+ self.schema = node_schema.get_attribute(name="display_label")
103
+
104
+ @property
105
+ def is_jinja2_template(self) -> bool:
106
+ if self.template is None:
107
+ return False
108
+
109
+ return any(c in self.template for c in "{}")
110
+
111
+ def _analyze_plain_value(self) -> None:
112
+ if self.template is None or "__" not in self.template:
113
+ return
114
+
115
+ items = self.template.split("__", maxsplit=1)
116
+ if items[0] not in self.node_schema.attribute_names:
117
+ raise ValueError(f"{items[0]} is not an attribute of {self.node_schema.kind}")
118
+
119
+ self.node_attributes.append(items[0])
120
+
121
+ def _analyze_jinja2_value(self) -> None:
122
+ if self.template is None or not self.is_jinja2_template:
123
+ return
124
+
125
+ tpl = Jinja2Template(template=self.template)
126
+ for variable in tpl.get_variables():
127
+ items = variable.split("__", maxsplit=1)
128
+ if items[0] in self.node_schema.attribute_names:
129
+ self.node_attributes.append(items[0])
130
+ elif items[0] in self.node_schema.relationship_names:
131
+ self.node_relationships.append(items[0])
132
+ else:
133
+ raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
134
+
135
+ def analyze_variables(self) -> None:
136
+ """Look at variables used in the display label and record attributes and relationships required to compute it."""
137
+ if not self.is_jinja2_template:
138
+ self._analyze_plain_value()
139
+ else:
140
+ self._analyze_jinja2_value()
141
+
142
+ async def compute(self, db: InfrahubDatabase, node: Node) -> None:
143
+ """Update the display label value by recomputing it from the template."""
144
+ if self.template is None or self._manually_assigned:
145
+ return
146
+
147
+ if node.get_schema() != self.node_schema:
148
+ raise ValueError(
149
+ f"display_label for schema {self.node_schema.kind} cannot be rendered for node {node.get_schema().kind} {node.id}"
150
+ )
151
+
152
+ if not self.is_jinja2_template:
153
+ path_value = await node.get_path_value(db=db, path=self.template)
154
+ # Use .value for enum to keep compat with old style display label
155
+ self.set_value(value=str(path_value if not isinstance(path_value, Enum) else path_value.value))
156
+ return
157
+
158
+ jinja2_template = Jinja2Template(template=self.template)
159
+
160
+ variables: dict[str, Any] = {}
161
+ for variable in jinja2_template.get_variables():
162
+ variables[variable] = await node.get_path_value(db=db, path=variable)
163
+
164
+ self.set_value(value=await jinja2_template.render(variables=variables))
165
+
166
+ def get_node_attribute(self, node: Node, at: Timestamp) -> StringOptional:
167
+ """Return a node attribute that can be stored in the database for this display label and node."""
168
+ return StringOptional(
169
+ name="display_label",
170
+ schema=self.schema,
171
+ branch=node.get_branch(),
172
+ at=at,
173
+ node=node,
174
+ data=self.attribute_value,
175
+ )
176
+
177
+
178
+ class HumanFriendlyIdentifier(NodePropertyAttribute[list[str]]):
179
+ def __init__(
180
+ self,
181
+ node_schema: NodeSchema | ProfileSchema | TemplateSchema,
182
+ template: list[str] | None,
183
+ value: AttributeFromDB | list[str] | None = None,
184
+ ) -> None:
185
+ super().__init__(node_schema=node_schema, template=template, value=value)
186
+
187
+ self.schema = node_schema.get_attribute(name="human_friendly_id")
188
+
189
+ def _analyze_single_variable(self, value: str) -> None:
190
+ items = value.split("__", maxsplit=1)
191
+ if items[0] in self.node_schema.attribute_names:
192
+ self.node_attributes.append(items[0])
193
+ elif items[0] in self.node_schema.relationship_names:
194
+ self.node_relationships.append(items[0])
195
+ else:
196
+ raise ValueError(f"{items[0]} is neither an attribute or a relationship of {self.node_schema.kind}")
197
+
198
+ def analyze_variables(self) -> None:
199
+ """Look at variables used in the HFID and record attributes and relationships required to compute it."""
200
+ for item in self.template or []:
201
+ self._analyze_single_variable(value=item)
202
+
203
+ async def compute(self, db: InfrahubDatabase, node: Node) -> None:
204
+ """Update the HFID value by recomputing it from the template."""
205
+ if self.template is None or self._manually_assigned:
206
+ return
207
+
208
+ if node.get_schema() != self.node_schema:
209
+ raise ValueError(
210
+ f"human_friendly_id for schema {self.node_schema.kind} cannot be computed for node {node.get_schema().kind} {node.id}"
211
+ )
212
+
213
+ value: list[str] = []
214
+ for path in self.template:
215
+ path_value = await node.get_path_value(db=db, path=path)
216
+ # Use .value for enum to be consistent with display label
217
+ value.append(path_value if not isinstance(path_value, Enum) else path_value.value)
218
+
219
+ self.set_value(value=value)
220
+
221
+ def get_node_attribute(self, node: Node, at: Timestamp) -> ListAttributeOptional:
222
+ """Return a node attribute that can be stored in the database for this HFID and node."""
223
+ return ListAttributeOptional(
224
+ name="human_friendly_id",
225
+ schema=self.schema,
226
+ branch=node.get_branch(),
227
+ at=at,
228
+ node=node,
229
+ data=self.attribute_value,
230
+ )
@@ -111,7 +111,7 @@ class StandardNode(BaseModel):
111
111
  node = result.get("n")
112
112
 
113
113
  self.id = node.element_id
114
- self.uuid = node["uuid"]
114
+ self.uuid = UUID(node["uuid"])
115
115
 
116
116
  return True
117
117
 
infrahub/core/property.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from enum import Enum
3
4
  from typing import TYPE_CHECKING
4
5
  from uuid import UUID
5
6
 
@@ -26,6 +27,10 @@ class NodePropertyData(BaseModel):
26
27
  peer_id: str
27
28
 
28
29
 
30
+ class ClearValue(Enum):
31
+ CLEAR = "clear"
32
+
33
+
29
34
  class FlagPropertyMixin:
30
35
  _flag_properties: list[str] = [v.value for v in FlagProperty]
31
36
 
@@ -51,6 +56,7 @@ class NodePropertyMixin:
51
56
  for node in self._node_properties:
52
57
  setattr(self, f"_{node}", None)
53
58
  setattr(self, f"{node}_id", None)
59
+ setattr(self, f"_clear_{node}", False)
54
60
 
55
61
  if not kwargs:
56
62
  return
@@ -79,12 +85,14 @@ class NodePropertyMixin:
79
85
 
80
86
  def clear_owner(self) -> None:
81
87
  self._set_node_property(name="owner", value=None)
88
+ self._clear_owner = True
82
89
 
83
90
  async def get_source(self, db: InfrahubDatabase) -> Node | None:
84
91
  return await self._get_node_property(name="source", db=db)
85
92
 
86
93
  def clear_source(self) -> None:
87
94
  self._set_node_property(name="source", value=None)
95
+ self._clear_source = True
88
96
 
89
97
  def set_source(self, value: str | Node | UUID) -> None:
90
98
  self._set_node_property(name="source", value=value)
@@ -95,6 +103,9 @@ class NodePropertyMixin:
95
103
  def set_owner(self, value: str | Node | UUID) -> None:
96
104
  self._set_node_property(name="owner", value=value)
97
105
 
106
+ def is_clear(self, name: str) -> bool:
107
+ return getattr(self, f"_clear_{name}", False)
108
+
98
109
  def _get_node_property_from_cache(self, name: str) -> Node:
99
110
  """Return the node attribute if it's already present locally,
100
111
  Otherwise raise an exception
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- from .protocols_base import CoreNode
7
+ from infrahub.core.protocols_base import CoreNode
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from enum import Enum
@@ -125,6 +125,7 @@ class CoreGenericRepository(CoreNode):
125
125
  queries: RelationshipManager
126
126
  checks: RelationshipManager
127
127
  generators: RelationshipManager
128
+ groups_objects: RelationshipManager
128
129
 
129
130
 
130
131
  class CoreGroup(CoreNode):
@@ -349,6 +350,10 @@ class CoreGeneratorAction(CoreAction):
349
350
  generator: RelationshipManager
350
351
 
351
352
 
353
+ class CoreGeneratorAwareGroup(CoreGroup):
354
+ pass
355
+
356
+
352
357
  class CoreGeneratorCheck(CoreCheck):
353
358
  instance: String
354
359
 
@@ -360,6 +365,8 @@ class CoreGeneratorDefinition(CoreTaskTarget):
360
365
  file_path: String
361
366
  class_name: String
362
367
  convert_query_response: BooleanOptional
368
+ execute_in_proposed_change: BooleanOptional
369
+ execute_after_merge: BooleanOptional
363
370
  query: RelationshipManager
364
371
  repository: RelationshipManager
365
372
  targets: RelationshipManager
@@ -133,7 +133,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
133
133
  def __init__(
134
134
  self,
135
135
  prop_name: str,
136
- prop_id: str,
136
+ prop_id: str | None = None,
137
137
  **kwargs: Any,
138
138
  ):
139
139
  self.prop_name = prop_name
@@ -144,6 +144,8 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
144
144
  async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
145
145
  at = self.at or self.attr.at
146
146
 
147
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
148
+ self.params.update(branch_params)
147
149
  self.params["attr_uuid"] = self.attr.id
148
150
  self.params["branch"] = self.branch.name
149
151
  self.params["branch_level"] = self.branch.hierarchy_level
@@ -151,18 +153,34 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
151
153
  self.params["prop_name"] = self.prop_name
152
154
  self.params["prop_id"] = self.prop_id
153
155
 
154
- rel_name = f"HAS_{self.prop_name.upper()}"
156
+ rel_label = f"HAS_{self.prop_name.upper()}"
155
157
 
156
- query = (
158
+ if self.branch.is_default or self.branch.is_global:
159
+ node_query = """
160
+ MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
161
+ WHERE r.branch IN $branch0
162
+ AND r.status = "active"
163
+ AND r.from <= $at AND (r.to IS NULL OR r.to > $at)
164
+ WITH np
165
+ LIMIT 1
157
166
  """
167
+ else:
168
+ node_query = """
169
+ MATCH (np:Node { uuid: $prop_id })-[r:IS_PART_OF]->(:Root)
170
+ WHERE %(branch_filter)s
171
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
172
+ LIMIT 1
173
+ WITH np
174
+ WHERE r.status = "active"
175
+ """ % {"branch_filter": branch_filter}
176
+ self.add_to_query(node_query)
177
+
178
+ attr_query = """
158
179
  MATCH (a:Attribute { uuid: $attr_uuid })
159
- MATCH (np:Node { uuid: $prop_id })
160
- CREATE (a)-[r:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
161
- """
162
- % rel_name
163
- )
180
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
181
+ """ % {"rel_label": rel_label}
182
+ self.add_to_query(attr_query)
164
183
 
165
- self.add_to_query(query)
166
184
  self.return_labels = ["a", "np", "r"]
167
185
 
168
186
 
@@ -204,7 +222,6 @@ async def default_attribute_query_filter(
204
222
  param_prefix: str | None = None,
205
223
  db: InfrahubDatabase | None = None, # noqa: ARG001
206
224
  partial_match: bool = False,
207
- support_profiles: bool = False,
208
225
  ) -> tuple[list[QueryElement], dict[str, Any], list[str]]:
209
226
  """Generate Query String Snippet to filter the right node."""
210
227
  attribute_value_label = GraphAttributeValueNode.get_default_label()
@@ -251,9 +268,6 @@ async def default_attribute_query_filter(
251
268
  query_where.append(f"toString(av.{filter_name}) =~ ${param_prefix}_{filter_name}")
252
269
  elif filter_name == "isnull":
253
270
  query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
254
- elif support_profiles:
255
- query_filter.append(QueryNode(name="av", labels=[attribute_value_label]))
256
- query_where.append(f"(av.{filter_name} = ${param_prefix}_{filter_name} OR av.is_default)")
257
271
  else:
258
272
  query_filter.append(
259
273
  QueryNode(
@@ -271,8 +285,6 @@ async def default_attribute_query_filter(
271
285
  if attribute_kind and attribute_kind == "List":
272
286
  query_params[f"{param_prefix}_{filter_name}"] = build_regex_attrs(values=filter_value)
273
287
  query_where.append(f"toString(av.value) =~ ${param_prefix}_{filter_name}")
274
- elif support_profiles:
275
- query_where.append(f"(av.value IN ${param_prefix}_value OR av.is_default)")
276
288
  else:
277
289
  query_where.append(f"av.value IN ${param_prefix}_value")
278
290
  query_params[f"{param_prefix}_value"] = filter_value