infrahub-server 1.2.0rc0__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/api/dependencies.py +6 -6
- infrahub/api/diff/validation_models.py +7 -7
- infrahub/api/schema.py +1 -1
- infrahub/artifacts/models.py +5 -3
- infrahub/artifacts/tasks.py +3 -5
- infrahub/cli/__init__.py +13 -9
- infrahub/cli/constants.py +3 -0
- infrahub/cli/db.py +165 -183
- infrahub/cli/upgrade.py +146 -0
- infrahub/computed_attribute/gather.py +185 -0
- infrahub/computed_attribute/models.py +240 -12
- infrahub/computed_attribute/tasks.py +77 -441
- infrahub/computed_attribute/triggers.py +13 -47
- infrahub/config.py +43 -32
- infrahub/context.py +14 -0
- infrahub/core/account.py +4 -4
- infrahub/core/attribute.py +58 -58
- infrahub/core/branch/tasks.py +74 -22
- infrahub/core/changelog/diff.py +95 -36
- infrahub/core/changelog/models.py +217 -43
- infrahub/core/constants/__init__.py +28 -0
- infrahub/core/constants/infrahubkind.py +2 -0
- infrahub/core/constants/schema.py +2 -0
- infrahub/core/constraint/node/runner.py +9 -8
- infrahub/core/diff/branch_differ.py +10 -10
- infrahub/core/diff/enricher/cardinality_one.py +5 -0
- infrahub/core/diff/enricher/hierarchy.py +17 -4
- infrahub/core/diff/enricher/labels.py +5 -0
- infrahub/core/diff/enricher/path_identifier.py +4 -0
- infrahub/core/diff/ipam_diff_parser.py +4 -5
- infrahub/core/diff/model/diff.py +27 -27
- infrahub/core/diff/model/path.py +32 -9
- infrahub/core/diff/parent_node_adder.py +78 -0
- infrahub/core/diff/payload_builder.py +13 -2
- infrahub/core/diff/query/filters.py +2 -2
- infrahub/core/diff/query/merge.py +20 -17
- infrahub/core/diff/query/save.py +188 -182
- infrahub/core/diff/query/summary_counts_enricher.py +51 -4
- infrahub/core/diff/query_parser.py +4 -4
- infrahub/core/diff/repository/deserializer.py +8 -3
- infrahub/core/diff/repository/repository.py +156 -38
- infrahub/core/diff/tasks.py +4 -4
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +3 -0
- infrahub/core/initialization.py +1 -10
- infrahub/core/ipam/constants.py +3 -4
- infrahub/core/ipam/reconciler.py +12 -12
- infrahub/core/ipam/utilization.py +10 -13
- infrahub/core/manager.py +36 -36
- infrahub/core/merge.py +7 -7
- infrahub/core/migrations/__init__.py +2 -3
- infrahub/core/migrations/graph/__init__.py +12 -3
- infrahub/core/migrations/graph/m017_add_core_profile.py +1 -5
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +4 -4
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
- infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
- infrahub/core/migrations/graph/m022_add_generate_template_attr.py +48 -0
- infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +96 -0
- infrahub/core/migrations/query/attribute_add.py +2 -2
- infrahub/core/migrations/query/node_duplicate.py +43 -26
- infrahub/core/migrations/query/schema_attribute_update.py +2 -2
- infrahub/core/migrations/schema/models.py +19 -4
- infrahub/core/migrations/schema/node_remove.py +26 -12
- infrahub/core/migrations/schema/tasks.py +2 -2
- infrahub/core/migrations/shared.py +16 -16
- infrahub/core/models.py +15 -6
- infrahub/core/node/__init__.py +43 -39
- infrahub/core/node/base.py +2 -4
- infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
- infrahub/core/node/constraints/grouped_uniqueness.py +99 -47
- infrahub/core/node/constraints/interface.py +1 -2
- infrahub/core/node/delete_validator.py +3 -5
- infrahub/core/node/ipam.py +4 -4
- infrahub/core/node/permissions.py +7 -7
- infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
- infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -6
- infrahub/core/node/resource_manager/number_pool.py +3 -3
- infrahub/core/path.py +12 -12
- infrahub/core/property.py +11 -11
- infrahub/core/protocols.py +7 -0
- infrahub/core/protocols_base.py +21 -21
- infrahub/core/query/__init__.py +33 -33
- infrahub/core/query/attribute.py +6 -4
- infrahub/core/query/diff.py +3 -3
- infrahub/core/query/node.py +82 -32
- infrahub/core/query/relationship.py +228 -40
- infrahub/core/query/resource_manager.py +2 -0
- infrahub/core/query/standard_node.py +3 -3
- infrahub/core/query/subquery.py +9 -9
- infrahub/core/registry.py +13 -15
- infrahub/core/relationship/constraints/count.py +3 -4
- infrahub/core/relationship/constraints/peer_kind.py +3 -4
- infrahub/core/relationship/constraints/profiles_kind.py +2 -2
- infrahub/core/relationship/model.py +51 -59
- infrahub/core/schema/attribute_schema.py +16 -8
- infrahub/core/schema/basenode_schema.py +105 -44
- infrahub/core/schema/computed_attribute.py +3 -3
- infrahub/core/schema/definitions/core/__init__.py +147 -0
- infrahub/core/schema/definitions/core/account.py +171 -0
- infrahub/core/schema/definitions/core/artifact.py +136 -0
- infrahub/core/schema/definitions/core/builtin.py +24 -0
- infrahub/core/schema/definitions/core/check.py +68 -0
- infrahub/core/schema/definitions/core/core.py +17 -0
- infrahub/core/schema/definitions/core/generator.py +100 -0
- infrahub/core/schema/definitions/core/graphql_query.py +79 -0
- infrahub/core/schema/definitions/core/group.py +108 -0
- infrahub/core/schema/definitions/core/ipam.py +193 -0
- infrahub/core/schema/definitions/core/lineage.py +19 -0
- infrahub/core/schema/definitions/core/menu.py +48 -0
- infrahub/core/schema/definitions/core/permission.py +163 -0
- infrahub/core/schema/definitions/core/profile.py +18 -0
- infrahub/core/schema/definitions/core/propose_change.py +97 -0
- infrahub/core/schema/definitions/core/propose_change_comment.py +193 -0
- infrahub/core/schema/definitions/core/propose_change_validator.py +328 -0
- infrahub/core/schema/definitions/core/repository.py +286 -0
- infrahub/core/schema/definitions/core/resource_pool.py +170 -0
- infrahub/core/schema/definitions/core/template.py +27 -0
- infrahub/core/schema/definitions/core/transform.py +96 -0
- infrahub/core/schema/definitions/core/webhook.py +134 -0
- infrahub/core/schema/definitions/internal.py +16 -16
- infrahub/core/schema/dropdown.py +3 -4
- infrahub/core/schema/generated/attribute_schema.py +15 -18
- infrahub/core/schema/generated/base_node_schema.py +12 -14
- infrahub/core/schema/generated/node_schema.py +3 -5
- infrahub/core/schema/generated/relationship_schema.py +9 -11
- infrahub/core/schema/generic_schema.py +2 -2
- infrahub/core/schema/manager.py +20 -9
- infrahub/core/schema/node_schema.py +4 -2
- infrahub/core/schema/relationship_schema.py +14 -6
- infrahub/core/schema/schema_branch.py +292 -144
- infrahub/core/schema/schema_branch_computed.py +41 -4
- infrahub/core/task/task.py +3 -3
- infrahub/core/task/user_task.py +15 -15
- infrahub/core/timestamp.py +3 -3
- infrahub/core/utils.py +20 -18
- infrahub/core/validators/__init__.py +1 -3
- infrahub/core/validators/aggregated_checker.py +2 -2
- infrahub/core/validators/attribute/choices.py +2 -2
- infrahub/core/validators/attribute/enum.py +2 -2
- infrahub/core/validators/attribute/kind.py +2 -2
- infrahub/core/validators/attribute/length.py +2 -2
- infrahub/core/validators/attribute/optional.py +2 -2
- infrahub/core/validators/attribute/regex.py +2 -2
- infrahub/core/validators/attribute/unique.py +2 -2
- infrahub/core/validators/checks_runner.py +25 -2
- infrahub/core/validators/determiner.py +1 -3
- infrahub/core/validators/interface.py +6 -2
- infrahub/core/validators/model.py +22 -3
- infrahub/core/validators/models/validate_migration.py +17 -4
- infrahub/core/validators/node/attribute.py +2 -2
- infrahub/core/validators/node/generate_profile.py +2 -2
- infrahub/core/validators/node/hierarchy.py +3 -5
- infrahub/core/validators/node/inherit_from.py +27 -5
- infrahub/core/validators/node/relationship.py +2 -2
- infrahub/core/validators/relationship/count.py +4 -4
- infrahub/core/validators/relationship/optional.py +2 -2
- infrahub/core/validators/relationship/peer.py +2 -2
- infrahub/core/validators/shared.py +2 -2
- infrahub/core/validators/tasks.py +8 -0
- infrahub/core/validators/uniqueness/checker.py +22 -21
- infrahub/core/validators/uniqueness/index.py +2 -2
- infrahub/core/validators/uniqueness/model.py +11 -11
- infrahub/database/__init__.py +27 -22
- infrahub/database/metrics.py +7 -1
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
- infrahub/dependencies/builder/diff/deserializer.py +3 -1
- infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
- infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
- infrahub/dependencies/component/registry.py +2 -2
- infrahub/events/__init__.py +25 -2
- infrahub/events/artifact_action.py +64 -0
- infrahub/events/branch_action.py +33 -22
- infrahub/events/generator.py +71 -0
- infrahub/events/group_action.py +51 -21
- infrahub/events/models.py +18 -19
- infrahub/events/node_action.py +88 -37
- infrahub/events/repository_action.py +5 -18
- infrahub/events/schema_action.py +4 -9
- infrahub/events/utils.py +16 -0
- infrahub/events/validator_action.py +55 -0
- infrahub/exceptions.py +32 -24
- infrahub/generators/models.py +2 -3
- infrahub/generators/tasks.py +24 -4
- infrahub/git/base.py +7 -7
- infrahub/git/integrator.py +48 -24
- infrahub/git/models.py +101 -9
- infrahub/git/repository.py +3 -3
- infrahub/git/tasks.py +408 -6
- infrahub/git/utils.py +48 -0
- infrahub/git/worktree.py +1 -2
- infrahub/git_credential/askpass.py +1 -2
- infrahub/graphql/analyzer.py +12 -0
- infrahub/graphql/app.py +13 -15
- infrahub/graphql/context.py +39 -0
- infrahub/graphql/initialization.py +3 -0
- infrahub/graphql/loaders/node.py +2 -12
- infrahub/graphql/loaders/peers.py +77 -0
- infrahub/graphql/loaders/shared.py +13 -0
- infrahub/graphql/manager.py +17 -19
- infrahub/graphql/mutations/artifact_definition.py +5 -5
- infrahub/graphql/mutations/branch.py +26 -1
- infrahub/graphql/mutations/computed_attribute.py +9 -5
- infrahub/graphql/mutations/diff.py +23 -11
- infrahub/graphql/mutations/diff_conflict.py +5 -0
- infrahub/graphql/mutations/generator.py +83 -0
- infrahub/graphql/mutations/graphql_query.py +5 -5
- infrahub/graphql/mutations/ipam.py +54 -74
- infrahub/graphql/mutations/main.py +195 -132
- infrahub/graphql/mutations/menu.py +7 -7
- infrahub/graphql/mutations/models.py +2 -4
- infrahub/graphql/mutations/node_getter/by_default_filter.py +10 -10
- infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
- infrahub/graphql/mutations/node_getter/by_id.py +1 -3
- infrahub/graphql/mutations/node_getter/interface.py +1 -2
- infrahub/graphql/mutations/proposed_change.py +7 -7
- infrahub/graphql/mutations/relationship.py +93 -19
- infrahub/graphql/mutations/repository.py +8 -8
- infrahub/graphql/mutations/resource_manager.py +3 -3
- infrahub/graphql/mutations/schema.py +19 -4
- infrahub/graphql/mutations/webhook.py +137 -0
- infrahub/graphql/parser.py +4 -4
- infrahub/graphql/permissions.py +1 -10
- infrahub/graphql/queries/diff/tree.py +19 -14
- infrahub/graphql/queries/event.py +5 -2
- infrahub/graphql/queries/ipam.py +2 -2
- infrahub/graphql/queries/relationship.py +2 -2
- infrahub/graphql/queries/search.py +2 -2
- infrahub/graphql/resolvers/many_relationship.py +264 -0
- infrahub/graphql/resolvers/resolver.py +13 -110
- infrahub/graphql/schema.py +2 -0
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/context.py +12 -0
- infrahub/graphql/types/event.py +84 -17
- infrahub/graphql/types/node.py +2 -2
- infrahub/graphql/utils.py +2 -2
- infrahub/groups/ancestors.py +29 -0
- infrahub/groups/parsers.py +107 -0
- infrahub/lock.py +20 -20
- infrahub/menu/constants.py +0 -1
- infrahub/menu/generator.py +9 -21
- infrahub/menu/menu.py +17 -38
- infrahub/menu/models.py +117 -16
- infrahub/menu/repository.py +111 -0
- infrahub/menu/utils.py +5 -8
- infrahub/message_bus/__init__.py +11 -13
- infrahub/message_bus/messages/__init__.py +1 -21
- infrahub/message_bus/messages/check_generator_run.py +3 -3
- infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
- infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +6 -0
- infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
- infrahub/message_bus/messages/send_echo_request.py +1 -1
- infrahub/message_bus/operations/__init__.py +1 -10
- infrahub/message_bus/operations/check/__init__.py +2 -2
- infrahub/message_bus/operations/check/generator.py +1 -0
- infrahub/message_bus/operations/event/__init__.py +2 -2
- infrahub/message_bus/operations/event/worker.py +0 -3
- infrahub/message_bus/operations/finalize/validator.py +51 -1
- infrahub/message_bus/operations/requests/__init__.py +0 -2
- infrahub/message_bus/operations/requests/generator_definition.py +21 -23
- infrahub/message_bus/operations/requests/proposed_change.py +14 -10
- infrahub/permissions/globals.py +15 -0
- infrahub/pools/number.py +2 -4
- infrahub/proposed_change/models.py +3 -0
- infrahub/proposed_change/tasks.py +58 -45
- infrahub/pytest_plugin.py +13 -10
- infrahub/server.py +2 -3
- infrahub/services/__init__.py +2 -2
- infrahub/services/adapters/cache/__init__.py +4 -6
- infrahub/services/adapters/cache/nats.py +4 -5
- infrahub/services/adapters/cache/redis.py +3 -7
- infrahub/services/adapters/event/__init__.py +1 -1
- infrahub/services/adapters/message_bus/__init__.py +3 -3
- infrahub/services/adapters/message_bus/local.py +2 -2
- infrahub/services/adapters/message_bus/nats.py +4 -4
- infrahub/services/adapters/message_bus/rabbitmq.py +4 -4
- infrahub/services/adapters/workflow/local.py +2 -2
- infrahub/services/component.py +5 -5
- infrahub/services/protocols.py +7 -7
- infrahub/services/scheduler.py +1 -3
- infrahub/task_manager/event.py +102 -9
- infrahub/task_manager/models.py +27 -7
- infrahub/tasks/artifact.py +7 -6
- infrahub/telemetry/__init__.py +0 -0
- infrahub/telemetry/constants.py +9 -0
- infrahub/telemetry/database.py +86 -0
- infrahub/telemetry/models.py +65 -0
- infrahub/telemetry/task_manager.py +77 -0
- infrahub/{tasks/telemetry.py → telemetry/tasks.py} +49 -56
- infrahub/telemetry/utils.py +11 -0
- infrahub/trace.py +4 -4
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +4 -6
- infrahub/trigger/constants.py +0 -8
- infrahub/trigger/models.py +54 -5
- infrahub/trigger/setup.py +90 -0
- infrahub/trigger/tasks.py +35 -84
- infrahub/utils.py +11 -1
- infrahub/validators/__init__.py +0 -0
- infrahub/validators/events.py +42 -0
- infrahub/validators/tasks.py +41 -0
- infrahub/webhook/gather.py +17 -0
- infrahub/webhook/models.py +176 -44
- infrahub/webhook/tasks.py +154 -155
- infrahub/webhook/triggers.py +31 -7
- infrahub/workers/infrahub_async.py +2 -2
- infrahub/workers/utils.py +2 -2
- infrahub/workflows/catalogue.py +86 -35
- infrahub/workflows/initialization.py +8 -2
- infrahub/workflows/models.py +27 -1
- infrahub/workflows/utils.py +10 -1
- infrahub_sdk/client.py +35 -8
- infrahub_sdk/config.py +3 -0
- infrahub_sdk/context.py +13 -0
- infrahub_sdk/ctl/branch.py +3 -2
- infrahub_sdk/ctl/cli_commands.py +5 -1
- infrahub_sdk/ctl/utils.py +0 -16
- infrahub_sdk/exceptions.py +12 -0
- infrahub_sdk/generator.py +4 -1
- infrahub_sdk/graphql.py +45 -13
- infrahub_sdk/node.py +71 -22
- infrahub_sdk/protocols.py +21 -8
- infrahub_sdk/protocols_base.py +32 -11
- infrahub_sdk/query_groups.py +6 -35
- infrahub_sdk/schema/__init__.py +55 -26
- infrahub_sdk/schema/main.py +8 -0
- infrahub_sdk/task/__init__.py +11 -0
- infrahub_sdk/task/constants.py +3 -0
- infrahub_sdk/task/exceptions.py +25 -0
- infrahub_sdk/task/manager.py +551 -0
- infrahub_sdk/task/models.py +74 -0
- infrahub_sdk/testing/schemas/animal.py +9 -0
- infrahub_sdk/timestamp.py +142 -33
- infrahub_sdk/utils.py +29 -1
- {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +8 -6
- {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +349 -293
- {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/entry_points.txt +1 -0
- infrahub_testcontainers/constants.py +2 -0
- infrahub_testcontainers/container.py +157 -12
- infrahub_testcontainers/docker-compose.test.yml +31 -6
- infrahub_testcontainers/helpers.py +18 -73
- infrahub_testcontainers/host.py +41 -0
- infrahub_testcontainers/measurements.py +93 -0
- infrahub_testcontainers/models.py +38 -0
- infrahub_testcontainers/performance_test.py +166 -0
- infrahub_testcontainers/plugin.py +136 -0
- infrahub_testcontainers/prometheus.yml +30 -0
- infrahub/core/schema/definitions/core.py +0 -2286
- infrahub/message_bus/messages/check_repository_checkdefinition.py +0 -20
- infrahub/message_bus/messages/check_repository_mergeconflicts.py +0 -16
- infrahub/message_bus/messages/check_repository_usercheck.py +0 -26
- infrahub/message_bus/messages/event_branch_create.py +0 -11
- infrahub/message_bus/messages/event_branch_delete.py +0 -11
- infrahub/message_bus/messages/event_branch_rebased.py +0 -9
- infrahub/message_bus/messages/event_node_mutated.py +0 -15
- infrahub/message_bus/messages/event_schema_update.py +0 -9
- infrahub/message_bus/messages/request_repository_checks.py +0 -12
- infrahub/message_bus/messages/request_repository_userchecks.py +0 -18
- infrahub/message_bus/operations/check/repository.py +0 -293
- infrahub/message_bus/operations/event/node.py +0 -20
- infrahub/message_bus/operations/event/schema.py +0 -17
- infrahub/message_bus/operations/requests/repository.py +0 -133
- infrahub/webhook/constants.py +0 -1
- {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.0rc0.dist-info → infrahub_server-1.2.1.dist-info}/WHEEL +0 -0
|
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import copy
|
|
4
4
|
import hashlib
|
|
5
5
|
from collections import defaultdict
|
|
6
|
-
from itertools import chain
|
|
7
|
-
from typing import
|
|
6
|
+
from itertools import chain, combinations
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from infrahub_sdk.topological_sort import DependencyCycleExistsError, topological_sort
|
|
10
10
|
from infrahub_sdk.utils import compare_lists, deep_merge_dict, duplicates, intersection
|
|
@@ -13,6 +13,7 @@ from typing_extensions import Self
|
|
|
13
13
|
from infrahub.computed_attribute.constants import VALID_KINDS as VALID_COMPUTED_ATTRIBUTE_KINDS
|
|
14
14
|
from infrahub.core.constants import (
|
|
15
15
|
OBJECT_TEMPLATE_NAME_ATTR,
|
|
16
|
+
OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
16
17
|
RESERVED_ATTR_GEN_NAMES,
|
|
17
18
|
RESERVED_ATTR_REL_NAMES,
|
|
18
19
|
RESTRICTED_NAMESPACES,
|
|
@@ -55,14 +56,13 @@ from infrahub.types import ATTRIBUTE_TYPES
|
|
|
55
56
|
from infrahub.utils import format_label
|
|
56
57
|
from infrahub.visuals import select_color
|
|
57
58
|
|
|
59
|
+
from ... import config
|
|
60
|
+
from ..constants.schema import PARENT_CHILD_IDENTIFIER
|
|
58
61
|
from .constants import INTERNAL_SCHEMA_NODE_KINDS, SchemaNamespace
|
|
59
62
|
from .schema_branch_computed import ComputedAttributes
|
|
60
63
|
|
|
61
64
|
log = get_logger()
|
|
62
65
|
|
|
63
|
-
if TYPE_CHECKING:
|
|
64
|
-
from pydantic import ValidationInfo
|
|
65
|
-
|
|
66
66
|
|
|
67
67
|
class SchemaBranch:
|
|
68
68
|
def __init__(
|
|
@@ -72,7 +72,7 @@ class SchemaBranch:
|
|
|
72
72
|
data: dict[str, dict[str, str]] | None = None,
|
|
73
73
|
computed_attributes: ComputedAttributes | None = None,
|
|
74
74
|
):
|
|
75
|
-
self._cache: dict[str,
|
|
75
|
+
self._cache: dict[str, NodeSchema | GenericSchema] = cache
|
|
76
76
|
self.name: str | None = name
|
|
77
77
|
self.nodes: dict[str, str] = {}
|
|
78
78
|
self.generics: dict[str, str] = {}
|
|
@@ -87,15 +87,11 @@ class SchemaBranch:
|
|
|
87
87
|
self.templates = data.get("templates", {})
|
|
88
88
|
|
|
89
89
|
@classmethod
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if isinstance(v, cls):
|
|
96
|
-
return v
|
|
97
|
-
if isinstance(v, dict):
|
|
98
|
-
return cls.from_dict_schema_object(data=v)
|
|
90
|
+
def validate(cls, data: Any) -> Self: # noqa: ARG003
|
|
91
|
+
if isinstance(data, cls):
|
|
92
|
+
return data
|
|
93
|
+
if isinstance(data, dict):
|
|
94
|
+
return cls.from_dict_schema_object(data=data)
|
|
99
95
|
raise ValueError("must be a class or a dict")
|
|
100
96
|
|
|
101
97
|
@property
|
|
@@ -106,6 +102,10 @@ class SchemaBranch:
|
|
|
106
102
|
def generic_names(self) -> list[str]:
|
|
107
103
|
return list(self.generics.keys())
|
|
108
104
|
|
|
105
|
+
@property
|
|
106
|
+
def generic_names_without_templates(self) -> list[str]:
|
|
107
|
+
return [g for g in self.generic_names if not g.startswith("Template")]
|
|
108
|
+
|
|
109
109
|
@property
|
|
110
110
|
def profile_names(self) -> list[str]:
|
|
111
111
|
return list(self.profiles.keys())
|
|
@@ -114,10 +114,10 @@ class SchemaBranch:
|
|
|
114
114
|
def template_names(self) -> list[str]:
|
|
115
115
|
return list(self.templates.keys())
|
|
116
116
|
|
|
117
|
-
def get_all_kind_id_map(self,
|
|
117
|
+
def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str]:
|
|
118
118
|
kind_id_map = {}
|
|
119
|
-
if
|
|
120
|
-
names = self.node_names + self.
|
|
119
|
+
if nodes_and_generics_only:
|
|
120
|
+
names = self.node_names + self.generic_names_without_templates
|
|
121
121
|
else:
|
|
122
122
|
names = self.all_names
|
|
123
123
|
for name in names:
|
|
@@ -181,8 +181,8 @@ class SchemaBranch:
|
|
|
181
181
|
|
|
182
182
|
def diff(self, other: SchemaBranch) -> SchemaDiff:
|
|
183
183
|
# Identify the nodes or generics that have been added or removed
|
|
184
|
-
local_kind_id_map = self.get_all_kind_id_map(
|
|
185
|
-
other_kind_id_map = other.get_all_kind_id_map(
|
|
184
|
+
local_kind_id_map = self.get_all_kind_id_map(nodes_and_generics_only=True)
|
|
185
|
+
other_kind_id_map = other.get_all_kind_id_map(nodes_and_generics_only=True)
|
|
186
186
|
clean_local_ids = [id for id in local_kind_id_map.values() if id is not None]
|
|
187
187
|
clean_other_ids = [id for id in other_kind_id_map.values() if id is not None]
|
|
188
188
|
shared_ids = intersection(list1=clean_local_ids, list2=clean_other_ids)
|
|
@@ -236,12 +236,6 @@ class SchemaBranch:
|
|
|
236
236
|
other_item = schema.get(name=item_kind)
|
|
237
237
|
self.set(name=item_kind, schema=other_item)
|
|
238
238
|
|
|
239
|
-
# for item_kind in local_only:
|
|
240
|
-
# if item_kind in self.nodes:
|
|
241
|
-
# del self.nodes[item_kind]
|
|
242
|
-
# else:
|
|
243
|
-
# del self.generics[item_kind]
|
|
244
|
-
|
|
245
239
|
def validate_node_deletions(self, diff: SchemaDiff) -> None:
|
|
246
240
|
"""Given a diff, check if a deleted node is still used in relationships of other nodes."""
|
|
247
241
|
removed_schema_names = set(diff.removed.keys())
|
|
@@ -263,7 +257,7 @@ class SchemaBranch:
|
|
|
263
257
|
result.validate_all(migration_map=MIGRATION_MAP, validator_map=CONSTRAINT_VALIDATOR_MAP)
|
|
264
258
|
return result
|
|
265
259
|
|
|
266
|
-
def duplicate(self, name:
|
|
260
|
+
def duplicate(self, name: str | None = None) -> SchemaBranch:
|
|
267
261
|
"""Duplicate the current object but conserve the same cache."""
|
|
268
262
|
return self.__class__(
|
|
269
263
|
name=name,
|
|
@@ -433,7 +427,7 @@ class SchemaBranch:
|
|
|
433
427
|
return list(namespaces.values())
|
|
434
428
|
|
|
435
429
|
def get_schemas_for_namespaces(
|
|
436
|
-
self, namespaces:
|
|
430
|
+
self, namespaces: list[str] | None = None, include_internal: bool = False
|
|
437
431
|
) -> list[MainSchemaTypes]:
|
|
438
432
|
"""Retrive everything in a single dictionary."""
|
|
439
433
|
all_schemas = self.get_all(include_internal=include_internal, duplicate=False)
|
|
@@ -450,12 +444,12 @@ class SchemaBranch:
|
|
|
450
444
|
nodes.append(self.get(name=node_name, duplicate=True))
|
|
451
445
|
return nodes
|
|
452
446
|
|
|
453
|
-
def generate_fields_for_display_label(self, name: str) ->
|
|
447
|
+
def generate_fields_for_display_label(self, name: str) -> dict | None:
|
|
454
448
|
node = self.get(name=name, duplicate=False)
|
|
455
449
|
if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
|
|
456
450
|
return node.generate_fields_for_display_label()
|
|
457
451
|
|
|
458
|
-
fields: dict[str,
|
|
452
|
+
fields: dict[str, str | None | dict[str, None]] = {}
|
|
459
453
|
if isinstance(node, GenericSchema):
|
|
460
454
|
for child_node_name in node.used_by:
|
|
461
455
|
child_node = self.get(name=child_node_name, duplicate=False)
|
|
@@ -620,7 +614,7 @@ class SchemaBranch:
|
|
|
620
614
|
node_schema: BaseNodeSchema,
|
|
621
615
|
path: str,
|
|
622
616
|
allowed_path_types: SchemaElementPathType,
|
|
623
|
-
element_name:
|
|
617
|
+
element_name: str | None = None,
|
|
624
618
|
) -> SchemaAttributePath:
|
|
625
619
|
error_header = f"{node_schema.kind}"
|
|
626
620
|
error_header += f".{element_name}" if element_name else ""
|
|
@@ -686,7 +680,7 @@ class SchemaBranch:
|
|
|
686
680
|
return schema_attribute_path
|
|
687
681
|
|
|
688
682
|
def sync_uniqueness_constraints_and_unique_attributes(self) -> None:
|
|
689
|
-
for name in self.
|
|
683
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
690
684
|
node_schema = self.get(name=name, duplicate=False)
|
|
691
685
|
|
|
692
686
|
if not node_schema.unique_attributes and not node_schema.uniqueness_constraints:
|
|
@@ -722,10 +716,12 @@ class SchemaBranch:
|
|
|
722
716
|
for attr_name in attrs_to_make_unique:
|
|
723
717
|
attr_schema = node_schema.get_attribute(name=attr_name)
|
|
724
718
|
attr_schema.unique = True
|
|
719
|
+
|
|
725
720
|
if attrs_to_add_to_constraints:
|
|
726
721
|
node_schema.uniqueness_constraints = (node_schema.uniqueness_constraints or []) + sorted(
|
|
727
722
|
[[f"{attr_name}__value"] for attr_name in attrs_to_add_to_constraints]
|
|
728
723
|
)
|
|
724
|
+
|
|
729
725
|
self.set(name=name, schema=node_schema)
|
|
730
726
|
|
|
731
727
|
def validate_uniqueness_constraints(self) -> None:
|
|
@@ -801,7 +797,7 @@ class SchemaBranch:
|
|
|
801
797
|
)
|
|
802
798
|
|
|
803
799
|
def validate_default_values(self) -> None:
|
|
804
|
-
for name in self.
|
|
800
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
805
801
|
node_schema = self.get(name=name, duplicate=False)
|
|
806
802
|
for node_attr in node_schema.local_attributes:
|
|
807
803
|
if node_attr.default_value is None:
|
|
@@ -820,15 +816,35 @@ class SchemaBranch:
|
|
|
820
816
|
f"{node_schema.namespace}{node_schema.name}: default value {exc.message}"
|
|
821
817
|
) from exc
|
|
822
818
|
|
|
819
|
+
def _is_attr_combination_unique(
|
|
820
|
+
self, attrs_paths: list[str], uniqueness_constraints: list[list[str]] | None
|
|
821
|
+
) -> bool:
|
|
822
|
+
"""
|
|
823
|
+
Return whether at least one combination of any length of `attrs_paths` is equal to a uniqueness constraint.
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
if not uniqueness_constraints:
|
|
827
|
+
return False
|
|
828
|
+
|
|
829
|
+
unique_constraint_group_sets = [set(ucg) for ucg in uniqueness_constraints]
|
|
830
|
+
for i in range(1, len(attrs_paths) + 1):
|
|
831
|
+
for attr_combo in combinations(attrs_paths, i):
|
|
832
|
+
if any(ucg == set(attr_combo) for ucg in unique_constraint_group_sets):
|
|
833
|
+
return True
|
|
834
|
+
return False
|
|
835
|
+
|
|
823
836
|
def validate_human_friendly_id(self) -> None:
|
|
824
|
-
for name in self.
|
|
837
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
825
838
|
node_schema = self.get(name=name, duplicate=False)
|
|
826
|
-
hf_attr_names = set()
|
|
827
839
|
|
|
828
840
|
if not node_schema.human_friendly_id:
|
|
829
841
|
continue
|
|
830
842
|
|
|
831
843
|
allowed_types = SchemaElementPathType.ATTR_WITH_PROP | SchemaElementPathType.REL_ONE_MANDATORY_ATTR
|
|
844
|
+
|
|
845
|
+
# Mapping relationship identifiers -> list of attributes paths
|
|
846
|
+
rel_schemas_to_paths: dict[str, tuple[MainSchemaTypes, list[str]]] = {}
|
|
847
|
+
|
|
832
848
|
for hfid_path in node_schema.human_friendly_id:
|
|
833
849
|
schema_path = self.validate_schema_path(
|
|
834
850
|
node_schema=node_schema,
|
|
@@ -837,12 +853,24 @@ class SchemaBranch:
|
|
|
837
853
|
element_name="human_friendly_id",
|
|
838
854
|
)
|
|
839
855
|
|
|
840
|
-
if schema_path.
|
|
841
|
-
|
|
856
|
+
if schema_path.is_type_relationship:
|
|
857
|
+
# Construct the name without relationship prefix to match with how it would be defined in peer schema uniqueness constraint
|
|
858
|
+
rel_identifier = schema_path.relationship_schema.identifier
|
|
859
|
+
if rel_identifier not in rel_schemas_to_paths:
|
|
860
|
+
rel_schemas_to_paths[rel_identifier] = (schema_path.related_schema, [])
|
|
861
|
+
rel_schemas_to_paths[rel_identifier][1].append(schema_path.attribute_path_as_str)
|
|
862
|
+
|
|
863
|
+
if config.SETTINGS.main.schema_strict_mode:
|
|
864
|
+
# For every relationship referred within hfid, check whether the combination of attributes is unique is the peer schema node
|
|
865
|
+
for related_schema, attrs_paths in rel_schemas_to_paths.values():
|
|
866
|
+
if not self._is_attr_combination_unique(attrs_paths, related_schema.uniqueness_constraints):
|
|
867
|
+
raise ValidationError(
|
|
868
|
+
f"HFID of {node_schema.kind} refers peer {related_schema.kind} with a non-unique combination of attributes {attrs_paths}"
|
|
869
|
+
)
|
|
842
870
|
|
|
843
871
|
def validate_required_relationships(self) -> None:
|
|
844
872
|
reverse_dependency_map: dict[str, set[str]] = {}
|
|
845
|
-
for name in self.node_names + self.
|
|
873
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
846
874
|
node_schema = self.get(name=name, duplicate=False)
|
|
847
875
|
for relationship_schema in node_schema.relationships:
|
|
848
876
|
if relationship_schema.optional:
|
|
@@ -860,7 +888,7 @@ class SchemaBranch:
|
|
|
860
888
|
def validate_parent_component(self) -> None:
|
|
861
889
|
# {parent_kind: {component_kind_1, component_kind_2, ...}}
|
|
862
890
|
dependency_map: dict[str, set[str]] = defaultdict(set)
|
|
863
|
-
for name in self.
|
|
891
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
864
892
|
node_schema = self.get(name=name, duplicate=False)
|
|
865
893
|
|
|
866
894
|
parent_relationships: list[RelationshipSchema] = []
|
|
@@ -899,7 +927,7 @@ class SchemaBranch:
|
|
|
899
927
|
raise ValueError(f"Cycles exist among parents and components in schema: {exc.get_cycle_strings()}") from exc
|
|
900
928
|
|
|
901
929
|
def _validate_parents_one_schema(
|
|
902
|
-
self, node_schema:
|
|
930
|
+
self, node_schema: NodeSchema | GenericSchema, parent_relationships: list[RelationshipSchema]
|
|
903
931
|
) -> None:
|
|
904
932
|
if not parent_relationships:
|
|
905
933
|
return
|
|
@@ -960,9 +988,9 @@ class SchemaBranch:
|
|
|
960
988
|
for rel in node.relationships:
|
|
961
989
|
if rel.peer in [InfrahubKind.GENERICGROUP]:
|
|
962
990
|
continue
|
|
963
|
-
if not self.has(rel.peer):
|
|
991
|
+
if not self.has(rel.peer) or self.get(rel.peer, duplicate=False).state == HashableModelState.ABSENT:
|
|
964
992
|
raise ValueError(
|
|
965
|
-
f"{node.kind}: Relationship {rel.name!r} is
|
|
993
|
+
f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
|
|
966
994
|
) from None
|
|
967
995
|
|
|
968
996
|
def validate_computed_attributes(self) -> None:
|
|
@@ -1129,7 +1157,7 @@ class SchemaBranch:
|
|
|
1129
1157
|
for name in self.all_names:
|
|
1130
1158
|
node = self.get(name=name, duplicate=False)
|
|
1131
1159
|
|
|
1132
|
-
schema_to_update:
|
|
1160
|
+
schema_to_update: NodeSchema | GenericSchema | None = None
|
|
1133
1161
|
for relationship in node.relationships:
|
|
1134
1162
|
if relationship.on_delete is not None:
|
|
1135
1163
|
continue
|
|
@@ -1146,49 +1174,50 @@ class SchemaBranch:
|
|
|
1146
1174
|
self.set(name=schema_to_update.kind, schema=schema_to_update)
|
|
1147
1175
|
|
|
1148
1176
|
def process_human_friendly_id(self) -> None:
|
|
1149
|
-
|
|
1177
|
+
"""
|
|
1178
|
+
For each schema node, if there is no HFID defined, set it with:
|
|
1179
|
+
- The first unique attribute if existing
|
|
1180
|
+
- Otherwise the first uniqueness constraint with a single attribute
|
|
1181
|
+
|
|
1182
|
+
Also, HFID is added to the uniqueness constraints.
|
|
1183
|
+
"""
|
|
1184
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
1150
1185
|
node = self.get(name=name, duplicate=False)
|
|
1151
1186
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
# If human_friendly_id IS defined
|
|
1155
|
-
# but no unique attributes and no uniquess constraints, we add a uniqueness_constraint
|
|
1156
|
-
if not node.human_friendly_id and node.unique_attributes:
|
|
1157
|
-
for attr in node.unique_attributes:
|
|
1187
|
+
if not node.human_friendly_id:
|
|
1188
|
+
if node.unique_attributes:
|
|
1158
1189
|
node = self.get(name=name, duplicate=True)
|
|
1159
|
-
node.human_friendly_id = [f"{
|
|
1190
|
+
node.human_friendly_id = [f"{node.unique_attributes[0].name}__value"]
|
|
1160
1191
|
self.set(name=node.kind, schema=node)
|
|
1161
|
-
break
|
|
1162
|
-
continue
|
|
1163
1192
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
uniqueness_constraints.append(
|
|
1190
|
-
|
|
1191
|
-
|
|
1193
|
+
# if no human_friendly_id and a uniqueness_constraint with a single attribute exists
|
|
1194
|
+
# then use that attribute as the human_friendly_id
|
|
1195
|
+
elif node.uniqueness_constraints:
|
|
1196
|
+
for constraint_paths in node.uniqueness_constraints:
|
|
1197
|
+
if len(constraint_paths) > 1:
|
|
1198
|
+
continue
|
|
1199
|
+
constraint_path = constraint_paths[0]
|
|
1200
|
+
schema_path = node.parse_schema_path(path=constraint_path, schema=node)
|
|
1201
|
+
if (
|
|
1202
|
+
schema_path.is_type_attribute
|
|
1203
|
+
and schema_path.attribute_property_name == "value"
|
|
1204
|
+
and schema_path.attribute_schema
|
|
1205
|
+
):
|
|
1206
|
+
node = self.get(name=name, duplicate=True)
|
|
1207
|
+
node.human_friendly_id = [f"{schema_path.attribute_schema.name}__value"]
|
|
1208
|
+
self.set(name=node.kind, schema=node)
|
|
1209
|
+
break
|
|
1210
|
+
|
|
1211
|
+
# Add hfid to uniqueness constraint
|
|
1212
|
+
hfid_uniqueness_constraint = node.convert_hfid_to_uniqueness_constraint(schema_branch=self)
|
|
1213
|
+
if hfid_uniqueness_constraint:
|
|
1214
|
+
node = self.get(name=name, duplicate=True)
|
|
1215
|
+
# Make sure there is no duplicate regarding generics values.
|
|
1216
|
+
if node.uniqueness_constraints:
|
|
1217
|
+
if hfid_uniqueness_constraint not in node.uniqueness_constraints:
|
|
1218
|
+
node.uniqueness_constraints.append(hfid_uniqueness_constraint)
|
|
1219
|
+
else:
|
|
1220
|
+
node.uniqueness_constraints = [hfid_uniqueness_constraint]
|
|
1192
1221
|
self.set(name=node.kind, schema=node)
|
|
1193
1222
|
|
|
1194
1223
|
def process_hierarchy(self) -> None:
|
|
@@ -1208,7 +1237,7 @@ class SchemaBranch:
|
|
|
1208
1237
|
node = node.duplicate()
|
|
1209
1238
|
changed = False
|
|
1210
1239
|
|
|
1211
|
-
if node.hierarchy not in node.inherit_from:
|
|
1240
|
+
if node.hierarchy and node.hierarchy not in node.inherit_from:
|
|
1212
1241
|
node.inherit_from.append(node.hierarchy)
|
|
1213
1242
|
changed = True
|
|
1214
1243
|
|
|
@@ -1227,6 +1256,23 @@ class SchemaBranch:
|
|
|
1227
1256
|
if changed:
|
|
1228
1257
|
self.set(name=name, schema=node)
|
|
1229
1258
|
|
|
1259
|
+
def _get_generic_fields_map(
|
|
1260
|
+
self, node_schema: MainSchemaTypes
|
|
1261
|
+
) -> dict[str, tuple[GenericSchema, AttributeSchema | RelationshipSchema]]:
|
|
1262
|
+
generic_fields_map: dict[str, tuple[GenericSchema, AttributeSchema | RelationshipSchema]] = {}
|
|
1263
|
+
if isinstance(node_schema, NodeSchema) and node_schema.inherit_from:
|
|
1264
|
+
for generic_kind in node_schema.inherit_from:
|
|
1265
|
+
generic_schema = self.get_generic(name=generic_kind, duplicate=False)
|
|
1266
|
+
for generic_attr in generic_schema.attributes:
|
|
1267
|
+
if generic_attr.name in node_schema.attribute_names:
|
|
1268
|
+
generic_fields_map[generic_attr.name] = (generic_schema, generic_attr)
|
|
1269
|
+
continue
|
|
1270
|
+
for generic_rel in generic_schema.relationships:
|
|
1271
|
+
if generic_rel.name in node_schema.relationship_names:
|
|
1272
|
+
generic_fields_map[generic_rel.name] = (generic_schema, generic_rel)
|
|
1273
|
+
continue
|
|
1274
|
+
return generic_fields_map
|
|
1275
|
+
|
|
1230
1276
|
def process_inheritance(self) -> None:
|
|
1231
1277
|
"""Extend all the nodes with the attributes and relationships
|
|
1232
1278
|
from the Interface objects defined in inherited_from.
|
|
@@ -1279,7 +1325,7 @@ class SchemaBranch:
|
|
|
1279
1325
|
|
|
1280
1326
|
# Update all generics with the list of nodes referrencing them.
|
|
1281
1327
|
for generic_name in self.generics.keys():
|
|
1282
|
-
generic = self.
|
|
1328
|
+
generic = self.get_generic(name=generic_name)
|
|
1283
1329
|
|
|
1284
1330
|
if generic.kind in generics_used_by:
|
|
1285
1331
|
generic.used_by = sorted(generics_used_by[generic.kind])
|
|
@@ -1297,40 +1343,46 @@ class SchemaBranch:
|
|
|
1297
1343
|
for name in self.all_names:
|
|
1298
1344
|
node = self.get(name=name, duplicate=False)
|
|
1299
1345
|
|
|
1300
|
-
|
|
1301
|
-
change_required = False
|
|
1302
|
-
for attr in node.attributes:
|
|
1303
|
-
if attr.branch is None:
|
|
1304
|
-
change_required = True
|
|
1305
|
-
break
|
|
1306
|
-
if not change_required:
|
|
1307
|
-
for rel in node.relationships:
|
|
1308
|
-
if rel.branch is None:
|
|
1309
|
-
change_required = True
|
|
1310
|
-
break
|
|
1311
|
-
|
|
1312
|
-
if not change_required:
|
|
1313
|
-
continue
|
|
1314
|
-
|
|
1315
|
-
node = node.duplicate()
|
|
1346
|
+
generic_fields_map = self._get_generic_fields_map(node_schema=node)
|
|
1316
1347
|
|
|
1348
|
+
attrs_to_update: dict[str, BranchSupportType] = {}
|
|
1317
1349
|
for attr in node.attributes:
|
|
1318
|
-
if attr.
|
|
1319
|
-
|
|
1350
|
+
if attr.inherited and attr.name in generic_fields_map:
|
|
1351
|
+
generic_schema, generic_attr = generic_fields_map[attr.name]
|
|
1352
|
+
if attr.branch == generic_schema.branch == generic_attr.branch != node.branch:
|
|
1353
|
+
attrs_to_update[attr.name] = node.branch
|
|
1320
1354
|
|
|
1321
|
-
attr.branch
|
|
1355
|
+
if attr.branch is None:
|
|
1356
|
+
attrs_to_update[attr.name] = node.branch
|
|
1322
1357
|
|
|
1358
|
+
rels_to_update: dict[str, BranchSupportType] = {}
|
|
1323
1359
|
for rel in node.relationships:
|
|
1324
|
-
if rel.branch is not None:
|
|
1360
|
+
if not rel.inherited and rel.branch is not None:
|
|
1325
1361
|
continue
|
|
1362
|
+
needs_update = rel.branch is None
|
|
1363
|
+
if needs_update is False and rel.inherited and rel.name in generic_fields_map:
|
|
1364
|
+
generic_schema, generic_rel = generic_fields_map[rel.name]
|
|
1365
|
+
if rel.branch == generic_schema.branch == generic_rel.branch != node.branch:
|
|
1366
|
+
needs_update = True
|
|
1367
|
+
if needs_update:
|
|
1368
|
+
peer_node = self.get(name=rel.peer, duplicate=False)
|
|
1369
|
+
if node.branch == peer_node.branch:
|
|
1370
|
+
rels_to_update[rel.name] = node.branch
|
|
1371
|
+
elif BranchSupportType.LOCAL in (node.branch, peer_node.branch):
|
|
1372
|
+
rels_to_update[rel.name] = BranchSupportType.LOCAL
|
|
1373
|
+
else:
|
|
1374
|
+
rels_to_update[rel.name] = BranchSupportType.AWARE
|
|
1375
|
+
|
|
1376
|
+
if not attrs_to_update and not rels_to_update:
|
|
1377
|
+
continue
|
|
1326
1378
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1379
|
+
node = node.duplicate()
|
|
1380
|
+
for node_attr in node.attributes:
|
|
1381
|
+
if node_attr.name in attrs_to_update:
|
|
1382
|
+
node_attr.branch = attrs_to_update[node_attr.name]
|
|
1383
|
+
for node_rel in node.relationships:
|
|
1384
|
+
if node_rel.name in rels_to_update:
|
|
1385
|
+
node_rel.branch = rels_to_update[node_rel.name]
|
|
1334
1386
|
|
|
1335
1387
|
self.set(name=name, schema=node)
|
|
1336
1388
|
|
|
@@ -1423,14 +1475,15 @@ class SchemaBranch:
|
|
|
1423
1475
|
self.set(name=name, schema=node)
|
|
1424
1476
|
|
|
1425
1477
|
def generate_weight(self) -> None:
|
|
1426
|
-
for name in self.
|
|
1478
|
+
for name in self.generic_names:
|
|
1427
1479
|
node = self.get(name=name, duplicate=False)
|
|
1480
|
+
|
|
1428
1481
|
items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
|
|
1482
|
+
|
|
1429
1483
|
if not items_to_update:
|
|
1430
1484
|
continue
|
|
1431
1485
|
|
|
1432
1486
|
node = node.duplicate()
|
|
1433
|
-
|
|
1434
1487
|
current_weight = 0
|
|
1435
1488
|
for item in node.attributes + node.relationships:
|
|
1436
1489
|
current_weight += 1000
|
|
@@ -1439,6 +1492,30 @@ class SchemaBranch:
|
|
|
1439
1492
|
|
|
1440
1493
|
self.set(name=name, schema=node)
|
|
1441
1494
|
|
|
1495
|
+
for name in self.node_names + self.profile_names:
|
|
1496
|
+
node = self.get(name=name, duplicate=False)
|
|
1497
|
+
|
|
1498
|
+
items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
|
|
1499
|
+
|
|
1500
|
+
if not items_to_update:
|
|
1501
|
+
continue
|
|
1502
|
+
node = node.duplicate()
|
|
1503
|
+
|
|
1504
|
+
generic_fields_map = self._get_generic_fields_map(node_schema=node)
|
|
1505
|
+
|
|
1506
|
+
current_weight = 0
|
|
1507
|
+
for item in node.attributes + node.relationships:
|
|
1508
|
+
current_weight += 1000
|
|
1509
|
+
if not item.order_weight:
|
|
1510
|
+
if item.inherited:
|
|
1511
|
+
_, generic_field = generic_fields_map[item.name]
|
|
1512
|
+
if generic_field:
|
|
1513
|
+
item.order_weight = generic_field.order_weight
|
|
1514
|
+
if not item.order_weight:
|
|
1515
|
+
item.order_weight = current_weight
|
|
1516
|
+
|
|
1517
|
+
self.set(name=name, schema=node)
|
|
1518
|
+
|
|
1442
1519
|
def cleanup_inherited_elements(self) -> None:
|
|
1443
1520
|
for name in self.node_names:
|
|
1444
1521
|
node = self.get_node(name=name, duplicate=False)
|
|
@@ -1535,7 +1612,7 @@ class SchemaBranch:
|
|
|
1535
1612
|
def _get_hierarchy_child_rel(self, peer: str, hierarchical: str | None, read_only: bool) -> RelationshipSchema:
|
|
1536
1613
|
return RelationshipSchema(
|
|
1537
1614
|
name="children",
|
|
1538
|
-
identifier=
|
|
1615
|
+
identifier=PARENT_CHILD_IDENTIFIER,
|
|
1539
1616
|
peer=peer,
|
|
1540
1617
|
kind=RelationshipKind.HIERARCHY,
|
|
1541
1618
|
cardinality=RelationshipCardinality.MANY,
|
|
@@ -1550,7 +1627,7 @@ class SchemaBranch:
|
|
|
1550
1627
|
) -> RelationshipSchema:
|
|
1551
1628
|
return RelationshipSchema(
|
|
1552
1629
|
name="parent",
|
|
1553
|
-
identifier=
|
|
1630
|
+
identifier=PARENT_CHILD_IDENTIFIER,
|
|
1554
1631
|
peer=peer,
|
|
1555
1632
|
kind=RelationshipKind.HIERARCHY,
|
|
1556
1633
|
cardinality=RelationshipCardinality.ONE,
|
|
@@ -1629,11 +1706,10 @@ class SchemaBranch:
|
|
|
1629
1706
|
if not self.has(name=InfrahubKind.PROFILE):
|
|
1630
1707
|
# TODO: This logic is actually only for testing purposes as since 1.0.9 CoreProfile is loaded in db.
|
|
1631
1708
|
# Ideally, we would remove this and instead load CoreProfile properly within tests.
|
|
1632
|
-
|
|
1633
|
-
self.set(name=core_profile_schema.kind, schema=core_profile_schema)
|
|
1709
|
+
self.set(name=core_profile_schema_definition.kind, schema=core_profile_schema_definition)
|
|
1634
1710
|
|
|
1635
1711
|
profile_schema_kinds = set()
|
|
1636
|
-
for node_name in self.node_names + self.
|
|
1712
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
1637
1713
|
node = self.get(name=node_name, duplicate=False)
|
|
1638
1714
|
if (
|
|
1639
1715
|
node.namespace in RESTRICTED_NAMESPACES
|
|
@@ -1789,7 +1865,7 @@ class SchemaBranch:
|
|
|
1789
1865
|
continue
|
|
1790
1866
|
|
|
1791
1867
|
template_rel_settings: dict[str, Any] = {
|
|
1792
|
-
"name":
|
|
1868
|
+
"name": OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
1793
1869
|
"identifier": "node__objecttemplate",
|
|
1794
1870
|
"peer": self._get_object_template_kind(node.kind),
|
|
1795
1871
|
"kind": RelationshipKind.TEMPLATE,
|
|
@@ -1799,14 +1875,14 @@ class SchemaBranch:
|
|
|
1799
1875
|
}
|
|
1800
1876
|
|
|
1801
1877
|
# Add relationship between node and template
|
|
1802
|
-
if
|
|
1878
|
+
if OBJECT_TEMPLATE_RELATIONSHIP_NAME not in node.relationship_names:
|
|
1803
1879
|
node_schema = self.get(name=node_name, duplicate=True)
|
|
1804
1880
|
|
|
1805
1881
|
node_schema.relationships.append(RelationshipSchema(**template_rel_settings))
|
|
1806
1882
|
self.set(name=node_name, schema=node_schema)
|
|
1807
1883
|
else:
|
|
1808
1884
|
has_changes: bool = False
|
|
1809
|
-
rel_template = node.get_relationship(name=
|
|
1885
|
+
rel_template = node.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1810
1886
|
for name, value in template_rel_settings.items():
|
|
1811
1887
|
if getattr(rel_template, name) != value:
|
|
1812
1888
|
has_changes = True
|
|
@@ -1815,7 +1891,7 @@ class SchemaBranch:
|
|
|
1815
1891
|
continue
|
|
1816
1892
|
|
|
1817
1893
|
node_schema = self.get(name=node_name, duplicate=True)
|
|
1818
|
-
rel_template = node_schema.get_relationship(name=
|
|
1894
|
+
rel_template = node_schema.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1819
1895
|
for name, value in template_rel_settings.items():
|
|
1820
1896
|
if getattr(rel_template, name) != value:
|
|
1821
1897
|
setattr(rel_template, name, value)
|
|
@@ -1824,21 +1900,28 @@ class SchemaBranch:
|
|
|
1824
1900
|
|
|
1825
1901
|
def add_relationships_to_template(self, node: NodeSchema) -> None:
|
|
1826
1902
|
template_schema = self.get(name=self._get_object_template_kind(node_kind=node.kind), duplicate=False)
|
|
1903
|
+
if template_schema.is_generic_schema:
|
|
1904
|
+
return
|
|
1905
|
+
|
|
1906
|
+
# Remove previous relationships to account for new ones
|
|
1907
|
+
template_schema.relationships = [
|
|
1908
|
+
r for r in template_schema.relationships if r.kind == RelationshipKind.TEMPLATE
|
|
1909
|
+
]
|
|
1910
|
+
# Tell if the user explicitely requested this template
|
|
1911
|
+
is_autogenerated_subtemplate = node.generate_template is False
|
|
1827
1912
|
|
|
1828
1913
|
for relationship in node.relationships:
|
|
1829
|
-
if relationship.peer in [
|
|
1830
|
-
InfrahubKind.GENERICGROUP,
|
|
1831
|
-
InfrahubKind.PROFILE,
|
|
1832
|
-
] or relationship.kind not in [
|
|
1914
|
+
if relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE] or relationship.kind not in [
|
|
1833
1915
|
RelationshipKind.COMPONENT,
|
|
1834
1916
|
RelationshipKind.PARENT,
|
|
1835
1917
|
RelationshipKind.ATTRIBUTE,
|
|
1918
|
+
RelationshipKind.GENERIC,
|
|
1836
1919
|
]:
|
|
1837
1920
|
continue
|
|
1838
1921
|
|
|
1839
1922
|
rel_template_peer = (
|
|
1840
1923
|
self._get_object_template_kind(node_kind=relationship.peer)
|
|
1841
|
-
if relationship.kind
|
|
1924
|
+
if relationship.kind not in [RelationshipKind.ATTRIBUTE, RelationshipKind.GENERIC]
|
|
1842
1925
|
else relationship.peer
|
|
1843
1926
|
)
|
|
1844
1927
|
template_schema.relationships.append(
|
|
@@ -1846,33 +1929,81 @@ class SchemaBranch:
|
|
|
1846
1929
|
name=relationship.name,
|
|
1847
1930
|
peer=rel_template_peer,
|
|
1848
1931
|
kind=relationship.kind,
|
|
1849
|
-
optional=relationship.
|
|
1932
|
+
optional=relationship.optional
|
|
1933
|
+
if is_autogenerated_subtemplate
|
|
1934
|
+
else relationship.kind != RelationshipKind.PARENT,
|
|
1850
1935
|
cardinality=relationship.cardinality,
|
|
1936
|
+
direction=relationship.direction,
|
|
1851
1937
|
branch=relationship.branch,
|
|
1852
|
-
identifier=
|
|
1938
|
+
identifier=f"template_{relationship.identifier}"
|
|
1939
|
+
if relationship.identifier
|
|
1940
|
+
else self._generate_identifier_string(template_schema.kind, rel_template_peer),
|
|
1853
1941
|
min_count=relationship.min_count,
|
|
1854
1942
|
max_count=relationship.max_count,
|
|
1943
|
+
label=f"{relationship.name} template".title()
|
|
1944
|
+
if relationship.kind in [RelationshipKind.COMPONENT, RelationshipKind.PARENT]
|
|
1945
|
+
else relationship.name.title(),
|
|
1855
1946
|
)
|
|
1856
1947
|
)
|
|
1857
1948
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1949
|
+
if relationship.kind == RelationshipKind.PARENT:
|
|
1950
|
+
template_schema.human_friendly_id = [
|
|
1951
|
+
f"{relationship.name}__template_name__value"
|
|
1952
|
+
] + template_schema.human_friendly_id
|
|
1953
|
+
template_schema.uniqueness_constraints[0].append(relationship.name)
|
|
1954
|
+
|
|
1955
|
+
def generate_object_template_from_node(
|
|
1956
|
+
self, node: NodeSchema | GenericSchema, need_templates: set[NodeSchema | GenericSchema]
|
|
1957
|
+
) -> TemplateSchema | GenericSchema:
|
|
1958
|
+
# Tell if the user explicitely requested this template
|
|
1959
|
+
is_autogenerated_subtemplate = node.generate_template is False
|
|
1960
|
+
|
|
1961
|
+
core_template_schema = (
|
|
1962
|
+
self.get(name=InfrahubKind.OBJECTCOMPONENTTEMPLATE, duplicate=False)
|
|
1963
|
+
if is_autogenerated_subtemplate
|
|
1964
|
+
else self.get(name=InfrahubKind.OBJECTTEMPLATE, duplicate=False)
|
|
1965
|
+
)
|
|
1860
1966
|
core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
|
|
1861
1967
|
template_name_attr = AttributeSchema(
|
|
1862
1968
|
**core_name_attr.model_dump(exclude=["id", "inherited"]),
|
|
1863
1969
|
)
|
|
1864
1970
|
template_name_attr.branch = node.branch
|
|
1865
1971
|
|
|
1972
|
+
template: TemplateSchema | GenericSchema
|
|
1973
|
+
need_template_kinds = [n.kind for n in need_templates]
|
|
1974
|
+
|
|
1975
|
+
if node.is_generic_schema:
|
|
1976
|
+
# When needing a template for a generic, we generate an empty shell mostly to make sure that schemas (including the GraphQL one) will
|
|
1977
|
+
# look right. We don't really care about applying inheritance of fields as it was already processed and actual templates will have the
|
|
1978
|
+
# correct attributes and relationships
|
|
1979
|
+
template = GenericSchema(
|
|
1980
|
+
name=node.kind,
|
|
1981
|
+
namespace="Template",
|
|
1982
|
+
label=f"Generic object template {node.label}",
|
|
1983
|
+
description=f"Generic object template for generic {node.kind}",
|
|
1984
|
+
generate_profile=False,
|
|
1985
|
+
branch=node.branch,
|
|
1986
|
+
include_in_menu=False,
|
|
1987
|
+
attributes=[template_name_attr],
|
|
1988
|
+
)
|
|
1989
|
+
|
|
1990
|
+
for used in node.used_by:
|
|
1991
|
+
if used in need_template_kinds:
|
|
1992
|
+
template.used_by.append(self._get_object_template_kind(node_kind=used))
|
|
1993
|
+
|
|
1994
|
+
return template
|
|
1995
|
+
|
|
1866
1996
|
template = TemplateSchema(
|
|
1867
1997
|
name=node.kind,
|
|
1868
1998
|
namespace="Template",
|
|
1869
1999
|
label=f"Object template {node.label}",
|
|
1870
2000
|
description=f"Object template for {node.kind}",
|
|
1871
2001
|
branch=node.branch,
|
|
1872
|
-
include_in_menu=
|
|
2002
|
+
include_in_menu=False,
|
|
1873
2003
|
display_labels=["template_name__value"],
|
|
1874
|
-
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.OBJECTTEMPLATE, InfrahubKind.NODE],
|
|
1875
2004
|
human_friendly_id=["template_name__value"],
|
|
2005
|
+
uniqueness_constraints=[["template_name__value"]],
|
|
2006
|
+
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.NODE, core_template_schema.kind],
|
|
1876
2007
|
default_filter="template_name__value",
|
|
1877
2008
|
attributes=[template_name_attr],
|
|
1878
2009
|
relationships=[
|
|
@@ -1887,46 +2018,62 @@ class SchemaBranch:
|
|
|
1887
2018
|
],
|
|
1888
2019
|
)
|
|
1889
2020
|
|
|
2021
|
+
for inherited in node.inherit_from:
|
|
2022
|
+
if inherited in need_template_kinds:
|
|
2023
|
+
template.inherit_from.append(self._get_object_template_kind(node_kind=inherited))
|
|
2024
|
+
|
|
1890
2025
|
for node_attr in node.attributes:
|
|
1891
|
-
if node_attr.unique:
|
|
2026
|
+
if node_attr.unique or node_attr.read_only:
|
|
1892
2027
|
continue
|
|
1893
2028
|
|
|
1894
2029
|
attr = AttributeSchema(
|
|
1895
|
-
optional=
|
|
2030
|
+
optional=node_attr.optional if is_autogenerated_subtemplate else True,
|
|
2031
|
+
**node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "inherited"]),
|
|
1896
2032
|
)
|
|
1897
2033
|
template.attributes.append(attr)
|
|
1898
2034
|
|
|
1899
2035
|
return template
|
|
1900
2036
|
|
|
1901
2037
|
def identify_required_object_templates(
|
|
1902
|
-
self, node_schema: NodeSchema, identified: set[NodeSchema]
|
|
2038
|
+
self, node_schema: NodeSchema | GenericSchema, identified: set[NodeSchema | GenericSchema]
|
|
1903
2039
|
) -> set[NodeSchema]:
|
|
1904
2040
|
"""Identify all templates required to turn a given node into a template."""
|
|
1905
|
-
if node_schema in identified:
|
|
2041
|
+
if node_schema in identified or node_schema.state == HashableModelState.ABSENT:
|
|
1906
2042
|
return identified
|
|
1907
2043
|
|
|
1908
2044
|
identified.add(node_schema)
|
|
1909
2045
|
|
|
1910
2046
|
for relationship in node_schema.relationships:
|
|
1911
|
-
if
|
|
1912
|
-
InfrahubKind.GENERICGROUP,
|
|
1913
|
-
|
|
1914
|
-
|
|
2047
|
+
if (
|
|
2048
|
+
relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE]
|
|
2049
|
+
or (relationship.kind == RelationshipKind.PARENT and node_schema.generate_template)
|
|
2050
|
+
or relationship.kind not in [RelationshipKind.PARENT, RelationshipKind.COMPONENT]
|
|
2051
|
+
):
|
|
1915
2052
|
continue
|
|
1916
2053
|
|
|
1917
2054
|
peer_schema = self.get(name=relationship.peer, duplicate=False)
|
|
1918
|
-
if not isinstance(peer_schema, NodeSchema) or peer_schema in identified:
|
|
2055
|
+
if not isinstance(peer_schema, NodeSchema | GenericSchema) or peer_schema in identified:
|
|
1919
2056
|
continue
|
|
2057
|
+
# In a context of a generic, we won't be able to create objects out of it, so any kind of nodes implementing the generic is a valid
|
|
2058
|
+
# option, we therefore need to have a template for each of those nodes
|
|
2059
|
+
if isinstance(peer_schema, GenericSchema) and peer_schema.used_by:
|
|
2060
|
+
if relationship.kind != RelationshipKind.PARENT or not any(
|
|
2061
|
+
u in [i.kind for i in identified] for u in peer_schema.used_by
|
|
2062
|
+
):
|
|
2063
|
+
for used_by in peer_schema.used_by:
|
|
2064
|
+
identified |= self.identify_required_object_templates(
|
|
2065
|
+
node_schema=self.get(name=used_by, duplicate=False), identified=identified
|
|
2066
|
+
)
|
|
1920
2067
|
|
|
1921
2068
|
identified |= self.identify_required_object_templates(node_schema=peer_schema, identified=identified)
|
|
1922
2069
|
|
|
1923
2070
|
return identified
|
|
1924
2071
|
|
|
1925
2072
|
def manage_object_template_schemas(self) -> None:
|
|
1926
|
-
need_templates: set[NodeSchema] = set()
|
|
2073
|
+
need_templates: set[NodeSchema | GenericSchema] = set()
|
|
1927
2074
|
template_schema_kinds: set[str] = set()
|
|
1928
2075
|
|
|
1929
|
-
for node_name in self.node_names + self.
|
|
2076
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
1930
2077
|
node = self.get(name=node_name, duplicate=False)
|
|
1931
2078
|
|
|
1932
2079
|
# Delete old object templates if schemas were removed
|
|
@@ -1936,6 +2083,7 @@ class SchemaBranch:
|
|
|
1936
2083
|
or node.state == HashableModelState.ABSENT
|
|
1937
2084
|
):
|
|
1938
2085
|
try:
|
|
2086
|
+
node.relationships = [r for r in node.relationships if r.name != OBJECT_TEMPLATE_RELATIONSHIP_NAME]
|
|
1939
2087
|
self.delete(name=self._get_object_template_kind(node_kind=node.kind))
|
|
1940
2088
|
except SchemaNotFoundError:
|
|
1941
2089
|
...
|
|
@@ -1945,7 +2093,7 @@ class SchemaBranch:
|
|
|
1945
2093
|
|
|
1946
2094
|
# Generate templates with their attributes
|
|
1947
2095
|
for node in need_templates:
|
|
1948
|
-
template = self.generate_object_template_from_node(node=node)
|
|
2096
|
+
template = self.generate_object_template_from_node(node=node, need_templates=need_templates)
|
|
1949
2097
|
self.set(name=template.kind, schema=template)
|
|
1950
2098
|
template_schema_kinds.add(template.kind)
|
|
1951
2099
|
|