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
infrahub/lock.py CHANGED
@@ -5,7 +5,7 @@ import time
5
5
  import uuid
6
6
  from asyncio import Lock as LocalLock
7
7
  from asyncio import sleep
8
- from typing import TYPE_CHECKING, Optional, Union
8
+ from typing import TYPE_CHECKING
9
9
 
10
10
  import redis.asyncio as redis
11
11
  from prometheus_client import Histogram
@@ -47,7 +47,7 @@ GLOBAL_GRAPH_LOCK = "global.graph"
47
47
  class InfrahubMultiLock:
48
48
  """Context manager to allow multiple locks to be reserved together"""
49
49
 
50
- def __init__(self, lock_registry: InfrahubLockRegistry, locks: Optional[list[str]] = None) -> None:
50
+ def __init__(self, lock_registry: InfrahubLockRegistry, locks: list[str] | None = None) -> None:
51
51
  self.registry = lock_registry
52
52
  self.locks = locks or []
53
53
 
@@ -56,9 +56,9 @@ class InfrahubMultiLock:
56
56
 
57
57
  async def __aexit__(
58
58
  self,
59
- exc_type: Optional[type[BaseException]],
60
- exc_value: Optional[BaseException],
61
- traceback: Optional[TracebackType],
59
+ exc_type: type[BaseException] | None,
60
+ exc_value: BaseException | None,
61
+ traceback: TracebackType | None,
62
62
  ):
63
63
  await self.release()
64
64
 
@@ -84,9 +84,9 @@ class NATSLock:
84
84
 
85
85
  async def __aexit__(
86
86
  self,
87
- exc_type: Optional[type[BaseException]],
88
- exc_value: Optional[BaseException],
89
- traceback: Optional[TracebackType],
87
+ exc_type: type[BaseException] | None,
88
+ exc_value: BaseException | None,
89
+ traceback: TracebackType | None,
90
90
  ):
91
91
  await self.release()
92
92
 
@@ -117,18 +117,18 @@ class InfrahubLock:
117
117
  def __init__(
118
118
  self,
119
119
  name: str,
120
- connection: Optional[Union[redis.Redis, InfrahubServices]] = None,
121
- local: Optional[bool] = None,
120
+ connection: redis.Redis | InfrahubServices | None = None,
121
+ local: bool | None = None,
122
122
  in_multi: bool = False,
123
123
  ) -> None:
124
124
  self.use_local: bool = local
125
125
  self.local: LocalLock = None
126
126
  self.remote: GlobalLock = None
127
127
  self.name: str = name
128
- self.connection: Optional[redis.Redis] = connection
128
+ self.connection: redis.Redis | None = connection
129
129
  self.in_multi: bool = in_multi
130
130
  self.lock_type: str = "multi" if self.in_multi else "individual"
131
- self.acquire_time: Optional[int] = None
131
+ self.acquire_time: int | None = None
132
132
  self.event = asyncio.Event()
133
133
 
134
134
  if not self.connection or (self.use_local is None and name.startswith("local.")):
@@ -146,9 +146,9 @@ class InfrahubLock:
146
146
 
147
147
  async def __aexit__(
148
148
  self,
149
- exc_type: Optional[type[BaseException]],
150
- exc_value: Optional[BaseException],
151
- traceback: Optional[TracebackType],
149
+ exc_type: type[BaseException] | None,
150
+ exc_value: BaseException | None,
151
+ traceback: TracebackType | None,
152
152
  ):
153
153
  await self.release()
154
154
 
@@ -179,7 +179,7 @@ class InfrahubLock:
179
179
 
180
180
  class InfrahubLockRegistry:
181
181
  def __init__(
182
- self, token: Optional[str] = None, local_only: bool = False, service: Optional[InfrahubServices] = None
182
+ self, token: str | None = None, local_only: bool = False, service: InfrahubServices | None = None
183
183
  ) -> None:
184
184
  if config.SETTINGS.cache.enable and not local_only:
185
185
  if config.SETTINGS.cache.driver == config.CacheDriver.Redis:
@@ -201,7 +201,7 @@ class InfrahubLockRegistry:
201
201
  self.locks: dict[str, InfrahubLock] = {}
202
202
 
203
203
  @classmethod
