infrahub-server 1.3.5__py3-none-any.whl → 1.4.0b0__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 (158) hide show
  1. infrahub/api/internal.py +5 -0
  2. infrahub/artifacts/tasks.py +17 -22
  3. infrahub/branch/merge_mutation_checker.py +38 -0
  4. infrahub/cli/__init__.py +2 -2
  5. infrahub/cli/context.py +7 -3
  6. infrahub/cli/db.py +5 -16
  7. infrahub/cli/upgrade.py +7 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +53 -2
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +9 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +20 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/repository/repository.py +0 -8
  17. infrahub/core/diff/tasks.py +11 -8
  18. infrahub/core/graph/__init__.py +1 -1
  19. infrahub/core/graph/index.py +1 -2
  20. infrahub/core/graph/schema.py +50 -29
  21. infrahub/core/initialization.py +62 -33
  22. infrahub/core/ipam/tasks.py +4 -3
  23. infrahub/core/merge.py +8 -10
  24. infrahub/core/migrations/graph/__init__.py +2 -0
  25. infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
  26. infrahub/core/migrations/query/attribute_add.py +27 -2
  27. infrahub/core/migrations/schema/tasks.py +6 -5
  28. infrahub/core/node/proposed_change.py +43 -0
  29. infrahub/core/protocols.py +12 -0
  30. infrahub/core/query/attribute.py +32 -14
  31. infrahub/core/query/diff.py +11 -0
  32. infrahub/core/query/ipam.py +13 -7
  33. infrahub/core/query/node.py +51 -10
  34. infrahub/core/query/resource_manager.py +3 -3
  35. infrahub/core/schema/basenode_schema.py +8 -0
  36. infrahub/core/schema/definitions/core/__init__.py +10 -1
  37. infrahub/core/schema/definitions/core/ipam.py +28 -2
  38. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  39. infrahub/core/schema/definitions/core/webhook.py +3 -0
  40. infrahub/core/schema/generic_schema.py +10 -0
  41. infrahub/core/schema/manager.py +10 -1
  42. infrahub/core/schema/node_schema.py +22 -17
  43. infrahub/core/schema/profile_schema.py +8 -0
  44. infrahub/core/schema/schema_branch.py +9 -5
  45. infrahub/core/schema/template_schema.py +8 -0
  46. infrahub/core/validators/checks_runner.py +5 -5
  47. infrahub/core/validators/tasks.py +6 -7
  48. infrahub/core/validators/uniqueness/checker.py +4 -2
  49. infrahub/core/validators/uniqueness/model.py +1 -0
  50. infrahub/core/validators/uniqueness/query.py +57 -7
  51. infrahub/database/__init__.py +2 -1
  52. infrahub/events/__init__.py +18 -0
  53. infrahub/events/constants.py +7 -0
  54. infrahub/events/generator.py +29 -2
  55. infrahub/events/proposed_change_action.py +181 -0
  56. infrahub/generators/tasks.py +24 -20
  57. infrahub/git/base.py +4 -7
  58. infrahub/git/integrator.py +21 -12
  59. infrahub/git/repository.py +15 -30
  60. infrahub/git/tasks.py +121 -106
  61. infrahub/graphql/field_extractor.py +69 -0
  62. infrahub/graphql/manager.py +15 -11
  63. infrahub/graphql/mutations/account.py +2 -2
  64. infrahub/graphql/mutations/action.py +8 -2
  65. infrahub/graphql/mutations/artifact_definition.py +4 -1
  66. infrahub/graphql/mutations/branch.py +10 -5
  67. infrahub/graphql/mutations/graphql_query.py +2 -1
  68. infrahub/graphql/mutations/main.py +14 -8
  69. infrahub/graphql/mutations/menu.py +2 -1
  70. infrahub/graphql/mutations/proposed_change.py +225 -8
  71. infrahub/graphql/mutations/relationship.py +5 -0
  72. infrahub/graphql/mutations/repository.py +2 -1
  73. infrahub/graphql/mutations/tasks.py +7 -9
  74. infrahub/graphql/mutations/webhook.py +4 -1
  75. infrahub/graphql/parser.py +15 -6
  76. infrahub/graphql/queries/__init__.py +10 -1
  77. infrahub/graphql/queries/account.py +3 -3
  78. infrahub/graphql/queries/branch.py +2 -2
  79. infrahub/graphql/queries/diff/tree.py +3 -3
  80. infrahub/graphql/queries/event.py +13 -3
  81. infrahub/graphql/queries/ipam.py +23 -1
  82. infrahub/graphql/queries/proposed_change.py +84 -0
  83. infrahub/graphql/queries/relationship.py +2 -2
  84. infrahub/graphql/queries/resource_manager.py +3 -3
  85. infrahub/graphql/queries/search.py +3 -2
  86. infrahub/graphql/queries/status.py +3 -2
  87. infrahub/graphql/queries/task.py +2 -2
  88. infrahub/graphql/resolvers/ipam.py +440 -0
  89. infrahub/graphql/resolvers/many_relationship.py +4 -3
  90. infrahub/graphql/resolvers/resolver.py +5 -5
  91. infrahub/graphql/resolvers/single_relationship.py +3 -2
  92. infrahub/graphql/schema.py +25 -5
  93. infrahub/graphql/types/__init__.py +2 -2
  94. infrahub/graphql/types/attribute.py +3 -3
  95. infrahub/graphql/types/event.py +60 -0
  96. infrahub/groups/tasks.py +6 -6
  97. infrahub/lock.py +3 -2
  98. infrahub/menu/generator.py +8 -0
  99. infrahub/message_bus/operations/__init__.py +9 -12
  100. infrahub/message_bus/operations/git/file.py +6 -5
  101. infrahub/message_bus/operations/git/repository.py +12 -20
  102. infrahub/message_bus/operations/refresh/registry.py +15 -9
  103. infrahub/message_bus/operations/send/echo.py +7 -4
  104. infrahub/message_bus/types.py +1 -0
  105. infrahub/permissions/globals.py +1 -4
  106. infrahub/permissions/manager.py +8 -5
  107. infrahub/pools/prefix.py +7 -5
  108. infrahub/prefect_server/app.py +31 -0
  109. infrahub/prefect_server/bootstrap.py +18 -0
  110. infrahub/proposed_change/action_checker.py +206 -0
  111. infrahub/proposed_change/approval_revoker.py +40 -0
  112. infrahub/proposed_change/branch_diff.py +3 -1
  113. infrahub/proposed_change/checker.py +45 -0
  114. infrahub/proposed_change/constants.py +32 -2
  115. infrahub/proposed_change/tasks.py +182 -150
  116. infrahub/py.typed +0 -0
  117. infrahub/server.py +29 -17
  118. infrahub/services/__init__.py +13 -28
  119. infrahub/services/adapters/cache/__init__.py +4 -0
  120. infrahub/services/adapters/cache/nats.py +2 -0
  121. infrahub/services/adapters/cache/redis.py +3 -0
  122. infrahub/services/adapters/message_bus/__init__.py +0 -2
  123. infrahub/services/adapters/message_bus/local.py +1 -2
  124. infrahub/services/adapters/message_bus/nats.py +6 -8
  125. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  126. infrahub/services/adapters/workflow/__init__.py +1 -0
  127. infrahub/services/adapters/workflow/local.py +1 -8
  128. infrahub/services/component.py +2 -1
  129. infrahub/task_manager/event.py +52 -0
  130. infrahub/task_manager/models.py +9 -0
  131. infrahub/tasks/artifact.py +6 -7
  132. infrahub/tasks/check.py +4 -7
  133. infrahub/telemetry/tasks.py +15 -18
  134. infrahub/transformations/tasks.py +10 -6
  135. infrahub/trigger/tasks.py +4 -3
  136. infrahub/types.py +4 -0
  137. infrahub/validators/events.py +7 -7
  138. infrahub/validators/tasks.py +6 -7
  139. infrahub/webhook/models.py +45 -45
  140. infrahub/webhook/tasks.py +25 -24
  141. infrahub/workers/dependencies.py +143 -0
  142. infrahub/workers/infrahub_async.py +19 -43
  143. infrahub/workflows/catalogue.py +16 -2
  144. infrahub/workflows/initialization.py +5 -4
  145. infrahub/workflows/models.py +2 -0
  146. infrahub_sdk/client.py +6 -6
  147. infrahub_sdk/ctl/repository.py +51 -0
  148. infrahub_sdk/ctl/schema.py +9 -9
  149. infrahub_sdk/protocols.py +40 -6
  150. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/METADATA +5 -4
  151. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/RECORD +158 -144
  152. infrahub_testcontainers/container.py +17 -0
  153. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  154. infrahub_testcontainers/docker-compose.test.yml +56 -1
  155. infrahub_testcontainers/helpers.py +4 -1
  156. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/LICENSE.txt +0 -0
  157. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/WHEEL +0 -0
  158. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ from graphql import (
4
+ FieldNode,
5
+ FragmentSpreadNode,
6
+ GraphQLResolveInfo,
7
+ InlineFragmentNode,
8
+ SelectionSetNode,
9
+ )
10
+
11
+
12
+ class GraphQLFieldExtractor:
13
+ """Class to extract fields from a GraphQL selection set."""
14
+
15
+ def __init__(self, info: GraphQLResolveInfo):
16
+ self.info = info
17
+ self.fragments = info.fragments
18
+
19
+ def get_fields(self) -> dict[str, Any]:
20
+ """Extract fields from the GraphQL selection set."""
21
+ fields = self._extract_fields(selection_set=self.info.field_nodes[0].selection_set)
22
+ return fields or {}
23
+
24
+ def _extract_fields(self, selection_set: SelectionSetNode | None) -> dict[str, dict] | None:
25
+ """This function extract all the requested fields in a tree of Dict from a SelectionSetNode
26
+
27
+ The goal of this function is to limit the fields that we need to query from the backend.
28
+
29
+ Currently the function support Fields and InlineFragments but in a combined tree where the fragments are merged together
30
+ This implementation may seam counter intuitive but in the current implementation
31
+ it's better to have slightly more information at time passed to the query manager.
32
+
33
+ In the future we'll probably need to redesign how we read GraphQL queries to generate better Database query.
34
+ """
35
+
36
+ if not selection_set:
37
+ return None
38
+
39
+ fields: dict[str, dict | Any] = {}
40
+ for node in selection_set.selections:
41
+ sub_selection_set = getattr(node, "selection_set", None)
42
+ if isinstance(node, FieldNode):
43
+ value = self._extract_fields(sub_selection_set)
44
+ if node.name.value not in fields:
45
+ fields[node.name.value] = value
46
+ elif isinstance(fields[node.name.value], dict) and isinstance(value, dict):
47
+ fields[node.name.value].update(value)
48
+
49
+ elif isinstance(node, InlineFragmentNode):
50
+ for sub_node in node.selection_set.selections:
51
+ sub_sub_selection_set = getattr(sub_node, "selection_set", None)
52
+ value = self._extract_fields(sub_sub_selection_set)
53
+ sub_node_name = getattr(sub_node, "name", "")
54
+ sub_node_name_value = getattr(sub_node_name, "value", "")
55
+ if sub_node_name_value not in fields:
56
+ fields[sub_node_name_value] = self._extract_fields(sub_sub_selection_set)
57
+ elif isinstance(fields[sub_node_name_value], dict) and isinstance(value, dict):
58
+ fields[sub_node_name_value].update(value)
59
+ elif isinstance(node, FragmentSpreadNode):
60
+ if node.name.value in self.info.fragments:
61
+ if fragment_fields := self._extract_fields(self.info.fragments[node.name.value].selection_set):
62
+ fields.update(fragment_fields)
63
+
64
+ return fields
65
+
66
+
67
+ def extract_graphql_fields(info: GraphQLResolveInfo) -> dict[str, Any]:
68
+ graphql_extractor = GraphQLFieldExtractor(info=info)
69
+ return graphql_extractor.get_fields()
@@ -40,6 +40,7 @@ from .mutations.resource_manager import (
40
40
  InfrahubNumberPoolMutation,
41
41
  )
