A deep merge function for Python dictionaries and other mapping objects with configurable merge strategies.
npx @tessl/cli install tessl/pypi-mergedeep@1.3.00
# mergedeep
1
2
A deep merge function for Python dictionaries and other mapping objects with configurable merge strategies. This package provides comprehensive deep merging functionality with fine-grained control over how conflicts are resolved, supporting various Python collection types and type safety options.
3
4
## Package Information
5
6
- **Package Name**: mergedeep
7
- **Language**: Python
8
- **Installation**: `pip install mergedeep`
9
- **Minimum Python Version**: 3.6+
10
11
## Core Imports
12
13
```python
14
from mergedeep import merge, Strategy
15
```
16
17
Package-level import:
18
19
```python
20
import mergedeep
21
# Access via mergedeep.merge, mergedeep.Strategy
22
```
23
24
## Basic Usage
25
26
```python
27
from mergedeep import merge, Strategy
28
29
# Basic non-mutating merge
30
a = {"keyA": 1}
31
b = {"keyB": {"sub1": 10}}
32
c = {"keyB": {"sub2": 20}}
33
34
merged = merge({}, a, b, c)
35
print(merged)
36
# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
37
38
# Mutating merge into existing dict
39
result = {"initial": "data"}
40
merge(result, a, b, c)
41
print(result)
42
# {"initial": "data", "keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
43
44
# Using different merge strategies
45
dst = {"key": [1, 2]}
46
src = {"key": [3, 4]}
47
48
# Replace strategy (default)
49
merge(dst, src, strategy=Strategy.REPLACE)
50
print(dst) # {"key": [3, 4]}
51
52
# Additive strategy
53
dst = {"key": [1, 2]}
54
merge(dst, src, strategy=Strategy.ADDITIVE)
55
print(dst) # {"key": [1, 2, 3, 4]}
56
```
57
58
## Architecture
59
60
The mergedeep package implements a flexible merge system built around the Strategy pattern and recursive processing:
61
62
### Core Components
63
64
- **Strategy Enumeration**: Defines merge behavior policies (REPLACE, ADDITIVE, TYPESAFE variants)
65
- **Handler Dispatch System**: Maps each strategy to specialized handler functions that implement the merge logic
66
- **Recursive Merge Engine**: The `_deepmerge` function that traverses nested mapping structures
67
- **Type-Aware Processing**: Specialized handling for different Python collection types (list, set, tuple, Counter)
68
69
### Merge Process Flow
70
71
1. **Strategy Selection**: User specifies merge strategy (defaults to REPLACE)
72
2. **Source Iteration**: Multiple source mappings are processed sequentially using `functools.reduce`
73
3. **Key-by-Key Processing**: For each key in source mappings:
74
- If key exists in destination: Apply strategy-specific merge logic
75
- If key is new: Deep copy value from source to destination
76
4. **Recursive Descent**: When both destination and source values are mappings (but not Counter objects), recurse with `_deepmerge`
77
5. **Collection Handling**: Strategy handlers apply type-specific merge operations for lists, sets, tuples, and Counter objects
78
79
### Design Patterns
80
81
- **Strategy Pattern**: Encapsulates merge algorithms in the Strategy enum and handler functions
82
- **Immutability Support**: Uses `copy.deepcopy` to prevent unintended mutations between source and destination
83
- **Type Safety**: Optional type checking prevents accidental type mismatches during merge operations
84
- **Functional Composition**: Uses `functools.reduce` and `functools.partial` for clean handler composition
85
86
This architecture enables mergedeep to handle complex nested data structures while providing fine-grained control over merge behavior through configurable strategies.
87
88
## Capabilities
89
90
### Deep Merge Function
91
92
The core merge function that performs deep merging of multiple source mappings into a destination mapping with configurable strategies.
93
94
```python { .api }
95
def merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping:
96
"""
97
A deep merge function for Python dictionaries and mapping objects.
98
99
Parameters:
100
- destination (MutableMapping): The target mapping to merge into
101
- *sources (Mapping): Variable number of source mappings to merge from
102
- strategy (Strategy, optional): The merge strategy to use (defaults to Strategy.REPLACE)
103
104
Returns:
105
MutableMapping: The merged destination mapping (same object as input destination)
106
"""
107
```
108
109
#### Usage Examples
110
111
```python
112
from mergedeep import merge, Strategy
113
from collections import Counter
114
115
# Merging nested dictionaries
116
config = {"database": {"host": "localhost", "port": 5432}}
117
overrides = {"database": {"port": 3306, "ssl": True}}
118
merge(config, overrides)
119
# {"database": {"host": "localhost", "port": 3306, "ssl": True}}
120
121
# Working with different collection types
122
data = {
123
"lists": [1, 2],
124
"sets": {1, 2},
125
"tuples": (1, 2),
126
"counters": Counter({"a": 1, "b": 1})
127
}
128
updates = {
129
"lists": [3, 4],
130
"sets": {3, 4},
131
"tuples": (3, 4),
132
"counters": Counter({"a": 1, "c": 1})
133
}
134
135
# Additive merge combines collections
136
merge(data, updates, strategy=Strategy.ADDITIVE)
137
# {
138
# "lists": [1, 2, 3, 4],
139
# "sets": {1, 2, 3, 4},
140
# "tuples": (1, 2, 3, 4),
141
# "counters": Counter({"a": 2, "b": 1, "c": 1})
142
# }
143
144
# Type-safe merge with error checking
145
try:
146
dst = {"key": [1, 2]}
147
src = {"key": {3, 4}} # Different type (set vs list)
148
merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE)
149
except TypeError as e:
150
print(e) # destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"
151
```
152
153
### Merge Strategies
154
155
Enumeration defining different strategies for handling conflicting keys during merge operations.
156
157
```python { .api }
158
class Strategy(Enum):
159
"""
160
Enumeration of merge strategies for handling conflicts.
161
162
Values:
163
- REPLACE (0): Replace destination value with source value (default)
164
- ADDITIVE (1): Combine collections by extending/updating
165
- TYPESAFE (2): Alias for TYPESAFE_REPLACE
166
- TYPESAFE_REPLACE (3): Raise TypeError if types differ, otherwise REPLACE
167
- TYPESAFE_ADDITIVE (4): Raise TypeError if types differ, otherwise ADDITIVE
168
"""
169
REPLACE = 0
170
ADDITIVE = 1
171
TYPESAFE = 2
172
TYPESAFE_REPLACE = 3
173
TYPESAFE_ADDITIVE = 4
174
```
175
176
#### Strategy Behaviors
177
178
**REPLACE (default)**
179
- When keys exist in both destination and source, replace destination value with source value
180
- With multiple sources, the rightmost (last) source value appears in the merged result
181
- Nested dictionaries are recursively merged
182
183
**ADDITIVE**
184
- Combines collections of the same type:
185
- `list`: extends destination with source items
186
- `set`: updates destination with source items
187
- `tuple`: concatenates tuples
188
- `Counter`: updates counter values
189
- Falls back to REPLACE behavior for non-collection types or type mismatches
190
191
**TYPESAFE_REPLACE (TYPESAFE)**
192
- Raises `TypeError` when destination and source types differ
193
- Otherwise performs REPLACE merge
194
- Ensures type consistency during merge operations
195
196
**TYPESAFE_ADDITIVE**
197
- Raises `TypeError` when destination and source types differ
198
- Otherwise performs ADDITIVE merge
199
- Provides type-safe collection merging
200
201
#### Strategy Usage Examples
202
203
```python
204
from mergedeep import merge, Strategy
205
from collections import Counter
206
207
# Replace strategy examples
208
dst = {"key": [1, 2], "nested": {"a": 1}}
209
src = {"key": [3, 4], "nested": {"b": 2}}
210
merge(dst, src, strategy=Strategy.REPLACE)
211
# {"key": [3, 4], "nested": {"a": 1, "b": 2}} # Lists replaced, dicts merged
212
213
# Additive strategy examples
214
dst = {"numbers": [1, 2], "count": Counter({"x": 1})}
215
src = {"numbers": [3, 4], "count": Counter({"x": 1, "y": 1})}
216
merge(dst, src, strategy=Strategy.ADDITIVE)
217
# {"numbers": [1, 2, 3, 4], "count": Counter({"x": 2, "y": 1})}
218
219
# Type-safe examples
220
dst = {"data": [1, 2]}
221
src = {"data": "string"} # Different type
222
try:
223
merge(dst, src, strategy=Strategy.TYPESAFE)
224
except TypeError as e:
225
print(f"Type safety error: {e}")
226
```
227
228
## Error Handling
229
230
The package raises `TypeError` when using type-safe strategies and type mismatches occur:
231
232
```python
233
try:
234
dst = {"key": [1, 2]}
235
src = {"key": {3, 4}} # set instead of list
236
merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE)
237
except TypeError as e:
238
# Error format: 'destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"'
239
print(f"Merge failed: {e}")
240
```
241
242
## Supported Collection Types
243
244
The merge function supports these Python collection types:
245
246
- **dict** and other `Mapping` types: Recursive deep merge
247
- **list**: Extend destination with source items (ADDITIVE mode)
248
- **tuple**: Concatenate tuples (ADDITIVE mode)
249
- **set**: Update destination with source items (ADDITIVE mode)
250
- **Counter**: Update counter values, with special handling for nested Counter objects
251
252
## Package Constants
253
254
### Version Information
255
256
```python { .api }
257
__version__: str
258
# Package version string, currently "1.3.4"
259
# Usage: from mergedeep import __version__
260
```
261
262
## Types
263
264
```python { .api }
265
from typing import MutableMapping, Mapping
266
from enum import Enum
267
from collections import Counter
268
269
# Core function signature uses these types
270
MutableMapping # For destination parameter
271
Mapping # For source parameters
272
Enum # Base class for Strategy
273
Counter # Supported collection type
274
```
275
276
## Advanced Usage Patterns
277
278
### Configuration Management
279
280
```python
281
from mergedeep import merge
282
283
# Base configuration
284
base_config = {
285
"database": {"host": "localhost", "port": 5432, "ssl": False},
286
"cache": {"ttl": 300, "size": 1000},
287
"features": ["logging", "monitoring"]
288
}
289
290
# Environment-specific overrides
291
production_config = {
292
"database": {"host": "prod.db.com", "ssl": True},
293
"cache": {"size": 10000},
294
"features": ["logging", "monitoring", "analytics"]
295
}
296
297
# Merge configurations
298
final_config = merge({}, base_config, production_config)
299
```
300
301
### Data Processing Pipelines
302
303
```python
304
from mergedeep import merge, Strategy
305
from collections import Counter
306
307
# Aggregating results from multiple sources
308
results = [
309
{"metrics": Counter({"success": 10, "error": 2}), "data": [1, 2, 3]},
310
{"metrics": Counter({"success": 15, "error": 1}), "data": [4, 5, 6]},
311
{"metrics": Counter({"success": 8, "error": 3}), "data": [7, 8, 9]}
312
]
313
314
# Combine all results
315
aggregated = {}
316
for result in results:
317
merge(aggregated, result, strategy=Strategy.ADDITIVE)
318
319
# aggregated = {
320
# "metrics": Counter({"success": 33, "error": 6}),
321
# "data": [1, 2, 3, 4, 5, 6, 7, 8, 9]
322
# }
323
```
324
325
### API Response Merging
326
327
```python
328
from mergedeep import merge
329
330
# Merging paginated API responses
331
page1 = {"users": [{"id": 1, "name": "Alice"}], "pagination": {"page": 1, "total": 100}}
332
page2 = {"users": [{"id": 2, "name": "Bob"}], "pagination": {"page": 2, "total": 100}}
333
334
# Combine user data while preserving pagination info
335
combined = merge({}, page1)
336
merge(combined["users"], {"users": page2["users"]}, strategy=Strategy.ADDITIVE)
337
merge(combined, {"pagination": page2["pagination"]})
338
```