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,551 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from ..graphql import Query
8
+ from .constants import FINAL_STATES
9
+ from .exceptions import TaskNotCompletedError, TaskNotFoundError, TooManyTasksError
10
+ from .models import Task, TaskFilter
11
+
12
+ if TYPE_CHECKING:
13
+ from ..client import InfrahubClient, InfrahubClientSync
14
+
15
+
16
+ class InfraHubTaskManagerBase:
17
+ @classmethod
18
+ def _generate_query(
19
+ cls,
20
+ filters: TaskFilter | None = None,
21
+ include_logs: bool = False,
22
+ include_related_nodes: bool = False,
23
+ offset: int | None = None,
24
+ limit: int | None = None,
25
+ count: bool = False,
26
+ ) -> Query:
27
+ query: dict[str, Any] = {
28
+ "InfrahubTask": {
29
+ "edges": {
30
+ "node": {
31
+ "id": None,
32
+ "title": None,
33
+ "state": None,
34
+ "progress": None,
35
+ "workflow": None,
36
+ "branch": None,
37
+ "created_at": None,
38
+ "updated_at": None,
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ if not filters and (offset or limit):
45
+ filters = TaskFilter(offset=offset, limit=limit)
46
+ elif filters and offset:
47
+ filters.offset = offset
48
+ elif filters and limit:
49
+ filters.limit = limit
50
+
51
+ if filters:
52
+ query["InfrahubTask"]["@filters"] = filters.to_dict()
53
+
54
+ if count:
55
+ query["InfrahubTask"]["count"] = None
56
+
57
+ if include_logs:
58
+ query["InfrahubTask"]["edges"]["node"]["logs"] = {
59
+ "edges": {
60
+ "node": {
61
+ "message": None,
62
+ "severity": None,
63
+ "timestamp": None,
64
+ }
65
+ }
66
+ }
67
+
68
+ if include_related_nodes:
69
+ query["InfrahubTask"]["edges"]["node"]["related_nodes"] = {"id": None, "kind": None}
70
+
71
+ return Query(query=query)
72
+
73
+ @classmethod
74
+ def _generate_count_query(cls, filters: TaskFilter | None = None) -> Query:
75
+ query: dict[str, Any] = {
76
+ "InfrahubTask": {
77
+ "count": None,
78
+ }
79
+ }
80
+ if filters:
81
+ query["InfrahubTask"]["@filters"] = filters.to_dict()
82
+
83
+ return Query(query=query)
84
+
85
+
86
+ class InfrahubTaskManager(InfraHubTaskManagerBase):
87
+ client: InfrahubClient
88
+
89
+ def __init__(self, client: InfrahubClient):
90
+ self.client = client
91
+
92
+ async def count(self, filters: TaskFilter | None = None) -> int:
93
+ """Count the number of tasks.
94
+
95
+ Args:
96
+ filters: The filter to apply to the tasks. Defaults to None.
97
+
98
+ Returns:
99
+ The number of tasks.
100
+ """
101
+
102
+ query = self._generate_count_query(filters=filters)
103
+ response = await self.client.execute_graphql(
104
+ query=query.render(convert_enum=False), tracker="query-tasks-count"
105
+ )
106
+ return int(response["InfrahubTask"]["count"])
107
+
108
+ async def all(
109
+ self,
110
+ limit: int | None = None,
111
+ offset: int | None = None,
112
+ timeout: int | None = None,
113
+ parallel: bool = False,
114
+ include_logs: bool = False,
115
+ include_related_nodes: bool = False,
116
+ ) -> list[Task]:
117
+ """Get all tasks.
118
+
119
+ Args:
120
+ limit: The maximum number of tasks to return. Defaults to None.
121
+ offset: The offset to start the tasks from. Defaults to None.
122
+ timeout: The timeout to wait for the tasks to complete. Defaults to None.
123
+ parallel: Whether to query the tasks in parallel. Defaults to False.
124
+ include_logs: Whether to include the logs in the tasks. Defaults to False.
125
+ include_related_nodes: Whether to include the related nodes in the tasks. Defaults to False.
126
+
127
+ Returns:
128
+ A list of tasks.
129
+ """
130
+
131
+ return await self.filter(
132
+ limit=limit,
133
+ offset=offset,
134
+ timeout=timeout,
135
+ parallel=parallel,
136
+ include_logs=include_logs,
137
+ include_related_nodes=include_related_nodes,
138
+ )
139
+
140
+ async def filter(
141
+ self,
142
+ filter: TaskFilter | None = None,
143
+ limit: int | None = None,
144
+ offset: int | None = None,
145
+ timeout: int | None = None,
146
+ parallel: bool = False,
147
+ include_logs: bool = False,
148
+ include_related_nodes: bool = False,
149
+ ) -> list[Task]:
150
+ """Filter tasks.
151
+
152
+ Args:
153
+ filter: The filter to apply to the tasks. Defaults to None.
154
+ limit: The maximum number of tasks to return. Defaults to None.
155
+ offset: The offset to start the tasks from. Defaults to None.
156
+ timeout: The timeout to wait for the tasks to complete. Defaults to None.
157
+ parallel: Whether to query the tasks in parallel. Defaults to False.
158
+ include_logs: Whether to include the logs in the tasks. Defaults to False.
159
+ include_related_nodes: Whether to include the related nodes in the tasks. Defaults to False.
160
+
161
+ Returns:
162
+ A list of tasks.
163
+ """
164
+ if filter is None:
165
+ filter = TaskFilter()
166
+
167
+ if limit:
168
+ tasks, _ = await self.process_page(
169
+ self.client, self._generate_query(filters=filter, offset=offset, limit=limit, count=False), 1, timeout
170
+ )
171
+ return tasks
172
+
173
+ if parallel:
174
+ return await self.process_batch(
175
+ filters=filter,
176
+ timeout=timeout,
177
+ include_logs=include_logs,
178
+ include_related_nodes=include_related_nodes,
179
+ )
180
+
181
+ return await self.process_non_batch(
182
+ filters=filter,
183
+ offset=offset,
184
+ limit=limit,
185
+ timeout=timeout,
186
+ include_logs=include_logs,
187
+ include_related_nodes=include_related_nodes,
188
+ )
189
+
190
+ async def get(self, id: str, include_logs: bool = False, include_related_nodes: bool = False) -> Task:
191
+ tasks = await self.filter(
192
+ filter=TaskFilter(ids=[id]),
193
+ include_logs=include_logs,
194
+ include_related_nodes=include_related_nodes,
195
+ parallel=False,
196
+ )
197
+ if not tasks:
198
+ raise TaskNotFoundError(id=id)
199
+
200
+ if len(tasks) != 1:
201
+ raise TooManyTasksError(expected_id=id, received_ids=[task.id for task in tasks])
202
+
203
+ return tasks[0]
204
+
205
+ async def wait_for_completion(self, id: str, interval: int = 1, timeout: int = 60) -> Task:
206
+ """Wait for a task to complete.
207
+
208
+ Args:
209
+ id: The id of the task to wait for.
210
+ interval: The interval to check the task state. Defaults to 1.
211
+ timeout: The timeout to wait for the task to complete. Defaults to 60.
212
+
213
+ Raises:
214
+ TaskNotCompletedError: The task did not complete in the given timeout.
215
+
216
+ Returns:
217
+ The task object.
218
+ """
219
+ for _ in range(timeout // interval):
220
+ task = await self.get(id=id)
221
+ if task.state in FINAL_STATES:
222
+ return task
223
+ await asyncio.sleep(interval)
224
+ raise TaskNotCompletedError(id=id, message=f"Task {id} did not complete in {timeout} seconds")
225
+
226
+ @staticmethod
227
+ async def process_page(
228
+ client: InfrahubClient, query: Query, page_number: int, timeout: int | None = None
229
+ ) -> tuple[list[Task], int | None]:
230
+ """Process a single page of results.
231
+
232
+ Args:
233
+ client: The client to use to execute the query.
234
+ query: The query to execute.
235
+ page_number: The page number to process.
236
+ timeout: The timeout to wait for the query to complete. Defaults to None.
237
+
238
+ Returns:
239
+ A tuple containing a list of tasks and the count of tasks.
240
+ """
241
+
242
+ response = await client.execute_graphql(
243
+ query=query.render(convert_enum=False),
244
+ tracker=f"query-tasks-page{page_number}",
245
+ timeout=timeout,
246
+ )
247
+ count = response["InfrahubTask"].get("count", None)
248
+ return [Task.from_graphql(task["node"]) for task in response["InfrahubTask"]["edges"]], count
249
+
250
+ async def process_batch(
251
+ self,
252
+ filters: TaskFilter | None = None,
253
+ timeout: int | None = None,
254
+ include_logs: bool = False,
255
+ include_related_nodes: bool = False,
256
+ ) -> list[Task]:
257
+ """Process queries in parallel mode."""
258
+ pagination_size = self.client.pagination_size
259
+ tasks = []
260
+ batch_process = await self.client.create_batch()
261
+ count = await self.count(filters=filters)
262
+ total_pages = (count + pagination_size - 1) // pagination_size
263
+
264
+ for page_number in range(1, total_pages + 1):
265
+ page_offset = (page_number - 1) * pagination_size
266
+ query = self._generate_query(
267
+ filters=filters,
268
+ offset=page_offset,
269
+ limit=pagination_size,
270
+ include_logs=include_logs,
271
+ include_related_nodes=include_related_nodes,
272
+ count=False,
273
+ )
274
+ batch_process.add(
275
+ task=self.process_page, client=self.client, query=query, page_number=page_number, timeout=timeout
276
+ )
277
+
278
+ async for _, (new_tasks, _) in batch_process.execute():
279
+ tasks.extend(new_tasks)
280
+
281
+ return tasks
282
+
283
+ async def process_non_batch(
284
+ self,
285
+ filters: TaskFilter | None = None,
286
+ offset: int | None = None,
287
+ limit: int | None = None,
288
+ timeout: int | None = None,
289
+ include_logs: bool = False,
290
+ include_related_nodes: bool = False,
291
+ ) -> list[Task]:
292
+ """Process queries without parallel mode."""
293
+ tasks = []
294
+ has_remaining_items = True
295
+ page_number = 1
296
+
297
+ while has_remaining_items:
298
+ page_offset = (page_number - 1) * self.client.pagination_size
299
+ query = self._generate_query(
300
+ filters=filters,
301
+ offset=page_offset,
302
+ limit=self.client.pagination_size,
303
+ include_logs=include_logs,
304
+ include_related_nodes=include_related_nodes,
305
+ count=True,
306
+ )
307
+ new_tasks, count = await self.process_page(
308
+ client=self.client, query=query, page_number=page_number, timeout=timeout
309
+ )
310
+ if count is None:
311
+ raise ValueError("Count is None, a value must be retrieve from the query")
312
+
313
+ tasks.extend(new_tasks)
314
+ remaining_items = count - (page_offset + self.client.pagination_size)
315
+ if remaining_items < 0 or offset is not None or limit is not None:
316
+ has_remaining_items = False
317
+ page_number += 1
318
+ return tasks
319
+
320
+
321
+ class InfrahubTaskManagerSync(InfraHubTaskManagerBase):
322
+ client: InfrahubClientSync
323
+
324
+ def __init__(self, client: InfrahubClientSync):
325
+ self.client = client
326
+
327
+ def count(self, filters: TaskFilter | None = None) -> int:
328
+ """Count the number of tasks.
329
+
330
+ Args:
331
+ filters: The filter to apply to the tasks. Defaults to None.
332
+
333
+ Returns:
334
+ The number of tasks.
335
+ """
336
+
337
+ query = self._generate_count_query(filters=filters)
338
+ response = self.client.execute_graphql(query=query.render(convert_enum=False), tracker="query-tasks-count")
339
+ return int(response["InfrahubTask"]["count"])
340
+
341
+ def all(
342
+ self,
343
+ limit: int | None = None,
344
+ offset: int | None = None,
345
+ timeout: int | None = None,
346
+ parallel: bool = False,
347
+ include_logs: bool = False,
348
+ include_related_nodes: bool = False,
349
+ ) -> list[Task]:
350
+ """Get all tasks.
351
+
352
+ Args:
353
+ limit: The maximum number of tasks to return. Defaults to None.
354
+ offset: The offset to start the tasks from. Defaults to None.
355
+ timeout: The timeout to wait for the tasks to complete. Defaults to None.
356
+ parallel: Whether to query the tasks in parallel. Defaults to False.
357
+ include_logs: Whether to include the logs in the tasks. Defaults to False.
358
+ include_related_nodes: Whether to include the related nodes in the tasks. Defaults to False.
359
+
360
+ Returns:
361
+ A list of tasks.
362
+ """
363
+
364
+ return self.filter(
365
+ limit=limit,
366
+ offset=offset,
367
+ timeout=timeout,
368
+ parallel=parallel,
369
+ include_logs=include_logs,
370
+ include_related_nodes=include_related_nodes,
371
+ )
372
+
373
+ def filter(
374
+ self,
375
+ filter: TaskFilter | None = None,
376
+ limit: int | None = None,
377
+ offset: int | None = None,
378
+ timeout: int | None = None,
379
+ parallel: bool = False,
380
+ include_logs: bool = False,
381
+ include_related_nodes: bool = False,
382
+ ) -> list[Task]:
383
+ """Filter tasks.
384
+
385
+ Args:
386
+ filter: The filter to apply to the tasks. Defaults to None.
387
+ limit: The maximum number of tasks to return. Defaults to None.
388
+ offset: The offset to start the tasks from. Defaults to None.
389
+ timeout: The timeout to wait for the tasks to complete. Defaults to None.
390
+ parallel: Whether to query the tasks in parallel. Defaults to False.
391
+ include_logs: Whether to include the logs in the tasks. Defaults to False.
392
+ include_related_nodes: Whether to include the related nodes in the tasks. Defaults to False.
393
+
394
+ Returns:
395
+ A list of tasks.
396
+ """
397
+ if filter is None:
398
+ filter = TaskFilter()
399
+
400
+ if limit:
401
+ tasks, _ = self.process_page(
402
+ self.client, self._generate_query(filters=filter, offset=offset, limit=limit, count=False), 1, timeout
403
+ )
404
+ return tasks
405
+
406
+ if parallel:
407
+ return self.process_batch(
408
+ filters=filter,
409
+ timeout=timeout,
410
+ include_logs=include_logs,
411
+ include_related_nodes=include_related_nodes,
412
+ )
413
+
414
+ return self.process_non_batch(
415
+ filters=filter,
416
+ offset=offset,
417
+ limit=limit,
418
+ timeout=timeout,
419
+ include_logs=include_logs,
420
+ include_related_nodes=include_related_nodes,
421
+ )
422
+
423
+ def get(self, id: str, include_logs: bool = False, include_related_nodes: bool = False) -> Task:
424
+ tasks = self.filter(
425
+ filter=TaskFilter(ids=[id]),
426
+ include_logs=include_logs,
427
+ include_related_nodes=include_related_nodes,
428
+ parallel=False,
429
+ )
430
+ if not tasks:
431
+ raise TaskNotFoundError(id=id)
432
+
433
+ if len(tasks) != 1:
434
+ raise TooManyTasksError(expected_id=id, received_ids=[task.id for task in tasks])
435
+
436
+ return tasks[0]
437
+
438
+ def wait_for_completion(self, id: str, interval: int = 1, timeout: int = 60) -> Task:
439
+ """Wait for a task to complete.
440
+
441
+ Args:
442
+ id: The id of the task to wait for.
443
+ interval: The interval to check the task state. Defaults to 1.
444
+ timeout: The timeout to wait for the task to complete. Defaults to 60.
445
+
446
+ Raises:
447
+ TaskNotCompletedError: The task did not complete in the given timeout.
448
+
449
+ Returns:
450
+ The task object.
451
+ """
452
+ for _ in range(timeout // interval):
453
+ task = self.get(id=id)
454
+ if task.state in FINAL_STATES:
455
+ return task
456
+ time.sleep(interval)
457
+ raise TaskNotCompletedError(id=id, message=f"Task {id} did not complete in {timeout} seconds")
458
+
459
+ @staticmethod
460
+ def process_page(
461
+ client: InfrahubClientSync, query: Query, page_number: int, timeout: int | None = None
462
+ ) -> tuple[list[Task], int | None]:
463
+ """Process a single page of results.
464
+
465
+ Args:
466
+ client: The client to use to execute the query.
467
+ query: The query to execute.
468
+ page_number: The page number to process.
469
+ timeout: The timeout to wait for the query to complete. Defaults to None.
470
+
471
+ Returns:
472
+ A tuple containing a list of tasks and the count of tasks.
473
+ """
474
+
475
+ response = client.execute_graphql(
476
+ query=query.render(convert_enum=False),
477
+ tracker=f"query-tasks-page{page_number}",
478
+ timeout=timeout,
479
+ )
480
+ count = response["InfrahubTask"].get("count", None)
481
+ return [Task.from_graphql(task["node"]) for task in response["InfrahubTask"]["edges"]], count
482
+
483
+ def process_batch(
484
+ self,
485
+ filters: TaskFilter | None = None,
486
+ timeout: int | None = None,
487
+ include_logs: bool = False,
488
+ include_related_nodes: bool = False,
489
+ ) -> list[Task]:
490
+ """Process queries in parallel mode."""
491
+ pagination_size = self.client.pagination_size
492
+ tasks = []
493
+ batch_process = self.client.create_batch()
494
+ count = self.count(filters=filters)
495
+ total_pages = (count + pagination_size - 1) // pagination_size
496
+
497
+ for page_number in range(1, total_pages + 1):
498
+ page_offset = (page_number - 1) * pagination_size
499
+ query = self._generate_query(
500
+ filters=filters,
501
+ offset=page_offset,
502
+ limit=pagination_size,
503
+ include_logs=include_logs,
504
+ include_related_nodes=include_related_nodes,
505
+ count=False,
506
+ )
507
+ batch_process.add(
508
+ task=self.process_page, client=self.client, query=query, page_number=page_number, timeout=timeout
509
+ )
510
+
511
+ for _, (new_tasks, _) in batch_process.execute():
512
+ tasks.extend(new_tasks)
513
+
514
+ return tasks
515
+
516
+ def process_non_batch(
517
+ self,
518
+ filters: TaskFilter | None = None,
519
+ offset: int | None = None,
520
+ limit: int | None = None,
521
+ timeout: int | None = None,
522
+ include_logs: bool = False,
523
+ include_related_nodes: bool = False,
524
+ ) -> list[Task]:
525
+ """Process queries without parallel mode."""
526
+ tasks = []
527
+ has_remaining_items = True
528
+ page_number = 1
529
+
530
+ while has_remaining_items:
531
+ page_offset = (page_number - 1) * self.client.pagination_size
532
+ query = self._generate_query(
533
+ filters=filters,
534
+ offset=page_offset,
535
+ limit=self.client.pagination_size,
536
+ include_logs=include_logs,
537
+ include_related_nodes=include_related_nodes,
538
+ count=True,
539
+ )
540
+ new_tasks, count = self.process_page(
541
+ client=self.client, query=query, page_number=page_number, timeout=timeout
542
+ )
543
+ if count is None:
544
+ raise ValueError("Count is None, a value must be retrieve from the query")
545
+
546
+ tasks.extend(new_tasks)
547
+ remaining_items = count - (page_offset + self.client.pagination_size)
548
+ if remaining_items < 0 or offset is not None or limit is not None:
549
+ has_remaining_items = False
550
+ page_number += 1
551
+ return tasks
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class TaskState(str, Enum):
10
+ SCHEDULED = "SCHEDULED"
11
+ PENDING = "PENDING"
12
+ RUNNING = "RUNNING"
13
+ COMPLETED = "COMPLETED"
14
+ FAILED = "FAILED"
15
+ CANCELLED = "CANCELLED"
16
+ CRASHED = "CRASHED"
17
+ PAUSED = "PAUSED"
18
+ CANCELLING = "CANCELLING"
19
+
20
+
21
+ class TaskLog(BaseModel):
22
+ message: str
23
+ severity: str
24
+ timestamp: datetime
25
+
26
+
27
+ class TaskRelatedNode(BaseModel):
28
+ id: str
29
+ kind: str
30
+
31
+
32
+ class Task(BaseModel):
33
+ id: str
34
+ title: str
35
+ state: TaskState
36
+ progress: float | None = None
37
+ workflow: str | None = None
38
+ branch: str | None = None
39
+ # start_time: datetime # Is it still required
40
+ created_at: datetime
41
+ updated_at: datetime
42
+ parameters: dict | None = None
43
+ tags: list[str] | None = None
44
+ related_nodes: list[TaskRelatedNode] = Field(default_factory=list)
45
+ logs: list[TaskLog] = Field(default_factory=list)
46
+
47
+ @classmethod
48
+ def from_graphql(cls, data: dict) -> Task:
49
+ related_nodes: list[TaskRelatedNode] = []
50
+ logs: list[TaskLog] = []
51
+
52
+ if data.get("related_nodes"):
53
+ related_nodes = [TaskRelatedNode(**item) for item in data["related_nodes"]]
54
+ del data["related_nodes"]
55
+
56
+ if data.get("logs"):
57
+ logs = [TaskLog(**item["node"]) for item in data["logs"]["edges"]]
58
+ del data["logs"]
59
+
60
+ return cls(**data, related_nodes=related_nodes, logs=logs)
61
+
62
+
63
+ class TaskFilter(BaseModel):
64
+ ids: list[str] | None = None
65
+ q: str | None = None
66
+ branch: str | None = None
67
+ state: list[TaskState] | None = None
68
+ workflow: list[str] | None = None
69
+ limit: int | None = None
70
+ offset: int | None = None
71
+ related_node__ids: list[str] | None = None
72
+
73
+ def to_dict(self) -> dict:
74
+ return self.model_dump(exclude_none=True)
@@ -80,6 +80,7 @@ class SchemaAnimal:
80
80
  namespace=NAMESPACE,
81
81
  include_in_menu=True,
82
82
  inherit_from=[TESTING_ANIMAL],
83
+ human_friendly_id=["owner__name__value", "name__value", "color__value"],
83
84
  display_labels=["name__value", "breed__value", "color__value"],
84
85
  order_by=["name__value"],
85
86
  attributes=[
@@ -108,6 +109,14 @@ class SchemaAnimal:
108
109
  identifier="person__animal",
109
110
  cardinality="many",
110
111
  direction=RelationshipDirection.INBOUND,
112
+ max_count=10,
113
+ ),
114
+ Rel(
115
+ name="favorite_animal",
116
+ peer=TESTING_ANIMAL,
117
+ identifier="favorite_animal",
118
+ cardinality="one",
119
+ direction=RelationshipDirection.INBOUND,
111
120
  ),
112
121
  Rel(
113
122
  name="best_friends",