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,137 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Any, Self, cast
5
+
6
+ from graphene import InputObjectType, Mutation
7
+
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.protocols import CoreWebhook
10
+ from infrahub.core.schema import NodeSchema
11
+ from infrahub.database import retry_db_transaction
12
+ from infrahub.events.utils import get_all_infrahub_node_kind_events
13
+ from infrahub.exceptions import ValidationError
14
+ from infrahub.log import get_logger
15
+
16
+ from .main import InfrahubMutationMixin, InfrahubMutationOptions
17
+
18
+ if TYPE_CHECKING:
19
+ from graphql import GraphQLResolveInfo
20
+
21
+ from infrahub.core.branch import Branch
22
+ from infrahub.core.node import Node
23
+ from infrahub.database import InfrahubDatabase
24
+ from infrahub.graphql.initialization import GraphqlContext
25
+
26
+ log = get_logger()
27
+
28
+
29
+ @dataclass
30
+ class StringValue:
31
+ value: str
32
+
33
+
34
+ @dataclass
35
+ class WebhookCreate:
36
+ event_type: StringValue | None = field(default=None)
37
+ node_kind: StringValue | None = field(default=None)
38
+
39
+
40
+ class WebhookUpdate(WebhookCreate):
41
+ id: str | None = field(default=None)
42
+ hfid: list[str] | None = field(default=None)
43
+
44
+
45
+ class InfrahubWebhookMutation(InfrahubMutationMixin, Mutation):
46
+ _meta: InfrahubMutationOptions
47
+
48
+ @classmethod
49
+ def __init_subclass_with_meta__(
50
+ cls, schema: NodeSchema | None = None, _meta: InfrahubMutationOptions | None = None, **options: dict[str, Any]
51
+ ) -> None:
52
+ # Make sure schema is a valid NodeSchema Node Class
53
+ if not isinstance(schema, NodeSchema):
54
+ raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
55
+
56
+ if not _meta:
57
+ _meta = InfrahubMutationOptions(cls)
58
+
59
+ _meta.schema = schema
60
+
61
+ super().__init_subclass_with_meta__(_meta=_meta, **options)
62
+
63
+ @classmethod
64
+ async def mutate_create(
65
+ cls,
66
+ info: GraphQLResolveInfo,
67
+ data: InputObjectType,
68
+ branch: Branch,
69
+ database: InfrahubDatabase | None = None,
70
+ ) -> tuple[Node, Self]:
71
+ graphql_context: GraphqlContext = info.context
72
+
73
+ input_data = cast("WebhookCreate", data)
74
+
75
+ _validate_input(graphql_context=graphql_context, branch=branch, input_data=input_data)
76
+
77
+ obj, result = await super().mutate_create(info=info, data=data, branch=branch, database=database)
78
+
79
+ return obj, result
80
+
81
+ @classmethod
82
+ @retry_db_transaction(name="object_update")
83
+ async def mutate_update(
84
+ cls,
85
+ info: GraphQLResolveInfo,
86
+ data: InputObjectType,
87
+ branch: Branch,
88
+ database: InfrahubDatabase | None = None,
89
+ node: Node | None = None,
90
+ ) -> tuple[Node, Self]:
91
+ graphql_context: GraphqlContext = info.context
92
+
93
+ db = database or graphql_context.db
94
+
95
+ input_data = cast("WebhookUpdate", data)
96
+
97
+ _validate_input(graphql_context=graphql_context, branch=branch, input_data=input_data)
98
+
99
+ obj = node or await NodeManager.find_object(
100
+ db=db,
101
+ kind=cls._meta.active_schema.kind,
102
+ id=input_data.id,
103
+ hfid=input_data.hfid,
104
+ branch=branch,
105
+ )
106
+
107
+ webhook = cast(CoreWebhook, obj)
108
+
109
+ event_type = input_data.event_type.value if input_data.event_type else webhook.event_type.value.value
110
+ node_kind = input_data.node_kind.value if input_data.node_kind else webhook.node_kind.value
111
+
112
+ if event_type and node_kind:
113
+ updated_data = WebhookUpdate(
114
+ event_type=StringValue(value=event_type), node_kind=StringValue(value=node_kind)
115
+ )
116
+ _validate_input(graphql_context=graphql_context, branch=branch, input_data=updated_data)
117
+
118
+ try:
119
+ obj, result = await cls._call_mutate_update(info=info, data=data, db=db, branch=branch, obj=obj)
120
+ except ValidationError as exc:
121
+ raise ValueError(str(exc)) from exc
122
+
123
+ return obj, result
124
+
125
+
126
+ def _validate_input(graphql_context: GraphqlContext, branch: Branch, input_data: WebhookCreate) -> None:
127
+ if input_data.node_kind:
128
+ # Validate that the requested node_kind exists, will raise an error if not
129
+ graphql_context.db.schema.get(name=input_data.node_kind.value, branch=branch, duplicate=False)
130
+
131
+ if input_data.event_type:
132
+ # If the event type is not set then all events are applicable, this will mean that some events
133
+ # would be filtered out, as they won't match the node.
134
+ if input_data.event_type.value not in get_all_infrahub_node_kind_events():
135
+ raise ValidationError(
136
+ input_value=f"Defining a node_kind is not valid for {input_data.event_type.value} events"
137
+ )
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from graphql.language import (
7
7
  DirectiveNode,
@@ -172,13 +172,13 @@ class GraphQLExtractor:
172
172
  return fields
173
173
 
174
174
  async def extract_fields(
175
- self, selection_set: Optional[SelectionSetNode], path: str = "/"
176
- ) -> Optional[dict[str, Optional[dict]]]:
175
+ self, selection_set: SelectionSetNode | None, path: str = "/"
176
+ ) -> dict[str, dict | None] | None:
177
177
  """Extract fields and apply Directives"""
