infrahub-server 1.4.10__py3-none-any.whl → 1.5.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 (103) hide show
  1. infrahub/actions/tasks.py +200 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/query.py +2 -0
  4. infrahub/api/schema.py +3 -0
  5. infrahub/auth.py +5 -5
  6. infrahub/cli/db.py +2 -2
  7. infrahub/config.py +7 -2
  8. infrahub/core/attribute.py +22 -19
  9. infrahub/core/branch/models.py +2 -2
  10. infrahub/core/branch/needs_rebase_status.py +11 -0
  11. infrahub/core/branch/tasks.py +2 -2
  12. infrahub/core/constants/__init__.py +1 -0
  13. infrahub/core/convert_object_type/object_conversion.py +201 -0
  14. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  15. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  16. infrahub/core/diff/query/artifact.py +1 -1
  17. infrahub/core/graph/__init__.py +1 -1
  18. infrahub/core/initialization.py +2 -2
  19. infrahub/core/manager.py +3 -81
  20. infrahub/core/migrations/graph/__init__.py +2 -0
  21. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  22. infrahub/core/node/__init__.py +23 -2
  23. infrahub/core/node/create.py +67 -35
  24. infrahub/core/node/lock_utils.py +98 -0
  25. infrahub/core/property.py +11 -0
  26. infrahub/core/protocols.py +1 -0
  27. infrahub/core/query/attribute.py +27 -15
  28. infrahub/core/query/node.py +47 -184
  29. infrahub/core/query/relationship.py +43 -26
  30. infrahub/core/query/subquery.py +0 -8
  31. infrahub/core/relationship/model.py +59 -19
  32. infrahub/core/schema/attribute_schema.py +0 -2
  33. infrahub/core/schema/definitions/core/repository.py +7 -0
  34. infrahub/core/schema/relationship_schema.py +0 -1
  35. infrahub/core/schema/schema_branch.py +3 -2
  36. infrahub/generators/models.py +31 -12
  37. infrahub/generators/tasks.py +3 -1
  38. infrahub/git/base.py +38 -1
  39. infrahub/graphql/api/dependencies.py +2 -4
  40. infrahub/graphql/api/endpoints.py +2 -2
  41. infrahub/graphql/app.py +2 -4
  42. infrahub/graphql/initialization.py +2 -3
  43. infrahub/graphql/manager.py +212 -137
  44. infrahub/graphql/middleware.py +12 -0
  45. infrahub/graphql/mutations/branch.py +11 -0
  46. infrahub/graphql/mutations/computed_attribute.py +110 -3
  47. infrahub/graphql/mutations/convert_object_type.py +34 -13
  48. infrahub/graphql/mutations/ipam.py +21 -8
  49. infrahub/graphql/mutations/main.py +37 -153
  50. infrahub/graphql/mutations/profile.py +195 -0
  51. infrahub/graphql/mutations/proposed_change.py +2 -1
  52. infrahub/graphql/mutations/repository.py +22 -83
  53. infrahub/graphql/mutations/webhook.py +1 -1
  54. infrahub/graphql/registry.py +173 -0
  55. infrahub/graphql/schema.py +4 -1
  56. infrahub/lock.py +52 -26
  57. infrahub/locks/__init__.py +0 -0
  58. infrahub/locks/tasks.py +37 -0
  59. infrahub/patch/plan_writer.py +2 -2
  60. infrahub/profiles/__init__.py +0 -0
  61. infrahub/profiles/node_applier.py +101 -0
  62. infrahub/profiles/queries/__init__.py +0 -0
  63. infrahub/profiles/queries/get_profile_data.py +99 -0
  64. infrahub/profiles/tasks.py +63 -0
  65. infrahub/repositories/__init__.py +0 -0
  66. infrahub/repositories/create_repository.py +113 -0
  67. infrahub/tasks/registry.py +6 -4
  68. infrahub/webhook/models.py +1 -1
  69. infrahub/workflows/catalogue.py +38 -3
  70. infrahub/workflows/models.py +17 -2
  71. infrahub_sdk/branch.py +5 -8
  72. infrahub_sdk/client.py +364 -84
  73. infrahub_sdk/convert_object_type.py +61 -0
  74. infrahub_sdk/ctl/check.py +2 -3
  75. infrahub_sdk/ctl/cli_commands.py +16 -12
  76. infrahub_sdk/ctl/config.py +8 -2
  77. infrahub_sdk/ctl/generator.py +2 -3
  78. infrahub_sdk/ctl/repository.py +39 -1
  79. infrahub_sdk/ctl/schema.py +12 -1
  80. infrahub_sdk/ctl/utils.py +4 -0
  81. infrahub_sdk/ctl/validate.py +5 -3
  82. infrahub_sdk/diff.py +4 -5
  83. infrahub_sdk/exceptions.py +2 -0
  84. infrahub_sdk/graphql.py +7 -2
  85. infrahub_sdk/node/attribute.py +2 -0
  86. infrahub_sdk/node/node.py +28 -20
  87. infrahub_sdk/playback.py +1 -2
  88. infrahub_sdk/protocols.py +40 -6
  89. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  90. infrahub_sdk/pytest_plugin/utils.py +40 -0
  91. infrahub_sdk/repository.py +1 -2
  92. infrahub_sdk/schema/main.py +1 -0
  93. infrahub_sdk/spec/object.py +43 -4
  94. infrahub_sdk/spec/range_expansion.py +118 -0
  95. infrahub_sdk/timestamp.py +18 -6
  96. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/METADATA +6 -9
  97. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/RECORD +102 -84
  98. infrahub_testcontainers/models.py +2 -2
  99. infrahub_testcontainers/performance_test.py +4 -4
  100. infrahub/core/convert_object_type/conversion.py +0 -134
  101. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/LICENSE.txt +0 -0
  102. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/WHEEL +0 -0
  103. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from graphene import Boolean, InputObjectType, Mutation, String
