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.
Files changed (250) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/exceptions.py +2 -2
  3. infrahub/api/schema.py +3 -1
  4. infrahub/artifacts/tasks.py +1 -0
  5. infrahub/auth.py +2 -2
  6. infrahub/cli/db.py +54 -28
  7. infrahub/computed_attribute/gather.py +3 -4
  8. infrahub/computed_attribute/tasks.py +23 -6
  9. infrahub/config.py +8 -0
  10. infrahub/constants/enums.py +12 -0
  11. infrahub/core/account.py +4 -4
  12. infrahub/core/attribute.py +106 -108
  13. infrahub/core/branch/models.py +44 -71
  14. infrahub/core/branch/tasks.py +5 -3
  15. infrahub/core/changelog/diff.py +1 -20
  16. infrahub/core/changelog/models.py +0 -7
  17. infrahub/core/constants/__init__.py +17 -0
  18. infrahub/core/constants/database.py +0 -1
  19. infrahub/core/constants/schema.py +0 -1
  20. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  21. infrahub/core/diff/branch_differ.py +1 -1
  22. infrahub/core/diff/conflict_transferer.py +1 -1
  23. infrahub/core/diff/data_check_synchronizer.py +4 -3
  24. infrahub/core/diff/enricher/cardinality_one.py +2 -2
  25. infrahub/core/diff/enricher/hierarchy.py +1 -1
  26. infrahub/core/diff/enricher/labels.py +1 -1
  27. infrahub/core/diff/merger/merger.py +28 -2
  28. infrahub/core/diff/merger/serializer.py +3 -10
  29. infrahub/core/diff/model/diff.py +1 -1
  30. infrahub/core/diff/query/merge.py +376 -135
  31. infrahub/core/diff/repository/repository.py +3 -1
  32. infrahub/core/graph/__init__.py +1 -1
  33. infrahub/core/graph/constraints.py +3 -3
  34. infrahub/core/graph/schema.py +2 -12
  35. infrahub/core/ipam/reconciler.py +8 -6
  36. infrahub/core/ipam/utilization.py +8 -15
  37. infrahub/core/manager.py +133 -152
  38. infrahub/core/merge.py +1 -1
  39. infrahub/core/metadata/__init__.py +0 -0
  40. infrahub/core/metadata/interface.py +37 -0
  41. infrahub/core/metadata/model.py +31 -0
  42. infrahub/core/metadata/query/__init__.py +0 -0
  43. infrahub/core/metadata/query/node_metadata.py +301 -0
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
  47. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  48. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  49. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  50. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  51. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  52. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  53. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  54. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  55. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  56. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
  57. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
  58. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  59. infrahub/core/migrations/query/__init__.py +2 -2
  60. infrahub/core/migrations/query/attribute_add.py +17 -6
  61. infrahub/core/migrations/query/attribute_remove.py +19 -5
  62. infrahub/core/migrations/query/attribute_rename.py +21 -5
  63. infrahub/core/migrations/query/node_duplicate.py +19 -4
  64. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  65. infrahub/core/migrations/schema/attribute_kind_update.py +21 -2
  66. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  67. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
  68. infrahub/core/migrations/schema/models.py +3 -0
  69. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  70. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  71. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  72. infrahub/core/migrations/schema/node_remove.py +24 -2
  73. infrahub/core/migrations/schema/tasks.py +4 -1
  74. infrahub/core/migrations/shared.py +13 -6
  75. infrahub/core/models.py +6 -6
  76. infrahub/core/node/__init__.py +157 -58
  77. infrahub/core/node/base.py +9 -5
  78. infrahub/core/node/create.py +7 -3
  79. infrahub/core/node/delete_validator.py +1 -1
  80. infrahub/core/node/standard.py +100 -14
  81. infrahub/core/order.py +30 -0
  82. infrahub/core/property.py +0 -1
  83. infrahub/core/protocols.py +1 -0
  84. infrahub/core/protocols_base.py +10 -2
  85. infrahub/core/query/__init__.py +5 -3
  86. infrahub/core/query/attribute.py +164 -49
  87. infrahub/core/query/branch.py +58 -70
  88. infrahub/core/query/delete.py +1 -1
  89. infrahub/core/query/diff.py +7 -7
  90. infrahub/core/query/ipam.py +104 -43
  91. infrahub/core/query/node.py +1072 -281
  92. infrahub/core/query/relationship.py +531 -325
  93. infrahub/core/query/resource_manager.py +107 -18
  94. infrahub/core/query/standard_node.py +25 -5
  95. infrahub/core/query/utils.py +2 -4
  96. infrahub/core/relationship/constraints/count.py +1 -1
  97. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  98. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  99. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  100. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  101. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  102. infrahub/core/relationship/model.py +293 -139
  103. infrahub/core/schema/attribute_schema.py +2 -2
  104. infrahub/core/schema/basenode_schema.py +3 -0
  105. infrahub/core/schema/definitions/core/__init__.py +8 -2
  106. infrahub/core/schema/definitions/core/account.py +10 -10
  107. infrahub/core/schema/definitions/core/artifact.py +14 -8
  108. infrahub/core/schema/definitions/core/check.py +10 -4
  109. infrahub/core/schema/definitions/core/generator.py +26 -6
  110. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  111. infrahub/core/schema/definitions/core/group.py +9 -2
  112. infrahub/core/schema/definitions/core/ipam.py +80 -10
  113. infrahub/core/schema/definitions/core/menu.py +41 -7
  114. infrahub/core/schema/definitions/core/permission.py +16 -2
  115. infrahub/core/schema/definitions/core/profile.py +16 -2
  116. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  117. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  118. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  119. infrahub/core/schema/definitions/core/repository.py +10 -0
  120. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  121. infrahub/core/schema/definitions/core/template.py +19 -2
  122. infrahub/core/schema/definitions/core/transform.py +11 -5
  123. infrahub/core/schema/definitions/core/webhook.py +27 -9
  124. infrahub/core/schema/manager.py +63 -43
  125. infrahub/core/schema/relationship_schema.py +6 -2
  126. infrahub/core/schema/schema_branch.py +48 -10
  127. infrahub/core/task/task.py +4 -2
  128. infrahub/core/utils.py +3 -25
  129. infrahub/core/validators/aggregated_checker.py +1 -1
  130. infrahub/core/validators/attribute/choices.py +1 -1
  131. infrahub/core/validators/attribute/enum.py +1 -1
  132. infrahub/core/validators/attribute/kind.py +1 -1
  133. infrahub/core/validators/attribute/length.py +1 -1
  134. infrahub/core/validators/attribute/min_max.py +1 -1
  135. infrahub/core/validators/attribute/number_pool.py +1 -1
  136. infrahub/core/validators/attribute/optional.py +1 -1
  137. infrahub/core/validators/attribute/regex.py +1 -1
  138. infrahub/core/validators/determiner.py +3 -3
  139. infrahub/core/validators/node/attribute.py +1 -1
  140. infrahub/core/validators/node/relationship.py +1 -1
  141. infrahub/core/validators/relationship/peer.py +1 -1
  142. infrahub/database/__init__.py +4 -4
  143. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  144. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  145. infrahub/dependencies/registry.py +2 -0
  146. infrahub/display_labels/tasks.py +12 -3
  147. infrahub/git/integrator.py +18 -18
  148. infrahub/git/tasks.py +1 -1
  149. infrahub/git/utils.py +1 -1
  150. infrahub/graphql/constants.py +3 -0
  151. infrahub/graphql/context.py +1 -1
  152. infrahub/graphql/field_extractor.py +1 -1
  153. infrahub/graphql/initialization.py +11 -0
  154. infrahub/graphql/loaders/account.py +134 -0
  155. infrahub/graphql/loaders/node.py +5 -12
  156. infrahub/graphql/loaders/peers.py +5 -7
  157. infrahub/graphql/manager.py +175 -21
  158. infrahub/graphql/metadata.py +91 -0
  159. infrahub/graphql/mutations/account.py +6 -6
  160. infrahub/graphql/mutations/attribute.py +0 -2
  161. infrahub/graphql/mutations/branch.py +9 -5
  162. infrahub/graphql/mutations/computed_attribute.py +1 -1
  163. infrahub/graphql/mutations/display_label.py +1 -1
  164. infrahub/graphql/mutations/hfid.py +1 -1
  165. infrahub/graphql/mutations/ipam.py +4 -6
  166. infrahub/graphql/mutations/main.py +9 -4
  167. infrahub/graphql/mutations/profile.py +16 -22
  168. infrahub/graphql/mutations/proposed_change.py +4 -4
  169. infrahub/graphql/mutations/relationship.py +40 -10
  170. infrahub/graphql/mutations/repository.py +14 -12
  171. infrahub/graphql/mutations/schema.py +2 -2
  172. infrahub/graphql/order.py +14 -0
  173. infrahub/graphql/queries/branch.py +62 -6
  174. infrahub/graphql/queries/resource_manager.py +25 -24
  175. infrahub/graphql/resolvers/account_metadata.py +84 -0
  176. infrahub/graphql/resolvers/ipam.py +6 -8
  177. infrahub/graphql/resolvers/many_relationship.py +77 -35
  178. infrahub/graphql/resolvers/resolver.py +59 -14
  179. infrahub/graphql/resolvers/single_relationship.py +87 -23
  180. infrahub/graphql/subscription/graphql_query.py +2 -0
  181. infrahub/graphql/types/__init__.py +0 -1
  182. infrahub/graphql/types/attribute.py +10 -5
  183. infrahub/graphql/types/branch.py +40 -53
  184. infrahub/graphql/types/enums.py +3 -0
  185. infrahub/graphql/types/metadata.py +28 -0
  186. infrahub/graphql/types/node.py +22 -2
  187. infrahub/graphql/types/relationship.py +10 -2
  188. infrahub/graphql/types/standard_node.py +12 -7
  189. infrahub/hfid/tasks.py +12 -3
  190. infrahub/lock.py +7 -0
  191. infrahub/menu/repository.py +1 -1
  192. infrahub/patch/queries/base.py +1 -1
  193. infrahub/pools/number.py +1 -8
  194. infrahub/profiles/gather.py +56 -0
  195. infrahub/profiles/mandatory_fields_checker.py +116 -0
  196. infrahub/profiles/models.py +66 -0
  197. infrahub/profiles/node_applier.py +154 -13
  198. infrahub/profiles/queries/get_profile_data.py +143 -31
  199. infrahub/profiles/tasks.py +79 -27
  200. infrahub/profiles/triggers.py +22 -0
  201. infrahub/proposed_change/action_checker.py +1 -1
  202. infrahub/proposed_change/tasks.py +4 -1
  203. infrahub/services/__init__.py +1 -1
  204. infrahub/services/adapters/cache/nats.py +1 -1
  205. infrahub/services/adapters/cache/redis.py +7 -0
  206. infrahub/tasks/artifact.py +1 -0
  207. infrahub/transformations/tasks.py +2 -2
  208. infrahub/trigger/catalogue.py +2 -0
  209. infrahub/trigger/models.py +1 -0
  210. infrahub/trigger/setup.py +3 -3
  211. infrahub/trigger/tasks.py +3 -0
  212. infrahub/validators/tasks.py +1 -0
  213. infrahub/webhook/gather.py +1 -1
  214. infrahub/webhook/models.py +1 -1
  215. infrahub/webhook/tasks.py +23 -7
  216. infrahub/workers/dependencies.py +9 -3
  217. infrahub/workers/infrahub_async.py +13 -4
  218. infrahub/workflows/catalogue.py +19 -0
  219. infrahub_sdk/analyzer.py +2 -2
  220. infrahub_sdk/branch.py +12 -39
  221. infrahub_sdk/checks.py +4 -4
  222. infrahub_sdk/client.py +36 -0
  223. infrahub_sdk/ctl/cli_commands.py +2 -1
  224. infrahub_sdk/ctl/graphql.py +15 -4
  225. infrahub_sdk/ctl/utils.py +2 -2
  226. infrahub_sdk/enums.py +6 -0
  227. infrahub_sdk/graphql/renderers.py +21 -0
  228. infrahub_sdk/graphql/utils.py +85 -0
  229. infrahub_sdk/node/attribute.py +12 -2
  230. infrahub_sdk/node/constants.py +12 -0
  231. infrahub_sdk/node/metadata.py +69 -0
  232. infrahub_sdk/node/node.py +65 -14
  233. infrahub_sdk/node/property.py +3 -0
  234. infrahub_sdk/node/related_node.py +37 -5
  235. infrahub_sdk/node/relationship.py +18 -1
  236. infrahub_sdk/operation.py +2 -2
  237. infrahub_sdk/schema/repository.py +1 -2
  238. infrahub_sdk/transforms.py +2 -2
  239. infrahub_sdk/types.py +18 -2
  240. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
  241. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +249 -228
  242. infrahub_testcontainers/container.py +3 -3
  243. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  244. infrahub_testcontainers/docker-compose.test.yml +13 -5
  245. infrahub_testcontainers/models.py +3 -3
  246. infrahub_testcontainers/performance_test.py +1 -1
  247. infrahub/graphql/models.py +0 -6
  248. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
  249. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
  250. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from infrahub import config