178
178
  if not selection_set:
179
179
  return None
180
180
 
181
- fields: dict[str, Optional[dict]] = {}
181
+ fields: dict[str, dict | None] = {}
182
182
  for node in selection_set.selections:
183
183
  sub_selection_set = getattr(node, "selection_set", None)
184
184
  if isinstance(node, FieldNode):
@@ -15,16 +15,7 @@ async def get_permissions(schema: MainSchemaTypes, graphql_context: GraphqlConte
15
15
  schema_objects = [schema]
16
16
  if isinstance(schema, GenericSchema):
17
17
  for node_name in schema.used_by:
18
- schema_object: MainSchemaTypes
19
- try:
20
- schema_object = registry.schema.get_node_schema(
21
- name=node_name, branch=graphql_context.branch, duplicate=False
22
- )
23
- except ValueError:
24
- schema_object = registry.schema.get_profile_schema(
25
- name=node_name, branch=graphql_context.branch, duplicate=False
26
- )
27
- schema_objects.append(schema_object)
18
+ schema_objects.append(registry.schema.get(name=node_name, branch=graphql_context.branch, duplicate=False))
28
19
 
29
20
  response: dict[str, Any] = {"count": len(schema_objects), "edges": []}
30
21
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Optional, Union
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import Argument, Boolean, DateTime, Field, InputObjectType, Int, List, NonNull, ObjectType, String
6
6
  from graphene import Enum as GrapheneEnum
@@ -193,7 +193,7 @@ class DiffTreeResolver:
193
193
  label=enriched_node.label,
194
194
  status=enriched_node.action,
195
195
  parent=parent,
196
- last_changed_at=enriched_node.changed_at.obj if enriched_node.changed_at else None,
196
+ last_changed_at=enriched_node.changed_at.to_datetime() if enriched_node.changed_at else None,
197
197
  path_identifier=enriched_node.path_identifier,
198
198
  attributes=diff_attributes,
199
199
  relationships=diff_relationships,
@@ -219,7 +219,7 @@ class DiffTreeResolver:
219
219
  diff_prop.conflict = None
220
220
  return DiffAttribute(
221
221
  name=enriched_attribute.name,
222
- last_changed_at=enriched_attribute.changed_at.obj,
222
+ last_changed_at=enriched_attribute.changed_at.to_datetime(),
223
223
  status=enriched_attribute.action,
224
224
  path_identifier=enriched_attribute.path_identifier,
225
225
  properties=diff_properties,
@@ -241,7 +241,9 @@ class DiffTreeResolver:
241
241
  return DiffRelationship(
242
242
  name=enriched_relationship.name,
243
243
  label=enriched_relationship.label,
244
- last_changed_at=enriched_relationship.changed_at.obj if enriched_relationship.changed_at else None,
244
+ last_changed_at=enriched_relationship.changed_at.to_datetime()
245
+ if enriched_relationship.changed_at
246
+ else None,
245
247
  status=enriched_relationship.action,
246
248
  cardinality=enriched_relationship.cardinality,
247
249
  path_identifier=enriched_relationship.path_identifier,
@@ -263,7 +265,7 @@ class DiffTreeResolver:
263
265
  enriched_conflict=enriched_element.conflict, graphql_context=graphql_context
264
266
  )
265
267
  return DiffSingleRelationship(
266
- last_changed_at=enriched_element.changed_at.obj,
268
+ last_changed_at=enriched_element.changed_at.to_datetime(),
267
269
  status=enriched_element.action,
268
270
  peer_id=enriched_element.peer_id,
269
271
  peer_label=enriched_element.peer_label,
@@ -287,7 +289,7 @@ class DiffTreeResolver:
287
289
  )
288
290
  return DiffProperty(
289
291
  property_type=enriched_property.property_type.value,
290
- last_changed_at=enriched_property.changed_at.obj,
292
+ last_changed_at=enriched_property.changed_at.to_datetime(),
291
293
  previous_value=enriched_property.previous_value,
292
294
  new_value=enriched_property.new_value,
293
295
  previous_label=enriched_property.previous_label,
@@ -306,13 +308,13 @@ class DiffTreeResolver:
306
308
  uuid=enriched_conflict.uuid,
307
309
  base_branch_action=enriched_conflict.base_branch_action,
308
310
  base_branch_value=enriched_conflict.base_branch_value,
309
- base_branch_changed_at=enriched_conflict.base_branch_changed_at.obj
311
+ base_branch_changed_at=enriched_conflict.base_branch_changed_at.to_datetime()
310
312
  if enriched_conflict.base_branch_changed_at
311
313
  else None,
312
314
  base_branch_label=enriched_conflict.base_branch_label,
313
315
  diff_branch_action=enriched_conflict.diff_branch_action,
314
316
  diff_branch_value=enriched_conflict.diff_branch_value,
315
- diff_branch_changed_at=enriched_conflict.diff_branch_changed_at.obj
317
+ diff_branch_changed_at=enriched_conflict.diff_branch_changed_at.to_datetime()
316
318
  if enriched_conflict.diff_branch_changed_at
317
319
  else None,
318
320
  diff_branch_label=enriched_conflict.diff_branch_label,
@@ -322,7 +324,7 @@ class DiffTreeResolver:
322
324
 
323
325
  async def to_graphql(
324
326
  self, fields: dict[str, dict], diff_object: Any | None
325
- ) -> Optional[Union[list[dict[str, Any]], dict[str, Any]]]:
327
+ ) -> list[dict[str, Any]] | dict[str, Any] | None:
326
328
  if diff_object is None:
327
329
  return None
328
330
  if isinstance(diff_object, list):
@@ -385,7 +387,7 @@ class DiffTreeResolver:
385
387
  root_node_uuids: list[str] | None = None,
386
388
  limit: int | None = None,
387
389
  offset: int | None = None,
388
- ) -> Optional[Union[list[dict[str, Any]], dict[str, Any]]]:
390
+ ) -> list[dict[str, Any]] | dict[str, Any] | None:
389
391
  component_registry = get_component_registry()
