Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-deepmerge for
openSUSE:Factory checked in at 2026-08-01 18:34:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-deepmerge (Old)
and /work/SRC/openSUSE:Factory/.python-deepmerge.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-deepmerge"
Sat Aug 1 18:34:10 2026 rev:5 rq:1368878 version:2.1.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-deepmerge/python-deepmerge.changes
2024-09-30 15:38:50.825138000 +0200
+++
/work/SRC/openSUSE:Factory/.python-deepmerge.new.16738/python-deepmerge.changes
2026-08-01 18:37:20.940393528 +0200
@@ -1,0 +2,8 @@
+Fri Jul 31 16:48:14 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.1.0:
+ * minor: add __all__ to strategy __init__
+ * fix: compare by equality, not just hash, in ExtendedSet
+ * docs: functools.reduce usage
+
+-------------------------------------------------------------------
Old:
----
deepmerge-2.0.tar.gz
New:
----
deepmerge-2.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-deepmerge.spec ++++++
--- /var/tmp/diff_new_pack.awzadR/_old 2026-08-01 18:37:21.740421055 +0200
+++ /var/tmp/diff_new_pack.awzadR/_new 2026-08-01 18:37:21.744421192 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-deepmerge
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-deepmerge
-Version: 2.0
+Version: 2.1.0
Release: 0
License: MIT
Summary: A toolset to deeply merge python dictionaries
++++++ deepmerge-2.0.tar.gz -> deepmerge-2.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/Makefile new/deepmerge-2.1.0/Makefile
--- old/deepmerge-2.0/Makefile 2024-08-30 07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/Makefile 2026-06-22 07:45:38.000000000 +0200
@@ -9,6 +9,9 @@
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
@@ -18,4 +21,15 @@
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
+
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/PKG-INFO new/deepmerge-2.1.0/PKG-INFO
--- old/deepmerge-2.0/PKG-INFO 2024-08-30 07:31:39.765339400 +0200
+++ new/deepmerge-2.1.0/PKG-INFO 2026-06-22 07:45:57.335164500 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: deepmerge
-Version: 2.0
+Version: 2.1.0
Summary: A toolset for deeply merging Python dictionaries.
Author-email: Yusuke Tsutsumi <[email protected]>
License: MIT Licence
@@ -28,6 +28,9 @@
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
+Requires-Dist: sphinx; extra == "dev"
+Requires-Dist: sphinx-rtd-theme; extra == "dev"
+Dynamic: license-file
=========
deepmerge
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/_version.py
new/deepmerge-2.1.0/deepmerge/_version.py
--- old/deepmerge-2.0/deepmerge/_version.py 2024-08-30 07:31:39.000000000
+0200
+++ new/deepmerge-2.1.0/deepmerge/_version.py 2026-06-22 07:45:57.000000000
+0200
@@ -1,16 +1,24 @@
-# file generated by setuptools_scm
+# file generated by vcs-versioning
# don't change, don't track in version control
-TYPE_CHECKING = False
-if TYPE_CHECKING:
- from typing import Tuple, Union
- VERSION_TUPLE = Tuple[Union[int, str], ...]
-else:
- VERSION_TUPLE = object
+from __future__ import annotations
+
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
version: str
__version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
+__version_tuple__: tuple[int | str, ...]
+version_tuple: tuple[int | str, ...]
+commit_id: str | None
+__commit_id__: str | None
+
+__version__ = version = '2.1.0'
+__version_tuple__ = version_tuple = (2, 1, 0)
-__version__ = version = '2.0'
-__version_tuple__ = version_tuple = (2, 0)
+__commit_id__ = commit_id = 'gf4822dec8'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/extended_set.py
new/deepmerge-2.1.0/deepmerge/extended_set.py
--- old/deepmerge-2.0/deepmerge/extended_set.py 2024-08-30 07:31:21.000000000
+0200
+++ new/deepmerge-2.1.0/deepmerge/extended_set.py 2026-06-22
07:45:38.000000000 +0200
@@ -1,4 +1,4 @@
-from typing import Sequence, Any
+from typing import Sequence, Any, Dict, List
class ExtendedSet(set):
@@ -13,10 +13,18 @@
"""
def __init__(self, elements: Sequence) -> None:
- self._values_by_hash = {self._hash_element(e): e for e in elements}
+ # Elements are grouped into buckets keyed by their (possibly lossy)
+ # hash. Membership and insertion compare a candidate against the
+ # members of the matching bucket using equality, so two distinct
+ # elements that merely collide on the hash are not treated as equal.
+ self._buckets: Dict[int, List[Any]] = {}
+ for element in elements:
+ self._insert(element)
def _insert(self, element: Any) -> None:
- self._values_by_hash[self._hash_element(element)] = element
+ bucket = self._buckets.setdefault(self._hash_element(element), [])
+ if not any(element == existing for existing in bucket):
+ bucket.append(element)
return
def _hash_element(self, element: Any) -> int:
@@ -29,4 +37,7 @@
return hash(str(element))
def __contains__(self, obj: Any) -> bool:
- return self._hash_element(obj) in self._values_by_hash
+ bucket = self._buckets.get(self._hash_element(obj))
+ if bucket is None:
+ return False
+ return any(obj == existing for existing in bucket)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/strategy/__init__.py
new/deepmerge-2.1.0/deepmerge/strategy/__init__.py
--- old/deepmerge-2.0/deepmerge/strategy/__init__.py 2024-08-30
07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge/strategy/__init__.py 2026-06-22
07:45:38.000000000 +0200
@@ -4,3 +4,14 @@
from .list import ListStrategies
from .set import SetStrategies
from .type_conflict import TypeConflictStrategies
+
+__all__ = [
+ "StrategyList",
+ "StrategyCallable",
+ "StrategyListInitable",
+ "DictStrategies",
+ "FallbackStrategies",
+ "ListStrategies",
+ "SetStrategies",
+ "TypeConflictStrategies",
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/strategy/fallback.py
new/deepmerge-2.1.0/deepmerge/strategy/fallback.py
--- old/deepmerge-2.0/deepmerge/strategy/fallback.py 2024-08-30
07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge/strategy/fallback.py 2026-06-22
07:45:38.000000000 +0200
@@ -5,7 +5,6 @@
import deepmerge.merger
from .core import StrategyList
-
T = TypeVar("T")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/tests/strategy/test_list.py
new/deepmerge-2.1.0/deepmerge/tests/strategy/test_list.py
--- old/deepmerge-2.0/deepmerge/tests/strategy/test_list.py 2024-08-30
07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge/tests/strategy/test_list.py 2026-06-22
07:45:38.000000000 +0200
@@ -43,3 +43,23 @@
result = custom_merger.merge(base, nxt)
assert result == [{"bar": "bob", "foo": "baz"}, {"x": "y"}]
+
+
+def test_strategy_append_unique_keeps_hash_colliding_dicts(custom_merger):
+ """append_unique must keep distinct dicts even when their lossy
+ "key:value" hash collides (e.g. ``{"a": "1", "b": "2"}`` and
+ ``{"a": "1,b:2"}`` both hash to ``"a:1,b:2"``), and must not treat an
+ ``int`` value as equal to its ``str`` form.
+ """
+ assert custom_merger.merge([{"a": "1", "b": "2"}], [{"a": "1,b:2"}]) == [
+ {"a": "1", "b": "2"},
+ {"a": "1,b:2"},
+ ]
+ assert custom_merger.merge([{"k": 1}], [{"k": "1"}]) == [{"k": 1}, {"k":
"1"}]
+
+
+def test_strategy_append_unique_hashable_hash_collision(custom_merger):
+ """A hashable element must not be considered present just because a
+ different element shares its hash (e.g. ``hash(-1) == hash(-2)``).
+ """
+ assert custom_merger.merge([-1], [-2]) == [-1, -2]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge/tests/test_full.py
new/deepmerge-2.1.0/deepmerge/tests/test_full.py
--- old/deepmerge-2.0/deepmerge/tests/test_full.py 2024-08-30
07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge/tests/test_full.py 2026-06-22
07:45:38.000000000 +0200
@@ -58,3 +58,40 @@
result = always_merger.merge(base, next)
assert dict(result) == {"foo": "value", "bar": "value2", "baz": ["a", "b"]}
+
+
[email protected](
+ "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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge.egg-info/PKG-INFO
new/deepmerge-2.1.0/deepmerge.egg-info/PKG-INFO
--- old/deepmerge-2.0/deepmerge.egg-info/PKG-INFO 2024-08-30
07:31:39.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge.egg-info/PKG-INFO 2026-06-22
07:45:57.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: deepmerge
-Version: 2.0
+Version: 2.1.0
Summary: A toolset for deeply merging Python dictionaries.
Author-email: Yusuke Tsutsumi <[email protected]>
License: MIT Licence
@@ -28,6 +28,9 @@
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
+Requires-Dist: sphinx; extra == "dev"
+Requires-Dist: sphinx-rtd-theme; extra == "dev"
+Dynamic: license-file
=========
deepmerge
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/deepmerge.egg-info/requires.txt
new/deepmerge-2.1.0/deepmerge.egg-info/requires.txt
--- old/deepmerge-2.0/deepmerge.egg-info/requires.txt 2024-08-30
07:31:39.000000000 +0200
+++ new/deepmerge-2.1.0/deepmerge.egg-info/requires.txt 2026-06-22
07:45:57.000000000 +0200
@@ -10,3 +10,5 @@
pytest
build
twine
+sphinx
+sphinx-rtd-theme
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/docs/api.rst
new/deepmerge-2.1.0/docs/api.rst
--- old/deepmerge-2.0/docs/api.rst 2024-08-30 07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/docs/api.rst 2026-06-22 07:45:38.000000000 +0200
@@ -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:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/docs/conf.py
new/deepmerge-2.1.0/docs/conf.py
--- old/deepmerge-2.0/docs/conf.py 2024-08-30 07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/docs/conf.py 2026-06-22 07:45:38.000000000 +0200
@@ -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:
@@ -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'
@@ -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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/docs/guide.rst
new/deepmerge-2.1.0/docs/guide.rst
--- old/deepmerge-2.0/docs/guide.rst 2024-08-30 07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/docs/guide.rst 2026-06-22 07:45:38.000000000 +0200
@@ -30,6 +30,8 @@
"baz": ["a", "b"]
}
+.. _merges_are_destructive:
+
Merges are Destructive
======================
@@ -45,6 +47,72 @@
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
==========================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/deepmerge-2.0/pyproject.toml
new/deepmerge-2.1.0/pyproject.toml
--- old/deepmerge-2.0/pyproject.toml 2024-08-30 07:31:21.000000000 +0200
+++ new/deepmerge-2.1.0/pyproject.toml 2026-06-22 07:45:38.000000000 +0200
@@ -49,6 +49,9 @@
## Build / Release
"build",
"twine",
+ ## Documentation
+ "sphinx",
+ "sphinx-rtd-theme"
]
[tool.setuptools.packages.find]