6
+ from graphql import GraphQLResolveInfo
7
+ from opentelemetry import trace
8
+ from typing_extensions import Self
9
+
10
+ from infrahub.core.manager import NodeManager
11
+ from infrahub.core.schema import ProfileSchema
12
+ from infrahub.graphql.types.context import ContextInput
13
+ from infrahub.log import get_logger
14
+ from infrahub.profiles.node_applier import NodeProfilesApplier
15
+ from infrahub.workflows.catalogue import PROFILE_REFRESH_MULTIPLE
16
+
17
+ from .main import InfrahubMutationMixin, InfrahubMutationOptions
18
+
19
+ if TYPE_CHECKING:
20
+ from graphql import GraphQLResolveInfo
21
+
22
+ from infrahub.core.branch import Branch
23
+ from infrahub.core.node import Node
24
+ from infrahub.database import InfrahubDatabase
25
+ from infrahub.graphql.initialization import GraphqlContext
26
+ from infrahub.services.adapters.workflow import InfrahubWorkflow
27
+
28
+ log = get_logger()
29
+
30
+
31
+ class InfrahubProfileMutation(InfrahubMutationMixin, Mutation):
32
+ @classmethod
33
+ def __init_subclass_with_meta__(
34
+ cls,
35
+ schema: ProfileSchema,
36
+ _meta: InfrahubMutationOptions | None = None,
37
+ **options: dict[str, Any],
38
+ ) -> None:
39
+ # Make sure schema is a valid NodeSchema Node Class
40
+ if not isinstance(schema, ProfileSchema):
41
+ raise ValueError(f"You need to pass a valid ProfileSchema in '{cls.__name__}.Meta', received '{schema}'")
42
+
43
+ if not _meta:
44
+ _meta = InfrahubMutationOptions(cls)
45
+ _meta.schema = schema
46
+
47
+ super().__init_subclass_with_meta__(_meta=_meta, **options)
48
+
49
+ @classmethod
50
+ async def _send_profile_refresh_workflows(
51
+ cls,
52
+ db: InfrahubDatabase,
53
+ workflow_service: InfrahubWorkflow,
54
+ branch_name: str,
55
+ obj: Node,
56
+ node_ids: list[str] | None = None,
57
+ ) -> None:
58
+ if not node_ids:
59
+ related_nodes = await obj.related_nodes.get_relationships(db=db) # type: ignore[attr-defined]
60
+ node_ids = [rel.peer_id for rel in related_nodes]
61
+ if node_ids:
62
+ await workflow_service.submit_workflow(
63
+ workflow=PROFILE_REFRESH_MULTIPLE,
64
+ parameters={
65
+ "branch_name": branch_name,
66
+ "node_ids": node_ids,
67
+ },
68
+ )
69
+
70
+ @classmethod
71
+ def _get_profile_attr_values_map(cls, obj: Node) -> dict[str, Any]:
72
+ attr_values_map = {}
73
+ for attr_schema in obj.get_schema().attributes:
74
+ # profile name update can be ignored
75
+ if attr_schema.name == "profile_name":
76
+ continue
77
+ attr_values_map[attr_schema.name] = getattr(obj, attr_schema.name).value
78
+ return attr_values_map
79
+
80
+ @classmethod
81
+ async def _get_profile_related_node_ids(cls, db: InfrahubDatabase, obj: Node) -> set[str]:
82
+ related_nodes = await obj.related_nodes.get_relationships(db=db) # type: ignore[attr-defined]
83
+ if related_nodes:
84
+ related_node_ids = {rel.peer_id for rel in related_nodes}
85
+ else:
86
+ related_node_ids = set()
87
+ return related_node_ids
88
+
89
+ @classmethod
90
+ async def mutate_create(
91
+ cls,
92
+ info: GraphQLResolveInfo,
93
+ data: InputObjectType,
94
+ branch: Branch,
95
+ database: InfrahubDatabase | None = None,
96
+ override_data: dict[str, Any] | None = None,
97
+ ) -> tuple[Node, Self]:
98
+ graphql_context: GraphqlContext = info.context
99
+ db = database or graphql_context.db
100
+ workflow_service = graphql_context.active_service.workflow
101
+
102
+ obj, mutation = await super().mutate_create(
103
+ info=info, data=data, branch=branch, database=database, override_data=override_data
104
+ )
105
+ await cls._send_profile_refresh_workflows(
106
+ db=db, workflow_service=workflow_service, branch_name=branch.name, obj=obj
107
+ )
108
+
109
+ return obj, mutation
110
+
111
+ @classmethod
112
+ async def _call_mutate_update(
113
+ cls,
114
+ info: GraphQLResolveInfo,
115
+ data: InputObjectType,
116
+ branch: Branch,
117
+ db: InfrahubDatabase,
118
+ obj: Node,
119
+ skip_uniqueness_check: bool = False,
120
+ ) -> tuple[Node, Self]:
121
+ workflow_service = info.context.active_service.workflow
122
+ original_attr_values = cls._get_profile_attr_values_map(obj=obj)
123
+ original_related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
124
+
125
+ obj, mutation = await super()._call_mutate_update(
126
+ info=info, data=data, branch=branch, db=db, obj=obj, skip_uniqueness_check=skip_uniqueness_check
127
+ )
128
+
129
+ updated_attr_values = cls._get_profile_attr_values_map(obj=obj)
130
+ updated_related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
131
+
132
+ if original_attr_values != updated_attr_values:
133
+ await cls._send_profile_refresh_workflows(
134
+ db=db, workflow_service=workflow_service, branch_name=branch.name, obj=obj
135
+ )
136
+ elif updated_related_node_ids != original_related_node_ids:
137
+ removed_node_ids = original_related_node_ids - updated_related_node_ids
138
+ added_node_ids = updated_related_node_ids - original_related_node_ids
139
+ await cls._send_profile_refresh_workflows(
140
+ db=db,
141
+ workflow_service=workflow_service,
142
+ branch_name=branch.name,
143
+ obj=obj,
144
+ node_ids=list(removed_node_ids) + list(added_node_ids),
145
+ )
146
+
147
+ return obj, mutation
148
+
149
+ @classmethod
150
+ async def _delete_obj(cls, graphql_context: GraphqlContext, branch: Branch, obj: Node) -> list[Node]:
151
+ db = graphql_context.db
152
+ workflow_service = graphql_context.active_service.workflow
153
+ related_node_ids = await cls._get_profile_related_node_ids(db=db, obj=obj)
154
+ deleted = await super()._delete_obj(graphql_context=graphql_context, branch=branch, obj=obj)
155
+ await cls._send_profile_refresh_workflows(
156
+ db=db, workflow_service=workflow_service, branch_name=branch.name, obj=obj, node_ids=list(related_node_ids)
157
+ )
158
+ return deleted
159
+
160
+
161
+ class ProfilesRefreshInput(InputObjectType):
162
+ id = String(required=False)
163
+
164
+
165
+ class InfrahubProfilesRefresh(Mutation):
166
+ class Arguments:
167
+ data = ProfilesRefreshInput(required=True)
168
+ context = ContextInput(required=False)
169
+
170
+ ok = Boolean()
171
+
172
+ @classmethod
173
+ @trace.get_tracer(__name__).start_as_current_span("profiles_refresh")
174
+ async def mutate(
175
+ cls,
176
+ root: dict, # noqa: ARG003
177
+ info: GraphQLResolveInfo,
178
+ data: ProfilesRefreshInput,
179
+ context: ContextInput | None = None, # noqa: ARG003
180
+ ) -> Self:
181
+ graphql_context: GraphqlContext = info.context
182
+ db = graphql_context.db
183
+ branch = graphql_context.branch
184
+ obj = await NodeManager.get_one(
185
+ db=db,
186
+ branch=branch,
187
+ id=str(data.id),
188
+ include_source=True,
189
+ )
190
+ node_profiles_applier = NodeProfilesApplier(db=db, branch=branch)
191
+ updated_fields = await node_profiles_applier.apply_profiles(node=obj)
192
+ if updated_fields:
193
+ await obj.save(db=db, fields=updated_fields)
194
+
195
+ return cls(ok=True)
@@ -29,13 +29,14 @@ from infrahub.exceptions import BranchNotFoundError, PermissionDeniedError, Vali
29
29
  from infrahub.graphql.mutations.main import InfrahubMutationMixin
