infrahub-server 1.1.6__py3-none-any.whl → 1.2.0rc0__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 (346) hide show
  1. infrahub/api/artifact.py +16 -4
  2. infrahub/api/dependencies.py +8 -0
  3. infrahub/api/oauth2.py +0 -1
  4. infrahub/api/oidc.py +0 -1
  5. infrahub/api/query.py +18 -7
  6. infrahub/api/schema.py +32 -6
  7. infrahub/api/transformation.py +12 -5
  8. infrahub/{message_bus/messages/check_artifact_create.py → artifacts/models.py} +2 -4
  9. infrahub/{message_bus/operations/check/artifact.py → artifacts/tasks.py} +26 -25
  10. infrahub/cli/__init__.py +0 -2
  11. infrahub/cli/db.py +6 -7
  12. infrahub/cli/events.py +8 -3
  13. infrahub/cli/git_agent.py +9 -7
  14. infrahub/cli/tasks.py +4 -6
  15. infrahub/computed_attribute/tasks.py +63 -17
  16. infrahub/computed_attribute/triggers.py +90 -0
  17. infrahub/config.py +1 -1
  18. infrahub/context.py +39 -0
  19. infrahub/core/account.py +5 -8
  20. infrahub/core/attribute.py +53 -21
  21. infrahub/core/branch/models.py +4 -4
  22. infrahub/core/branch/tasks.py +89 -130
  23. infrahub/core/changelog/__init__.py +0 -0
  24. infrahub/core/changelog/diff.py +232 -0
  25. infrahub/core/changelog/models.py +488 -0
  26. infrahub/core/constants/__init__.py +19 -2
  27. infrahub/core/constants/infrahubkind.py +1 -0
  28. infrahub/core/diff/combiner.py +12 -8
  29. infrahub/core/diff/coordinator.py +49 -70
  30. infrahub/core/diff/data_check_synchronizer.py +86 -7
  31. infrahub/core/diff/enricher/aggregated.py +3 -3
  32. infrahub/core/diff/enricher/cardinality_one.py +2 -7
  33. infrahub/core/diff/enricher/hierarchy.py +5 -3
  34. infrahub/core/diff/enricher/labels.py +14 -4
  35. infrahub/core/diff/enricher/path_identifier.py +3 -9
  36. infrahub/core/diff/enricher/summary_counts.py +3 -1
  37. infrahub/core/diff/merger/merger.py +8 -4
  38. infrahub/core/diff/model/path.py +47 -29
  39. infrahub/core/diff/query/all_conflicts.py +6 -3
  40. infrahub/core/diff/query/artifact.py +1 -1
  41. infrahub/core/diff/query/delete_query.py +1 -1
  42. infrahub/core/diff/query/diff_get.py +3 -2
  43. infrahub/core/diff/query/diff_summary.py +1 -1
  44. infrahub/core/diff/query/field_specifiers.py +3 -1
  45. infrahub/core/diff/query/field_summary.py +3 -2
  46. infrahub/core/diff/query/filters.py +12 -1
  47. infrahub/core/diff/query/get_conflict_query.py +1 -1
  48. infrahub/core/diff/query/has_conflicts_query.py +6 -3
  49. infrahub/core/diff/query/merge.py +3 -3
  50. infrahub/core/diff/query/{drop_tracking_id.py → merge_tracking_id.py} +4 -4
  51. infrahub/core/diff/query/roots_metadata.py +9 -2
  52. infrahub/core/diff/query/save.py +151 -66
  53. infrahub/core/diff/query/summary_counts_enricher.py +220 -0
  54. infrahub/core/diff/query/time_range_query.py +3 -2
  55. infrahub/core/diff/query/update_conflict_query.py +1 -1
  56. infrahub/core/diff/query_parser.py +49 -24
  57. infrahub/core/diff/repository/deserializer.py +24 -25
  58. infrahub/core/diff/repository/repository.py +76 -20
  59. infrahub/core/diff/tasks.py +9 -8
  60. infrahub/core/enums.py +1 -1
  61. infrahub/core/integrity/object_conflict/conflict_recorder.py +1 -1
  62. infrahub/core/ipam/reconciler.py +1 -1
  63. infrahub/core/ipam/tasks.py +2 -3
  64. infrahub/core/manager.py +18 -13
  65. infrahub/core/merge.py +5 -2
  66. infrahub/core/migrations/graph/m001_add_version_to_graph.py +1 -1
  67. infrahub/core/migrations/graph/m002_attribute_is_default.py +2 -2
  68. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +2 -2
  69. infrahub/core/migrations/graph/m004_add_attr_documentation.py +1 -1
  70. infrahub/core/migrations/graph/m005_add_rel_read_only.py +1 -1
  71. infrahub/core/migrations/graph/m006_add_rel_on_delete.py +1 -1
  72. infrahub/core/migrations/graph/m007_add_rel_allow_override.py +1 -1
  73. infrahub/core/migrations/graph/m008_add_human_friendly_id.py +1 -1
  74. infrahub/core/migrations/graph/m009_add_generate_profile_attr.py +1 -1
  75. infrahub/core/migrations/graph/m010_add_generate_profile_attr_generic.py +1 -1
  76. infrahub/core/migrations/graph/m011_remove_profile_relationship_schema.py +2 -2
  77. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -23
  78. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -11
  79. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +2 -2
  80. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
  81. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
  82. infrahub/core/migrations/graph/m017_add_core_profile.py +1 -1
  83. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -2
  84. infrahub/core/migrations/query/attribute_add.py +1 -1
  85. infrahub/core/migrations/query/attribute_rename.py +1 -1
  86. infrahub/core/migrations/query/delete_element_in_schema.py +1 -1
  87. infrahub/core/migrations/query/node_duplicate.py +1 -1
  88. infrahub/core/migrations/query/relationship_duplicate.py +1 -1
  89. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  90. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  91. infrahub/core/migrations/schema/node_remove.py +1 -1
  92. infrahub/core/migrations/schema/tasks.py +5 -5
  93. infrahub/core/migrations/shared.py +4 -4
  94. infrahub/core/models.py +7 -8
  95. infrahub/core/node/__init__.py +161 -40
  96. infrahub/core/node/base.py +1 -1
  97. infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
  98. infrahub/core/node/delete_validator.py +4 -4
  99. infrahub/core/node/ipam.py +13 -8
  100. infrahub/core/node/permissions.py +4 -0
  101. infrahub/core/node/resource_manager/ip_prefix_pool.py +8 -5
  102. infrahub/core/node/standard.py +3 -5
  103. infrahub/core/property.py +1 -1
  104. infrahub/core/protocols.py +4 -0
  105. infrahub/core/protocols_base.py +4 -2
  106. infrahub/core/query/__init__.py +2 -5
  107. infrahub/core/query/attribute.py +9 -9
  108. infrahub/core/query/branch.py +5 -5
  109. infrahub/core/query/delete.py +1 -1
  110. infrahub/core/query/diff.py +45 -7
  111. infrahub/core/query/ipam.py +4 -4
  112. infrahub/core/query/node.py +19 -14
  113. infrahub/core/query/relationship.py +10 -11
  114. infrahub/core/query/resource_manager.py +13 -11
  115. infrahub/core/query/standard_node.py +6 -6
  116. infrahub/core/query/task.py +3 -3
  117. infrahub/core/query/task_log.py +1 -1
  118. infrahub/core/query/utils.py +5 -5
  119. infrahub/core/registry.py +0 -2
  120. infrahub/core/relationship/constraints/count.py +1 -1
  121. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  122. infrahub/core/relationship/model.py +66 -26
  123. infrahub/core/schema/__init__.py +6 -4
  124. infrahub/core/schema/basenode_schema.py +1 -3
  125. infrahub/core/schema/definitions/core.py +14 -2
  126. infrahub/core/schema/definitions/internal.py +16 -0
  127. infrahub/core/schema/generated/genericnode_schema.py +5 -0
  128. infrahub/core/schema/generated/node_schema.py +5 -0
  129. infrahub/core/schema/generic_schema.py +5 -1
  130. infrahub/core/schema/manager.py +45 -42
  131. infrahub/core/schema/node_schema.py +4 -0
  132. infrahub/core/schema/profile_schema.py +4 -0
  133. infrahub/core/schema/relationship_schema.py +2 -2
  134. infrahub/core/schema/schema_branch.py +248 -14
  135. infrahub/core/schema/template_schema.py +36 -0
  136. infrahub/core/task/user_task.py +7 -5
  137. infrahub/core/timestamp.py +1 -1
  138. infrahub/core/utils.py +3 -2
  139. infrahub/core/validators/attribute/choices.py +1 -1
  140. infrahub/core/validators/attribute/enum.py +1 -1
  141. infrahub/core/validators/attribute/kind.py +1 -1
  142. infrahub/core/validators/attribute/length.py +1 -1
  143. infrahub/core/validators/attribute/optional.py +1 -1
  144. infrahub/core/validators/attribute/regex.py +1 -1
  145. infrahub/core/validators/attribute/unique.py +1 -1
  146. infrahub/core/validators/checks_runner.py +37 -0
  147. infrahub/core/validators/node/generate_profile.py +1 -1
  148. infrahub/core/validators/node/hierarchy.py +1 -1
  149. infrahub/core/validators/query.py +1 -1
  150. infrahub/core/validators/relationship/count.py +1 -1
  151. infrahub/core/validators/relationship/optional.py +1 -1
  152. infrahub/core/validators/relationship/peer.py +1 -1
  153. infrahub/core/validators/tasks.py +8 -6
  154. infrahub/core/validators/uniqueness/query.py +20 -17
  155. infrahub/database/__init__.py +15 -2
  156. infrahub/database/memgraph.py +1 -1
  157. infrahub/dependencies/builder/constraint/grouped/node_runner.py +0 -2
  158. infrahub/dependencies/builder/diff/combiner.py +1 -1
  159. infrahub/dependencies/builder/diff/conflicts_enricher.py +1 -1
  160. infrahub/dependencies/builder/diff/coordinator.py +0 -2
  161. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  162. infrahub/dependencies/builder/diff/enricher/summary_counts.py +1 -1
  163. infrahub/events/branch_action.py +47 -21
  164. infrahub/events/group_action.py +73 -0
  165. infrahub/events/models.py +159 -51
  166. infrahub/events/node_action.py +74 -8
  167. infrahub/events/repository_action.py +8 -8
  168. infrahub/events/schema_action.py +21 -8
  169. infrahub/generators/tasks.py +12 -13
  170. infrahub/git/base.py +3 -5
  171. infrahub/git/constants.py +0 -1
  172. infrahub/git/integrator.py +36 -35
  173. infrahub/git/repository.py +7 -8
  174. infrahub/git/tasks.py +43 -107
  175. infrahub/git_credential/helper.py +2 -3
  176. infrahub/graphql/analyzer.py +572 -11
  177. infrahub/graphql/app.py +34 -26
  178. infrahub/graphql/auth/query_permission_checker/anonymous_checker.py +5 -5
  179. infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +4 -4
  180. infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +4 -4
  181. infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +28 -35
  182. infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -5
  183. infrahub/graphql/enums.py +1 -1
  184. infrahub/graphql/initialization.py +5 -1
  185. infrahub/graphql/loaders/node.py +2 -2
  186. infrahub/graphql/manager.py +59 -54
  187. infrahub/graphql/mutations/account.py +20 -13
  188. infrahub/graphql/mutations/artifact_definition.py +16 -12
  189. infrahub/graphql/mutations/branch.py +61 -40
  190. infrahub/graphql/mutations/computed_attribute.py +19 -13
  191. infrahub/graphql/mutations/diff.py +37 -9
  192. infrahub/graphql/mutations/diff_conflict.py +9 -8
  193. infrahub/graphql/mutations/graphql_query.py +19 -11
  194. infrahub/graphql/mutations/ipam.py +21 -19
  195. infrahub/graphql/mutations/main.py +197 -44
  196. infrahub/graphql/mutations/menu.py +8 -8
  197. infrahub/graphql/mutations/proposed_change.py +36 -28
  198. infrahub/graphql/mutations/relationship.py +302 -105
  199. infrahub/graphql/mutations/repository.py +41 -35
  200. infrahub/graphql/mutations/resource_manager.py +26 -26
  201. infrahub/graphql/mutations/schema.py +51 -33
  202. infrahub/graphql/mutations/tasks.py +16 -10
  203. infrahub/graphql/parser.py +1 -1
  204. infrahub/graphql/permissions.py +6 -4
  205. infrahub/graphql/queries/account.py +22 -18
  206. infrahub/graphql/queries/branch.py +6 -4
  207. infrahub/graphql/queries/diff/tree.py +48 -42
  208. infrahub/graphql/queries/event.py +112 -0
  209. infrahub/graphql/queries/internal.py +3 -3
  210. infrahub/graphql/queries/ipam.py +23 -18
  211. infrahub/graphql/queries/relationship.py +11 -10
  212. infrahub/graphql/queries/resource_manager.py +43 -27
  213. infrahub/graphql/queries/search.py +9 -8
  214. infrahub/graphql/queries/status.py +12 -9
  215. infrahub/graphql/queries/task.py +11 -9
  216. infrahub/graphql/resolvers/resolver.py +69 -43
  217. infrahub/graphql/resolvers/single_relationship.py +16 -10
  218. infrahub/graphql/schema.py +2 -0
  219. infrahub/graphql/subscription/__init__.py +1 -1
  220. infrahub/graphql/subscription/events.py +1 -1
  221. infrahub/graphql/subscription/graphql_query.py +8 -8
  222. infrahub/graphql/types/branch.py +2 -2
  223. infrahub/graphql/types/common.py +6 -1
  224. infrahub/graphql/types/enums.py +2 -0
  225. infrahub/graphql/types/event.py +100 -0
  226. infrahub/graphql/types/interface.py +2 -2
  227. infrahub/graphql/types/node.py +3 -3
  228. infrahub/graphql/types/permission.py +2 -2
  229. infrahub/graphql/types/relationship.py +3 -3
  230. infrahub/graphql/types/standard_node.py +9 -11
  231. infrahub/graphql/utils.py +28 -182
  232. infrahub/groups/tasks.py +2 -3
  233. infrahub/lock.py +1 -1
  234. infrahub/menu/constants.py +1 -0
  235. infrahub/menu/generator.py +14 -3
  236. infrahub/menu/menu.py +116 -127
  237. infrahub/menu/models.py +4 -4
  238. infrahub/message_bus/messages/__init__.py +0 -4
  239. infrahub/message_bus/messages/event_branch_merge.py +3 -0
  240. infrahub/message_bus/messages/request_proposedchange_pipeline.py +2 -0
  241. infrahub/message_bus/operations/__init__.py +3 -5
  242. infrahub/message_bus/operations/check/__init__.py +2 -2
  243. infrahub/message_bus/operations/check/generator.py +1 -3
  244. infrahub/message_bus/operations/check/repository.py +1 -1
  245. infrahub/message_bus/operations/event/branch.py +7 -3
  246. infrahub/message_bus/operations/event/schema.py +1 -1
  247. infrahub/message_bus/operations/finalize/validator.py +1 -1
  248. infrahub/message_bus/operations/git/file.py +2 -2
  249. infrahub/message_bus/operations/git/repository.py +1 -1
  250. infrahub/message_bus/operations/requests/__init__.py +0 -2
  251. infrahub/message_bus/operations/requests/generator_definition.py +1 -1
  252. infrahub/message_bus/operations/requests/proposed_change.py +26 -11
  253. infrahub/message_bus/operations/requests/repository.py +2 -2
  254. infrahub/message_bus/operations/send/echo.py +1 -1
  255. infrahub/message_bus/types.py +1 -1
  256. infrahub/permissions/__init__.py +2 -1
  257. infrahub/permissions/types.py +26 -0
  258. infrahub/pools/prefix.py +29 -165
  259. infrahub/prefect_server/__init__.py +0 -0
  260. infrahub/prefect_server/app.py +18 -0
  261. infrahub/prefect_server/database.py +20 -0
  262. infrahub/prefect_server/events.py +28 -0
  263. infrahub/prefect_server/models.py +46 -0
  264. infrahub/proposed_change/models.py +15 -1
  265. infrahub/proposed_change/tasks.py +173 -35
  266. infrahub/pytest_plugin.py +4 -4
  267. infrahub/server.py +12 -11
  268. infrahub/services/__init__.py +147 -62
  269. infrahub/services/adapters/cache/__init__.py +7 -5
  270. infrahub/services/adapters/cache/nats.py +40 -22
  271. infrahub/services/adapters/cache/redis.py +0 -4
  272. infrahub/services/adapters/event/__init__.py +10 -18
  273. infrahub/services/adapters/http/__init__.py +0 -5
  274. infrahub/services/adapters/http/httpx.py +22 -15
  275. infrahub/services/adapters/message_bus/__init__.py +23 -6
  276. infrahub/services/adapters/message_bus/local.py +8 -6
  277. infrahub/services/adapters/message_bus/nats.py +12 -6
  278. infrahub/services/adapters/message_bus/rabbitmq.py +22 -9
  279. infrahub/services/adapters/workflow/__init__.py +11 -8
  280. infrahub/services/adapters/workflow/local.py +28 -7
  281. infrahub/services/adapters/workflow/worker.py +23 -7
  282. infrahub/services/component.py +38 -35
  283. infrahub/services/scheduler.py +32 -29
  284. infrahub/storage.py +2 -4
  285. infrahub/task_manager/constants.py +1 -1
  286. infrahub/task_manager/event.py +182 -0
  287. infrahub/task_manager/models.py +125 -1
  288. infrahub/task_manager/task.py +1 -1
  289. infrahub/tasks/artifact.py +14 -16
  290. infrahub/tasks/registry.py +1 -1
  291. infrahub/tasks/telemetry.py +13 -14
  292. infrahub/transformations/tasks.py +3 -5
  293. infrahub/trigger/__init__.py +0 -0
  294. infrahub/trigger/catalogue.py +15 -0
  295. infrahub/trigger/constants.py +9 -0
  296. infrahub/trigger/models.py +69 -0
  297. infrahub/trigger/tasks.py +85 -0
  298. infrahub/types.py +1 -1
  299. infrahub/utils.py +1 -1
  300. infrahub/webhook/constants.py +0 -2
  301. infrahub/webhook/models.py +8 -2
  302. infrahub/webhook/tasks.py +20 -73
  303. infrahub/webhook/triggers.py +20 -0
  304. infrahub/workers/infrahub_async.py +36 -25
  305. infrahub/workers/utils.py +63 -0
  306. infrahub/workflows/catalogue.py +13 -37
  307. infrahub/workflows/initialization.py +6 -8
  308. infrahub/workflows/models.py +3 -5
  309. infrahub/workflows/utils.py +1 -1
  310. infrahub_sdk/ctl/check.py +3 -3
  311. infrahub_sdk/ctl/cli_commands.py +11 -10
  312. infrahub_sdk/ctl/exceptions.py +0 -6
  313. infrahub_sdk/ctl/exporter.py +1 -1
  314. infrahub_sdk/ctl/generator.py +5 -5
  315. infrahub_sdk/ctl/importer.py +3 -2
  316. infrahub_sdk/ctl/menu.py +1 -1
  317. infrahub_sdk/ctl/object.py +1 -1
  318. infrahub_sdk/ctl/repository.py +23 -15
  319. infrahub_sdk/ctl/schema.py +2 -2
  320. infrahub_sdk/ctl/utils.py +4 -3
  321. infrahub_sdk/ctl/validate.py +2 -1
  322. infrahub_sdk/exceptions.py +6 -0
  323. infrahub_sdk/generator.py +3 -0
  324. infrahub_sdk/node.py +2 -2
  325. infrahub_sdk/schema/__init__.py +14 -2
  326. infrahub_sdk/schema/main.py +7 -0
  327. infrahub_sdk/utils.py +11 -1
  328. infrahub_sdk/yaml.py +2 -3
  329. {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/METADATA +46 -12
  330. {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/RECORD +338 -321
  331. infrahub_testcontainers/container.py +14 -6
  332. infrahub_testcontainers/docker-compose.test.yml +24 -5
  333. infrahub_testcontainers/haproxy.cfg +43 -0
  334. infrahub_testcontainers/helpers.py +85 -1
  335. infrahub/core/branch/constants.py +0 -2
  336. infrahub/graphql/query.py +0 -52
  337. infrahub/message_bus/messages/request_artifactdefinition_check.py +0 -17
  338. infrahub/message_bus/operations/requests/artifact_definition.py +0 -148
  339. infrahub/schema/constants.py +0 -1
  340. infrahub/schema/tasks.py +0 -76
  341. infrahub/services/adapters/database/__init__.py +0 -9
  342. infrahub_sdk/ctl/_file.py +0 -13
  343. /infrahub/{schema → artifacts}/__init__.py +0 -0
  344. {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/LICENSE.txt +0 -0
  345. {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/WHEEL +0 -0
  346. {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/entry_points.txt +0 -0
@@ -6,6 +6,7 @@ from uuid import uuid4
6
6
 
7
7
  from infrahub import lock
8
8
  from infrahub.core.timestamp import Timestamp
9
+ from infrahub.exceptions import ValidationError
9
10
  from infrahub.log import get_logger
10
11
 
11
12
  from .model.path import (
@@ -29,7 +30,6 @@ if TYPE_CHECKING:
29
30
  from .data_check_synchronizer import DiffDataCheckSynchronizer
30
31
  from .enricher.aggregated import AggregatedDiffEnricher
31
32
  from .enricher.labels import DiffLabelsEnricher
32
- from .enricher.summary_counts import DiffSummaryCountsEnricher
33
33
  from .repository.repository import DiffRepository
34
34
 
35
35
 
@@ -42,7 +42,7 @@ class EnrichedDiffRequest:
42
42
  diff_branch: Branch
43
43
  from_time: Timestamp
44
44
  to_time: Timestamp
45
- tracking_id: TrackingId | None = field(default=None)
45
+ tracking_id: TrackingId
46
46
  node_field_specifiers: dict[str, set[str]] = field(default_factory=dict)
47
47
 
48
48
  def __repr__(self) -> str:
@@ -65,7 +65,6 @@ class DiffCoordinator:
65
65
  diff_combiner: DiffCombiner,
66
66
  conflicts_enricher: ConflictsEnricher,
67
67
  labels_enricher: DiffLabelsEnricher,
68
- summary_counts_enricher: DiffSummaryCountsEnricher,
69
68
  data_check_synchronizer: DiffDataCheckSynchronizer,
70
69
  conflict_transferer: DiffConflictTransferer,
71
70
  ) -> None:
@@ -75,7 +74,6 @@ class DiffCoordinator:
75
74
  self.diff_combiner = diff_combiner
76
75
  self.conflicts_enricher = conflicts_enricher
77
76
  self.labels_enricher = labels_enricher
78
- self.summary_counts_enricher = summary_counts_enricher
79
77
  self.data_check_synchronizer = data_check_synchronizer
80
78
  self.conflict_transferer = conflict_transferer
81
79
  self.lock_registry = lock.registry
@@ -101,6 +99,8 @@ class DiffCoordinator:
101
99
  to_timestamp = Timestamp(to_time)
102
100
  else:
103
101
  to_timestamp = Timestamp()
102
+ if not name:
103
+ raise ValidationError("diff with specified time range requires a name")
104
104
  await self.create_or_update_arbitrary_timeframe_diff(
105
105
  base_branch=base_branch,
106
106
  diff_branch=diff_branch,
@@ -115,27 +115,7 @@ class DiffCoordinator:
115
115
  lock_name += "__incremental"
116
116
  return lock_name
117
117
 
118
- async def update_branch_diff_and_return(self, base_branch: Branch, diff_branch: Branch) -> EnrichedDiffRoot:
119
- enriched_diff = await self.update_branch_diff(base_branch=base_branch, diff_branch=diff_branch)
120
- if isinstance(enriched_diff, EnrichedDiffRoot):
121
- return enriched_diff
122
- return await self._finalize_diff_root_metadata(diff_root_metadata=enriched_diff)
123
-
124
- async def _finalize_diff_root_metadata(self, diff_root_metadata: EnrichedDiffRootMetadata) -> EnrichedDiffRoot:
125
- # if this is EnrichedDiffMetadata, we need to retrieve the full diff and set its metadata to match
126
- full_enriched_diff = await self.diff_repo.get_one(
127
- diff_branch_name=diff_root_metadata.diff_branch_name, diff_id=diff_root_metadata.uuid
128
- )
129
- full_enriched_diff.update_metadata(
130
- from_time=diff_root_metadata.from_time,
131
- to_time=diff_root_metadata.to_time,
132
- tracking_id=diff_root_metadata.tracking_id,
133
- )
134
- return full_enriched_diff
135
-
136
- async def update_branch_diff(
137
- self, base_branch: Branch, diff_branch: Branch
138
- ) -> EnrichedDiffRoot | EnrichedDiffRootMetadata:
118
+ async def update_branch_diff(self, base_branch: Branch, diff_branch: Branch) -> EnrichedDiffRootMetadata:
139
119
  log.info(f"Received request to update branch diff for {base_branch.name} - {diff_branch.name}")
140
120
  incremental_lock_name = self._get_lock_name(
141
121
  base_branch_name=base_branch.name, diff_branch_name=diff_branch.name, is_incremental=True
@@ -169,12 +149,6 @@ class DiffCoordinator:
169
149
  tracking_id=tracking_id,
170
150
  force_branch_refresh=False,
171
151
  )
172
- if not isinstance(enriched_diffs, EnrichedDiffs):
173
- await self._update_core_data_checks(enriched_diff=enriched_diffs.diff_branch_diff)
174
- return enriched_diffs.diff_branch_diff
175
-
176
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.base_branch_diff)
177
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.diff_branch_diff)
178
152
  await self.diff_repo.save(enriched_diffs=enriched_diffs)
179
153
  await self._update_core_data_checks(enriched_diff=enriched_diffs.diff_branch_diff)
180
154
  log.info(f"Branch diff update complete for {base_branch.name} - {diff_branch.name}")
@@ -186,11 +160,9 @@ class DiffCoordinator:
186
160
  diff_branch: Branch,
187
161
  from_time: Timestamp,
188
162
  to_time: Timestamp,
189
- name: str | None = None,
190
- ) -> EnrichedDiffRoot:
191
- tracking_id = None
192
- if name:
193
- tracking_id = NameTrackingId(name=name)
163
+ name: str,
164
+ ) -> EnrichedDiffRootMetadata:
165
+ tracking_id = NameTrackingId(name=name)
194
166
  general_lock_name = self._get_lock_name(
195
167
  base_branch_name=base_branch.name, diff_branch_name=diff_branch.name, is_incremental=False
196
168
  )
@@ -204,13 +176,7 @@ class DiffCoordinator:
204
176
  tracking_id=tracking_id,
205
177
  force_branch_refresh=False,
206
178
  )
207
- # metadata-only diff, so no nodes to enrich
208
- if not isinstance(enriched_diffs, EnrichedDiffs):
209
- await self._update_core_data_checks(enriched_diff=enriched_diffs.diff_branch_diff)
210
- return await self._finalize_diff_root_metadata(diff_root_metadata=enriched_diffs.diff_branch_diff)
211
179
 
212
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.base_branch_diff)
213
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.diff_branch_diff)
214
180
  await self.diff_repo.save(enriched_diffs=enriched_diffs)