390
392
  graphql_context: GraphqlContext = info.context
391
393
  base_branch = await registry.get_branch(db=graphql_context.db, branch=registry.default_branch)
@@ -426,7 +428,10 @@ class DiffTreeResolver:
426
428
  # take the one with the longest duration that covers multiple branches
427
429
  enriched_diff = sorted(
428
430
  enriched_diffs,
429
- key=lambda d: (d.base_branch_name != d.diff_branch_name, d.to_time.obj - d.from_time.obj),
431
+ key=lambda d: (
432
+ d.base_branch_name != d.diff_branch_name,
433
+ d.to_time.to_datetime() - d.from_time.to_datetime(),
434
+ ),
430
435
  reverse=True,
431
436
  )[0]
432
437
  else:
@@ -454,7 +459,7 @@ class DiffTreeResolver:
454
459
  from_time: datetime | None = None,
455
460
  to_time: datetime | None = None,
456
461
  filters: dict | None = None,
457
- ) -> Optional[Union[list[dict[str, Any]], dict[str, Any]]]:
462
+ ) -> list[dict[str, Any]] | dict[str, Any] | None:
458
463
  component_registry = get_component_registry()
459
464
  graphql_context: GraphqlContext = info.context
460
465
  base_branch = await registry.get_branch(db=graphql_context.db, branch=registry.default_branch)