30
30
  from infrahub.graphql.types.enums import CheckType as GraphQLCheckType
31
31
  from infrahub.graphql.types.task import TaskInfo
32
- from infrahub.lock import InfrahubLock, build_object_lock_name
32
+ from infrahub.lock import InfrahubLock
33
33
  from infrahub.proposed_change.approval_revoker import do_revoke_approvals_on_updated_pcs
34
34
  from infrahub.proposed_change.constants import ProposedChangeApprovalDecision, ProposedChangeState
35
35
  from infrahub.proposed_change.models import RequestProposedChangePipeline
36
36
  from infrahub.workers.dependencies import get_event_service
37
37
  from infrahub.workflows.catalogue import PROPOSED_CHANGE_MERGE, REQUEST_PROPOSED_CHANGE_PIPELINE
38
38
 
39
+ from ...core.node.lock_utils import build_object_lock_name
39
40
  from .main import InfrahubMutationOptions
40
41
 
41
42
  if TYPE_CHECKING:
@@ -7,14 +7,11 @@ import httpx
7
7
  from graphene import Boolean, Field, InputObjectType, Mutation, String
8
8
 
9
9
  from infrahub import config
10
- from infrahub.core.constants import InfrahubKind, RepositoryInternalStatus
10
+ from infrahub.core.constants import InfrahubKind
11
11
  from infrahub.core.manager import NodeManager
