Skip to content
Open
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
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ build: .venv/deps
rm -rf ./dist/
.venv/bin/python -m build

format: .venv/deps
.venv/bin/black deepmerge

# only works with python 3+
lint: .venv/deps
.venv/bin/validate-pyproject pyproject.toml
Expand All @@ -18,4 +21,15 @@ lint: .venv/deps
test: .venv/deps
.venv/bin/pytest deepmerge

docs: .venv/deps
$(MAKE) -C docs html

docs-serve: docs
.venv/bin/python -m http.server --directory docs/_build/html

ready-pr: test lint

clean:
rm -rf .venv
rm -rf build

37 changes: 37 additions & 0 deletions deepmerge/tests/test_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,40 @@ def test_subtypes():
result = always_merger.merge(base, next)

assert dict(result) == {"foo": "value", "bar": "value2", "baz": ["a", "b"]}


@pytest.mark.parametrize(
"initializer,expectation",
[
(None, {"foo": "value", "bar": "value3", "baz": ["a", "b", "c"]}),
({}, {"foo": "value", "bar": "value3", "baz": ["a", "b", "c"]}),
(
{"init": "value"},
{"foo": "value", "bar": "value3", "baz": ["a", "b", "c"], "init": "value"},
),
],
ids=["no initilaizer", "empty initializer", "set initializer"],
)
def test_reduce(initializer, expectation):
# Given
from functools import reduce

base = {"foo": "value", "baz": ["a"]}
next = {"bar": "value2", "baz": ["b"]}
more = {"bar": "value3", "baz": ["c"]}

list_to_merge = [base, next, more]

# When
if initializer is None:
result = reduce(always_merger.merge, list_to_merge)
else:
result = reduce(always_merger.merge, list_to_merge, initializer)

# Then
assert result == expectation

if initializer is None:
assert result is base
else:
assert result is not base
26 changes: 26 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,35 @@
API Reference
=============

Merger
======

.. autoclass:: deepmerge.merger.Merger
:members:

Strategies
==========

.. automodule:: deepmerge.strategy.core
:members:

.. automodule:: deepmerge.strategy.dict
:members:

.. automodule:: deepmerge.strategy.list
:members:

.. automodule:: deepmerge.strategy.set
:members:

.. automodule:: deepmerge.strategy.fallback
:members:

.. automodule:: deepmerge.strategy.type_conflict
:members:

Exceptions
==========

.. automodule:: deepmerge.exception
:members:
Expand Down
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = "en"

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
Expand Down Expand Up @@ -129,7 +129,7 @@
import sphinx_rtd_theme

html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # DEPRECATED

# html_theme = 'alabaster'

Expand Down Expand Up @@ -165,7 +165,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# html_static_path = ["_static"]

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down
68 changes: 68 additions & 0 deletions docs/guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Once a merger is constructed, it then has a merge() method that can be called:
"baz": ["a", "b"]
}

.. _merges_are_destructive:

Merges are Destructive
======================

Expand All @@ -45,6 +47,72 @@ This is intentional, as an implicit copy would result in a significant performan

assert id(result) != id(base)

`functools.reduce` Usage
========================

If you have an iterable collection of data structures to merge, you can use `functools.reduce` to merge them all together.
Beware that there is some nuanced behaviour when using the initialiser with `functools.reduce`
which relates to the :ref:`merges_are_destructive` section above.


**Example 1**: Not setting an initialiser

.. code-block:: python

from deepmerge import always_merger
from functools import reduce

base = {"foo": "value", "baz": ["a"]}
next = {"bar": "value2", "baz": ["b"]}
more = {"bar": "value3", "baz": ["c"]}

list_to_merge = [base, next, more]

result = reduce(always_merger.merge, list_to_merge)

assert result == {
"foo": "value",
"bar": "value3",
"baz": ["a", "b", "c"]
}
assert result == base

**Example 2**: Setting an empty initialiser

Where as the following will not impact `base` because we initialise a new empty `dict`:

.. code-block:: python

result = reduce(always_merger.merge, list_to_merge, {})

assert result == {
"foo": "value",
"bar": "value3",
"baz": ["a", "b", "c"]
}
assert result != base

Which can lead to some fun and easy configuration loading implementations like:

.. code-block:: python

import pathlib
import yaml #PyYAML
from functools import reduce
from deepmerge import always_merger

# Recursively find yaml files, parse and then merge them through pythonic map-reduce idioms
my_configuration = reduce(
always_merger.merge,
map(
lambda f: yaml.safe_load(f.read_text()),
pathlib.Path.cwd().glob("**/*.yml")
),
{}
)



Authoring your own Mergers
==========================

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ dev = [
## Build / Release
"build",
"twine",
## Documentation
"sphinx",
"sphinx-rtd-theme"
]

[tool.setuptools.packages.find]
Expand Down