204
- def _generate_name(cls, name: str, namespace: Optional[str] = None, local: Optional[bool] = None) -> str:
204
+ def _generate_name(cls, name: str, namespace: str | None = None, local: bool | None = None) -> str:
205
205
  if namespace is None and local is None:
206
206
  return name
207
207
 
@@ -221,7 +221,7 @@ class InfrahubLockRegistry:
221
221
  self,
222
222
  name: str,
223
223
  namespace: str | None,
224
- local: Optional[bool] = None,
224
+ local: bool | None = None,
225
225
  ) -> InfrahubLock | None:
226
226
  lock_name = self._generate_name(name=name, namespace=namespace, local=local)
227
227
  if lock_name not in self.locks:
@@ -229,7 +229,7 @@ class InfrahubLockRegistry:
229
229
  return self.locks[lock_name]
230
230
 
231
231
  def get(
232
- self, name: str, namespace: Optional[str] = None, local: Optional[bool] = None, in_multi: bool = False
232
+ self, name: str, namespace: str | None = None, local: bool | None = None, in_multi: bool = False
233
233
  ) -> InfrahubLock:
234
234
  lock_name = self._generate_name(name=name, namespace=namespace, local=local)
235
235
  if lock_name not in self.locks:
@@ -252,7 +252,7 @@ class InfrahubLockRegistry:
252
252
  return InfrahubMultiLock(lock_registry=self, locks=[LOCAL_SCHEMA_LOCK, GLOBAL_GRAPH_LOCK, GLOBAL_SCHEMA_LOCK])
253
253
 
254
254
 
255
- def initialize_lock(local_only: bool = False, service: Optional[InfrahubServices] = None) -> None:
255
+ def initialize_lock(local_only: bool = False, service: InfrahubServices | None = None) -> None:
256
256
  global registry
257
257
  registry = InfrahubLockRegistry(local_only=local_only, service=service)
258
258
 
@@ -8,4 +8,3 @@ class MenuSection(InfrahubStringEnum):
8
8
 
9
9
  DEFAULT_MENU = "Other"
10
10
  FULL_DEFAULT_MENU = "BuiltinOther"
11
- TEMPLATE_MENU = "Object templates"
@@ -3,11 +3,10 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from infrahub.core import registry
6
- from infrahub.core.constants import InfrahubKind
7
6
  from infrahub.core.protocols import CoreMenuItem
8
7
  from infrahub.log import get_logger
9
8
 
10
- from .constants import FULL_DEFAULT_MENU, TEMPLATE_MENU
9
+ from .constants import FULL_DEFAULT_MENU
11
10
  from .models import MenuDict, MenuItemDict
12
11
 
13
12
  if TYPE_CHECKING:
@@ -71,7 +70,7 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
71
70
  menu_item = structure.find_item(name=parent_full_name)
72
71
  if menu_item:
73
72
  child_item = MenuItemDict.from_node(obj=item)
74
- menu_item.children[child_item.identifier] = child_item
73
+ menu_item.children[str(child_item.identifier)] = child_item
75
74
  else:
76
75
  log.warning(
77
76
  "new_menu_request: unable to find the parent menu item",
@@ -82,11 +81,6 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
82
81
 
83
82
  items_to_add = {schema.kind: False for schema in full_schema.values() if schema.include_in_menu is True}
84
83
 
85
- # Object templates should go into a dedicated menu item
86
- object_templates_menu = MenuItemDict(
87
- identifier=InfrahubKind.OBJECTTEMPLATE, label=TEMPLATE_MENU.title(), icon="mdi:pencil-ruler", order_weight=20000
88
- )
89
-
90
84
  nbr_remaining_items_last_round = len(items_to_add.values())
91
85
  nbr_remaining_items = len([value for value in items_to_add.values() if value is False])
92
86
  while not all(items_to_add.values()):
@@ -96,23 +90,20 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
96
90
 
97
91
  schema = full_schema[item_name]
98
92
  menu_item = MenuItemDict.from_schema(model=schema)
99
- already_in_schema = bool(structure.find_item(name=menu_item.identifier))
93
+ already_in_schema = bool(structure.find_item(name=str(menu_item.identifier)))
100
94
  if already_in_schema:
101
95
  items_to_add[item_name] = True
102
96
  continue
103
97
 
104
- if schema.namespace == "Template":
105
- object_templates_menu.children[menu_item.identifier] = menu_item
106
- items_to_add[item_name] = True
107
- elif not schema.menu_placement:
98
+ if not schema.menu_placement:
108
99
  first_element = MenuItemDict.from_schema(model=schema)
109
- first_element.identifier = f"{first_element.identifier}Sub"
100
+ first_element.name = f"{first_element.name}Sub"
110
101
  first_element.order_weight = 1
111
- menu_item.children[first_element.identifier] = first_element
112
- structure.data[menu_item.identifier] = menu_item
102
+ menu_item.children[str(first_element.identifier)] = first_element
103
+ structure.data[str(menu_item.identifier)] = menu_item
113
104
  items_to_add[item_name] = True
114
105
  elif menu_placement := structure.find_item(name=schema.menu_placement):
115
- menu_placement.children[menu_item.identifier] = menu_item
106
+ menu_placement.children[str(menu_item.identifier)] = menu_item
116
107
  items_to_add[item_name] = True
117
108
  continue
118
109
 
@@ -121,9 +112,6 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
121
112
  break
122
113
  nbr_remaining_items_last_round = nbr_remaining_items
123
114
 
124
- if object_templates_menu.children:
125
- structure.data[object_templates_menu.identifier] = object_templates_menu
126
-
127
115
  # ----------------------------------------------------------------------------
128
116
  # Assign the remaining items for which we couldn't find the menu_placement to the default menu
129
117
  # ----------------------------------------------------------------------------
@@ -142,7 +130,7 @@ async def generate_menu(db: InfrahubDatabase, branch: Branch, menu_items: list[C
142
130
  item=schema.kind,
143
131
  menu_placement=schema.menu_placement,
144
132
  )
145
- default_menu.children[menu_item.identifier] = menu_item
133
+ default_menu.children[str(menu_item.identifier)] = menu_item
146
134
  items_to_add[item_name] = True
147
135
 
148
136
  return structure
infrahub/menu/menu.py CHANGED
@@ -135,6 +135,16 @@ default_menu = [
135
135
  section=MenuSection.INTERNAL,
136
136
  order_weight=1500,
137
137
  ),
138
+ MenuItemDefinition(
139
+ namespace="Builtin",
140
+ name="Templates",
141
+ label="Templates",
142
+ kind=InfrahubKind.OBJECTTEMPLATE,
143
+ icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.OBJECTTEMPLATE)),
144
+ protected=True,
145
+ section=MenuSection.INTERNAL,
146
+ order_weight=2000,
147
+ ),
138
148
  MenuItemDefinition(
139
149
  namespace="Builtin",
140
150
  name="ResourceManager",
@@ -143,7 +153,7 @@ default_menu = [
143
153
  icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.RESOURCEPOOL)),
144
154
  protected=True,
145
155
  section=MenuSection.INTERNAL,
146
- order_weight=2000,
156
+ order_weight=2500,
147
157
  ),
148
158
  MenuItemDefinition(
149
159
  namespace="Builtin",
@@ -153,7 +163,7 @@ default_menu = [
153
163
  icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.ARTIFACT)),
154
164
  protected=True,
155
165
  section=MenuSection.INTERNAL,
156
- order_weight=2500,
166
+ order_weight=3000,
157
167
  ),
158
168
  MenuItemDefinition(
159
169
  namespace="Builtin",
@@ -175,16 +185,6 @@ default_menu = [
175
185
  section=MenuSection.INTERNAL,
176
186
  order_weight=3500,
177
187
  ),
178
- MenuItemDefinition(
179
- namespace="Builtin",
180
- name="ActivityLogs",
181
- label="Activity Logs",
182
- path="/activities",
183
- icon="mdi:format-list-bulleted",
184
- protected=True,
185
- section=MenuSection.INTERNAL,
186
- order_weight=4000,
187
- ),
188
188
  ],
189
189
  ),
190
190
  MenuItemDefinition(
@@ -261,32 +261,11 @@ default_menu = [
261
261
  namespace="Builtin",
262
262
  name="Webhooks",
263
263
  label="Webhooks",
264
- icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.CUSTOMWEBHOOK)),
264
+ icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.WEBHOOK)),
265
+ kind=InfrahubKind.WEBHOOK,
265
266
  protected=True,
