infrahub-server 1.1.6__py3-none-any.whl → 1.2.0rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/api/artifact.py +16 -4
- infrahub/api/dependencies.py +8 -0
- infrahub/api/oauth2.py +0 -1
- infrahub/api/oidc.py +0 -1
- infrahub/api/query.py +18 -7
- infrahub/api/schema.py +32 -6
- infrahub/api/transformation.py +12 -5
- infrahub/{message_bus/messages/check_artifact_create.py → artifacts/models.py} +2 -4
- infrahub/{message_bus/operations/check/artifact.py → artifacts/tasks.py} +26 -25
- infrahub/cli/__init__.py +0 -2
- infrahub/cli/db.py +6 -7
- infrahub/cli/events.py +8 -3
- infrahub/cli/git_agent.py +9 -7
- infrahub/cli/tasks.py +4 -6
- infrahub/computed_attribute/tasks.py +63 -17
- infrahub/computed_attribute/triggers.py +90 -0
- infrahub/config.py +1 -1
- infrahub/context.py +39 -0
- infrahub/core/account.py +5 -8
- infrahub/core/attribute.py +53 -21
- infrahub/core/branch/models.py +4 -4
- infrahub/core/branch/tasks.py +89 -130
- infrahub/core/changelog/__init__.py +0 -0
- infrahub/core/changelog/diff.py +232 -0
- infrahub/core/changelog/models.py +488 -0
- infrahub/core/constants/__init__.py +19 -2
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/diff/combiner.py +12 -8
- infrahub/core/diff/coordinator.py +49 -70
- infrahub/core/diff/data_check_synchronizer.py +86 -7
- infrahub/core/diff/enricher/aggregated.py +3 -3
- infrahub/core/diff/enricher/cardinality_one.py +2 -7
- infrahub/core/diff/enricher/hierarchy.py +5 -3
- infrahub/core/diff/enricher/labels.py +14 -4
- infrahub/core/diff/enricher/path_identifier.py +3 -9
- infrahub/core/diff/enricher/summary_counts.py +3 -1
- infrahub/core/diff/merger/merger.py +8 -4
- infrahub/core/diff/model/path.py +47 -29
- infrahub/core/diff/query/all_conflicts.py +6 -3
- infrahub/core/diff/query/artifact.py +1 -1
- infrahub/core/diff/query/delete_query.py +1 -1
- infrahub/core/diff/query/diff_get.py +3 -2
- infrahub/core/diff/query/diff_summary.py +1 -1
- infrahub/core/diff/query/field_specifiers.py +3 -1
- infrahub/core/diff/query/field_summary.py +3 -2
- infrahub/core/diff/query/filters.py +12 -1
- infrahub/core/diff/query/get_conflict_query.py +1 -1
- infrahub/core/diff/query/has_conflicts_query.py +6 -3
- infrahub/core/diff/query/merge.py +3 -3
- infrahub/core/diff/query/{drop_tracking_id.py → merge_tracking_id.py} +4 -4
- infrahub/core/diff/query/roots_metadata.py +9 -2
- infrahub/core/diff/query/save.py +151 -66
- infrahub/core/diff/query/summary_counts_enricher.py +220 -0
- infrahub/core/diff/query/time_range_query.py +3 -2
- infrahub/core/diff/query/update_conflict_query.py +1 -1
- infrahub/core/diff/query_parser.py +49 -24
- infrahub/core/diff/repository/deserializer.py +24 -25
- infrahub/core/diff/repository/repository.py +76 -20
- infrahub/core/diff/tasks.py +9 -8
- infrahub/core/enums.py +1 -1
- infrahub/core/integrity/object_conflict/conflict_recorder.py +1 -1
- infrahub/core/ipam/reconciler.py +1 -1
- infrahub/core/ipam/tasks.py +2 -3
- infrahub/core/manager.py +18 -13
- infrahub/core/merge.py +5 -2
- infrahub/core/migrations/graph/m001_add_version_to_graph.py +1 -1
- infrahub/core/migrations/graph/m002_attribute_is_default.py +2 -2
- infrahub/core/migrations/graph/m003_relationship_parent_optional.py +2 -2
- infrahub/core/migrations/graph/m004_add_attr_documentation.py +1 -1
- infrahub/core/migrations/graph/m005_add_rel_read_only.py +1 -1
- infrahub/core/migrations/graph/m006_add_rel_on_delete.py +1 -1
- infrahub/core/migrations/graph/m007_add_rel_allow_override.py +1 -1
- infrahub/core/migrations/graph/m008_add_human_friendly_id.py +1 -1
- infrahub/core/migrations/graph/m009_add_generate_profile_attr.py +1 -1
- infrahub/core/migrations/graph/m010_add_generate_profile_attr_generic.py +1 -1
- infrahub/core/migrations/graph/m011_remove_profile_relationship_schema.py +2 -2
- infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -23
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -11
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +2 -2
- infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
- infrahub/core/migrations/graph/m017_add_core_profile.py +1 -1
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -2
- infrahub/core/migrations/query/attribute_add.py +1 -1
- infrahub/core/migrations/query/attribute_rename.py +1 -1
- infrahub/core/migrations/query/delete_element_in_schema.py +1 -1
- infrahub/core/migrations/query/node_duplicate.py +1 -1
- infrahub/core/migrations/query/relationship_duplicate.py +1 -1
- infrahub/core/migrations/query/schema_attribute_update.py +1 -1
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
- infrahub/core/migrations/schema/node_remove.py +1 -1
- infrahub/core/migrations/schema/tasks.py +5 -5
- infrahub/core/migrations/shared.py +4 -4
- infrahub/core/models.py +7 -8
- infrahub/core/node/__init__.py +161 -40
- infrahub/core/node/base.py +1 -1
- infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
- infrahub/core/node/delete_validator.py +4 -4
- infrahub/core/node/ipam.py +13 -8
- infrahub/core/node/permissions.py +4 -0
- infrahub/core/node/resource_manager/ip_prefix_pool.py +8 -5
- infrahub/core/node/standard.py +3 -5
- infrahub/core/property.py +1 -1
- infrahub/core/protocols.py +4 -0
- infrahub/core/protocols_base.py +4 -2
- infrahub/core/query/__init__.py +2 -5
- infrahub/core/query/attribute.py +9 -9
- infrahub/core/query/branch.py +5 -5
- infrahub/core/query/delete.py +1 -1
- infrahub/core/query/diff.py +45 -7
- infrahub/core/query/ipam.py +4 -4
- infrahub/core/query/node.py +19 -14
- infrahub/core/query/relationship.py +10 -11
- infrahub/core/query/resource_manager.py +13 -11
- infrahub/core/query/standard_node.py +6 -6
- infrahub/core/query/task.py +3 -3
- infrahub/core/query/task_log.py +1 -1
- infrahub/core/query/utils.py +5 -5
- infrahub/core/registry.py +0 -2
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/constraints/peer_kind.py +1 -1
- infrahub/core/relationship/model.py +66 -26
- infrahub/core/schema/__init__.py +6 -4
- infrahub/core/schema/basenode_schema.py +1 -3
- infrahub/core/schema/definitions/core.py +14 -2
- infrahub/core/schema/definitions/internal.py +16 -0
- infrahub/core/schema/generated/genericnode_schema.py +5 -0
- infrahub/core/schema/generated/node_schema.py +5 -0
- infrahub/core/schema/generic_schema.py +5 -1
- infrahub/core/schema/manager.py +45 -42
- infrahub/core/schema/node_schema.py +4 -0
- infrahub/core/schema/profile_schema.py +4 -0
- infrahub/core/schema/relationship_schema.py +2 -2
- infrahub/core/schema/schema_branch.py +248 -14
- infrahub/core/schema/template_schema.py +36 -0
- infrahub/core/task/user_task.py +7 -5
- infrahub/core/timestamp.py +1 -1
- infrahub/core/utils.py +3 -2
- infrahub/core/validators/attribute/choices.py +1 -1
- infrahub/core/validators/attribute/enum.py +1 -1
- infrahub/core/validators/attribute/kind.py +1 -1
- infrahub/core/validators/attribute/length.py +1 -1
- infrahub/core/validators/attribute/optional.py +1 -1
- infrahub/core/validators/attribute/regex.py +1 -1
- infrahub/core/validators/attribute/unique.py +1 -1
- infrahub/core/validators/checks_runner.py +37 -0
- infrahub/core/validators/node/generate_profile.py +1 -1
- infrahub/core/validators/node/hierarchy.py +1 -1
- infrahub/core/validators/query.py +1 -1
- infrahub/core/validators/relationship/count.py +1 -1
- infrahub/core/validators/relationship/optional.py +1 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/core/validators/tasks.py +8 -6
- infrahub/core/validators/uniqueness/query.py +20 -17
- infrahub/database/__init__.py +15 -2
- infrahub/database/memgraph.py +1 -1
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +0 -2
- infrahub/dependencies/builder/diff/combiner.py +1 -1
- infrahub/dependencies/builder/diff/conflicts_enricher.py +1 -1
- infrahub/dependencies/builder/diff/coordinator.py +0 -2
- infrahub/dependencies/builder/diff/deserializer.py +1 -1
- infrahub/dependencies/builder/diff/enricher/summary_counts.py +1 -1
- infrahub/events/branch_action.py +47 -21
- infrahub/events/group_action.py +73 -0
- infrahub/events/models.py +159 -51
- infrahub/events/node_action.py +74 -8
- infrahub/events/repository_action.py +8 -8
- infrahub/events/schema_action.py +21 -8
- infrahub/generators/tasks.py +12 -13
- infrahub/git/base.py +3 -5
- infrahub/git/constants.py +0 -1
- infrahub/git/integrator.py +36 -35
- infrahub/git/repository.py +7 -8
- infrahub/git/tasks.py +43 -107
- infrahub/git_credential/helper.py +2 -3
- infrahub/graphql/analyzer.py +572 -11
- infrahub/graphql/app.py +34 -26
- infrahub/graphql/auth/query_permission_checker/anonymous_checker.py +5 -5
- infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +4 -4
- infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +4 -4
- infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +28 -35
- infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -5
- infrahub/graphql/enums.py +1 -1
- infrahub/graphql/initialization.py +5 -1
- infrahub/graphql/loaders/node.py +2 -2
- infrahub/graphql/manager.py +59 -54
- infrahub/graphql/mutations/account.py +20 -13
- infrahub/graphql/mutations/artifact_definition.py +16 -12
- infrahub/graphql/mutations/branch.py +61 -40
- infrahub/graphql/mutations/computed_attribute.py +19 -13
- infrahub/graphql/mutations/diff.py +37 -9
- infrahub/graphql/mutations/diff_conflict.py +9 -8
- infrahub/graphql/mutations/graphql_query.py +19 -11
- infrahub/graphql/mutations/ipam.py +21 -19
- infrahub/graphql/mutations/main.py +197 -44
- infrahub/graphql/mutations/menu.py +8 -8
- infrahub/graphql/mutations/proposed_change.py +36 -28
- infrahub/graphql/mutations/relationship.py +302 -105
- infrahub/graphql/mutations/repository.py +41 -35
- infrahub/graphql/mutations/resource_manager.py +26 -26
- infrahub/graphql/mutations/schema.py +51 -33
- infrahub/graphql/mutations/tasks.py +16 -10
- infrahub/graphql/parser.py +1 -1
- infrahub/graphql/permissions.py +6 -4
- infrahub/graphql/queries/account.py +22 -18
- infrahub/graphql/queries/branch.py +6 -4
- infrahub/graphql/queries/diff/tree.py +48 -42
- infrahub/graphql/queries/event.py +112 -0
- infrahub/graphql/queries/internal.py +3 -3
- infrahub/graphql/queries/ipam.py +23 -18
- infrahub/graphql/queries/relationship.py +11 -10
- infrahub/graphql/queries/resource_manager.py +43 -27
- infrahub/graphql/queries/search.py +9 -8
- infrahub/graphql/queries/status.py +12 -9
- infrahub/graphql/queries/task.py +11 -9
- infrahub/graphql/resolvers/resolver.py +69 -43
- infrahub/graphql/resolvers/single_relationship.py +16 -10
- infrahub/graphql/schema.py +2 -0
- infrahub/graphql/subscription/__init__.py +1 -1
- infrahub/graphql/subscription/events.py +1 -1
- infrahub/graphql/subscription/graphql_query.py +8 -8
- infrahub/graphql/types/branch.py +2 -2
- infrahub/graphql/types/common.py +6 -1
- infrahub/graphql/types/enums.py +2 -0
- infrahub/graphql/types/event.py +100 -0
- infrahub/graphql/types/interface.py +2 -2
- infrahub/graphql/types/node.py +3 -3
- infrahub/graphql/types/permission.py +2 -2
- infrahub/graphql/types/relationship.py +3 -3
- infrahub/graphql/types/standard_node.py +9 -11
- infrahub/graphql/utils.py +28 -182
- infrahub/groups/tasks.py +2 -3
- infrahub/lock.py +1 -1
- infrahub/menu/constants.py +1 -0
- infrahub/menu/generator.py +14 -3
- infrahub/menu/menu.py +116 -127
- infrahub/menu/models.py +4 -4
- infrahub/message_bus/messages/__init__.py +0 -4
- infrahub/message_bus/messages/event_branch_merge.py +3 -0
- infrahub/message_bus/messages/request_proposedchange_pipeline.py +2 -0
- infrahub/message_bus/operations/__init__.py +3 -5
- infrahub/message_bus/operations/check/__init__.py +2 -2
- infrahub/message_bus/operations/check/generator.py +1 -3
- infrahub/message_bus/operations/check/repository.py +1 -1
- infrahub/message_bus/operations/event/branch.py +7 -3
- infrahub/message_bus/operations/event/schema.py +1 -1
- infrahub/message_bus/operations/finalize/validator.py +1 -1
- infrahub/message_bus/operations/git/file.py +2 -2
- infrahub/message_bus/operations/git/repository.py +1 -1
- infrahub/message_bus/operations/requests/__init__.py +0 -2
- infrahub/message_bus/operations/requests/generator_definition.py +1 -1
- infrahub/message_bus/operations/requests/proposed_change.py +26 -11
- infrahub/message_bus/operations/requests/repository.py +2 -2
- infrahub/message_bus/operations/send/echo.py +1 -1
- infrahub/message_bus/types.py +1 -1
- infrahub/permissions/__init__.py +2 -1
- infrahub/permissions/types.py +26 -0
- infrahub/pools/prefix.py +29 -165
- infrahub/prefect_server/__init__.py +0 -0
- infrahub/prefect_server/app.py +18 -0
- infrahub/prefect_server/database.py +20 -0
- infrahub/prefect_server/events.py +28 -0
- infrahub/prefect_server/models.py +46 -0
- infrahub/proposed_change/models.py +15 -1
- infrahub/proposed_change/tasks.py +173 -35
- infrahub/pytest_plugin.py +4 -4
- infrahub/server.py +12 -11
- infrahub/services/__init__.py +147 -62
- infrahub/services/adapters/cache/__init__.py +7 -5
- infrahub/services/adapters/cache/nats.py +40 -22
- infrahub/services/adapters/cache/redis.py +0 -4
- infrahub/services/adapters/event/__init__.py +10 -18
- infrahub/services/adapters/http/__init__.py +0 -5
- infrahub/services/adapters/http/httpx.py +22 -15
- infrahub/services/adapters/message_bus/__init__.py +23 -6
- infrahub/services/adapters/message_bus/local.py +8 -6
- infrahub/services/adapters/message_bus/nats.py +12 -6
- infrahub/services/adapters/message_bus/rabbitmq.py +22 -9
- infrahub/services/adapters/workflow/__init__.py +11 -8
- infrahub/services/adapters/workflow/local.py +28 -7
- infrahub/services/adapters/workflow/worker.py +23 -7
- infrahub/services/component.py +38 -35
- infrahub/services/scheduler.py +32 -29
- infrahub/storage.py +2 -4
- infrahub/task_manager/constants.py +1 -1
- infrahub/task_manager/event.py +182 -0
- infrahub/task_manager/models.py +125 -1
- infrahub/task_manager/task.py +1 -1
- infrahub/tasks/artifact.py +14 -16
- infrahub/tasks/registry.py +1 -1
- infrahub/tasks/telemetry.py +13 -14
- infrahub/transformations/tasks.py +3 -5
- infrahub/trigger/__init__.py +0 -0
- infrahub/trigger/catalogue.py +15 -0
- infrahub/trigger/constants.py +9 -0
- infrahub/trigger/models.py +69 -0
- infrahub/trigger/tasks.py +85 -0
- infrahub/types.py +1 -1
- infrahub/utils.py +1 -1
- infrahub/webhook/constants.py +0 -2
- infrahub/webhook/models.py +8 -2
- infrahub/webhook/tasks.py +20 -73
- infrahub/webhook/triggers.py +20 -0
- infrahub/workers/infrahub_async.py +36 -25
- infrahub/workers/utils.py +63 -0
- infrahub/workflows/catalogue.py +13 -37
- infrahub/workflows/initialization.py +6 -8
- infrahub/workflows/models.py +3 -5
- infrahub/workflows/utils.py +1 -1
- infrahub_sdk/ctl/check.py +3 -3
- infrahub_sdk/ctl/cli_commands.py +11 -10
- infrahub_sdk/ctl/exceptions.py +0 -6
- infrahub_sdk/ctl/exporter.py +1 -1
- infrahub_sdk/ctl/generator.py +5 -5
- infrahub_sdk/ctl/importer.py +3 -2
- infrahub_sdk/ctl/menu.py +1 -1
- infrahub_sdk/ctl/object.py +1 -1
- infrahub_sdk/ctl/repository.py +23 -15
- infrahub_sdk/ctl/schema.py +2 -2
- infrahub_sdk/ctl/utils.py +4 -3
- infrahub_sdk/ctl/validate.py +2 -1
- infrahub_sdk/exceptions.py +6 -0
- infrahub_sdk/generator.py +3 -0
- infrahub_sdk/node.py +2 -2
- infrahub_sdk/schema/__init__.py +14 -2
- infrahub_sdk/schema/main.py +7 -0
- infrahub_sdk/utils.py +11 -1
- infrahub_sdk/yaml.py +2 -3
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/METADATA +46 -12
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/RECORD +338 -321
- infrahub_testcontainers/container.py +14 -6
- infrahub_testcontainers/docker-compose.test.yml +24 -5
- infrahub_testcontainers/haproxy.cfg +43 -0
- infrahub_testcontainers/helpers.py +85 -1
- infrahub/core/branch/constants.py +0 -2
- infrahub/graphql/query.py +0 -52
- infrahub/message_bus/messages/request_artifactdefinition_check.py +0 -17
- infrahub/message_bus/operations/requests/artifact_definition.py +0 -148
- infrahub/schema/constants.py +0 -1
- infrahub/schema/tasks.py +0 -76
- infrahub/services/adapters/database/__init__.py +0 -9
- infrahub_sdk/ctl/_file.py +0 -13
- /infrahub/{schema → artifacts}/__init__.py +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.6.dist-info → infrahub_server-1.2.0rc0.dist-info}/entry_points.txt +0 -0
infrahub/graphql/analyzer.py
CHANGED
|
@@ -1,38 +1,344 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections import deque
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from graphql import (
|
|
11
|
+
FieldNode,
|
|
12
|
+
FragmentDefinitionNode,
|
|
13
|
+
FragmentSpreadNode,
|
|
14
|
+
GraphQLSchema,
|
|
15
|
+
InlineFragmentNode,
|
|
16
|
+
NamedTypeNode,
|
|
17
|
+
NonNullTypeNode,
|
|
18
|
+
OperationDefinitionNode,
|
|
19
|
+
OperationType,
|
|
20
|
+
SelectionSetNode,
|
|
21
|
+
)
|
|
4
22
|
from infrahub_sdk.analyzer import GraphQLQueryAnalyzer
|
|
5
23
|
from infrahub_sdk.utils import extract_fields
|
|
6
24
|
|
|
7
|
-
from infrahub.core.
|
|
25
|
+
from infrahub.core.constants import RelationshipCardinality
|
|
26
|
+
from infrahub.core.schema import GenericSchema
|
|
27
|
+
from infrahub.exceptions import SchemaNotFoundError
|
|
8
28
|
from infrahub.graphql.utils import extract_schema_models
|
|
9
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from infrahub.core.branch import Branch
|
|
32
|
+
from infrahub.core.schema import MainSchemaTypes
|
|
33
|
+
from infrahub.core.schema.schema_branch import SchemaBranch
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MutateAction(str, Enum):
|
|
37
|
+
CREATE = "create"
|
|
38
|
+
DELETE = "delete"
|
|
39
|
+
UPDATE = "update"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ContextType(str, Enum):
|
|
43
|
+
EDGE = "edge"
|
|
44
|
+
NODE = "node"
|
|
45
|
+
DIRECT = "direct"
|
|
46
|
+
OBJECT = "object"
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_operation(cls, operation: OperationType) -> ContextType:
|
|
50
|
+
match operation:
|
|
51
|
+
case OperationType.QUERY:
|
|
52
|
+
return cls.EDGE
|
|
53
|
+
case OperationType.MUTATION:
|
|
54
|
+
return cls.OBJECT
|
|
55
|
+
case OperationType.SUBSCRIPTION:
|
|
56
|
+
return cls.EDGE
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_relationship_cardinality(cls, cardinality: RelationshipCardinality) -> ContextType:
|
|
60
|
+
match cardinality:
|
|
61
|
+
case RelationshipCardinality.MANY:
|
|
62
|
+
return cls.EDGE
|
|
63
|
+
case RelationshipCardinality.ONE:
|
|
64
|
+
return cls.NODE
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class GraphQLOperation(str, Enum):
|
|
68
|
+
QUERY = "query"
|
|
69
|
+
MUTATION = "mutation"
|
|
70
|
+
SUBSCRIPTION = "subscription"
|
|
71
|
+
UNDEFINED = "undefined"
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_operation(cls, operation: OperationType) -> GraphQLOperation:
|
|
75
|
+
match operation:
|
|
76
|
+
case OperationType.QUERY:
|
|
77
|
+
return cls.QUERY
|
|
78
|
+
case OperationType.MUTATION:
|
|
79
|
+
return cls.MUTATION
|
|
80
|
+
case OperationType.SUBSCRIPTION:
|
|
81
|
+
return cls.SUBSCRIPTION
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class GraphQLSelectionSet:
|
|
86
|
+
field_nodes: list[FieldNode]
|
|
87
|
+
fragment_spread_nodes: list[FragmentSpreadNode]
|
|
88
|
+
inline_fragment_nodes: list[InlineFragmentNode]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class GraphQLArgument:
|
|
93
|
+
name: str
|
|
94
|
+
value: str
|
|
95
|
+
kind: str
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class ObjectAccess:
|
|
100
|
+
attributes: set[str] = field(default_factory=set)
|
|
101
|
+
relationships: set[str] = field(default_factory=set)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class GraphQLVariable:
|
|
106
|
+
name: str
|
|
107
|
+
type: str
|
|
108
|
+
required: bool
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class GraphQLQueryModel:
|
|
113
|
+
model: MainSchemaTypes
|
|
114
|
+
root: bool
|
|
115
|
+
arguments: list[GraphQLArgument]
|
|
116
|
+
attributes: set[str]
|
|
117
|
+
relationships: set[str]
|
|
118
|
+
mutate_actions: list[MutateAction] = field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class GraphQLQueryNode:
|
|
123
|
+
path: str
|
|
124
|
+
operation: GraphQLOperation = field(default=GraphQLOperation.UNDEFINED)
|
|
125
|
+
arguments: list[GraphQLArgument] = field(default_factory=list)
|
|
126
|
+
variables: list[GraphQLVariable] = field(default_factory=list)
|
|
127
|
+
context_type: ContextType = field(default=ContextType.EDGE)
|
|
128
|
+
parent: GraphQLQueryNode | None = field(default=None)
|
|
129
|
+
children: list[GraphQLQueryNode] = field(default_factory=list)
|
|
130
|
+
infrahub_model: MainSchemaTypes | None = field(default=None)
|
|
131
|
+
infrahub_node_models: list[MainSchemaTypes] = field(default_factory=list)
|
|
132
|
+
infrahub_attributes: set[str] = field(default_factory=set)
|
|
133
|
+
infrahub_relationships: set[str] = field(default_factory=set)
|
|
134
|
+
field_node: FieldNode | None = field(default=None)
|
|
135
|
+
mutate_actions: list[MutateAction] = field(default_factory=list)
|
|
136
|
+
|
|
137
|
+
def context_model(self) -> MainSchemaTypes | None:
|
|
138
|
+
"""Return the closest Infrahub object by going up in the tree"""
|
|
139
|
+
if self.infrahub_model:
|
|
140
|
+
return self.infrahub_model
|
|
141
|
+
if self.parent:
|
|
142
|
+
return self.parent.context_model()
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def context_path(self) -> str:
|
|
147
|
+
"""Return the relative path for the current context with the closest Infrahub object as the root"""
|
|
148
|
+
if self.infrahub_model:
|
|
149
|
+
return f"/{self.path}"
|
|
150
|
+
if self.parent:
|
|
151
|
+
return f"{self.parent.context_path()}/{self.path}"
|
|
152
|
+
return self.path
|
|
153
|
+
|
|
154
|
+
def properties_path(self) -> str:
|
|
155
|
+
"""Indicate the expected path to where Infrahub attributes and relationships would be defined."""
|
|
156
|
+
if self.infrahub_model:
|
|
157
|
+
match self.context_type:
|
|
158
|
+
case ContextType.DIRECT:
|
|
159
|
+
return f"/{self.path}"
|
|
160
|
+
case ContextType.EDGE:
|
|
161
|
+
return f"/{self.path}/edges/node"
|
|
162
|
+
case ContextType.NODE:
|
|
163
|
+
return f"/{self.path}/node"
|
|
164
|
+
case ContextType.OBJECT:
|
|
165
|
+
return f"/{self.path}/object"
|
|
166
|
+
if self.parent:
|
|
167
|
+
return self.parent.properties_path()
|
|
168
|
+
|
|
169
|
+
return self.path
|
|
170
|
+
|
|
171
|
+
def full_path(self) -> str:
|
|
172
|
+
"""Return the full path within the tree for the current context."""
|
|
173
|
+
if self.parent:
|
|
174
|
+
return f"{self.parent.full_path()}/{self.path}"
|
|
175
|
+
return self.path
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def at_root(self) -> bool:
|
|
179
|
+
if self.parent:
|
|
180
|
+
return False
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def in_property_level(self) -> bool:
|
|
185
|
+
"""Indicate if properties, i.e., attributes and relationships could exist at this level."""
|
|
186
|
+
return self.context_path() == self.properties_path()
|
|
187
|
+
|
|
188
|
+
def append_attribute(self, attribute: str) -> None:
|
|
189
|
+
"""Add attributes to the closes parent Infrahub object."""
|
|
190
|
+
if self.infrahub_model:
|
|
191
|
+
self.infrahub_attributes.add(attribute)
|
|
192
|
+
elif self.parent:
|
|
193
|
+
self.parent.append_attribute(attribute=attribute)
|
|
194
|
+
|
|
195
|
+
def append_relationship(self, relationship: str) -> None:
|
|
196
|
+
"""Add relationships to the closes parent Infrahub object."""
|
|
197
|
+
if self.infrahub_model:
|
|
198
|
+
self.infrahub_relationships.add(relationship)
|
|
199
|
+
elif self.parent:
|
|
200
|
+
self.parent.append_relationship(relationship=relationship)
|
|
201
|
+
|
|
202
|
+
def get_models(self) -> list[GraphQLQueryModel]:
|
|
203
|
+
"""Return all models defined on this node along with child nodes"""
|
|
204
|
+
models: list[GraphQLQueryModel] = []
|
|
205
|
+
if self.infrahub_model:
|
|
206
|
+
models.append(
|
|
207
|
+
GraphQLQueryModel(
|
|
208
|
+
model=self.infrahub_model,
|
|
209
|
+
root=self.at_root,
|
|
210
|
+
arguments=self.arguments,
|
|
211
|
+
attributes=self.infrahub_attributes,
|
|
212
|
+
relationships=self.infrahub_relationships,
|
|
213
|
+
mutate_actions=self.mutate_actions,
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
for used_by in self.infrahub_node_models:
|
|
217
|
+
models.append(
|
|
218
|
+
GraphQLQueryModel(
|
|
219
|
+
model=used_by,
|
|
220
|
+
root=self.at_root,
|
|
221
|
+
arguments=self.arguments,
|
|
222
|
+
attributes=self.infrahub_attributes,
|
|
223
|
+
relationships=self.infrahub_relationships,
|
|
224
|
+
mutate_actions=self.mutate_actions,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
for child in self.children:
|
|
229
|
+
models.extend(child.get_models())
|
|
230
|
+
return models
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class GraphQLQueryReport:
|
|
235
|
+
queries: list[GraphQLQueryNode]
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def impacted_models(self) -> list[str]:
|
|
239
|
+
"""Return a list of all Infrahub objects that are impacted by queries within the request"""
|
|
240
|
+
models: set[str] = set()
|
|
241
|
+
for query in self.queries:
|
|
242
|
+
query_models = query.get_models()
|
|
243
|
+
models.update([query_model.model.kind for query_model in query_models])
|
|
244
|
+
|
|
245
|
+
return sorted(models)
|
|
246
|
+
|
|
247
|
+
@cached_property
|
|
248
|
+
def requested_read(self) -> dict[str, ObjectAccess]:
|
|
249
|
+
"""Return Infrahub objects and the fields (attributes and relationships) that this query would attempt to read"""
|
|
250
|
+
access: dict[str, ObjectAccess] = {}
|
|
251
|
+
for query in self.queries:
|
|
252
|
+
query_models = query.get_models()
|
|
253
|
+
for query_model in query_models:
|
|
254
|
+
if query_model.model.kind not in access:
|
|
255
|
+
access[query_model.model.kind] = ObjectAccess()
|
|
256
|
+
access[query_model.model.kind].attributes.update(query_model.attributes)
|
|
257
|
+
access[query_model.model.kind].relationships.update(query_model.relationships)
|
|
258
|
+
|
|
259
|
+
return access
|
|
260
|
+
|
|
261
|
+
@cached_property
|
|
262
|
+
def kind_action_map(self) -> dict[str, set[MutateAction]]:
|
|
263
|
+
access: dict[str, set[MutateAction]] = {}
|
|
264
|
+
root_models: set[str] = set()
|
|
265
|
+
includes_mutations: bool = False
|
|
266
|
+
for query in self.queries:
|
|
267
|
+
query_models = query.get_models()
|
|
268
|
+
for query_model in query_models:
|
|
269
|
+
if query_model.mutate_actions:
|
|
270
|
+
includes_mutations = True
|
|
271
|
+
if includes_mutations:
|
|
272
|
+
if query_model.model.kind not in access:
|
|
273
|
+
access[query_model.model.kind] = set()
|
|
274
|
+
if query_model.root:
|
|
275
|
+
root_models.add(query_model.model.kind)
|
|
276
|
+
access[query_model.model.kind].update(query_model.mutate_actions)
|
|
277
|
+
|
|
278
|
+
# Until we properly analyze the data payload it is assumed that the required permissions on non-root objects is update
|
|
279
|
+
# the idea around this is that at this point even if we only return data from a relationship without actually updating
|
|
280
|
+
# that relationship we'd still expect to have UPDATE permissions on that related object. However, this is still a step
|
|
281
|
+
# in the right direction as we'd previously require the same permissions as that of the base object so this is still
|
|
282
|
+
# more correct.
|
|
283
|
+
for node_kind, node_actions in access.items():
|
|
284
|
+
if node_kind not in root_models:
|
|
285
|
+
node_actions.add(MutateAction.UPDATE)
|
|
286
|
+
|
|
287
|
+
return access
|
|
288
|
+
|
|
10
289
|
|
|
11
290
|
class InfrahubGraphQLQueryAnalyzer(GraphQLQueryAnalyzer):
|
|
12
291
|
def __init__(
|
|
13
292
|
self,
|
|
14
293
|
query: str,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
294
|
+
branch: Branch,
|
|
295
|
+
schema_branch: SchemaBranch,
|
|
296
|
+
schema: GraphQLSchema | None = None,
|
|
297
|
+
query_variables: dict[str, Any] | None = None,
|
|
298
|
+
operation_name: str | None = None,
|
|
19
299
|
) -> None:
|
|
20
|
-
self.branch
|
|
21
|
-
self.
|
|
300
|
+
self.branch = branch
|
|
301
|
+
self.schema_branch = schema_branch
|
|
302
|
+
self.operation_name = operation_name
|
|
22
303
|
self.query_variables: dict[str, Any] = query_variables or {}
|
|
304
|
+
self._named_fragments: dict[str, GraphQLQueryNode] = {}
|
|
305
|
+
self._fragment_dependencies: dict[str, set[str]] = {}
|
|
23
306
|
super().__init__(query=query, schema=schema)
|
|
24
307
|
|
|
25
308
|
@property
|
|
26
309
|
def operation_names(self) -> list[str]:
|
|
27
310
|
return [operation.name for operation in self.operations if operation.name is not None]
|
|
28
311
|
|
|
312
|
+
@cached_property
|
|
313
|
+
def _fragment_definitions(self) -> list[FragmentDefinitionNode]:
|
|
314
|
+
return [
|
|
315
|
+
definition for definition in self.document.definitions if isinstance(definition, FragmentDefinitionNode)
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
@cached_property
|
|
319
|
+
def _operation_definitions(self) -> list[OperationDefinitionNode]:
|
|
320
|
+
return [
|
|
321
|
+
definition for definition in self.document.definitions if isinstance(definition, OperationDefinitionNode)
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
def get_named_fragment_with_parent(self, name: str, parent: GraphQLQueryNode) -> GraphQLQueryNode:
|
|
325
|
+
"""Return a copy of the named fragment and attach it to a parent.
|
|
326
|
+
|
|
327
|
+
We return a copy of the object as a named fragment could be used by multiple queries and as we're
|
|
328
|
+
generally working with references to objects we wouldn't want to override the parent of a previously
|
|
329
|
+
assigned object
|
|
330
|
+
"""
|
|
331
|
+
named_fragment = deepcopy(self._named_fragments[name])
|
|
332
|
+
named_fragment.parent = parent
|
|
333
|
+
return named_fragment
|
|
334
|
+
|
|
29
335
|
async def get_models_in_use(self, types: dict[str, Any]) -> set[str]:
|
|
30
336
|
"""List of Infrahub models that are referenced in the query."""
|
|
31
337
|
graphql_types = set()
|
|
32
338
|
models = set()
|
|
33
339
|
|
|
34
|
-
if not
|
|
35
|
-
raise ValueError("Schema
|
|
340
|
+
if not self.schema:
|
|
341
|
+
raise ValueError("Schema must be provided to extract the models in use.")
|
|
36
342
|
|
|
37
343
|
for definition in self.document.definitions:
|
|
38
344
|
fields = await extract_fields(definition.selection_set)
|
|
@@ -58,3 +364,258 @@ class InfrahubGraphQLQueryAnalyzer(GraphQLQueryAnalyzer):
|
|
|
58
364
|
continue
|
|
59
365
|
|
|
60
366
|
return models
|
|
367
|
+
|
|
368
|
+
@cached_property
|
|
369
|
+
def query_report(self) -> GraphQLQueryReport:
|
|
370
|
+
self._populate_named_fragments()
|
|
371
|
+
operations = self._get_operations()
|
|
372
|
+
|
|
373
|
+
return GraphQLQueryReport(queries=operations)
|
|
374
|
+
|
|
375
|
+
def _get_operations(self) -> list[GraphQLQueryNode]:
|
|
376
|
+
operations: list[GraphQLQueryNode] = []
|
|
377
|
+
for operation_definition in self._operation_definitions:
|
|
378
|
+
selections = self._get_selections(selection_set=operation_definition.selection_set)
|
|
379
|
+
|
|
380
|
+
for field_node in selections.field_nodes:
|
|
381
|
+
schema_model: MainSchemaTypes
|
|
382
|
+
infrahub_node_models: list[MainSchemaTypes] = []
|
|
383
|
+
model_name = self._get_model_name(node=field_node, operation_definition=operation_definition)
|
|
384
|
+
|
|
385
|
+
if model_name in self.schema_branch.node_names:
|
|
386
|
+
schema_model = self.schema_branch.get_node(name=model_name, duplicate=False)
|
|
387
|
+
elif model_name in self.schema_branch.generic_names:
|
|
388
|
+
schema_model = self.schema_branch.get_generic(name=model_name, duplicate=False)
|
|
389
|
+
infrahub_node_models = [
|
|
390
|
+
self.schema_branch.get(name=used_by, duplicate=False) for used_by in schema_model.used_by
|
|
391
|
+
]
|
|
392
|
+
elif model_name in self.schema_branch.profile_names:
|
|
393
|
+
schema_model = self.schema_branch.get_profile(name=model_name, duplicate=False)
|
|
394
|
+
else:
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
operational_node = GraphQLQueryNode(
|
|
398
|
+
operation=GraphQLOperation.from_operation(operation=operation_definition.operation),
|
|
399
|
+
path=schema_model.kind,
|
|
400
|
+
infrahub_model=schema_model,
|
|
401
|
+
infrahub_node_models=infrahub_node_models,
|
|
402
|
+
mutate_actions=self._get_model_mutations(
|
|
403
|
+
node=field_node, operation_definition=operation_definition
|
|
404
|
+
),
|
|
405
|
+
context_type=ContextType.from_operation(operation=operation_definition.operation),
|
|
406
|
+
arguments=self._parse_arguments(field_node=field_node),
|
|
407
|
+
variables=self._get_variables(operation=operation_definition),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if field_node.selection_set:
|
|
411
|
+
selections = self._get_selections(selection_set=field_node.selection_set)
|
|
412
|
+
for selection_field_node in selections.field_nodes:
|
|
413
|
+
operational_node.children.append(
|
|
414
|
+
self._populate_field_node(node=selection_field_node, query_node=operational_node)
|
|
415
|
+
)
|
|
416
|
+
operations.append(operational_node)
|
|
417
|
+
return operations
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
def _get_model_name(node: FieldNode, operation_definition: OperationDefinitionNode) -> str:
|
|
421
|
+
if operation_definition.operation == OperationType.MUTATION and node.name.value.endswith(
|
|
422
|
+
("Create", "Delete", "Update", "Upsert")
|
|
423
|
+
):
|
|
424
|
+
return node.name.value[:-6]
|
|
425
|
+
return node.name.value
|
|
426
|
+
|
|
427
|
+
@staticmethod
|
|
428
|
+
def _get_model_mutations(node: FieldNode, operation_definition: OperationDefinitionNode) -> list[MutateAction]:
|
|
429
|
+
if operation_definition.operation == OperationType.MUTATION:
|
|
430
|
+
if node.name.value.endswith("Create"):
|
|
431
|
+
return [MutateAction.CREATE]
|
|
432
|
+
if node.name.value.endswith("Delete"):
|
|
433
|
+
return [MutateAction.DELETE]
|
|
434
|
+
if node.name.value.endswith("Update"):
|
|
435
|
+
return [MutateAction.UPDATE]
|
|
436
|
+
if node.name.value.endswith("Upsert"):
|
|
437
|
+
return [MutateAction.CREATE, MutateAction.UPDATE]
|
|
438
|
+
return []
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def _sorted_fragment_definitions(self) -> list[FragmentDefinitionNode]:
|
|
442
|
+
"""Sort fragments so that we start processing fragments that don't depend on other fragments"""
|
|
443
|
+
dependencies = deepcopy(self._fragment_dependencies)
|
|
444
|
+
|
|
445
|
+
independent_fragments = deque([frag for frag, deps in dependencies.items() if not deps])
|
|
446
|
+
|
|
447
|
+
sorted_fragments = []
|
|
448
|
+
|
|
449
|
+
while independent_fragments:
|
|
450
|
+
fragment_name = independent_fragments.popleft()
|
|
451
|
+
sorted_fragments.append(fragment_name)
|
|
452
|
+
|
|
453
|
+
for dependent, deps in dependencies.items():
|
|
454
|
+
if fragment_name in deps:
|
|
455
|
+
deps.remove(fragment_name)
|
|
456
|
+
if not deps:
|
|
457
|
+
independent_fragments.append(dependent)
|
|
458
|
+
|
|
459
|
+
if len(sorted_fragments) != len(self._fragment_dependencies):
|
|
460
|
+
raise ValueError("Circular fragment dependency detected.")
|
|
461
|
+
|
|
462
|
+
fragment_name_to_definition = {frag.name.value: frag for frag in self._fragment_definitions}
|
|
463
|
+
return [fragment_name_to_definition[name] for name in sorted_fragments]
|
|
464
|
+
|
|
465
|
+
def _populate_fragment_dependency(self, name: str, selection_set: SelectionSetNode | None) -> None:
|
|
466
|
+
if selection_set:
|
|
467
|
+
for selection in selection_set.selections:
|
|
468
|
+
if isinstance(selection, FragmentSpreadNode):
|
|
469
|
+
self._fragment_dependencies[name].add(selection.name.value)
|
|
470
|
+
elif isinstance(selection, FieldNode):
|
|
471
|
+
self._populate_fragment_dependency(name=name, selection_set=selection.selection_set)
|
|
472
|
+
elif isinstance(selection, InlineFragmentNode):
|
|
473
|
+
self._populate_fragment_dependency(name=name, selection_set=selection.selection_set)
|
|
474
|
+
|
|
475
|
+
def _populate_fragment_dependencies(self) -> None:
|
|
476
|
+
for fragment in self._fragment_definitions:
|
|
477
|
+
fragment_name = fragment.name.value
|
|
478
|
+
self._fragment_dependencies[fragment_name] = set()
|
|
479
|
+
self._populate_fragment_dependency(name=fragment_name, selection_set=fragment.selection_set)
|
|
480
|
+
|
|
481
|
+
def _populate_named_fragments(self) -> None:
|
|
482
|
+
self._populate_fragment_dependencies()
|
|
483
|
+
self._named_fragments = {}
|
|
484
|
+
|
|
485
|
+
for fragment_definition in self._sorted_fragment_definitions:
|
|
486
|
+
fragment_name = fragment_definition.name.value
|
|
487
|
+
condition_name = fragment_definition.type_condition.name.value
|
|
488
|
+
selections = self._get_selections(selection_set=fragment_definition.selection_set)
|
|
489
|
+
|
|
490
|
+
try:
|
|
491
|
+
infrahub_model = self.schema_branch.get(name=condition_name, duplicate=False)
|
|
492
|
+
except SchemaNotFoundError:
|
|
493
|
+
infrahub_model = None
|
|
494
|
+
|
|
495
|
+
named_fragment = GraphQLQueryNode(
|
|
496
|
+
path=fragment_definition.type_condition.name.value,
|
|
497
|
+
context_type=ContextType.DIRECT,
|
|
498
|
+
infrahub_model=infrahub_model,
|
|
499
|
+
)
|
|
500
|
+
for field_node in selections.field_nodes:
|
|
501
|
+
named_fragment.children.append(self._populate_field_node(node=field_node, query_node=named_fragment))
|
|
502
|
+
for inline_fragment_node in selections.inline_fragment_nodes:
|
|
503
|
+
named_fragment.children.append(
|
|
504
|
+
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=named_fragment)
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
self._named_fragments[fragment_name] = named_fragment
|
|
508
|
+
|
|
509
|
+
def _populate_field_node(self, node: FieldNode, query_node: GraphQLQueryNode) -> GraphQLQueryNode:
|
|
510
|
+
context_type = query_node.context_type
|
|
511
|
+
infrahub_model = None
|
|
512
|
+
infrahub_node_models: list[MainSchemaTypes] = []
|
|
513
|
+
if query_node.in_property_level:
|
|
514
|
+
if model := query_node.context_model():
|
|
515
|
+
if node.name.value in model.attribute_names or node.name.value == "display_label":
|
|
516
|
+
query_node.append_attribute(attribute=node.name.value)
|
|
517
|
+
elif node.name.value in model.relationship_names:
|
|
518
|
+
rel = model.get_relationship_or_none(name=node.name.value)
|
|
519
|
+
if rel:
|
|
520
|
+
infrahub_model = self.schema_branch.get(name=rel.peer, duplicate=False)
|
|
521
|
+
if isinstance(infrahub_model, GenericSchema):
|
|
522
|
+
infrahub_node_models = [
|
|
523
|
+
self.schema_branch.get(name=used_by, duplicate=False)
|
|
524
|
+
for used_by in infrahub_model.used_by
|
|
525
|
+
]
|
|
526
|
+
|
|
527
|
+
context_type = ContextType.from_relationship_cardinality(cardinality=rel.cardinality)
|
|
528
|
+
query_node.append_relationship(relationship=node.name.value)
|
|
529
|
+
|
|
530
|
+
current_node = GraphQLQueryNode(
|
|
531
|
+
parent=query_node,
|
|
532
|
+
path=node.name.value,
|
|
533
|
+
context_type=context_type,
|
|
534
|
+
infrahub_model=infrahub_model,
|
|
535
|
+
infrahub_node_models=infrahub_node_models,
|
|
536
|
+
arguments=self._parse_arguments(field_node=node),
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if node.selection_set:
|
|
540
|
+
selections = self._get_selections(selection_set=node.selection_set)
|
|
541
|
+
for field_node in selections.field_nodes:
|
|
542
|
+
current_node.children.append(self._populate_field_node(node=field_node, query_node=current_node))
|
|
543
|
+
for inline_fragment_node in selections.inline_fragment_nodes:
|
|
544
|
+
current_node.children.append(
|
|
545
|
+
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=current_node)
|
|
546
|
+
)
|
|
547
|
+
for fragment_spread_node in selections.fragment_spread_nodes:
|
|
548
|
+
current_node.children.append(
|
|
549
|
+
self._populate_fragment_spread_node(node=fragment_spread_node, query_node=current_node)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
return current_node
|
|
553
|
+
|
|
554
|
+
def _populate_inline_fragment_node(
|
|
555
|
+
self, node: InlineFragmentNode, query_node: GraphQLQueryNode
|
|
556
|
+
) -> GraphQLQueryNode:
|
|
557
|
+
context_type = query_node.context_type
|
|
558
|
+
infrahub_model = self.schema_branch.get(name=node.type_condition.name.value)
|
|
559
|
+
context_type = ContextType.DIRECT
|
|
560
|
+
current_node = GraphQLQueryNode(
|
|
561
|
+
parent=query_node,
|
|
562
|
+
path=node.type_condition.name.value,
|
|
563
|
+
context_type=context_type,
|
|
564
|
+
infrahub_model=infrahub_model,
|
|
565
|
+
)
|
|
566
|
+
if node.selection_set:
|
|
567
|
+
selections = self._get_selections(selection_set=node.selection_set)
|
|
568
|
+
for field_node in selections.field_nodes:
|
|
569
|
+
current_node.children.append(self._populate_field_node(node=field_node, query_node=current_node))
|
|
570
|
+
for inline_fragment_node in selections.inline_fragment_nodes:
|
|
571
|
+
current_node.children.append(
|
|
572
|
+
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=current_node)
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
return current_node
|
|
576
|
+
|
|
577
|
+
def _populate_fragment_spread_node(
|
|
578
|
+
self, node: FragmentSpreadNode, query_node: GraphQLQueryNode
|
|
579
|
+
) -> GraphQLQueryNode:
|
|
580
|
+
return self.get_named_fragment_with_parent(name=node.name.value, parent=query_node)
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
def _get_selections(selection_set: SelectionSetNode) -> GraphQLSelectionSet:
|
|
584
|
+
return GraphQLSelectionSet(
|
|
585
|
+
field_nodes=[selection for selection in selection_set.selections if isinstance(selection, FieldNode)],
|
|
586
|
+
fragment_spread_nodes=[
|
|
587
|
+
selection for selection in selection_set.selections if isinstance(selection, FragmentSpreadNode)
|
|
588
|
+
],
|
|
589
|
+
inline_fragment_nodes=[
|
|
590
|
+
selection for selection in selection_set.selections if isinstance(selection, InlineFragmentNode)
|
|
591
|
+
],
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
@staticmethod
|
|
595
|
+
def _get_variables(operation: OperationDefinitionNode) -> list[GraphQLVariable]:
|
|
596
|
+
variables = []
|
|
597
|
+
for variable in operation.variable_definitions:
|
|
598
|
+
if isinstance(variable.type, NamedTypeNode):
|
|
599
|
+
variables.append(
|
|
600
|
+
GraphQLVariable(name=variable.variable.name.value, type=variable.type.name.value, required=False)
|
|
601
|
+
)
|
|
602
|
+
elif isinstance(variable.type, NonNullTypeNode):
|
|
603
|
+
if isinstance(variable.type.type, NamedTypeNode):
|
|
604
|
+
variables.append(
|
|
605
|
+
GraphQLVariable(
|
|
606
|
+
name=variable.variable.name.value, type=variable.type.type.name.value, required=True
|
|
607
|
+
)
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
return variables
|
|
611
|
+
|
|
612
|
+
@staticmethod
|
|
613
|
+
def _parse_arguments(field_node: FieldNode) -> list[GraphQLArgument]:
|
|
614
|
+
return [
|
|
615
|
+
GraphQLArgument(
|
|
616
|
+
name=argument.name.value,
|
|
617
|
+
value=getattr(argument.value, "value", ""),
|
|
618
|
+
kind=argument.value.kind,
|
|
619
|
+
)
|
|
620
|
+
for argument in field_node.arguments
|
|
621
|
+
]
|