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
@@ -4,7 +4,8 @@ import hashlib
4
4
  import keyword
5
5
  import os
6
6
  from dataclasses import asdict, dataclass
7
- from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, Optional, Union, overload
7
+ from enum import Enum
8
+ from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, overload
8
9
 
9
10
  from infrahub_sdk.utils import compare_lists, intersection
10
11
  from pydantic import field_validator
@@ -24,6 +25,7 @@ if TYPE_CHECKING:
24
25
 
25
26
 
26
27
  NODE_METADATA_ATTRIBUTES = ["_source", "_owner"]
28
+ INHERITED = "INHERITED"
27
29
 
28
30
 
29
31
  class BaseNodeSchema(GeneratedBaseNodeSchema):
@@ -62,6 +64,17 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
62
64
  Be careful hash generated from hash() have a salt by default and they will not be the same across run"""
63
65
  return hash(self.get_hash())
64
66
 
67
+ def to_dict(self) -> dict:
68
+ data = self.model_dump(
69
+ exclude_unset=True, exclude_none=True, exclude_defaults=True, exclude={"attributes", "relationships"}
70
+ )
71
+ for field_name, value in data.items():
72
+ if isinstance(value, Enum):
73
+ data[field_name] = value.value
74
+ data["attributes"] = [attr.to_dict() for attr in self.attributes]
75
+ data["relationships"] = [rel.to_dict() for rel in self.relationships]
76
+ return data
77
+
65
78
  def get_hash(self, display_values: bool = False) -> str:
66
79
  """Extend the Hash Calculation to account for attributes and relationships."""
67
80
 
@@ -108,7 +121,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
108
121
  other: Self,
109
122
  get_func: Callable,
110
123
  get_map_func: Callable,
111
- obj_type: type[Union[AttributeSchema, RelationshipSchema]],
124
+ obj_type: type[AttributeSchema | RelationshipSchema],
112
125
  ) -> HashableModelDiff:
113
126
  """The goal of this function is to reduce the amount of code duplicated between Attribute and Relationship to calculate a diff
114
127
  The logic is the same for both, except that the functions we are using to access these objects are differents
@@ -126,8 +139,8 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
126
139
  reversed_map_other = dict(map(reversed, other_map.items()))
127
140
 
128
141
  # Identify which elements are using the same id on both sides
129
- clean_local_ids = [id for id in local_map.values() if id is not None]
130
- clean_other_ids = [id for id in other_map.values() if id is not None]
142
+ clean_local_ids = [id for id in local_map.values() if id is not None and id != INHERITED]
143
+ clean_other_ids = [id for id in other_map.values() if id is not None and id != INHERITED]
131
144
  shared_ids = intersection(list1=clean_local_ids, list2=clean_other_ids)
132
145
 
133
146
  # Identify which elements are present on both side based on the name
@@ -164,16 +177,14 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
164
177
  return elements_diff
165
178
 
166
179
  @overload
167
- def get_field(
168
- self, name: str, raise_on_error: Literal[True] = True
169
- ) -> Union[AttributeSchema, RelationshipSchema]: ...
180
+ def get_field(self, name: str, raise_on_error: Literal[True] = True) -> AttributeSchema | RelationshipSchema: ...
170
181
 
171
182
  @overload
172
183
  def get_field(
173
184
  self, name: str, raise_on_error: Literal[False] = False
174
- ) -> Optional[Union[AttributeSchema, RelationshipSchema]]: ...
185
+ ) -> AttributeSchema | RelationshipSchema | None: ...
175
186
 
176
- def get_field(self, name: str, raise_on_error: bool = True) -> Optional[Union[AttributeSchema, RelationshipSchema]]:
187
+ def get_field(self, name: str, raise_on_error: bool = True) -> AttributeSchema | RelationshipSchema | None:
177
188
  if field := self.get_attribute_or_none(name=name):
178
189
  return field
179
190
 
@@ -192,7 +203,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
192
203
 
193
204
  raise ValueError(f"Unable to find the attribute {name}")
194
205
 
195
- def get_attribute_or_none(self, name: str) -> Optional[AttributeSchema]:
206
+ def get_attribute_or_none(self, name: str) -> AttributeSchema | None:
196
207
  for item in self.attributes:
197
208
  if item.name == name:
198
209
  return item
@@ -218,7 +229,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
218
229
 
219
230
  raise ValueError(f"Unable to find the relationship with the ID: {id}")
