infrahub-server 1.2.0b1__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 (297) 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 +1 -3
  5. infrahub/artifacts/tasks.py +1 -3
  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 +239 -11
  12. infrahub/computed_attribute/tasks.py +77 -442
  13. infrahub/computed_attribute/triggers.py +11 -45
  14. infrahub/config.py +43 -32
  15. infrahub/context.py +14 -0
  16. infrahub/core/account.py +4 -4
  17. infrahub/core/attribute.py +57 -57
  18. infrahub/core/branch/tasks.py +12 -9
  19. infrahub/core/changelog/diff.py +16 -8
  20. infrahub/core/changelog/models.py +189 -26
  21. infrahub/core/constants/__init__.py +5 -1
  22. infrahub/core/constants/infrahubkind.py +2 -0
  23. infrahub/core/constraint/node/runner.py +9 -8
  24. infrahub/core/diff/branch_differ.py +10 -10
  25. infrahub/core/diff/ipam_diff_parser.py +4 -5
  26. infrahub/core/diff/model/diff.py +27 -27
  27. infrahub/core/diff/model/path.py +3 -3
  28. infrahub/core/diff/query/merge.py +20 -17
  29. infrahub/core/diff/query_parser.py +4 -4
  30. infrahub/core/graph/__init__.py +1 -1
  31. infrahub/core/initialization.py +1 -10
  32. infrahub/core/ipam/constants.py +3 -4
  33. infrahub/core/ipam/reconciler.py +12 -12
  34. infrahub/core/ipam/utilization.py +10 -13
  35. infrahub/core/manager.py +34 -34
  36. infrahub/core/merge.py +7 -7
  37. infrahub/core/migrations/__init__.py +2 -3
  38. infrahub/core/migrations/graph/__init__.py +9 -4
  39. infrahub/core/migrations/graph/m017_add_core_profile.py +1 -5
  40. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +4 -4
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
  43. infrahub/core/migrations/graph/{m020_add_generate_template_attr.py → m022_add_generate_template_attr.py} +3 -3
  44. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +96 -0
  45. infrahub/core/migrations/query/attribute_add.py +2 -2
  46. infrahub/core/migrations/query/node_duplicate.py +18 -21
  47. infrahub/core/migrations/query/schema_attribute_update.py +2 -2
  48. infrahub/core/migrations/schema/models.py +19 -4
  49. infrahub/core/migrations/schema/tasks.py +2 -2
  50. infrahub/core/migrations/shared.py +16 -16
  51. infrahub/core/models.py +15 -6
  52. infrahub/core/node/__init__.py +29 -28
  53. infrahub/core/node/base.py +2 -4
  54. infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
  55. infrahub/core/node/constraints/grouped_uniqueness.py +99 -47
  56. infrahub/core/node/constraints/interface.py +1 -2
  57. infrahub/core/node/delete_validator.py +3 -5
  58. infrahub/core/node/ipam.py +4 -4
  59. infrahub/core/node/permissions.py +7 -7
  60. infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
  61. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -6
  62. infrahub/core/node/resource_manager/number_pool.py +3 -3
  63. infrahub/core/path.py +12 -12
  64. infrahub/core/property.py +11 -11
  65. infrahub/core/protocols.py +5 -0
  66. infrahub/core/protocols_base.py +21 -21
  67. infrahub/core/query/__init__.py +33 -33
  68. infrahub/core/query/attribute.py +6 -4
  69. infrahub/core/query/diff.py +3 -3
  70. infrahub/core/query/node.py +82 -32
  71. infrahub/core/query/relationship.py +24 -24
  72. infrahub/core/query/resource_manager.py +2 -0
  73. infrahub/core/query/standard_node.py +3 -3
  74. infrahub/core/query/subquery.py +9 -9
  75. infrahub/core/registry.py +13 -15
  76. infrahub/core/relationship/constraints/count.py +3 -4
  77. infrahub/core/relationship/constraints/peer_kind.py +3 -4
  78. infrahub/core/relationship/constraints/profiles_kind.py +2 -2
  79. infrahub/core/relationship/model.py +40 -46
  80. infrahub/core/schema/attribute_schema.py +9 -9
  81. infrahub/core/schema/basenode_schema.py +93 -44
  82. infrahub/core/schema/computed_attribute.py +3 -3
  83. infrahub/core/schema/definitions/core/__init__.py +13 -19
  84. infrahub/core/schema/definitions/core/account.py +151 -148
  85. infrahub/core/schema/definitions/core/artifact.py +122 -113
  86. infrahub/core/schema/definitions/core/builtin.py +19 -16
  87. infrahub/core/schema/definitions/core/check.py +61 -53
  88. infrahub/core/schema/definitions/core/core.py +17 -0
  89. infrahub/core/schema/definitions/core/generator.py +89 -85
  90. infrahub/core/schema/definitions/core/graphql_query.py +72 -70
  91. infrahub/core/schema/definitions/core/group.py +96 -93
  92. infrahub/core/schema/definitions/core/ipam.py +176 -235
  93. infrahub/core/schema/definitions/core/lineage.py +18 -16
  94. infrahub/core/schema/definitions/core/menu.py +42 -40
  95. infrahub/core/schema/definitions/core/permission.py +144 -142
  96. infrahub/core/schema/definitions/core/profile.py +16 -27
  97. infrahub/core/schema/definitions/core/propose_change.py +88 -79
  98. infrahub/core/schema/definitions/core/propose_change_comment.py +170 -165
  99. infrahub/core/schema/definitions/core/propose_change_validator.py +290 -288
  100. infrahub/core/schema/definitions/core/repository.py +231 -225
  101. infrahub/core/schema/definitions/core/resource_pool.py +156 -166
  102. infrahub/core/schema/definitions/core/template.py +27 -12
  103. infrahub/core/schema/definitions/core/transform.py +85 -76
  104. infrahub/core/schema/definitions/core/webhook.py +127 -101
  105. infrahub/core/schema/definitions/internal.py +16 -16
  106. infrahub/core/schema/dropdown.py +3 -4
  107. infrahub/core/schema/generated/attribute_schema.py +15 -18
  108. infrahub/core/schema/generated/base_node_schema.py +12 -14
  109. infrahub/core/schema/generated/node_schema.py +3 -5
  110. infrahub/core/schema/generated/relationship_schema.py +9 -11
  111. infrahub/core/schema/generic_schema.py +2 -2
  112. infrahub/core/schema/manager.py +20 -9
  113. infrahub/core/schema/node_schema.py +4 -2
  114. infrahub/core/schema/relationship_schema.py +7 -7
  115. infrahub/core/schema/schema_branch.py +276 -138
  116. infrahub/core/schema/schema_branch_computed.py +41 -4
  117. infrahub/core/task/task.py +3 -3
  118. infrahub/core/task/user_task.py +15 -15
  119. infrahub/core/utils.py +20 -18
  120. infrahub/core/validators/__init__.py +1 -3
  121. infrahub/core/validators/aggregated_checker.py +2 -2
  122. infrahub/core/validators/attribute/choices.py +2 -2
  123. infrahub/core/validators/attribute/enum.py +2 -2
  124. infrahub/core/validators/attribute/kind.py +2 -2
  125. infrahub/core/validators/attribute/length.py +2 -2
  126. infrahub/core/validators/attribute/optional.py +2 -2
  127. infrahub/core/validators/attribute/regex.py +2 -2
  128. infrahub/core/validators/attribute/unique.py +2 -2
  129. infrahub/core/validators/checks_runner.py +25 -2
  130. infrahub/core/validators/determiner.py +1 -3
  131. infrahub/core/validators/interface.py +6 -2
  132. infrahub/core/validators/model.py +22 -3
  133. infrahub/core/validators/models/validate_migration.py +17 -4
  134. infrahub/core/validators/node/attribute.py +2 -2
  135. infrahub/core/validators/node/generate_profile.py +2 -2
  136. infrahub/core/validators/node/hierarchy.py +3 -5
  137. infrahub/core/validators/node/inherit_from.py +27 -5
  138. infrahub/core/validators/node/relationship.py +2 -2
  139. infrahub/core/validators/relationship/count.py +4 -4
  140. infrahub/core/validators/relationship/optional.py +2 -2
  141. infrahub/core/validators/relationship/peer.py +2 -2
  142. infrahub/core/validators/shared.py +2 -2
  143. infrahub/core/validators/tasks.py +8 -0
  144. infrahub/core/validators/uniqueness/checker.py +22 -21
  145. infrahub/core/validators/uniqueness/index.py +2 -2
  146. infrahub/core/validators/uniqueness/model.py +11 -11
  147. infrahub/database/__init__.py +26 -22
  148. infrahub/database/metrics.py +7 -1
  149. infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
  150. infrahub/dependencies/component/registry.py +2 -2
  151. infrahub/events/__init__.py +25 -2
  152. infrahub/events/artifact_action.py +13 -25
  153. infrahub/events/branch_action.py +26 -18
  154. infrahub/events/generator.py +71 -0
  155. infrahub/events/group_action.py +10 -24
  156. infrahub/events/models.py +10 -16
  157. infrahub/events/node_action.py +87 -32
  158. infrahub/events/repository_action.py +5 -18
  159. infrahub/events/schema_action.py +4 -9
  160. infrahub/events/utils.py +16 -0
  161. infrahub/events/validator_action.py +55 -0
  162. infrahub/exceptions.py +23 -24
  163. infrahub/generators/models.py +1 -3
  164. infrahub/git/base.py +7 -7
  165. infrahub/git/integrator.py +26 -25
  166. infrahub/git/models.py +22 -9
  167. infrahub/git/repository.py +3 -3
  168. infrahub/git/tasks.py +67 -49
  169. infrahub/git/utils.py +48 -0
  170. infrahub/git/worktree.py +1 -2
  171. infrahub/git_credential/askpass.py +1 -2
  172. infrahub/graphql/analyzer.py +12 -0
  173. infrahub/graphql/app.py +13 -15
  174. infrahub/graphql/context.py +6 -0
  175. infrahub/graphql/initialization.py +3 -0
  176. infrahub/graphql/loaders/node.py +2 -12
  177. infrahub/graphql/loaders/peers.py +77 -0
  178. infrahub/graphql/loaders/shared.py +13 -0
  179. infrahub/graphql/manager.py +13 -10
  180. infrahub/graphql/mutations/artifact_definition.py +5 -5
  181. infrahub/graphql/mutations/computed_attribute.py +4 -5
  182. infrahub/graphql/mutations/graphql_query.py +5 -5
  183. infrahub/graphql/mutations/ipam.py +50 -70
  184. infrahub/graphql/mutations/main.py +164 -141
  185. infrahub/graphql/mutations/menu.py +5 -5
  186. infrahub/graphql/mutations/models.py +2 -4
  187. infrahub/graphql/mutations/node_getter/by_default_filter.py +10 -10
  188. infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
  189. infrahub/graphql/mutations/node_getter/by_id.py +1 -3
  190. infrahub/graphql/mutations/node_getter/interface.py +1 -2
  191. infrahub/graphql/mutations/proposed_change.py +7 -7
  192. infrahub/graphql/mutations/relationship.py +67 -35
  193. infrahub/graphql/mutations/repository.py +8 -8
  194. infrahub/graphql/mutations/resource_manager.py +3 -3
  195. infrahub/graphql/mutations/schema.py +4 -4
  196. infrahub/graphql/mutations/webhook.py +137 -0
  197. infrahub/graphql/parser.py +4 -4
  198. infrahub/graphql/queries/diff/tree.py +4 -4
  199. infrahub/graphql/queries/ipam.py +2 -2
  200. infrahub/graphql/queries/relationship.py +2 -2
  201. infrahub/graphql/queries/search.py +2 -2
  202. infrahub/graphql/resolvers/many_relationship.py +264 -0
  203. infrahub/graphql/resolvers/resolver.py +13 -110
  204. infrahub/graphql/subscription/graphql_query.py +2 -0
  205. infrahub/graphql/types/event.py +20 -11
  206. infrahub/graphql/types/node.py +2 -2
  207. infrahub/graphql/utils.py +2 -2
  208. infrahub/groups/ancestors.py +29 -0
  209. infrahub/groups/parsers.py +107 -0
  210. infrahub/menu/generator.py +7 -7
  211. infrahub/menu/menu.py +0 -10
  212. infrahub/menu/models.py +117 -16
  213. infrahub/menu/repository.py +111 -0
  214. infrahub/menu/utils.py +5 -8
  215. infrahub/message_bus/messages/__init__.py +1 -11
  216. infrahub/message_bus/messages/check_generator_run.py +2 -0
  217. infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
  218. infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
  219. infrahub/message_bus/operations/__init__.py +0 -2
  220. infrahub/message_bus/operations/check/generator.py +1 -0
  221. infrahub/message_bus/operations/event/__init__.py +2 -2
  222. infrahub/message_bus/operations/finalize/validator.py +51 -1
  223. infrahub/message_bus/operations/requests/generator_definition.py +19 -19
  224. infrahub/message_bus/operations/requests/proposed_change.py +3 -1
  225. infrahub/pools/number.py +2 -4
  226. infrahub/proposed_change/tasks.py +37 -28
  227. infrahub/pytest_plugin.py +13 -10
  228. infrahub/server.py +1 -2
  229. infrahub/services/adapters/event/__init__.py +1 -1
  230. infrahub/task_manager/event.py +23 -9
  231. infrahub/tasks/artifact.py +2 -4
  232. infrahub/telemetry/__init__.py +0 -0
  233. infrahub/telemetry/constants.py +9 -0
  234. infrahub/telemetry/database.py +86 -0
  235. infrahub/telemetry/models.py +65 -0
  236. infrahub/telemetry/task_manager.py +77 -0
  237. infrahub/{tasks/telemetry.py → telemetry/tasks.py} +49 -56
  238. infrahub/telemetry/utils.py +11 -0
  239. infrahub/trace.py +4 -4
  240. infrahub/transformations/tasks.py +2 -2
  241. infrahub/trigger/catalogue.py +2 -5
  242. infrahub/trigger/constants.py +0 -8
  243. infrahub/trigger/models.py +14 -1
  244. infrahub/trigger/setup.py +90 -0
  245. infrahub/trigger/tasks.py +35 -90
  246. infrahub/utils.py +11 -1
  247. infrahub/validators/__init__.py +0 -0
  248. infrahub/validators/events.py +42 -0
  249. infrahub/validators/tasks.py +41 -0
  250. infrahub/webhook/gather.py +17 -0
  251. infrahub/webhook/models.py +22 -5
  252. infrahub/webhook/tasks.py +44 -19
  253. infrahub/webhook/triggers.py +22 -5
  254. infrahub/workers/infrahub_async.py +2 -2
  255. infrahub/workers/utils.py +2 -2
  256. infrahub/workflows/catalogue.py +28 -20
  257. infrahub/workflows/initialization.py +1 -3
  258. infrahub/workflows/models.py +1 -1
  259. infrahub/workflows/utils.py +10 -1
  260. infrahub_sdk/client.py +27 -8
  261. infrahub_sdk/config.py +3 -0
  262. infrahub_sdk/context.py +13 -0
  263. infrahub_sdk/exceptions.py +6 -0
  264. infrahub_sdk/generator.py +4 -1
  265. infrahub_sdk/graphql.py +45 -13
  266. infrahub_sdk/node.py +69 -20
  267. infrahub_sdk/protocols_base.py +32 -11
  268. infrahub_sdk/query_groups.py +6 -35
  269. infrahub_sdk/schema/__init__.py +55 -26
  270. infrahub_sdk/schema/main.py +8 -0
  271. infrahub_sdk/task/__init__.py +10 -0
  272. infrahub_sdk/task/manager.py +12 -6
  273. infrahub_sdk/testing/schemas/animal.py +9 -0
  274. infrahub_sdk/timestamp.py +12 -4
  275. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +3 -2
  276. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +289 -260
  277. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/entry_points.txt +1 -0
  278. infrahub_testcontainers/constants.py +2 -0
  279. infrahub_testcontainers/container.py +157 -12
  280. infrahub_testcontainers/docker-compose.test.yml +31 -6
  281. infrahub_testcontainers/helpers.py +18 -73
  282. infrahub_testcontainers/host.py +41 -0
  283. infrahub_testcontainers/measurements.py +93 -0
  284. infrahub_testcontainers/models.py +38 -0
  285. infrahub_testcontainers/performance_test.py +166 -0
  286. infrahub_testcontainers/plugin.py +136 -0
  287. infrahub_testcontainers/prometheus.yml +30 -0
  288. infrahub/message_bus/messages/event_branch_create.py +0 -11
  289. infrahub/message_bus/messages/event_branch_delete.py +0 -11
  290. infrahub/message_bus/messages/event_branch_rebased.py +0 -9
  291. infrahub/message_bus/messages/event_node_mutated.py +0 -15
  292. infrahub/message_bus/messages/event_schema_update.py +0 -9
  293. infrahub/message_bus/operations/event/node.py +0 -20
  294. infrahub/message_bus/operations/event/schema.py +0 -17
  295. infrahub/webhook/constants.py +0 -1
  296. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
  297. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/WHEEL +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Mapping, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Mapping