215
181
  await self._update_core_data_checks(enriched_diff=enriched_diffs.diff_branch_diff)
216
182
  log.info(f"Arbitrary diff update complete for {base_branch.name} - {diff_branch.name}")
@@ -252,8 +218,6 @@ class DiffCoordinator:
252
218
  earlier=current_branch_diff, later=enriched_diffs.diff_branch_diff
253
219
  )
254
220
 
255
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.base_branch_diff)
256
- await self.summary_counts_enricher.enrich(enriched_diff_root=enriched_diffs.diff_branch_diff)
257
221
  await self.diff_repo.save(enriched_diffs=enriched_diffs)
258
222
  await self._update_core_data_checks(enriched_diff=enriched_diffs.diff_branch_diff)
259
223
  log.info(f"Diff recalculation complete for {base_branch.name} - {diff_branch.name}")
@@ -316,7 +280,7 @@ class DiffCoordinator:
316
280
  diff_branch: Branch,
317
281
  from_time: Timestamp,
318
282
  to_time: Timestamp,
319
- tracking_id: TrackingId | None = None,
283
+ tracking_id: TrackingId,
320
284
  force_branch_refresh: Literal[True] = ...,
321
285
  ) -> EnrichedDiffs: ...
322
286
 
@@ -327,7 +291,7 @@ class DiffCoordinator:
327
291
  diff_branch: Branch,