42
42
  from .mutations.webhook import InfrahubWebhookMutation
43
+ from .resolvers.ipam import ipam_paginated_list_resolver
43
44
  from .resolvers.resolver import (
44
45
  account_resolver,
45
46
  ancestors_resolver,
@@ -57,8 +58,8 @@ from .types import (
57
58
  InfrahubObject,
58
59
  PaginatedObjectPermission,
59
60
  RelatedIPAddressNodeInput,
61
+ RelatedIPPrefixNodeInput,
60
62
  RelatedNodeInput,
61
- RelatedPrefixNodeInput,
62
63
  )
63
64
  from .types.attribute import BaseAttribute as BaseAttributeType
64
65
  from .types.attribute import TextAttributeType
@@ -343,14 +344,10 @@ class GraphQLSchemaManager:
343
344
 
344
345
  def _get_related_input_type(self, relationship: RelationshipSchema) -> type[RelatedNodeInput]:
345
346
  peer_schema = self.schema.get(name=relationship.peer, duplicate=False)
346
- if (isinstance(peer_schema, NodeSchema) and peer_schema.is_ip_prefix()) or (
347
- isinstance(peer_schema, GenericSchema) and relationship.peer == InfrahubKind.IPPREFIX
348
- ):
349
- return RelatedPrefixNodeInput
347
+ if peer_schema.is_ip_prefix:
348
+ return RelatedIPPrefixNodeInput
350
349
 
351
- if (isinstance(peer_schema, NodeSchema) and peer_schema.is_ip_address()) or (
352
- isinstance(peer_schema, GenericSchema) and relationship.peer == InfrahubKind.IPADDRESS
353
- ):
350
+ if peer_schema.is_ip_address:
354
351
  return RelatedIPAddressNodeInput
355
352
 
356
353
  return RelatedNodeInput
@@ -495,7 +492,9 @@ class GraphQLSchemaManager:
495
492
 
496
493
  class_attrs[node_schema.kind] = graphene.Field(
497
494
  node_type,
498
- resolver=default_paginated_list_resolver,
495
+ resolver=default_paginated_list_resolver
496
+ if node_name not in [InfrahubKind.IPADDRESS, InfrahubKind.IPPREFIX]
497
+ else ipam_paginated_list_resolver,
499
498
  required=True,
500
499
  **node_filters,
501
500
  )
@@ -530,9 +529,9 @@ class GraphQLSchemaManager:
530
529
  InfrahubKind.NODETRIGGERRELATIONSHIPMATCH: InfrahubTriggerRuleMatchMutation,
531
530
  }
