infrahub-server 1.2.0b1__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/api/dependencies.py +6 -6
- infrahub/api/diff/validation_models.py +7 -7
- infrahub/api/schema.py +1 -1
- infrahub/artifacts/models.py +1 -3
- infrahub/artifacts/tasks.py +1 -3
- 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 +239 -11
- infrahub/computed_attribute/tasks.py +77 -442
- infrahub/computed_attribute/triggers.py +11 -45
- infrahub/config.py +43 -32
- infrahub/context.py +14 -0
- infrahub/core/account.py +4 -4
- infrahub/core/attribute.py +57 -57
- infrahub/core/branch/tasks.py +12 -9
- infrahub/core/changelog/diff.py +16 -8
- infrahub/core/changelog/models.py +189 -26
- infrahub/core/constants/__init__.py +5 -1
- infrahub/core/constants/infrahubkind.py +2 -0
- infrahub/core/constraint/node/runner.py +9 -8
- infrahub/core/diff/branch_differ.py +10 -10
- infrahub/core/diff/ipam_diff_parser.py +4 -5
- infrahub/core/diff/model/diff.py +27 -27
- infrahub/core/diff/model/path.py +3 -3
- infrahub/core/diff/query/merge.py +20 -17
- infrahub/core/diff/query_parser.py +4 -4
- infrahub/core/graph/__init__.py +1 -1
- 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 +34 -34
- infrahub/core/merge.py +7 -7
- infrahub/core/migrations/__init__.py +2 -3
- infrahub/core/migrations/graph/__init__.py +9 -4
- 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/m020_duplicate_edges.py +160 -0
- infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
- infrahub/core/migrations/graph/{m020_add_generate_template_attr.py → m022_add_generate_template_attr.py} +3 -3
- 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 +18 -21
- infrahub/core/migrations/query/schema_attribute_update.py +2 -2
- infrahub/core/migrations/schema/models.py +19 -4
- 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 +29 -28
- 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 +5 -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 +24 -24
- 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 +40 -46
- infrahub/core/schema/attribute_schema.py +9 -9
- infrahub/core/schema/basenode_schema.py +93 -44
- infrahub/core/schema/computed_attribute.py +3 -3
- infrahub/core/schema/definitions/core/__init__.py +13 -19
- infrahub/core/schema/definitions/core/account.py +151 -148
- infrahub/core/schema/definitions/core/artifact.py +122 -113
- infrahub/core/schema/definitions/core/builtin.py +19 -16
- infrahub/core/schema/definitions/core/check.py +61 -53
- infrahub/core/schema/definitions/core/core.py +17 -0
- infrahub/core/schema/definitions/core/generator.py +89 -85
- infrahub/core/schema/definitions/core/graphql_query.py +72 -70
- infrahub/core/schema/definitions/core/group.py +96 -93
- infrahub/core/schema/definitions/core/ipam.py +176 -235
- infrahub/core/schema/definitions/core/lineage.py +18 -16
- infrahub/core/schema/definitions/core/menu.py +42 -40
- infrahub/core/schema/definitions/core/permission.py +144 -142
- infrahub/core/schema/definitions/core/profile.py +16 -27
- infrahub/core/schema/definitions/core/propose_change.py +88 -79
- infrahub/core/schema/definitions/core/propose_change_comment.py +170 -165
- infrahub/core/schema/definitions/core/propose_change_validator.py +290 -288
- infrahub/core/schema/definitions/core/repository.py +231 -225
- infrahub/core/schema/definitions/core/resource_pool.py +156 -166
- infrahub/core/schema/definitions/core/template.py +27 -12
- infrahub/core/schema/definitions/core/transform.py +85 -76
- infrahub/core/schema/definitions/core/webhook.py +127 -101
- 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 +7 -7
- infrahub/core/schema/schema_branch.py +276 -138
- 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/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 +26 -22
- infrahub/database/metrics.py +7 -1
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
- infrahub/dependencies/component/registry.py +2 -2
- infrahub/events/__init__.py +25 -2
- infrahub/events/artifact_action.py +13 -25
- infrahub/events/branch_action.py +26 -18
- infrahub/events/generator.py +71 -0
- infrahub/events/group_action.py +10 -24
- infrahub/events/models.py +10 -16
- infrahub/events/node_action.py +87 -32
- 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 +23 -24
- infrahub/generators/models.py +1 -3
- infrahub/git/base.py +7 -7
- infrahub/git/integrator.py +26 -25
- infrahub/git/models.py +22 -9
- infrahub/git/repository.py +3 -3
- infrahub/git/tasks.py +67 -49
- 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 +6 -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 +13 -10
- infrahub/graphql/mutations/artifact_definition.py +5 -5
- infrahub/graphql/mutations/computed_attribute.py +4 -5
- infrahub/graphql/mutations/graphql_query.py +5 -5
- infrahub/graphql/mutations/ipam.py +50 -70
- infrahub/graphql/mutations/main.py +164 -141
- infrahub/graphql/mutations/menu.py +5 -5
- 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 +67 -35
- infrahub/graphql/mutations/repository.py +8 -8
- infrahub/graphql/mutations/resource_manager.py +3 -3
- infrahub/graphql/mutations/schema.py +4 -4
- infrahub/graphql/mutations/webhook.py +137 -0
- infrahub/graphql/parser.py +4 -4
- infrahub/graphql/queries/diff/tree.py +4 -4
- 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/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/event.py +20 -11
- 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/menu/generator.py +7 -7
- infrahub/menu/menu.py +0 -10
- infrahub/menu/models.py +117 -16
- infrahub/menu/repository.py +111 -0
- infrahub/menu/utils.py +5 -8
- infrahub/message_bus/messages/__init__.py +1 -11
- infrahub/message_bus/messages/check_generator_run.py +2 -0
- infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
- infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
- infrahub/message_bus/operations/__init__.py +0 -2
- infrahub/message_bus/operations/check/generator.py +1 -0
- infrahub/message_bus/operations/event/__init__.py +2 -2
- infrahub/message_bus/operations/finalize/validator.py +51 -1
- infrahub/message_bus/operations/requests/generator_definition.py +19 -19
- infrahub/message_bus/operations/requests/proposed_change.py +3 -1
- infrahub/pools/number.py +2 -4
- infrahub/proposed_change/tasks.py +37 -28
- infrahub/pytest_plugin.py +13 -10
- infrahub/server.py +1 -2
- infrahub/services/adapters/event/__init__.py +1 -1
- infrahub/task_manager/event.py +23 -9
- infrahub/tasks/artifact.py +2 -4
- 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 +2 -5
- infrahub/trigger/constants.py +0 -8
- infrahub/trigger/models.py +14 -1
- infrahub/trigger/setup.py +90 -0
- infrahub/trigger/tasks.py +35 -90
- 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 +22 -5
- infrahub/webhook/tasks.py +44 -19
- infrahub/webhook/triggers.py +22 -5
- infrahub/workers/infrahub_async.py +2 -2
- infrahub/workers/utils.py +2 -2
- infrahub/workflows/catalogue.py +28 -20
- infrahub/workflows/initialization.py +1 -3
- infrahub/workflows/models.py +1 -1
- infrahub/workflows/utils.py +10 -1
- infrahub_sdk/client.py +27 -8
- infrahub_sdk/config.py +3 -0
- infrahub_sdk/context.py +13 -0
- infrahub_sdk/exceptions.py +6 -0
- infrahub_sdk/generator.py +4 -1
- infrahub_sdk/graphql.py +45 -13
- infrahub_sdk/node.py +69 -20
- 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 +10 -0
- infrahub_sdk/task/manager.py +12 -6
- infrahub_sdk/testing/schemas/animal.py +9 -0
- infrahub_sdk/timestamp.py +12 -4
- {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +3 -2
- {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +289 -260
- {infrahub_server-1.2.0b1.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/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/operations/event/node.py +0 -20
- infrahub/message_bus/operations/event/schema.py +0 -17
- infrahub/webhook/constants.py +0 -1
- {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.0b1.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,15 +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
|
|
58
60
|
from ..constants.schema import PARENT_CHILD_IDENTIFIER
|
|
59
61
|
from .constants import INTERNAL_SCHEMA_NODE_KINDS, SchemaNamespace
|
|
60
62
|
from .schema_branch_computed import ComputedAttributes
|
|
61
63
|
|
|
62
64
|
log = get_logger()
|
|
63
65
|
|
|
64
|
-
if TYPE_CHECKING:
|
|
65
|
-
from pydantic import ValidationInfo
|
|
66
|
-
|
|
67
66
|
|
|
68
67
|
class SchemaBranch:
|
|
69
68
|
def __init__(
|
|
@@ -73,7 +72,7 @@ class SchemaBranch:
|
|
|
73
72
|
data: dict[str, dict[str, str]] | None = None,
|
|
74
73
|
computed_attributes: ComputedAttributes | None = None,
|
|
75
74
|
):
|
|
76
|
-
self._cache: dict[str,
|
|
75
|
+
self._cache: dict[str, NodeSchema | GenericSchema] = cache
|
|
77
76
|
self.name: str | None = name
|
|
78
77
|
self.nodes: dict[str, str] = {}
|
|
79
78
|
self.generics: dict[str, str] = {}
|
|
@@ -88,15 +87,11 @@ class SchemaBranch:
|
|
|
88
87
|
self.templates = data.get("templates", {})
|
|
89
88
|
|
|
90
89
|
@classmethod
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if isinstance(v, cls):
|
|
97
|
-
return v
|
|
98
|
-
if isinstance(v, dict):
|
|
99
|
-
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)
|
|
100
95
|
raise ValueError("must be a class or a dict")
|
|
101
96
|
|
|
102
97
|
@property
|
|
@@ -107,6 +102,10 @@ class SchemaBranch:
|
|
|
107
102
|
def generic_names(self) -> list[str]:
|
|
108
103
|
return list(self.generics.keys())
|
|
109
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
|
+
|
|
110
109
|
@property
|
|
111
110
|
def profile_names(self) -> list[str]:
|
|
112
111
|
return list(self.profiles.keys())
|
|
@@ -115,10 +114,10 @@ class SchemaBranch:
|
|
|
115
114
|
def template_names(self) -> list[str]:
|
|
116
115
|
return list(self.templates.keys())
|
|
117
116
|
|
|
118
|
-
def get_all_kind_id_map(self,
|
|
117
|
+
def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str]:
|
|
119
118
|
kind_id_map = {}
|
|
120
|
-
if
|
|
121
|
-
names = self.node_names + self.
|
|
119
|
+
if nodes_and_generics_only:
|
|
120
|
+
names = self.node_names + self.generic_names_without_templates
|
|
122
121
|
else:
|
|
123
122
|
names = self.all_names
|
|
124
123
|
for name in names:
|
|
@@ -182,8 +181,8 @@ class SchemaBranch:
|
|
|
182
181
|
|
|
183
182
|
def diff(self, other: SchemaBranch) -> SchemaDiff:
|
|
184
183
|
# Identify the nodes or generics that have been added or removed
|
|
185
|
-
local_kind_id_map = self.get_all_kind_id_map(
|
|
186
|
-
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)
|
|
187
186
|
clean_local_ids = [id for id in local_kind_id_map.values() if id is not None]
|
|
188
187
|
clean_other_ids = [id for id in other_kind_id_map.values() if id is not None]
|
|
189
188
|
shared_ids = intersection(list1=clean_local_ids, list2=clean_other_ids)
|
|
@@ -237,12 +236,6 @@ class SchemaBranch:
|
|
|
237
236
|
other_item = schema.get(name=item_kind)
|
|
238
237
|
self.set(name=item_kind, schema=other_item)
|
|
239
238
|
|
|
240
|
-
# for item_kind in local_only:
|
|
241
|
-
# if item_kind in self.nodes:
|
|
242
|
-
# del self.nodes[item_kind]
|
|
243
|
-
# else:
|
|
244
|
-
# del self.generics[item_kind]
|
|
245
|
-
|
|
246
239
|
def validate_node_deletions(self, diff: SchemaDiff) -> None:
|
|
247
240
|
"""Given a diff, check if a deleted node is still used in relationships of other nodes."""
|
|
248
241
|
removed_schema_names = set(diff.removed.keys())
|
|
@@ -264,7 +257,7 @@ class SchemaBranch:
|
|
|
264
257
|
result.validate_all(migration_map=MIGRATION_MAP, validator_map=CONSTRAINT_VALIDATOR_MAP)
|
|
265
258
|
return result
|
|
266
259
|
|
|
267
|
-
def duplicate(self, name:
|
|
260
|
+
def duplicate(self, name: str | None = None) -> SchemaBranch:
|
|
268
261
|
"""Duplicate the current object but conserve the same cache."""
|
|
269
262
|
return self.__class__(
|
|
270
263
|
name=name,
|
|
@@ -434,7 +427,7 @@ class SchemaBranch:
|
|
|
434
427
|
return list(namespaces.values())
|
|
435
428
|
|
|
436
429
|
def get_schemas_for_namespaces(
|
|
437
|
-
self, namespaces:
|
|
430
|
+
self, namespaces: list[str] | None = None, include_internal: bool = False
|
|
438
431
|
) -> list[MainSchemaTypes]:
|
|
439
432
|
"""Retrive everything in a single dictionary."""
|
|
440
433
|
all_schemas = self.get_all(include_internal=include_internal, duplicate=False)
|
|
@@ -451,12 +444,12 @@ class SchemaBranch:
|
|
|
451
444
|
nodes.append(self.get(name=node_name, duplicate=True))
|
|
452
445
|
return nodes
|
|
453
446
|
|
|
454
|
-
def generate_fields_for_display_label(self, name: str) ->
|
|
447
|
+
def generate_fields_for_display_label(self, name: str) -> dict | None:
|
|
455
448
|
node = self.get(name=name, duplicate=False)
|
|
456
449
|
if isinstance(node, NodeSchema | ProfileSchema | TemplateSchema):
|
|
457
450
|
return node.generate_fields_for_display_label()
|
|
458
451
|
|
|
459
|
-
fields: dict[str,
|
|
452
|
+
fields: dict[str, str | None | dict[str, None]] = {}
|
|
460
453
|
if isinstance(node, GenericSchema):
|
|
461
454
|
for child_node_name in node.used_by:
|
|
462
455
|
child_node = self.get(name=child_node_name, duplicate=False)
|
|
@@ -621,7 +614,7 @@ class SchemaBranch:
|
|
|
621
614
|
node_schema: BaseNodeSchema,
|
|
622
615
|
path: str,
|
|
623
616
|
allowed_path_types: SchemaElementPathType,
|
|
624
|
-
element_name:
|
|
617
|
+
element_name: str | None = None,
|
|
625
618
|
) -> SchemaAttributePath:
|
|
626
619
|
error_header = f"{node_schema.kind}"
|
|
627
620
|
error_header += f".{element_name}" if element_name else ""
|
|
@@ -687,7 +680,7 @@ class SchemaBranch:
|
|
|
687
680
|
return schema_attribute_path
|
|
688
681
|
|
|
689
682
|
def sync_uniqueness_constraints_and_unique_attributes(self) -> None:
|
|
690
|
-
for name in self.
|
|
683
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
691
684
|
node_schema = self.get(name=name, duplicate=False)
|
|
692
685
|
|
|
693
686
|
if not node_schema.unique_attributes and not node_schema.uniqueness_constraints:
|
|
@@ -723,10 +716,12 @@ class SchemaBranch:
|
|
|
723
716
|
for attr_name in attrs_to_make_unique:
|
|
724
717
|
attr_schema = node_schema.get_attribute(name=attr_name)
|
|
725
718
|
attr_schema.unique = True
|
|
719
|
+
|
|
726
720
|
if attrs_to_add_to_constraints:
|
|
727
721
|
node_schema.uniqueness_constraints = (node_schema.uniqueness_constraints or []) + sorted(
|
|
728
722
|
[[f"{attr_name}__value"] for attr_name in attrs_to_add_to_constraints]
|
|
729
723
|
)
|
|
724
|
+
|
|
730
725
|
self.set(name=name, schema=node_schema)
|
|
731
726
|
|
|
732
727
|
def validate_uniqueness_constraints(self) -> None:
|
|
@@ -802,7 +797,7 @@ class SchemaBranch:
|
|
|
802
797
|
)
|
|
803
798
|
|
|
804
799
|
def validate_default_values(self) -> None:
|
|
805
|
-
for name in self.
|
|
800
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
806
801
|
node_schema = self.get(name=name, duplicate=False)
|
|
807
802
|
for node_attr in node_schema.local_attributes:
|
|
808
803
|
if node_attr.default_value is None:
|
|
@@ -821,15 +816,35 @@ class SchemaBranch:
|
|
|
821
816
|
f"{node_schema.namespace}{node_schema.name}: default value {exc.message}"
|
|
822
817
|
) from exc
|
|
823
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
|
+
|
|
824
836
|
def validate_human_friendly_id(self) -> None:
|
|
825
|
-
for name in self.
|
|
837
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
826
838
|
node_schema = self.get(name=name, duplicate=False)
|
|
827
|
-
hf_attr_names = set()
|
|
828
839
|
|
|
829
840
|
if not node_schema.human_friendly_id:
|
|
830
841
|
continue
|
|
831
842
|
|
|
832
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
|
+
|
|
833
848
|
for hfid_path in node_schema.human_friendly_id:
|
|
834
849
|
schema_path = self.validate_schema_path(
|
|
835
850
|
node_schema=node_schema,
|
|
@@ -838,12 +853,24 @@ class SchemaBranch:
|
|
|
838
853
|
element_name="human_friendly_id",
|
|
839
854
|
)
|
|
840
855
|
|
|
841
|
-
if schema_path.
|
|
842
|
-
|
|
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
|
+
)
|
|
843
870
|
|
|
844
871
|
def validate_required_relationships(self) -> None:
|
|
845
872
|
reverse_dependency_map: dict[str, set[str]] = {}
|
|
846
|
-
for name in self.node_names + self.
|
|
873
|
+
for name in self.node_names + self.generic_names_without_templates:
|
|
847
874
|
node_schema = self.get(name=name, duplicate=False)
|
|
848
875
|
for relationship_schema in node_schema.relationships:
|
|
849
876
|
if relationship_schema.optional:
|
|
@@ -861,7 +888,7 @@ class SchemaBranch:
|
|
|
861
888
|
def validate_parent_component(self) -> None:
|
|
862
889
|
# {parent_kind: {component_kind_1, component_kind_2, ...}}
|
|
863
890
|
dependency_map: dict[str, set[str]] = defaultdict(set)
|
|
864
|
-
for name in self.
|
|
891
|
+
for name in self.generic_names_without_templates + self.node_names:
|
|
865
892
|
node_schema = self.get(name=name, duplicate=False)
|
|
866
893
|
|
|
867
894
|
parent_relationships: list[RelationshipSchema] = []
|
|
@@ -900,7 +927,7 @@ class SchemaBranch:
|
|
|
900
927
|
raise ValueError(f"Cycles exist among parents and components in schema: {exc.get_cycle_strings()}") from exc
|
|
901
928
|
|
|
902
929
|
def _validate_parents_one_schema(
|
|
903
|
-
self, node_schema:
|
|
930
|
+
self, node_schema: NodeSchema | GenericSchema, parent_relationships: list[RelationshipSchema]
|
|
904
931
|
) -> None:
|
|
905
932
|
if not parent_relationships:
|
|
906
933
|
return
|
|
@@ -961,9 +988,9 @@ class SchemaBranch:
|
|
|
961
988
|
for rel in node.relationships:
|
|
962
989
|
if rel.peer in [InfrahubKind.GENERICGROUP]:
|
|
963
990
|
continue
|
|
964
|
-
if not self.has(rel.peer):
|
|
991
|
+
if not self.has(rel.peer) or self.get(rel.peer, duplicate=False).state == HashableModelState.ABSENT:
|
|
965
992
|
raise ValueError(
|
|
966
|
-
f"{node.kind}: Relationship {rel.name!r} is
|
|
993
|
+
f"{node.kind}: Relationship {rel.name!r} is referring an invalid peer {rel.peer!r}"
|
|
967
994
|
) from None
|
|
968
995
|
|
|
969
996
|
def validate_computed_attributes(self) -> None:
|
|
@@ -1130,7 +1157,7 @@ class SchemaBranch:
|
|
|
1130
1157
|
for name in self.all_names:
|
|
1131
1158
|
node = self.get(name=name, duplicate=False)
|
|
1132
1159
|
|
|
1133
|
-
schema_to_update:
|
|
1160
|
+
schema_to_update: NodeSchema | GenericSchema | None = None
|
|
1134
1161
|
for relationship in node.relationships:
|
|
1135
1162
|
if relationship.on_delete is not None:
|
|
1136
1163
|
continue
|
|
@@ -1147,49 +1174,50 @@ class SchemaBranch:
|
|
|
1147
1174
|
self.set(name=schema_to_update.kind, schema=schema_to_update)
|
|
1148
1175
|
|
|
1149
1176
|
def process_human_friendly_id(self) -> None:
|
|
1150
|
-
|
|
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:
|
|
1151
1185
|
node = self.get(name=name, duplicate=False)
|
|
1152
1186
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
# If human_friendly_id IS defined
|
|
1156
|
-
# but no unique attributes and no uniquess constraints, we add a uniqueness_constraint
|
|
1157
|
-
if not node.human_friendly_id and node.unique_attributes:
|
|
1158
|
-
for attr in node.unique_attributes:
|
|
1187
|
+
if not node.human_friendly_id:
|
|
1188
|
+
if node.unique_attributes:
|
|
1159
1189
|
node = self.get(name=name, duplicate=True)
|
|
1160
|
-
node.human_friendly_id = [f"{
|
|
1190
|
+
node.human_friendly_id = [f"{node.unique_attributes[0].name}__value"]
|
|
1161
1191
|
self.set(name=node.kind, schema=node)
|
|
1162
|
-
break
|
|
1163
|
-
continue
|
|
1164
1192
|
|
|
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
|
-
|
|
1190
|
-
uniqueness_constraints.append(
|
|
1191
|
-
|
|
1192
|
-
|
|
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]
|
|
1193
1221
|
self.set(name=node.kind, schema=node)
|
|
1194
1222
|
|
|
1195
1223
|
def process_hierarchy(self) -> None:
|
|
@@ -1209,7 +1237,7 @@ class SchemaBranch:
|
|
|
1209
1237
|
node = node.duplicate()
|
|
1210
1238
|
changed = False
|
|
1211
1239
|
|
|
1212
|
-
if node.hierarchy not in node.inherit_from:
|
|
1240
|
+
if node.hierarchy and node.hierarchy not in node.inherit_from:
|
|
1213
1241
|
node.inherit_from.append(node.hierarchy)
|
|
1214
1242
|
changed = True
|
|
1215
1243
|
|
|
@@ -1228,6 +1256,23 @@ class SchemaBranch:
|
|
|
1228
1256
|
if changed:
|
|
1229
1257
|
self.set(name=name, schema=node)
|
|
1230
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
|
+
|
|
1231
1276
|
def process_inheritance(self) -> None:
|
|
1232
1277
|
"""Extend all the nodes with the attributes and relationships
|
|
1233
1278
|
from the Interface objects defined in inherited_from.
|
|
@@ -1280,7 +1325,7 @@ class SchemaBranch:
|
|
|
1280
1325
|
|
|
1281
1326
|
# Update all generics with the list of nodes referrencing them.
|
|
1282
1327
|
for generic_name in self.generics.keys():
|
|
1283
|
-
generic = self.
|
|
1328
|
+
generic = self.get_generic(name=generic_name)
|
|
1284
1329
|
|
|
1285
1330
|
if generic.kind in generics_used_by:
|
|
1286
1331
|
generic.used_by = sorted(generics_used_by[generic.kind])
|
|
@@ -1298,40 +1343,46 @@ class SchemaBranch:
|
|
|
1298
1343
|
for name in self.all_names:
|
|
1299
1344
|
node = self.get(name=name, duplicate=False)
|
|
1300
1345
|
|
|
1301
|
-
|
|
1302
|
-
change_required = False
|
|
1303
|
-
for attr in node.attributes:
|
|
1304
|
-
if attr.branch is None:
|
|
1305
|
-
change_required = True
|
|
1306
|
-
break
|
|
1307
|
-
if not change_required:
|
|
1308
|
-
for rel in node.relationships:
|
|
1309
|
-
if rel.branch is None:
|
|
1310
|
-
change_required = True
|
|
1311
|
-
break
|
|
1312
|
-
|
|
1313
|
-
if not change_required:
|
|
1314
|
-
continue
|
|
1315
|
-
|
|
1316
|
-
node = node.duplicate()
|
|
1346
|
+
generic_fields_map = self._get_generic_fields_map(node_schema=node)
|
|
1317
1347
|
|
|
1348
|
+
attrs_to_update: dict[str, BranchSupportType] = {}
|
|
1318
1349
|
for attr in node.attributes:
|
|
1319
|
-
if attr.
|
|
1320
|
-
|
|
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
|
|
1321
1354
|
|
|
1322
|
-
attr.branch
|
|
1355
|
+
if attr.branch is None:
|
|
1356
|
+
attrs_to_update[attr.name] = node.branch
|
|
1323
1357
|
|
|
1358
|
+
rels_to_update: dict[str, BranchSupportType] = {}
|
|
1324
1359
|
for rel in node.relationships:
|
|
1325
|
-
if rel.branch is not None:
|
|
1360
|
+
if not rel.inherited and rel.branch is not None:
|
|
1326
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
|
|
1327
1378
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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]
|
|
1335
1386
|
|
|
1336
1387
|
self.set(name=name, schema=node)
|
|
1337
1388
|
|
|
@@ -1424,14 +1475,15 @@ class SchemaBranch:
|
|
|
1424
1475
|
self.set(name=name, schema=node)
|
|
1425
1476
|
|
|
1426
1477
|
def generate_weight(self) -> None:
|
|
1427
|
-
for name in self.
|
|
1478
|
+
for name in self.generic_names:
|
|
1428
1479
|
node = self.get(name=name, duplicate=False)
|
|
1480
|
+
|
|
1429
1481
|
items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
|
|
1482
|
+
|
|
1430
1483
|
if not items_to_update:
|
|
1431
1484
|
continue
|
|
1432
1485
|
|
|
1433
1486
|
node = node.duplicate()
|
|
1434
|
-
|
|
1435
1487
|
current_weight = 0
|
|
1436
1488
|
for item in node.attributes + node.relationships:
|
|
1437
1489
|
current_weight += 1000
|
|
@@ -1440,6 +1492,30 @@ class SchemaBranch:
|
|
|
1440
1492
|
|
|
1441
1493
|
self.set(name=name, schema=node)
|
|
1442
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
|
+
|
|
1443
1519
|
def cleanup_inherited_elements(self) -> None:
|
|
1444
1520
|
for name in self.node_names:
|
|
1445
1521
|
node = self.get_node(name=name, duplicate=False)
|
|
@@ -1630,11 +1706,10 @@ class SchemaBranch:
|
|
|
1630
1706
|
if not self.has(name=InfrahubKind.PROFILE):
|
|
1631
1707
|
# TODO: This logic is actually only for testing purposes as since 1.0.9 CoreProfile is loaded in db.
|
|
1632
1708
|
# Ideally, we would remove this and instead load CoreProfile properly within tests.
|
|
1633
|
-
|
|
1634
|
-
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)
|
|
1635
1710
|
|
|
1636
1711
|
profile_schema_kinds = set()
|
|
1637
|
-
for node_name in self.node_names + self.
|
|
1712
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
1638
1713
|
node = self.get(name=node_name, duplicate=False)
|
|
1639
1714
|
if (
|
|
1640
1715
|
node.namespace in RESTRICTED_NAMESPACES
|
|
@@ -1790,7 +1865,7 @@ class SchemaBranch:
|
|
|
1790
1865
|
continue
|
|
1791
1866
|
|
|
1792
1867
|
template_rel_settings: dict[str, Any] = {
|
|
1793
|
-
"name":
|
|
1868
|
+
"name": OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
1794
1869
|
"identifier": "node__objecttemplate",
|
|
1795
1870
|
"peer": self._get_object_template_kind(node.kind),
|
|
1796
1871
|
"kind": RelationshipKind.TEMPLATE,
|
|
@@ -1800,14 +1875,14 @@ class SchemaBranch:
|
|
|
1800
1875
|
}
|
|
1801
1876
|
|
|
1802
1877
|
# Add relationship between node and template
|
|
1803
|
-
if
|
|
1878
|
+
if OBJECT_TEMPLATE_RELATIONSHIP_NAME not in node.relationship_names:
|
|
1804
1879
|
node_schema = self.get(name=node_name, duplicate=True)
|
|
1805
1880
|
|
|
1806
1881
|
node_schema.relationships.append(RelationshipSchema(**template_rel_settings))
|
|
1807
1882
|
self.set(name=node_name, schema=node_schema)
|
|
1808
1883
|
else:
|
|
1809
1884
|
has_changes: bool = False
|
|
1810
|
-
rel_template = node.get_relationship(name=
|
|
1885
|
+
rel_template = node.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1811
1886
|
for name, value in template_rel_settings.items():
|
|
1812
1887
|
if getattr(rel_template, name) != value:
|
|
1813
1888
|
has_changes = True
|
|
@@ -1816,7 +1891,7 @@ class SchemaBranch:
|
|
|
1816
1891
|
continue
|
|
1817
1892
|
|
|
1818
1893
|
node_schema = self.get(name=node_name, duplicate=True)
|
|
1819
|
-
rel_template = node_schema.get_relationship(name=
|
|
1894
|
+
rel_template = node_schema.get_relationship(name=OBJECT_TEMPLATE_RELATIONSHIP_NAME)
|
|
1820
1895
|
for name, value in template_rel_settings.items():
|
|
1821
1896
|
if getattr(rel_template, name) != value:
|
|
1822
1897
|
setattr(rel_template, name, value)
|
|
@@ -1825,16 +1900,18 @@ class SchemaBranch:
|
|
|
1825
1900
|
|
|
1826
1901
|
def add_relationships_to_template(self, node: NodeSchema) -> None:
|
|
1827
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
|
+
|
|
1828
1906
|
# Remove previous relationships to account for new ones
|
|
1829
1907
|
template_schema.relationships = [
|
|
1830
1908
|
r for r in template_schema.relationships if r.kind == RelationshipKind.TEMPLATE
|
|
1831
1909
|
]
|
|
1910
|
+
# Tell if the user explicitely requested this template
|
|
1911
|
+
is_autogenerated_subtemplate = node.generate_template is False
|
|
1832
1912
|
|
|
1833
1913
|
for relationship in node.relationships:
|
|
1834
|
-
if relationship.peer in [
|
|
1835
|
-
InfrahubKind.GENERICGROUP,
|
|
1836
|
-
InfrahubKind.PROFILE,
|
|
1837
|
-
] or relationship.kind not in [
|
|
1914
|
+
if relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE] or relationship.kind not in [
|
|
1838
1915
|
RelationshipKind.COMPONENT,
|
|
1839
1916
|
RelationshipKind.PARENT,
|
|
1840
1917
|
RelationshipKind.ATTRIBUTE,
|
|
@@ -1852,11 +1929,15 @@ class SchemaBranch:
|
|
|
1852
1929
|
name=relationship.name,
|
|
1853
1930
|
peer=rel_template_peer,
|
|
1854
1931
|
kind=relationship.kind,
|
|
1855
|
-
optional=relationship.
|
|
1856
|
-
|
|
1932
|
+
optional=relationship.optional
|
|
1933
|
+
if is_autogenerated_subtemplate
|
|
1934
|
+
else relationship.kind != RelationshipKind.PARENT,
|
|
1857
1935
|
cardinality=relationship.cardinality,
|
|
1936
|
+
direction=relationship.direction,
|
|
1858
1937
|
branch=relationship.branch,
|
|
1859
|
-
identifier=
|
|
1938
|
+
identifier=f"template_{relationship.identifier}"
|
|
1939
|
+
if relationship.identifier
|
|
1940
|
+
else self._generate_identifier_string(template_schema.kind, rel_template_peer),
|
|
1860
1941
|
min_count=relationship.min_count,
|
|
1861
1942
|
max_count=relationship.max_count,
|
|
1862
1943
|
label=f"{relationship.name} template".title()
|
|
@@ -1865,14 +1946,53 @@ class SchemaBranch:
|
|
|
1865
1946
|
)
|
|
1866
1947
|
)
|
|
1867
1948
|
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
+
)
|
|
1870
1966
|
core_name_attr = core_template_schema.get_attribute(name=OBJECT_TEMPLATE_NAME_ATTR)
|
|
1871
1967
|
template_name_attr = AttributeSchema(
|
|
1872
1968
|
**core_name_attr.model_dump(exclude=["id", "inherited"]),
|
|
1873
1969
|
)
|
|
1874
1970
|
template_name_attr.branch = node.branch
|
|
1875
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
|
+
|
|
1876
1996
|
template = TemplateSchema(
|
|
1877
1997
|
name=node.kind,
|
|
1878
1998
|
namespace="Template",
|
|
@@ -1881,8 +2001,9 @@ class SchemaBranch:
|
|
|
1881
2001
|
branch=node.branch,
|
|
1882
2002
|
include_in_menu=False,
|
|
1883
2003
|
display_labels=["template_name__value"],
|
|
1884
|
-
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.OBJECTTEMPLATE, InfrahubKind.NODE],
|
|
1885
2004
|
human_friendly_id=["template_name__value"],
|
|
2005
|
+
uniqueness_constraints=[["template_name__value"]],
|
|
2006
|
+
inherit_from=[InfrahubKind.LINEAGESOURCE, InfrahubKind.NODE, core_template_schema.kind],
|
|
1886
2007
|
default_filter="template_name__value",
|
|
1887
2008
|
attributes=[template_name_attr],
|
|
1888
2009
|
relationships=[
|
|
@@ -1897,12 +2018,17 @@ class SchemaBranch:
|
|
|
1897
2018
|
],
|
|
1898
2019
|
)
|
|
1899
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
|
+
|
|
1900
2025
|
for node_attr in node.attributes:
|
|
1901
|
-
if node_attr.unique:
|
|
2026
|
+
if node_attr.unique or node_attr.read_only:
|
|
1902
2027
|
continue
|
|
1903
2028
|
|
|
1904
2029
|
attr = AttributeSchema(
|
|
1905
|
-
optional=
|
|
2030
|
+
optional=node_attr.optional if is_autogenerated_subtemplate else True,
|
|
2031
|
+
**node_attr.model_dump(exclude=["id", "unique", "optional", "read_only", "inherited"]),
|
|
1906
2032
|
)
|
|
1907
2033
|
template.attributes.append(attr)
|
|
1908
2034
|
|
|
@@ -1912,21 +2038,32 @@ class SchemaBranch:
|
|
|
1912
2038
|
self, node_schema: NodeSchema | GenericSchema, identified: set[NodeSchema | GenericSchema]
|
|
1913
2039
|
) -> set[NodeSchema]:
|
|
1914
2040
|
"""Identify all templates required to turn a given node into a template."""
|
|
1915
|
-
if node_schema in identified:
|
|
2041
|
+
if node_schema in identified or node_schema.state == HashableModelState.ABSENT:
|
|
1916
2042
|
return identified
|
|
1917
2043
|
|
|
1918
2044
|
identified.add(node_schema)
|
|
1919
2045
|
|
|
1920
2046
|
for relationship in node_schema.relationships:
|
|
1921
|
-
if
|
|
1922
|
-
InfrahubKind.GENERICGROUP,
|
|
1923
|
-
|
|
1924
|
-
|
|
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
|
+
):
|
|
1925
2052
|
continue
|
|
1926
2053
|
|
|
1927
2054
|
peer_schema = self.get(name=relationship.peer, duplicate=False)
|
|
1928
2055
|
if not isinstance(peer_schema, NodeSchema | GenericSchema) or peer_schema in identified:
|
|
1929
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
|
+
)
|
|
1930
2067
|
|
|
1931
2068
|
identified |= self.identify_required_object_templates(node_schema=peer_schema, identified=identified)
|
|
1932
2069
|
|
|
@@ -1936,7 +2073,7 @@ class SchemaBranch:
|
|
|
1936
2073
|
need_templates: set[NodeSchema | GenericSchema] = set()
|
|
1937
2074
|
template_schema_kinds: set[str] = set()
|
|
1938
2075
|
|
|
1939
|
-
for node_name in self.node_names + self.
|
|
2076
|
+
for node_name in self.node_names + self.generic_names_without_templates:
|
|
1940
2077
|
node = self.get(name=node_name, duplicate=False)
|
|
1941
2078
|
|
|
1942
2079
|
# Delete old object templates if schemas were removed
|
|
@@ -1946,6 +2083,7 @@ class SchemaBranch:
|
|
|
1946
2083
|
or node.state == HashableModelState.ABSENT
|
|
1947
2084
|
):
|
|
1948
2085
|
try:
|
|
2086
|
+
node.relationships = [r for r in node.relationships if r.name != OBJECT_TEMPLATE_RELATIONSHIP_NAME]
|
|
1949
2087
|
self.delete(name=self._get_object_template_kind(node_kind=node.kind))
|
|
1950
2088
|
except SchemaNotFoundError:
|
|
1951
2089
|
...
|
|
@@ -1955,7 +2093,7 @@ class SchemaBranch:
|
|
|
1955
2093
|
|
|
1956
2094
|
# Generate templates with their attributes
|
|
1957
2095
|
for node in need_templates:
|
|
1958
|
-
template = self.generate_object_template_from_node(node=node)
|
|
2096
|
+
template = self.generate_object_template_from_node(node=node, need_templates=need_templates)
|
|
1959
2097
|
self.set(name=template.kind, schema=template)
|
|
1960
2098
|
template_schema_kinds.add(template.kind)
|
|
1961
2099
|
|