5
5
 
6
6
  from graphene import InputObjectType, Mutation
7
7
  from graphene.types.mutation import MutationOptions
@@ -10,7 +10,6 @@ from typing_extensions import Self
10
10
 
11
11
  from infrahub import config, lock
12
12
  from infrahub.core import registry
13
- from infrahub.core.changelog.models import RelationshipChangelogGetter
14
13
  from infrahub.core.constants import InfrahubKind, MutationAction, RelationshipCardinality, RelationshipKind
15
14
  from infrahub.core.constraint.node.runner import NodeConstraintRunner
16
15
  from infrahub.core.manager import NodeManager
@@ -22,16 +21,13 @@ from infrahub.core.schema.template_schema import TemplateSchema
22
21
  from infrahub.core.timestamp import Timestamp
23
22
  from infrahub.database import retry_db_transaction
24
23
  from infrahub.dependencies.registry import get_component_registry
25
- from infrahub.events import EventMeta, NodeMutatedEvent
26
- from infrahub.exceptions import ValidationError
24
+ from infrahub.events.generator import generate_node_mutation_events
25
+ from infrahub.exceptions import HFIDViolatedError, InitializationError
27
26
  from infrahub.graphql.context import apply_external_context
28
27
  from infrahub.lock import InfrahubMultiLock, build_object_lock_name