532
531
 
533
- if isinstance(node_schema, NodeSchema) and node_schema.is_ip_prefix():
532
+ if isinstance(node_schema, NodeSchema) and node_schema.is_ip_prefix:
534
533
  base_class = InfrahubIPPrefixMutation
535
- elif isinstance(node_schema, NodeSchema) and node_schema.is_ip_address():
534
+ elif isinstance(node_schema, NodeSchema) and node_schema.is_ip_address:
536
535
  base_class = InfrahubIPAddressMutation
537
536
  else:
538
537
  base_class = mutation_map.get(node_schema.kind, InfrahubMutation)
@@ -912,6 +911,11 @@ class GraphQLSchemaManager:
912
911
  filters.update(get_attribute_type().get_graphql_filters(name="any"))
913
912
  filters["partial_match"] = graphene.Boolean()
914
913
 
914
+ if schema.kind in [InfrahubKind.IPADDRESS, InfrahubKind.IPPREFIX]:
915
+ # This is only available for IPAM generics
916
+ filters["include_available"] = graphene.Boolean()
917
+ filters["kinds"] = graphene.List(graphene.NonNull(graphene.String))
918
+
915
919
  if not top_level:
916
920
  return filters
917
921
 
@@ -2,7 +2,6 @@ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  from graphene import Boolean, Field, InputField, InputObjectType, Mutation, String
4
4
  from graphql import GraphQLResolveInfo