328
292
  from_time: Timestamp,
329
293
  to_time: Timestamp,
330
- tracking_id: TrackingId | None = None,
294
+ tracking_id: TrackingId,
331
295
  force_branch_refresh: Literal[False] = ...,
332
296
  ) -> EnrichedDiffs | EnrichedDiffsMetadata: ...
333
297
 
@@ -337,7 +301,7 @@ class DiffCoordinator:
337
301
  diff_branch: Branch,
338
302
  from_time: Timestamp,
339
303
  to_time: Timestamp,
340
- tracking_id: TrackingId | None = None,
304
+ tracking_id: TrackingId,
341
305
  force_branch_refresh: bool = False,
342
306
  ) -> EnrichedDiffs | EnrichedDiffsMetadata:
343
307
  # start with empty diffs b/c we only care about their metadata for now, hydrate them with data as needed
@@ -346,6 +310,7 @@ class DiffCoordinator:
346
310
  diff_branch_names=[diff_branch.name],
347
311
  from_time=from_time,
348
312
  to_time=to_time,
313
+ tracking_id=tracking_id,
349
314
  )
350
315
  aggregated_enriched_diffs = await self._aggregate_enriched_diffs(
351
316
  diff_request=EnrichedDiffRequest(
@@ -357,22 +322,23 @@ class DiffCoordinator:
357
322
  ),
358
323
  partial_enriched_diffs=diff_pairs_metadata if not force_branch_refresh else None,
359
324
  )