6
5
  from infrahub.core.branch.enums import BranchStatus
7
6
  from infrahub.core.constants import GLOBAL_BRANCH_NAME
8
7
  from infrahub.core.query import Query, QueryType
@@ -18,7 +17,7 @@ class DeleteBranchRelationshipsQuery(Query):
18
17
 
19
18
  type: QueryType = QueryType.WRITE
20
19
 
21
- def __init__(self, branch_name: str, **kwargs: Any):
20
+ def __init__(self, branch_name: str, **kwargs: Any) -> None:
22
21
  self.branch_name = branch_name
23
22
  super().__init__(**kwargs)
24
23
 
@@ -70,89 +69,78 @@ CALL (vertex_id) {
70
69
  self.add_to_query(query)
71
70
 
72
71
 
73
- class GetAllBranchInternalRelationshipQuery(Query):
74
- name: str = "get_internal_relationship"
72
+ class RebaseBranchQuery(Query):
73
+ """Rebase a branch onto the default branch by updating edge timestamps
75
74
 
76
- type: QueryType = QueryType.READ
77
- insert_return: bool = False
78
-
79
- async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
80
- query = """
81
- MATCH p = ()-[r]-()
82
- WHERE r.branch = $branch_name
83
- RETURN DISTINCT r
84
- """
85
- self.add_to_query(query=query)
86
- self.params["branch_name"] = self.branch.name
87
- self.return_labels = ["r"]
88
-
89
-
90
- class RebaseBranchUpdateRelationshipQuery(Query):
91
- name: str = "rebase_branch_update"
75
+ For every edge on this branch
76
+ if it has a from time before $at and no to time, update it to $at
77
+ if it has a to time before $at, delete the edge
78
+ if it has a to time after $at, update the from time to $at
79
+ Then delete any orphaned vertices
80
+ """
92
81
 
82
+ name: str = "rebase_branch"
93
83
  type: QueryType = QueryType.WRITE
94
-
95
- def __init__(self, ids: list[str], **kwargs: Any) -> None:
96
- self.ids = ids
97
- super().__init__(**kwargs)
84
+ insert_return: bool = False
85
+ raise_error_if_empty: bool = False
98
86
 
99
87
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
100
- query = """
101
- MATCH ()-[r]->()
102
- WHERE %(id_func)s(r) IN $ids
103
- SET r.from = $at
104
- SET r.conflict = NULL
105
- """ % {
106
- "id_func": db.get_id_function_name(),
107
- }
108
-
109
- self.add_to_query(query=query)
110
-
88
+ self.params["branch_name"] = self.branch.name
111
89
  self.params["at"] = self.at.to_string()
112
- self.params["ids"] = [db.to_database_id(id) for id in self.ids]
113
- self.return_labels = [f"{db.get_id_function_name()}(r)"]
114
-
115
-
116
- class RebaseBranchDeleteRelationshipQuery(Query):
117
- name: str = "rebase_branch_delete"
118
90
 
119
- type: QueryType = QueryType.WRITE
120
- insert_return: bool = False
121
-
122
- def __init__(self, ids: list[str], **kwargs: Any) -> None:
123
- self.ids = ids
124
- super().__init__(**kwargs)
91
+ query = """
92
+ // --------------
93
+ // Get all edges on this branch with their source and destination vertices
94
+ // --------------
95
+ MATCH (s)-[r]-(d)
96
+ WHERE r.branch = $branch_name
97
+ WITH DISTINCT r, s, d
98
+ WITH r, s, d,
99
+ CASE
100
+ // No `to` and `from` <= at: update
101
+ WHEN r.to IS NULL AND r.from <= $at THEN TRUE
102
+ // Has `to` and `to` < at: delete
103
+ WHEN r.to IS NOT NULL AND r.to < $at THEN FALSE
104
+ // Has `to` and `to` >= at: update
105
+ ELSE TRUE
106
+ END AS do_update
125
107
 
126
- async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
127
- if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
128
- query = """
129
- MATCH p = (s)-[r]-(d)
130
- WHERE %(id_func)s(r) IN $ids
131
- DELETE r
132
- """
133
- else:
134
- query = """
135
- MATCH p = (s)-[r]-(d)
136
- WHERE %(id_func)s(r) IN $ids
137
- DELETE r
138
- WITH *
139
- UNWIND nodes(p) AS n
140
- MATCH (n)
141
- WHERE NOT exists((n)--())
142
- DELETE n
143
- """
144
- query %= {
145
- "id_func": db.get_id_function_name(),
146
- }
108
+ // --------------
109
+ // Process updates: set from = at for relationships we're keeping
110
+ // --------------
111
+ CALL (r, do_update) {
112
+ WITH r, do_update
113
+ WHERE do_update = TRUE
114
+ SET r.from = $at
115
+ }
147
116
 
117
+ // --------------
118
+ // Delete the edges
119
+ // --------------
120
+ WITH r, s, d, do_update
121
+ WHERE do_update = FALSE
122
+ CALL (r, s, d) {
123
+ DELETE r
124
+ }
125
+ // --------------
126
+ // Clean up any orpahned nodes edges
127
+ // --------------
128
+ WITH DISTINCT s, d
129
+ UNWIND [s, d] AS n
130
+ WITH DISTINCT n
131
+ CALL (n) {
132
+ MATCH (n)
133
+ WHERE NOT exists((n)--())
134
+ DELETE n
135
+ }
136
+ """
148
137
  self.add_to_query(query=query)
149
138
 
150
- self.params["ids"] = [db.to_database_id(id) for id in self.ids]
151
-
152
139
 
153
140
  class BranchNodeGetListQuery(StandardNodeGetListQuery):
154
141
  def __init__(self, exclude_global: bool = False, **kwargs: Any) -> None:
155
142
  self.raw_filter = f"n.status <> '{BranchStatus.DELETING.value}'"
143
+
156
144
  if exclude_global:
157
145
  self.raw_filter += f" AND n.name <> '{GLOBAL_BRANCH_NAME}'"
158
146
 
@@ -11,7 +11,7 @@ class DeleteAfterTimeQuery(Query):
11
11
  insert_return: bool = False
12
12
  type: QueryType = QueryType.WRITE
13
13
 
14
- def __init__(self, timestamp: Timestamp, **kwargs: Any):
14
+ def __init__(self, timestamp: Timestamp, **kwargs: Any) -> None:
15
15
  self.timestamp = timestamp
16
16
  super().__init__(**kwargs)
17
17
 
@@ -26,7 +26,7 @@ class DiffQuery(Query):
26
26
  diff_from: Timestamp | str = None,
27
27
  diff_to: Timestamp | str = None,
28
28
  **kwargs,
29
- ):
29
+ ) -> None:
30
30
  """A diff is always in the context of a branch"""
