infrahub-server 1.2.12__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 (205) 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/infrahubkind.py +9 -0
  18. infrahub/core/constraint/node/runner.py +3 -1
  19. infrahub/core/convert_object_type/__init__.py +0 -0
  20. infrahub/core/convert_object_type/conversion.py +124 -0
  21. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  22. infrahub/core/diff/coordinator.py +8 -1
  23. infrahub/core/diff/query/all_conflicts.py +1 -5
  24. infrahub/core/diff/query/artifact.py +10 -20
  25. infrahub/core/diff/query/delete_query.py +8 -4
  26. infrahub/core/diff/query/diff_get.py +3 -6
  27. infrahub/core/diff/query/field_specifiers.py +1 -1
  28. infrahub/core/diff/query/field_summary.py +2 -4
  29. infrahub/core/diff/query/merge.py +72 -125
  30. infrahub/core/diff/query/save.py +28 -43
  31. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  32. infrahub/core/diff/query/time_range_query.py +0 -1
  33. infrahub/core/diff/repository/repository.py +4 -0
  34. infrahub/core/manager.py +14 -11
  35. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  36. infrahub/core/migrations/graph/m012_convert_account_generic.py +1 -1
  37. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -6
  38. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -2
  39. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -2
  40. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  43. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +2 -2
  44. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  45. infrahub/core/migrations/graph/m028_delete_diffs.py +1 -2
  46. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +30 -48
  47. infrahub/core/migrations/graph/m030_illegal_edges.py +1 -2
  48. infrahub/core/migrations/query/attribute_add.py +1 -2
  49. infrahub/core/migrations/query/attribute_rename.py +6 -11
  50. infrahub/core/migrations/query/delete_element_in_schema.py +19 -17
  51. infrahub/core/migrations/query/node_duplicate.py +19 -21
  52. infrahub/core/migrations/query/relationship_duplicate.py +19 -18
  53. infrahub/core/migrations/schema/node_attribute_remove.py +4 -8
  54. infrahub/core/migrations/schema/node_remove.py +19 -20
  55. infrahub/core/models.py +29 -2
  56. infrahub/core/node/__init__.py +131 -28
  57. infrahub/core/node/base.py +1 -1
  58. infrahub/core/node/create.py +211 -0
  59. infrahub/core/node/resource_manager/number_pool.py +31 -5
  60. infrahub/core/node/standard.py +6 -1
  61. infrahub/core/path.py +15 -1
  62. infrahub/core/protocols.py +57 -0
  63. infrahub/core/protocols_base.py +3 -0
  64. infrahub/core/query/__init__.py +2 -2
  65. infrahub/core/query/delete.py +3 -3
  66. infrahub/core/query/diff.py +19 -32
  67. infrahub/core/query/ipam.py +10 -20
  68. infrahub/core/query/node.py +29 -47
  69. infrahub/core/query/relationship.py +55 -34
  70. infrahub/core/query/resource_manager.py +1 -2
  71. infrahub/core/query/standard_node.py +19 -5
  72. infrahub/core/query/subquery.py +2 -4
  73. infrahub/core/relationship/constraints/count.py +10 -9
  74. infrahub/core/relationship/constraints/interface.py +2 -1
  75. infrahub/core/relationship/constraints/peer_kind.py +2 -1
  76. infrahub/core/relationship/constraints/peer_parent.py +56 -0
  77. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  78. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  79. infrahub/core/relationship/model.py +4 -1
  80. infrahub/core/schema/__init__.py +2 -1
  81. infrahub/core/schema/attribute_parameters.py +160 -0
  82. infrahub/core/schema/attribute_schema.py +130 -7
  83. infrahub/core/schema/basenode_schema.py +27 -3
  84. infrahub/core/schema/definitions/core/__init__.py +29 -1
  85. infrahub/core/schema/definitions/core/group.py +45 -0
  86. infrahub/core/schema/definitions/core/resource_pool.py +9 -0
  87. infrahub/core/schema/definitions/internal.py +43 -5
  88. infrahub/core/schema/generated/attribute_schema.py +16 -3
  89. infrahub/core/schema/generated/relationship_schema.py +11 -1
  90. infrahub/core/schema/manager.py +7 -2
  91. infrahub/core/schema/schema_branch.py +104 -9
  92. infrahub/core/validators/__init__.py +15 -2
  93. infrahub/core/validators/attribute/choices.py +1 -3
  94. infrahub/core/validators/attribute/enum.py +1 -3
  95. infrahub/core/validators/attribute/kind.py +1 -3
  96. infrahub/core/validators/attribute/length.py +13 -7
  97. infrahub/core/validators/attribute/min_max.py +118 -0
  98. infrahub/core/validators/attribute/number_pool.py +106 -0
  99. infrahub/core/validators/attribute/optional.py +1 -4
  100. infrahub/core/validators/attribute/regex.py +5 -6
  101. infrahub/core/validators/attribute/unique.py +1 -3
  102. infrahub/core/validators/determiner.py +18 -2
  103. infrahub/core/validators/enum.py +12 -0
  104. infrahub/core/validators/node/hierarchy.py +3 -6
  105. infrahub/core/validators/query.py +1 -3
  106. infrahub/core/validators/relationship/count.py +6 -12
  107. infrahub/core/validators/relationship/optional.py +2 -4
  108. infrahub/core/validators/relationship/peer.py +177 -12
  109. infrahub/core/validators/tasks.py +1 -1
  110. infrahub/core/validators/uniqueness/query.py +5 -9
  111. infrahub/database/__init__.py +12 -4
  112. infrahub/database/validation.py +1 -2
  113. infrahub/dependencies/builder/constraint/grouped/node_runner.py +4 -0
  114. infrahub/dependencies/builder/constraint/relationship_manager/peer_parent.py +8 -0
  115. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  116. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  117. infrahub/dependencies/builder/constraint/schema/relationship_peer.py +8 -0
  118. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  119. infrahub/dependencies/registry.py +4 -0
  120. infrahub/events/group_action.py +1 -0
  121. infrahub/events/models.py +1 -1
  122. infrahub/git/base.py +5 -3
  123. infrahub/git/integrator.py +96 -5
  124. infrahub/git/tasks.py +1 -0
  125. infrahub/graphql/analyzer.py +139 -18
  126. infrahub/graphql/manager.py +4 -0
  127. infrahub/graphql/mutations/action.py +164 -0
  128. infrahub/graphql/mutations/convert_object_type.py +71 -0
  129. infrahub/graphql/mutations/main.py +24 -175
  130. infrahub/graphql/mutations/proposed_change.py +20 -17
  131. infrahub/graphql/mutations/relationship.py +32 -0
  132. infrahub/graphql/mutations/resource_manager.py +63 -7
  133. infrahub/graphql/queries/convert_object_type_mapping.py +34 -0
  134. infrahub/graphql/queries/resource_manager.py +7 -1
  135. infrahub/graphql/resolvers/many_relationship.py +1 -1
  136. infrahub/graphql/resolvers/resolver.py +2 -2
  137. infrahub/graphql/resolvers/single_relationship.py +1 -1
  138. infrahub/graphql/schema.py +6 -0
  139. infrahub/menu/menu.py +34 -2
  140. infrahub/message_bus/messages/__init__.py +0 -10
  141. infrahub/message_bus/operations/__init__.py +0 -8
  142. infrahub/message_bus/operations/refresh/registry.py +3 -6
  143. infrahub/patch/queries/delete_duplicated_edges.py +10 -15
  144. infrahub/pools/models.py +14 -0
  145. infrahub/pools/number.py +5 -3
  146. infrahub/pools/registration.py +22 -0
  147. infrahub/pools/tasks.py +126 -0
  148. infrahub/prefect_server/models.py +1 -19
  149. infrahub/proposed_change/models.py +68 -3
  150. infrahub/proposed_change/tasks.py +911 -34
  151. infrahub/schema/__init__.py +0 -0
  152. infrahub/schema/tasks.py +27 -0
  153. infrahub/schema/triggers.py +23 -0
  154. infrahub/task_manager/models.py +10 -6
  155. infrahub/trigger/catalogue.py +6 -0
  156. infrahub/trigger/models.py +23 -6
  157. infrahub/trigger/setup.py +26 -2
  158. infrahub/trigger/tasks.py +4 -2
  159. infrahub/types.py +6 -0
  160. infrahub/webhook/tasks.py +4 -8
  161. infrahub/workflows/catalogue.py +103 -1
  162. infrahub_sdk/client.py +43 -10
  163. infrahub_sdk/ctl/generator.py +4 -4
  164. infrahub_sdk/ctl/repository.py +1 -1
  165. infrahub_sdk/node/__init__.py +39 -0
  166. infrahub_sdk/node/attribute.py +122 -0
  167. infrahub_sdk/node/constants.py +21 -0
  168. infrahub_sdk/{node.py → node/node.py} +158 -803
  169. infrahub_sdk/node/parsers.py +15 -0
  170. infrahub_sdk/node/property.py +24 -0
  171. infrahub_sdk/node/related_node.py +266 -0
  172. infrahub_sdk/node/relationship.py +302 -0
  173. infrahub_sdk/protocols.py +112 -0
  174. infrahub_sdk/protocols_base.py +34 -2
  175. infrahub_sdk/pytest_plugin/items/python_transform.py +2 -1
  176. infrahub_sdk/query_groups.py +17 -5
  177. infrahub_sdk/schema/main.py +1 -0
  178. infrahub_sdk/schema/repository.py +16 -0
  179. infrahub_sdk/spec/object.py +1 -1
  180. infrahub_sdk/store.py +1 -1
  181. infrahub_sdk/testing/schemas/car_person.py +1 -0
  182. infrahub_sdk/utils.py +7 -20
  183. infrahub_sdk/yaml.py +6 -5
  184. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/METADATA +3 -3
  185. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/RECORD +192 -166
  186. infrahub_testcontainers/container.py +0 -1
  187. infrahub_testcontainers/docker-compose.test.yml +1 -1
  188. infrahub_testcontainers/helpers.py +8 -2
  189. infrahub/message_bus/messages/check_generator_run.py +0 -26
  190. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  191. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  192. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  193. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  194. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  195. infrahub/message_bus/operations/check/__init__.py +0 -3
  196. infrahub/message_bus/operations/check/generator.py +0 -156
  197. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  198. infrahub/message_bus/operations/finalize/validator.py +0 -133
  199. infrahub/message_bus/operations/requests/__init__.py +0 -9
  200. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  201. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  202. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  203. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/LICENSE.txt +0 -0
  204. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/WHEEL +0 -0
  205. {infrahub_server-1.2.12.dist-info → infrahub_server-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -15,7 +15,7 @@ from infrahub.events import BranchDeletedEvent
15
15
  from infrahub.git.repository import get_initialized_repo
16
16
  from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
17
17
  from infrahub.trigger.models import TriggerSetupReport, TriggerType
18
- from infrahub.trigger.setup import setup_triggers
18
+ from infrahub.trigger.setup import setup_triggers, setup_triggers_specific
19
19
  from infrahub.workflows.catalogue import (
20
20
  COMPUTED_ATTRIBUTE_PROCESS_JINJA2,
21
21
  COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM,
@@ -304,36 +304,30 @@ async def computed_attribute_setup_jinja2(
304
304
  await add_tags(branches=[branch_name])
305
305
  await wait_for_schema_to_converge(branch_name=branch_name, component=service.component, db=db, log=log)
306
306
 
307
- triggers = await gather_trigger_computed_attribute_jinja2()
308
-
307
+ report: TriggerSetupReport = await setup_triggers_specific(
308
+ gatherer=gather_trigger_computed_attribute_jinja2, trigger_type=TriggerType.COMPUTED_ATTR_JINJA2
309
+ ) # type: ignore[misc]
309
310
  # Configure all ComputedAttrJinja2Trigger in Prefect
310
- async with get_client(sync_client=False) as prefect_client:
311
- report: TriggerSetupReport = await setup_triggers(
312
- client=prefect_client,
313
- triggers=triggers,
314
- trigger_type=TriggerType.COMPUTED_ATTR_JINJA2,
315
- force_update=False,
316
- ) # type: ignore[misc]
317
311
 
318
- # Since we can have multiple trigger per NodeKind
319
- # we need to extract the list of unique node that should be processed
320
- unique_nodes: set[tuple[str, str, str]] = {
321
- (trigger.branch, trigger.computed_attribute.kind, trigger.computed_attribute.attribute.name) # type: ignore[attr-defined]
322
- for trigger in report.updated + report.created
323
- }
324
- for branch, kind, attribute_name in unique_nodes:
325
- if event_name != BranchDeletedEvent.event_name and branch == branch_name:
326
- await service.workflow.submit_workflow(
327
- workflow=TRIGGER_UPDATE_JINJA_COMPUTED_ATTRIBUTES,
328
- context=context,
329
- parameters={
330
- "branch_name": branch,
331
- "computed_attribute_name": attribute_name,
332
- "computed_attribute_kind": kind,
333
- },
334
- )
335
-
336
- log.info(f"{len(triggers)} Computed Attribute for Jinja2 automation configuration completed")
312
+ # Since we can have multiple trigger per NodeKind
313
+ # we need to extract the list of unique node that should be processed
314
+ unique_nodes: set[tuple[str, str, str]] = {
315
+ (trigger.branch, trigger.computed_attribute.kind, trigger.computed_attribute.attribute.name) # type: ignore[attr-defined]
316
+ for trigger in report.updated + report.created
317
+ }
318
+ for branch, kind, attribute_name in unique_nodes:
319
+ if event_name != BranchDeletedEvent.event_name and branch == branch_name:
320
+ await service.workflow.submit_workflow(
321
+ workflow=TRIGGER_UPDATE_JINJA_COMPUTED_ATTRIBUTES,
322
+ context=context,
323
+ parameters={
324
+ "branch_name": branch,
325
+ "computed_attribute_name": attribute_name,
326
+ "computed_attribute_kind": kind,
327
+ },
328
+ )
329
+
330
+ log.info(f"{report.in_use_count} Computed Attribute for Jinja2 automation configuration completed")
337
331
 
338
332
 
339
333
  @flow(
infrahub/core/account.py CHANGED
@@ -69,8 +69,7 @@ class AccountGlobalPermissionQuery(Query):
69
69
  query = """
70
70
  MATCH (account:%(generic_account_node)s)
71
71
  WHERE account.uuid = $account_id
72
- CALL {
73
- WITH account
72
+ CALL (account) {
74
73
  MATCH (account)-[r:IS_PART_OF]-(root:Root)
75
74
  WHERE %(branch_filter)s
76
75
  RETURN account as account1, r as r1
@@ -80,8 +79,7 @@ class AccountGlobalPermissionQuery(Query):
80
79
  WITH account, r1 as r
81
80
  WHERE r.status = "active"
82
81
  WITH account
83
- CALL {
84
- WITH account
82
+ CALL (account) {
85
83
  MATCH (account)-[r1:IS_RELATED]->(:Relationship {name: "group_member"})<-[r2:IS_RELATED]-(account_group:%(account_group_node)s)
86
84
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
87
85
  WITH account_group, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -92,8 +90,7 @@ class AccountGlobalPermissionQuery(Query):
92
90
  }
93
91
  WITH account_group
94
92
 
95
- CALL {
96
- WITH account_group
93
+ CALL (account_group) {
97
94
  MATCH (account_group)-[r1:IS_RELATED]->(:Relationship {name: "role__accountgroups"})<-[r2:IS_RELATED]-(account_role:%(account_role_node)s)
98
95
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
99
96
  WITH account_role, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -104,8 +101,7 @@ class AccountGlobalPermissionQuery(Query):
104
101
  }
105
102
  WITH account_role
106
103
 
107
- CALL {
108
- WITH account_role
104
+ CALL (account_role) {
109
105
  MATCH (account_role)-[r1:IS_RELATED]->(:Relationship {name: "role__permissions"})<-[r2:IS_RELATED]-(global_permission:%(global_permission_node)s)
110
106
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
111
107
  WITH global_permission, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -116,7 +112,7 @@ class AccountGlobalPermissionQuery(Query):
116
112
  }
117
113
  WITH global_permission
118
114
 
119
- CALL {
115
+ CALL (global_permission) {
120
116
  WITH global_permission
121
117
  MATCH (global_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[r2:HAS_VALUE]->(global_permission_action:AttributeValue)
122
118
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
@@ -127,8 +123,7 @@ class AccountGlobalPermissionQuery(Query):
127
123
  WITH global_permission, global_permission_action, is_active AS gpa_is_active
128
124
  WHERE gpa_is_active = TRUE
129
125
 
130
- CALL {
131
- WITH global_permission
126
+ CALL (global_permission) {
132
127
  MATCH (global_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "decision"})-[r2:HAS_VALUE]->(global_permission_decision:AttributeValue)
133
128
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
134
129
  RETURN global_permission_decision, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -183,8 +178,7 @@ class AccountObjectPermissionQuery(Query):
183
178
  query = """
184
179
  MATCH (account:%(generic_account_node)s)
185
180
  WHERE account.uuid = $account_id
186
- CALL {
187
- WITH account
181
+ CALL (account) {
188
182
  MATCH (account)-[r:IS_PART_OF]-(root:Root)
189
183
  WHERE %(branch_filter)s
190
184
  RETURN account as account1, r as r1
@@ -194,8 +188,7 @@ class AccountObjectPermissionQuery(Query):
194
188
  WITH account, r1 as r
195
189
  WHERE r.status = "active"
196
190
  WITH account
197
- CALL {
198
- WITH account
191
+ CALL (account) {
199
192
  MATCH (account)-[r1:IS_RELATED]->(:Relationship {name: "group_member"})<-[r2:IS_RELATED]-(account_group:%(account_group_node)s)
200
193
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
201
194
  WITH account_group, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -206,8 +199,7 @@ class AccountObjectPermissionQuery(Query):
206
199
  }
207
200
  WITH account_group
208
201
 
209
- CALL {
210
- WITH account_group
202
+ CALL (account_group) {
211
203
  MATCH (account_group)-[r1:IS_RELATED]->(:Relationship {name: "role__accountgroups"})<-[r2:IS_RELATED]-(account_role:%(account_role_node)s)
212
204
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
213
205
  WITH account_role, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -218,8 +210,7 @@ class AccountObjectPermissionQuery(Query):
218
210
  }
219
211
  WITH account_role
220
212
 
221
- CALL {
222
- WITH account_role
213
+ CALL (account_role) {
223
214
  MATCH (account_role)-[r1:IS_RELATED]->(:Relationship {name: "role__permissions"})<-[r2:IS_RELATED]-(object_permission:%(object_permission_node)s)
224
215
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
225
216
  WITH object_permission, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -230,8 +221,7 @@ class AccountObjectPermissionQuery(Query):
230
221
  }
231
222
  WITH object_permission
232
223
 
233
- CALL {
234
- WITH object_permission
224
+ CALL (object_permission) {
235
225
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "namespace"})-[r2:HAS_VALUE]->(object_permission_namespace:AttributeValue)
236
226
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
237
227
  RETURN object_permission_namespace, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -240,8 +230,7 @@ class AccountObjectPermissionQuery(Query):
240
230
  }
241
231
  WITH object_permission, object_permission_namespace, is_active AS opn_is_active
242
232
  WHERE opn_is_active = TRUE
243
- CALL {
244
- WITH object_permission
233
+ CALL (object_permission) {
245
234
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[r2:HAS_VALUE]->(object_permission_name:AttributeValue)
246
235
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
247
236
  RETURN object_permission_name, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -250,8 +239,7 @@ class AccountObjectPermissionQuery(Query):
250
239
  }
251
240
  WITH object_permission, object_permission_namespace, object_permission_name, is_active AS opn_is_active
252
241
  WHERE opn_is_active = TRUE
253
- CALL {
254
- WITH object_permission
242
+ CALL (object_permission) {
255
243
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[r2:HAS_VALUE]->(object_permission_action:AttributeValue)
256
244
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
257
245
  RETURN object_permission_action, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -260,8 +248,7 @@ class AccountObjectPermissionQuery(Query):
260
248
  }
261
249
  WITH object_permission, object_permission_namespace, object_permission_name, object_permission_action, is_active AS opa_is_active
262
250
  WHERE opa_is_active = TRUE
263
- CALL {
264
- WITH object_permission
251
+ CALL (object_permission) {
265
252
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "decision"})-[r2:HAS_VALUE]->(object_permission_decision:AttributeValue)
266
253
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
267
254
  RETURN object_permission_decision, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -336,8 +323,7 @@ class AccountRoleGlobalPermissionQuery(Query):
336
323
  query = """
337
324
  MATCH (account_role:%(account_role_node)s)
338
325
  WHERE account_role.uuid = $role_id
339
- CALL {
340
- WITH account_role
326
+ CALL (account_role) {
341
327
  MATCH (account_role)-[r:IS_PART_OF]-(root:Root)
342
328
  WHERE %(branch_filter)s
343
329
  RETURN account_role as account_role1, r as r1
@@ -348,8 +334,7 @@ class AccountRoleGlobalPermissionQuery(Query):
348
334
  WHERE r.status = "active"
349
335
  WITH account_role
350
336
 
351
- CALL {
352
- WITH account_role
337
+ CALL (account_role) {
353
338
  MATCH (account_role)-[r1:IS_RELATED]->(:Relationship {name: "role__permissions"})<-[r2:IS_RELATED]-(global_permission:%(global_permission_node)s)
354
339
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
355
340
  WITH global_permission, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -360,8 +345,7 @@ class AccountRoleGlobalPermissionQuery(Query):
360
345
  }
361
346
  WITH global_permission
362
347
 
363
- CALL {
364
- WITH global_permission
348
+ CALL (global_permission) {
365
349
  MATCH (global_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[r2:HAS_VALUE]->(global_permission_action:AttributeValue)
366
350
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
367
351
  RETURN global_permission_action, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -371,8 +355,7 @@ class AccountRoleGlobalPermissionQuery(Query):
371
355
  WITH global_permission, global_permission_action, is_active AS gpa_is_active
372
356
  WHERE gpa_is_active = TRUE
373
357
 
374
- CALL {
375
- WITH global_permission
358
+ CALL (global_permission) {
376
359
  MATCH (global_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "decision"})-[r2:HAS_VALUE]->(global_permission_decision:AttributeValue)
377
360
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
378
361
  RETURN global_permission_decision, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -425,8 +408,7 @@ class AccountRoleObjectPermissionQuery(Query):
425
408
  query = """
426
409
  MATCH (account_role:%(account_role_node)s)
427
410
  WHERE account_role.uuid = $role_id
428
- CALL {
429
- WITH account_role
411
+ CALL (account_role) {
430
412
  MATCH (account_role)-[r:IS_PART_OF]-(root:Root)
431
413
  WHERE %(branch_filter)s
432
414
  RETURN account_role as account_role1, r as r1
@@ -437,8 +419,7 @@ class AccountRoleObjectPermissionQuery(Query):
437
419
  WHERE r.status = "active"
438
420
  WITH account_role
439
421
 
440
- CALL {
441
- WITH account_role
422
+ CALL (account_role) {
442
423
  MATCH (account_role)-[r1:IS_RELATED]->(:Relationship {name: "role__permissions"})<-[r2:IS_RELATED]-(object_permission:%(object_permission_node)s)
443
424
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
444
425
  WITH object_permission, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -449,8 +430,7 @@ class AccountRoleObjectPermissionQuery(Query):
449
430
  }
450
431
  WITH object_permission
451
432
 
452
- CALL {
453
- WITH object_permission
433
+ CALL (object_permission) {
454
434
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "namespace"})-[r2:HAS_VALUE]->(object_permission_namespace:AttributeValue)
455
435
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
456
436
  RETURN object_permission_namespace, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -459,8 +439,7 @@ class AccountRoleObjectPermissionQuery(Query):
459
439
  }
460
440
  WITH object_permission, object_permission_namespace, is_active AS opn_is_active
461
441
  WHERE opn_is_active = TRUE
462
- CALL {
463
- WITH object_permission
442
+ CALL (object_permission) {
464
443
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[r2:HAS_VALUE]->(object_permission_name:AttributeValue)
465
444
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
466
445
  RETURN object_permission_name, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -469,8 +448,7 @@ class AccountRoleObjectPermissionQuery(Query):
469
448
  }
470
449
  WITH object_permission, object_permission_namespace, object_permission_name, is_active AS opn_is_active
471
450
  WHERE opn_is_active = TRUE
472
- CALL {
473
- WITH object_permission
451
+ CALL (object_permission) {
474
452
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[r2:HAS_VALUE]->(object_permission_action:AttributeValue)
475
453
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
476
454
  RETURN object_permission_action, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -479,8 +457,7 @@ class AccountRoleObjectPermissionQuery(Query):
479
457
  }
480
458
  WITH object_permission, object_permission_namespace, object_permission_name, object_permission_action, is_active AS opa_is_active
481
459
  WHERE opa_is_active = TRUE
482
- CALL {
483
- WITH object_permission
460
+ CALL (object_permission) {
484
461
  MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "decision"})-[r2:HAS_VALUE]->(object_permission_decision:AttributeValue)
485
462
  WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
486
463
  RETURN object_permission_decision, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -93,8 +93,8 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
93
93
  updated_at: Timestamp | str | None = None,
94
94
  is_default: bool = False,
95
95
  is_from_profile: bool = False,
96
- **kwargs,
97
- ):
96
+ **kwargs: dict[str, Any],
97
+ ) -> None:
98
98
  self.id = id
99
99
  self.db_id = db_id
100
100
 
@@ -169,7 +169,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
169
169
  return self.branch
170
170
 
171
171
  @classmethod
172
- def __init_subclass__(cls, **kwargs) -> None:
172
+ def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None:
173
173
  super().__init_subclass__(**kwargs)
174
174
  registry.attribute[cls.__name__] = cls
175
175
 
@@ -232,7 +232,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
232
232
  Raises:
233
233
  ValidationError: Content of the attribute value is not valid
234
234
  """
235
- if schema.regex:
235
+ if regex := schema.get_regex():
236
236
  if schema.kind == "List":
237
237
  validation_values = [str(entry) for entry in value]
238
238
  else:
@@ -240,22 +240,20 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
240
240
 
241
241
  for validation_value in validation_values:
242
242
  try:
243
- is_valid = re.match(pattern=schema.regex, string=str(validation_value))
243
+ is_valid = re.match(pattern=regex, string=str(validation_value))
244
244
  except re.error as exc:
245
- raise ValidationError(
246
- {name: f"The regex defined in the schema is not valid ({schema.regex!r})"}
247
- ) from exc
245
+ raise ValidationError({name: f"The regex defined in the schema is not valid ({regex!r})"}) from exc
248
246
 
249
247
  if not is_valid:
250
- raise ValidationError({name: f"{validation_value} must conform with the regex: {schema.regex!r}"})
248
+ raise ValidationError({name: f"{validation_value} must conform with the regex: {regex!r}"})
251
249
 
252
- if schema.min_length:
253
- if len(value) < schema.min_length:
254
- raise ValidationError({name: f"{value} must have a minimum length of {schema.min_length!r}"})
250
+ if min_length := schema.get_min_length():
251
+ if len(value) < min_length:
252
+ raise ValidationError({name: f"{value} must have a minimum length of {min_length!r}"})
255
253
 
256
- if schema.max_length:
257
- if len(value) > schema.max_length:
258
- raise ValidationError({name: f"{value} must have a maximum length of {schema.max_length!r}"})
254
+ if max_length := schema.get_max_length():
255
+ if len(value) > max_length:
256
+ raise ValidationError({name: f"{value} must have a maximum length of {max_length!r}"})
259
257
 
260
258
  if schema.enum:
261
259
  try:
@@ -112,6 +112,11 @@ class AccountType(InfrahubStringEnum):
112
112
  Git = "Git"
113
113
 
114
114
 
115
+ class NumberPoolType(InfrahubStringEnum):
116
+ USER = "User"
117
+ SCHEMA = "Schema"
118
+
119
+
115
120
  class AccountStatus(InfrahubStringEnum):
116
121
  ACTIVE = "active"
117
122
  INACTIVE = "inactive"
@@ -146,6 +151,11 @@ class AllowOverrideType(InfrahubStringEnum):
146
151
  ANY = "any"
147
152
 
148
153
 
154
+ class RepositoryObjects(InfrahubStringEnum):
155
+ OBJECT = "object"
156
+ MENU = "menu"
157
+
158
+
149
159
  class ContentType(InfrahubStringEnum):
150
160
  APPLICATION_JSON = "application/json"
151
161
  APPLICATION_YAML = "application/yaml"
@@ -2,6 +2,7 @@ ACCOUNT = "CoreAccount"
2
2
  ACCOUNTGROUP = "CoreAccountGroup"
3
3
  ACCOUNTROLE = "CoreAccountRole"
4
4
  ACCOUNTTOKEN = "InternalAccountToken"
5
+ ACTION = "CoreAction"
5
6
  ARTIFACT = "CoreArtifact"
6
7
  ARTIFACTCHECK = "CoreArtifactCheck"
7
8
  ARTIFACTDEFINITION = "CoreArtifactDefinition"
@@ -21,6 +22,7 @@ DATAVALIDATOR = "CoreDataValidator"
21
22
  FILECHECK = "CoreFileCheck"
22
23
  FILETHREAD = "CoreFileThread"
23
24
  GENERICACCOUNT = "CoreGenericAccount"
25
+ GENERATORACTION = "CoreGeneratorAction"
24
26
  GENERATORCHECK = "CoreGeneratorCheck"
25
27
  GENERATORDEFINITION = "CoreGeneratorDefinition"
26
28
  GENERATORINSTANCE = "CoreGeneratorInstance"
@@ -30,6 +32,8 @@ GENERICGROUP = "CoreGroup"
30
32
  GLOBALPERMISSION = "CoreGlobalPermission"
31
33
  GRAPHQLQUERY = "CoreGraphQLQuery"
32
34
  GRAPHQLQUERYGROUP = "CoreGraphQLQueryGroup"
35
+ GROUPACTION = "CoreGroupAction"
36
+ GROUPTRIGGERRULE = "CoreGroupTriggerRule"
33
37
  IPNAMESPACE = "BuiltinIPNamespace"
34
38
  IPADDRESS = "BuiltinIPAddress"
35
39
  IPADDRESSPOOL = "CoreIPAddressPool"
@@ -39,6 +43,9 @@ MENU = "CoreMenu"
39
43
  MENUITEM = "CoreMenuItem"
40
44
  NAMESPACE = "IpamNamespace"
41
45
  NODE = "CoreNode"
46
+ NODETRIGGERRULE = "CoreNodeTriggerRule"
47
+ NODETRIGGERATTRIBUTEMATCH = "CoreNodeTriggerAttributeMatch"
48
+ NODETRIGGERRELATIONSHIPMATCH = "CoreNodeTriggerRelationshipMatch"
42
49
  NUMBERPOOL = "CoreNumberPool"
43
50
  LINEAGEOWNER = "LineageOwner"
44
51
  LINEAGESOURCE = "LineageSource"
@@ -51,6 +58,7 @@ PROFILE = "CoreProfile"
51
58
  PROPOSEDCHANGE = "CoreProposedChange"
52
59
  REFRESHTOKEN = "InternalRefreshToken"
53
60
  REPOSITORY = "CoreRepository"
61
+ REPOSITORYGROUP = "CoreRepositoryGroup"
54
62
  RESOURCEPOOL = "CoreResourcePool"
55
63
  GENERICREPOSITORY = "CoreGenericRepository"
56
64
  READONLYREPOSITORY = "CoreReadOnlyRepository"
@@ -67,6 +75,7 @@ THREADCOMMENT = "CoreThreadComment"
67
75
  TRANSFORM = "CoreTransformation"
68
76
  TRANSFORMJINJA2 = "CoreTransformJinja2"
69
77
  TRANSFORMPYTHON = "CoreTransformPython"
78
+ TRIGGERRULE = "CoreTriggerRule"
70
79
  USERVALIDATOR = "CoreUserValidator"
71
80
  VALIDATOR = "CoreValidator"
72
81
  WEBHOOK = "CoreWebhook"
@@ -38,4 +38,6 @@ class NodeConstraintRunner:
38
38
  relationship_manager: RelationshipManager = getattr(node, relationship_name)
39
39
  await relationship_manager.fetch_relationship_ids(db=db, force_refresh=True)
40
40
  for relationship_constraint in self.relationship_manager_constraints:
41
- await relationship_constraint.check(relm=relationship_manager, node_schema=node.get_schema())
41
+ await relationship_constraint.check(
42
+ relm=relationship_manager, node_schema=node.get_schema(), node=node
43
+ )
File without changes
@@ -0,0 +1,124 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from infrahub.core.attribute import BaseAttribute
6
+ from infrahub.core.branch import Branch
7
+ from infrahub.core.constants import RelationshipCardinality
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.node import Node
10
+ from infrahub.core.node.create import create_node
11
+ from infrahub.core.query.relationship import GetAllPeersIds
12
+ from infrahub.core.relationship import RelationshipManager
13
+ from infrahub.core.schema import NodeSchema
14
+ from infrahub.database import InfrahubDatabase
15
+
16
+
17
+ class InputDataForDestField(BaseModel): # Only one of these fields can be not None
18
+ attribute_value: Any | None = None
19
+ peer_id: str | None = None
20
+ peers_ids: list[str] | None = None
21
+
22
+ @property
23
+ def value(self) -> Any:
24
+ fields = [self.attribute_value, self.peer_id, self.peers_ids]
25
+ set_fields = [f for f in fields if f is not None]
26
+ if len(set_fields) != 1:
27
+ raise ValueError("Exactly one of attribute_value, peer_id, or peers_ids must be set")
28
+ return set_fields[0]
29
+
30
+
31
+ class InputForDestField(BaseModel): # Only one of these fields can be not None
32
+ source_field: str | None = None
33
+ data: InputDataForDestField | None = None
34
+
35
+ @property
36
+ def value(self) -> Any:
37
+ if self.source_field is not None and self.data is not None:
38
+ raise ValueError("Only one of source_field or data can be set")
39
+ if self.source_field is None and self.data is None:
40
+ raise ValueError("Either source_field or data must be set")
41
+ return self.source_field if self.source_field is not None else self.data
42
+
43
+
44
+ async def get_out_rels_peers_ids(node: Node, db: InfrahubDatabase) -> list[str]:
45
+ all_peers: list[Node] = []
46
+ for name in node._relationships:
47
+ relm: RelationshipManager = getattr(node, name)
48
+ peers = await relm.get_peers(db=db)
49
+ all_peers.extend(peers.values())
50
+ return [peer.id for peer in all_peers]
51
+
52
+
53
+ async def build_data_new_node(db: InfrahubDatabase, mapping: dict[str, InputForDestField], node: Node) -> dict:
54
+ """Value of a given field on the target kind to convert is either an input source attribute/relationship of the source node,
55
+ or a raw value."""
56
+
57
+ data = {}
58
+ for dest_field_name, input_for_dest_field in mapping.items():
59
+ value = input_for_dest_field.value
60
+ if isinstance(value, str): # source_field
61
+ item = getattr(node, value)
62
+ if isinstance(item, BaseAttribute):
63
+ data[dest_field_name] = item.value
64
+ elif isinstance(item, RelationshipManager):
65
+ if item.schema.cardinality == RelationshipCardinality.ONE:
66
+ peer = await item.get_peer(db=db)
67
+ if peer is not None:
68
+ data[dest_field_name] = {"id": peer.id}
69
+ # else, relationship is optional, and if the target relationship is mandatory an error will be raised during creation
70
+ elif item.schema.cardinality == RelationshipCardinality.MANY:
71
+ data[dest_field_name] = [{"id": peer.id} for _, peer in (await item.get_peers(db=db)).items()]
72
+ else:
73
+ raise ValueError(f"Unknown cardinality {item.schema.cardinality=}")
74
+ else: # user input data
75
+ data[dest_field_name] = value.value
76
+ return data
77
+
78
+
79
+ async def get_unidirectional_rels_peers_ids(node: Node, branch: Branch, db: InfrahubDatabase) -> list[str]:
80
+ """
81
+ Returns peers ids of nodes connected to input `node` through an incoming unidirectional relationship.
82
+ """
83
+
84
+ out_rels_identifier = [rel.identifier for rel in node.get_schema().relationships]
85
+ query = await GetAllPeersIds.init(db=db, node_id=node.id, branch=branch, exclude_identifiers=out_rels_identifier)
86
+ await query.execute(db=db)
87
+ return query.get_peers_uuids()
88
+
89
+
90
+ async def convert_object_type(
91
+ node: Node, target_schema: NodeSchema, mapping: dict[str, InputForDestField], branch: Branch, db: InfrahubDatabase
92
+ ) -> Node:
93
+ """Delete the node and return the new created one. If creation fails, the node is not deleted, and raise an error.
94
+ An extra check is performed on input node peers relationships to make sure they are still valid."""
95
+
96
+ node_schema = node.get_schema()
97
+ if not isinstance(node_schema, NodeSchema):
98
+ raise ValueError(f"Only a node with a NodeSchema can be converted, got {type(node_schema)}")
99
+
100
+ async with db.start_transaction() as dbt:
101
+ deleted_node_out_rels_peer_ids = await get_out_rels_peers_ids(node=node, db=dbt)
102
+ deleted_node_unidir_rels_peer_ids = await get_unidirectional_rels_peers_ids(node=node, db=dbt, branch=branch)
103
+
104
+ # Delete the node, so we delete relationships with peers as well, which might temporarily break cardinality constraints
105
+ # but they should be restored when creating the new node.
106
+ deleted_nodes = await NodeManager.delete(db=dbt, branch=branch, nodes=[node], cascade_delete=False)
107
+ if len(deleted_nodes) != 1:
108
+ raise ValueError(f"Deleted {len(deleted_nodes)} nodes instead of 1")
109
+
110
+ data_new_node = await build_data_new_node(dbt, mapping, node)
111
+ node_created = await create_node(
112
+ data=data_new_node,
113
+ db=dbt,
114
+ branch=branch,
115
+ schema=target_schema,
116
+ )
117
+
118
+ # Make sure relationships with constraints are not broken by retrieving them
119
+ peers_ids = deleted_node_out_rels_peer_ids + deleted_node_unidir_rels_peer_ids
120
+ peers = await NodeManager.get_many(ids=peers_ids, db=dbt, prefetch_relationships=True, branch=branch)
121
+ for peer in peers.values():
122
+ peer.validate_relationships()
123
+
124
+ return node_created
@@ -0,0 +1,56 @@
1
+ from pydantic import BaseModel
2
+
3
+ from infrahub.core.constants import RelationshipCardinality
4
+ from infrahub.core.schema import NodeSchema
5
+
6
+
7
+ class SchemaMappingValue(BaseModel):
8
+ is_mandatory: bool
9
+ source_field_name: str | None = None # None means there is no corresponding source field name
10
+ relationship_cardinality: RelationshipCardinality | None = None
11
+
12
+
13
+ SchemaMapping = dict[str, SchemaMappingValue]
14
+
15
+
16
+ def get_schema_mapping(source_schema: NodeSchema, target_schema: NodeSchema) -> SchemaMapping:
17
+ """
18
+ Return fields mapping meant to be used for converting a node from `source_kind` to `target_kind`.
19
+ For any field of the target kind, field of the source kind will be matched if:
20
+ - It's an attribute with identical name and type.
21
+ - It's a relationship with identical peer kind and cardinality.
22
+ If there is no match, the mapping will only indicate whether the field is mandatory or not.
23
+ """
24
+
25
+ target_field_to_source_field = {}
26
+
27
+ # Create lookup dictionaries for source attributes and relationships
28
+ source_attrs = {attr.name: attr for attr in source_schema.attributes}
29
+ source_rels = {rel.name: rel for rel in source_schema.relationships}
30
+
31
+ # Process attributes
32
+ for target_attr in target_schema.attributes:
33
+ source_attr = source_attrs.get(target_attr.name)
34
+ if source_attr and source_attr.kind == target_attr.kind:
35
+ target_field_to_source_field[target_attr.name] = SchemaMappingValue(
36
+ source_field_name=source_attr.name, is_mandatory=not target_attr.optional
37
+ )
38
+ else:
39
+ target_field_to_source_field[target_attr.name] = SchemaMappingValue(is_mandatory=not target_attr.optional)
40
+
41
+ # Process relationships
42
+ for target_rel in target_schema.relationships:
43
+ source_rel = source_rels.get(target_rel.name)
44
+ if source_rel and source_rel.peer == target_rel.peer and source_rel.cardinality == target_rel.cardinality:
45
+ target_field_to_source_field[target_rel.name] = SchemaMappingValue(
46
+ source_field_name=source_rel.name,
47
+ is_mandatory=not target_rel.optional,
48
+ relationship_cardinality=target_rel.cardinality,
49
+ )
50
+ else:
51
+ target_field_to_source_field[target_rel.name] = SchemaMappingValue(
52
+ is_mandatory=not target_rel.optional,
53
+ relationship_cardinality=target_rel.cardinality,
54
+ )
55
+
56
+ return target_field_to_source_field
@@ -4,7 +4,10 @@ from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING, Iterable, Literal, Sequence, overload
5
5
  from uuid import uuid4
6
6
 
7
+ from prefect import flow
8
+
7
9
  from infrahub import lock
10
+ from infrahub.core.branch import Branch # noqa: TC001
8
11
  from infrahub.core.timestamp import Timestamp
9
12
  from infrahub.exceptions import ValidationError
10
13
  from infrahub.log import get_logger
@@ -22,7 +25,6 @@ from .model.path import (
22
25
  )
23
26
 
24
27
  if TYPE_CHECKING:
25
- from infrahub.core.branch import Branch
26
28
  from infrahub.core.node import Node
27
29
 
28
30
  from .calculator import DiffCalculator
@@ -301,6 +303,11 @@ class DiffCoordinator:
301
303
  force_branch_refresh: Literal[False] = ...,
302
304
  ) -> tuple[EnrichedDiffs | EnrichedDiffsMetadata, set[NodeIdentifier]]: ...
303
305
 
306
+ @flow( # type: ignore[misc]
307
+ name="update-diff",
308
+ flow_run_name="Update diff for {base_branch.name} - {diff_branch.name}: ({from_time}-{to_time}),tracking_id={tracking_id}",
309
+ validate_parameters=False,
310
+ )
304
311
  async def _update_diffs(
305
312
  self,
306
313
  base_branch: Branch,