12
- from infrahub.core.protocols import CoreGenericRepository, CoreReadOnlyRepository, CoreRepository
12
+ from infrahub.core.protocols import CoreReadOnlyRepository, CoreRepository
13
13
  from infrahub.core.schema import NodeSchema
14
- from infrahub.exceptions import ValidationError
15
14
  from infrahub.git.models import (
16
- GitRepositoryAdd,
17
- GitRepositoryAddReadOnly,
18
15
  GitRepositoryImportObjects,
19
16
  GitRepositoryPullReadOnly,
20
17
  )
@@ -22,15 +19,15 @@ from infrahub.graphql.types.common import IdentifierInput
22
19
  from infrahub.log import get_logger
23
20
  from infrahub.message_bus import messages
24
21
  from infrahub.message_bus.messages.git_repository_connectivity import GitRepositoryConnectivityResponse
22
+ from infrahub.repositories.create_repository import RepositoryFinalizer
25
23
  from infrahub.workflows.catalogue import (
26
24
  GIT_REPOSITORIES_IMPORT_OBJECTS,
27
25
  GIT_REPOSITORIES_PULL_READ_ONLY,
28
- GIT_REPOSITORY_ADD,
29
- GIT_REPOSITORY_ADD_READ_ONLY,
30
26
  )