29
28
  from infrahub.log import get_log_data, get_logger
30
- from infrahub.worker import WORKER_IDENTITY
31
29
 
32
30
  from .node_getter.by_default_filter import MutationNodeGetterByDefaultFilter
33
- from .node_getter.by_hfid import MutationNodeGetterByHfid
34
- from .node_getter.by_id import MutationNodeGetterById
35
31
 
36
32
  if TYPE_CHECKING:
37
33
  from graphql import GraphQLResolveInfo
@@ -44,7 +40,6 @@ if TYPE_CHECKING:
44
40
  from infrahub.graphql.types.context import ContextInput
45
41
 
46
42
  from ..initialization import GraphqlContext
47
- from .node_getter.interface import MutationNodeGetterInterface
48
43
 
49
44
 
50
45
  log = get_logger()
@@ -63,10 +58,18 @@ class DeleteResult:
63
58
  # Infrahub GraphQLType
64
59
  # ------------------------------------------
65
60
  class InfrahubMutationOptions(MutationOptions):
66
- schema: Optional[NodeSchema] = None
61
+ schema: MainSchemaTypes | None = None
62
+
63
+ @property
64
+ def active_schema(self) -> MainSchemaTypes:
65
+ if self.schema:
66
+ return self.schema
67
+ raise InitializationError("This class is not initialized with a schema")
67
68
 
