Skip to content

Commit

Permalink
Add Mergeable to contract utils (#59)
Browse files Browse the repository at this point in the history
* Add `Mergeable` to contract utils

At this moment in time, `Mergeable` is defined in core
[here](https://github.com/dbt-labs/dbt-core/blob/1a5d6922dddf9ffe018d8860bb2f2141594ddbbe/core/dbt/contracts/util.py#L27).
We're curren't in the process of moving data artifacts of nodes defined
in dbt-core's `nodes.py` into dbt-artifacts. Some of these data artifacts
depend on `Mergeable`. We don't want artifacts to depend on core, thus
`Mergeable` has to be moved _somewhere_ upstream. Given `Mergeable`'s
similarity to `Replaceable` it made the most sense to move next to
`Replaceable` here in dbt-common.

* Add changie doc for add mergeable

* Add unit test for newly added `Mergeable` class
  • Loading branch information
QMalcolm authored Feb 1, 2024
1 parent caaab94 commit 5ce1bee
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20240201-101851.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Add dataclass utility `Mergeable`
time: 2024-02-01T10:18:51.474231-08:00
custom:
Author: QMalcolm
Issue: "58"
16 changes: 16 additions & 0 deletions dbt_common/contracts/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,19 @@
class Replaceable:
def replace(self, **kwargs):
return dataclasses.replace(self, **kwargs)


class Mergeable(Replaceable):
def merged(self, *args):
"""Perform a shallow merge, where the last non-None write wins. This is
intended to merge dataclasses that are a collection of optional values.
"""
replacements = {}
cls = type(self)
for arg in args:
for field in dataclasses.fields(cls):
value = getattr(arg, field.name)
if value is not None:
replacements[field.name] = value

return self.replace(**replacements)
26 changes: 26 additions & 0 deletions tests/unit/test_contracts_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest

from dataclasses import dataclass
from dbt_common.contracts.util import Mergeable
from typing import List, Optional


@dataclass
class ExampleMergableClass(Mergeable):
attr_a: str
attr_b: Optional[int]
attr_c: Optional[List[str]]


class TestMergableClass(unittest.TestCase):
def test_mergeability(self):
mergeable1 = ExampleMergableClass(
attr_a="loses", attr_b=None, attr_c=["I'll", "still", "exist"]
)
mergeable2 = ExampleMergableClass(attr_a="Wins", attr_b=1, attr_c=None)
merge_result: ExampleMergableClass = mergeable1.merged(mergeable2)
assert (
merge_result.attr_a == mergeable2.attr_a
) # mergeable2's attr_a is the "last" non None value
assert merge_result.attr_b == mergeable2.attr_b # mergeable1's attrb_b value was None
assert merge_result.attr_c == mergeable1.attr_c # mergeable2's attr_c value was None

0 comments on commit 5ce1bee

Please sign in to comment.