A deep merge function for Python dictionaries and other mapping objects with configurable merge strategies.
npx @tessl/cli install tessl/pypi-mergedeep@1.3.0A 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.
pip install mergedeepfrom mergedeep import merge, StrategyPackage-level import:
import mergedeep
# Access via mergedeep.merge, mergedeep.Strategyfrom mergedeep import merge, Strategy
# Basic non-mutating merge
a = {"keyA": 1}
b = {"keyB": {"sub1": 10}}
c = {"keyB": {"sub2": 20}}
merged = merge({}, a, b, c)
print(merged)
# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
# Mutating merge into existing dict
result = {"initial": "data"}
merge(result, a, b, c)
print(result)
# {"initial": "data", "keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
# Using different merge strategies
dst = {"key": [1, 2]}
src = {"key": [3, 4]}
# Replace strategy (default)
merge(dst, src, strategy=Strategy.REPLACE)
print(dst) # {"key": [3, 4]}
# Additive strategy
dst = {"key": [1, 2]}
merge(dst, src, strategy=Strategy.ADDITIVE)
print(dst) # {"key": [1, 2, 3, 4]}The mergedeep package implements a flexible merge system built around the Strategy pattern and recursive processing:
_deepmerge function that traverses nested mapping structuresfunctools.reduce_deepmergecopy.deepcopy to prevent unintended mutations between source and destinationfunctools.reduce and functools.partial for clean handler compositionThis architecture enables mergedeep to handle complex nested data structures while providing fine-grained control over merge behavior through configurable strategies.
The core merge function that performs deep merging of multiple source mappings into a destination mapping with configurable strategies.
def merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping:
"""
A deep merge function for Python dictionaries and mapping objects.
Parameters:
- destination (MutableMapping): The target mapping to merge into
- *sources (Mapping): Variable number of source mappings to merge from
- strategy (Strategy, optional): The merge strategy to use (defaults to Strategy.REPLACE)
Returns:
MutableMapping: The merged destination mapping (same object as input destination)
"""from mergedeep import merge, Strategy
from collections import Counter
# Merging nested dictionaries
config = {"database": {"host": "localhost", "port": 5432}}
overrides = {"database": {"port": 3306, "ssl": True}}
merge(config, overrides)
# {"database": {"host": "localhost", "port": 3306, "ssl": True}}
# Working with different collection types
data = {
"lists": [1, 2],
"sets": {1, 2},
"tuples": (1, 2),
"counters": Counter({"a": 1, "b": 1})
}
updates = {
"lists": [3, 4],
"sets": {3, 4},
"tuples": (3, 4),
"counters": Counter({"a": 1, "c": 1})
}
# Additive merge combines collections
merge(data, updates, strategy=Strategy.ADDITIVE)
# {
# "lists": [1, 2, 3, 4],
# "sets": {1, 2, 3, 4},
# "tuples": (1, 2, 3, 4),
# "counters": Counter({"a": 2, "b": 1, "c": 1})
# }
# Type-safe merge with error checking
try:
dst = {"key": [1, 2]}
src = {"key": {3, 4}} # Different type (set vs list)
merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE)
except TypeError as e:
print(e) # destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"Enumeration defining different strategies for handling conflicting keys during merge operations.
class Strategy(Enum):
"""
Enumeration of merge strategies for handling conflicts.
Values:
- REPLACE (0): Replace destination value with source value (default)
- ADDITIVE (1): Combine collections by extending/updating
- TYPESAFE (2): Alias for TYPESAFE_REPLACE
- TYPESAFE_REPLACE (3): Raise TypeError if types differ, otherwise REPLACE
- TYPESAFE_ADDITIVE (4): Raise TypeError if types differ, otherwise ADDITIVE
"""
REPLACE = 0
ADDITIVE = 1
TYPESAFE = 2
TYPESAFE_REPLACE = 3
TYPESAFE_ADDITIVE = 4REPLACE (default)
ADDITIVE
list: extends destination with source itemsset: updates destination with source itemstuple: concatenates tuplesCounter: updates counter valuesTYPESAFE_REPLACE (TYPESAFE)
TypeError when destination and source types differTYPESAFE_ADDITIVE
TypeError when destination and source types differfrom mergedeep import merge, Strategy
from collections import Counter
# Replace strategy examples
dst = {"key": [1, 2], "nested": {"a": 1}}
src = {"key": [3, 4], "nested": {"b": 2}}
merge(dst, src, strategy=Strategy.REPLACE)
# {"key": [3, 4], "nested": {"a": 1, "b": 2}} # Lists replaced, dicts merged
# Additive strategy examples
dst = {"numbers": [1, 2], "count": Counter({"x": 1})}
src = {"numbers": [3, 4], "count": Counter({"x": 1, "y": 1})}
merge(dst, src, strategy=Strategy.ADDITIVE)
# {"numbers": [1, 2, 3, 4], "count": Counter({"x": 2, "y": 1})}
# Type-safe examples
dst = {"data": [1, 2]}
src = {"data": "string"} # Different type
try:
merge(dst, src, strategy=Strategy.TYPESAFE)
except TypeError as e:
print(f"Type safety error: {e}")The package raises TypeError when using type-safe strategies and type mismatches occur:
try:
dst = {"key": [1, 2]}
src = {"key": {3, 4}} # set instead of list
merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE)
except TypeError as e:
# Error format: 'destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"'
print(f"Merge failed: {e}")The merge function supports these Python collection types:
Mapping types: Recursive deep merge__version__: str
# Package version string, currently "1.3.4"
# Usage: from mergedeep import __version__from typing import MutableMapping, Mapping
from enum import Enum
from collections import Counter
# Core function signature uses these types
MutableMapping # For destination parameter
Mapping # For source parameters
Enum # Base class for Strategy
Counter # Supported collection typefrom mergedeep import merge
# Base configuration
base_config = {
"database": {"host": "localhost", "port": 5432, "ssl": False},
"cache": {"ttl": 300, "size": 1000},
"features": ["logging", "monitoring"]
}
# Environment-specific overrides
production_config = {
"database": {"host": "prod.db.com", "ssl": True},
"cache": {"size": 10000},
"features": ["logging", "monitoring", "analytics"]
}
# Merge configurations
final_config = merge({}, base_config, production_config)from mergedeep import merge, Strategy
from collections import Counter
# Aggregating results from multiple sources
results = [
{"metrics": Counter({"success": 10, "error": 2}), "data": [1, 2, 3]},
{"metrics": Counter({"success": 15, "error": 1}), "data": [4, 5, 6]},
{"metrics": Counter({"success": 8, "error": 3}), "data": [7, 8, 9]}
]
# Combine all results
aggregated = {}
for result in results:
merge(aggregated, result, strategy=Strategy.ADDITIVE)
# aggregated = {
# "metrics": Counter({"success": 33, "error": 6}),
# "data": [1, 2, 3, 4, 5, 6, 7, 8, 9]
# }from mergedeep import merge
# Merging paginated API responses
page1 = {"users": [{"id": 1, "name": "Alice"}], "pagination": {"page": 1, "total": 100}}
page2 = {"users": [{"id": 2, "name": "Bob"}], "pagination": {"page": 2, "total": 100}}
# Combine user data while preserving pagination info
combined = merge({}, page1)
merge(combined["users"], {"users": page2["users"]}, strategy=Strategy.ADDITIVE)
merge(combined, {"pagination": page2["pagination"]})