31
31
 
32
32
  if not diff_from and branch.is_default:
@@ -59,7 +59,7 @@ class DiffCountChanges(Query):
59
59
  diff_from: Timestamp,
60
60
  diff_to: Timestamp,
61
61
  **kwargs,
62
- ):
62
+ ) -> None:
63
63
  self.branch_names = branch_names
64
64
  self.diff_from = diff_from
65
65
  self.diff_to = diff_to
@@ -77,7 +77,7 @@ class DiffCountChanges(Query):
77
77
  AND diff_rel.branch in $branch_names
78
78
  AND (
79
79
  (diff_rel.from >= $from_time AND diff_rel.from < $to_time)
80
- OR (diff_rel.to >= $to_time AND diff_rel.to < $to_time)
80
+ OR (diff_rel.to >= $from_time AND diff_rel.to < $to_time)
81
81
  )
82
82
  AND (p.branch_support = "aware" OR q.branch_support = "aware")
83
83
  WITH diff_rel.branch AS branch_name, count(*) AS num_changes
@@ -122,7 +122,7 @@ class DiffCalculationQuery(DiffQuery):
122
122
  current_node_field_specifiers: NodeFieldSpecifierMap | None = None,
123
123
  new_node_field_specifiers: NodeFieldSpecifierMap | None = None,
