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
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Mapping, Optional, Union
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Any, Mapping
4
5
 
5
6
  from graphene import InputObjectType, Mutation
6
7
  from graphene.types.mutation import MutationOptions
@@ -9,27 +10,24 @@ from typing_extensions import Self
9
10
 
10
11
  from infrahub import config, lock
11
12
  from infrahub.core import registry
12
- from infrahub.core.changelog.models import RelationshipChangelogGetter
13
13
  from infrahub.core.constants import InfrahubKind, MutationAction, RelationshipCardinality, RelationshipKind
14
14
  from infrahub.core.constraint.node.runner import NodeConstraintRunner
15
15
  from infrahub.core.manager import NodeManager
16
16
  from infrahub.core.node import Node
17
- from infrahub.core.schema import NodeSchema, RelationshipSchema
17
+ from infrahub.core.schema import MainSchemaTypes, NodeSchema, RelationshipSchema
18
18
  from infrahub.core.schema.generic_schema import GenericSchema
19
19
  from infrahub.core.schema.profile_schema import ProfileSchema
20
20
  from infrahub.core.schema.template_schema import TemplateSchema
21
21
  from infrahub.core.timestamp import Timestamp
22
22
  from infrahub.database import retry_db_transaction
23
23
  from infrahub.dependencies.registry import get_component_registry
24
- from infrahub.events import EventMeta, NodeMutatedEvent
25
- from infrahub.exceptions import ValidationError
24
+ from infrahub.events.generator import generate_node_mutation_events
25
+ from infrahub.exceptions import HFIDViolatedError, InitializationError
26
+ from infrahub.graphql.context import apply_external_context
26
27
  from infrahub.lock import InfrahubMultiLock, build_object_lock_name
27
28
  from infrahub.log import get_log_data, get_logger
28
- from infrahub.worker import WORKER_IDENTITY
29
29
 
30
30
  from .node_getter.by_default_filter import MutationNodeGetterByDefaultFilter
31
- from .node_getter.by_hfid import MutationNodeGetterByHfid
32
- from .node_getter.by_id import MutationNodeGetterById
33
31
 
34
32
  if TYPE_CHECKING:
35
33
  from graphql import GraphQLResolveInfo
@@ -39,9 +37,9 @@ if TYPE_CHECKING:
39
37
  from infrahub.core.relationship.model import RelationshipManager
40
38
  from infrahub.core.schema.schema_branch import SchemaBranch
41
39
  from infrahub.database import InfrahubDatabase
40
+ from infrahub.graphql.types.context import ContextInput
42
41
 
43
42
  from ..initialization import GraphqlContext
44
- from .node_getter.interface import MutationNodeGetterInterface
45
43
 
46
44
 
47
45
  log = get_logger()
@@ -49,44 +47,74 @@ log = get_logger()
49
47
  KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED = [InfrahubKind.GENERICGROUP]
50
48
 
51
49
 
50
+ @dataclass
51
+ class DeleteResult:
52
+ node: Node
53
+ mutation: InfrahubMutationMixin
54
+ deleted_nodes: list[Node] = field(default_factory=list)
55
+
56
+
52
57
  # ------------------------------------------
53
58
  # Infrahub GraphQLType
54
59
  # ------------------------------------------
55
60
  class InfrahubMutationOptions(MutationOptions):
56
- schema: Optional[NodeSchema] = None
61
+ schema: MainSchemaTypes | None = None
62
+
63
+ @property
64
+ def active_schema(self) -> MainSchemaTypes:
65
+ if self.schema:
66
+ return self.schema
67
+ raise InitializationError("This class is not initialized with a schema")
57
68
 
58
69
 
59
70
  class InfrahubMutationMixin:
71
+ _meta: InfrahubMutationOptions
72
+
60
73
  @classmethod
61
- async def mutate(cls, root: dict, info: GraphQLResolveInfo, data: InputObjectType, *args: Any, **kwargs): # noqa: ARG003
74
+ async def mutate(
75
+ cls,
76
+ root: dict, # noqa: ARG003
77
+ info: GraphQLResolveInfo,
78
+ data: InputObjectType,
79
+ context: ContextInput | None = None,
80
+ **kwargs: dict[str, Any],
81
+ ) -> Self:
62
82
  graphql_context: GraphqlContext = info.context
83
+ await apply_external_context(graphql_context=graphql_context, context_input=context)
63
84
 