@@ -485,8 +490,8 @@ class DiffTreeResolver:
485
490
  diff_tree_summary = DiffTreeSummary(
486
491
  base_branch=base_branch.name,
487
492
  diff_branch=diff_branch.name,
488
- from_time=summary.from_time.obj,
489
- to_time=summary.to_time.obj,
493
+ from_time=summary.from_time.to_datetime(),
494
+ to_time=summary.to_time.to_datetime(),
490
495
  **summary.model_dump(exclude={"from_time", "to_time"}),
491
496
  )
492
497
  full_fields = await extract_fields(info.field_nodes[0].selection_set)
@@ -2,11 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from graphene import Boolean, DateTime, Field, Int, List, NonNull, ObjectType, String
5
+ from graphene import Argument, Boolean, DateTime, Field, Int, List, NonNull, ObjectType, String
6
6
  from infrahub_sdk.utils import extract_fields_first_node
7
7
 
8
8
  from infrahub.exceptions import ValidationError
9
- from infrahub.graphql.types.event import EventNodes
9
+ from infrahub.graphql.types.event import EventNodes, EventTypeFilter
10
10
  from infrahub.task_manager.event import PrefectEvent
11
11
  from infrahub.task_manager.models import InfrahubEventFilter
12
12
 
@@ -32,6 +32,7 @@ class Events(ObjectType):
32
32
  ids: list[str] | None = None,
33
33
  branches: list[str] | None = None,
34
34
  event_type: list[str] | None = None,
35
+ event_type_filter: dict[str, Any] | None = None,
35
36
  related_node__ids: list[str] | None = None,
36
37
  primary_node__ids: list[str] | None = None,
37
38
  parent__ids: list[str] | None = None,
@@ -49,6 +50,7 @@ class Events(ObjectType):
49
50
  account__ids=account__ids,
50
51
  has_children=has_children,
51
52
  event_type=event_type,
53
+ event_type_filter=event_type_filter,
52
54
  related_node__ids=related_node__ids,
53
55
  primary_node__ids=primary_node__ids,
54
56
  parent__ids=parent__ids,