360
- if tracking_id:
361
- diff_uuids_to_delete: list[str] = []
362
- for diff_pair in diff_pairs_metadata:
363
- if (
364
- diff_pair.base_branch_diff.tracking_id == tracking_id
365
- and diff_pair.base_branch_diff.uuid != aggregated_enriched_diffs.base_branch_diff.uuid
366
- ):
367
- diff_uuids_to_delete.append(diff_pair.base_branch_diff.uuid)
368
- if (
369
- diff_pair.diff_branch_diff.tracking_id == tracking_id
370
- and diff_pair.diff_branch_diff.uuid != aggregated_enriched_diffs.diff_branch_diff.uuid
371
- ):
372
- diff_uuids_to_delete.append(diff_pair.diff_branch_diff.uuid)
373
-
374
- if diff_uuids_to_delete:
375
- await self.diff_repo.delete_diff_roots(diff_root_uuids=diff_uuids_to_delete)
325
+ diff_uuids_to_delete: list[str] = []
326
+ for diff_pair in diff_pairs_metadata:
327
+ if (
328
+ diff_pair.base_branch_diff.tracking_id == tracking_id
329
+ and diff_pair.base_branch_diff.uuid != aggregated_enriched_diffs.base_branch_diff.uuid
330
+ and diff_pair.base_branch_diff.exists_on_database
331
+ ):
332
+ diff_uuids_to_delete.append(diff_pair.base_branch_diff.uuid)
333
+ if (
334
+ diff_pair.diff_branch_diff.tracking_id == tracking_id
335
+ and diff_pair.diff_branch_diff.uuid != aggregated_enriched_diffs.diff_branch_diff.uuid
336
+ and diff_pair.diff_branch_diff.exists_on_database
337
+ ):
338
+ diff_uuids_to_delete.append(diff_pair.diff_branch_diff.uuid)
339
+
340
+ if diff_uuids_to_delete:
341
+ await self.diff_repo.delete_diff_roots(diff_root_uuids=diff_uuids_to_delete)
376
342
 