68
69
 
69
70
  class InfrahubMutationMixin:
71
+ _meta: InfrahubMutationOptions
72
+
70
73
  @classmethod
71
74
  async def mutate(
72
75
  cls,
@@ -74,9 +77,8 @@ class InfrahubMutationMixin:
74
77
  info: GraphQLResolveInfo,
75
78
  data: InputObjectType,
76
79
  context: ContextInput | None = None,
77
- *args: Any, # noqa: ARG003
78
- **kwargs,
79
- ):
80
+ **kwargs: dict[str, Any],
81
+ ) -> Self:
80
82
  graphql_context: GraphqlContext = info.context
81
83
  await apply_external_context(graphql_context=graphql_context, context_input=context)
82
84
 
@@ -86,20 +88,22 @@ class InfrahubMutationMixin:
86
88
  deleted_nodes: list[Node] = []
87
89
 
88
90
  if "Create" in cls.__name__:
89
- obj, mutation = await cls.mutate_create(info=info, branch=graphql_context.branch, data=data, **kwargs)
91
+ obj, mutation = await cls.mutate_create(info=info, branch=graphql_context.branch, data=data)
90
92
  action = MutationAction.CREATED
91
93
  elif "Update" in cls.__name__:
92
94
  obj, mutation = await cls.mutate_update(info=info, branch=graphql_context.branch, data=data, **kwargs)
93
95
  action = MutationAction.UPDATED
94
96
  elif "Upsert" in cls.__name__:
95
97
  node_manager = NodeManager()
96
- node_getters = [
97
- MutationNodeGetterById(db=graphql_context.db, node_manager=node_manager),
98
- MutationNodeGetterByHfid(db=graphql_context.db, node_manager=node_manager),
99
- MutationNodeGetterByDefaultFilter(db=graphql_context.db, node_manager=node_manager),
100
- ]
98
+ node_getter_default_filter = MutationNodeGetterByDefaultFilter(
99
+ db=graphql_context.db, node_manager=node_manager
100
+ )
101
101
  obj, mutation, created = await cls.mutate_upsert(
102
- info=info, branch=graphql_context.branch, data=data, node_getters=node_getters, **kwargs
102
+ info=info,
103
+ branch=graphql_context.branch,
104
+ data=data,
105
+ node_getter_default_filter=node_getter_default_filter,
106
+ **kwargs,
103
107
  )
104
108
  if created:
105
109
  action = MutationAction.CREATED
@@ -124,57 +128,15 @@ class InfrahubMutationMixin:
124
128
  log_data = get_log_data()
125
129
  request_id = log_data.get("request_id", "")
126
130
 
