infrahub-server 1.2.11__py3-none-any.whl → 1.3.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.
Files changed (211) hide show
  1. infrahub/actions/constants.py +130 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +243 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +393 -0
  6. infrahub/actions/tasks.py +119 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/branch/__init__.py +0 -0
  9. infrahub/branch/tasks.py +29 -0
  10. infrahub/branch/triggers.py +22 -0
  11. infrahub/cli/db.py +3 -4
  12. infrahub/computed_attribute/gather.py +3 -1
  13. infrahub/computed_attribute/tasks.py +23 -29
  14. infrahub/core/account.py +24 -47
  15. infrahub/core/attribute.py +13 -15
  16. infrahub/core/constants/__init__.py +10 -0
  17. infrahub/core/constants/database.py +1 -0
  18. infrahub/core/constants/infrahubkind.py +9 -0
  19. infrahub/core/constraint/node/runner.py +3 -1
  20. infrahub/core/convert_object_type/__init__.py +0 -0
  21. infrahub/core/convert_object_type/conversion.py +124 -0
  22. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  23. infrahub/core/diff/coordinator.py +8 -1
  24. infrahub/core/diff/query/all_conflicts.py +1 -5
  25. infrahub/core/diff/query/artifact.py +10 -20
  26. infrahub/core/diff/query/delete_query.py +8 -4
  27. infrahub/core/diff/query/diff_get.py +3 -6
  28. infrahub/core/diff/query/field_specifiers.py +1 -1
  29. infrahub/core/diff/query/field_summary.py +2 -4
  30. infrahub/core/diff/query/merge.py +72 -125
  31. infrahub/core/diff/query/save.py +83 -68
  32. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  33. infrahub/core/diff/query/time_range_query.py +0 -1
  34. infrahub/core/diff/repository/repository.py +4 -0
  35. infrahub/core/graph/__init__.py +1 -1
  36. infrahub/core/manager.py +14 -11
  37. infrahub/core/migrations/graph/__init__.py +6 -0
  38. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  39. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  41. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  42. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  43. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  44. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  45. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  46. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  47. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  48. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  49. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
  50. infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
  51. infrahub/core/migrations/query/attribute_add.py +14 -11
  52. infrahub/core/migrations/query/attribute_rename.py +6 -11
  53. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  54. infrahub/core/migrations/query/node_duplicate.py +19 -21
  55. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  56. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  57. infrahub/core/migrations/schema/node_remove.py +19 -20
  58. infrahub/core/models.py +29 -2
  59. infrahub/core/node/__init__.py +131 -28
  60. infrahub/core/node/base.py +1 -1
  61. infrahub/core/node/create.py +211 -0
  62. infrahub/core/node/resource_manager/number_pool.py +31 -5
  63. infrahub/core/node/standard.py +6 -1
  64. infrahub/core/path.py +15 -1
  65. infrahub/core/protocols.py +57 -0
  66. infrahub/core/protocols_base.py +3 -0
  67. infrahub/core/query/__init__.py +2 -2
  68. infrahub/core/query/delete.py +3 -3
  69. infrahub/core/query/diff.py +19 -32
  70. infrahub/core/query/ipam.py +10 -20
  71. infrahub/core/query/node.py +29 -47
  72. infrahub/core/query/relationship.py +55 -34
  73. infrahub/core/query/resource_manager.py +1 -2
  74. infrahub/core/query/standard_node.py +19 -5
  75. infrahub/core/query/subquery.py +2 -4
  76. infrahub/core/relationship/constraints/count.py +10 -9
  77. infrahub/core/relationship/constraints/interface.py +2 -1
  78. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  79. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  80. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  81. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  82. infrahub/core/relationship/model.py +4 -1
  83. infrahub/core/schema/__init__.py +2 -1
  84. infrahub/core/schema/attribute_parameters.py +160 -0
  85. infrahub/core/schema/attribute_schema.py +130 -7
  86. infrahub/core/schema/basenode_schema.py +27 -3
  87. infrahub/core/schema/definitions/core/__init__.py +29 -1
  88. infrahub/core/schema/definitions/core/group.py +45 -0
  89. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  90. infrahub/core/schema/definitions/internal.py +43 -5
  91. infrahub/core/schema/generated/attribute_schema.py +16 -3
  92. infrahub/core/schema/generated/relationship_schema.py +11 -1
  93. infrahub/core/schema/manager.py +7 -2
  94. infrahub/core/schema/schema_branch.py +109 -12
  95. infrahub/core/validators/__init__.py +15 -2
  96. infrahub/core/validators/attribute/choices.py +1 -3
  97. infrahub/core/validators/attribute/enum.py +1 -3
  98. infrahub/core/validators/attribute/kind.py +1 -3
  99. infrahub/core/validators/attribute/length.py +13 -7
  100. infrahub/core/validators/attribute/min_max.py +118 -0
  101. infrahub/core/validators/attribute/number_pool.py +106 -0
  102. infrahub/core/validators/attribute/optional.py +1 -4
  103. infrahub/core/validators/attribute/regex.py +5 -6
  104. infrahub/core/validators/attribute/unique.py +1 -3
  105. infrahub/core/validators/determiner.py +18 -2
  106. infrahub/core/validators/enum.py +12 -0
  107. infrahub/core/validators/node/hierarchy.py +3 -6
  108. infrahub/core/validators/query.py +1 -3
  109. infrahub/core/validators/relationship/count.py +6 -12
  110. infrahub/core/validators/relationship/optional.py +2 -4
  111. infrahub/core/validators/relationship/peer.py +177 -12
  112. infrahub/core/validators/tasks.py +1 -1
  113. infrahub/core/validators/uniqueness/query.py +5 -9
  114. infrahub/database/__init__.py +12 -4
  115. infrahub/database/validation.py +100 -0
  116. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  117. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  118. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  119. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  120. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  121. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  122. infrahub/dependencies/registry.py +4 -0
  123. infrahub/events/group_action.py +1 -0
  124. infrahub/events/models.py +1 -1
  125. infrahub/git/base.py +5 -3
  126. infrahub/git/integrator.py +96 -5
  127. infrahub/git/tasks.py +1 -0
  128. infrahub/graphql/analyzer.py +139 -18
  129. infrahub/graphql/manager.py +4 -0
  130. infrahub/graphql/mutations/action.py +164 -0
  131. infrahub/graphql/mutations/convert_object_type.py +71 -0
  132. infrahub/graphql/mutations/main.py +25 -176
  133. infrahub/graphql/mutations/proposed_change.py +20 -17
  134. infrahub/graphql/mutations/relationship.py +32 -0
  135. infrahub/graphql/mutations/resource_manager.py +63 -7
  136. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  137. infrahub/graphql/queries/resource_manager.py +7 -1
  138. infrahub/graphql/resolvers/many_relationship.py +1 -1
  139. infrahub/graphql/resolvers/resolver.py +2 -2
  140. infrahub/graphql/resolvers/single_relationship.py +1 -1
  141. infrahub/graphql/schema.py +6 -0
  142. infrahub/menu/menu.py +34 -2
  143. infrahub/message_bus/messages/__init__.py +0 -10
  144. infrahub/message_bus/operations/__init__.py +0 -8
  145. infrahub/message_bus/operations/refresh/registry.py +4 -7
  146. infrahub/patch/queries/delete_duplicated_edges.py +45 -39
  147. infrahub/pools/models.py +14 -0
  148. infrahub/pools/number.py +5 -3
  149. infrahub/pools/registration.py +22 -0
  150. infrahub/pools/tasks.py +126 -0
  151. infrahub/prefect_server/models.py +1 -19
  152. infrahub/proposed_change/models.py +68 -3
  153. infrahub/proposed_change/tasks.py +911 -34
  154. infrahub/schema/__init__.py +0 -0
  155. infrahub/schema/tasks.py +27 -0
  156. infrahub/schema/triggers.py +23 -0
  157. infrahub/task_manager/models.py +10 -6
  158. infrahub/trigger/catalogue.py +6 -0
  159. infrahub/trigger/models.py +23 -6
  160. infrahub/trigger/setup.py +26 -2
  161. infrahub/trigger/tasks.py +4 -2
  162. infrahub/types.py +6 -0
  163. infrahub/webhook/tasks.py +6 -9
  164. infrahub/workflows/catalogue.py +103 -1
  165. infrahub_sdk/client.py +43 -10
  166. infrahub_sdk/ctl/generator.py +4 -4
  167. infrahub_sdk/ctl/repository.py +1 -1
  168. infrahub_sdk/node/__init__.py +39 -0
  169. infrahub_sdk/node/attribute.py +122 -0
  170. infrahub_sdk/node/constants.py +21 -0
  171. infrahub_sdk/{node.py → node/node.py} +158 -803
  172. infrahub_sdk/node/parsers.py +15 -0
  173. infrahub_sdk/node/property.py +24 -0
  174. infrahub_sdk/node/related_node.py +266 -0
  175. infrahub_sdk/node/relationship.py +302 -0
  176. infrahub_sdk/protocols.py +112 -0
  177. infrahub_sdk/protocols_base.py +34 -2
  178. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  179. infrahub_sdk/query_groups.py +17 -5
  180. infrahub_sdk/schema/main.py +1 -0
  181. infrahub_sdk/schema/repository.py +16 -0
  182. infrahub_sdk/spec/object.py +1 -1
  183. infrahub_sdk/store.py +1 -1
  184. infrahub_sdk/testing/schemas/car_person.py +1 -0
  185. infrahub_sdk/utils.py +7 -20
  186. infrahub_sdk/yaml.py +6 -5
  187. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +5 -5
  188. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +197 -168
  189. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +1 -1
  190. infrahub_testcontainers/container.py +239 -65
  191. infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
  192. infrahub_testcontainers/docker-compose.test.yml +2 -1
  193. infrahub_testcontainers/helpers.py +23 -3
  194. infrahub_testcontainers/plugin.py +9 -0
  195. infrahub/message_bus/messages/check_generator_run.py +0 -26
  196. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  197. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  198. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  199. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  200. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  201. infrahub/message_bus/operations/check/__init__.py +0 -3
  202. infrahub/message_bus/operations/check/generator.py +0 -156
  203. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  204. infrahub/message_bus/operations/finalize/validator.py +0 -133
  205. infrahub/message_bus/operations/requests/__init__.py +0 -9
  206. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  207. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  208. infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -109
  209. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  210. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  211. {infrahub_server-1.2.11.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -2,22 +2,44 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from infrahub.core import registry
5
6
  from infrahub.core.query.resource_manager import NumberPoolGetReserved, NumberPoolGetUsed, NumberPoolSetReserved
7
+ from infrahub.core.schema.attribute_parameters import NumberAttributeParameters
6
8
  from infrahub.exceptions import PoolExhaustedError
7
9
 
8
10
  from .. import Node
9
11
 
10
12
  if TYPE_CHECKING:
13
+ from infrahub.core.attribute import BaseAttribute
11
14
  from infrahub.core.branch import Branch
12
15
  from infrahub.database import InfrahubDatabase
13
16
 
14
17
 
15
18
  class CoreNumberPool(Node):
19
+ def get_attribute_nb_excluded_values(self) -> int:
20
+ """
21
+ Returns the number of excluded values for the attribute of the number pool.
22
+ """
23
+
24
+ pool_node = registry.schema.get(name=self.node.value) # type: ignore [attr-defined]
25
+ attribute = [attribute for attribute in pool_node.attributes if attribute.name == self.node_attribute.value][0] # type: ignore [attr-defined]
26
+ if not isinstance(attribute.parameters, NumberAttributeParameters):
27
+ return 0
28
+
29
+ sum_excluded_values = 0
30
+ excluded_ranges = attribute.parameters.get_excluded_ranges()
31
+ for start_range, end_range in excluded_ranges:
32
+ sum_excluded_values += end_range - start_range + 1
33
+
34
+ res = len(attribute.parameters.get_excluded_single_values()) + sum_excluded_values
35
+ return res
36
+
16
37
  async def get_resource(
17
38
  self,
18
39
  db: InfrahubDatabase,
19
40
  branch: Branch,
20
41
  node: Node,
42
+ attribute: BaseAttribute,
21
43
  identifier: str | None = None,
22
44
  ) -> int:
23
45
  identifier = identifier or node.get_id()
@@ -31,23 +53,24 @@ class CoreNumberPool(Node):
31
53
  return reservation
32
54
 
33
55
  # If we have not returned a value we need to find one if avaiable
34
- number = await self.get_next(db=db, branch=branch)
56
+ number = await self.get_next(db=db, branch=branch, attribute=attribute)
35
57
 
36
58
  query_set = await NumberPoolSetReserved.init(
37
59
  db=db, pool_id=self.get_id(), identifier=identifier, reserved=number
38
60
  )
39
61
  await query_set.execute(db=db)
40
-
41
62
  return number
42
63
 
43
- async def get_next(self, db: InfrahubDatabase, branch: Branch) -> int:
64
+ async def get_next(self, db: InfrahubDatabase, branch: Branch, attribute: BaseAttribute) -> int:
44
65
  query = await NumberPoolGetUsed.init(db=db, branch=branch, pool=self, branch_agnostic=True)
45
66
  await query.execute(db=db)
46
67
  taken = [result.get_as_optional_type("av.value", return_type=int) for result in query.results]
68
+ parameters = attribute.schema.parameters
47
69
  next_number = find_next_free(
48
70
  start=self.start_range.value, # type: ignore[attr-defined]
49
71
  end=self.end_range.value, # type: ignore[attr-defined]
50
72
  taken=taken,
73
+ parameters=parameters if isinstance(parameters, NumberAttributeParameters) else None,
51
74
  )
52
75
  if next_number is None:
53
76
  raise PoolExhaustedError("There are no more values available in this pool.")
@@ -55,12 +78,15 @@ class CoreNumberPool(Node):
55
78
  return next_number
56
79
 
57
80
 
58
- def find_next_free(start: int, end: int, taken: list[int | None]) -> int | None:
81
+ def find_next_free(
82
+ start: int, end: int, taken: list[int | None], parameters: NumberAttributeParameters | None
83
+ ) -> int | None:
59
84
  used_numbers = [number for number in taken if number is not None]
60
85
  used_set = set(used_numbers)
61
86
 
62
87
  for num in range(start, end + 1):
63
88
  if num not in used_set:
64
- return num
89
+ if parameters is None or parameters.is_valid_value(num):
90
+ return num
65
91
 
66
92
  return None
@@ -210,7 +210,12 @@ class StandardNode(BaseModel):
210
210
 
211
211
  @classmethod
212
212
  async def get_list(
213
- cls, db: InfrahubDatabase, limit: int = 1000, ids: list[str] | None = None, name: str | None = None, **kwargs
213
+ cls,
214
+ db: InfrahubDatabase,
215
+ limit: int = 1000,
216
+ ids: list[str] | None = None,
217
+ name: str | None = None,
218
+ **kwargs: dict[str, Any],
214
219
  ) -> list[Self]:
215
220
  query: Query = await StandardNodeGetListQuery.init(
216
221
  db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
infrahub/core/path.py CHANGED
@@ -63,6 +63,20 @@ class DataPath(InfrahubPath):
63
63
  peer_id: str | None = Field(default=None, description="")
64
64
  value: Any | None = Field(default=None, description="Optional value of the resource")
65
65
 
66
+ def __hash__(self) -> int:
67
+ return hash(
68
+ (
69
+ self.branch,
70
+ self.path_type,
71
+ self.node_id,
72
+ self.kind,
73
+ self.field_name,
74
+ self.property_name,
75
+ self.peer_id,
76
+ str(self.value),
77
+ )
78
+ )
79
+
66
80
  @property
67
81
  def resource_type(self) -> PathResourceType:
68
82
  return PathResourceType.DATA
@@ -125,7 +139,7 @@ class SchemaPath(InfrahubPath):
125
139
  if self.field_name:
126
140
  identifier += f"/{self.field_name}"
127
141
 
128
- if self.property_name and not self.path_type == SchemaPathType.NODE:
142
+ if self.property_name and self.path_type != SchemaPathType.NODE:
129
143
  identifier += f"/{self.property_name}"
130
144
 
131
145
  return identifier
@@ -62,6 +62,12 @@ class BuiltinIPPrefix(CoreNode):
62
62
  children: RelationshipManager
63
63
 
64
64
 
65
+ class CoreAction(CoreNode):
66
+ name: String
67
+ description: StringOptional
68
+ triggers: RelationshipManager
69
+
70
+
65
71
  class CoreArtifactTarget(CoreNode):
66
72
  artifacts: RelationshipManager
67
73
 
@@ -148,6 +154,10 @@ class CoreMenu(CoreNode):
148
154
  children: RelationshipManager
149
155
 
150
156
 
157
+ class CoreNodeTriggerMatch(CoreNode):
158
+ trigger: RelationshipManager
159
+
160
+
151
161
  class CoreObjectComponentTemplate(CoreNode):
152
162
  template_name: String
153
163
 
@@ -189,6 +199,14 @@ class CoreTransformation(CoreNode):
189
199
  tags: RelationshipManager
190
200
 
191
201
 
202
+ class CoreTriggerRule(CoreNode):
203
+ name: String
204
+ description: StringOptional
205
+ active: Boolean
206
+ branch_scope: Dropdown
207
+ action: RelationshipManager
208
+
209
+
192
210
  class CoreValidator(CoreNode):
193
211
  label: StringOptional
194
212
  state: Enum
@@ -326,6 +344,10 @@ class CoreFileThread(CoreThread):
326
344
  repository: RelationshipManager
327
345
 
328
346
 
347
+ class CoreGeneratorAction(CoreAction):
348
+ generator: RelationshipManager
349
+
350
+
329
351
  class CoreGeneratorCheck(CoreCheck):
330
352
  instance: String
331
353
 
@@ -380,6 +402,16 @@ class CoreGraphQLQueryGroup(CoreGroup):
380
402
  query: RelationshipManager
381
403
 
382
404
 
405
+ class CoreGroupAction(CoreAction):
406
+ member_action: Dropdown
407
+ group: RelationshipManager
408
+
409
+
410
+ class CoreGroupTriggerRule(CoreTriggerRule):
411
+ member_update: Dropdown
412
+ group: RelationshipManager
413
+
414
+
383
415
  class CoreIPAddressPool(CoreResourcePool, LineageSource):
384
416
  default_address_type: String
385
417
  default_prefix_length: IntegerOptional
@@ -399,11 +431,31 @@ class CoreMenuItem(CoreMenu):
399
431
  pass
400
432
 
401
433
 
434
+ class CoreNodeTriggerAttributeMatch(CoreNodeTriggerMatch):
435
+ attribute_name: String
436
+ value: StringOptional
437
+ value_previous: StringOptional
438
+ value_match: Dropdown
439
+
440
+
441
+ class CoreNodeTriggerRelationshipMatch(CoreNodeTriggerMatch):
442
+ relationship_name: String
443
+ modification_type: Dropdown
444
+ peer: StringOptional
445
+
446
+
447
+ class CoreNodeTriggerRule(CoreTriggerRule):
448
+ node_kind: String
449
+ mutation_action: Enum
450
+ matches: RelationshipManager
451
+
452
+
402
453
  class CoreNumberPool(CoreResourcePool, LineageSource):
403
454
  node: String
404
455
  node_attribute: String
405
456
  start_range: Integer
406
457
  end_range: Integer
458
+ pool_type: Enum
407
459
 
408
460
 
409
461
  class CoreObjectPermission(CoreBasePermission):
@@ -446,6 +498,11 @@ class CoreRepository(LineageOwner, LineageSource, CoreGenericRepository, CoreTas
446
498
  commit: StringOptional
447
499
 
448
500
 
501
+ class CoreRepositoryGroup(CoreGroup):
502
+ content: Dropdown
503
+ repository: RelationshipManager
504
+
505
+
449
506
  class CoreRepositoryValidator(CoreValidator):
450
507
  repository: RelationshipManager
451
508
 
@@ -7,6 +7,7 @@ from typing_extensions import Self
7
7
  if TYPE_CHECKING:
8
8
  from neo4j import AsyncResult, AsyncSession, AsyncTransaction, Record
9
9
 
10
+ from infrahub.core.schema import NonGenericSchemaTypes
10
11
  from infrahub.core.schema.schema_branch import SchemaBranch
11
12
 
12
13
 
@@ -70,6 +71,8 @@ class CoreNode(Protocol):
70
71
 
71
72
  def get_id(self) -> str: ...
72
73
  def get_kind(self) -> str: ...
74
+ def get_schema(self) -> NonGenericSchemaTypes: ...
75
+
73
76
  @classmethod
74
77
  async def init(
75
78
  cls,
@@ -424,8 +424,8 @@ class Query(ABC):
424
424
  else:
425
425
  self.query_lines.extend([line.strip() for line in query.split("\n") if line.strip()])
426
426
 
427
- def add_subquery(self, subquery: str, with_clause: str | None = None) -> None:
428
- self.add_to_query("CALL {")
427
+ def add_subquery(self, subquery: str, node_alias: str, with_clause: str | None = None) -> None:
428
+ self.add_to_query(f"CALL ({node_alias}) {{")
429
429
  self.add_to_query(subquery)
430
430
  self.add_to_query("}")
431
431
  if with_clause:
@@ -21,7 +21,7 @@ class DeleteAfterTimeQuery(Query):
21
21
  // ---------------------
22
22
  // Reset edges with to time after timestamp
23
23
  // ---------------------
24
- CALL {
24
+ CALL () {
25
25
  OPTIONAL MATCH (p)-[r]-(q)
26
26
  WHERE r.to > $timestamp
27
27
  SET r.to = NULL
@@ -33,7 +33,7 @@ class DeleteAfterTimeQuery(Query):
33
33
  // ---------------------
34
34
  // Delete edges with from time after timestamp timestamp
35
35
  // ---------------------
36
- CALL {
36
+ CALL () {
37
37
  OPTIONAL MATCH (p)-[r]->(q)
38
38
  WHERE r.from > $timestamp
39
39
  DELETE r
@@ -49,7 +49,7 @@ class DeleteAfterTimeQuery(Query):
49
49
  // ---------------------
50
50
  // Delete edges with from time after timestamp timestamp
51
51
  // ---------------------
52
- CALL {
52
+ CALL () {
53
53
  OPTIONAL MATCH (p)-[r]->(q)
54
54
  WHERE r.from > $timestamp
55
55
  DELETE r
@@ -121,10 +121,9 @@ class DiffCalculationQuery(DiffQuery):
121
121
 
122
122
  previous_base_path_query = """
123
123
  WITH DISTINCT diff_path AS diff_path, has_more_data
124
- CALL {
125
- WITH diff_path
126
- WITH diff_path, nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
127
- WITH diff_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
124
+ CALL (diff_path) {
125
+ WITH nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
126
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
128
127
  // -------------------------------------
129
128
  // add base branch paths before branched_from, if they exist
130
129
  // -------------------------------------
@@ -158,10 +157,9 @@ CALL {
158
157
  WITH diff_path, latest_base_path, has_more_data
159
158
  UNWIND [diff_path, latest_base_path] AS penultimate_path
160
159
  WITH DISTINCT penultimate_path, has_more_data
161
- CALL {
162
- WITH penultimate_path
163
- WITH penultimate_path, nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
164
- WITH penultimate_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
160
+ CALL (penultimate_path) {
161
+ WITH nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
162
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
165
163
  // -------------------------------------
166
164
  // Add peer-side of any relationships to get the peer's ID
167
165
  // -------------------------------------
@@ -299,8 +297,7 @@ WITH one_result[0] AS p, one_result[1] AS q, one_result[2] AS diff_rel, one_resu
299
297
  // -------------------------------------
300
298
  // Exclude nodes added then removed on branch within timeframe
301
299
  // -------------------------------------
302
- CALL {
303
- WITH p, q, row_from_time
300
+ CALL (p, q, row_from_time) {
304
301
  OPTIONAL MATCH (q)<-[is_part_of:IS_PART_OF {branch: $branch_name}]-(p)
305
302
  WHERE row_from_time <= is_part_of.from < $to_time
306
303
  WITH DISTINCT is_part_of.status AS rel_status
@@ -312,8 +309,7 @@ WHERE intra_branch_update = FALSE
312
309
  // -------------------------------------
313
310
  // Get every path on this branch under each node
314
311
  // -------------------------------------
315
- CALL {
316
- WITH p, q, diff_rel, row_from_time
312
+ CALL (p, q, diff_rel, row_from_time) {
317
313
  OPTIONAL MATCH path = (
318
314
  (q)<-[top_diff_rel:IS_PART_OF]-(p)-[r_node]-(node)-[r_prop]-(prop)
319
315
  )
@@ -340,12 +336,11 @@ CALL {
340
336
  p.uuid IS NULL OR prop.uuid IS NULL OR p.uuid <> prop.uuid
341
337
  OR type(r_node) <> "IS_RELATED" OR type(r_prop) <> "IS_RELATED"
342
338
  )
343
- WITH path, p, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
339
+ WITH path, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
344
340
  // -------------------------------------
345
341
  // Exclude attributes/relationships added then removed on branch within timeframe
346
342
  // -------------------------------------
347
- CALL {
348
- WITH p, rel_type, node, row_from_time
343
+ CALL (p, rel_type, node, row_from_time) {
349
344
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(node)
350
345
  WHERE row_from_time <= rel_to_check.from < $to_time
351
346
  AND type(rel_to_check) = rel_type
@@ -484,8 +479,7 @@ END AS row_from_time
484
479
  // Exclude attributes/relationship under nodes deleted on this branch in the timeframe
485
480
  // because those were all handled above at the node level
486
481
  // -------------------------------------
487
- CALL {
488
- WITH root, p, row_from_time
482
+ CALL (root, p, row_from_time) {
489
483
  OPTIONAL MATCH (root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(p)
490
484
  WHERE row_from_time <= r_root_deleted.from < $to_time
491
485
  WITH r_root_deleted
@@ -499,8 +493,7 @@ WHERE node_deleted = FALSE
499
493
  // Exclude relationships added and deleted within the timeframe
500
494
  // -------------------------------------
501
495
  WITH root, r_root, p, diff_rel, q, has_more_data, row_from_time, type(diff_rel) AS rel_type
502
- CALL {
503
- WITH p, rel_type, q, row_from_time
496
+ CALL (p, rel_type, q, row_from_time) {
504
497
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(q)
505
498
  WHERE row_from_time <= rel_to_check.from < $to_time
506
499
  AND type(rel_to_check) = rel_type
@@ -513,8 +506,7 @@ WHERE intra_branch_update = FALSE
513
506
  // -------------------------------------
514
507
  // Get every path on this branch under each attribute/relationship
515
508
  // -------------------------------------
516
- CALL {
517
- WITH root, r_root, p, diff_rel, q
509
+ CALL (root, r_root, p, diff_rel, q) {
518
510
  OPTIONAL MATCH path = (
519
511
  (root:Root)<-[mid_r_root:IS_PART_OF]-(p)-[mid_diff_rel]-(q)-[r_prop]-(prop)
520
512
  )
@@ -549,8 +541,7 @@ CALL {
549
541
  // Exclude properties added and deleted within the timeframe
550
542
  // -------------------------------------
551
543
  WITH q, nodes(latest_prop_path)[3] AS prop, type(relationships(latest_prop_path)[2]) AS rel_type, latest_prop_path, has_more_data, row_from_time
552
- CALL {
553
- WITH q, rel_type, prop, row_from_time
544
+ CALL (q, rel_type, prop, row_from_time) {
554
545
  OPTIONAL MATCH (q)-[rel_to_check {branch: $branch_name}]-(prop)
555
546
  WHERE row_from_time <= rel_to_check.from < $to_time
556
547
  AND type(rel_to_check) = rel_type
@@ -717,14 +708,12 @@ ORDER BY
717
708
  r_node.from DESC,
718
709
  r_root.from DESC
719
710
  WITH n, p, row_from_time, diff_rel, diff_rel_path, has_more_data
720
- CALL {
711
+ CALL (n, p, row_from_time){
721
712
  // -------------------------------------
722
713
  // Exclude properties under nodes and attributes/relationships deleted
723
714
  // on this branch in the timeframe because those were all handled above
724
715
  // -------------------------------------
725
- WITH n, p, row_from_time
726
- CALL {
727
- WITH n, row_from_time
716
+ CALL (n, row_from_time) {
728
717
  OPTIONAL MATCH (root:Root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(n)
729
718
  WHERE r_root_deleted.from < $to_time
730
719
  WITH r_root_deleted
@@ -732,9 +721,8 @@ CALL {
732
721
  LIMIT 1
733
722
  RETURN COALESCE(r_root_deleted.status = "deleted", FALSE) AS node_deleted
734
723
  }
735
- WITH n, p, row_from_time, node_deleted
736
- CALL {
737
- WITH n, p, row_from_time
724
+ WITH node_deleted
725
+ CALL (n, p, row_from_time) {
738
726
  OPTIONAL MATCH (n)-[r_node_deleted {branch: $branch_name}]-(p)
739
727
  WHERE row_from_time <= r_node_deleted.from < $to_time
740
728
  AND type(r_node_deleted) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
@@ -803,8 +791,7 @@ AND (
803
791
  // -------------------------------------
804
792
  // Ignore node created and deleted on this branch
805
793
  // -------------------------------------
806
- CALL {
807
- WITH n
794
+ CALL (n) {
808
795
  OPTIONAL MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n)
809
796
  WITH diff_rel
810
797
  ORDER BY diff_rel.from ASC
@@ -80,8 +80,7 @@ class IPPrefixSubnetFetch(Query):
80
80
  // First match on IPNAMESPACE
81
81
  MATCH (ns:%(ns_label)s)
82
82
  WHERE ns.uuid = $ns_id
83
- CALL {
84
- WITH ns
83
+ CALL (ns) {
85
84
  MATCH (ns)-[r:IS_PART_OF]-(root:Root)
86
85
  WHERE %(branch_filter)s
87
86
  RETURN ns as ns1, r as r1
@@ -104,8 +103,7 @@ class IPPrefixSubnetFetch(Query):
104
103
  // ---
105
104
  // FIND ALL CHILDREN OF THESE PREFIXES
106
105
  // ---
107
- CALL {
108
- WITH all_prefixes
106
+ CALL (all_prefixes) {
109
107
  UNWIND all_prefixes as prefix
110
108
  OPTIONAL MATCH (prefix)<-[:IS_RELATED]-(ch_rel:Relationship)<-[:IS_RELATED]-(children:BuiltinIPPrefix)
111
109
  WHERE ch_rel.name = "parent__child"
@@ -171,8 +169,7 @@ class IPPrefixIPAddressFetch(Query):
171
169
  // First match on IPNAMESPACE
172
170
  MATCH (ns:%(ns_label)s)
173
171
  WHERE ns.uuid = $ns_id
174
- CALL {
175
- WITH ns
172
+ CALL (ns) {
176
173
  MATCH (ns)-[r:IS_PART_OF]-(root:Root)
177
174
  WHERE %(branch_filter)s
178
175
  RETURN ns as ns1, r as r1
@@ -271,8 +268,7 @@ class IPPrefixUtilization(Query):
271
268
  query = f"""
272
269
  MATCH (pfx:Node)
273
270
  WHERE pfx.uuid IN $ids
274
- CALL {{
275
- WITH pfx
271
+ CALL (pfx) {{
276
272
  MATCH (pfx)-[r_rel1:IS_RELATED]-(rl:Relationship)<-[r_rel2:IS_RELATED]-(child:Node)
277
273
  WHERE rl.name IN [{", ".join(self.allocated_kinds_rel)}]
278
274
  AND any(l IN labels(child) WHERE l IN [{", ".join(self.allocated_kinds)}])
@@ -425,8 +421,7 @@ class IPPrefixReconcileQuery(Query):
425
421
  // ------------------
426
422
  // Get prefix node's current parent, if it exists
427
423
  // ------------------
428
- CALL {
429
- WITH ip_node
424
+ CALL (ip_node) {
430
425
  OPTIONAL MATCH parent_prefix_path = (ip_node)-[r1:IS_RELATED]->(:Relationship {name: "parent__child"})-[r2:IS_RELATED]->(current_parent:%(ip_prefix_kind)s)
431
426
  WHERE all(r IN relationships(parent_prefix_path) WHERE (%(branch_filter)s))
432
427
  RETURN current_parent, (r1.status = "active" AND r2.status = "active") AS parent_is_active
@@ -444,8 +439,7 @@ class IPPrefixReconcileQuery(Query):
444
439
  // ------------------
445
440
  // Get prefix node's current prefix children, if any exist
446
441
  // ------------------
447
- CALL {
448
- WITH ip_node
442
+ CALL (ip_node) {
449
443
  OPTIONAL MATCH child_prefix_path = (ip_node)<-[r1:IS_RELATED]-(:Relationship {name: "parent__child"})<-[r2:IS_RELATED]-(current_prefix_child:%(ip_prefix_kind)s)
450
444
  WHERE all(r IN relationships(child_prefix_path) WHERE (%(branch_filter)s))
451
445
  WITH current_prefix_child, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -457,8 +451,7 @@ class IPPrefixReconcileQuery(Query):
457
451
  // ------------------
458
452
  // Get prefix node's current address children, if any exist
459
453
  // ------------------
460
- CALL {
461
- WITH ip_node
454
+ CALL (ip_node) {
462
455
  OPTIONAL MATCH child_address_path = (ip_node)-[r1:IS_RELATED]-(:Relationship {name: "ip_prefix__ip_address"})-[r2:IS_RELATED]-(current_address_child:%(ip_address_kind)s)
463
456
  WHERE all(r IN relationships(child_address_path) WHERE (%(branch_filter)s))
464
457
  WITH current_address_child, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -480,8 +473,7 @@ class IPPrefixReconcileQuery(Query):
480
473
  // ------------------
481
474
  // Identify the correct parent, if any, for the prefix node
482
475
  // ------------------
483
- CALL {
484
- WITH ip_namespace
476
+ CALL (ip_namespace) {
485
477
  OPTIONAL MATCH parent_path = (ip_namespace)-[pr1:IS_RELATED {status: "active"}]-(ns_rel:Relationship {name: "ip_namespace__ip_prefix"})
486
478
  -[pr2:IS_RELATED {status: "active"}]-(maybe_new_parent:%(ip_prefix_kind)s)
487
479
  -[har:HAS_ATTRIBUTE]->(:Attribute {name: "prefix"})
@@ -517,9 +509,8 @@ class IPPrefixReconcileQuery(Query):
517
509
  // ------------------
518
510
  // Identify the correct children, if any, for the prefix node
519
511
  // ------------------
520
- CALL {
512
+ CALL (ip_namespace, ip_node) {
521
513
  // Get ALL possible children for the prefix node
522
- WITH ip_namespace, ip_node
523
514
  OPTIONAL MATCH child_path = (
524
515
  (ip_namespace)-[r1:IS_RELATED]
525
516
  -(ns_rel:Relationship)-[r2:IS_RELATED]
@@ -558,12 +549,11 @@ class IPPrefixReconcileQuery(Query):
558
549
  WITH ip_namespace, ip_node, current_parent, current_children, new_parent, collect([maybe_new_child, latest_mnc_attribute]) AS maybe_children_ips
559
550
  WITH ip_namespace, ip_node, current_parent, current_children, new_parent, maybe_children_ips, range(0, size(maybe_children_ips) - 1) AS child_indices
560
551
  UNWIND child_indices as ind
561
- CALL {
552
+ CALL (ind, maybe_children_ips) {
562
553
  // ------------------
563
554
  // Filter all possible children to remove those that have a more-specific parent
564
555
  // among the list of all possible children
565
556
  // ------------------
566
- WITH ind, maybe_children_ips
567
557
  WITH ind, maybe_children_ips AS ips
568
558
  RETURN REDUCE(
569
559
  has_more_specific_parent = FALSE, potential_parent IN ips |