added podman, json and yaml

This commit is contained in:
2022-11-27 19:11:46 +01:00
parent 01135dea09
commit 5226e858bb
790 changed files with 114578 additions and 16 deletions

View File

@ -0,0 +1,5 @@
__version__ = "1.3.4"
from mergedeep.mergedeep import merge, Strategy
__all__ = ["merge", "Strategy"]

View File

@ -0,0 +1,100 @@
from collections import Counter
from collections.abc import Mapping
from copy import deepcopy
from enum import Enum
from functools import reduce, partial
from typing import MutableMapping
class Strategy(Enum):
# Replace `destination` item with one from `source` (default).
REPLACE = 0
# Combine `list`, `tuple`, `set`, or `Counter` types into one collection.
ADDITIVE = 1
# Alias to: `TYPESAFE_REPLACE`
TYPESAFE = 2
# Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `REPLACE` merge.
TYPESAFE_REPLACE = 3
# Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `ADDITIVE` merge.
TYPESAFE_ADDITIVE = 4
def _handle_merge_replace(destination, source, key):
if isinstance(destination[key], Counter) and isinstance(source[key], Counter):
# Merge both destination and source `Counter` as if they were a standard dict.
_deepmerge(destination[key], source[key])
else:
# If a key exists in both objects and the values are `different`, the value from the `source` object will be used.
destination[key] = deepcopy(source[key])
def _handle_merge_additive(destination, source, key):
# Values are combined into one long collection.
if isinstance(destination[key], list) and isinstance(source[key], list):
# Extend destination if both destination and source are `list` type.
destination[key].extend(deepcopy(source[key]))
elif isinstance(destination[key], set) and isinstance(source[key], set):
# Update destination if both destination and source are `set` type.
destination[key].update(deepcopy(source[key]))
elif isinstance(destination[key], tuple) and isinstance(source[key], tuple):
# Update destination if both destination and source are `tuple` type.
destination[key] = destination[key] + deepcopy(source[key])
elif isinstance(destination[key], Counter) and isinstance(source[key], Counter):
# Update destination if both destination and source are `Counter` type.
destination[key].update(deepcopy(source[key]))
else:
_handle_merge[Strategy.REPLACE](destination, source, key)
def _handle_merge_typesafe(destination, source, key, strategy):
# Raise a TypeError if the destination and source types differ.
if type(destination[key]) is not type(source[key]):
raise TypeError(
f'destination type: {type(destination[key])} differs from source type: {type(source[key])} for key: "{key}"'
)
else:
_handle_merge[strategy](destination, source, key)
_handle_merge = {
Strategy.REPLACE: _handle_merge_replace,
Strategy.ADDITIVE: _handle_merge_additive,
Strategy.TYPESAFE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE),
Strategy.TYPESAFE_REPLACE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE),
Strategy.TYPESAFE_ADDITIVE: partial(_handle_merge_typesafe, strategy=Strategy.ADDITIVE),
}
def _is_recursive_merge(a, b):
both_mapping = isinstance(a, Mapping) and isinstance(b, Mapping)
both_counter = isinstance(a, Counter) and isinstance(b, Counter)
return both_mapping and not both_counter
def _deepmerge(dst, src, strategy=Strategy.REPLACE):
for key in src:
if key in dst:
if _is_recursive_merge(dst[key], src[key]):
# If the key for both `dst` and `src` are both Mapping types (e.g. dict), then recurse.
_deepmerge(dst[key], src[key], strategy)
elif dst[key] is src[key]:
# If a key exists in both objects and the values are `same`, the value from the `dst` object will be used.
pass
else:
_handle_merge.get(strategy)(dst, src, key)
else:
# If the key exists only in `src`, the value from the `src` object will be used.
dst[key] = deepcopy(src[key])
return dst
def merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping:
"""
A deep merge function for 🐍.
:param destination: The destination mapping.
:param sources: The source mappings.
:param strategy: The merge strategy.
:return:
"""
return reduce(partial(_deepmerge, strategy=strategy), sources, destination)

View File