124
124
  **kwargs: Any,
125
- ):
125
+ ) -> None:
126
126
  self.base_branch = base_branch
127
127
  self.diff_branch_from_time = diff_branch_from_time
128
128
  self.current_node_field_specifiers = current_node_field_specifiers
@@ -335,7 +335,7 @@ CALL (p, q, diff_rel, row_from_time) {
335
335
  AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
336
336
  AND any(l in labels(node) WHERE l in ["Attribute", "Relationship"])
337
337
  AND node.branch_support IN [$branch_aware, $branch_agnostic]
338
- AND type(r_prop) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
338
+ AND type(r_prop) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
339
339
  AND any(l in labels(prop) WHERE l in ["Boolean", "Node", "AttributeValue"])
340
340
  AND (top_diff_rel.to IS NULL OR top_diff_rel.to >= r_node.from)
341
341
  AND (r_node.to IS NULL OR r_node.to >= r_prop.from)
@@ -532,7 +532,7 @@ CALL (root, r_root, p, diff_rel, q) {
532
532
  )
533
533
  WHERE %(id_func)s(mid_r_root) = %(id_func)s(r_root)
534
534
  AND %(id_func)s(mid_diff_rel) = %(id_func)s(diff_rel)
535
- AND type(r_prop) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
535
+ AND type(r_prop) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
536
536
  AND any(l in labels(prop) WHERE l in ["Boolean", "Node", "AttributeValue"])
537
537
  AND r_prop.from < $to_time AND r_prop.branch = mid_diff_rel.branch
538
538
  AND (mid_diff_rel.to IS NULL OR mid_diff_rel.to >= r_prop.from)
@@ -609,7 +609,7 @@ class DiffPropertyPathsQuery(DiffCalculationQuery):
609
609
  MATCH diff_rel_path = (root:Root)<-[r_root:IS_PART_OF]-(n:Node)-[r_node]-(p)-[diff_rel {branch: $branch_name}]->(q)
610
610
  WHERE p.branch_support = $branch_aware
611
611
  AND any(l in labels(p) WHERE l in ["Attribute", "Relationship"])
612
- AND type(diff_rel) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE"]
612
+ AND type(diff_rel) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE"]
613
613
  AND any(l in labels(q) WHERE l in ["Boolean", "Node", "AttributeValue"])
614
614
  AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
615
615
  // node ID and field name filtering first pass
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Iterable
7
7
  from infrahub.core.constants import InfrahubKind
8
8
  from infrahub.core.graph.schema import GraphAttributeIPHostNode, GraphAttributeIPNetworkNode
9
9
  from infrahub.core.ipam.constants import AllIPTypes, IPAddressType, IPNetworkType
10
- from infrahub.core.query import QueryType
10
+ from infrahub.core.query import QueryResult, QueryType
11
11
  from infrahub.core.registry import registry
12
12
  from infrahub.core.utils import convert_ip_to_binary_str
13
13
 
@@ -57,7 +57,7 @@ class IPPrefixSubnetFetch(Query):
57
57
  obj: IPNetworkType,
58
58
  namespace: Node | str | None = None,
59
59
  **kwargs,
60
- ):
60
+ ) -> None:
61
61
  self.obj = obj