5
- from infrahub_sdk.utils import extract_fields
6
5
  from infrahub_sdk.uuidt import UUIDT
7
6
  from typing_extensions import Self
8
7
 
@@ -14,6 +13,7 @@ from infrahub.core.protocols import CoreAccount, CoreNode, InternalAccountToken
14
13
  from infrahub.core.timestamp import Timestamp
15
14
  from infrahub.database import InfrahubDatabase, retry_db_transaction
16
15
  from infrahub.exceptions import NodeNotFoundError, PermissionDeniedError
16
+ from infrahub.graphql.field_extractor import extract_graphql_fields
17
17
 
18
18
  from ..models import OrderModel
19
19
  from ..types import InfrahubObjectType
@@ -101,7 +101,7 @@ class AccountMixin:
101
101
  async with db.start_transaction() as dbt:
102
102
  await obj.save(db=dbt)
103
103
 
104
- fields = await extract_fields(info.field_nodes[0].selection_set)
104
+ fields = extract_graphql_fields(info=info)
105
105
  return cls(object=await obj.to_graphql(db=db, fields=fields.get("object", {})), ok=True) # type: ignore[call-arg]
106
106
 
107
107
  @classmethod
@@ -46,11 +46,14 @@ class InfrahubTriggerRuleMutation(InfrahubMutationMixin, Mutation):
46
46
  data: InputObjectType,