64
85
  obj = None
65
86
  mutation = None
66
87
  action = MutationAction.UNDEFINED
88
+ deleted_nodes: list[Node] = []
67
89
 
68
90
  if "Create" in cls.__name__:
69
- obj, mutation = await cls.mutate_create(info=info, branch=graphql_context.branch, data=data, **kwargs)
91
+ obj, mutation = await cls.mutate_create(info=info, branch=graphql_context.branch, data=data)
70
92
  action = MutationAction.CREATED
71
93
  elif "Update" in cls.__name__:
72
94
  obj, mutation = await cls.mutate_update(info=info, branch=graphql_context.branch, data=data, **kwargs)
73
95
  action = MutationAction.UPDATED
74
96
  elif "Upsert" in cls.__name__:
75
97
  node_manager = NodeManager()
76
- node_getters = [
77
- MutationNodeGetterById(db=graphql_context.db, node_manager=node_manager),
78
- MutationNodeGetterByHfid(db=graphql_context.db, node_manager=node_manager),
79
- MutationNodeGetterByDefaultFilter(db=graphql_context.db, node_manager=node_manager),
80
- ]
98
+ node_getter_default_filter = MutationNodeGetterByDefaultFilter(
99
+ db=graphql_context.db, node_manager=node_manager
100
+ )
81
101
  obj, mutation, created = await cls.mutate_upsert(
82
- info=info, branch=graphql_context.branch, data=data, node_getters=node_getters, **kwargs
102
+ info=info,
103
+ branch=graphql_context.branch,
104
+ data=data,
105
+ node_getter_default_filter=node_getter_default_filter,
106
+ **kwargs,
83
107
  )
84
108
  if created:
85
109
  action = MutationAction.CREATED
86
110
  else:
87
111
  action = MutationAction.UPDATED
88
112
  elif "Delete" in cls.__name__:
89
- obj, mutation = await cls.mutate_delete(info=info, branch=graphql_context.branch, data=data, **kwargs)
113
+ delete_result = await cls.mutate_delete(info=info, branch=graphql_context.branch, data=data, **kwargs)
114
+ obj = delete_result.node
115
+ mutation = delete_result.mutation
116
+ deleted_nodes = delete_result.deleted_nodes
117
+
90
118
  action = MutationAction.DELETED
91
119
  else:
92
120
  raise ValueError(
@@ -100,41 +128,15 @@ class InfrahubMutationMixin:
100
128
  log_data = get_log_data()
101
129
  request_id = log_data.get("request_id", "")
102
130
 
103
- account_id: str | None = None
104
- if graphql_context.account_session:
105
- account_id = graphql_context.account_session.account_id
106
-
107
- meta = EventMeta(
108
- account_id=account_id,
109
- initiator_id=WORKER_IDENTITY,
110
- request_id=request_id,
131
+ events = await generate_node_mutation_events(
132
+ node=obj,
133
+ deleted_nodes=deleted_nodes,
134
+ db=graphql_context.db,
111
135
  branch=graphql_context.branch,
112
136
  context=graphql_context.get_context(),
113
- )
114
- main_event = NodeMutatedEvent(
115
- kind=obj._schema.kind,
116
- node_id=obj.id,
117
- data=obj.node_changelog,
137
+ request_id=request_id,
118
138
  action=action,
119
- fields=_get_data_fields(data),
120
- meta=meta,
121
139
  )
122
- relationship_changelogs = RelationshipChangelogGetter(db=graphql_context.db, branch=graphql_context.branch)
123
- node_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=obj.node_changelog)
124
-
125
- events = [main_event]
126
-
127
- for node_changelog in node_changelogs:
128
- meta = EventMeta.from_parent(parent=main_event)
129
- event = NodeMutatedEvent(
130
- kind=node_changelog.node_kind,
131
- node_id=node_changelog.node_id,
132
- data=node_changelog,
133
- action=MutationAction.UPDATED,
134
- fields=node_changelog.updated_fields,
135
- meta=meta,
136
- )
137
- events.append(event)
138
140
 
139
141
  for event in events:
140
142
  graphql_context.background.add_task(graphql_context.active_service.event.send, event)
@@ -150,7 +152,7 @@ class InfrahubMutationMixin:
150
152
 
151
153
  @classmethod
152
154
  async def _refresh_for_profile_update(
153
- cls, db: InfrahubDatabase, branch: Branch, obj: Node, previous_profile_ids: Optional[set[str]] = None
155
+ cls, db: InfrahubDatabase, branch: Branch, obj: Node, previous_profile_ids: set[str] | None = None
154
156
  ) -> Node:
155
157
  if not hasattr(obj, "profiles"):
156
158
  return obj
@@ -158,7 +160,7 @@ class InfrahubMutationMixin:
158
160
  if previous_profile_ids is None or previous_profile_ids != current_profile_ids:
159
161
  refreshed_node = await NodeManager.get_one_by_id_or_default_filter(
160
162
  db=db,
161
- kind=cls._meta.schema.kind,
163
+ kind=cls._meta.active_schema.kind,
162
164
  id=obj.get_id(),
163
165
  branch=branch,
164
166
  include_owner=True,
@@ -169,13 +171,13 @@ class InfrahubMutationMixin:
169
171
  return obj
170
172
 
171
173
  @classmethod
172
- async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch):
174
+ async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch) -> Node:
173
175
  """
174
176
  Wrapper around mutate_create_object to potentially activate locking.
175
177
  """