62
62
  self.namespace_id = _get_namespace_id(namespace)
63
63
 
@@ -146,7 +146,7 @@ class IPPrefixIPAddressFetch(Query):
146
146
  obj: IPNetworkType,
147
147
  namespace: Node | str | None = None,
148
148
  **kwargs,
149
- ):
149
+ ) -> None:
150
150
  self.obj = obj
151
151
  self.namespace_id = _get_namespace_id(namespace)
152
152
 
@@ -242,11 +242,53 @@ async def get_ip_addresses(
242
242
  return query.get_addresses()
243
243
 
244
244
 
245
+ @dataclass(frozen=True)
246
+ class IPPrefixUtilizationResult:
247
+ """Result from IPPrefixUtilization containing prefix child allocation data."""
248
+
249
+ prefix_uuid: str
250
+ """UUID of the parent prefix node."""
251
+
252
+ child_uuid: str
253
+ """UUID of the child node (prefix or address)."""
254
+
255
+ child_kind: str
256
+ """Kind/type of the child node."""
257
+
258
+ child_labels: tuple[str, ...]
259
+ """Labels of the child node (used to determine if IPADDRESS or IPPREFIX)."""
260
+
261
+ ip_value: str
262
+ """IP value (address or prefix) of the child."""
263
+
264
+ prefixlen: int
265
+ """Prefix length of the child IP value."""
266
+
267
+ branch: str
268
+ """Branch name where this allocation exists."""
269
+
270
+ @classmethod
271
+ def from_db(cls, result: QueryResult) -> IPPrefixUtilizationResult:
272
+ """Convert raw QueryResult to typed dataclass."""
273
+ pfx = result.get_node("pfx")
274
+ child = result.get_node("child")
275
+ av = result.get_node("av")
276
+ return cls(
277
+ prefix_uuid=str(pfx.get("uuid")),
278
+ child_uuid=str(child.get("uuid")),
279
+ child_kind=child.get("kind"),
280
+ child_labels=tuple(child.labels),
281
+ ip_value=av.get("value"),
282
+ prefixlen=av.get("prefixlen"),
283
+ branch=str(result.get("branch")),
284
+ )
285
+
286
+
245
287
  class IPPrefixUtilization(Query):
246
288
  name = "ipprefix_utilization_prefix"
247
289
  type = QueryType.READ
248
290
 
249
- def __init__(self, ip_prefixes: list[str], allocated_kinds: list[str], **kwargs):
291
+ def __init__(self, ip_prefixes: list[str], allocated_kinds: list[str], **kwargs) -> None:
250
292
  self.ip_prefixes = ip_prefixes
251
293
  self.allocated_kinds: list[str] = []
252
294
  self.allocated_kinds_rel: list[str] = []
@@ -315,6 +357,55 @@ class IPPrefixUtilization(Query):
315
357
  self.return_labels = ["pfx", "child", "av", "branch_level", "branch"]
316
358
  self.add_to_query(query)
317
359
 
360
+ def get_data(self) -> list[IPPrefixUtilizationResult]:
361
+ """Return results as typed dataclass instances.
362
+
363
+ Returns:
364
+ List of IPPrefixUtilizationResult containing prefix child allocation data.
365
+ """
366
+ return [IPPrefixUtilizationResult.from_db(result) for result in self.get_results()]
367
+
368
+
369
+ @dataclass(frozen=True)
370
+ class IPPrefixReconcileQueryResult:
371
+ """Result from IPPrefixReconcileQuery containing IP reconciliation data."""
372
+
373
+ ip_node_uuid: str | None
374
+ """UUID of the IP node being reconciled, or None if not found."""
375
+
376
+ current_parent_uuid: str | None
377
+ """UUID of the current parent prefix, or None if no parent exists."""
378
+
379
+ calculated_parent_uuid: str | None
380
+ """UUID of the calculated correct parent prefix, or None if should be top-level."""
381
+
382
+ current_children_uuids: tuple[str, ...]
383
+ """UUIDs of current child prefixes/addresses."""
384
+
385
+ calculated_children_uuids: tuple[str, ...]
386
+ """UUIDs of calculated correct child prefixes/addresses."""
387
+
388
+ @classmethod
389
+ def from_db(cls, result: QueryResult) -> IPPrefixReconcileQueryResult:
390
+ """Convert raw QueryResult to typed dataclass."""
391
+
392
+ def get_optional_node_uuid(label: str) -> str | None:
393
+ """Extract UUID from an optional node (may be None from OPTIONAL MATCH)."""
394
+ node = result.get(label)
395
+ return str(node.get("uuid")) if node and node.get("uuid") else None
396
+
397
+ def get_collection_uuids(label: str) -> tuple[str, ...]:
398
+ """Extract UUIDs from a node collection (may contain None from COLLECT)."""
399
+ return tuple(str(n.get("uuid")) for n in result.get_node_collection(label) if n and n.get("uuid"))
400
+
401
+ return cls(
402
+ ip_node_uuid=get_optional_node_uuid("ip_node"),
403
+ current_parent_uuid=get_optional_node_uuid("current_parent"),
404
+ calculated_parent_uuid=get_optional_node_uuid("new_parent"),
405
+ current_children_uuids=get_collection_uuids("current_children"),
406
+ calculated_children_uuids=get_collection_uuids("new_children"),
407
+ )
408
+
318
409
 
319
410
  class IPPrefixReconcileQuery(Query):
320
411
  name = "ip_prefix_reconcile"
@@ -326,7 +417,7 @@ class IPPrefixReconcileQuery(Query):
326
417
  namespace: Node | str | None = None,
327
418
  node_uuid: str | None = None,
328
419
  **kwargs,
329
- ):
420
+ ) -> None:
330
421
  self.ip_value = ip_value