266
267
  section=MenuSection.INTERNAL,
267
268
  order_weight=3000,
268
- children=[
269
- MenuItemDefinition(
270
- namespace="Builtin",
271
- name="WebhookStandard",
272
- label="Webhooks",
273
- kind=InfrahubKind.STANDARDWEBHOOK,
274
- icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.STANDARDWEBHOOK)),
275
- protected=True,
276
- section=MenuSection.INTERNAL,
277
- order_weight=1000,
278
- ),
279
- MenuItemDefinition(
280
- namespace="Builtin",
281
- name="WebhookCustom",
282
- label="Custom Webhooks",
283
- kind=InfrahubKind.CUSTOMWEBHOOK,
284
- icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.CUSTOMWEBHOOK)),
285
- protected=True,
286
- section=MenuSection.INTERNAL,
287
- order_weight=2000,
288
- ),
289
- ],
290
269
  ),
291
270
  MenuItemDefinition(
292
271
  namespace="Builtin",
@@ -331,9 +310,9 @@ default_menu = [
331
310
  ),
332
311
  MenuItemDefinition(
333
312
  namespace="Builtin",
334
- name="ActivityLog",
335
- label="Activity Log",
336
- path="/activity-log",
313
+ name="Activities",
314
+ label="Activities",
315
+ path="/activities",
337
316
  icon="mdi:timeline-text",
338
317
  protected=True,
339
318
  section=MenuSection.INTERNAL,
infrahub/menu/models.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
- from pydantic import BaseModel, Field
6
+ from pydantic import BaseModel, Field, computed_field
7
7
  from typing_extensions import Self
8
8
 
9
9
  from infrahub.core.account import GlobalPermission
@@ -35,21 +35,26 @@ def _get_full_name_schema(node: MainSchemaTypes) -> str:
35
35
  class MenuDict:
36
36
  data: dict[str, MenuItemDict] = field(default_factory=dict)
37
37
 
38
+ def get_item_location(self, name: str) -> list[str]:
39
+ location, _ = self._find_child_item(name=name, children=self.data)
40
+ return location
41
+
38
42
  def find_item(self, name: str) -> MenuItemDict | None:
39
- return self._find_child_item(name=name, children=self.data)
43
+ _, item = self._find_child_item(name=name, children=self.data)
44
+ return item
40
45
 
41
46
  @classmethod
42
- def _find_child_item(cls, name: str, children: dict[str, MenuItemDict]) -> MenuItemDict | None:
47
+ def _find_child_item(cls, name: str, children: dict[str, MenuItemDict]) -> tuple[list[str], MenuItemDict | None]:
43
48
  if name in children.keys():
44
- return children[name]
49
+ return [], children[name]
45
50
 
46
51
  for child in children.values():
47
52
  if not child.children:
48
53
  continue
49
- found = cls._find_child_item(name=name, children=child.children)
54
+ position, found = cls._find_child_item(name=name, children=child.children)
50
55
  if found:
51
- return found
52
- return None
56
+ return [str(child.identifier)] + position, found
57
+ return [], None
53
58
 
54
59
  def to_rest(self) -> Menu:
55
60
  data: dict[str, list[MenuItemList]] = {}
@@ -62,10 +67,15 @@ class MenuDict:
62
67
 
63
68
  return Menu(sections=data)
64
69
 
65
- # @staticmethod
66
- # def _sort_menu_items(items: dict[str, MenuItem]) -> dict[str, MenuItem]:
67
- # sorted_dict = dict(sorted(items.items(), key=lambda x: (x[1].order_weight, x[0]), reverse=False))
68
- # return sorted_dict
70
+ @classmethod
71
+ def from_definition_list(cls, definitions: list[MenuItemDefinition]) -> Self:
72
+ menu = cls()
73
+ for definition in definitions:
74
+ menu.data[definition.full_name] = MenuItemDict.from_definition(definition=definition)
75
+ return menu
76
+
77
+ def get_all_identifiers(self) -> set[str]:
78
+ return {identifier for item in self.data.values() for identifier in item.get_all_identifiers()}
69
79
 
70
80
 
71
81
  @dataclass
@@ -74,7 +84,11 @@ class Menu:
74
84
 
75
85
 
76
86
  class MenuItem(BaseModel):
77
- identifier: str = Field(..., description="Unique identifier for this menu item")
87
+ id: str | None = None
88
+ namespace: str = Field(..., description="Namespace of the menu item")
89
+ name: str = Field(..., description="Name of the menu item")
90
+ description: str = Field(default="", description="Description of the menu item")
91
+ protected: bool = Field(default=False, description="Whether the menu item is protected")
78
92
  label: str = Field(..., description="Title of the menu item")
79
93
  path: str = Field(default="", description="URL endpoint if applicable")
80
94
  icon: str = Field(default="", description="The icon to show for the current view")
@@ -83,10 +97,27 @@ class MenuItem(BaseModel):
83
97
  section: MenuSection = MenuSection.OBJECT
84
98
  permissions: list[str] = Field(default_factory=list)
85
99
 
100
+ @computed_field
101
+ def identifier(self) -> str:
102
+ return f"{self.namespace}{self.name}"
103
+
104
+ def get_path(self) -> str | None:
105
+ if self.path:
106
+ return self.path
107
+
108
+ if self.kind:
109
+ return f"/objects/{self.kind}"
110
+
111
+ return None
112
+
86
113
  @classmethod
87
114
  def from_node(cls, obj: CoreMenuItem) -> Self:
88
115
  return cls(
89
- identifier=get_full_name(obj),
116
+ id=obj.get_id(),
117
+ name=obj.name.value,
118
+ namespace=obj.namespace.value,
119
+ protected=obj.protected.value,
120
+ description=obj.description.value or "",
90
121
  label=obj.label.value or "",
91
122
  icon=obj.icon.value or "",
92
123
  order_weight=obj.order_weight.value,
@@ -96,10 +127,30 @@ class MenuItem(BaseModel):
96
127
  permissions=obj.required_permissions.value or [],
97
128
  )
98
129
 
130
+ async def to_node(self, db: InfrahubDatabase, parent: CoreMenuItem | None = None) -> CoreMenuItem:
131
+ obj = await Node.init(db=db, schema=CoreMenuItem)
132
+ await obj.new(
133
+ db=db,
134
+ namespace=self.namespace,
135
+ name=self.name,
136
+ label=self.label,
137
+ kind=self.kind,
138
+ path=self.get_path(),
139
+ description=self.description or None,
140
+ icon=self.icon or None,
141
+ protected=self.protected,
142
+ section=self.section.value,
143
+ order_weight=self.order_weight,
144
+ parent=parent.id if parent else None,
145
+ required_permissions=self.permissions,
146
+ )
147
+ return obj
148
+
99
149
  @classmethod
100
150
  def from_schema(cls, model: NodeSchema | GenericSchema | ProfileSchema | TemplateSchema) -> Self:
101
151
  return cls(
102
- identifier=get_full_name(model),
152
+ name=model.name,
153
+ namespace=model.namespace,
103
154
  label=model.label or model.kind,
104
155
  path=f"/objects/{model.kind}",
105
156
  icon=model.icon or "",
@@ -111,8 +162,14 @@ class MenuItemDict(MenuItem):
111
162
  hidden: bool = False
112
163
  children: dict[str, MenuItemDict] = Field(default_factory=dict, description="Child objects")
113
164
 
165
+ def get_all_identifiers(self) -> set[str]:
166
+ identifiers: set[str] = {str(self.identifier)}
167
+ for child in self.children.values():
168
+ identifiers.update(child.get_all_identifiers())
169
+ return identifiers
170
+
114
171
  def to_list(self) -> MenuItemList:
115
- data = self.model_dump(exclude={"children"})
172
+ data = self.model_dump(exclude={"children", "id"})
116
173
  unsorted_children = [child.to_list() for child in self.children.values() if child.hidden is False]
117
174
  data["children"] = sorted(unsorted_children, key=lambda d: d.order_weight)
118
175
  return MenuItemList(**data)
@@ -125,6 +182,35 @@ class MenuItemDict(MenuItem):
125
182
  permissions.append(GlobalPermission.from_string(input=permission))
126
183
  return permissions
127
184
 
185
+ def diff_attributes(self, other: Self) -> dict[str, Any]:
186
+ other_attributes = other.model_dump(exclude={"children"})
187
+ self_attributes = self.model_dump(exclude={"children"})
188
+ return {
189
+ key: value
190
+ for key, value in other_attributes.items()
191
+ if value != self_attributes[key] and key not in ["id", "children"]
192
+ }
193
+
194
+ @classmethod
195
+ def from_definition(cls, definition: MenuItemDefinition) -> Self:
196
+ menu_item = cls(
197
+ name=definition.name,
198
+ namespace=definition.namespace,
199
+ label=definition.label,
200
+ path=definition.get_path() or "",
201
+ icon=definition.icon,
202
+ kind=definition.kind,
203
+ protected=definition.protected,
204
+ section=definition.section,
205
+ permissions=definition.permissions,
206
+ order_weight=definition.order_weight,
207
+ )
208
+
209
+ for child in definition.children:
210
+ menu_item.children[child.full_name] = MenuItemDict.from_definition(definition=child)
211
+
212
+ return menu_item
213
+
128
214
 
129
215
  class MenuItemList(MenuItem):
130
216
  children: list[MenuItemList] = Field(default_factory=list, description="Child objects")
@@ -162,6 +248,21 @@ class MenuItemDefinition(BaseModel):
162
248
  )
163
249
  return obj
164
250
 
251
+ @classmethod
252
+ async def from_node(cls, node: CoreMenuItem) -> Self:
253
+ return cls(
254
+ namespace=node.namespace.value,
255
+ name=node.name.value,
256
+ label=node.label.value or "",
257
+ description=node.description.value or "",
258
+ icon=node.icon.value or "",
259
+ protected=node.protected.value,
260
+ path=node.path.value or "",
261
+ kind=node.kind.value or "",
262
+ section=node.section.value,
263
+ order_weight=node.order_weight.value,
264
+ )
265
+
165
266
  def get_path(self) -> str | None:
166
267
  if self.path:
167
268
  return self.path
@@ -0,0 +1,111 @@
1
+ from infrahub.core.manager import NodeManager
2
+ from infrahub.core.protocols import CoreMenuItem
3
+ from infrahub.database import InfrahubDatabase
4
+
5
+ from .models import MenuDict, MenuItemDefinition, MenuItemDict
6
+
7
+
8
+ class MenuRepository:
9
+ def __init__(self, db: InfrahubDatabase):
10
+ self.db = db
11
+
12
+ async def get_menu(self, nodes: dict[str, CoreMenuItem] | None = None) -> MenuDict:
13
+ menu_nodes = nodes or await self.get_menu_db()
14
+ return await self._convert_menu_from_db(nodes=menu_nodes)
15
+
16
+ async def _convert_menu_from_db(self, nodes: dict[str, CoreMenuItem]) -> MenuDict:
17
+ menu = MenuDict()
18
+ menu_by_ids = {menu_node.get_id(): MenuItemDict.from_node(menu_node) for menu_node in nodes.values()}
19
+
20
+ async def add_children(menu_item: MenuItemDict, menu_node: CoreMenuItem) -> MenuItemDict:
21
+ children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
22
+ for child_id, child_node in children.items():
23
+ child_menu_item = menu_by_ids[child_id]
24
+ child = await add_children(child_menu_item, child_node)
25
+ menu_item.children[str(child.identifier)] = child
26
+ return menu_item
27
+
28
+ for menu_node in nodes.values():
29
+ menu_item = menu_by_ids[menu_node.get_id()]
30
+ parent = await menu_node.parent.get_peer(db=self.db, peer_type=CoreMenuItem)
31
+ if parent:
32
+ continue
33
+
34
+ children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
35
+ for child_id, child_node in children.items():
36
+ child_menu_item = menu_by_ids[child_id]
37
+ child = await add_children(child_menu_item, child_node)
38
+ menu_item.children[str(child.identifier)] = child
39
+
40
+ menu.data[str(menu_item.identifier)] = menu_item
41
+
42
+ return menu
43
+
44
+ async def get_menu_db(self) -> dict[str, CoreMenuItem]:
45
+ menu_nodes = await NodeManager.query(
46
+ schema=CoreMenuItem,
47
+ filters={"namespace__value": "Builtin"},
48
+ prefetch_relationships=True,
49
+ db=self.db,
50
+ )
51
+ return {node.get_id(): node for node in menu_nodes}
52
+
53
+ async def create_menu(self, menu: list[MenuItemDefinition]) -> None:
54
+ for item in menu:
55
+ obj = await item.to_node(db=self.db)
56
+ await obj.save(db=self.db)
57
+ if item.children:
58
+ await self.create_menu_children(parent=obj, children=item.children)
59
+
60
+ async def create_menu_children(self, parent: CoreMenuItem, children: list[MenuItemDefinition]) -> None:
61
+ for child in children:
62
+ obj = await child.to_node(db=self.db, parent=parent)
63
+ await obj.save(db=self.db)
64
+ if child.children:
65
+ await self.create_menu_children(parent=obj, children=child.children)
66
+
67
+ async def update_menu(
68
+ self, existing_menu: MenuDict, new_menu: MenuDict, menu_nodes: dict[str, CoreMenuItem]
69
+ ) -> None:
70
+ async def process_menu_item(menu_item: MenuItemDict, parent: CoreMenuItem | None) -> None:
71
+ existing_item = existing_menu.find_item(name=str(menu_item.identifier))
72
+ if existing_item and existing_item.id:
73
+ node = menu_nodes[existing_item.id]
74
+ await self.update_menu_item(
75
+ node=node, existing_menu_item=existing_item, new_menu_item=menu_item, parent=parent
76
+ )
77
+ else:
78
+ node = await self.create_menu_item(new_menu_item=menu_item, parent=parent)
79
+
80
+ for child_item in menu_item.children.values():
81
+ await process_menu_item(menu_item=child_item, parent=node)
82
+
83
+ for top_level_item in new_menu.data.values():
84
+ await process_menu_item(menu_item=top_level_item, parent=None)
85
+
86
+ # Delete items that are not in the new menu
87
+ menu_to_delete = existing_menu.get_all_identifiers() - new_menu.get_all_identifiers()
88
+ for item_to_delete in menu_to_delete:
89
+ existing_item = existing_menu.find_item(name=item_to_delete)
90
+ if existing_item and existing_item.id:
91
+ node = menu_nodes[existing_item.id]
92
+ await node.delete(db=self.db)
93
+
94
+ async def update_menu_item(
95
+ self,
96
+ node: CoreMenuItem,
97
+ existing_menu_item: MenuItemDict,
98
+ new_menu_item: MenuItemDict,
99
+ parent: CoreMenuItem | None,
100
+ ) -> None:
101
+ attrs_to_update = existing_menu_item.diff_attributes(new_menu_item)
102
+ for attr_name, value in attrs_to_update.items():
103
+ attr = getattr(node, attr_name)
104
+ attr.value = value
105
+ await node.parent.update(data=parent, db=self.db) # type: ignore[arg-type]
106
+ await node.save(db=self.db)
107
+
108
+ async def create_menu_item(self, new_menu_item: MenuItemDict, parent: CoreMenuItem | None) -> CoreMenuItem:
109
+ obj = await new_menu_item.to_node(db=self.db, parent=parent)
110
+ await obj.save(db=self.db)
111
+ return obj
infrahub/menu/utils.py CHANGED
@@ -1,12 +1,9 @@
1
- from infrahub.core.protocols import CoreMenuItem
2
1
  from infrahub.database import InfrahubDatabase
3
2
 
4
- from .models import MenuItemDefinition
3
+ from .menu import default_menu
4
+ from .repository import MenuRepository
5
5
 
6
6
 
7
- async def create_menu_children(db: InfrahubDatabase, parent: CoreMenuItem, children: list[MenuItemDefinition]) -> None:
8
- for child in children:
9
- obj = await child.to_node(db=db, parent=parent)
10
- await obj.save(db=db)
11
- if child.children:
12
- await create_menu_children(db=db, parent=obj, children=child.children)
7
+ async def create_default_menu(db: InfrahubDatabase) -> None:
8
+ repository = MenuRepository(db=db)
9
+ await repository.create_menu(menu=default_menu)