47
47
  branch: Branch,
48
48
  database: InfrahubDatabase | None = None,
49
+ override_data: dict[str, Any] | None = None,
49
50
  ) -> tuple[Node, Self]:
50
51
  graphql_context: GraphqlContext = info.context
51
52
  db = database or graphql_context.db
52
53
  _validate_node_kind(data=data, db=db)
53
- trigger_rule_definition, result = await super().mutate_create(info=info, data=data, branch=branch, database=db)
54
+ trigger_rule_definition, result = await super().mutate_create(
55
+ info=info, data=data, branch=branch, database=db, override_data=override_data
56
+ )
54
57
 
55
58
  return trigger_rule_definition, result
56
59
 
@@ -93,11 +96,14 @@ class InfrahubTriggerRuleMatchMutation(InfrahubMutationMixin, Mutation):
93
96
  data: InputObjectType,
94
97
  branch: Branch,
95
98
  database: InfrahubDatabase | None = None, # noqa: ARG003
99
+ override_data: dict[str, Any] | None = None,
96
100
  ) -> tuple[Node, Self]:
97
101
  graphql_context: GraphqlContext = info.context
98
102
 
99
103
  async with graphql_context.db.start_transaction() as dbt:
100
- trigger_match, result = await super().mutate_create(info=info, data=data, branch=branch, database=dbt)
104
+ trigger_match, result = await super().mutate_create(
105
+ info=info, data=data, branch=branch, database=dbt, override_data=override_data
106
+ )
101
107
  trigger_match_model = cast(CoreNodeTriggerAttributeMatch | CoreNodeTriggerRelationshipMatch, trigger_match)
102
108
  node_trigger_rule = await trigger_match_model.trigger.get_peer(db=dbt, raise_on_error=True)
103
109
  node_trigger_rule_model = cast(CoreNodeTriggerRule, node_trigger_rule)
@@ -49,10 +49,13 @@ class InfrahubArtifactDefinitionMutation(InfrahubMutationMixin, Mutation):
49
49
  data: InputObjectType,
50
50
  branch: Branch,
51
51
  database: InfrahubDatabase | None = None, # noqa: ARG003
52
+ override_data: dict[str, Any] | None = None,
52
53
  ) -> tuple[Node, Self]:
53
54
  graphql_context: GraphqlContext = info.context
54
55
 
55
- artifact_definition, result = await super().mutate_create(info=info, data=data, branch=branch)
56
+ artifact_definition, result = await super().mutate_create(
57
+ info=info, data=data, branch=branch, override_data=override_data
58
+ )
56
59
 
57
60
  if graphql_context.service:
