Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions cookie_composer/data_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from collections import ChainMap, OrderedDict
from functools import reduce

from frozendict import frozendict


def deep_merge(*dicts) -> dict:
"""
Expand Down Expand Up @@ -43,7 +45,7 @@ def merge_iterables(iter1: Iterable, iter2: Iterable) -> set:
"""
from itertools import chain

return set(chain(iter1, iter2))
return set(chain(freeze_data(iter1), freeze_data(iter2)))


def comprehensive_merge(*args) -> Any:
Expand All @@ -62,7 +64,7 @@ def comprehensive_merge(*args) -> Any:
Returns:
The merged data
"""
dict_types = (dict, OrderedDict)
dict_types = (dict, OrderedDict, frozendict)
iterable_types = (list, set, tuple)

def merge_into(d1, d2):
Expand Down Expand Up @@ -96,7 +98,7 @@ def merge_into(d1, d2):


class Context(ChainMap):
"""Provides merging and convenence functions for managing contexts."""
"""Provides merging and convenience functions for managing contexts."""

@property
def is_empty(self) -> bool:
Expand All @@ -106,3 +108,18 @@ def is_empty(self) -> bool:
def flatten(self) -> dict:
"""Comprehensively merge all the maps into a single mapping."""
return reduce(comprehensive_merge, self.maps, {})


def freeze_data(obj):
"""Check type and recursively return a new read-only object."""
if isinstance(obj, (str, int, float, bytes, type(None), bool)):
return obj
elif isinstance(obj, tuple) and type(obj) != tuple: # assumed namedtuple
return type(obj)(*(freeze_data(i) for i in obj))
elif isinstance(obj, (tuple, list)):
return tuple(freeze_data(i) for i in obj)
elif isinstance(obj, (dict, OrderedDict, frozendict)):
return frozendict({k: freeze_data(v) for k, v in obj.items()})
elif isinstance(obj, (set, frozenset)):
return frozenset(freeze_data(i) for i in obj)
raise ValueError(obj)
2 changes: 2 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ filelock==3.8.0
# virtualenv
flake8==5.0.4
# via -r test.txt
frozendict==2.3.4
# via -r test.txt
frozenlist==1.3.1
# via
# -r test.txt
Expand Down
1 change: 1 addition & 0 deletions requirements/prod.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ aiohttp
backports.shutil-copytree
click-log
cookiecutter>=2.0.0
frozendict
fsspec
gitpython
pydantic
Expand Down
2 changes: 2 additions & 0 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ commonmark==0.9.1
# via rich
cookiecutter==2.1.1
# via -r prod.in
frozendict==2.3.4
# via -r prod.in
frozenlist==1.3.1
# via
# aiohttp
Expand Down
2 changes: 2 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ filelock==3.8.0
# via virtualenv
flake8==5.0.4
# via -r test.in
frozendict==2.3.4
# via -r prod.txt
frozenlist==1.3.1
# via
# -r prod.txt
Expand Down
14 changes: 14 additions & 0 deletions tests/test_merge_files_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections import OrderedDict

import pytest
from frozendict import frozendict
from pytest import param

from cookie_composer import data_merge
Expand Down Expand Up @@ -76,6 +77,19 @@ def test_comprehensive_merge(args: list, expected: Any):
assert data_merge.comprehensive_merge(*args) == expected


def test_comprehensive_merge_list_of_dicts():
"""A list of dicts should resolve into a list of frozendicts in random order."""
result = data_merge.comprehensive_merge([{"a": 1}, {"b": 2}], [{"c": 3}, {"d": 4}])
expected = [
frozendict({"d": 4}),
frozendict({"c": 3}),
frozendict({"b": 2}),
frozendict({"a": 1}),
]
assert isinstance(result, list)
assert set(result) == set(expected)


def test_context_flatten():
"""Should return a merged dict."""
context = data_merge.Context(
Expand Down