infrahub-server 1.2.0rc0__py3-none-any.whl → 1.2.1__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 (365) hide show
  1. infrahub/api/dependencies.py +6 -6
  2. infrahub/api/diff/validation_models.py +7 -7
  3. infrahub/api/schema.py +1 -1
  4. infrahub/artifacts/models.py +5 -3
  5. infrahub/artifacts/tasks.py +3 -5
  6. infrahub/cli/__init__.py +13 -9
  7. infrahub/cli/constants.py +3 -0
  8. infrahub/cli/db.py +165 -183
  9. infrahub/cli/upgrade.py +146 -0
  10. infrahub/computed_attribute/gather.py +185 -0
  11. infrahub/computed_attribute/models.py +240 -12
  12. infrahub/computed_attribute/tasks.py +77 -441
  13. infrahub/computed_attribute/triggers.py +13 -47
  14. infrahub/config.py +43 -32
  15. infrahub/context.py +14 -0
  16. infrahub/core/account.py +4 -4
  17. infrahub/core/attribute.py +58 -58
  18. infrahub/core/branch/tasks.py +74 -22
  19. infrahub/core/changelog/diff.py +95 -36
  20. infrahub/core/changelog/models.py +217 -43
  21. infrahub/core/constants/__init__.py +28 -0
  22. infrahub/core/constants/infrahubkind.py +2 -0
  23. infrahub/core/constants/schema.py +2 -0
  24. infrahub/core/constraint/node/runner.py +9 -8
  25. infrahub/core/diff/branch_differ.py +10 -10
  26. infrahub/core/diff/enricher/cardinality_one.py +5 -0
  27. infrahub/core/diff/enricher/hierarchy.py +17 -4
  28. infrahub/core/diff/enricher/labels.py +5 -0
  29. infrahub/core/diff/enricher/path_identifier.py +4 -0
  30. infrahub/core/diff/ipam_diff_parser.py +4 -5
  31. infrahub/core/diff/model/diff.py +27 -27
  32. infrahub/core/diff/model/path.py +32 -9
  33. infrahub/core/diff/parent_node_adder.py +78 -0
  34. infrahub/core/diff/payload_builder.py +13 -2
  35. infrahub/core/diff/query/filters.py +2 -2
  36. infrahub/core/diff/query/merge.py +20 -17
  37. infrahub/core/diff/query/save.py +188 -182
  38. infrahub/core/diff/query/summary_counts_enricher.py +51 -4
  39. infrahub/core/diff/query_parser.py +4 -4
  40. infrahub/core/diff/repository/deserializer.py +8 -3
  41. infrahub/core/diff/repository/repository.py +156 -38
  42. infrahub/core/diff/tasks.py +4 -4
  43. infrahub/core/graph/__init__.py +1 -1
  44. infrahub/core/graph/index.py +3 -0
  45. infrahub/core/initialization.py +1 -10
  46. infrahub/core/ipam/constants.py +3 -4
  47. infrahub/core/ipam/reconciler.py +12 -12
  48. infrahub/core/ipam/utilization.py +10 -13
  49. infrahub/core/manager.py +36 -36
  50. infrahub/core/merge.py +7 -7
  51. infrahub/core/migrations/__init__.py +2 -3
  52. infrahub/core/migrations/graph/__init__.py +12 -3
  53. infrahub/core/migrations/graph/m017_add_core_profile.py +1 -5
  54. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +4 -4
  55. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
  56. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  57. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
  58. infrahub/core/migrations/graph/m022_add_generate_template_attr.py +48 -0
  59. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +96 -0
  60. infrahub/core/migrations/query/attribute_add.py +2 -2
  61. infrahub/core/migrations/query/node_duplicate.py +43 -26
  62. infrahub/core/migrations/query/schema_attribute_update.py +2 -2
  63. infrahub/core/migrations/schema/models.py +19 -4
  64. infrahub/core/migrations/schema/node_remove.py +26 -12
  65. infrahub/core/migrations/schema/tasks.py +2 -2
  66. infrahub/core/migrations/shared.py +16 -16
  67. infrahub/core/models.py +15 -6
  68. infrahub/core/node/__init__.py +43 -39
  69. infrahub/core/node/base.py +2 -4
  70. infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
  71. infrahub/core/node/constraints/grouped_uniqueness.py +99 -47
  72. infrahub/core/node/constraints/interface.py +1 -2
  73. infrahub/core/node/delete_validator.py +3 -5
  74. infrahub/core/node/ipam.py +4 -4
  75. infrahub/core/node/permissions.py +7 -7
  76. infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
  77. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -6
  78. infrahub/core/node/resource_manager/number_pool.py +3 -3
  79. infrahub/core/path.py +12 -12
  80. infrahub/core/property.py +11 -11
  81. infrahub/core/protocols.py +7 -0
  82. infrahub/core/protocols_base.py +21 -21
  83. infrahub/core/query/__init__.py +33 -33
  84. infrahub/core/query/attribute.py +6 -4
  85. infrahub/core/query/diff.py +3 -3
  86. infrahub/core/query/node.py +82 -32
  87. infrahub/core/query/relationship.py +228 -40
  88. infrahub/core/query/resource_manager.py +2 -0
  89. infrahub/core/query/standard_node.py +3 -3
  90. infrahub/core/query/subquery.py +9 -9
  91. infrahub/core/registry.py +13 -15
  92. infrahub/core/relationship/constraints/count.py +3 -4
  93. infrahub/core/relationship/constraints/peer_kind.py +3 -4
  94. infrahub/core/relationship/constraints/profiles_kind.py +2 -2
  95. infrahub/core/relationship/model.py +51 -59
  96. infrahub/core/schema/attribute_schema.py +16 -8
  97. infrahub/core/schema/basenode_schema.py +105 -44
  98. infrahub/core/schema/computed_attribute.py +3 -3
  99. infrahub/core/schema/definitions/core/__init__.py +147 -0
  100. infrahub/core/schema/definitions/core/account.py +171 -0
  101. infrahub/core/schema/definitions/core/artifact.py +136 -0
  102. infrahub/core/schema/definitions/core/builtin.py +24 -0
  103. infrahub/core/schema/definitions/core/check.py +68 -0
  104. infrahub/core/schema/definitions/core/core.py +17 -0
  105. infrahub/core/schema/definitions/core/generator.py +100 -0
  106. infrahub/core/schema/definitions/core/graphql_query.py +79 -0
  107. infrahub/core/schema/definitions/core/group.py +108 -0
  108. infrahub/core/schema/definitions/core/ipam.py +193 -0
  109. infrahub/core/schema/definitions/core/lineage.py +19 -0
  110. infrahub/core/schema/definitions/core/menu.py +48 -0
  111. infrahub/core/schema/definitions/core/permission.py +163 -0
  112. infrahub/core/schema/definitions/core/profile.py +18 -0
  113. infrahub/core/schema/definitions/core/propose_change.py +97 -0
  114. infrahub/core/schema/definitions/core/propose_change_comment.py +193 -0
  115. infrahub/core/schema/definitions/core/propose_change_validator.py +328 -0
  116. infrahub/core/schema/definitions/core/repository.py +286 -0
  117. infrahub/core/schema/definitions/core/resource_pool.py +170 -0
  118. infrahub/core/schema/definitions/core/template.py +27 -0
  119. infrahub/core/schema/definitions/core/transform.py +96 -0
  120. infrahub/core/schema/definitions/core/webhook.py +134 -0
  121. infrahub/core/schema/definitions/internal.py +16 -16
  122. infrahub/core/schema/dropdown.py +3 -4
  123. infrahub/core/schema/generated/attribute_schema.py +15 -18
  124. infrahub/core/schema/generated/base_node_schema.py +12 -14
  125. infrahub/core/schema/generated/node_schema.py +3 -5
  126. infrahub/core/schema/generated/relationship_schema.py +9 -11
  127. infrahub/core/schema/generic_schema.py +2 -2
  128. infrahub/core/schema/manager.py +20 -9
  129. infrahub/core/schema/node_schema.py +4 -2
  130. infrahub/core/schema/relationship_schema.py +14 -6
  131. infrahub/core/schema/schema_branch.py +292 -144
  132. infrahub/core/schema/schema_branch_computed.py +41 -4
  133. infrahub/core/task/task.py +3 -3
  134. infrahub/core/task/user_task.py +15 -15
  135. infrahub/core/timestamp.py +3 -3
  136. infrahub/core/utils.py +20 -18
  137. infrahub/core/validators/__init__.py +1 -3
  138. infrahub/core/validators/aggregated_checker.py +2 -2
  139. infrahub/core/validators/attribute/choices.py +2 -2
  140. infrahub/core/validators/attribute/enum.py +2 -2
  141. infrahub/core/validators/attribute/kind.py +2 -2
  142. infrahub/core/validators/attribute/length.py +2 -2
  143. infrahub/core/validators/attribute/optional.py +2 -2
  144. infrahub/core/validators/attribute/regex.py +2 -2
  145. infrahub/core/validators/attribute/unique.py +2 -2
  146. infrahub/core/validators/checks_runner.py +25 -2
  147. infrahub/core/validators/determiner.py +1 -3
  148. infrahub/core/validators/interface.py +6 -2
  149. infrahub/core/validators/model.py +22 -3
  150. infrahub/core/validators/models/validate_migration.py +17 -4
  151. infrahub/core/validators/node/attribute.py +2 -2
  152. infrahub/core/validators/node/generate_profile.py +2 -2
  153. infrahub/core/validators/node/hierarchy.py +3 -5
  154. infrahub/core/validators/node/inherit_from.py +27 -5
  155. infrahub/core/validators/node/relationship.py +2 -2
  156. infrahub/core/validators/relationship/count.py +4 -4
  157. infrahub/core/validators/relationship/optional.py +2 -2
  158. infrahub/core/validators/relationship/peer.py +2 -2
  159. infrahub/core/validators/shared.py +2 -2
  160. infrahub/core/validators/tasks.py +8 -0
  161. infrahub/core/validators/uniqueness/checker.py +22 -21
  162. infrahub/core/validators/uniqueness/index.py +2 -2
  163. infrahub/core/validators/uniqueness/model.py +11 -11
  164. infrahub/database/__init__.py +27 -22
  165. infrahub/database/metrics.py +7 -1
  166. infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
  167. infrahub/dependencies/builder/diff/deserializer.py +3 -1
  168. infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
  169. infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
  170. infrahub/dependencies/component/registry.py +2 -2
  171. infrahub/events/__init__.py +25 -2
  172. infrahub/events/artifact_action.py +64 -0
  173. infrahub/events/branch_action.py +33 -22
  174. infrahub/events/generator.py +71 -0
  175. infrahub/events/group_action.py +51 -21
  176. infrahub/events/models.py +18 -19
  177. infrahub/events/node_action.py +88 -37
  178. infrahub/events/repository_action.py +5 -18
  179. infrahub/events/schema_action.py +4 -9
  180. infrahub/events/utils.py +16 -0
  181. infrahub/events/validator_action.py +55 -0
  182. infrahub/exceptions.py +32 -24
  183. infrahub/generators/models.py +2 -3
  184. infrahub/generators/tasks.py +24 -4
  185. infrahub/git/base.py +7 -7
  186. infrahub/git/integrator.py +48 -24
  187. infrahub/git/models.py +101 -9
  188. infrahub/git/repository.py +3 -3
  189. infrahub/git/tasks.py +408 -6
  190. infrahub/git/utils.py +48 -0
  191. infrahub/git/worktree.py +1 -2
  192. infrahub/git_credential/askpass.py +1 -2
  193. infrahub/graphql/analyzer.py +12 -0
  194. infrahub/graphql/app.py +13 -15
  195. infrahub/graphql/context.py +39 -0
  196. infrahub/graphql/initialization.py +3 -0
  197. infrahub/graphql/loaders/node.py +2 -12
  198. infrahub/graphql/loaders/peers.py +77 -0
  199. infrahub/graphql/loaders/shared.py +13 -0
  200. infrahub/graphql/manager.py +17 -19
  201. infrahub/graphql/mutations/artifact_definition.py +5 -5
  202. infrahub/graphql/mutations/branch.py +26 -1
  203. infrahub/graphql/mutations/computed_attribute.py +9 -5
  204. infrahub/graphql/mutations/diff.py +23 -11
  205. infrahub/graphql/mutations/diff_conflict.py +5 -0
  206. infrahub/graphql/mutations/generator.py +83 -0
  207. infrahub/graphql/mutations/graphql_query.py +5 -5
  208. infrahub/graphql/mutations/ipam.py +54 -74
  209. infrahub/graphql/mutations/main.py +195 -132
  210. infrahub/graphql/mutations/menu.py +7 -7
  211. infrahub/graphql/mutations/models.py +2 -4
  212. infrahub/graphql/mutations/node_getter/by_default_filter.py +10 -10
  213. infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
  214. infrahub/graphql/mutations/node_getter/by_id.py +1 -3
  215. infrahub/graphql/mutations/node_getter/interface.py +1 -2
  216. infrahub/graphql/mutations/proposed_change.py +7 -7
  217. infrahub/graphql/mutations/relationship.py +93 -19
  218. infrahub/graphql/mutations/repository.py +8 -8
  219. infrahub/graphql/mutations/resource_manager.py +3 -3
  220. infrahub/graphql/mutations/schema.py +19 -4
  221. infrahub/graphql/mutations/webhook.py +137 -0
  222. infrahub/graphql/parser.py +4 -4
  223. infrahub/graphql/permissions.py +1 -10
  224. infrahub/graphql/queries/diff/tree.py +19 -14
  225. infrahub/graphql/queries/event.py +5 -2
  226. infrahub/graphql/queries/ipam.py +2 -2
  227. infrahub/graphql/queries/relationship.py +2 -2
  228. infrahub/graphql/queries/search.py +2 -2
  229. infrahub/graphql/resolvers/many_relationship.py +264 -0
  230. infrahub/graphql/resolvers/resolver.py +13 -110
  231. infrahub/graphql/schema.py +2 -0
  232. infrahub/graphql/subscription/graphql_query.py +2 -0
  233. infrahub/graphql/types/context.py +12 -0
  234. infrahub/graphql/types/event.py +84 -17
  235. infrahub/graphql/types/node.py +2 -2
  236. infrahub/graphql/utils.py +2 -2
  237. infrahub/groups/ancestors.py +29 -0
  238. infrahub/groups/parsers.py +107 -0
  239. infrahub/lock.py +20 -20
  240. infrahub/menu/constants.py +0 -1
  241. infrahub/menu/generator.py +9 -21
  242. infrahub/menu/menu.py +17 -38
  243. infrahub/menu/models.py +117 -16
  244. infrahub/menu/repository.py +111 -0
  245. infrahub/menu/utils.py +5 -8
  246. infrahub/message_bus/__init__.py +11 -13
  247. infrahub/message_bus/messages/__init__.py +1 -21
  248. infrahub/message_bus/messages/check_generator_run.py +3 -3
  249. infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
  250. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +6 -0
  251. infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
  252. infrahub/message_bus/messages/send_echo_request.py +1 -1
  253. infrahub/message_bus/operations/__init__.py +1 -10
  254. infrahub/message_bus/operations/check/__init__.py +2 -2
  255. infrahub/message_bus/operations/check/generator.py +1 -0
  256. infrahub/message_bus/operations/event/__init__.py +2 -2
  257. infrahub/message_bus/operations/event/worker.py +0 -3
  258. infrahub/message_bus/operations/finalize/validator.py +51 -1
  259. infrahub/message_bus/operations/requests/__init__.py +0 -2
  260. infrahub/message_bus/operations/requests/generator_definition.py +21 -23
  261. infrahub/message_bus/operations/requests/proposed_change.py +14 -10
  262. infrahub/permissions/globals.py +15 -0
  263. infrahub/pools/number.py +2 -4
  264. infrahub/proposed_change/models.py +3 -0
  265. infrahub/proposed_change/tasks.py +58 -45
  266. infrahub/pytest_plugin.py +13 -10
  267. infrahub/server.py +2 -3
  268. infrahub/services/__init__.py +2 -2
  269. infrahub/services/adapters/cache/__init__.py +4 -6
  270. infrahub/services/adapters/cache/nats.py +4 -5
  271. infrahub/services/adapters/cache/redis.py +3 -7
  272. infrahub/services/adapters/event/__init__.py +1 -1
  273. infrahub/services/adapters/message_bus/__init__.py +3 -3
  274. infrahub/services/adapters/message_bus/local.py +2 -2
  275. infrahub/services/adapters/message_bus/nats.py +4 -4
  276. infrahub/services/adapters/message_bus/rabbitmq.py +4 -4
  277. infrahub/services/adapters/workflow/local.py +2 -2
  278. infrahub/services/component.py +5 -5
  279. infrahub/services/protocols.py +7 -7
  280. infrahub/services/scheduler.py +1 -3
  281. infrahub/task_manager/event.py +102 -9
  282. infrahub/task_manager/models.py +27 -7
  283. infrahub/tasks/artifact.py +7 -6
  284. infrahub/telemetry/__init__.py +0 -0
  285. infrahub/telemetry/constants.py +9 -0
  286. infrahub/telemetry/database.py +86 -0
  287. infrahub/telemetry/models.py +65 -0
  288. infrahub/telemetry/task_manager.py +77 -0
  289. infrahub/{tasks/telemetry.py → telemetry/tasks.py} +49 -56
  290. infrahub/telemetry/utils.py +11 -0
  291. infrahub/trace.py +4 -4
  292. infrahub/transformations/tasks.py +2 -2
  293. infrahub/trigger/catalogue.py +4 -6
  294. infrahub/trigger/constants.py +0 -8
  295. infrahub/trigger/models.py +54 -5
  296. infrahub/trigger/setup.py +90 -0
  297. infrahub/trigger/tasks.py +35 -84
  298. infrahub/utils.py +11 -1
  299. infrahub/validators/__init__.py +0 -0
  300. infrahub/validators/events.py +42 -0
  301. infrahub/validators/tasks.py +41 -0
  302. infrahub/webhook/gather.py +17 -0
  303. infrahub/webhook/models.py +176 -44
  304. infrahub/webhook/tasks.py +154 -155
  305. infrahub/webhook/triggers.py +31 -7
  306. infrahub/workers/infrahub_async.py +2 -2
  307. infrahub/workers/utils.py +2 -2
  308. infrahub/workflows/catalogue.py +86 -35
  309. infrahub/workflows/initialization.py +8 -2
  310. infrahub/workflows/models.py +27 -1
  311. infrahub/workflows/utils.py +10 -1
  312. infrahub_sdk/client.py +35 -8
  313. infrahub_sdk/config.py +3 -0
  314. infrahub_sdk/context.py +13 -0
  315. infrahub_sdk/ctl/branch.py +3 -2
  316. infrahub_sdk/ctl/cli_commands.py +5 -1
  317. infrahub_sdk/ctl/utils.py +0 -16
  318. infrahub_sdk/exceptions.py +12 -0
  319. infrahub_sdk/generator.py +4 -1
  320. infrahub_sdk/graphql.py +45 -13
  321. infrahub_sdk/node.py +71 -22
  322. infrahub_sdk/protocols.py +21 -8
  323. infrahub_sdk/protocols_base.py +32 -11
  324. infrahub_sdk/query_groups.py +6 -35
  325. infrahub_sdk/schema/__init__.py +55 -26
  326. infrahub_sdk/schema/main.py +8 -0
  327. infrahub_sdk/task/__init__.py +11 -0
  328. infrahub_sdk/task/constants.py +3 -0
  329. infrahub_sdk/task/exceptions.py +25 -0
  330. infrahub_sdk/task/manager.py +551 -0
  331. infrahub_sdk/task/models.py +74 -0
  332. infrahub_sdk/testing/schemas/animal.py +9 -0
  333. infrahub_sdk/timestamp.py +142 -33
  334. infrahub_sdk/utils.py +29 -1
  335. {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +8 -6
  336. {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +349 -293
  337. {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/entry_points.txt +1 -0
  338. infrahub_testcontainers/constants.py +2 -0
  339. infrahub_testcontainers/container.py +157 -12
  340. infrahub_testcontainers/docker-compose.test.yml +31 -6
  341. infrahub_testcontainers/helpers.py +18 -73
  342. infrahub_testcontainers/host.py +41 -0
  343. infrahub_testcontainers/measurements.py +93 -0
  344. infrahub_testcontainers/models.py +38 -0
  345. infrahub_testcontainers/performance_test.py +166 -0
  346. infrahub_testcontainers/plugin.py +136 -0
  347. infrahub_testcontainers/prometheus.yml +30 -0
  348. infrahub/core/schema/definitions/core.py +0 -2286
  349. infrahub/message_bus/messages/check_repository_checkdefinition.py +0 -20
  350. infrahub/message_bus/messages/check_repository_mergeconflicts.py +0 -16
  351. infrahub/message_bus/messages/check_repository_usercheck.py +0 -26
  352. infrahub/message_bus/messages/event_branch_create.py +0 -11
  353. infrahub/message_bus/messages/event_branch_delete.py +0 -11
  354. infrahub/message_bus/messages/event_branch_rebased.py +0 -9
  355. infrahub/message_bus/messages/event_node_mutated.py +0 -15
  356. infrahub/message_bus/messages/event_schema_update.py +0 -9
  357. infrahub/message_bus/messages/request_repository_checks.py +0 -12
  358. infrahub/message_bus/messages/request_repository_userchecks.py +0 -18
  359. infrahub/message_bus/operations/check/repository.py +0 -293
  360. infrahub/message_bus/operations/event/node.py +0 -20
  361. infrahub/message_bus/operations/event/schema.py +0 -17
  362. infrahub/message_bus/operations/requests/repository.py +0 -133
  363. infrahub/webhook/constants.py +0 -1
  364. {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
  365. {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from infrahub.core.constants import GlobalPermissions, InfrahubKind
6
+ from infrahub.core.manager import NodeManager
7
+ from infrahub.exceptions import NodeNotFoundError, ValidationError
8
+ from infrahub.permissions.globals import define_global_permission_from_branch
9
+
10
+ if TYPE_CHECKING:
11
+ from .initialization import GraphqlContext
12
+ from .types.context import ContextInput
13
+
14
+
15
+ async def apply_external_context(graphql_context: GraphqlContext, context_input: ContextInput | None) -> None:
16
+ """Applies context provided by an external mutation to the GraphQL context"""
17
+ if not context_input or not context_input.account:
18
+ return
19
+
20
+ if graphql_context.active_account_session.account_id == context_input.account.id:
21
+ # If the account_id from the request context is the same as the current account
22
+ # there's no point moving forward with other checks to override the current
23
+ # context we can just continue with what is already there.
24
+ return
25
+
26
+ permission = define_global_permission_from_branch(
27
+ permission=GlobalPermissions.OVERRIDE_CONTEXT, branch_name=graphql_context.branch.name
28
+ )
29
+
30
+ graphql_context.active_permissions.raise_for_permission(permission=permission)
31
+
32
+ try:
33
+ account = await NodeManager.get_one_by_id_or_default_filter(
34
+ db=graphql_context.db, id=str(context_input.account.id), kind=InfrahubKind.GENERICACCOUNT
35
+ )
36
+ except NodeNotFoundError as exc:
37
+ raise ValidationError(input_value="Unable to set context for account that doesn't exist") from exc
38
+
39
+ graphql_context.active_account_session.account_id = account.id
@@ -9,6 +9,7 @@ from infrahub.context import InfrahubContext
9
9
  from infrahub.core import registry
10
10
  from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.exceptions import InitializationError
12
+ from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
12
13
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
13
14
  from infrahub.permissions import PermissionManager
14
15
 
@@ -36,6 +37,7 @@ class GraphqlContext:
36
37
  branch: Branch
37
38
  types: dict
38
39
  single_relationship_resolver: SingleRelationshipResolver
40
+ many_relationship_resolver: ManyRelationshipResolver
39
41
  service: InfrahubServices | None = None
40
42
  at: Timestamp | None = None
41
43
  related_node_ids: set | None = None
@@ -111,6 +113,7 @@ async def prepare_graphql_params(
111
113
  db=db,
112
114
  branch=branch,
113
115
  single_relationship_resolver=SingleRelationshipResolver(),
116
+ many_relationship_resolver=ManyRelationshipResolver(),
114
117
  at=Timestamp(at),
115
118
  types=gqlm.get_graphql_types(),
116
119
  related_node_ids=set(),
@@ -10,6 +10,8 @@ from infrahub.core.node import Node
10
10
  from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.database import InfrahubDatabase
12
12
 
13
+ from .shared import to_frozen_set
14
+
13
15
 
14
16
  @dataclass
15
17
  class GetManyParams:
@@ -68,15 +70,3 @@ class NodeDataLoader(DataLoader[str, Node | None]):
68
70
  for node_id in keys:
69
71
  results.append(nodes_by_id.get(node_id, None))
70
72
  return results
71
-
72
-
73
- def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
74
- freezing_dict = {}
75
- for k, v in to_freeze.items():
76
- if isinstance(v, dict):
77
- freezing_dict[k] = to_frozen_set(v)
78
- elif isinstance(v, list | set):
79
- freezing_dict[k] = frozenset(v)
80
- else:
81
- freezing_dict[k] = v
82
- return frozenset(freezing_dict)
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from aiodataloader import DataLoader
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.manager import NodeManager
8
+ from infrahub.core.relationship.model import Relationship
9
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
10
+ from infrahub.core.timestamp import Timestamp
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+ from .shared import to_frozen_set
14
+
15
+
16
+ @dataclass
17
+ class QueryPeerParams:
18
+ branch: Branch | str
19
+ source_kind: str
20
+ schema: RelationshipSchema
21
+ filters: dict[str, Any]
22
+ fields: dict | None = None
23
+ at: Timestamp | str | None = None
24
+ branch_agnostic: bool = False
25
+
26
+ def __hash__(self) -> int:
27
+ frozen_fields: frozenset | None = None
28
+ if self.fields:
29
+ frozen_fields = to_frozen_set(self.fields)
30
+ frozen_filters = to_frozen_set(self.filters)
31
+ timestamp = Timestamp(self.at)
32
+ branch = self.branch.name if isinstance(self.branch, Branch) else self.branch
33
+ hash_str = "|".join(
34
+ [
35
+ str(hash(frozen_fields)),
36
+ str(hash(frozen_filters)),
37
+ timestamp.to_string(),
38
+ branch,
39
+ self.schema.name,
40
+ str(self.source_kind),
41
+ str(self.branch_agnostic),
42
+ ]
43
+ )
44
+ return hash(hash_str)
45
+
46
+
47
+ class PeerRelationshipsDataLoader(DataLoader[str, list[Relationship]]):
48
+ def __init__(self, db: InfrahubDatabase, query_params: QueryPeerParams, *args: Any, **kwargs: Any) -> None:
49
+ super().__init__(*args, **kwargs)
50
+ self.query_params = query_params
51
+ self.db = db
52
+
53
+ async def batch_load_fn(self, keys: list[Any]) -> list[list[Relationship]]: # pylint: disable=method-hidden
54
+ async with self.db.start_session() as db:
55
+ peer_rels = await NodeManager.query_peers(
56
+ db=db,
57
+ ids=keys,
58
+ source_kind=self.query_params.source_kind,
59
+ schema=self.query_params.schema,
60
+ filters=self.query_params.filters,
61
+ fields=self.query_params.fields,
62
+ at=self.query_params.at,
63
+ branch=self.query_params.branch,
64
+ branch_agnostic=self.query_params.branch_agnostic,
65
+ fetch_peers=True,
66
+ )
67
+ peer_rels_by_node_id: dict[str, list[Relationship]] = {}
68
+ for rel in peer_rels:
69
+ node_id = rel.node_id
70
+ if node_id not in peer_rels_by_node_id:
71
+ peer_rels_by_node_id[node_id] = []
72
+ peer_rels_by_node_id[node_id].append(rel)
73
+
74
+ results = []
75
+ for node_id in keys:
76
+ results.append(peer_rels_by_node_id.get(node_id, []))
77
+ return results
@@ -0,0 +1,13 @@
1
+ from typing import Any
2
+
3
+
4
+ def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
5
+ freezing_dict = {}
6
+ for k, v in to_freeze.items():
7
+ if isinstance(v, dict):
8
+ freezing_dict[k] = to_frozen_set(v)
9
+ elif isinstance(v, list | set):
10
+ freezing_dict[k] = frozenset(v)
11
+ else:
12
+ freezing_dict[k] = v
13
+ return frozenset(freezing_dict)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Iterable
5
5
 
6
6
  import graphene
7
7
 
@@ -38,6 +38,7 @@ from .mutations.repository import InfrahubRepositoryMutation
38
38
  from .mutations.resource_manager import (
39
39
  InfrahubNumberPoolMutation,
40
40
  )
41
+ from .mutations.webhook import InfrahubWebhookMutation
41
42
  from .resolvers.resolver import (
42
43
  account_resolver,
43
44
  ancestors_resolver,
@@ -60,6 +61,7 @@ from .types import (
60
61
  )
61
62
  from .types.attribute import BaseAttribute as BaseAttributeType
62
63
  from .types.attribute import TextAttributeType
64
+ from .types.context import ContextInput
63
65
  from .types.event import EVENT_TYPES
64
66
 
65
67
  if TYPE_CHECKING:
@@ -74,9 +76,7 @@ class DeleteInput(graphene.InputObjectType):
74
76
  hfid = graphene.List(of_type=graphene.String, required=False)
75
77
 
76
78
 
77
- GraphQLTypes = Union[
78
- type[InfrahubMutation], type[BaseAttributeType], type[graphene.Interface], type[graphene.ObjectType]
79
- ]
79
+ GraphQLTypes = type[InfrahubMutation] | type[BaseAttributeType] | type[graphene.Interface] | type[graphene.ObjectType]
80
80
 
81
81
 
82
82
  class OrderInput(graphene.InputObjectType):
@@ -512,6 +512,8 @@ class GraphQLSchemaManager:
512
512
  InfrahubKind.NAMESPACE: InfrahubIPNamespaceMutation,
513
513
  InfrahubKind.NUMBERPOOL: InfrahubNumberPoolMutation,
514
514
  InfrahubKind.MENUITEM: InfrahubCoreMenuMutation,
515
+ InfrahubKind.STANDARDWEBHOOK: InfrahubWebhookMutation,
516
+ InfrahubKind.CUSTOMWEBHOOK: InfrahubWebhookMutation,
515
517
  }
516
518
 
517
519
  if isinstance(node_schema, NodeSchema) and node_schema.is_ip_prefix():
@@ -642,7 +644,9 @@ class GraphQLSchemaManager:
642
644
  self.set_type(name=type_name, graphql_type=relationship_property)
643
645
 
644
646
  def generate_graphql_mutations(
645
- self, schema: NodeSchema | ProfileSchema | TemplateSchema, base_class: type[InfrahubMutation]
647
+ self,
648
+ schema: NodeSchema | ProfileSchema | TemplateSchema,
649
+ base_class: type[InfrahubMutation],
646
650
  ) -> GraphqlMutations:
647
651
  graphql_mutation_create_input = self.generate_graphql_mutation_create_input(schema)
648
652
  graphql_mutation_update_input = self.generate_graphql_mutation_update_input(schema)
@@ -681,7 +685,7 @@ class GraphQLSchemaManager:
681
685
  slug = InputField(StringAttributeCreate, required=True)
682
686
  description = InputField(StringAttributeCreate, required=False)
683
687
  """
684
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {"id": graphene.String(required=False)}
688
+ attrs: dict[str, graphene.String | graphene.InputField] = {"id": graphene.String(required=False)}
685
689
 
686
690
  for attr in schema.attributes:
687
691
  if attr.read_only:
@@ -717,7 +721,7 @@ class GraphQLSchemaManager:
717
721
  slug = InputField(StringAttributeUpdate, required=False)
718
722
  description = InputField(StringAttributeUpdate, required=False)
719
723
  """
720
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {
724
+ attrs: dict[str, graphene.String | graphene.InputField] = {
721
725
  "id": graphene.String(required=False),
722
726
  "hfid": graphene.List(of_type=graphene.String, required=False),
723
727
  }
@@ -758,7 +762,7 @@ class GraphQLSchemaManager:
758
762
  slug = InputField(StringAttributeUpdate, required=True)
759
763
  description = InputField(StringAttributeUpdate, required=False)
760
764
  """
761
- attrs: dict[str, Union[graphene.String, graphene.InputField]] = {
765
+ attrs: dict[str, graphene.String | graphene.InputField] = {
762
766
  "id": graphene.String(required=False),
763
767
  "hfid": graphene.List(of_type=graphene.String, required=False),
764
768
  }
@@ -809,9 +813,7 @@ class GraphQLSchemaManager:
809
813
  meta_attrs: dict[str, Any] = {"schema": schema, "name": name, "description": schema.description}
810
814
  main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
811
815
 
812
- args_attrs = {
813
- "data": input_type(required=True),
814
- }
816
+ args_attrs = {"data": input_type(required=True), "context": ContextInput(required=False)}
815
817
  main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
816
818
 
817
819
  return type(name, (base_class,), main_attrs)
@@ -832,9 +834,7 @@ class GraphQLSchemaManager:
832
834
  meta_attrs: dict[str, Any] = {"schema": schema, "name": name, "description": schema.description}
833
835
  main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
834
836
 
835
- args_attrs = {
836
- "data": input_type(required=True),
837
- }
837
+ args_attrs = {"data": input_type(required=True), "context": ContextInput(required=False)}
838
838
  main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
839
839
 
840
840
  return type(name, (base_class,), main_attrs)
@@ -851,16 +851,14 @@ class GraphQLSchemaManager:
851
851
  meta_attrs = {"schema": schema, "name": name, "description": schema.description}
852
852
  main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
853
853
 
854
- args_attrs: dict[str, Any] = {
855
- "data": DeleteInput(required=True),
856
- }
854
+ args_attrs: dict[str, Any] = {"data": DeleteInput(required=True), "context": ContextInput(required=False)}
857
855
  main_attrs["Arguments"] = type("Arguments", (object,), args_attrs)
858
856
 
859
857
  return type(name, (base_class,), main_attrs)
860
858
 
861
859
  def generate_filters(
862
860
  self, schema: MainSchemaTypes, top_level: bool = False, include_properties: bool = True
863
- ) -> dict[str, Union[graphene.Scalar, graphene.List]]:
861
+ ) -> dict[str, graphene.Scalar | graphene.List]:
864
862
  """Generate the GraphQL filters for a given Schema object.
865
863
 
866
864
  The generated filter will be different if we are at the top_level (query)
@@ -925,7 +923,7 @@ class GraphQLSchemaManager:
925
923
  self,
926
924
  schema: MainSchemaTypes,
927
925
  node: type[InfrahubObject],
928
- relation_property: Optional[type[InfrahubObject]] = None,
926
+ relation_property: type[InfrahubObject] | None = None,
929
927
  populate_cache: bool = False,
930
928
  ) -> type[InfrahubObject]:
931
929
  """Generate a edged GraphQL object Type from a Infrahub NodeSchema for pagination."""
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Optional
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import InputObjectType, Mutation
6
6
  from typing_extensions import Self
@@ -28,7 +28,7 @@ class InfrahubArtifactDefinitionMutation(InfrahubMutationMixin, Mutation):
28
28
  def __init_subclass_with_meta__(
29
29
  cls,
30
30
  schema: NodeSchema,
31
- _meta: Optional[Any] = None,
31
+ _meta: Any | None = None,
32
32
  **options: dict[str, Any],
33
33
  ) -> None:
34
34
  # Make sure schema is a valid NodeSchema Node Class
@@ -48,7 +48,7 @@ class InfrahubArtifactDefinitionMutation(InfrahubMutationMixin, Mutation):
48
48
  info: GraphQLResolveInfo,
49
49
  data: InputObjectType,
50
50
  branch: Branch,
51
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
51
+ database: InfrahubDatabase | None = None, # noqa: ARG003
52
52
  ) -> tuple[Node, Self]:
53
53
  graphql_context: GraphqlContext = info.context
54
54
 
@@ -74,8 +74,8 @@ class InfrahubArtifactDefinitionMutation(InfrahubMutationMixin, Mutation):
74
74
  info: GraphQLResolveInfo,
75
75
  data: InputObjectType,
76
76
  branch: Branch,
77
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
78
- node: Optional[Node] = None, # noqa: ARG003
77
+ database: InfrahubDatabase | None = None, # noqa: ARG003
78
+ node: Node | None = None, # noqa: ARG003
79
79
  ) -> tuple[Node, Self]:
80
80
  graphql_context: GraphqlContext = info.context
81
81
 
@@ -9,6 +9,8 @@ from typing_extensions import Self
9
9
 
10
10
  from infrahub.core.branch import Branch
11
11
  from infrahub.database import retry_db_transaction
12
+ from infrahub.graphql.context import apply_external_context
13
+ from infrahub.graphql.types.context import ContextInput
12
14
  from infrahub.log import get_logger
13
15
  from infrahub.workflows.catalogue import (
14
16
  BRANCH_CREATE,
@@ -44,6 +46,7 @@ class BranchCreateInput(InputObjectType):
44
46
  class BranchCreate(Mutation):
45
47
  class Arguments:
46
48
  data = BranchCreateInput(required=True)
49
+ context = ContextInput(required=False)
47
50
  background_execution = Boolean(required=False, deprecation_reason="Please use `wait_until_completion` instead")
48
51
  wait_until_completion = Boolean(required=False)
49
52
 
@@ -58,6 +61,7 @@ class BranchCreate(Mutation):
58
61
  root: dict, # noqa: ARG003
59
62
  info: GraphQLResolveInfo,
60
63
  data: BranchCreateInput,
64
+ context: ContextInput | None = None,
61
65
  background_execution: bool = False,
62
66
  wait_until_completion: bool = True,
63
67
  ) -> Self:
@@ -65,6 +69,7 @@ class BranchCreate(Mutation):
65
69
  task: dict | None = None
66
70
 
67
71
  model = BranchCreateModel(**data)
72
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
68
73
 
69
74
  if background_execution or not wait_until_completion:
70
75
  workflow = await graphql_context.active_service.workflow.submit_workflow(
@@ -96,6 +101,7 @@ class BranchUpdateInput(InputObjectType):
96
101
  class BranchDelete(Mutation):
97
102
  class Arguments:
98
103
  data = BranchNameInput(required=True)
104
+ context = ContextInput(required=False)
99
105
  wait_until_completion = Boolean(required=False)
100
106
 
101
107
  ok = Boolean()
@@ -107,10 +113,12 @@ class BranchDelete(Mutation):
107
113
  root: dict, # noqa: ARG003
108
114
  info: GraphQLResolveInfo,
109
115
  data: BranchNameInput,
116
+ context: ContextInput | None = None,
110
117
  wait_until_completion: bool = True,
111
118
  ) -> Self:
112
119
  graphql_context: GraphqlContext = info.context
113
120
  obj = await Branch.get_by_name(db=graphql_context.db, name=str(data.name))
121
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
114
122
 
115
123
  if wait_until_completion:
116
124
  await graphql_context.active_service.workflow.execute_workflow(
@@ -127,15 +135,23 @@ class BranchDelete(Mutation):
127
135
  class BranchUpdate(Mutation):
128
136
  class Arguments:
129
137
  data = BranchUpdateInput(required=True)
138
+ context = ContextInput(required=False)
130
139
 
131
140
  ok = Boolean()
132
141
 
133
142
  @classmethod
134
143
  @retry_db_transaction(name="branch_update")
135
- async def mutate(cls, root: dict, info: GraphQLResolveInfo, data: BranchNameInput) -> Self: # noqa: ARG003
144
+ async def mutate(
145
+ cls,
146
+ root: dict, # noqa: ARG003
147
+ info: GraphQLResolveInfo,
148
+ data: BranchNameInput,
149
+ context: ContextInput | None = None,
150
+ ) -> Self:
136
151
  graphql_context: GraphqlContext = info.context
137
152
 
138
153
  obj = await Branch.get_by_name(db=graphql_context.db, name=data["name"])
154
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
139
155
 
140
156
  to_extract = ["description"]
141
157
  for field_name in to_extract:
@@ -151,6 +167,7 @@ class BranchUpdate(Mutation):
151
167
  class BranchRebase(Mutation):
152
168
  class Arguments:
153
169
  data = BranchNameInput(required=True)
170
+ context = ContextInput(required=False)
154
171
  wait_until_completion = Boolean(required=False)
155
172
 
156
173
  ok = Boolean()
@@ -163,11 +180,13 @@ class BranchRebase(Mutation):
163
180
  root: dict, # noqa: ARG003
164
181
  info: GraphQLResolveInfo,
165
182
  data: BranchNameInput,
183
+ context: ContextInput | None = None,
166
184
  wait_until_completion: bool = True,
167
185
  ) -> Self:
168
186
  graphql_context: GraphqlContext = info.context
169
187
 
170
188
  obj = await Branch.get_by_name(db=graphql_context.db, name=str(data.name))
189
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
171
190
  task: dict | None = None
172
191
 
173
192
  if wait_until_completion:
@@ -192,6 +211,7 @@ class BranchRebase(Mutation):
192
211
  class BranchValidate(Mutation):
193
212
  class Arguments:
194
213
  data = BranchNameInput(required=True)
214
+ context = ContextInput(required=False)
195
215
  wait_until_completion = Boolean(required=False)
196
216
 
197
217
  ok = Boolean()
@@ -205,11 +225,13 @@ class BranchValidate(Mutation):
205
225
  root: dict, # noqa: ARG003
206
226
  info: GraphQLResolveInfo,
207
227
  data: BranchNameInput,
228
+ context: ContextInput | None = None,
208
229
  wait_until_completion: bool = True,
209
230
  ) -> Self:
210
231
  graphql_context: GraphqlContext = info.context
211
232
 
212
233
  obj = await Branch.get_by_name(db=graphql_context.db, name=str(data.name))
234
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
213
235
  task: dict | None = None
214
236
  ok = True
215
237
 
@@ -231,6 +253,7 @@ class BranchValidate(Mutation):
231
253
  class BranchMerge(Mutation):
232
254
  class Arguments:
233
255
  data = BranchNameInput(required=True)
256
+ context = ContextInput(required=False)
234
257
  wait_until_completion = Boolean(required=False)
235
258
 
236
259
  ok = Boolean()
@@ -243,11 +266,13 @@ class BranchMerge(Mutation):
243
266
  root: dict, # noqa: ARG003
244
267
  info: GraphQLResolveInfo,
245
268
  data: BranchNameInput,
269
+ context: ContextInput | None = None,
246
270
  wait_until_completion: bool = True,
247
271
  ) -> Self:
248
272
  branch_name = data["name"]
249
273
  task: dict | None = None
250
274
  graphql_context: GraphqlContext = info.context
275
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
251
276
 
252
277
  if wait_until_completion:
253
278
  await graphql_context.active_service.workflow.execute_workflow(
@@ -5,13 +5,15 @@ from typing import TYPE_CHECKING, Any
5
5
  from graphene import Boolean, InputObjectType, Mutation, String
6
6
 
7
7
  from infrahub.core.account import ObjectPermission
8
- from infrahub.core.constants import ComputedAttributeKind, MutationAction, PermissionAction, PermissionDecision
8
+ from infrahub.core.constants import ComputedAttributeKind, PermissionAction, PermissionDecision
9
9
  from infrahub.core.manager import NodeManager
10
10
  from infrahub.core.registry import registry
11
11
  from infrahub.database import retry_db_transaction
12
12
  from infrahub.events import EventMeta
13
- from infrahub.events.node_action import NodeMutatedEvent
13
+ from infrahub.events.node_action import NodeUpdatedEvent
14
14
  from infrahub.exceptions import NodeNotFoundError, ValidationError
15
+ from infrahub.graphql.context import apply_external_context
16
+ from infrahub.graphql.types.context import ContextInput
15
17
  from infrahub.log import get_log_data
16
18
  from infrahub.worker import WORKER_IDENTITY
17
19
 
@@ -31,6 +33,7 @@ class InfrahubComputedAttributeUpdateInput(InputObjectType):
31
33
  class UpdateComputedAttribute(Mutation):
32
34
  class Arguments:
33
35
  data = InfrahubComputedAttributeUpdateInput(required=True)
36
+ context = ContextInput(required=False)
34
37
 
35
38
  ok = Boolean()
36
39
 
@@ -41,6 +44,7 @@ class UpdateComputedAttribute(Mutation):
41
44
  _: dict,
42
45
  info: GraphQLResolveInfo,
43
46
  data: InfrahubComputedAttributeUpdateInput,
47
+ context: ContextInput | None = None,
44
48
  ) -> UpdateComputedAttribute:
45
49
  graphql_context: GraphqlContext = info.context
46
50
  node_schema = registry.schema.get_node_schema(
@@ -63,6 +67,7 @@ class UpdateComputedAttribute(Mutation):
63
67
  else PermissionDecision.ALLOW_OTHER.value,
64
68
  )
65
69
  )
70
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
66
71
 
67
72
  if not (
68
73
  target_node := await NodeManager.get_one(
@@ -90,12 +95,11 @@ class UpdateComputedAttribute(Mutation):
90
95
  log_data = get_log_data()
91
96
  request_id = log_data.get("request_id", "")
92
97
 
93
- event = NodeMutatedEvent(
98
+ event = NodeUpdatedEvent(
94
99
  kind=node_schema.kind,
95
100
  node_id=target_node.get_id(),
96
- data=target_node.node_changelog.model_dump(),
101
+ changelog=target_node.node_changelog.model_dump(),
97
102
  fields=[str(data.attribute)],
98
- action=MutationAction.UPDATED,
99
103
  meta=EventMeta(
100
104
  context=graphql_context.get_context(),
101
105
  initiator_id=WORKER_IDENTITY,
@@ -1,6 +1,6 @@
1
1
  from typing import TYPE_CHECKING
2
2
 
3
- from graphene import Boolean, DateTime, InputObjectType, Mutation, String
3
+ from graphene import Boolean, DateTime, Field, InputObjectType, Mutation, String
4
4
  from graphql import GraphQLResolveInfo
5
5
 
6
6
  from infrahub.core import registry
@@ -9,11 +9,14 @@ from infrahub.core.diff.model.path import NameTrackingId
9
9
  from infrahub.core.diff.models import RequestDiffUpdate
10
10
  from infrahub.core.diff.repository.repository import DiffRepository
11
11
  from infrahub.core.timestamp import Timestamp
12
- from infrahub.database import retry_db_transaction
13
12
  from infrahub.dependencies.registry import get_component_registry
14
13
  from infrahub.exceptions import ValidationError
14
+ from infrahub.graphql.context import apply_external_context
15
+ from infrahub.graphql.types.context import ContextInput
15
16
  from infrahub.workflows.catalogue import DIFF_UPDATE
16
17
 
18
+ from ..types.task import TaskInfo
19
+
17
20
  if TYPE_CHECKING:
18
21
  from ..initialization import GraphqlContext
19
22
 
@@ -23,24 +26,32 @@ class DiffUpdateInput(InputObjectType):
23
26
  name = String(required=False)
24
27
  from_time = DateTime(required=False)
25
28
  to_time = DateTime(required=False)
26
- wait_for_completion = Boolean(required=False)
29
+ wait_for_completion = Boolean(required=False, deprecation_reason="Please use `wait_until_completion` instead")
27
30
 
28
31
 
29
32
  class DiffUpdateMutation(Mutation):
30
33
  class Arguments:
31
34
  data = DiffUpdateInput(required=True)
35
+ context = ContextInput(required=False)
36
+ wait_until_completion = Boolean(required=False)
32
37
 
33
38
  ok = Boolean()
39
+ task = Field(TaskInfo, required=False)
34
40
 
35
41
  @classmethod
36
- @retry_db_transaction(name="diff_update")
37
42
  async def mutate(
38
43
  cls,
39
44
  root: dict, # noqa: ARG003
40
45
  info: GraphQLResolveInfo,
41
46
  data: DiffUpdateInput,
42
- ) -> dict[str, bool]:
47
+ context: ContextInput | None = None,
48
+ wait_until_completion: bool = False,
49
+ ) -> dict[str, bool | dict[str, str]]:
43
50
  graphql_context: GraphqlContext = info.context
51
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
52
+
53
+ if data.wait_for_completion is True:
54
+ wait_until_completion = True
44
55
 
45
56
  from_timestamp_str = DateTime.serialize(data.from_time) if data.from_time else None
46
57
  to_timestamp_str = DateTime.serialize(data.to_time) if data.to_time else None
@@ -55,11 +66,11 @@ class DiffUpdateMutation(Mutation):
55
66
  )
56
67
 
57
68
  tracking_id = NameTrackingId(name=data.name)
58
- existing_diffs_metatdatas = await diff_repository.get_roots_metadata(
69
+ existing_diffs_metadatas = await diff_repository.get_roots_metadata(
59
70
  diff_branch_names=[diff_branch.name], base_branch_names=[base_branch.name], tracking_id=tracking_id
60
71
  )
61
- if existing_diffs_metatdatas:
62
- metadata = existing_diffs_metatdatas[0]
72
+ if existing_diffs_metadatas:
73
+ metadata = existing_diffs_metadatas[0]
63
74
  from_time = Timestamp(from_timestamp_str) if from_timestamp_str else None
64
75
  to_time = Timestamp(to_timestamp_str) if to_timestamp_str else None
65
76
  branched_from_timestamp = Timestamp(diff_branch.get_branched_from())
@@ -70,7 +81,7 @@ class DiffUpdateMutation(Mutation):
70
81
  if to_time and to_time < metadata.to_time:
71
82
  raise ValidationError(f"to_time must be null or greater than or equal to {metadata.to_time}")
72
83
 
73
- if data.wait_for_completion is True:
84
+ if wait_until_completion is True:
74
85
  diff_coordinator = await component_registry.get_component(
75
86
  DiffCoordinator, db=graphql_context.db, branch=diff_branch
76
87
  )
@@ -91,8 +102,9 @@ class DiffUpdateMutation(Mutation):
91
102
  to_time=to_timestamp_str,
92
103
  )
93
104
  if graphql_context.service:
94
- await graphql_context.service.workflow.submit_workflow(
95
- workflow=DIFF_UPDATE, context=graphql_context.get_context(), parameters={"model": model}
105
+ workflow = await graphql_context.service.workflow.submit_workflow(
106
+ workflow=DIFF_UPDATE, parameters={"model": model}
96
107
  )
108
+ return {"ok": True, "task": {"id": str(workflow.id)}}
97
109
 
98
110
  return {"ok": True}
@@ -11,7 +11,9 @@ from infrahub.core.manager import NodeManager
11
11
  from infrahub.database import retry_db_transaction
12
12
  from infrahub.dependencies.registry import get_component_registry
13
13
  from infrahub.exceptions import ProcessingError
14
+ from infrahub.graphql.context import apply_external_context
14
15
  from infrahub.graphql.enums import ConflictSelection as GraphQlConflictSelection
16
+ from infrahub.graphql.types.context import ContextInput
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from graphql import GraphQLResolveInfo
@@ -29,6 +31,7 @@ class ResolveDiffConflictInput(InputObjectType):
29
31
  class ResolveDiffConflict(Mutation):
30
32
  class Arguments:
31
33
  data = ResolveDiffConflictInput(required=True)
34
+ context = ContextInput(required=False)
32
35
 
33
36
  ok = Boolean()
34
37
 
@@ -39,8 +42,10 @@ class ResolveDiffConflict(Mutation):
39
42
  root: dict, # noqa: ARG003
40
43
  info: GraphQLResolveInfo,
41
44
  data: ResolveDiffConflictInput,
45
+ context: ContextInput | None = None,
42
46
  ) -> ResolveDiffConflict:
43
47
  graphql_context: GraphqlContext = info.context
48
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
44
49
 
45
50
  component_registry = get_component_registry()
46
51
  diff_repo = await component_registry.get_component(