31
27
 
28
+ from ...core.node.create import create_node
32
29
  from ..types.task import TaskInfo
33
- from .main import InfrahubMutationMixin, InfrahubMutationOptions
30
+ from .main import InfrahubMutationMixin, InfrahubMutationOptions, build_graphql_response
34
31
 
35
32
  if TYPE_CHECKING:
36
33
  from graphql import GraphQLResolveInfo
@@ -63,86 +60,28 @@ class InfrahubRepositoryMutation(InfrahubMutationMixin, Mutation):
63
60
  info: GraphQLResolveInfo,
64
61
  data: InputObjectType,
65
62
  branch: Branch,
66
- database: InfrahubDatabase | None = None, # noqa: ARG003
63
+ database: InfrahubDatabase | None = None,
67
64
  override_data: dict[str, Any] | None = None,
68
65
  ) -> tuple[Node, Self]:
69
66
  graphql_context: GraphqlContext = info.context
70
-
71
67
  cleanup_payload(data)
72
- # Create the object in the database
73
- obj, result = await super().mutate_create(info, data, branch, override_data=override_data)
74
- obj = cast(CoreGenericRepository, obj)
75
-
76
- # First check the connectivity to the remote repository
77
- # If the connectivity is not good, we remove the repository to allow the user to add a new one
78
- if graphql_context.service:
79
- message = messages.GitRepositoryConnectivity(
80
- repository_name=obj.name.value,
81
- repository_location=obj.location.value,
82
- )
83
- response = await graphql_context.service.message_bus.rpc(
84
- message=message, response_class=GitRepositoryConnectivityResponse
85
- )
86
-
87
- if response.data.success is False:
88
- await obj.delete(db=graphql_context.db)
89
- raise ValidationError(response.data.message)
90
-
91
- # If we are in the default branch, we set the sync status to Active
92
- # If we are in another branch, we set the sync status to Staging
93
- if branch.is_default:
94
- obj.internal_status.value = RepositoryInternalStatus.ACTIVE.value
95
- else:
96
- obj.internal_status.value = RepositoryInternalStatus.STAGING.value
97
- await obj.save(db=graphql_context.db)
98
-
99
- # Create the new repository in the filesystem.
100
- log.info("create_repository", name=obj.name.value)
101
- authenticated_user = None
102
- if graphql_context.account_session and graphql_context.account_session.authenticated:
103
- authenticated_user = graphql_context.account_session.account_id
104
- if obj.get_kind() == InfrahubKind.READONLYREPOSITORY:
105
- obj = cast(CoreReadOnlyRepository, obj)
106
- model = GitRepositoryAddReadOnly(
107
- repository_id=obj.id,
108
- repository_name=obj.name.value,
109
- location=obj.location.value,
110
- ref=obj.ref.value,
111
- infrahub_branch_name=branch.name,
112
- infrahub_branch_id=str(branch.get_uuid()),
113
- internal_status=obj.internal_status.value,
114
- created_by=authenticated_user,
115
- )
116
- if graphql_context.service:
117
- await graphql_context.service.workflow.submit_workflow(
118
- workflow=GIT_REPOSITORY_ADD_READ_ONLY,
119
- context=graphql_context.get_context(),
120
- parameters={"model": model},
121
- )
122
-
123
- else:
124
- obj = cast(CoreRepository, obj)
125
- git_repo_add_model = GitRepositoryAdd(
126
- repository_id=obj.id,
127
- repository_name=obj.name.value,
128
- location=obj.location.value,
129
- default_branch_name=obj.default_branch.value,
130
- infrahub_branch_name=branch.name,
131
- infrahub_branch_id=str(branch.get_uuid()),
132
- internal_status=obj.internal_status.value,
133
- created_by=authenticated_user,
134
- )
135
-
136
- if graphql_context.service:
137
- await graphql_context.service.workflow.submit_workflow(
138
- workflow=GIT_REPOSITORY_ADD,
139
- context=graphql_context.get_context(),
140
- parameters={"model": git_repo_add_model},
141
- )
142
-
143
- # TODO Validate that the creation of the repository went as expected
68
+ db = database or graphql_context.db
69
+ create_data = dict(data)
70
+ create_data.update(override_data or {})
71
+ obj = await create_node(data=create_data, db=db, branch=branch, schema=cls._meta.active_schema)
72
+
73
+ await RepositoryFinalizer(
74
+ account_session=graphql_context.active_account_session,
75
+ services=graphql_context.active_service,
76
+ context=graphql_context.get_context(),
77
+ ).post_create(
78
+ obj=obj, # type: ignore
79
+ branch=branch,
80
+ db=db,
81
+ )
144
82
 