176
178
  schema_branch = db.schema.get_schema_branch(name=branch.name)
177
179
  lock_names = _get_kind_lock_names_on_object_mutation(
178
- kind=cls._meta.schema.kind, branch=branch, schema_branch=schema_branch
180
+ kind=cls._meta.active_schema.kind, branch=branch, schema_branch=schema_branch
179
181
  )
180
182
  if lock_names:
181
183
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
@@ -203,7 +205,7 @@ class InfrahubMutationMixin:
203
205
  cls,
204
206
  db: InfrahubDatabase,
205
207
  template_peer: Node,
206
- obj_peer_schema,
208
+ obj_peer_schema: MainSchemaTypes,
207
209
  parent_obj: Node,
208
210
  current_template: CoreObjectTemplate,
209
211
  ) -> Mapping[str, Any]:
@@ -212,7 +214,7 @@ class InfrahubMutationMixin:
212
214
  for attr in template_peer.get_schema().attribute_names:
213
215
  if attr not in obj_peer_schema.attribute_names:
214
216
  continue
215
- obj_peer_data[attr] = {"value": getattr(template_peer, attr).value}
217
+ obj_peer_data[attr] = {"value": getattr(template_peer, attr).value, "source": template_peer.id}
216
218
 
217
219
  for rel in template_peer.get_schema().relationship_names:
218
220
  rel_manager: RelationshipManager = getattr(template_peer, rel)
@@ -248,8 +250,13 @@ class InfrahubMutationMixin:
248
250
  if not template_relationship_peers:
249
251
  continue
250
252
 
251
- obj_peer_schema = relationship.get_peer_schema(db=db, branch=branch)
252
253
  for template_relationship_peer in template_relationship_peers.values():
254
+ # We retrieve peer schema for each peer in case we are processing a relationship which is based on a generic
255
+ obj_peer_schema = registry.schema.get_node_schema(
256
+ name=template_relationship_peer.get_schema().kind.removeprefix("Template"),
257
+ branch=branch,
258
+ duplicate=False,
259
+ )
253
260
  obj_peer_data = await cls._extract_peer_data(
254
261
  db=db,
255
262
  template_peer=template_relationship_peer,
@@ -258,7 +265,7 @@ class InfrahubMutationMixin:
258
265
  current_template=template,
259
266
  )
260
267
 
261
- obj_peer = await Node.init(schema=obj_peer_schema, db=db)
268
+ obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch)
262
269
  await obj_peer.new(db=db, **obj_peer_data)
263
270
  await constraint_runner.check(node=obj_peer, field_filters=list(obj_peer_data))
264
271
  await obj_peer.save(db=db)
@@ -278,7 +285,7 @@ class InfrahubMutationMixin:
278
285
  info: GraphQLResolveInfo,
279
286
  data: InputObjectType,
280
287
  branch: Branch,
281
- database: Optional[InfrahubDatabase] = None,
288
+ database: InfrahubDatabase | None = None,
282
289
  ) -> tuple[Node, Self]:
283
290
  graphql_context: GraphqlContext = info.context
284
291
  db = database or graphql_context.db
@@ -299,44 +306,41 @@ class InfrahubMutationMixin:
299
306
  NodeConstraintRunner, db=db.start_session(), branch=branch