377
343
  # this is an EnrichedDiffsMetadata, so there are no nodes to enrich
378
344
  if not isinstance(aggregated_enriched_diffs, EnrichedDiffs):
@@ -459,6 +425,7 @@ class DiffCoordinator:
459
425
  diff_branch=diff_request.diff_branch,
460
426
  from_time=current_time,
461
427
  to_time=end_time,
428
+ tracking_id=diff_request.tracking_id,
462
429
  )
463
430
  )
464
431
  current_time = end_time
@@ -481,7 +448,6 @@ class DiffCoordinator:
481
448
  aggregated_enriched_diffs.update_metadata(
482
449
  from_time=diff_request.from_time, to_time=diff_request.to_time, tracking_id=diff_request.tracking_id
483
450
  )
484
- aggregated_enriched_diffs.set_fresh_uuids()
485
451
  return aggregated_enriched_diffs
486
452
 
487
453
  async def _concatenate_diffs_and_requests(
@@ -498,6 +464,7 @@ class DiffCoordinator:
498
464
  meaning multiple diffs (some that may have been freshly calculated) were combined
499
465
  """
500
466
  previous_diff_pair: EnrichedDiffs | EnrichedDiffsMetadata | None = None
467
+ updated_node_uuids: set[str] = set()
501
468
  for diff_or_request in diff_or_request_list:
502
469
  if isinstance(diff_or_request, EnrichedDiffRequest):
503
470
  if previous_diff_pair:
@@ -508,9 +475,12 @@ class DiffCoordinator:
508
475
  log.info(f"Number node field specifiers: {len(node_field_specifiers)}")
509
476
  diff_or_request.node_field_specifiers = node_field_specifiers
510
477
  is_incremental_diff = diff_or_request.from_time != full_diff_request.from_time
511
- single_enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata = await self._calculate_enriched_diff(
478
+ calculated_diff = await self._calculate_enriched_diff(
512
479
  diff_request=diff_or_request, is_incremental_diff=is_incremental_diff
513
480
  )
481
+ updated_node_uuids |= calculated_diff.base_node_uuids
482
+ updated_node_uuids |= calculated_diff.branch_node_uuids
483
+ single_enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata = calculated_diff
514
484
 
515
485
  elif isinstance(diff_or_request, EnrichedDiffsMetadata):
516
486
  single_enriched_diffs = diff_or_request
@@ -522,13 +492,20 @@ class DiffCoordinator:
522
492
  continue
523
493
 
524
494
  log.info("Combining diffs...")
525
- previous_diff_pair = await self._combine_diffs(earlier=previous_diff_pair, later=single_enriched_diffs)
495
+ previous_diff_pair = await self._combine_diffs(
496
+ earlier=previous_diff_pair,
497
+ later=single_enriched_diffs,
498
+ node_uuids=updated_node_uuids,
499
+ )
526
500
  log.info("Diffs combined.")
527
501
 
528
502
  return previous_diff_pair
529
503
 
530
504
  async def _combine_diffs(
531
- self, earlier: EnrichedDiffs | EnrichedDiffsMetadata, later: EnrichedDiffs | EnrichedDiffsMetadata
505
+ self,
506
+ earlier: EnrichedDiffs | EnrichedDiffsMetadata,
507
+ later: EnrichedDiffs | EnrichedDiffsMetadata,
508
+ node_uuids: set[str],
532
509
  ) -> EnrichedDiffs | EnrichedDiffsMetadata:
533
510
  log.info(f"Earlier diff to combine: {earlier!r}")
534
511
  log.info(f"Later diff to combine: {later!r}")
@@ -545,11 +522,11 @@ class DiffCoordinator:
545
522
  # hydrate the diffs to combine, if necessary
546
523
  if not isinstance(earlier, EnrichedDiffs):
547
524
  log.info("Hydrating earlier diff...")
548
- earlier = await self.diff_repo.hydrate_diff_pair(enriched_diffs_metadata=earlier)
525
+ earlier = await self.diff_repo.hydrate_diff_pair(enriched_diffs_metadata=earlier, node_uuids=node_uuids)
549
526
  log.info("Earlier diff hydrated.")
550
527
  if not isinstance(later, EnrichedDiffs):
551
528
  log.info("Hydrating later diff...")
552
- later = await self.diff_repo.hydrate_diff_pair(enriched_diffs_metadata=later)
529
+ later = await self.diff_repo.hydrate_diff_pair(enriched_diffs_metadata=later, node_uuids=node_uuids)
553
530
  log.info("Later diff hydrated.")
554
531
 
555
532
  return await self.diff_combiner.combine(earlier_diffs=earlier, later_diffs=later)
@@ -570,6 +547,8 @@ class DiffCoordinator:
570
547
  previous_node_specifiers=diff_request.node_field_specifiers,
571
548
  )
572
549
  log.info("Calculation complete. Enriching diff...")
573
- enriched_diff_pair = await self.diff_enricher.enrich(calculated_diffs=calculated_diff_pair)
550
+ enriched_diff_pair = await self.diff_enricher.enrich(
551
+ calculated_diffs=calculated_diff_pair, tracking_id=diff_request.tracking_id
552
+ )
574
553
  log.info("Enrichment complete")
575
554
  return enriched_diff_pair
@@ -10,7 +10,13 @@ from infrahub.proposed_change.constants import ProposedChangeState
10
10
 
11
11
  from .conflicts_extractor import DiffConflictsExtractor
12
12
  from .model.diff import DataConflict
13
- from .model.path import ConflictSelection, EnrichedDiffConflict, EnrichedDiffRoot, EnrichedDiffRootMetadata
13
+ from .model.path import (
14
+ ConflictSelection,
15
+ EnrichedDiffConflict,
16
+ EnrichedDiffNode,
17
+ EnrichedDiffRoot,
18
+ EnrichedDiffRootMetadata,
19
+ )
14
20
  from .repository.repository import DiffRepository
15
21
 
16
22
 
@@ -54,20 +60,31 @@ class DiffDataCheckSynchronizer:
54
60
  if not proposed_changes:
55
61
  return []
56
62
  all_data_checks = []
63
+ enriched_diff_all_conflicts: EnrichedDiffRoot | None = None
57
64
  for pc in proposed_changes:
58
65
  # if the enriched_diff is EnrichedDiffRootMetadata, then it has no new data in it
59
66
  if not isinstance(enriched_diff, EnrichedDiffRoot):
60
67
  has_validator = bool(await self.conflict_recorder.get_validator(proposed_change=pc))
61
- # if this pc does not have a validator, then it is a new ProposedChange
68
+ # if this pc has a validator, then the conflicts for this diff have already been synchronized and we can be done
62
69
  if has_validator:
63
70
  continue
64
- # if this is a new ProposedChange, we need to hydrate then EnrichedDiffRoot so that we can get the conflicts from it
65
- enriched_diff = await self.diff_repository.get_one(
66
- diff_branch_name=enriched_diff.diff_branch_name, diff_id=enriched_diff.uuid
71
+
72
+ # we need to get the conflicts for this diff from the database b/c `enriched_diff` might not include all nodes
73
+ if not enriched_diff_all_conflicts:
74
+ retrieved_diff_conflicts_only = await self.diff_repository.get_one(
75
+ diff_branch_name=enriched_diff.diff_branch_name,
76
+ diff_id=enriched_diff.uuid,
77
+ filters={"only_conflicted": True},
67
78
  )
79
+ enriched_diff_all_conflicts = retrieved_diff_conflicts_only
80
+ # if `enriched_diff` is an EnrichedDiffRootsMetadata, then there have been no changes to the diff and
81
+ # we can use `retrieved_diff_conflicts_only`
82
+ # otherwise, we need to combine the changes to `enriched_diff` with the conflicts from the database
83
+ if isinstance(enriched_diff, EnrichedDiffRoot):
84
+ self._update_diff_conflicts(updated_diff=enriched_diff, retrieved_diff=enriched_diff_all_conflicts)
68
85
 
69
- data_conflicts = await self._get_data_conflicts(enriched_diff=enriched_diff)
70
- enriched_conflicts_map = self._get_enriched_conflicts_map(enriched_diff=enriched_diff)
86
+ data_conflicts = await self._get_data_conflicts(enriched_diff=enriched_diff_all_conflicts)
87
+ enriched_conflicts_map = self._get_enriched_conflicts_map(enriched_diff=enriched_diff_all_conflicts)
71
88
  core_data_checks = await self.conflict_recorder.record_conflicts(
72
89
  proposed_change_id=pc.get_id(), conflicts=data_conflicts
73
90
  )
@@ -95,3 +112,65 @@ class DiffDataCheckSynchronizer:
95
112
  if enriched_conflict.selected_branch is ConflictSelection.DIFF_BRANCH:
96
113
  return BranchConflictKeep.SOURCE
97
114
  return None
115
+
116
+ def _update_diff_conflicts(self, updated_diff: EnrichedDiffRoot, retrieved_diff: EnrichedDiffRoot) -> None:
117
+ for updated_node in updated_diff.nodes:
118
+ try:
119
+ retrieved_node = retrieved_diff.get_node(node_uuid=updated_node.uuid)
120
+ except ValueError:
121
+ retrieved_node = None
122
+ if not retrieved_node:
123
+ retrieved_diff.nodes.add(updated_node)
124
+ continue
125
+ retrieved_node.conflict = updated_node.conflict
126
+ self._update_diff_attr_conflicts(updated_node=updated_node, retrieved_node=retrieved_node)
127
+ self._update_diff_relationship_conflicts(updated_node=updated_node, retrieved_node=retrieved_node)
128
+
129
+ def _update_diff_attr_conflicts(self, updated_node: EnrichedDiffNode, retrieved_node: EnrichedDiffNode) -> None:
130
+ for updated_attr in updated_node.attributes:
131
+ try:
132
+ retrieved_attr = retrieved_node.get_attribute(name=updated_attr.name)
133
+ except ValueError:
134
+ retrieved_attr = None
135
+ if not retrieved_attr:
136
+ retrieved_node.attributes.add(updated_attr)
137
+ continue
138
+ for updated_prop in updated_attr.properties:
139
+ try:
140
+ retrieved_prop = retrieved_attr.get_property(updated_prop.property_type)
141
+ except ValueError:
142
+ retrieved_prop = None
143
+ if not retrieved_prop:
144
+ retrieved_attr.properties.add(updated_prop)
145
+ continue
146
+ retrieved_prop.conflict = updated_prop.conflict
147
+
148
+ def _update_diff_relationship_conflicts(
149
+ self, updated_node: EnrichedDiffNode, retrieved_node: EnrichedDiffNode
150
+ ) -> None:
151
+ for updated_rel in updated_node.relationships:
152
+ try:
153
+ retrieved_rel = retrieved_node.get_relationship(name=updated_rel.name)
154
+ except ValueError:
155
+ retrieved_rel = None
156
+ if not retrieved_rel:
157
+ retrieved_node.relationships.add(updated_rel)
158
+ continue
159
+ for updated_element in updated_rel.relationships:
160
+ try:
161
+ retrieved_element = retrieved_rel.get_element(updated_element.peer_id)
162
+ except ValueError:
163
+ retrieved_element = None
164
+ if not retrieved_element:
165
+ retrieved_rel.relationships.add(updated_element)
166
+ continue
167
+ retrieved_element.conflict = updated_element.conflict
168
+ for updated_prop in updated_element.properties:
169
+ try:
170
+ retrieved_prop = retrieved_element.get_property(updated_prop.property_type)
171
+ except ValueError:
172
+ retrieved_prop = None
173
+ if not retrieved_prop:
174
+ retrieved_element.properties.add(updated_prop)
175
+ continue
176
+ retrieved_prop.conflict = updated_prop.conflict
@@ -1,4 +1,4 @@
1
- from ..model.path import CalculatedDiffs, EnrichedDiffs
1
+ from ..model.path import CalculatedDiffs, EnrichedDiffs, TrackingId
2
2
  from .interface import DiffEnricherInterface
3
3
 
4
4
 
@@ -6,8 +6,8 @@ class AggregatedDiffEnricher:
6
6
  def __init__(self, enrichers: list[DiffEnricherInterface]) -> None:
7
7
  self.enrichers = enrichers
8
8
 
9
- async def enrich(self, calculated_diffs: CalculatedDiffs) -> EnrichedDiffs:
10
- enriched_diffs = EnrichedDiffs.from_calculated_diffs(calculated_diffs=calculated_diffs)
9
+ async def enrich(self, calculated_diffs: CalculatedDiffs, tracking_id: TrackingId) -> EnrichedDiffs:
10
+ enriched_diffs = EnrichedDiffs.from_calculated_diffs(calculated_diffs=calculated_diffs, tracking_id=tracking_id)
11
11
 
12
12
  for enricher in self.enrichers:
13
13
  await enricher.enrich(enriched_diff_root=enriched_diffs.base_branch_diff, calculated_diffs=calculated_diffs)
@@ -32,7 +32,7 @@ class DiffCardinalityOneEnricher(DiffEnricherInterface):
32
32
  self.db = db
33
33
  self._node_schema_map: dict[str, MainSchemaTypes] = {}
34
34
 
35
- async def enrich(self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs) -> None:
35
+ async def enrich(self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs) -> None: # noqa: ARG002
36
36
  self._node_schema_map = {}
37
37
  for diff_node in enriched_diff_root.nodes:
38
38
  for relationship_group in diff_node.relationships:
@@ -103,14 +103,9 @@ class DiffCardinalityOneEnricher(DiffEnricherInterface):
103
103
  )
104
104
  )
105
105
  if consolidated_properties:
106
- element_timestamps = {element.changed_at for element in diff_relationship.relationships}
107
106
  element_actions = {element.action for element in diff_relationship.relationships}
108
107
  # check if this is a simultaneous update
109
- if (
110
- len(diff_relationship.relationships) > 1
111
- and len(element_timestamps) == 1
112
- and {DiffAction.REMOVED, DiffAction.ADDED} <= element_actions
113
- ):
108
+ if len(diff_relationship.relationships) > 1 and {DiffAction.REMOVED, DiffAction.ADDED} <= element_actions:
114
109
  latest_element = [
115
110
  element for element in diff_relationship.relationships if element.action is DiffAction.ADDED
116
111
  ][0]
@@ -5,7 +5,7 @@ from infrahub.core.constants import RelationshipHierarchyDirection, Relationship
5
5
  from infrahub.core.constants.database import DatabaseEdgeType
6
6
  from infrahub.core.query.node import NodeGetHierarchyQuery
7
7
  from infrahub.core.query.relationship import RelationshipGetPeerQuery, RelationshipPeerData
8
- from infrahub.core.schema import ProfileSchema
8
+ from infrahub.core.schema import ProfileSchema, TemplateSchema
9
9
  from infrahub.database import InfrahubDatabase
10
10
 
11
11
  from ..model.path import (
@@ -22,7 +22,9 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
22
22
  self.db = db
23
23
 
24
24
  async def enrich(
25
- self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs | None = None
25
+ self,
26
+ enriched_diff_root: EnrichedDiffRoot,
27
+ calculated_diffs: CalculatedDiffs | None = None, # noqa: ARG002
26
28
  ) -> None:
27
29
  # A hierarchy can be defined in 2 ways
28
30
  # - A node has a relationship of kind parent
@@ -36,7 +38,7 @@ class DiffHierarchyEnricher(DiffEnricherInterface):
36
38
  name=node.kind, branch=enriched_diff_root.diff_branch_name, duplicate=False
37
39
  )
38
40
 
39
- if isinstance(schema_node, ProfileSchema):
41
+ if isinstance(schema_node, ProfileSchema | TemplateSchema):
40
42
  continue
41
43
 
42
44
  if schema_node.has_parent_relationship:
@@ -154,10 +154,20 @@ class DiffLabelsEnricher(DiffEnricherInterface):
154
154
  for node in enriched_diff.nodes:
155
155
  if not node.relationships:
156
156
  continue
157
- node_schema = self.db.schema.get(name=node.kind, branch=self.diff_branch_name, duplicate=False)
157
+
158
+ node_schema = self.db.schema.get(name=node.kind, branch=enriched_diff.diff_branch_name, duplicate=False)
159
+ alternate_node_schema = None
160
+ if enriched_diff.diff_branch_name != enriched_diff.base_branch_name and self.db.schema.has(
161
+ name=node.kind, branch=enriched_diff.base_branch_name
162
+ ):
163
+ alternate_node_schema = self.db.schema.get(
164
+ name=node.kind, branch=enriched_diff.base_branch_name, duplicate=False
165
+ )
158
166
  for relationship_diff in node.relationships:
159
- relationship_schema = node_schema.get_relationship(name=relationship_diff.name)
160
- relationship_diff.label = relationship_schema.label or ""
167
+ relationship_schema = node_schema.get_relationship_or_none(name=relationship_diff.name)
168
+ if not relationship_schema and alternate_node_schema:
169
+ relationship_schema = alternate_node_schema.get_relationship_or_none(name=relationship_diff.name)
170
+ relationship_diff.label = relationship_schema.label or "" if relationship_schema else ""
161
171
 
162
172
  async def _get_display_label_map(
163
173
  self, display_label_requests: set[DisplayLabelRequest]
@@ -181,7 +191,7 @@ class DiffLabelsEnricher(DiffEnricherInterface):
181
191
  async def enrich(
182
192
  self,
183
193
  enriched_diff_root: EnrichedDiffRoot,
184
- calculated_diffs: CalculatedDiffs | None = None,
194
+ calculated_diffs: CalculatedDiffs | None = None, # noqa: ARG002
185
195
  conflicts_only: bool = False,
186
196
  ) -> None:
187
197
  self._base_branch_name = enriched_diff_root.base_branch_name
@@ -1,4 +1,4 @@
1
- from infrahub.core.constants import PathType, RelationshipCardinality
1
+ from infrahub.core.constants import PathType
2
2
  from infrahub.core.path import DataPath
3
3
  from infrahub.database import InfrahubDatabase
4
4
 
@@ -19,7 +19,7 @@ class DiffPathIdentifierEnricher(DiffEnricherInterface):
19
19
  raise RuntimeError("diff_branch_name not set")
20
20
  return self._diff_branch_name
21
21
 
22
- async def enrich(self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs) -> None:
22
+ async def enrich(self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs) -> None: # noqa: ARG002
23
23
  self._diff_branch_name = enriched_diff_root.diff_branch_name
24
24
  for node in enriched_diff_root.nodes:
25
25
  node_path = DataPath(
@@ -44,14 +44,8 @@ class DiffPathIdentifierEnricher(DiffEnricherInterface):
44
44
  attribute_property.path_identifier = property_path.get_path()
45
45
  if not node.relationships:
46
46
  continue
47
- node_schema = self.db.schema.get(name=node.kind, branch=self.diff_branch_name, duplicate=False)
48
47
  for relationship in node.relationships:
49
- relationship_schema = node_schema.get_relationship(name=relationship.name)
50
- path_type = (
51
- PathType.RELATIONSHIP_ONE
52
- if relationship_schema.cardinality is RelationshipCardinality.ONE
53
- else PathType.RELATIONSHIP_MANY
54
- )
48
+ path_type = PathType.from_relationship(relationship.cardinality)
55
49
  relationship_path = DataPath(
56
50
  branch=enriched_diff_root.diff_branch_name,
57
51
  path_type=path_type,
@@ -17,7 +17,9 @@ from .interface import DiffEnricherInterface
17
17
 
18
18
  class DiffSummaryCountsEnricher(DiffEnricherInterface):
19
19
  async def enrich(
20
- self, enriched_diff_root: EnrichedDiffRoot, calculated_diffs: CalculatedDiffs | None = None
20
+ self,
21
+ enriched_diff_root: EnrichedDiffRoot,
22
+ calculated_diffs: CalculatedDiffs | None = None, # noqa: ARG002
21
23
  ) -> None:
22
24
  self._add_root_summaries(diff_root=enriched_diff_root)
23
25
 
@@ -9,6 +9,7 @@ from infrahub.log import get_logger
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from infrahub.core.branch import Branch
12
+ from infrahub.core.diff.model.path import EnrichedDiffRoot
12
13
  from infrahub.core.diff.repository.repository import DiffRepository
13
14
  from infrahub.core.timestamp import Timestamp
14
15
  from infrahub.database import InfrahubDatabase
@@ -33,14 +34,16 @@ class DiffMerger:
33
34
  self.diff_repository = diff_repository
34
35
  self.serializer = serializer
35
36
 
36
- async def merge_graph(self, at: Timestamp) -> None:
37
+ async def merge_graph(self, at: Timestamp) -> EnrichedDiffRoot:
38
+ tracking_id = BranchTrackingId(name=self.source_branch.name)
37
39
  enriched_diffs = await self.diff_repository.get_roots_metadata(
38
- diff_branch_names=[self.source_branch.name], base_branch_names=[self.destination_branch.name]
40
+ diff_branch_names=[self.source_branch.name],
41
+ base_branch_names=[self.destination_branch.name],
42
+ tracking_id=tracking_id,
39
43
  )
40
44
  latest_diff = None
41
- tracking_id = BranchTrackingId(name=self.source_branch.name)
42
45
  for diff in enriched_diffs:
43
- if latest_diff is None or (diff.tracking_id == tracking_id and diff.to_time > latest_diff.to_time):
46
+ if latest_diff is None or (diff.to_time > latest_diff.to_time):
44
47
  latest_diff = diff
45
48
  if latest_diff is None:
46
49
  raise RuntimeError(f"Missing diff for branch {self.source_branch.name}")
@@ -75,6 +78,7 @@ class DiffMerger:
75
78
  self.source_branch.branched_from = at.to_string()
76
79
  await self.source_branch.save(db=self.db)
77
80
  registry.branch[self.source_branch.name] = self.source_branch
81
+ return enriched_diff
78
82
 
79
83
  async def rollback(self, at: Timestamp) -> None:
80
84
  rollback_query = await DiffMergeRollbackQuery.init(