145
- return obj, result
83
+ graphql_response = await build_graphql_response(info=info, db=db, obj=obj)
84
+ return obj, cls(**graphql_response)
146
85
 
147
86
  @classmethod
148
87
  async def mutate_update(
@@ -127,7 +127,7 @@ class InfrahubWebhookMutation(InfrahubMutationMixin, Mutation):
127
127
 
128
128
 
129
129
  def _validate_input(graphql_context: GraphqlContext, branch: Branch, input_data: WebhookCreate) -> None:
130
- if input_data.node_kind:
130
+ if input_data.node_kind and input_data.node_kind.value:
131
131
  # Validate that the requested node_kind exists, will raise an error if not
132
132
  graphql_context.db.schema.get(name=input_data.node_kind.value, branch=branch, duplicate=False)
133
133
 
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ from infrahub.core.timestamp import Timestamp
7
+ from infrahub.exceptions import InitializationError
8
+
9
+ if TYPE_CHECKING:
10
+ import graphene
11
+
12
+ from infrahub.core.branch import Branch
13
+ from infrahub.core.schema.schema_branch import SchemaBranch
14
+ from infrahub.graphql.manager import GraphQLSchemaManager, InterfaceReference
15
+
16
+ from .mutations.main import InfrahubMutation
17
+ from .types import InfrahubObject
18
+
19
+
20
+ @dataclass
21
+ class BranchDetails:
22
+ schema_changed_at: Timestamp
23
+ schema_hash: str
24
+ gql_manager: GraphQLSchemaManager
25
+
26
+
27
+ @dataclass
28
+ class GraphQLSchemaRegistry:
29
+ _branch_details_by_hash: dict[str, BranchDetails] = field(default_factory=dict)
30
+ _branch_name_by_hash: dict[str, set[str]] = field(default_factory=dict)
31
+ _branch_hash_activation_by_branch_name: dict[str, dict[str, str]] = field(default_factory=dict)
32
+ _registered_interface_types: dict[str, type[graphene.Interface]] = field(default_factory=dict)
33
+ _reference_hash_schema_map: dict[str, set[str]] = field(default_factory=dict)
34
+ _registered_edge_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
35
+ _registered_paginated_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
36
+ _registered_input_types: dict[str, type[graphene.InputObjectType]] = field(default_factory=dict)
37
+ _registered_object_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
38
+ _registered_mutation_types: dict[str, type[InfrahubMutation]] = field(default_factory=dict)
39
+
40
+ _manager_class: type[GraphQLSchemaManager] | None = None
41
+
42
+ def _add_branch_hash(self, branch_name: str, schema_hash: str) -> None:
43
+ if schema_hash not in self._branch_name_by_hash:
44
+ self._branch_name_by_hash[schema_hash] = set()
45
+
46
+ self._branch_name_by_hash[schema_hash].add(branch_name)
47
+
48
+ def _register_manager(self, manager: type[GraphQLSchemaManager]) -> None:
49
+ self._manager_class = manager
50
+
51
+ @property
52
+ def manager(self) -> type[GraphQLSchemaManager]:
53
+ if self._manager_class:
54
+ return self._manager_class
55
+ raise InitializationError
56
+
57
+ def clear_cache(self) -> None:
58
+ """Clear internal cache stored within this registry."""
59
+ self._branch_details_by_hash = {}
60
+ self._branch_name_by_hash = {}
61
+ self._branch_hash_activation_by_branch_name = {}
62
+ self._registered_interface_types = {}
63
+ self._reference_hash_schema_map = {}
64
+ self._registered_edge_types = {}
65
+ self._registered_paginated_types = {}
66
+ self._registered_input_types = {}
67
+ self._registered_object_types = {}
68
+ self._registered_mutation_types = {}
69
+
70
+ def _add_schema_to_reference_hash(self, reference_hash: str, schema_hash: str) -> None:
71
+ """Add the schema hash to a map containing the referenced object.
72
+
73
+ The goal of this is to be able to see all schemas that use a given reference type,
74
+ once no schemas use a specific type it's safe to remove the type from the registry.
75
+ """
76
+ if reference_hash not in self._reference_hash_schema_map:
77
+ self._reference_hash_schema_map[reference_hash] = set()
78
+ self._reference_hash_schema_map[reference_hash].add(schema_hash)
79
+
80
+ def get_edge_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
81
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
82
+ return self._registered_edge_types.get(reference_hash)
83
+
84
+ def set_edge_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
85
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
86
+ self._registered_edge_types[reference_hash] = reference
87
+
88
+ def get_input_type(self, reference_hash: str, schema_hash: str) -> type[graphene.InputObjectType] | None:
89
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
90
+ return self._registered_input_types.get(reference_hash)
91
+
92
+ def set_input_type(self, reference: type[graphene.InputObjectType], reference_hash: str, schema_hash: str) -> None:
93
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
94
+ self._registered_input_types[reference_hash] = reference
95
+
96
+ def get_interface_type(self, reference_hash: str, schema_hash: str) -> type[graphene.Interface] | None:
97
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
98
+ return self._registered_interface_types.get(reference_hash)
99
+
100
+ def set_interface_type(self, reference: InterfaceReference, schema_hash: str) -> None:
101
+ self._add_schema_to_reference_hash(reference_hash=reference.reference_hash, schema_hash=schema_hash)
102
+ self._registered_interface_types[reference.reference_hash] = reference.reference
103
+
104
+ def get_mutation_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubMutation] | None:
105
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
106
+ return self._registered_mutation_types.get(reference_hash)
107
+
108
+ def set_mutation_type(self, reference: type[InfrahubMutation], reference_hash: str, schema_hash: str) -> None:
109
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
110
+ self._registered_mutation_types[reference_hash] = reference
111
+
112
+ def get_object_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
113
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
114
+ return self._registered_object_types.get(reference_hash)
115
+
116
+ def set_object_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
117
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
118
+ self._registered_object_types[reference_hash] = reference
119
+
120
+ def get_paginated_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
121
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
122
+ return self._registered_paginated_types.get(reference_hash)
123
+
124
+ def set_paginated_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
125
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
126
+ self._registered_paginated_types[reference_hash] = reference
127
+
128
+ def purge_inactive(self, active_branches: list[str]) -> set[str]:
129
+ """Return inactive branches that were purged"""
130
+ inactive_branches: set[str] = set()
131
+ for schema_hash in list(self._branch_name_by_hash.keys()):
132
+ branches = list(self._branch_name_by_hash[schema_hash])
133
+ for branch in branches:
134
+ if branch not in active_branches and branch in self._branch_name_by_hash[schema_hash]:
135
+ inactive_branches.add(branch)
136
+ self._branch_name_by_hash[schema_hash].discard(branch)
137
+
138
+ for schema_hash in list(self._branch_name_by_hash.keys()):
139
+ if not self._branch_name_by_hash[schema_hash]:
140
+ # If no remaining branch is using the schema remove it completely
141
+ del self._branch_name_by_hash[schema_hash]
142
+ del self._branch_details_by_hash[schema_hash]
143
+
144
+ return inactive_branches
145
+
146
+ def cache_branch(self, branch: Branch, schema_branch: SchemaBranch, schema_hash: str) -> BranchDetails:
147
+ branch_details = BranchDetails(
148
+ schema_changed_at=Timestamp(branch.schema_changed_at) if branch.schema_changed_at else Timestamp(),
149
+ schema_hash=schema_hash,
150
+ gql_manager=self.manager(schema=schema_branch),
151
+ )
152
+
153
+ self._branch_details_by_hash[schema_hash] = branch_details
154
+
155
+ return branch_details
156
+
157
+ def get_manager_for_branch(self, branch: Branch, schema_branch: SchemaBranch) -> GraphQLSchemaManager:
158
+ if branch.schema_hash:
159
+ schema_hash = branch.schema_hash.main
160
+ else:
161
+ schema_hash = schema_branch.get_hash()
162
+
163
+ if schema_hash in self._branch_details_by_hash:
164
+ branch_details = self._branch_details_by_hash[schema_hash]
165
+ else:
166
+ branch_details = self.cache_branch(branch=branch, schema_branch=schema_branch, schema_hash=schema_hash)
167
+
168
+ self._add_branch_hash(branch_name=branch.name, schema_hash=schema_hash)
169
+
170
+ return branch_details.gql_manager
171
+
172
+
173
+ registry = GraphQLSchemaRegistry()
@@ -15,11 +15,12 @@ from .mutations.branch import (
15
15
  BranchUpdate,
16
16
  BranchValidate,
17
17
  )