127
- account_id: str | None = None
128
- if graphql_context.account_session:
129
- account_id = graphql_context.account_session.account_id
130
-
131
- meta = EventMeta(
132
- account_id=account_id,
133
- initiator_id=WORKER_IDENTITY,
134
- request_id=request_id,
131
+ events = await generate_node_mutation_events(
132
+ node=obj,
133
+ deleted_nodes=deleted_nodes,
134
+ db=graphql_context.db,
135
135
  branch=graphql_context.branch,
136
136
  context=graphql_context.get_context(),
137
- )
138
- main_event = NodeMutatedEvent(
139
- kind=obj._schema.kind,
140
- node_id=obj.id,
141
- data=obj.node_changelog,
137
+ request_id=request_id,
142
138
  action=action,
143
- fields=_get_data_fields(data),
144
- meta=meta,
145
139
  )
146
- relationship_changelogs = RelationshipChangelogGetter(db=graphql_context.db, branch=graphql_context.branch)
147
- node_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=obj.node_changelog)
148
-
149
- events = [main_event]
150
-
151
- deleted_changelogs = [node.node_changelog for node in deleted_nodes if node.id != obj.id]
152
- deleted_ids = {node.node_id for node in deleted_changelogs}
153
-
154
- for node_changelog in deleted_changelogs:
155
- meta = EventMeta.from_parent(parent=main_event)
156
- event = NodeMutatedEvent(
157
- kind=node_changelog.node_kind,
158
- node_id=node_changelog.node_id,
159
- data=node_changelog,
160
- action=MutationAction.DELETED,
161
- fields=node_changelog.updated_fields,
162
- meta=meta,
163
- )
164
- events.append(event)
165
-
166
- for node_changelog in node_changelogs:
167
- if node_changelog.node_id not in deleted_ids:
168
- meta = EventMeta.from_parent(parent=main_event)
169
- event = NodeMutatedEvent(
170
- kind=node_changelog.node_kind,
171
- node_id=node_changelog.node_id,
172
- data=node_changelog,
173
- action=MutationAction.UPDATED,
174
- fields=node_changelog.updated_fields,
175
- meta=meta,
176
- )
177
- events.append(event)
178
140
 
179
141
  for event in events:
180
142
  graphql_context.background.add_task(graphql_context.active_service.event.send, event)
@@ -190,7 +152,7 @@ class InfrahubMutationMixin:
190
152
 
191
153
  @classmethod
192
154
  async def _refresh_for_profile_update(
193
- cls, db: InfrahubDatabase, branch: Branch, obj: Node, previous_profile_ids: Optional[set[str]] = None
155
+ cls, db: InfrahubDatabase, branch: Branch, obj: Node, previous_profile_ids: set[str] | None = None
194
156
  ) -> Node:
195
157
  if not hasattr(obj, "profiles"):
196
158
  return obj
@@ -198,7 +160,7 @@ class InfrahubMutationMixin:
198
160
  if previous_profile_ids is None or previous_profile_ids != current_profile_ids:
199
161
  refreshed_node = await NodeManager.get_one_by_id_or_default_filter(
200
162
  db=db,
201
- kind=cls._meta.schema.kind,
163
+ kind=cls._meta.active_schema.kind,
202
164
  id=obj.get_id(),
203
165
  branch=branch,
204
166
  include_owner=True,
@@ -209,13 +171,13 @@ class InfrahubMutationMixin:
209
171
  return obj
210
172
 
211
173
  @classmethod
212
- async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch):
174
+ async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch) -> Node:
213
175
  """
214
176
  Wrapper around mutate_create_object to potentially activate locking.
215
177
  """
216
178
  schema_branch = db.schema.get_schema_branch(name=branch.name)
217
179
  lock_names = _get_kind_lock_names_on_object_mutation(
218
- kind=cls._meta.schema.kind, branch=branch, schema_branch=schema_branch
180
+ kind=cls._meta.active_schema.kind, branch=branch, schema_branch=schema_branch
219
181
  )
220
182
  if lock_names:
221
183
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
@@ -288,8 +250,13 @@ class InfrahubMutationMixin:
288
250
  if not template_relationship_peers:
289
251
  continue
290
252
 
291
- obj_peer_schema = relationship.get_peer_schema(db=db, branch=branch)
292
253
  for template_relationship_peer in template_relationship_peers.values():
254
+ # We retrieve peer schema for each peer in case we are processing a relationship which is based on a generic
255
+ obj_peer_schema = registry.schema.get_node_schema(
256
+ name=template_relationship_peer.get_schema().kind.removeprefix("Template"),
257
+ branch=branch,
258
+ duplicate=False,
259
+ )
293
260
  obj_peer_data = await cls._extract_peer_data(
294
261
  db=db,
295
262
  template_peer=template_relationship_peer,
@@ -298,7 +265,7 @@ class InfrahubMutationMixin:
298
265
  current_template=template,
299
266
  )
300
267
 
301
- obj_peer = await Node.init(schema=obj_peer_schema, db=db)
268
+ obj_peer = await Node.init(schema=obj_peer_schema, db=db, branch=branch)
302
269
  await obj_peer.new(db=db, **obj_peer_data)
303
270
  await constraint_runner.check(node=obj_peer, field_filters=list(obj_peer_data))
304
271
  await obj_peer.save(db=db)
@@ -318,7 +285,7 @@ class InfrahubMutationMixin:
318
285
  info: GraphQLResolveInfo,
319
286
  data: InputObjectType,
320
287
  branch: Branch,
321
- database: Optional[InfrahubDatabase] = None,
288
+ database: InfrahubDatabase | None = None,
322
289
  ) -> tuple[Node, Self]:
323
290
  graphql_context: GraphqlContext = info.context
324
291
  db = database or graphql_context.db
@@ -339,44 +306,41 @@ class InfrahubMutationMixin:
339
306
  NodeConstraintRunner, db=db.start_session(), branch=branch
340
307
  )
341
308
  node_class = Node
342
- if cls._meta.schema.kind in registry.node:
343
- node_class = registry.node[cls._meta.schema.kind]
309
+ if cls._meta.active_schema.kind in registry.node:
310
+ node_class = registry.node[cls._meta.active_schema.kind]
344
311
 
345
312
  fields_to_validate = list(data)
346
- try:
347
- if db.is_transaction:
348
- obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
349
- await obj.new(db=db, **data)
313
+ if db.is_transaction:
314
+ obj = await node_class.init(db=db, schema=cls._meta.schema, branch=branch)
315
+ await obj.new(db=db, **data)
316
+ await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
317
+ await obj.save(db=db)
318
+
319
+ object_template = await obj.get_object_template(db=db)
320
+ if object_template:
321
+ await cls._handle_template_relationships(
322
+ db=db,
323
+ branch=branch,
324
+ template=object_template,
325
+ obj=obj,
326
+ data=data,
327
+ )
328
+ else:
329
+ async with db.start_transaction() as dbt:
330
+ obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
331
+ await obj.new(db=dbt, **data)
350
332
  await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
