From 3e8f2f1c2730f937a5af5ff348cd0b3ef8dc6f78 Mon Sep 17 00:00:00 2001 From: Michelle Ark Date: Mon, 6 May 2024 11:30:14 -0400 Subject: [PATCH] test minor artifact changes for backward + forward compatability (#10066) --- core/dbt/artifacts/README.md | 9 +++- tests/unit/artifacts/test_base_resource.py | 57 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/unit/artifacts/test_base_resource.py diff --git a/core/dbt/artifacts/README.md b/core/dbt/artifacts/README.md index ebf28742fd4..ffe112af558 100644 --- a/core/dbt/artifacts/README.md +++ b/core/dbt/artifacts/README.md @@ -33,8 +33,13 @@ All existing resources are defined under `dbt/artifacts/resources/v1`. ### Non-breaking changes Freely make incremental, non-breaking changes in-place to the latest major version of any artifact in mantle (via minor or patch bumps). The only changes that are fully forward and backward compatible are: -* Adding a new field with a default -* Deleting an __optional__ field +1. Adding a new field with a default +2. Deleting a field with a default + * This is compatible in terms of serialization and deserialization, but still may be lead to suprising behaviour: + * For artifact consumers relying on the fields existence (e.g. `manifest["deleted_field"]` will stop working unless the access was implemented safely) + * Old code (e.g. in dbt-core) that relies on the value of the deleted field may have surprising behaviour given only the default value will be set when instantiated from the new schema + +These types of minor, non-breaking changes are tested by [tests/unit/artifacts/test_base_resource.py::TestMinorSchemaChange](https://github.com/dbt-labs/dbt-core/blob/main/tests/unit/artifacts/test_base_resource.py). ### Breaking changes A breaking change is anything that: diff --git a/tests/unit/artifacts/test_base_resource.py b/tests/unit/artifacts/test_base_resource.py new file mode 100644 index 00000000000..9067bf7a205 --- /dev/null +++ b/tests/unit/artifacts/test_base_resource.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +import pytest + +from dbt.artifacts.resources.base import BaseResource +from dbt.artifacts.resources.types import NodeType + + +@dataclass +class BaseResourceWithDefaultField(BaseResource): + field_with_default: bool = True + + +class TestMinorSchemaChange: + @pytest.fixture + def base_resource(self): + return BaseResource( + name="test", + resource_type=NodeType.Model, + package_name="test_package", + path="test_path", + original_file_path="test_original_file_path", + unique_id="test_unique_id", + ) + + @pytest.fixture + def base_resource_new_default_field(self): + return BaseResourceWithDefaultField( + name="test", + resource_type=NodeType.Model, + package_name="test_package", + path="test_path", + original_file_path="test_original_file_path", + unique_id="test_unique_id", + field_with_default=False, + ) + + def test_serializing_new_default_field_is_backward_compatabile( + self, base_resource_new_default_field + ): + # old code (using old class) can create an instance of itself given new data (new class) + BaseResource.from_dict(base_resource_new_default_field.to_dict()) + + def test_serializing_new_default_field_is_forward_compatible(self, base_resource): + # new code (using new class) can create an instance of itself given old data (old class) + BaseResourceWithDefaultField.from_dict(base_resource.to_dict()) + + def test_serializing_removed_default_field_is_backward_compatabile(self, base_resource): + # old code (using old class with default field) can create an instance of itself given new data (class w/o default field) + old_resource = BaseResourceWithDefaultField.from_dict(base_resource.to_dict()) + # set to the default value when not provided in data + assert old_resource.field_with_default is True + + def test_serializing_removed_default_field_is_forward_compatible( + self, base_resource_new_default_field + ): + # new code (using class without default field) can create an instance of itself given old data (class with old field) + BaseResource.from_dict(base_resource_new_default_field.to_dict())