@@ -93,6 +95,7 @@ Event = Field(
93
95
  level=Int(required=False),
94
96
  has_children=Boolean(required=False, description="Filter events based on if they can have children or not"),
95
97
  event_type=List(NonNull(String), description="Filter events that match a specific type"),
98
+ event_type_filter=Argument(EventTypeFilter, required=False, description="Filters specific to a given event_type"),
96
99
  primary_node__ids=List(
97
100
  NonNull(String), description="Filter events where the primary node id is within indicated node ids"
98
101
  ),
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ipaddress
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from graphene import Field, Int, ObjectType, String
7
7
  from netaddr import IPSet
@@ -27,7 +27,7 @@ class IPAddressGetNextAvailable(ObjectType):
27
27
  root: dict, # noqa: ARG004
28
28
  info: GraphQLResolveInfo,
29
29
  prefix_id: str,
30
- prefix_length: Optional[int] = None,
30
+ prefix_length: int | None = None,
31
31
  ) -> dict[str, str]:
32
32
  graphql_context: GraphqlContext = info.context
33
33
 
@@ -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 Field, Int, List, NonNull, ObjectType, String
6
6
  from infrahub_sdk.utils import extract_fields_first_node
@@ -25,7 +25,7 @@ class Relationships(ObjectType):
25
25
  ids: list[str],
26
26
  limit: int = 10,
27
27
  offset: int = 0,
28
- excluded_namespaces: Optional[list[str]] = None,
28
+ excluded_namespaces: list[str] | None = None,
29
29
  ) -> dict[str, Any]:
30
30
  graphql_context: GraphqlContext = info.context
31
31
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ipaddress
4
- from typing import TYPE_CHECKING, Any, Optional
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from graphene import Boolean, Field, Int, List, NonNull, ObjectType, String
7
7
  from infrahub_sdk.utils import extract_fields_first_node, is_valid_uuid