58
61
  model = RequestArtifactDefinitionGenerate(
@@ -3,13 +3,14 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from graphene import Boolean, Field, InputField, InputObjectType, Mutation, String
6
- from infrahub_sdk.utils import extract_fields, extract_fields_first_node
7
6
  from opentelemetry import trace
8
7
  from typing_extensions import Self
9
8
 
9
+ from infrahub.branch.merge_mutation_checker import verify_branch_merge_mutation_allowed
10
10
  from infrahub.core.branch import Branch
11
11
  from infrahub.database import retry_db_transaction
12
12
  from infrahub.graphql.context import apply_external_context
13
+ from infrahub.graphql.field_extractor import extract_graphql_fields
13
14
  from infrahub.graphql.types.context import ContextInput
14
15
  from infrahub.log import get_logger
15
16
  from infrahub.workflows.catalogue import (
@@ -84,7 +85,7 @@ class BranchCreate(Mutation):
84
85
 
85
86
  # Retrieve created branch
86
87
  obj = await Branch.get_by_name(db=graphql_context.db, name=model.name)
87
- fields = await extract_fields(info.field_nodes[0].selection_set)
88
+ fields = extract_graphql_fields(info=info)
88
89
  return cls(object=await obj.to_graphql(fields=fields.get("object", {})), ok=True, task=task)
89
90
 
90
91
 
@@ -202,7 +203,7 @@ class BranchRebase(Mutation):
202
203
  )
203
204
  task = {"id": workflow.id}
204
205
 
205
- fields = await extract_fields_first_node(info=info)
206
+ fields = extract_graphql_fields(info=info)
206
207
  ok = True
207
208
 
208
209
  return cls(object=await obj.to_graphql(fields=fields.get("object", {})), ok=ok, task=task)
@@ -245,7 +246,7 @@ class BranchValidate(Mutation):
245
246
  )
246
247
  task = {"id": workflow.id}
247
248
 
248
- fields = await extract_fields_first_node(info=info)
249
+ fields = extract_graphql_fields(info=info)
249
250
 
250
251
  return cls(object=await obj.to_graphql(fields=fields.get("object", {})), ok=ok, task=task)
251
252
 
@@ -274,6 +275,10 @@ class BranchMerge(Mutation):
274
275
  graphql_context: GraphqlContext = info.context
275
276
  await apply_external_context(graphql_context=graphql_context, context_input=context)
276
277
 
278
+ await verify_branch_merge_mutation_allowed(
279
+ db=graphql_context.db, account_session=graphql_context.active_account_session
280
+ )
281
+
277
282
  if wait_until_completion:
278
283
  await graphql_context.active_service.workflow.execute_workflow(
279
284
  workflow=BRANCH_MERGE_MUTATION,
@@ -291,7 +296,7 @@ class BranchMerge(Mutation):
291
296
  # Pull the latest information about the branch from the database directly
292
297
  obj = await Branch.get_by_name(db=graphql_context.db, name=branch_name)
293
298
 
294
- fields = await extract_fields(info.field_nodes[0].selection_set)
299
+ fields = extract_graphql_fields(info=info)
295
300
  ok = True
296
301
 
297
302
  return cls(object=await obj.to_graphql(fields=fields.get("object", {})), ok=ok, task=task)
@@ -68,6 +68,7 @@ class InfrahubGraphQLQueryMutation(InfrahubMutationMixin, Mutation):
68
68
  data: InputObjectType,
69
69
  branch: Branch,
70
70
  database: InfrahubDatabase | None = None, # noqa: ARG003
71
+ override_data: dict[str, Any] | None = None,
71
72
  ) -> tuple[Node, Self]:
72
73
  graphql_context: GraphqlContext = info.context
73
74
 
@@ -75,7 +76,7 @@ class InfrahubGraphQLQueryMutation(InfrahubMutationMixin, Mutation):
75
76
  await cls.extract_query_info(info=info, data=data, branch=graphql_context.branch, db=graphql_context.db)
76
77
  )
77
78
 
78
- obj, result = await super().mutate_create(info=info, data=data, branch=branch)
79
+ obj, result = await super().mutate_create(info=info, data=data, branch=branch, override_data=override_data)
79
80
 
80
81
  return obj, result
81
82
 
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from graphene import InputObjectType, Mutation
8
8
  from graphene.types.mutation import MutationOptions
9
- from infrahub_sdk.utils import extract_fields
10
9
  from typing_extensions import Self
11
10
 
12
11
  from infrahub import config, lock
@@ -28,6 +27,7 @@ from infrahub.dependencies.registry import get_component_registry
28
27
  from infrahub.events.generator import generate_node_mutation_events
29
28
  from infrahub.exceptions import HFIDViolatedError, InitializationError, NodeNotFoundError
30
29
  from infrahub.graphql.context import apply_external_context
30
+ from infrahub.graphql.field_extractor import extract_graphql_fields
31
31
  from infrahub.lock import InfrahubMultiLock, build_object_lock_name
32
32
  from infrahub.log import get_log_data, get_logger