351
- await obj.save(db=db)
333
+ await obj.save(db=dbt)
352
334
 
353
- object_template = await obj.get_object_template(db=db)
335
+ object_template = await obj.get_object_template(db=dbt)
354
336
  if object_template:
355
337
  await cls._handle_template_relationships(
356
- db=db,
338
+ db=dbt,
357
339
  branch=branch,
358
340
  template=object_template,
359
341
  obj=obj,
360
342
  data=data,
361
343
  )
362
- else:
363
- async with db.start_transaction() as dbt:
364
- obj = await node_class.init(db=dbt, schema=cls._meta.schema, branch=branch)
365
- await obj.new(db=dbt, **data)
366
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
367
- await obj.save(db=dbt)
368
-
369
- object_template = await obj.get_object_template(db=dbt)
370
- if object_template:
371
- await cls._handle_template_relationships(
372
- db=dbt,
373
- branch=branch,
374
- template=object_template,
375
- obj=obj,
376
- data=data,
377
- )
378
- except ValidationError as exc:
379
- raise ValidationError(input_value=str(exc)) from exc
380
344
 
381
345
  if await cls._get_profile_ids(db=db, obj=obj):
382
346
  obj = await cls._refresh_for_profile_update(db=db, branch=branch, obj=obj)
@@ -386,7 +350,7 @@ class InfrahubMutationMixin:
386
350
  @classmethod
387
351
  async def mutate_create_to_graphql(cls, info: GraphQLResolveInfo, db: InfrahubDatabase, obj: Node) -> Self:
388
352
  fields = await extract_fields(info.field_nodes[0].selection_set)
389
- result = {"ok": True}
353
+ result: dict[str, Any] = {"ok": True}
390
354
  if "object" in fields:
391
355
  result["object"] = await obj.to_graphql(db=db, fields=fields.get("object", {}))
392
356
  return cls(**result)
@@ -399,6 +363,7 @@ class InfrahubMutationMixin:
399
363
  branch: Branch,
400
364
  db: InfrahubDatabase,
401
365
  obj: Node,
366
+ run_constraint_checks: bool = True,
402
367
  ) -> tuple[Node, Self]:
403
368
  """
404
369
  Wrapper around mutate_update to potentially activate locking and call it within a database transaction.
@@ -406,24 +371,37 @@ class InfrahubMutationMixin:
406
371
 
407
372
  schema_branch = db.schema.get_schema_branch(name=branch.name)
408
373
  lock_names = _get_kind_lock_names_on_object_mutation(
409
- kind=cls._meta.schema.kind, branch=branch, schema_branch=schema_branch
374
+ kind=cls._meta.active_schema.kind, branch=branch, schema_branch=schema_branch
410
375
  )
411
376
 
412
377
  if db.is_transaction:
413
378
  if lock_names:
414
379
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
415
- obj = await cls.mutate_update_object(db=db, info=info, data=data, branch=branch, obj=obj)
380
+ obj = await cls.mutate_update_object(
381
+ db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
382
+ )
416
383
  else:
417
- obj = await cls.mutate_update_object(db=db, info=info, data=data, branch=branch, obj=obj)
384
+ obj = await cls.mutate_update_object(
385
+ db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
386
+ )
418
387
  result = await cls.mutate_update_to_graphql(db=db, info=info, obj=obj)
419
388
  return obj, result
420
389
 
421
390
  async with db.start_transaction() as dbt:
422
391
  if lock_names:
423
392
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
424
- obj = await cls.mutate_update_object(db=dbt, info=info, data=data, branch=branch, obj=obj)
393
+ obj = await cls.mutate_update_object(
394
+ db=dbt,
395
+ info=info,
396
+ data=data,
397
+ branch=branch,
398
+ obj=obj,
399
+ run_constraint_checks=run_constraint_checks,
400
+ )
425
401
  else:
426
- obj = await cls.mutate_update_object(db=dbt, info=info, data=data, branch=branch, obj=obj)
402
+ obj = await cls.mutate_update_object(
403
+ db=dbt, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
404
+ )
427
405
  result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=obj)
428
406
  return obj, result
429
407
 
@@ -434,20 +412,17 @@ class InfrahubMutationMixin:
434
412
  info: GraphQLResolveInfo,
435
413
  data: InputObjectType,
436
414
  branch: Branch,
437
- database: Optional[InfrahubDatabase] = None,
438
- node: Optional[Node] = None,
415
+ database: InfrahubDatabase | None = None,
416
+ node: Node | None = None,
439
417
  ) -> tuple[Node, Self]:
440
418
  graphql_context: GraphqlContext = info.context
441
419
  db = database or graphql_context.db
442
420
 
443
421
  obj = node or await NodeManager.find_object(
444
- db=db, kind=cls._meta.schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
422
+ db=db, kind=cls._meta.active_schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
445
423
  )
446
424
 
447
- try:
448
- obj, result = await cls._call_mutate_update(info=info, data=data, db=db, branch=branch, obj=obj)
449
- except ValidationError as exc:
450
- raise ValueError(str(exc)) from exc
425
+ obj, result = await cls._call_mutate_update(info=info, data=data, db=db, branch=branch, obj=obj)
451
426
 
452
427
  return obj, result
453
428
 
@@ -459,6 +434,7 @@ class InfrahubMutationMixin:
459
434
  data: InputObjectType,
460
435
  branch: Branch,
461
436
  obj: Node,
437
+ run_constraint_checks: bool = True,
462
438
  ) -> Node:
463
439
  component_registry = get_component_registry()
464
440
  node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
@@ -466,7 +442,8 @@ class InfrahubMutationMixin:
466
442
  before_mutate_profile_ids = await cls._get_profile_ids(db=db, obj=obj)
467
443
  await obj.from_graphql(db=db, data=data)
468
444
  fields_to_validate = list(data)
469
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
445
+ if run_constraint_checks:
446
+ await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
470
447
 
471
448
  fields = list(data.keys())
472
449
  for field_to_remove in ("id", "hfid"):
@@ -489,7 +466,7 @@ class InfrahubMutationMixin:
489
466
  ) -> Self:
490
467
  fields_object = await extract_fields(info.field_nodes[0].selection_set)
491
468
  fields_object = fields_object.get("object", {})
492
- result = {"ok": True}
469
+ result: dict[str, Any] = {"ok": True}
493
470
  if fields_object:
494
471
  result["object"] = await obj.to_graphql(db=db, fields=fields_object)
495
472
  return cls(**result)
@@ -501,31 +478,76 @@ class InfrahubMutationMixin:
501
478
  info: GraphQLResolveInfo,
502
479
  data: InputObjectType,
503
480
  branch: Branch,
504
- node_getters: list[MutationNodeGetterInterface],
505
- database: Optional[InfrahubDatabase] = None,
481
+ node_getter_default_filter: MutationNodeGetterByDefaultFilter,
482
+ database: InfrahubDatabase | None = None,
506
483
  ) -> tuple[Node, Self, bool]:
507
- schema_name = cls._meta.schema.kind
484
+ """
485
+ First, check whether payload contains data identifying the node, such as id, hfid, or relevant fields for
486
+ default_filter. If not, we will try to create the node, but this creation might fail if payload contains
487
+ hfid fields (not `hfid` field itself) that would match an existing node in the database. In that case,
488
+ we would update the node without rerunning uniqueness constraint.
489
+ """
490
+
491
+ schema_name = cls._meta.active_schema.kind
508
492
 
509
493
  graphql_context: GraphqlContext = info.context
510
494
  db = database or graphql_context.db
511
-
512
- node_schema = db.schema.get(name=schema_name, branch=branch)
513
-
495
+ dict_data = dict(data)
514
496
  node = None
515
- for getter in node_getters:
516
- node = await getter.get_node(node_schema=node_schema, data=data, branch=branch)
517
- if node:
518
- break
497
+ run_constraint_checks = True
519
498
 
520
- if node:
521
- updated_obj, mutation = await cls.mutate_update(info=info, data=data, branch=branch, database=db, node=node)
499
+ if "id" in dict_data:
500
+ node = await NodeManager.get_one(
501
+ db=db, id=dict_data["id"], kind=schema_name, branch=branch, raise_on_error=True
502
+ )
503
+ updated_obj, mutation = await cls._call_mutate_update(
504
+ info=info,
505
+ data=data,
506
+ db=db,
507
+ branch=branch,
508
+ obj=node,
509
+ run_constraint_checks=run_constraint_checks,
510
+ )
522
511
  return updated_obj, mutation, False
523
- # We need to convert the InputObjectType into a dict in order to remove hfid that isn't a valid input when creating the object
524
- data_dict = dict(data)
512
+
513
+ if cls._meta.active_schema.default_filter is not None:
514
+ node = await node_getter_default_filter.get_node(
515
+ node_schema=cls._meta.active_schema, data=data, branch=branch
516
+ )
517
+
525
518
  if "hfid" in data:
526
- del data_dict["hfid"]
527
- created_obj, mutation = await cls.mutate_create(info=info, data=data_dict, branch=branch)
528
- return created_obj, mutation, True
519
+ node = await NodeManager.get_one_by_hfid(db=db, hfid=dict_data["hfid"], kind=schema_name, branch=branch)
520
+
521
+ if node is not None:
522
+ updated_obj, mutation = await cls._call_mutate_update(
523
+ info=info,
524
+ data=data,
525
+ db=db,
526
+ branch=branch,
527
+ obj=node,
528
+ run_constraint_checks=run_constraint_checks,
529
+ )
530
+ return updated_obj, mutation, False
531
+
532
+ try:
533
+ dict_data.pop("hfid", "unused") # `hfid` is invalid for creation.
534
+ created_obj, mutation = await cls.mutate_create(info=info, data=dict_data, branch=branch)
535
+ return created_obj, mutation, True
536
+ except HFIDViolatedError as exc:
537
+ # Only the HFID constraint has been violated, it means the node exists and we can update without rerunning constraints
538
+ if len(exc.matching_nodes_ids) > 1:
539
+ raise RuntimeError(f"Multiple {schema_name} nodes have the same hfid (database corrupted)") from exc
540
+ node_id = list(exc.matching_nodes_ids)[0]
541
+ node = await NodeManager.get_one(db=db, id=node_id, kind=schema_name, branch=branch, raise_on_error=True)
542
+ updated_obj, mutation = await cls._call_mutate_update(
543
+ info=info,
544
+ data=data,
545
+ db=db,
546
+ branch=branch,
547
+ obj=node,
548
+ run_constraint_checks=run_constraint_checks,
549
+ )
550
+ return updated_obj, mutation, False
529
551
 
530
552
  @classmethod
531
553
  @retry_db_transaction(name="object_delete")
@@ -538,14 +560,15 @@ class InfrahubMutationMixin:
538
560
  graphql_context: GraphqlContext = info.context
539
561
 
540
562
  obj = await NodeManager.find_object(
541
- db=graphql_context.db, kind=cls._meta.schema.kind, id=data.get("id"), hfid=data.get("hfid"), branch=branch
563
+ db=graphql_context.db,
564
+ kind=cls._meta.active_schema.kind,
565
+ id=data.get("id"),
566
+ hfid=data.get("hfid"),
567
+ branch=branch,
542
568
  )
543
569
 
544
- try:
545
- async with graphql_context.db.start_transaction() as db:
546
- deleted = await NodeManager.delete(db=db, branch=branch, nodes=[obj])
547
- except ValidationError as exc:
548
- raise ValueError(str(exc)) from exc
570
+ async with graphql_context.db.start_transaction() as db:
571
+ deleted = await NodeManager.delete(db=db, branch=branch, nodes=[obj])
549
572
 
550
573
  deleted_str = ", ".join([f"{d.get_kind()}({d.get_id()})" for d in deleted])
551
574
  log.info(f"nodes deleted: {deleted_str}")
@@ -559,9 +582,9 @@ class InfrahubMutation(InfrahubMutationMixin, Mutation):
559
582
  @classmethod
560
583
  def __init_subclass_with_meta__(
561
584
  cls,
562
- schema: Optional[Union[NodeSchema, GenericSchema, ProfileSchema, TemplateSchema]] = None,
563
- _meta=None,
564
- **options,
585
+ schema: NodeSchema | GenericSchema | ProfileSchema | TemplateSchema | None = None,
586
+ _meta: InfrahubMutationOptions | None = None,
587
+ **options: dict[str, Any],
565
588
  ) -> None:
566
589
  # Make sure schema is a valid NodeSchema Node Class
567
590
  if not isinstance(schema, NodeSchema | GenericSchema | ProfileSchema | TemplateSchema):
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Any, Optional
1
+ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  from graphene import InputObjectType, Mutation
4
4
  from graphql import GraphQLResolveInfo
@@ -35,7 +35,7 @@ def validate_namespace(data: InputObjectType) -> None:
35
35
  class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
36
36
  @classmethod
37
37
  def __init_subclass_with_meta__(
38
- cls, schema: NodeSchema, _meta: Optional[Any] = None, **options: dict[str, Any]
38
+ cls, schema: NodeSchema, _meta: Any | None = None, **options: dict[str, Any]
39
39
  ) -> None:
40
40
  # Make sure schema is a valid NodeSchema Node Class
41
41
  if not isinstance(schema, NodeSchema):
@@ -53,7 +53,7 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
53
53
  info: GraphQLResolveInfo,
54
54
  data: InputObjectType,
55
55
  branch: Branch,
56
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
56
+ database: InfrahubDatabase | None = None, # noqa: ARG003
57
57
  ) -> tuple[Node, Self]:
58
58
  validate_namespace(data=data)
59
59
 
@@ -67,8 +67,8 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
67
67
  info: GraphQLResolveInfo,
68
68
  data: InputObjectType,
69
69
  branch: Branch,
70
- database: Optional[InfrahubDatabase] = None, # noqa: ARG003
71
- node: Optional[Node] = None, # noqa: ARG003
70
+ database: InfrahubDatabase | None = None, # noqa: ARG003
71
+ node: Node | None = None, # noqa: ARG003
72
72
  ) -> tuple[Node, Self]:
73
73
  graphql_context: GraphqlContext = info.context
74
74
 
@@ -1,13 +1,11 @@
1
- from typing import Optional
2
-
3
1
  from pydantic import BaseModel
4
2
 
5
3
 
6
4
  class BranchCreateModel(BaseModel):
7
5
  name: str
8
- id: Optional[str] = None
6
+ id: str | None = None
9
7
  description: str = ""
10
8
  origin_branch: str = "main"
11
- branched_from: Optional[str] = None
9
+ branched_from: str | None = None
12
10
  sync_with_git: bool = True
13
11
  is_isolated: bool = True
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from copy import copy
2
2
 
3
3
  from graphene import InputObjectType
4
4
 
@@ -21,21 +21,21 @@ class MutationNodeGetterByDefaultFilter(MutationNodeGetterInterface):
21
21
  node_schema: MainSchemaTypes,
22
22
  data: InputObjectType,
23
23
  branch: Branch,
24
- ) -> Optional[Node]:
25
- node = None
26
- default_filter_value = None
24
+ ) -> Node | None:
27
25
  if not node_schema.default_filter:
28
- return node
29
- this_datum = data
26
+ return None
27
+
28
+ data = copy(data)
30
29
 
31
30
  for filter_key in node_schema.default_filter.split("__"):
32
- if filter_key not in this_datum:
31
+ if filter_key not in data:
33
32
  break
34
- this_datum = this_datum[filter_key]
35
- default_filter_value = this_datum
33
+ data = data[filter_key]
34
+
35
+ default_filter_value = data
36
36
 
37
37
  if not default_filter_value:
38
- return node
38
+ return None
39
39
 
40
40
  return await self.node_manager.get_one_by_default_filter(
41
41
  db=self.db,
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from graphene import InputObjectType
4
2
 
5
3
  from infrahub.core.branch import Branch
@@ -22,7 +20,7 @@ class MutationNodeGetterByHfid(MutationNodeGetterInterface):
22
20
  node_schema: MainSchemaTypes,
23
21
  data: InputObjectType,
24
22
  branch: Branch,
25
- ) -> Optional[Node]:
23
+ ) -> Node | None:
26
24
  if not node_schema.human_friendly_id:
27
25
  return None
28
26
 
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from graphene import InputObjectType
4
2
 
5
3
  from infrahub.core.branch import Branch
@@ -21,7 +19,7 @@ class MutationNodeGetterById(MutationNodeGetterInterface):
21
19
  node_schema: MainSchemaTypes,
22
20
  data: InputObjectType,
23
21
  branch: Branch,
24
- ) -> Optional[Node]:
22
+ ) -> Node | None:
25
23
  node = None
26
24
  if "id" not in data:
27
25
  return node
@@ -1,5 +1,4 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Optional
3
2
 
4
3
  from graphene import InputObjectType
5
4
 
@@ -15,4 +14,4 @@ class MutationNodeGetterInterface(ABC):
15
14
  node_schema: MainSchemaTypes,
16
15
  data: InputObjectType,
17
16
  branch: Branch,
18
- ) -> Optional[Node]: ...
17
+ ) -> Node | None: ...