220
231
 
221
- def get_relationship_or_none(self, name: str) -> Optional[RelationshipSchema]:
232
+ def get_relationship_or_none(self, name: str) -> RelationshipSchema | None:
222
233
  for item in self.relationships:
223
234
  if item.name == name:
224
235
  return item
@@ -230,9 +241,9 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
230
241
  @overload
231
242
  def get_relationship_by_identifier(
232
243
  self, id: str, raise_on_error: Literal[False] = False
233
- ) -> Optional[RelationshipSchema]: ...
244
+ ) -> RelationshipSchema | None: ...
234
245
 
235
- def get_relationship_by_identifier(self, id: str, raise_on_error: bool = True) -> Optional[RelationshipSchema]:
246
+ def get_relationship_by_identifier(self, id: str, raise_on_error: bool = True) -> RelationshipSchema | None:
236
247
  for item in self.relationships:
237
248
  if item.identifier == id:
238
249
  return item
@@ -257,13 +268,13 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
257
268
  def get_attributes_name_id_map(self) -> dict[str, str]:
258
269
  name_id_map = {}
259
270
  for attr in self.attributes:
260
- name_id_map[attr.name] = attr.id
271
+ name_id_map[attr.name] = INHERITED if attr.inherited else attr.id
261
272
  return name_id_map
262
273
 
263
274
  def get_relationship_name_id_map(self) -> dict[str, str]:
264
275
  name_id_map = {}
265
276
  for rel in self.relationships:
266
- name_id_map[rel.name] = rel.id
277
+ name_id_map[rel.name] = INHERITED if rel.inherited else rel.id
267
278
  return name_id_map
268
279
 
269
280
  @property
@@ -331,7 +342,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
331
342
  fields[subpaths[0]] = cls.convert_path_to_graphql_fields(path=subpaths[1])
332
343
  return fields
333
344
 
334
- def generate_fields_for_display_label(self) -> Optional[dict]:
345
+ def generate_fields_for_display_label(self) -> dict | None:
335
346
  """Generate a dictionary containing the list of fields that are required
336
347
  to generate the display_label.
337
348
 
@@ -341,12 +352,12 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
341
352
  if not self.display_labels:
342
353
  return None
343
354
 
344
- fields: dict[str, Union[str, None, dict[str, None]]] = {}
355
+ fields: dict[str, str | None | dict[str, None]] = {}
345
356
  for item in self.display_labels:
346
357
  fields.update(self.convert_path_to_graphql_fields(path=item))
347
358
  return fields
348
359
 
349
- def generate_fields_for_hfid(self) -> Optional[dict]:
360
+ def generate_fields_for_hfid(self) -> dict | None:
350
361
  """Generate a dictionary containing the list of fields that are required
351
362
  to generate the hfid.
352
363
 
@@ -356,7 +367,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
356
367
  if not self.human_friendly_id:
357
368
  return None
358
369
 
359
- fields: dict[str, Union[str, None, dict[str, None]]] = {}
370
+ fields: dict[str, str | None | dict[str, None]] = {}
360
371
  for item in self.human_friendly_id:
361
372
  fields.update(self.convert_path_to_graphql_fields(path=item))
362
373
  return fields
@@ -369,11 +380,11 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
369
380
 
370
381
  return value
371
382
 
372
- def parse_schema_path(self, path: str, schema: Optional[SchemaBranch] = None) -> SchemaAttributePath:
383
+ def parse_schema_path(self, path: str, schema: SchemaBranch | None = None) -> SchemaAttributePath:
373
384
  schema_path = SchemaAttributePath()
374
- relationship_piece: Optional[str] = None
375
- attribute_piece: Optional[str] = None
376
- property_piece: Optional[str] = None
385
+ relationship_piece: str | None = None
386
+ attribute_piece: str | None = None
387
+ property_piece: str | None = None
377
388
 
378
389
  path_parts = path.split("__")
379
390
  if path_parts[0] in self.relationship_names:
@@ -422,33 +433,79 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
422
433
  def get_unique_constraint_schema_attribute_paths(
423
434
  self,
424
435
  schema_branch: SchemaBranch,
425
- include_unique_attributes: bool = False,
426
- ) -> list[list[SchemaAttributePath]]:
427
- constraint_paths_groups = []
428
- if include_unique_attributes:
429
- for attribute_schema in self.unique_attributes:
430
- constraint_paths_groups.append(
431
- [SchemaAttributePath(attribute_schema=attribute_schema, attribute_property_name="value")]
432
- )
433
-
434
- if not self.uniqueness_constraints:
435
- return constraint_paths_groups
436
+ ) -> list[SchemaUniquenessConstraintPath]:
437
+ if self.uniqueness_constraints is None:
438
+ return []
439
+
440
+ uniqueness_constraint_paths = []
436
441
 
437
442
  for uniqueness_path_group in self.uniqueness_constraints:
438
- constraint_paths_group = []
439
- for uniqueness_path_part in uniqueness_path_group:
440
- constraint_paths_group.append(self.parse_schema_path(path=uniqueness_path_part, schema=schema_branch))
441
- if constraint_paths_group not in constraint_paths_groups:
442
- constraint_paths_groups.append(constraint_paths_group)
443
- return constraint_paths_groups
443
+ attributes_paths = [
444
+ self.parse_schema_path(path=uniqueness_path_part, schema=schema_branch)
445
+ for uniqueness_path_part in uniqueness_path_group
446
+ ]
447
+ uniqueness_constraint_type = self.get_uniqueness_constraint_type(
448
+ uniqueness_constraint=set(uniqueness_path_group), schema_branch=schema_branch
449
+ )
450
+ uniqueness_constraint_path = SchemaUniquenessConstraintPath(
451
+ attributes_paths=attributes_paths, typ=uniqueness_constraint_type
452
+ )
453
+ uniqueness_constraint_paths.append(uniqueness_constraint_path)
454
+
455
+ return uniqueness_constraint_paths
456
+
457
+ def convert_hfid_to_uniqueness_constraint(self, schema_branch: SchemaBranch) -> list[str] | None:
458
+ if self.human_friendly_id is None:
459
+ return None
460
+
461
+ uniqueness_constraint = []
462
+ for item in self.human_friendly_id:
463
+ schema_attribute_path = self.parse_schema_path(path=item, schema=schema_branch)
464
+ if schema_attribute_path.is_type_attribute:
465
+ uniqueness_constraint.append(item)
466
+ elif schema_attribute_path.is_type_relationship:
467
+ uniqueness_constraint.append(schema_attribute_path.relationship_schema.name)
468
+ return uniqueness_constraint
469
+
470
+ def get_uniqueness_constraint_type(
471
+ self, uniqueness_constraint: set[str], schema_branch: SchemaBranch
472
+ ) -> UniquenessConstraintType:
473
+ hfid = self.convert_hfid_to_uniqueness_constraint(schema_branch=schema_branch)
474
+ if hfid is None:
475
+ return UniquenessConstraintType.STANDARD
476
+ hfid_set = set(hfid)
477
+ if uniqueness_constraint == hfid_set:
478
+ return UniquenessConstraintType.HFID
479
+ if uniqueness_constraint <= hfid_set:
480
+ return UniquenessConstraintType.SUBSET_OF_HFID
481
+ return UniquenessConstraintType.STANDARD
482
+
483
+
484
+ @dataclass
485
+ class SchemaUniquenessConstraintPath:
486
+ attributes_paths: list[SchemaAttributePath]
487
+ typ: UniquenessConstraintType
488
+
489
+
490
+ class UniquenessConstraintType(Enum):
491
+ HFID = "HFID"
492
+ SUBSET_OF_HFID = "SUBSET_OF_HFID"
493
+ STANDARD = "STANDARD"
494
+
495
+
496
+ @dataclass
497
+ class UniquenessConstraintViolation:
498
+ nodes_ids: set[str]
499
+ fields: list[str]
500
+ typ: UniquenessConstraintType
444
501
 
445
502
 
446
503
  @dataclass
447
504
  class SchemaAttributePath:
448
- relationship_schema: Optional[RelationshipSchema] = None
449
- related_schema: Optional[Union[NodeSchema, GenericSchema]] = None
450
- attribute_schema: Optional[AttributeSchema] = None
451
- attribute_property_name: Optional[str] = None
505
+ relationship_schema: RelationshipSchema | None = None
506
+ related_schema: NodeSchema | GenericSchema | None = None
507
+ attribute_schema: AttributeSchema | None = None
508
+ attribute_property_name: str | None = None
452
509
 
453
510
  @property
454
511
  def is_type_attribute(self) -> bool:
@@ -480,6 +537,10 @@ class SchemaAttributePath:
480
537
  return self.attribute_property_name
481
538
  raise AttributePathParsingError("An attribute_property_name was expected but not found")
482
539
 
540
+ @property
541
+ def attribute_path_as_str(self) -> str:
542
+ return self.active_attribute_schema.name + "__" + self.active_attribute_property_name
543
+
483
544
 
484
545
  @dataclass
485
546
  class SchemaAttributePathValue(SchemaAttributePath):
@@ -1,4 +1,4 @@
1
- from typing import Any, Optional
1
+ from typing import Any
2
2
 
3
3
  from pydantic import ConfigDict, Field, model_serializer
4
4
 
@@ -8,10 +8,10 @@ from infrahub.core.models import HashableModel
8
8
 
9
9
  class ComputedAttribute(HashableModel):
10
10
  kind: ComputedAttributeKind
11
- jinja2_template: Optional[str] = Field(
11
+ jinja2_template: str | None = Field(
12
12
  default=None, description="The Jinja2 template in string format, required when assignment_type=jinja2"
13
13
  )
14
- transform: Optional[str] = Field(
14
+ transform: str | None = Field(
15
15
  default=None, description="The Python Transform name or ID, required when assignment_type=transform"
16
16
  )
17
17
 
@@ -0,0 +1,147 @@
1
+ from typing import Any
2
+
3
+ from ...generic_schema import GenericSchema
4
+ from ...node_schema import NodeSchema
5
+ from .account import (
6
+ core_account,
7
+ core_account_token,
8
+ core_credential,
9
+ core_generic_account,
10
+ core_password_credential,
11
+ core_refresh_token,
12
+ )
13
+ from .artifact import core_artifact, core_artifact_definition, core_artifact_target
14
+ from .builtin import builtin_tag
15
+ from .check import core_check_definition
16
+ from .core import core_node, core_task_target
17
+ from .generator import core_generator_definition, core_generator_instance
18
+ from .graphql_query import core_graphql_query
19
+ from .group import core_generator_group, core_graphql_query_group, core_group, core_standard_group
20
+ from .ipam import builtin_ip_address, builtin_ip_prefix, builtin_ipam, core_ipam_namespace
21
+ from .lineage import lineage_owner, lineage_source
22
+ from .menu import generic_menu_item, menu_item
23
+ from .permission import (
24
+ core_account_group,
25
+ core_account_role,
26
+ core_base_permission,
27
+ core_global_permission,
28
+ core_object_permission,
29
+ )
30
+ from .profile import core_profile_schema_definition
31
+ from .propose_change import core_proposed_change
32
+ from .propose_change_comment import (
33
+ core_artifact_thread,
34
+ core_change_comment,
35
+ core_change_thread,
36
+ core_file_thread,
37
+ core_object_thread,
38
+ core_propose_change_comment,
39
+ core_thread,
40
+ core_thread_comment,
41
+ )
42
+ from .propose_change_validator import (
43
+ core_artifact_check,
44
+ core_artifact_validator,
45
+ core_check,
46
+ core_data_check,
47
+ core_data_validator,
48
+ core_file_check,
49
+ core_generator_check,
50
+ core_generator_validator,
51
+ core_propose_change_validator,
52
+ core_repository_validator,
53
+ core_schema_check,
54
+ core_schema_validator,
55
+ core_standard_check,
56
+ core_user_validator,
57
+ )
58
+ from .repository import core_generic_repository, core_read_only_repository, core_repository
59
+ from .resource_pool import core_ip_address_pool, core_ip_prefix_pool, core_number_pool, core_resource_pool
60
+ from .template import core_object_component_template, core_object_template
61
+ from .transform import core_transform, core_transform_jinja2, core_transform_python
62
+ from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
63
+
64
+ core_models_mixed: dict[str, list] = {
65
+ "generics": [
66
+ core_node,
67
+ lineage_owner,
68
+ core_profile_schema_definition,
69
+ lineage_source,
70
+ core_propose_change_comment,
71
+ core_thread,
72
+ core_group,
73
+ core_propose_change_validator,
74
+ core_check,
75
+ core_transform,
76
+ core_artifact_target,
77
+ core_task_target,
78
+ core_webhook,
79
+ core_generic_repository,
80
+ builtin_ipam,
81
+ builtin_ip_prefix,
82
+ builtin_ip_address,
83
+ core_resource_pool,
84
+ core_generic_account,
85
+ core_base_permission,
86
+ core_credential,
87
+ core_object_template,
88
+ core_object_component_template,
89
+ generic_menu_item,
90
+ ],
91
+ "nodes": [
92
+ menu_item,
93
+ core_standard_group,
94
+ core_generator_group,
95
+ core_graphql_query_group,
96
+ builtin_tag,
97
+ core_account,
98
+ core_account_token,
99
+ core_password_credential,
100
+ core_refresh_token,
101
+ core_proposed_change,
102
+ core_change_thread,
103
+ core_file_thread,
104
+ core_artifact_thread,
105
+ core_object_thread,
106
+ core_change_comment,
107
+ core_thread_comment,
108
+ core_repository,
109
+ core_read_only_repository,
110
+ core_transform_jinja2,
111
+ core_data_check,
112
+ core_standard_check,
113
+ core_schema_check,
114
+ core_file_check,
115
+ core_artifact_check,
116
+ core_generator_check,
117
+ core_data_validator,
118
+ core_repository_validator,
119
+ core_user_validator,
120
+ core_schema_validator,
121
+ core_artifact_validator,
122
+ core_generator_validator,
123
+ core_check_definition,
124
+ core_transform_python,
125
+ core_graphql_query,
126
+ core_artifact,
127
+ core_artifact_definition,
128
+ core_generator_definition,
129
+ core_generator_instance,
130
+ core_standard_webhook,
131
+ core_custom_webhook,
132
+ core_ipam_namespace,
133
+ core_ip_prefix_pool,
134
+ core_ip_address_pool,
135
+ core_number_pool,
136
+ core_global_permission,
137
+ core_object_permission,
138
+ core_account_role,
139
+ core_account_group,
140
+ ],
141
+ }
142
+
143
+
144
+ core_models: dict[str, Any] = {
145
+ "generics": [item.to_dict() if isinstance(item, GenericSchema) else item for item in core_models_mixed["generics"]],
146
+ "nodes": [item.to_dict() if isinstance(item, NodeSchema) else item for item in core_models_mixed["nodes"]],
147
+ }
@@ -0,0 +1,171 @@
1
+ from infrahub.core.constants import (
2
+ AccountStatus,
3
+ AccountType,
4
+ BranchSupportType,
5
+ InfrahubKind,
6
+ )
7
+ from infrahub.core.constants import RelationshipCardinality as Cardinality
8
+
9
+ from ...attribute_schema import AttributeSchema as Attr
10
+ from ...dropdown import DropdownChoice
11
+ from ...generic_schema import GenericSchema
12
+ from ...node_schema import NodeSchema
13
+ from ...relationship_schema import (
14
+ RelationshipSchema as Rel,
15
+ )
16
+
17
+ core_account = NodeSchema(
18
+ name="Account",
19
+ namespace="Core",
20
+ description="User Account for Infrahub",
21
+ include_in_menu=False,
22
+ label="Account",
23
+ icon="mdi:account",
24
+ default_filter="name__value",
25
+ order_by=["name__value"],
26
+ display_labels=["label__value"],
27
+ generate_profile=False,
28
+ branch=BranchSupportType.AGNOSTIC,
29
+ inherit_from=[InfrahubKind.LINEAGEOWNER, InfrahubKind.LINEAGESOURCE, InfrahubKind.GENERICACCOUNT],
30
+ )
31
+
32
+ core_account_token = NodeSchema(
33
+ name="AccountToken",
34
+ namespace="Internal",
35
+ description="Token for User Account",
36
+ include_in_menu=False,
37
+ label="Account Token",
38
+ default_filter="token__value",
39
+ display_labels=["token__value"],
40
+ generate_profile=False,
41
+ branch=BranchSupportType.AGNOSTIC,
42
+ uniqueness_constraints=[["token__value"]],
43
+ documentation="/topics/auth",
44
+ attributes=[
45
+ Attr(name="name", kind="Text", optional=True),
46
+ Attr(name="token", kind="Text", unique=True),
47
+ Attr(name="expiration", kind="DateTime", optional=True),
48
+ ],
49
+ relationships=[
50
+ Rel(
51
+ name="account",
52
+ peer=InfrahubKind.GENERICACCOUNT,
53
+ optional=False,
54
+ cardinality=Cardinality.ONE,
55
+ identifier="account__token",
56
+ ),
57
+ ],
58
+ )
59
+
60
+ core_password_credential = NodeSchema(
61
+ name="PasswordCredential",
62
+ namespace="Core",
63
+ description="Username/Password based credential",
64
+ include_in_menu=False,
65
+ label="Username / Password",
66
+ generate_profile=False,
67
+ branch=BranchSupportType.AGNOSTIC,
68
+ inherit_from=[InfrahubKind.CREDENTIAL],
69
+ attributes=[
70
+ Attr(name="username", kind="Text", optional=True, branch=BranchSupportType.AGNOSTIC, order_weight=6000),
71
+ Attr(
72
+ name="password",
73
+ kind="Password",
74
+ optional=True,
75
+ branch=BranchSupportType.AGNOSTIC,
76
+ order_weight=7000,
77
+ ),
78
+ ],
79
+ )
80
+
81
+ core_refresh_token = NodeSchema(
82
+ name="RefreshToken",
83
+ namespace="Internal",
84
+ description="Refresh Token",
85
+ include_in_menu=False,
86
+ label="Refresh Token",
87
+ display_labels=[],
88
+ generate_profile=False,
89
+ branch=BranchSupportType.AGNOSTIC,
90
+ attributes=[
91
+ Attr(name="expiration", kind="DateTime", optional=False),
92
+ ],
93
+ relationships=[
94
+ Rel(
95
+ name="account",
96
+ peer=InfrahubKind.GENERICACCOUNT,
97
+ optional=False,
98
+ cardinality=Cardinality.ONE,
99
+ identifier="account__refreshtoken",
100
+ ),
101
+ ],
102
+ )
103
+
104
+ core_credential = GenericSchema(
105
+ name="Credential",
106
+ namespace="Core",
107
+ description="A credential that could be referenced to access external services.",
108
+ include_in_menu=False,
109
+ label="Credential",
110
+ default_filter="name__value",
111
+ order_by=["name__value"],
112
+ display_labels=["label__value"],
113
+ icon="mdi:key-variant",
114
+ human_friendly_id=["name__value"],
115
+ branch=BranchSupportType.AGNOSTIC,
116
+ uniqueness_constraints=[["name__value"]],
117
+ documentation="/topics/auth",
118
+ attributes=[
119
+ Attr(name="name", kind="Text", unique=True, order_weight=1000),
120
+ Attr(name="label", kind="Text", optional=True, order_weight=2000),
121
+ Attr(name="description", kind="Text", optional=True, order_weight=3000),
122
+ ],
123
+ )
124
+
125
+ core_generic_account = GenericSchema(
126
+ name="GenericAccount",
127
+ namespace="Core",
128
+ description="User Account for Infrahub",
129
+ include_in_menu=False,
130
+ label="Account",
131
+ icon="mdi:account",
132
+ default_filter="name__value",
133
+ order_by=["name__value"],
134
+ display_labels=["label__value"],
135
+ human_friendly_id=["name__value"],
136
+ branch=BranchSupportType.AGNOSTIC,
137
+ documentation="/topics/auth",
138
+ uniqueness_constraints=[["name__value"]],
139
+ attributes=[
140
+ Attr(name="name", kind="Text", unique=True),
141
+ Attr(name="password", kind="HashedPassword", unique=False),
142
+ Attr(name="label", kind="Text", optional=True),
143
+ Attr(name="description", kind="Text", optional=True),
144
+ Attr(
145
+ name="account_type",
146
+ kind="Text",
147
+ default_value=AccountType.USER.value,
148
+ enum=AccountType.available_types(),
149
+ ),
150
+ Attr(
151
+ name="status",
152
+ kind="Dropdown",
153
+ choices=[
154
+ DropdownChoice(
155
+ name=AccountStatus.ACTIVE.value,
156
+ label="Active",
157
+ description="Account is allowed to login",
158
+ color="#52be80",
159
+ ),
160
+ DropdownChoice(
161
+ name=AccountStatus.INACTIVE.value,
162
+ label="Inactive",
163
+ description="Account is not allowed to login",
164
+ color="#e74c3c",
165
+ ),
166
+ ],
167
+ default_value=AccountStatus.ACTIVE.value,
168
+ ),
169
+ ],
170
+ relationships=[Rel(name="tokens", peer=InfrahubKind.ACCOUNTTOKEN, optional=True, cardinality=Cardinality.MANY)],
171
+ )