300
307
  )
301
308
  node_class = Node
302
- if cls._meta.schema.kind in registry.node:
303
- node_class = registry.node[cls._meta.schema.kind]
309
+ if cls._meta.active_schema.kind in registry.node:
310
+ node_class = registry.node[cls._meta.active_schema.kind]
304
311
 
305
312
  fields_to_validate = list(data)
306
- try:
307
- if db.is_transaction:
308
- obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
309
- await obj.new(db=db, **data)
313
+ if db.is_transaction:
314
+ obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
315
+ await obj.new(db=db, **data)
316
+ await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
317
+ await obj.save(db=db)
318
+
319
+ object_template = await obj.get_object_template(db=db)
320
+ if object_template:
321
+ await cls._handle_template_relationships(
322
+ db=db,
323
+ branch=branch,
324
+ template=object_template,
325
+ obj=obj,
326
+ data=data,
327
+ )
328
+ else:
329
+ async with db.start_transaction() as dbt:
330
+ obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
331
+ await obj.new(db=dbt, **data)
310
332
  await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
311
- await obj.save(db=db)
333
+ await obj.save(db=dbt)
312
334
 
313
- object_template = await obj.get_object_template(db=db)
335
+ object_template = await obj.get_object_template(db=dbt)
314
336
  if object_template:
315
337
  await cls._handle_template_relationships(
316
- db=db,
338
+ db=dbt,
317
339
  branch=branch,
318
340
  template=object_template,
319
341
  obj=obj,
320
342
  data=data,
321
343
  )
322
- else:
323
- async with db.start_transaction() as dbt:
324
- obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
325
- await obj.new(db=dbt, **data)
326
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
327
- await obj.save(db=dbt)
328
-
329
- object_template = await obj.get_object_template(db=dbt)
330
- if object_template:
331
- await cls._handle_template_relationships(
332
- db=dbt,
333
- branch=branch,
334
- template=object_template,
335
- obj=obj,
336
- data=data,
337
- )
338
- except ValidationError as exc:
339
- raise ValueError(str(exc)) from exc
340
344
 
341
345
  if await cls._get_profile_ids(db=db, obj=obj):
342
346
  obj = await cls._refresh_for_profile_update(db=db, branch=branch, obj=obj)
@@ -346,7 +350,7 @@ class InfrahubMutationMixin:
346
350
  @classmethod
347
351
  async def mutate_create_to_graphql(cls, info: GraphQLResolveInfo, db: InfrahubDatabase, obj: Node) -> Self:
348
352
  fields = await extract_fields(info.field_nodes[0].selection_set)
349
- result = {"ok": True}
353
+ result: dict[str, Any] = {"ok": True}
350
354
  if "object" in fields:
351
355
  result["object"] = await obj.to_graphql(db=db, fields=fields.get("object", {}))
352
356
  return cls(**result)
@@ -359,6 +363,7 @@ class InfrahubMutationMixin:
359
363
  branch: Branch,
360
364
  db: InfrahubDatabase,
361
365
  obj: Node,
366
+ run_constraint_checks: bool = True,
362
367
  ) -> tuple[Node, Self]:
