/
mergedeep.py
executable file
路94 lines (78 loc) 路 3.5 KB
/
mergedeep.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from collections.abc import Mapping
from copy import deepcopy
from enum import Enum
from functools import reduce
from typing import TypeVar, MutableMapping as Map
KT = TypeVar("KT")
VT = TypeVar("VT")
class Strategy(Enum):
# Replace `destination` item with one from `source` (default).
REPLACE = 0
# Combined `list`, `tuple`, or `set` types into one collection.
ADDITIVE = 1
# Raise `TypeError` when `destination` and `source` types differ.
TYPESAFE = 2
def _handle_merge_replace(destination, source, key):
# 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):
# List and Set 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])
else:
_handle_merge[Strategy.REPLACE](destination, source, key)
def _handle_merge_typesafe(destination, source, key):
# 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.REPLACE](destination, source, key)
_handle_merge = {
Strategy.REPLACE: _handle_merge_replace,
Strategy.ADDITIVE: _handle_merge_additive,
Strategy.TYPESAFE: _handle_merge_typesafe,
}
def merge(
destination: Map[KT, VT],
*sources: Map[KT, VT],
strategy: Strategy = Strategy.REPLACE,
) -> Map[KT, VT]:
"""
A deep merge function for 馃悕.
:param destination: Map[KT, VT]:
:param *sources: Map[KT, VT]:
:param strategy: Strategy (Default: Strategy.REPLACE):
"""
def _deepmerge(destination: Map[KT, VT], source: Map[KT, VT]):
"""
:param destination: Map[KT, VT]:
:param source: Map[KT, VT]:
"""
for key in source:
if key in destination:
if isinstance(destination[key], Mapping) and isinstance(
source[key], Mapping
):
# If the key for both `destination` and `source` are Mapping types, then recurse.
_deepmerge(destination[key], source[key])
elif destination[key] == source[key]:
# If a key exists in both objects and the values are `same`, the value from the `destination` object will be used.
pass
else:
_handle_merge.get(strategy, Strategy.REPLACE)(
destination, source, key
)
else:
# If the key exists only in `source`, the value from the `source` object will be used.
destination[key] = deepcopy(source[key])
return destination
return reduce(_deepmerge, sources, destination)