33
33
 
@@ -147,7 +147,9 @@ class InfrahubMutationMixin:
147
147
  return mutation
148
148
 
149
149
  @classmethod
150
- async def _call_mutate_create_object(cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch) -> Node:
150
+ async def _call_mutate_create_object(
151
+ cls, data: InputObjectType, db: InfrahubDatabase, branch: Branch, override_data: dict[str, Any] | None = None
152
+ ) -> Node:
151
153
  """
152
154
  Wrapper around mutate_create_object to potentially activate locking.
153
155
  """
@@ -157,9 +159,9 @@ class InfrahubMutationMixin:
157
159
  )
158
160
  if lock_names:
159
161
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
160
- return await cls.mutate_create_object(data=data, db=db, branch=branch)
162
+ return await cls.mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
161
163
 
162
- return await cls.mutate_create_object(data=data, db=db, branch=branch)
164
+ return await cls.mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
163
165
 
164
166
  @classmethod
165
167
  async def mutate_create(
@@ -168,10 +170,11 @@ class InfrahubMutationMixin:
168
170
  data: InputObjectType,
169
171
  branch: Branch,
170
172
  database: InfrahubDatabase | None = None,
173
+ override_data: dict[str, Any] | None = None,
171
174
  ) -> tuple[Node, Self]:
172
175
  graphql_context: GraphqlContext = info.context
173
176
  db = database or graphql_context.db
174
- obj = await cls._call_mutate_create_object(data=data, db=db, branch=branch)
177
+ obj = await cls._call_mutate_create_object(data=data, db=db, branch=branch, override_data=override_data)
175
178
  result = await cls.mutate_create_to_graphql(info=info, db=db, obj=obj)
176
179
  return obj, result
177
180
 
@@ -182,12 +185,15 @@ class InfrahubMutationMixin:
182
185
  data: InputObjectType,
183
186
  db: InfrahubDatabase,
184
187
  branch: Branch,
188
+ override_data: dict[str, Any] | None = None,
185
189
  ) -> Node:
186
190
  schema = cls._meta.active_schema
187
191
  if isinstance(schema, GenericSchema):
188
192
  raise ValueError(f"Node of generic schema `{schema.name=}` can not be instantiated.")
193
+ create_data = dict(data)
194
+ create_data.update(override_data or {})
189
195
  return await create_node(
190
- data=dict(data),
196
+ data=create_data,
191
197
  db=db,
192
198
  branch=branch,
193
199
  schema=schema,
@@ -195,7 +201,7 @@ class InfrahubMutationMixin:
195
201
 
196
202
  @classmethod
197
203
  async def mutate_create_to_graphql(cls, info: GraphQLResolveInfo, db: InfrahubDatabase, obj: Node) -> Self:
198
- fields = await extract_fields(info.field_nodes[0].selection_set)
204
+ fields = extract_graphql_fields(info=info)
199
205
  result: dict[str, Any] = {"ok": True}
200
206
  if "object" in fields:
201
207
  result["object"] = await obj.to_graphql(db=db, fields=fields.get("object", {}))
@@ -314,7 +320,7 @@ class InfrahubMutationMixin:
314
320
  info: GraphQLResolveInfo,
315
321
  obj: Node,
316
322
  ) -> Self:
317
- fields_object = await extract_fields(info.field_nodes[0].selection_set)
323
+ fields_object = extract_graphql_fields(info=info)
318
324
  fields_object = fields_object.get("object", {})
319
325
  result: dict[str, Any] = {"ok": True}
320
326
  if fields_object:
@@ -54,10 +54,11 @@ class InfrahubCoreMenuMutation(InfrahubMutationMixin, Mutation):
54
54
  data: InputObjectType,
55
55
  branch: Branch,
56
56
  database: InfrahubDatabase | None = None, # noqa: ARG003
57
+ override_data: dict[str, Any] | None = None,
57
58
  ) -> tuple[Node, Self]:
58
59
  validate_namespace(data=data)
59
60
 
60
- obj, result = await super().mutate_create(info=info, data=data, branch=branch)
61
+ obj, result = await super().mutate_create(info=info, data=data, branch=branch, override_data=override_data)
61
62
 
62
63
  return obj, result
63
64