363
368
  """
364
369
  Wrapper around mutate_update to potentially activate locking and call it within a database transaction.
@@ -366,24 +371,37 @@ class InfrahubMutationMixin:
366
371
 
367
372
  schema_branch = db.schema.get_schema_branch(name=branch.name)
368
373
  lock_names = _get_kind_lock_names_on_object_mutation(
369
- kind=cls._meta.schema.kind, branch=branch, schema_branch=schema_branch
374
+ kind=cls._meta.active_schema.kind, branch=branch, schema_branch=schema_branch
370
375
  )
371
376
 
372
377
  if db.is_transaction:
373
378
  if lock_names:
374
379
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
375
- obj = await cls.mutate_update_object(db=db, info=info, data=data, branch=branch, obj=obj)
380
+ obj = await cls.mutate_update_object(
381
+ db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
382
+ )
376
383
  else:
377
- obj = await cls.mutate_update_object(db=db, info=info, data=data, branch=branch, obj=obj)
384
+ obj = await cls.mutate_update_object(
385
+ db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
386
+ )
378
387
  result = await cls.mutate_update_to_graphql(db=db, info=info, obj=obj)
379
388
  return obj, result
380
389
 
381
390
  async with db.start_transaction() as dbt:
382
391
  if lock_names:
383
392
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
384
- obj = await cls.mutate_update_object(db=dbt, info=info, data=data, branch=branch, obj=obj)
393
+ obj = await cls.mutate_update_object(
394
+ db=dbt,
395
+ info=info,
396
+ data=data,
397
+ branch=branch,
398
+ obj=obj,
399
+ run_constraint_checks=run_constraint_checks,
400
+ )
385
401
  else:
386
- obj = await cls.mutate_update_object(db=dbt, info=info, data=data, branch=branch, obj=obj)
402
+ obj = await cls.mutate_update_object(
403
+ db=dbt, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
404
+ )
387
405
  result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=obj)
388
406
  return obj, result
389
407
 
@@ -394,20 +412,17 @@ class InfrahubMutationMixin:
394
412
  info: GraphQLResolveInfo,
395
413
  data: InputObjectType,
396
414
  branch: Branch,
397
- database: Optional[InfrahubDatabase] = None,
398
- node: Optional[Node] = None,
415
+ database: InfrahubDatabase | None = None,
416
+ node: Node | None = None,
399
417
  ) -> tuple[Node, Self]:
400
418
  graphql_context: GraphqlContext = info.context
401
419
  db = database or graphql_context.db
402
420
 
403
421
  obj = node or await NodeManager.find_object(
404
- db=db, kind=cls._meta.schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
422
+ db=db, kind=cls._meta.active_schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
405
423
  )
406
424
 
407
- try:
408
- obj, result = await cls._call_mutate_update(info=info, data=data, db=db, branch=branch, obj=obj)
409
- except ValidationError as exc:
410
- raise ValueError(str(exc)) from exc
425
+ obj, result = await cls._call_mutate_update(info=info, data=data, db=db, branch=branch, obj=obj)
411
426
 
412
427
  return obj, result
413
428
 
@@ -419,6 +434,7 @@ class InfrahubMutationMixin:
419
434
  data: InputObjectType,
420
435
  branch: Branch,
421
436
  obj: Node,
437
+ run_constraint_checks: bool = True,
422
438
  ) -> Node:
423
439
  component_registry = get_component_registry()
424
440
  node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
@@ -426,12 +442,13 @@ class InfrahubMutationMixin:
426
442
  before_mutate_profile_ids = await cls._get_profile_ids(db=db, obj=obj)
427
443
  await obj.from_graphql(db=db, data=data)
428
444
  fields_to_validate = list(data)
429
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
445
+ if run_constraint_checks:
446
+ await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
430
447
 
431
448
  fields = list(data.keys())
432
- for field in ("id", "hfid"):
433
- if field in fields:
434
- fields.remove(field)
449
+ for field_to_remove in ("id", "hfid"):
450
+ if field_to_remove in fields:
451
+ fields.remove(field_to_remove)
435
452
 
436
453
  await obj.save(db=db, fields=fields)
437
454
 
@@ -449,7 +466,7 @@ class InfrahubMutationMixin:
449
466
  ) -> Self:
450
467
  fields_object = await extract_fields(info.field_nodes[0].selection_set)
451
468
  fields_object = fields_object.get("object", {})
452
- result = {"ok": True}
469
+ result: dict[str, Any] = {"ok": True}
453
470
  if fields_object:
454
471
  result["object"] = await obj.to_graphql(db=db, fields=fields_object)
455
472
  return cls(**result)
@@ -461,31 +478,76 @@ class InfrahubMutationMixin:
461
478
  info: GraphQLResolveInfo,
462
479
  data: InputObjectType,
463
480
  branch: Branch,
464
- node_getters: list[MutationNodeGetterInterface],
465
- database: Optional[InfrahubDatabase] = None,
481
+ node_getter_default_filter: MutationNodeGetterByDefaultFilter,
482
+ database: InfrahubDatabase | None = None,
466
483
  ) -> tuple[Node, Self, bool]:
467
- schema_name = cls._meta.schema.kind
484
+ """
485
+ First, check whether payload contains data identifying the node, such as id, hfid, or relevant fields for
486
+ default_filter. If not, we will try to create the node, but this creation might fail if payload contains
487
+ hfid fields (not `hfid` field itself) that would match an existing node in the database. In that case,
488
+ we would update the node without rerunning uniqueness constraint.
489
+ """
490
+
491
+ schema_name = cls._meta.active_schema.kind
468
492
 
469
493
  graphql_context: GraphqlContext = info.context
470
494
  db = database or graphql_context.db
471
-
472
- node_schema = db.schema.get(name=schema_name, branch=branch)
473
-
495
+ dict_data = dict(data)
474
496
  node = None
475
- for getter in node_getters:
476
- node = await getter.get_node(node_schema=node_schema, data=data, branch=branch)
477
- if node:
478
- break
497
+ run_constraint_checks = True
479
498
 
480
- if node:
481
- updated_obj, mutation = await cls.mutate_update(info=info, data=data, branch=branch, database=db, node=node)
499
+ if "id" in dict_data:
500
+ node = await NodeManager.get_one(
501
+ db=db, id=dict_data["id"], kind=schema_name, branch=branch, raise_on_error=True
502
+ )
503
+ updated_obj, mutation = await cls._call_mutate_update(
504
+ info=info,
505
+ data=data,
506
+ db=db,
507
+ branch=branch,
508
+ obj=node,
509
+ run_constraint_checks=run_constraint_checks,
510
+ )
482
511
  return updated_obj, mutation, False
483
- # We need to convert the InputObjectType into a dict in order to remove hfid that isn't a valid input when creating the object
484
- data_dict = dict(data)
512
+
513
+ if cls._meta.active_schema.default_filter is not None:
514
+ node = await node_getter_default_filter.get_node(
515
+ node_schema=cls._meta.active_schema, data=data, branch=branch
516
+ )
517
+
485
518
  if "hfid" in data:
486
- del data_dict["hfid"]
487
- created_obj, mutation = await cls.mutate_create(info=info, data=data_dict, branch=branch)
488
- return created_obj, mutation, True
519
+ node = await NodeManager.get_one_by_hfid(db=db, hfid=dict_data["hfid"], kind=schema_name, branch=branch)
520
+
521
+ if node is not None:
522
+ updated_obj, mutation = await cls._call_mutate_update(
523
+ info=info,
524
+ data=data,
525
+ db=db,
526
+ branch=branch,
527
+ obj=node,
528
+ run_constraint_checks=run_constraint_checks,
529
+ )
530
+ return updated_obj, mutation, False
531
+
532
+ try:
533
+ dict_data.pop("hfid", "unused") # `hfid` is invalid for creation.
534
+ created_obj, mutation = await cls.mutate_create(info=info, data=dict_data, branch=branch)
535
+ return created_obj, mutation, True
536
+ except HFIDViolatedError as exc:
537
+ # Only the HFID constraint has been violated, it means the node exists and we can update without rerunning constraints
538
+ if len(exc.matching_nodes_ids) > 1:
539
+ raise RuntimeError(f"Multiple {schema_name} nodes have the same hfid (database corrupted)") from exc
540
+ node_id = list(exc.matching_nodes_ids)[0]
541
+ node = await NodeManager.get_one(db=db, id=node_id, kind=schema_name, branch=branch, raise_on_error=True)
542
+ updated_obj, mutation = await cls._call_mutate_update(
543
+ info=info,
544
+ data=data,
545
+ db=db,
546
+ branch=branch,
547
+ obj=node,
548
+ run_constraint_checks=run_constraint_checks,
549
+ )
550
+ return updated_obj, mutation, False
489
551
 
490
552
  @classmethod
491
553
  @retry_db_transaction(name="object_delete")
@@ -494,34 +556,35 @@ class InfrahubMutationMixin:
494
556
  info: GraphQLResolveInfo,
495
557
  data: InputObjectType,
496
558
  branch: Branch,
497
- ) -> tuple[Node, Self]:
559
+ ) -> DeleteResult:
498
560
  graphql_context: GraphqlContext = info.context
499
561
 
500
562
  obj = await NodeManager.find_object(
501
- db=graphql_context.db, kind=cls._meta.schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
563
+ db=graphql_context.db,
564
+ kind=cls._meta.active_schema.kind,
565
+ id=data.get("id"),
566
+ hfid=data.get("hfid"),
567
+ branch=branch,
502
568
  )
503
569
 
504
- try:
505
- async with graphql_context.db.start_transaction() as db:
506
- deleted = await NodeManager.delete(db=db, branch=branch, nodes=[obj])
507
- except ValidationError as exc:
508
- raise ValueError(str(exc)) from exc
570
+ async with graphql_context.db.start_transaction() as db:
571
+ deleted = await NodeManager.delete(db=db, branch=branch, nodes=[obj])
509
572
 
510
573
  deleted_str = ", ".join([f"{d.get_kind()}({d.get_id()})" for d in deleted])
511
574
  log.info(f"nodes deleted: {deleted_str}")
512
575
 
513
576
  ok = True
514
577
 
515
- return obj, cls(ok=ok)
578
+ return DeleteResult(node=obj, mutation=cls(ok=ok), deleted_nodes=deleted)
516
579
 
517
580
 
518
581
  class InfrahubMutation(InfrahubMutationMixin, Mutation):
519
582
  @classmethod
520
583
  def __init_subclass_with_meta__(
521
584
  cls,
522
- schema: Optional[Union[NodeSchema, GenericSchema, ProfileSchema, TemplateSchema]] = None,
523
- _meta=None,
524
- **options,
585
+ schema: NodeSchema | GenericSchema | ProfileSchema | TemplateSchema | None = None,
586
+ _meta: InfrahubMutationOptions | None = None,
587
+ **options: dict[str, Any],
525
588
  ) -> None:
526
589
  # Make sure schema is a valid NodeSchema Node Class
527
590
  if not isinstance(schema, NodeSchema | GenericSchema | ProfileSchema | TemplateSchema):
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Any, Optional
1
+ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  from graphene import InputObjectType, Mutation
4
4
  from graphql import GraphQLResolveInfo
@@ -14,7 +14,7 @@ from infrahub.database import InfrahubDatabase
14
14
  from infrahub.exceptions import ValidationError
15
15
  from infrahub.graphql.mutations.main import InfrahubMutationMixin
16
16
 
17
- from .main import InfrahubMutationOptions
17
+ from .main import DeleteResult, InfrahubMutationOptions
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from infrahub.graphql.initialization import GraphqlContext
@@ -35,7 +35,7 @@ def validate_namespace(data: InputObjectType) -> None:
35
35
  class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
36
36
  @classmethod
37
37
  def __init_subclass_with_meta__(
38
- cls, schema: NodeSchema, _meta: Optional[Any] = None, **options: dict[str, Any]
38
+ cls, schema: NodeSchema, _meta: Any | None = None, **options: dict[str, Any]
39
39
  ) -> None:
40
40
  # Make sure schema is a valid NodeSchema Node Class
41
41
  if not isinstance(schema, NodeSchema):
@@ -53,7 +53,7 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
53
53
  info: GraphQLResolveInfo,
54
54
  data: InputObjectType,
55
55
  branch: Branch,
56
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
56
+ database: InfrahubDatabase | None = None, # noqa: ARG003
57
57
  ) -> tuple[Node, Self]:
58
58
  validate_namespace(data=data)
59
59
 
@@ -67,8 +67,8 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
67
67
  info: GraphQLResolveInfo,
68
68
  data: InputObjectType,
69
69
  branch: Branch,
70
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
71
- node: Optional[Node] = None, # noqa: ARG003
70
+ database: InfrahubDatabase | None = None, # noqa: ARG003
71
+ node: Node | None = None, # noqa: ARG003
72
72
  ) -> tuple[Node, Self]:
73
73
  graphql_context: GraphqlContext = info.context
74
74
 
@@ -89,7 +89,7 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
89
89
  info: GraphQLResolveInfo,
90
90
  data: InputObjectType,
91
91
  branch: Branch,
92
- ) -> tuple[Node, Self]:
92
+ ) -> DeleteResult:
93
93
  graphql_context: GraphqlContext = info.context
94
94
  obj = await NodeManager.find_object(
95
95
  db=graphql_context.db, kind=CoreMenuItem, id=data.get("id"), hfid=data.get("hfid"), branch=branch
@@ -1,13 +1,11 @@
1
- from typing import Optional
2
-
3
1
  from pydantic import BaseModel
4
2
 
5
3
 
6
4
  class BranchCreateModel(BaseModel):
7
5
  name: str
8
- id: Optional[str] = None
6
+ id: str | None = None
9
7
  description: str = ""
10
8
  origin_branch: str = "main"
11
- branched_from: Optional[str] = None
9
+ branched_from: str | None = None
12
10
  sync_with_git: bool = True
13
11
  is_isolated: bool = True