331
422
  self.ip_uuid = node_uuid
332
423
  self.namespace_id = _get_namespace_id(namespace)
@@ -702,44 +793,14 @@ class IPPrefixReconcileQuery(Query):
702
793
  self.order_by = ["ip_node.uuid"]
703
794
  self.return_labels = ["ip_node", "current_parent", "current_children", "new_parent", "new_children"]
704
795
 
705
- def _get_uuid_from_query(self, node_name: str) -> str | None:
706
- results = list(self.get_results())
707
- if not results:
708
- return None
709
- result = results[0]
710
- node = result.get(node_name)
711
- if not node:
712
- return None
713
- node_uuid = node.get("uuid")
714
- if node_uuid:
715
- return str(node_uuid)
716
- return None
796
+ def get_data(self) -> IPPrefixReconcileQueryResult | None:
797
+ """Return single result as typed dataclass instance.
717
798
 
718
- def _get_uuids_from_query_list(self, alias_name: str) -> list[str]:
799
+ Returns:
800
+ IPPrefixReconcileQueryResult containing reconciliation data,
801
+ or None if no results found.
802
+ """
719
803
  results = list(self.get_results())
720
804
  if not results:
721
- return []
722
- result = results[0]
723
- element_uuids = []
724
- for element in result.get(alias_name):
725
- if not element:
726
- continue
727
- element_uuid = element.get("uuid")
728
- if element_uuid:
729
- element_uuids.append(str(element_uuid))
730
- return element_uuids
731
-
732
- def get_ip_node_uuid(self) -> str | None:
733
- return self._get_uuid_from_query("ip_node")
734
-
735
- def get_current_parent_uuid(self) -> str | None:
736
- return self._get_uuid_from_query("current_parent")
737
-
738
- def get_calculated_parent_uuid(self) -> str | None:
739
- return self._get_uuid_from_query("new_parent")
740
-
741
- def get_current_children_uuids(self) -> list[str]:
742
- return self._get_uuids_from_query_list("current_children")
743
-
744
- def get_calculated_children_uuids(self) -> list[str]:
745
- return self._get_uuids_from_query_list("new_children")
805
+ return None
806
+ return IPPrefixReconcileQueryResult.from_db(results[0])