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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from infrahub_sdk.batch import InfrahubBatch
|
|
6
6
|
from prefect import flow, task
|
|
@@ -33,7 +33,7 @@ async def schema_apply_migrations(message: SchemaApplyMigrationData, service: In
|
|
|
33
33
|
for migration in message.migrations:
|
|
34
34
|
log.info(f"Preparing migration for {migration.migration_name!r} ({migration.routing_key})")
|
|
35
35
|
|
|
36
|
-
new_node_schema:
|
|
36
|
+
new_node_schema: MainSchemaTypes | None = None
|
|
37
37
|
|
|
38
38
|
if message.new_schema.has(name=migration.path.schema_kind):
|
|
39
39
|
new_node_schema = message.new_schema.get(name=migration.path.schema_kind)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any,
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
from typing_extensions import Self
|
|
@@ -43,13 +43,11 @@ class SchemaMigration(BaseModel):
|
|
|
43
43
|
name: str = Field(..., description="Name of the migration")
|
|
44
44
|
queries: Sequence[type[MigrationQuery]] = Field(..., description="List of queries to execute for this migration")
|
|
45
45
|
|
|
46
|
-
new_node_schema:
|
|
47
|
-
previous_node_schema:
|
|
46
|
+
new_node_schema: NodeSchema | GenericSchema | None = None
|
|
47
|
+
previous_node_schema: NodeSchema | GenericSchema | None = None
|
|
48
48
|
schema_path: SchemaPath
|
|
49
49
|
|
|
50
|
-
async def execute(
|
|
51
|
-
self, db: InfrahubDatabase, branch: Branch, at: Optional[Union[Timestamp, str]] = None
|
|
52
|
-
) -> MigrationResult:
|
|
50
|
+
async def execute(self, db: InfrahubDatabase, branch: Branch, at: Timestamp | str | None = None) -> MigrationResult:
|
|
53
51
|
async with db.start_transaction() as ts:
|
|
54
52
|
result = MigrationResult()
|
|
55
53
|
|
|
@@ -65,13 +63,13 @@ class SchemaMigration(BaseModel):
|
|
|
65
63
|
return result
|
|
66
64
|
|
|
67
65
|
@property
|
|
68
|
-
def new_schema(self) ->
|
|
66
|
+
def new_schema(self) -> NodeSchema | GenericSchema:
|
|
69
67
|
if self.new_node_schema:
|
|
70
68
|
return self.new_node_schema
|
|
71
69
|
raise ValueError("new_node_schema hasn't been initialized")
|
|
72
70
|
|
|
73
71
|
@property
|
|
74
|
-
def previous_schema(self) ->
|
|
72
|
+
def previous_schema(self) -> NodeSchema | GenericSchema:
|
|
75
73
|
if self.previous_node_schema:
|
|
76
74
|
return self.previous_node_schema
|
|
77
75
|
raise ValueError("previous_node_schema hasn't been initialized")
|
|
@@ -120,15 +118,17 @@ class GraphMigration(BaseModel):
|
|
|
120
118
|
|
|
121
119
|
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
122
120
|
async with db.start_transaction() as ts:
|
|
123
|
-
|
|
121
|
+
return await self.do_execute(db=ts)
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
async def do_execute(self, db: InfrahubDatabase) -> MigrationResult:
|
|
124
|
+
result = MigrationResult()
|
|
125
|
+
for migration_query in self.queries:
|
|
126
|
+
try:
|
|
127
|
+
query = await migration_query.init(db=db)
|
|
128
|
+
await query.execute(db=db)
|
|
129
|
+
except Exception as exc:
|
|
130
|
+
result.errors.append(str(exc))
|
|
131
|
+
return result
|
|
132
132
|
|
|
133
133
|
return result
|
|
134
134
|
|
infrahub/core/models.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
6
|
from infrahub_sdk.utils import compare_lists, deep_merge_dict, duplicates, intersection
|
|
7
7
|
from pydantic import BaseModel, ConfigDict, Field
|
|
@@ -125,7 +125,7 @@ class SchemaUpdateValidationError(BaseModel):
|
|
|
125
125
|
model_config = ConfigDict(extra="forbid")
|
|
126
126
|
path: SchemaPath
|
|
127
127
|
error: UpdateValidationErrorType
|
|
128
|
-
message:
|
|
128
|
+
message: str | None = None
|
|
129
129
|
|
|
130
130
|
def to_string(self) -> str:
|
|
131
131
|
return f"{self.error.value!r}: {self.path.schema_kind} {self.path.field_name} {self.message}"
|
|
@@ -181,6 +181,15 @@ class SchemaUpdateValidationResult(BaseModel):
|
|
|
181
181
|
|
|
182
182
|
for schema_name, schema_diff in self.diff.changed.items():
|
|
183
183
|
schema_node = schema.get(name=schema_name, duplicate=False)
|
|
184
|
+
if "inherit_from" in schema_diff.changed:
|
|
185
|
+
self.migrations.append(
|
|
186
|
+
SchemaUpdateMigrationInfo(
|
|
187
|
+
path=SchemaPath( # type: ignore[call-arg]
|
|
188
|
+
schema_kind=schema_name, path_type=SchemaPathType.NODE
|
|
189
|
+
),
|
|
190
|
+
migration_name="node.inherit_from.update",
|
|
191
|
+
)
|
|
192
|
+
)
|
|
184
193
|
|
|
185
194
|
# Nothing to do today if we add a new attribute to a node in the schema
|
|
186
195
|
# for node_field_name, _ in schema_diff.added.items():
|
|
@@ -341,9 +350,9 @@ class SchemaUpdateValidationResult(BaseModel):
|
|
|
341
350
|
|
|
342
351
|
class HashableModelDiff(BaseModel):
|
|
343
352
|
model_config = ConfigDict(extra="forbid")
|
|
344
|
-
added: dict[str,
|
|
345
|
-
changed: dict[str,
|
|
346
|
-
removed: dict[str,
|
|
353
|
+
added: dict[str, HashableModelDiff | None] = Field(default_factory=dict)
|
|
354
|
+
changed: dict[str, HashableModelDiff | None] = Field(default_factory=dict)
|
|
355
|
+
removed: dict[str, HashableModelDiff | None] = Field(default_factory=dict)
|
|
347
356
|
|
|
348
357
|
@property
|
|
349
358
|
def has_diff(self) -> bool:
|
|
@@ -353,7 +362,7 @@ class HashableModelDiff(BaseModel):
|
|
|
353
362
|
class HashableModel(BaseModel):
|
|
354
363
|
model_config = ConfigDict(extra="forbid")
|
|
355
364
|
|
|
356
|
-
id:
|
|
365
|
+
id: str | None = None
|
|
357
366
|
state: HashableModelState = HashableModelState.PRESENT
|
|
358
367
|
|
|
359
368
|
_exclude_from_hash: list[str] = []
|
infrahub/core/node/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import TYPE_CHECKING, Any,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Sequence, TypeVar, overload
|
|
5
5
|
|
|
6
6
|
from infrahub_sdk.utils import is_valid_uuid
|
|
7
7
|
from infrahub_sdk.uuidt import UUIDT
|
|
@@ -9,6 +9,7 @@ from infrahub_sdk.uuidt import UUIDT
|
|
|
9
9
|
from infrahub.core import registry
|
|
10
10
|
from infrahub.core.changelog.models import NodeChangelog
|
|
11
11
|
from infrahub.core.constants import (
|
|
12
|
+
GLOBAL_BRANCH_NAME,
|
|
12
13
|
OBJECT_TEMPLATE_NAME_ATTR,
|
|
13
14
|
OBJECT_TEMPLATE_RELATIONSHIP_NAME,
|
|
14
15
|
BranchSupportType,
|
|
@@ -28,6 +29,7 @@ from infrahub.types import ATTRIBUTE_TYPES
|
|
|
28
29
|
|
|
29
30
|
from ...graphql.constants import KIND_GRAPHQL_FIELD_NAME
|
|
30
31
|
from ...graphql.models import OrderModel
|
|
32
|
+
from ..query.relationship import RelationshipDeleteAllQuery
|
|
31
33
|
from ..relationship import RelationshipManager
|
|
32
34
|
from ..utils import update_relationships_to
|
|
33
35
|
from .base import BaseNode, BaseNodeMeta, BaseNodeOptions
|
|
@@ -61,7 +63,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
61
63
|
_meta.default_filter = default_filter
|
|
62
64
|
super().__init_subclass_with_meta__(_meta=_meta, **options)
|
|
63
65
|
|
|
64
|
-
def get_schema(self) ->
|
|
66
|
+
def get_schema(self) -> NodeSchema | ProfileSchema | TemplateSchema:
|
|
65
67
|
return self._schema
|
|
66
68
|
|
|
67
69
|
def get_kind(self) -> str:
|
|
@@ -78,17 +80,18 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
78
80
|
def get_updated_at(self) -> Timestamp | None:
|
|
79
81
|
return self._updated_at
|
|
80
82
|
|
|
81
|
-
async def get_hfid(self, db: InfrahubDatabase, include_kind: bool = False) ->
|
|
83
|
+
async def get_hfid(self, db: InfrahubDatabase, include_kind: bool = False) -> list[str] | None:
|
|
82
84
|
"""Return the Human friendly id of the node."""
|
|
83
85
|
if not self._schema.human_friendly_id:
|
|
84
86
|
return None
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
hfid_values = [await self.get_path_value(db=db, path=item) for item in self._schema.human_friendly_id]
|
|
89
|
+
hfid = [value for value in hfid_values if value is not None]
|
|
87
90
|
if include_kind:
|
|
88
91
|
return [self.get_kind()] + hfid
|
|
89
92
|
return hfid
|
|
90
93
|
|
|
91
|
-
async def get_hfid_as_string(self, db: InfrahubDatabase, include_kind: bool = False) ->
|
|
94
|
+
async def get_hfid_as_string(self, db: InfrahubDatabase, include_kind: bool = False) -> str | None:
|
|
92
95
|
"""Return the Human friendly id of the node in string format separated with a dunder (__) ."""
|
|
93
96
|
hfid = await self.get_hfid(db=db, include_kind=include_kind)
|
|
94
97
|
if not hfid:
|
|
@@ -158,18 +161,18 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
158
161
|
|
|
159
162
|
return f"{self.get_kind()}(ID: {str(self.id)})"
|
|
160
163
|
|
|
161
|
-
def __init__(self, schema:
|
|
162
|
-
self._schema:
|
|
164
|
+
def __init__(self, schema: NodeSchema | ProfileSchema | TemplateSchema, branch: Branch, at: Timestamp):
|
|
165
|
+
self._schema: NodeSchema | ProfileSchema | TemplateSchema = schema
|
|
163
166
|
self._branch: Branch = branch
|
|
164
167
|
self._at: Timestamp = at
|
|
165
168
|
self._existing: bool = False
|
|
166
169
|
|
|
167
|
-
self._updated_at:
|
|
170
|
+
self._updated_at: Timestamp | None = None
|
|
168
171
|
self.id: str = None
|
|
169
172
|
self.db_id: str = None
|
|
170
173
|
|
|
171
|
-
self._source:
|
|
172
|
-
self._owner:
|
|
174
|
+
self._source: Node | None = None
|
|
175
|
+
self._owner: Node | None = None
|
|
173
176
|
self._is_protected: bool = None
|
|
174
177
|
self._computed_jinja2_attributes: list[str] = []
|
|
175
178
|
|
|
@@ -189,10 +192,10 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
189
192
|
@classmethod
|
|
190
193
|
async def init(
|
|
191
194
|
cls,
|
|
192
|
-
schema:
|
|
195
|
+
schema: NodeSchema | ProfileSchema | TemplateSchema | str,
|
|
193
196
|
db: InfrahubDatabase,
|
|
194
|
-
branch:
|
|
195
|
-
at:
|
|
197
|
+
branch: Branch | str | None = ...,
|
|
198
|
+
at: Timestamp | str | None = ...,
|
|
196
199
|
) -> Self: ...
|
|
197
200
|
|
|
198
201
|
@overload
|
|
@@ -201,17 +204,17 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
201
204
|
cls,
|
|
202
205
|
schema: type[SchemaProtocol],
|
|
203
206
|
db: InfrahubDatabase,
|
|
204
|
-
branch:
|
|
205
|
-
at:
|
|
207
|
+
branch: Branch | str | None = ...,
|
|
208
|
+
at: Timestamp | str | None = ...,
|
|
206
209
|
) -> SchemaProtocol: ...
|
|
207
210
|
|
|
208
211
|
@classmethod
|
|
209
212
|
async def init(
|
|
210
213
|
cls,
|
|
211
|
-
schema:
|
|
214
|
+
schema: NodeSchema | ProfileSchema | TemplateSchema | str | type[SchemaProtocol],
|
|
212
215
|
db: InfrahubDatabase,
|
|
213
|
-
branch:
|
|
214
|
-
at:
|
|
216
|
+
branch: Branch | str | None = None,
|
|
217
|
+
at: Timestamp | str | None = None,
|
|
215
218
|
) -> Self | SchemaProtocol:
|
|
216
219
|
attrs: dict[str, Any] = {}
|
|
217
220
|
|
|
@@ -308,13 +311,13 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
308
311
|
for attribute_name in template._attributes:
|
|
309
312
|
if attribute_name in list(fields) + [OBJECT_TEMPLATE_NAME_ATTR]:
|
|
310
313
|
continue
|
|
311
|
-
fields[attribute_name] = {"value": getattr(template, attribute_name).value}
|
|
314
|
+
fields[attribute_name] = {"value": getattr(template, attribute_name).value, "source": template.id}
|
|
312
315
|
|
|
313
316
|
for relationship_name in template._relationships:
|
|
314
317
|
relationship_schema = template._schema.get_relationship(name=relationship_name)
|
|
315
318
|
if (
|
|
316
319
|
relationship_name in list(fields)
|
|
317
|
-
or relationship_schema.kind
|
|
320
|
+
or relationship_schema.kind not in [RelationshipKind.ATTRIBUTE, RelationshipKind.GENERIC]
|
|
318
321
|
or relationship_name == OBJECT_TEMPLATE_RELATIONSHIP_NAME
|
|
319
322
|
):
|
|
320
323
|
continue
|
|
@@ -544,7 +547,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
544
547
|
)
|
|
545
548
|
return attr
|
|
546
549
|
|
|
547
|
-
async def process_label(self, db:
|
|
550
|
+
async def process_label(self, db: InfrahubDatabase | None = None) -> None: # noqa: ARG002
|
|
548
551
|
# If there label and name are both defined for this node
|
|
549
552
|
# if label is not define, we'll automatically populate it with a human friendy vesion of name
|
|
550
553
|
if not self._existing and hasattr(self, "label") and hasattr(self, "name"):
|
|
@@ -552,7 +555,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
552
555
|
self.label.value = " ".join([word.title() for word in self.name.value.split("_")])
|
|
553
556
|
self.label.is_default = False
|
|
554
557
|
|
|
555
|
-
async def new(self, db: InfrahubDatabase, id:
|
|
558
|
+
async def new(self, db: InfrahubDatabase, id: str | None = None, **kwargs: Any) -> Self:
|
|
556
559
|
if id and not is_valid_uuid(id):
|
|
557
560
|
raise ValidationError({"id": f"{id} is not a valid UUID"})
|
|
558
561
|
if id:
|
|
@@ -575,9 +578,9 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
575
578
|
async def load(
|
|
576
579
|
self,
|
|
577
580
|
db: InfrahubDatabase,
|
|
578
|
-
id:
|
|
579
|
-
db_id:
|
|
580
|
-
updated_at:
|
|
581
|
+
id: str | None = None,
|
|
582
|
+
db_id: str | None = None,
|
|
583
|
+
updated_at: Timestamp | str | None = None,
|
|
581
584
|
**kwargs: Any,
|
|
582
585
|
) -> Self:
|
|
583
586
|
self.id = id
|
|
@@ -628,7 +631,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
628
631
|
return node_changelog
|
|
629
632
|
|
|
630
633
|
async def _update(
|
|
631
|
-
self, db: InfrahubDatabase, at:
|
|
634
|
+
self, db: InfrahubDatabase, at: Timestamp | None = None, fields: list[str] | None = None
|
|
632
635
|
) -> NodeChangelog:
|
|
633
636
|
"""Update the node in the database if needed."""
|
|
634
637
|
|
|
@@ -650,7 +653,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
650
653
|
processed_relationships.append(name)
|
|
651
654
|
rel: RelationshipManager = getattr(self, name)
|
|
652
655
|
updated_relationship = await rel.save(at=update_at, db=db)
|
|
653
|
-
node_changelog.add_relationship(
|
|
656
|
+
node_changelog.add_relationship(relationship_changelog=updated_relationship)
|
|
654
657
|
|
|
655
658
|
if len(processed_relationships) != len(self._relationships):
|
|
656
659
|
# Analyze if the node has a parent and add it to the changelog if missing
|
|
@@ -663,7 +666,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
663
666
|
node_changelog.display_label = await self.render_display_label(db=db)
|
|
664
667
|
return node_changelog
|
|
665
668
|
|
|
666
|
-
async def save(self, db: InfrahubDatabase, at:
|
|
669
|
+
async def save(self, db: InfrahubDatabase, at: Timestamp | None = None, fields: list[str] | None = None) -> Self:
|
|
667
670
|
"""Create or Update the Node in the database."""
|
|
668
671
|
|
|
669
672
|
save_at = Timestamp(at)
|
|
@@ -675,7 +678,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
675
678
|
self._node_changelog = await self._create(at=save_at, db=db)
|
|
676
679
|
return self
|
|
677
680
|
|
|
678
|
-
async def delete(self, db: InfrahubDatabase, at:
|
|
681
|
+
async def delete(self, db: InfrahubDatabase, at: Timestamp | None = None) -> None:
|
|
679
682
|
"""Delete the Node in the database."""
|
|
680
683
|
|
|
681
684
|
delete_at = Timestamp(at)
|
|
@@ -690,16 +693,17 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
690
693
|
if deleted_attribute:
|
|
691
694
|
node_changelog.add_attribute(attribute=deleted_attribute)
|
|
692
695
|
|
|
693
|
-
# Go over the list of relationships and update them one by one
|
|
694
|
-
for name in self._relationships:
|
|
695
|
-
rel: RelationshipManager = getattr(self, name)
|
|
696
|
-
updated_relationship = await rel.delete(at=delete_at, db=db)
|
|
697
|
-
node_changelog.add_relationship(relationship=updated_relationship)
|
|
698
|
-
|
|
699
|
-
# Need to check if there are some unidirectional relationship as well
|
|
700
|
-
# For example, if we delete a tag, we must check the permissions and update all the relationships pointing at it
|
|
701
696
|
branch = self.get_branch_based_on_support_type()
|
|
702
697
|
|
|
698
|
+
delete_query = await RelationshipDeleteAllQuery.init(
|
|
699
|
+
db=db, node_id=self.get_id(), branch=branch, at=delete_at, branch_agnostic=branch.name == GLOBAL_BRANCH_NAME
|
|
700
|
+
)
|
|
701
|
+
await delete_query.execute(db=db)
|
|
702
|
+
|
|
703
|
+
deleted_relationships_changelogs = delete_query.get_deleted_relationships_changelog(self._schema)
|
|
704
|
+
for relationship_changelog in deleted_relationships_changelogs:
|
|
705
|
+
node_changelog.add_relationship(relationship_changelog=relationship_changelog)
|
|
706
|
+
|
|
703
707
|
# Update the relationship to the branch itself
|
|
704
708
|
query = await NodeGetListQuery.init(
|
|
705
709
|
db=db,
|
|
@@ -767,7 +771,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
767
771
|
response[field_name] = None
|
|
768
772
|
continue
|
|
769
773
|
|
|
770
|
-
field:
|
|
774
|
+
field: BaseAttribute | None = getattr(self, field_name, None)
|
|
771
775
|
|
|
772
776
|
if not field:
|
|
773
777
|
response[field_name] = None
|
|
@@ -829,7 +833,7 @@ class Node(BaseNode, metaclass=BaseNodeMeta):
|
|
|
829
833
|
|
|
830
834
|
return changed
|
|
831
835
|
|
|
832
|
-
async def render_display_label(self, db:
|
|
836
|
+
async def render_display_label(self, db: InfrahubDatabase | None = None) -> str: # noqa: ARG002
|
|
833
837
|
if not self._schema.display_labels:
|
|
834
838
|
return repr(self)
|
|
835
839
|
|
infrahub/core/node/base.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
3
|
from ..utils import SubclassWithMeta, SubclassWithMeta_Meta
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class BaseOptions:
|
|
9
|
-
name:
|
|
10
|
-
description:
|
|
7
|
+
name: str | None = None
|
|
8
|
+
description: str | None = None
|
|
11
9
|
|
|
12
10
|
_frozen: bool = False
|
|
13
11
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from infrahub.core import registry
|
|
4
4
|
from infrahub.core.branch import Branch
|
|
@@ -18,7 +18,7 @@ class NodeAttributeUniquenessConstraint(NodeConstraintInterface):
|
|
|
18
18
|
self.db = db
|
|
19
19
|
self.branch = branch
|
|
20
20
|
|
|
21
|
-
async def check(self, node: Node, at:
|
|
21
|
+
async def check(self, node: Node, at: Timestamp | None = None, filters: list[str] | None = None) -> None:
|
|
22
22
|
at = Timestamp(at)
|
|
23
23
|
node_schema = node.get_schema()
|
|
24
24
|
for unique_attr in node_schema.unique_attributes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Iterable
|
|
3
|
+
from typing import TYPE_CHECKING, Iterable
|
|
4
4
|
|
|
5
5
|
from infrahub.core import registry
|
|
6
6
|
from infrahub.core.constants import NULL_VALUE
|
|
@@ -9,6 +9,11 @@ from infrahub.core.schema import (
|
|
|
9
9
|
SchemaAttributePath,
|
|
10
10
|
SchemaAttributePathValue,
|
|
11
11
|
)
|
|
12
|
+
from infrahub.core.schema.basenode_schema import (
|
|
13
|
+
SchemaUniquenessConstraintPath,
|
|
14
|
+
UniquenessConstraintType,
|
|
15
|
+
UniquenessConstraintViolation,
|
|
16
|
+
)
|
|
12
17
|
from infrahub.core.validators.uniqueness.index import UniquenessQueryResultsIndex
|
|
13
18
|
from infrahub.core.validators.uniqueness.model import (
|
|
14
19
|
NodeUniquenessQueryRequest,
|
|
@@ -16,7 +21,7 @@ from infrahub.core.validators.uniqueness.model import (
|
|
|
16
21
|
QueryRelationshipAttributePath,
|
|
17
22
|
)
|
|
18
23
|
from infrahub.core.validators.uniqueness.query import NodeUniqueAttributeConstraintQuery
|
|
19
|
-
from infrahub.exceptions import ValidationError
|
|
24
|
+
from infrahub.exceptions import HFIDViolatedError, ValidationError
|
|
20
25
|
|
|
21
26
|
from .interface import NodeConstraintInterface
|
|
22
27
|
|
|
@@ -39,15 +44,15 @@ class NodeGroupedUniquenessConstraint(NodeConstraintInterface):
|
|
|
39
44
|
self,
|
|
40
45
|
updated_node: Node,
|
|
41
46
|
node_schema: MainSchemaTypes,
|
|
42
|
-
|
|
43
|
-
filters:
|
|
47
|
+
uniqueness_constraint_paths: list[SchemaUniquenessConstraintPath],
|
|
48
|
+
filters: list[str] | None = None,
|
|
44
49
|
) -> NodeUniquenessQueryRequest:
|
|
45
50
|
query_request = NodeUniquenessQueryRequest(kind=node_schema.kind)
|
|
46
|
-
for
|
|
51
|
+
for uniqueness_constraint_path in uniqueness_constraint_paths:
|
|
47
52
|
include_in_query = not filters
|
|
48
53
|
query_relationship_paths: set[QueryRelationshipAttributePath] = set()
|
|
49
54
|
query_attribute_paths: set[QueryAttributePath] = set()
|
|
50
|
-
for attribute_path in
|
|
55
|
+
for attribute_path in uniqueness_constraint_path.attributes_paths:
|
|
51
56
|
if attribute_path.related_schema and attribute_path.relationship_schema:
|
|
52
57
|
if filters and attribute_path.relationship_schema.name in filters:
|
|
53
58
|
include_in_query = True
|
|
@@ -118,71 +123,118 @@ class NodeGroupedUniquenessConstraint(NodeConstraintInterface):
|
|
|
118
123
|
)
|
|
119
124
|
return node_value_combination
|
|
120
125
|
|
|
121
|
-
def
|
|
122
|
-
self, schema_attribute_path_values: list[SchemaAttributePathValue], results_index: UniquenessQueryResultsIndex
|
|
123
|
-
) -> None:
|
|
124
|
-
# constraint cannot be violated if this node is missing any values
|
|
125
|
-
if any(sapv.value is None for sapv in schema_attribute_path_values):
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
matching_node_ids = results_index.get_node_ids_for_value_group(schema_attribute_path_values)
|
|
129
|
-
if not matching_node_ids:
|
|
130
|
-
return
|
|
131
|
-
uniqueness_constraint_fields = []
|
|
132
|
-
for sapv in schema_attribute_path_values:
|
|
133
|
-
if sapv.relationship_schema:
|
|
134
|
-
uniqueness_constraint_fields.append(sapv.relationship_schema.name)
|
|
135
|
-
elif sapv.attribute_schema:
|
|
136
|
-
uniqueness_constraint_fields.append(sapv.attribute_schema.name)
|
|
137
|
-
uniqueness_constraint_string = "-".join(uniqueness_constraint_fields)
|
|
138
|
-
error_msg = f"Violates uniqueness constraint '{uniqueness_constraint_string}'"
|
|
139
|
-
errors = [ValidationError({field_name: error_msg}) for field_name in uniqueness_constraint_fields]
|
|
140
|
-
raise ValidationError(errors)
|
|
141
|
-
|
|
142
|
-
async def _check_results(
|
|
126
|
+
async def _get_violations(
|
|
143
127
|
self,
|
|
144
128
|
updated_node: Node,
|
|
145
|
-
|
|
129
|
+
uniqueness_constraint_paths: list[SchemaUniquenessConstraintPath],
|
|
146
130
|
query_results: Iterable[QueryResult],
|
|
147
|
-
) ->
|
|
131
|
+
) -> list[UniquenessConstraintViolation]:
|
|
148
132
|
results_index = UniquenessQueryResultsIndex(
|
|
149
133
|
query_results=query_results, exclude_node_ids={updated_node.get_id()}
|
|
150
134
|
)
|
|
151
|
-
|
|
135
|
+
violations = []
|
|
136
|
+
for uniqueness_constraint_path in uniqueness_constraint_paths:
|
|
137
|
+
# path_group = one constraint (that can contain multiple items)
|
|
152
138
|
schema_attribute_path_values = await self._get_node_attribute_path_values(
|
|
153
|
-
updated_node=updated_node, path_group=
|
|
139
|
+
updated_node=updated_node, path_group=uniqueness_constraint_path.attributes_paths
|
|
154
140
|
)
|
|
155
|
-
|
|
156
|
-
|
|
141
|
+
|
|
142
|
+
# constraint cannot be violated if this node is missing any values
|
|
143
|
+
if any(sapv.value is None for sapv in schema_attribute_path_values):
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
matching_node_ids = results_index.get_node_ids_for_value_group(schema_attribute_path_values)
|
|
147
|
+
if not matching_node_ids:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
uniqueness_constraint_fields = []
|
|
151
|
+
for sapv in schema_attribute_path_values:
|
|
152
|
+
if sapv.relationship_schema:
|
|
153
|
+
uniqueness_constraint_fields.append(sapv.relationship_schema.name)
|
|
154
|
+
elif sapv.attribute_schema:
|
|
155
|
+
uniqueness_constraint_fields.append(sapv.attribute_schema.name)
|
|
156
|
+
|
|
157
|
+
violations.append(
|
|
158
|
+
UniquenessConstraintViolation(
|
|
159
|
+
nodes_ids=matching_node_ids,
|
|
160
|
+
fields=uniqueness_constraint_fields,
|
|
161
|
+
typ=uniqueness_constraint_path.typ,
|
|
162
|
+
)
|
|
157
163
|
)
|
|
158
164
|
|
|
159
|
-
|
|
165
|
+
return violations
|
|
166
|
+
|
|
167
|
+
async def _get_single_schema_violations(
|
|
160
168
|
self,
|
|
161
169
|
node: Node,
|
|
162
170
|
node_schema: MainSchemaTypes,
|
|
163
|
-
at:
|
|
164
|
-
filters:
|
|
165
|
-
) ->
|
|
171
|
+
at: Timestamp | None = None,
|
|
172
|
+
filters: list[str] | None = None,
|
|
173
|
+
) -> list[UniquenessConstraintViolation]:
|
|
166
174
|
schema_branch = self.db.schema.get_schema_branch(name=self.branch.name)
|
|
167
|
-
|
|
175
|
+
|
|
176
|
+
uniqueness_constraint_paths = node_schema.get_unique_constraint_schema_attribute_paths(
|
|
177
|
+
schema_branch=schema_branch
|
|
178
|
+
)
|
|
168
179
|
query_request = await self._build_query_request(
|
|
169
|
-
updated_node=node,
|
|
180
|
+
updated_node=node,
|
|
181
|
+
node_schema=node_schema,
|
|
182
|
+
uniqueness_constraint_paths=uniqueness_constraint_paths,
|
|
183
|
+
filters=filters,
|
|
170
184
|
)
|
|
171
185
|
if not query_request:
|
|
172
|
-
return
|
|
186
|
+
return []
|
|
187
|
+
|
|
173
188
|
query = await NodeUniqueAttributeConstraintQuery.init(
|
|
174
189
|
db=self.db, branch=self.branch, at=at, query_request=query_request, min_count_required=0
|
|
175
190
|
)
|
|
176
191
|
await query.execute(db=self.db)
|
|
177
|
-
await self.
|
|
192
|
+
return await self._get_violations(
|
|
193
|
+
updated_node=node,
|
|
194
|
+
uniqueness_constraint_paths=uniqueness_constraint_paths,
|
|
195
|
+
query_results=query.get_results(),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
async def check(self, node: Node, at: Timestamp | None = None, filters: list[str] | None = None) -> None:
|
|
199
|
+
def _frozen_constraints(schema: MainSchemaTypes) -> frozenset[frozenset[str]]:
|
|
200
|
+
if not schema.uniqueness_constraints:
|
|
201
|
+
return frozenset()
|
|
202
|
+
return frozenset(frozenset(uc) for uc in schema.uniqueness_constraints)
|
|
178
203
|
|
|
179
|
-
async def check(self, node: Node, at: Optional[Timestamp] = None, filters: Optional[list[str]] = None) -> None:
|
|
180
204
|
node_schema = node.get_schema()
|
|
181
|
-
|
|
205
|
+
include_node_schema = True
|
|
206
|
+
frozen_node_constraints = _frozen_constraints(node_schema)
|
|
207
|
+
schemas_to_check: list[MainSchemaTypes] = []
|
|
182
208
|
if node_schema.inherit_from:
|
|
183
209
|
for parent_schema_name in node_schema.inherit_from:
|
|
184
210
|
parent_schema = self.schema_branch.get(name=parent_schema_name, duplicate=False)
|
|
185
|
-
if parent_schema.uniqueness_constraints:
|
|
186
|
-
|
|
211
|
+
if not parent_schema.uniqueness_constraints:
|
|
212
|
+
continue
|
|
213
|
+
schemas_to_check.append(parent_schema)
|
|
214
|
+
frozen_parent_constraints = _frozen_constraints(parent_schema)
|
|
215
|
+
if frozen_node_constraints <= frozen_parent_constraints:
|
|
216
|
+
include_node_schema = False
|
|
217
|
+
|
|
218
|
+
if include_node_schema:
|
|
219
|
+
schemas_to_check.append(node_schema)
|
|
220
|
+
|
|
221
|
+
violations = []
|
|
187
222
|
for schema in schemas_to_check:
|
|
188
|
-
await self.
|
|
223
|
+
schema_violations = await self._get_single_schema_violations(
|
|
224
|
+
node=node, node_schema=schema, at=at, filters=filters
|
|
225
|
+
)
|
|
226
|
+
violations.extend(schema_violations)
|
|
227
|
+
|
|
228
|
+
is_hfid_violated = any(violation.typ == UniquenessConstraintType.HFID for violation in violations)
|
|
229
|
+
|
|
230
|
+
for violation in violations:
|
|
231
|
+
if violation.typ == UniquenessConstraintType.STANDARD or (
|
|
232
|
+
violation.typ == UniquenessConstraintType.SUBSET_OF_HFID and not is_hfid_violated
|
|
233
|
+
):
|
|
234
|
+
error_msg = f"Violates uniqueness constraint '{'-'.join(violation.fields)}'"
|
|
235
|
+
raise ValidationError(error_msg)
|
|
236
|
+
|
|
237
|
+
for violation in violations:
|
|
238
|
+
if violation.typ == UniquenessConstraintType.HFID:
|
|
239
|
+
error_msg = f"Violates uniqueness constraint '{'-'.join(violation.fields)}'"
|
|
240
|
+
raise HFIDViolatedError(error_msg, matching_nodes_ids=violation.nodes_ids)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from infrahub.core.node import Node
|
|
5
4
|
from infrahub.core.timestamp import Timestamp
|
|
@@ -7,4 +6,4 @@ from infrahub.core.timestamp import Timestamp
|
|
|
7
6
|
|
|
8
7
|
class NodeConstraintInterface(ABC):
|
|
9
8
|
@abstractmethod
|
|
10
|
-
async def check(self, node: Node, at:
|
|
9
|
+
async def check(self, node: Node, at: Timestamp | None = None, filters: list[str] | None = None) -> None: ...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Iterable
|
|
3
|
+
from typing import Iterable
|
|
4
4
|
|
|
5
5
|
from infrahub.core import registry
|
|
6
6
|
from infrahub.core.branch import Branch
|
|
@@ -124,16 +124,14 @@ class NodeDeleteValidator:
|
|
|
124
124
|
self._all_schemas_map = schema_branch.get_all(duplicate=False)
|
|
125
125
|
self.index: NodeDeleteIndex = NodeDeleteIndex(all_schemas_map=self._all_schemas_map)
|
|
126
126
|
|
|
127
|
-
async def get_ids_to_delete(self, nodes: Iterable[Node], at:
|
|
127
|
+
async def get_ids_to_delete(self, nodes: Iterable[Node], at: Timestamp | str | None = None) -> set[str]:
|
|
128
128
|
start_schemas = {node.get_schema() for node in nodes}
|
|
129
129
|
self.index.index(start_schemas=start_schemas)
|
|
130
130
|
at = Timestamp(at)
|
|
131
131
|
|
|
132
132
|
return await self._analyze_delete_dependencies(start_nodes=nodes, at=at)
|
|
133
133
|
|
|
134
|
-
async def _analyze_delete_dependencies(
|
|
135
|
-
self, start_nodes: Iterable[Node], at: Optional[Union[Timestamp, str]]
|
|
136
|
-
) -> set[str]:
|
|
134
|
+
async def _analyze_delete_dependencies(self, start_nodes: Iterable[Node], at: Timestamp | str | None) -> set[str]:
|
|
137
135
|
full_relationship_identifiers = self.index.get_relationship_identifiers()
|
|
138
136
|
if not full_relationship_identifiers:
|
|
139
137
|
return {node.get_id() for node in start_nodes}
|