@@ -110,7 +110,7 @@ async def search_resolver(
110
110
  fields = await extract_fields_first_node(info)
111
111
 
112
112
  if is_valid_uuid(q):
113
- matching: Optional[CoreNode] = await NodeManager.get_one(
113
+ matching: CoreNode | None = await NodeManager.get_one(
114
114
  db=graphql_context.db, branch=graphql_context.branch, at=graphql_context.at, id=q
115
115
  )
116
116
  if matching:
@@ -0,0 +1,264 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ from graphql import GraphQLResolveInfo
4
+ from infrahub_sdk.utils import deep_merge_dict, extract_fields
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.constants import BranchSupportType, RelationshipHierarchyDirection
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.query.node import NodeGetHierarchyQuery
10
+ from infrahub.core.schema.node_schema import NodeSchema
11
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
12
+ from infrahub.core.timestamp import Timestamp
13
+ from infrahub.database import InfrahubDatabase
14
+
15
+ from ..loaders.peers import PeerRelationshipsDataLoader, QueryPeerParams
16
+ from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
17
+
18
+ if TYPE_CHECKING:
19
+ from infrahub.core.schema import MainSchemaTypes
20
+
21
+ from ..initialization import GraphqlContext
22
+
23
+
24
+ class ManyRelationshipResolver:
25
+ def __init__(self) -> None:
26
+ self._data_loader_instances: dict[QueryPeerParams, PeerRelationshipsDataLoader] = {}
27
+
28
+ async def get_descendant_ids(
29
+ self,
30
+ db: InfrahubDatabase,
31
+ branch: Branch,
32
+ at: Timestamp | None,
33
+ parent_id: str,
34
+ node_schema: NodeSchema,
35
+ ) -> list[str]:
36
+ async with db.start_session() as dbs:
37
+ query = await NodeGetHierarchyQuery.init(
38
+ db=dbs,
39
+ direction=RelationshipHierarchyDirection.DESCENDANTS,
40
+ node_id=parent_id,
41
+ node_schema=node_schema,
42
+ at=at,
43
+ branch=branch,
44
+ )
45
+ await query.execute(db=dbs)
46
+ return list(query.get_peer_ids())
47
+
48
+ async def get_peer_count(
49
+ self,
50
+ db: InfrahubDatabase,
51
+ branch: Branch,
52
+ at: Timestamp | None,
53
+ ids: list[str],
54
+ source_kind: str,
55
+ rel_schema: RelationshipSchema,
56
+ filters: dict[str, Any],
57
+ ) -> int:
58
+ async with db.start_session() as dbs:
59
+ return await NodeManager.count_peers(
60
+ db=dbs,
61
+ ids=ids,
62
+ source_kind=source_kind,
63
+ schema=rel_schema,
64
+ filters=filters,
65
+ at=at,
66
+ branch=branch,
67
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
68
+ )
69
+
70
+ async def resolve(
71
+ self,
72
+ parent: dict,
73
+ info: GraphQLResolveInfo,
74
+ include_descendants: bool = False,
75
+ offset: int | None = None,
76
+ limit: int | None = None,
77
+ **kwargs: Any,
78
+ ) -> dict[str, Any]:
79
+ """Resolver for relationships of cardinality=one for Edged responses
80
+
81
+ This resolver is used for paginated responses and as such we redefined the requested
82
+ fields by only reusing information below the 'node' key.
83
+ """
84
+ # Extract the InfraHub schema by inspecting the GQL Schema
85
+
86
+ node_schema: MainSchemaTypes = info.parent_type.graphene_type._meta.schema # type: ignore[attr-defined]
87
+
88
+ graphql_context: GraphqlContext = info.context
89
+
90
+ # Extract the name of the fields in the GQL query
91
+ fields = await extract_fields(info.field_nodes[0].selection_set)
92
+ edges = fields.get("edges", {})
93
+ node_fields = edges.get("node", {})
94
+ property_fields = edges.get("properties", {})
95
+ for key, value in property_fields.items():
96
+ mapped_name = RELATIONS_PROPERTY_MAP[key]
97
+ node_fields[mapped_name] = value
98
+
99
+ filters = {
100
+ f"{info.field_name}__{key}": value
101
+ for key, value in kwargs.items()
102
+ if "__" in key and value or key in ["id", "ids"]
103
+ }
104
+
105
+ response: dict[str, Any] = {"edges": [], "count": None}
106
+
107
+ # Extract the schema of the node on the other end of the relationship from the GQL Schema
108
+ node_rel = node_schema.get_relationship(info.field_name)
109
+ source_kind = node_schema.kind
110
+ ids = [parent["id"]]
111
+ if isinstance(node_schema, NodeSchema):
112
+ if node_schema.hierarchy:
113
+ source_kind = node_schema.hierarchy
114
+
115
+ if include_descendants:
116
+ descendant_ids = await self.get_descendant_ids(
117
+ db=graphql_context.db,
118
+ branch=graphql_context.branch,
119
+ at=graphql_context.at,
120
+ parent_id=ids[0],
121
+ node_schema=node_schema,
122
+ )
123
+ ids.extend(descendant_ids)
124
+
125
+ if "count" in fields:
126
+ peer_count = await self.get_peer_count(
127
+ db=graphql_context.db,
128
+ branch=graphql_context.branch,
129
+ at=graphql_context.at,
130
+ ids=ids,
131
+ source_kind=source_kind,
132
+ rel_schema=node_rel,
133
+ filters=filters,
134
+ )
135
+ response["count"] = peer_count
136
+
137
+ if not node_fields:
138
+ return response
139
+
140
+ if offset or limit:
141
+ node_graph = await self._get_entities_simple(
142
+ db=graphql_context.db,
143
+ branch=graphql_context.branch,
144
+ ids=ids,
145
+ at=graphql_context.at,
146
+ related_node_ids=graphql_context.related_node_ids,
147
+ source_kind=source_kind,
148
+ rel_schema=node_rel,
149
+ filters=filters,
150
+ node_fields=node_fields,
151
+ offset=offset,
152
+ limit=limit,
153
+ )
154
+ else:
155
+ node_graph = await self._get_entities_with_data_loader(
156
+ db=graphql_context.db,
157
+ branch=graphql_context.branch,
158
+ ids=ids,
159
+ at=graphql_context.at,
160
+ related_node_ids=graphql_context.related_node_ids,
161
+ source_kind=source_kind,
162
+ rel_schema=node_rel,
163
+ filters=filters,
164
+ node_fields=node_fields,
165
+ )
166
+
167
+ if not node_graph:
168
+ return response
169
+
170
+ entries = []
171
+ for node in node_graph:
172
+ entry: dict[str, dict[str, Any]] = {"node": {}, "properties": {}}
173
+ for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
174
+ value = node.pop(key, None)
175
+ if value:
176
+ entry["properties"][mapped] = value
177
+ entry["node"] = node
178
+ entries.append(entry)
179
+
180
+ response["edges"] = entries
181
+ return response
182
+
183
+ async def _get_entities_simple(
184
+ self,
185
+ db: InfrahubDatabase,
186
+ branch: Branch,
187
+ ids: list[str],
188
+ at: Timestamp | None,
189
+ related_node_ids: set[str] | None,
190
+ source_kind: str,
191
+ rel_schema: RelationshipSchema,
192
+ filters: dict[str, Any],
193
+ node_fields: dict[str, Any],
194
+ offset: int | None = None,
195
+ limit: int | None = None,
196
+ ) -> list[dict[str, Any]] | None:
197
+ async with db.start_session() as dbs:
198
+ objs = await NodeManager.query_peers(
199
+ db=dbs,
200
+ ids=ids,
201
+ source_kind=source_kind,
202
+ schema=rel_schema,
203
+ filters=filters,
204
+ fields=node_fields,
205
+ offset=offset,
206
+ limit=limit,
207
+ at=at,
208
+ branch=branch,
209
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
210
+ fetch_peers=True,
211
+ )
212
+ if not objs:
213
+ return None
214
+ return [await obj.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids) for obj in objs]
215
+
216
+ async def _get_entities_with_data_loader(
217
+ self,
218
+ db: InfrahubDatabase,
219
+ branch: Branch,
220
+ ids: list[str],
221
+ at: Timestamp | None,
222
+ related_node_ids: set[str] | None,
223
+ source_kind: str,
224
+ rel_schema: RelationshipSchema,
225
+ filters: dict[str, Any],
226
+ node_fields: dict[str, Any],
227
+ ) -> list[dict[str, Any]] | None:
228
+ if node_fields and "display_label" in node_fields:
229
+ schema_branch = db.schema.get_schema_branch(name=branch.name)
230
+ display_label_fields = schema_branch.generate_fields_for_display_label(name=rel_schema.peer)
231
+ if display_label_fields:
232
+ node_fields = deep_merge_dict(dicta=node_fields, dictb=display_label_fields)
233
+
234
+ if node_fields and "hfid" in node_fields:
235
+ peer_schema = db.schema.get(name=rel_schema.peer, branch=branch, duplicate=False)
236
+ hfid_fields = peer_schema.generate_fields_for_hfid()
237
+ if hfid_fields:
238
+ node_fields = deep_merge_dict(dicta=node_fields, dictb=hfid_fields)
239
+
240
+ query_params = QueryPeerParams(
241
+ branch=branch,
242
+ source_kind=source_kind,
243
+ schema=rel_schema,
244
+ filters=filters,
245
+ fields=node_fields,
246
+ at=at,
247
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
248
+ )
249
+ if query_params in self._data_loader_instances:
250
+ loader = self._data_loader_instances[query_params]
251
+ else:
252
+ loader = PeerRelationshipsDataLoader(db=db, query_params=query_params)
253
+ self._data_loader_instances[query_params] = loader
254
+ all_peer_rels = []
255
+ for node_id in ids:
256
+ node_peer_rels = await loader.load(key=node_id)
257
+ all_peer_rels.extend(node_peer_rels)
258
+ if not all_peer_rels:
259
+ return None
260
+ async with db.start_session() as dbs:
261
+ return [
262
+ await obj.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
263
+ for obj in all_peer_rels
264
+ ]