@ -0,0 +1,397 @@
"""mergedeep test module"""
import inspect
import unittest
from collections import Counter
from copy import deepcopy
from mergedeep import merge, Strategy
class test_mergedeep(unittest.TestCase):
"""mergedeep function tests."""
##############################################################################################################################
# REPLACE
##############################################################################################################################
def test_should_merge_3_dicts_into_new_dict_using_replace_strategy_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": [4, 5, 6],
"g": (100, 200),
"h": Counter({"a": 5, "b": 1, "c": 1}),
"i": 2,
"j": Counter({"z": 2}),
"z": Counter({"a": 2}),
}
a = {
"a": {"b": {"c": 5}},
"d": 1,
"e": {2: 3},
"f": [1, 2, 3],
"g": (2, 4, 6),
"h": Counter({"a": 1, "b": 1}),
"j": 1,
}
a_copy = deepcopy(a)
b = {
"a": {"B": {"C": 10}},
"d": 2,
"e": 2,
"f": [4, 5, 6],
"g": (100, 200),
"h": Counter({"a": 5, "c": 1}),
"i": Counter({"a": 1}),
"z": Counter({"a": 2}),
}
b_copy = deepcopy(b)
c = {
"a": {"b": {"_c": 15}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"i": 2,
"j": Counter({"z": 2}),
"z": Counter({"a": 2}),
}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.REPLACE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_merge_2_dicts_into_existing_dict_using_replace_strategy_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": [4, 5, 6],
"g": (100, 200),
"h": Counter({"a": 1, "b": 1, "c": 1}),
"i": 2,
"j": Counter({"z": 2}),
}
a = {
"a": {"b": {"c": 5}},
"d": 1,
"e": {2: 3},
"f": [1, 2, 3],
"g": (2, 4, 6),
"h": Counter({"a": 1, "b": 1}),
"j": 1,
}
a_copy = deepcopy(a)
b = {
"a": {"B": {"C": 10}},
"d": 2,
"e": 2,
"f": [4, 5, 6],
"g": (100, 200),
"h": Counter({"a": 1, "c": 1}),
"i": Counter({"a": 1}),
}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}, "i": 2, "j": Counter({"z": 2})}
c_copy = deepcopy(c)
actual = merge(a, b, c, strategy=Strategy.REPLACE)
self.assertEqual(actual, expected)
self.assertEqual(actual, a)
self.assertNotEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_have_default_strategy_of_replace(self):
func_spec = inspect.getfullargspec(merge)
default_strategy = Strategy.REPLACE
self.assertEqual(func_spec.kwonlydefaults.get("strategy"), default_strategy)
# mock_merge.method.assert_called_with(target, source, strategy=Strategy.REPLACE)
##############################################################################################################################
# ADDITIVE
##############################################################################################################################
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_lists_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": [1, 2, 3, 4, 5, 6],
}
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_sets_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": {1, 2, 3, 4, 5, 6},
}
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": {1, 2, 3}}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": {4, 5, 6}}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_tuples_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": (1, 2, 3, 4, 5, 6),
}
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": (1, 2, 3)}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": (4, 5, 6)}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_counters_and_only_mutate_target(self,):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"f": Counter({"a": 2, "c": 1, "b": 1}),
"i": 2,
"j": Counter({"z": 2}),
"z": Counter({"a": 4}),
}
a = {
"a": {"b": {"c": 5}},
"d": 1,
"e": {2: 3},
"f": Counter({"a": 1, "c": 1}),
"i": Counter({"f": 9}),
"j": Counter({"a": 1, "z": 4}),
}
a_copy = deepcopy(a)
b = {
"a": {"B": {"C": 10}},
"d": 2,
"e": 2,
"f": Counter({"a": 1, "b": 1}),
"j": [1, 2, 3],
"z": Counter({"a": 2}),
}
b_copy = deepcopy(b)
c = {
"a": {"b": {"_c": 15}},
"d": 3,
"e": {1: 2, "a": {"f": 2}},
"i": 2,
"j": Counter({"z": 2}),
"z": Counter({"a": 2}),
}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_not_copy_references(self):
before = 1
after = 99
o1 = {"key1": before}
o2 = {"key2": before}
expected = {"list": deepcopy([o1, o2]), "tuple": deepcopy((o1, o2))}
a = {"list": [o1], "tuple": (o1,)}
b = {"list": [o2], "tuple": (o2,)}
actual = merge({}, a, b, strategy=Strategy.ADDITIVE)
o1["key1"] = after
o2["key2"] = after
self.assertEqual(actual, expected)
# Copied dicts should `not` mutate
self.assertEqual(actual["list"][0]["key1"], before)
self.assertEqual(actual["list"][1]["key2"], before)
self.assertEqual(actual["tuple"][0]["key1"], before)
self.assertEqual(actual["tuple"][1]["key2"], before)
# Non-copied dicts should mutate
self.assertEqual(a["list"][0]["key1"], after)
self.assertEqual(b["list"][0]["key2"], after)
self.assertEqual(a["tuple"][0]["key1"], after)
self.assertEqual(b["tuple"][0]["key2"], after)
##############################################################################################################################
# TYPESAFE
# TYPESAFE_REPLACE
##############################################################################################################################
def test_should_raise_TypeError_using_typesafe_strategy_if_types_differ(self):
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
with self.assertRaises(TypeError):
merge({}, a, b, c, strategy=Strategy.TYPESAFE)
def test_should_raise_TypeError_using_typesafe_replace_strategy_if_types_differ(self,):
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
with self.assertRaises(TypeError):
merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
def test_should_merge_3_dicts_into_new_dict_using_typesafe_strategy_and_only_mutate_target_if_types_are_compatible(
self,
):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"f": [4, 5, 6],
"g": {2, 3, 4},
"h": (1, 3),
"z": Counter({"a": 1, "b": 1, "c": 1}),
}
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
def test_should_merge_3_dicts_into_new_dict_using_typesafe_replace_strategy_and_only_mutate_target_if_types_are_compatible(
self,
):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"f": [4, 5, 6],
"g": {2, 3, 4},
"h": (1, 3),
"z": Counter({"a": 1, "b": 1, "c": 1}),
}
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
##############################################################################################################################
# TYPESAFE_ADDITIVE
##############################################################################################################################
def test_should_raise_TypeError_using_typesafe_additive_strategy_if_types_differ(self,):
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
with self.assertRaises(TypeError):
merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
def test_should_merge_3_dicts_into_new_dict_using_typesafe_additive_strategy_and_only_mutate_target_if_types_are_compatible(
self,
):
expected = {
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
"d": 3,
"f": [1, 2, 3, 4, 5, 6],
"g": {1, 2, 3, 4},
"h": (1, 1, 3),
"z": Counter({"a": 2, "b": 1, "c": 1}),
}
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
a_copy = deepcopy(a)
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
b_copy = deepcopy(b)
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
c_copy = deepcopy(c)
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
self.assertEqual(actual, expected)
self.assertEqual(a, a_copy)
self.assertEqual(b, b_copy)
self.assertEqual(c, c_copy)
if __name__ == "__main__":
unittest.main()