18
- from .mutations.computed_attribute import UpdateComputedAttribute
18
+ from .mutations.computed_attribute import RecomputeComputedAttribute, UpdateComputedAttribute
19
19
  from .mutations.convert_object_type import ConvertObjectType
20
20
  from .mutations.diff import DiffUpdateMutation
21
21
  from .mutations.diff_conflict import ResolveDiffConflict
22
22
  from .mutations.generator import GeneratorDefinitionRequestRun
23
+ from .mutations.profile import InfrahubProfilesRefresh
23
24
  from .mutations.proposed_change import (
24
25
  ProposedChangeCheckForApprovalRevoke,
25
26
  ProposedChangeMerge,
@@ -113,6 +114,7 @@ class InfrahubBaseMutation(ObjectType):
113
114
  InfrahubRepositoryProcess = ProcessRepository.Field()
114
115
  InfrahubRepositoryConnectivity = ValidateRepositoryConnectivity.Field()
115
116
  InfrahubUpdateComputedAttribute = UpdateComputedAttribute.Field()
117
+ InfrahubRecomputeComputedAttribute = RecomputeComputedAttribute.Field()
116
118
 
117
119
  RelationshipAdd = RelationshipAdd.Field()
118
120
  RelationshipRemove = RelationshipRemove.Field()
@@ -124,3 +126,4 @@ class InfrahubBaseMutation(ObjectType):
124
126
 
125
127
  ConvertObjectType = ConvertObjectType.Field()
126
128
  CoreProposedChangeCheckForApprovalRevoke = ProposedChangeCheckForApprovalRevoke.Field()
129
+ InfrahubProfilesRefresh = InfrahubProfilesRefresh.Field()