aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrekby <rekby@ydb.tech>2024-08-16 13:53:46 +0300
committerrekby <rekby@ydb.tech>2024-08-16 14:02:56 +0300
commitf23ace9b7c0c2e8578421e3e640e3d1cc0fe381b (patch)
treeb8e99314de0a9340e442e5fa1a5289cf1a6a8704
parenta63920bcb9be4fb351b4e4e85c7cadc6b331ba18 (diff)
downloadydb-f23ace9b7c0c2e8578421e3e640e3d1cc0fe381b.tar.gz
Export python deepmerge library instead of mergedeep to github.com/ydb-platform/ydb for support python2
98bbe613ba94337077da6f6bb9b519768fdef800
-rw-r--r--contrib/python/deepmerge/py2/.dist-info/METADATA80
-rw-r--r--contrib/python/deepmerge/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/deepmerge/py2/LICENSE (renamed from contrib/python/mergedeep/LICENSE)12
-rw-r--r--contrib/python/deepmerge/py2/README.rst70
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/__init__.py34
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/_version.py5
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/compat.py4
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/exception.py18
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/extended_set.py25
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/merger.py44
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/__init__.py0
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/core.py38
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/dict.py32
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/fallback.py19
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/list.py31
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/set.py30
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/strategy/type_conflict.py22
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/__init__.py0
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/strategy/__init__.py0
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_core.py41
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_list.py33
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_set_merge.py13
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_type_conflict.py22
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/test_full.py50
-rw-r--r--contrib/python/deepmerge/py2/deepmerge/tests/test_merger.py30
-rw-r--r--contrib/python/deepmerge/py2/tests/ya.make25
-rw-r--r--contrib/python/deepmerge/py2/ya.make40
-rw-r--r--contrib/python/deepmerge/py3/.dist-info/METADATA80
-rw-r--r--contrib/python/deepmerge/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/deepmerge/py3/LICENSE21
-rw-r--r--contrib/python/deepmerge/py3/README.rst70
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/__init__.py34
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/_version.py16
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/compat.py4
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/exception.py18
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/extended_set.py25
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/merger.py44
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/__init__.py0
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/core.py38
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/dict.py32
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/fallback.py19
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/list.py31
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/set.py30
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/strategy/type_conflict.py22
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/__init__.py0
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/strategy/__init__.py0
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_core.py41
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_list.py33
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_set_merge.py13
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_type_conflict.py22
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/test_full.py60
-rw-r--r--contrib/python/deepmerge/py3/deepmerge/tests/test_merger.py30
-rw-r--r--contrib/python/deepmerge/py3/tests/ya.make25
-rw-r--r--contrib/python/deepmerge/py3/ya.make40
-rw-r--r--contrib/python/deepmerge/ya.make22
-rw-r--r--contrib/python/mergedeep/.dist-info/METADATA154
-rw-r--r--contrib/python/mergedeep/.dist-info/top_level.txt1
-rw-r--r--contrib/python/mergedeep/.yandex_meta/yamaker.yaml2
-rw-r--r--contrib/python/mergedeep/README.md133
-rw-r--r--contrib/python/mergedeep/mergedeep/__init__.py5
-rw-r--r--contrib/python/mergedeep/mergedeep/mergedeep.py100
-rw-r--r--contrib/python/mergedeep/mergedeep/test_mergedeep.py397
-rw-r--r--contrib/python/mergedeep/tests/ya.make17
-rw-r--r--contrib/python/mergedeep/ya.make29
64 files changed, 1484 insertions, 844 deletions
diff --git a/contrib/python/deepmerge/py2/.dist-info/METADATA b/contrib/python/deepmerge/py2/.dist-info/METADATA
new file mode 100644
index 0000000000..dd6807cf68
--- /dev/null
+++ b/contrib/python/deepmerge/py2/.dist-info/METADATA
@@ -0,0 +1,80 @@
+Metadata-Version: 2.1
+Name: deepmerge
+Version: 1.1.0
+Summary: a toolset to deeply merge python dictionaries.
+Home-page: http://deepmerge.readthedocs.io/en/latest/
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 3
+License-File: LICENSE
+
+=========
+deepmerge
+=========
+
+.. image:: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml/badge.svg
+ :target: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml
+
+A tools to handle merging of
+nested data structures in python.
+
+------------
+Installation
+------------
+
+deepmerge is available on `pypi <https://pypi.python.org/>`_:
+
+.. code-block:: bash
+
+ pip install deepmerge
+
+-------
+Example
+-------
+
+**Generic Strategy**
+
+.. code-block:: python
+
+ from deepmerge import always_merger
+
+ base = {"foo": ["bar"]}
+ next = {"foo": ["baz"]}
+
+ expected_result = {'foo': ['bar', 'baz']}
+ result = always_merger.merge(base, next)
+
+ assert expected_result == result
+
+
+**Custom Strategy**
+
+.. code-block:: python
+
+ from deepmerge import Merger
+
+ my_merger = Merger(
+ # pass in a list of tuple, with the
+ # strategies you are looking to apply
+ # to each type.
+ [
+ (list, ["append"]),
+ (dict, ["merge"]),
+ (set, ["union"])
+ ],
+ # next, choose the fallback strategies,
+ # applied to all other types:
+ ["override"],
+ # finally, choose the strategies in
+ # the case where the types conflict:
+ ["override"]
+ )
+ base = {"foo": ["bar"]}
+ next = {"bar": "baz"}
+ my_merger.merge(base, next)
+ assert base == {"foo": ["bar"], "bar": "baz"}
+
+
+You can also pass in your own merge functions, instead of a string.
+
+For more information, see the `docs <https://deepmerge.readthedocs.io/en/latest/>`_
diff --git a/contrib/python/deepmerge/py2/.dist-info/top_level.txt b/contrib/python/deepmerge/py2/.dist-info/top_level.txt
new file mode 100644
index 0000000000..046e27c3a8
--- /dev/null
+++ b/contrib/python/deepmerge/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+deepmerge
diff --git a/contrib/python/mergedeep/LICENSE b/contrib/python/deepmerge/py2/LICENSE
index 45c6440816..7e0b51be12 100644
--- a/contrib/python/mergedeep/LICENSE
+++ b/contrib/python/deepmerge/py2/LICENSE
@@ -1,6 +1,6 @@
-The MIT License (MIT)
+MIT License
-Copyright (c) 2019 Travis Clarke <travis.m.clarke@gmail.com> (https://www.travismclarke.com/)
+Copyright (c) 2016 Yusuke Tsutsumi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/contrib/python/deepmerge/py2/README.rst b/contrib/python/deepmerge/py2/README.rst
new file mode 100644
index 0000000000..24050eb556
--- /dev/null
+++ b/contrib/python/deepmerge/py2/README.rst
@@ -0,0 +1,70 @@
+=========
+deepmerge
+=========
+
+.. image:: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml/badge.svg
+ :target: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml
+
+A tools to handle merging of
+nested data structures in python.
+
+------------
+Installation
+------------
+
+deepmerge is available on `pypi <https://pypi.python.org/>`_:
+
+.. code-block:: bash
+
+ pip install deepmerge
+
+-------
+Example
+-------
+
+**Generic Strategy**
+
+.. code-block:: python
+
+ from deepmerge import always_merger
+
+ base = {"foo": ["bar"]}
+ next = {"foo": ["baz"]}
+
+ expected_result = {'foo': ['bar', 'baz']}
+ result = always_merger.merge(base, next)
+
+ assert expected_result == result
+
+
+**Custom Strategy**
+
+.. code-block:: python
+
+ from deepmerge import Merger
+
+ my_merger = Merger(
+ # pass in a list of tuple, with the
+ # strategies you are looking to apply
+ # to each type.
+ [
+ (list, ["append"]),
+ (dict, ["merge"]),
+ (set, ["union"])
+ ],
+ # next, choose the fallback strategies,
+ # applied to all other types:
+ ["override"],
+ # finally, choose the strategies in
+ # the case where the types conflict:
+ ["override"]
+ )
+ base = {"foo": ["bar"]}
+ next = {"bar": "baz"}
+ my_merger.merge(base, next)
+ assert base == {"foo": ["bar"], "bar": "baz"}
+
+
+You can also pass in your own merge functions, instead of a string.
+
+For more information, see the `docs <https://deepmerge.readthedocs.io/en/latest/>`_ \ No newline at end of file
diff --git a/contrib/python/deepmerge/py2/deepmerge/__init__.py b/contrib/python/deepmerge/py2/deepmerge/__init__.py
new file mode 100644
index 0000000000..b7686cf91c
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/__init__.py
@@ -0,0 +1,34 @@
+from .merger import Merger
+from .strategy.core import STRATEGY_END # noqa
+
+# some standard mergers available
+
+DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES = [
+ (list, "append"),
+ (dict, "merge"),
+ (set, "union"),
+]
+
+# this merge will never raise an exception.
+# in the case of type mismatches,
+# the value from the second object
+# will override the previous one.
+always_merger = Merger(
+ DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, ["override"], ["override"]
+)
+
+# this merge strategies attempts
+# to merge (append for list, unify for dicts)
+# if possible, but raises an exception
+# in the case of type conflicts.
+merge_or_raise = Merger(DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, [], [])
+
+# a conservative merge tactic:
+# for data structures with a specific
+# strategy, keep the existing value.
+# similar to always_merger but instead
+# keeps existing values when faced
+# with a type conflict.
+conservative_merger = Merger(
+ DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, ["use_existing"], ["use_existing"]
+)
diff --git a/contrib/python/deepmerge/py2/deepmerge/_version.py b/contrib/python/deepmerge/py2/deepmerge/_version.py
new file mode 100644
index 0000000000..bef9f66601
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/_version.py
@@ -0,0 +1,5 @@
+# coding: utf-8
+# file generated by setuptools_scm
+# don't change, don't track in version control
+__version__ = version = '1.1.0'
+__version_tuple__ = version_tuple = (1, 1, 0)
diff --git a/contrib/python/deepmerge/py2/deepmerge/compat.py b/contrib/python/deepmerge/py2/deepmerge/compat.py
new file mode 100644
index 0000000000..d2872a6edf
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/compat.py
@@ -0,0 +1,4 @@
+try:
+ string_type = basestring
+except:
+ string_type = str
diff --git a/contrib/python/deepmerge/py2/deepmerge/exception.py b/contrib/python/deepmerge/py2/deepmerge/exception.py
new file mode 100644
index 0000000000..ea36dac3ba
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/exception.py
@@ -0,0 +1,18 @@
+class DeepMergeException(Exception):
+ pass
+
+
+class StrategyNotFound(DeepMergeException):
+ pass
+
+
+class InvalidMerge(DeepMergeException):
+ def __init__(self, strategy_list_name, merge_args, merge_kwargs):
+ super(InvalidMerge, self).__init__(
+ "no more strategies found for {0} and arguments {1}, {2}".format(
+ strategy_list_name, merge_args, merge_kwargs
+ )
+ )
+ self.strategy_list_name = strategy_list_name
+ self.merge_args = merge_args
+ self.merge_kwargs = merge_kwargs
diff --git a/contrib/python/deepmerge/py2/deepmerge/extended_set.py b/contrib/python/deepmerge/py2/deepmerge/extended_set.py
new file mode 100644
index 0000000000..1d51b431b9
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/extended_set.py
@@ -0,0 +1,25 @@
+class ExtendedSet(set):
+ """
+ ExtendedSet is an extension of set, which allows for usage
+ of types that are typically not allowed in a set
+ (e.g. unhashable).
+
+ The following types that cannot be used in a set are supported:
+
+ - unhashable types
+ """
+
+ def __init__(self, elements):
+ self._values_by_hash = {self._hash(e): e for e in elements}
+
+ def _insert(self, element):
+ self._values_by_hash[self._hash(element)] = element
+
+ def _hash(self, element):
+ if getattr(element, "__hash__") is not None:
+ return hash(element)
+ else:
+ return hash(str(element))
+
+ def __contains__(self, obj):
+ return self._hash(obj) in self._values_by_hash
diff --git a/contrib/python/deepmerge/py2/deepmerge/merger.py b/contrib/python/deepmerge/py2/deepmerge/merger.py
new file mode 100644
index 0000000000..c1f747ef3d
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/merger.py
@@ -0,0 +1,44 @@
+from .strategy.list import ListStrategies
+from .strategy.dict import DictStrategies
+from .strategy.set import SetStrategies
+from .strategy.type_conflict import TypeConflictStrategies
+from .strategy.fallback import FallbackStrategies
+
+
+class Merger(object):
+ """
+ :param type_strategies, List[Tuple]: a list of (Type, Strategy) pairs
+ that should be used against incoming types. For example: (dict, "override").
+ """
+
+ PROVIDED_TYPE_STRATEGIES = {
+ list: ListStrategies,
+ dict: DictStrategies,
+ set: SetStrategies,
+ }
+
+ def __init__(self, type_strategies, fallback_strategies, type_conflict_strategies):
+ self._fallback_strategy = FallbackStrategies(fallback_strategies)
+
+ expanded_type_strategies = []
+ for typ, strategy in type_strategies:
+ if typ in self.PROVIDED_TYPE_STRATEGIES:
+ strategy = self.PROVIDED_TYPE_STRATEGIES[typ](strategy)
+ expanded_type_strategies.append((typ, strategy))
+ self._type_strategies = expanded_type_strategies
+
+ self._type_conflict_strategy = TypeConflictStrategies(type_conflict_strategies)
+
+ def merge(self, base, nxt):
+ return self.value_strategy([], base, nxt)
+
+ def type_conflict_strategy(self, *args):
+ return self._type_conflict_strategy(self, *args)
+
+ def value_strategy(self, path, base, nxt):
+ if not (isinstance(base, type(nxt)) or isinstance(nxt, type(base))):
+ return self.type_conflict_strategy(path, base, nxt)
+ for typ, strategy in self._type_strategies:
+ if isinstance(nxt, typ):
+ return strategy(self, path, base, nxt)
+ return self._fallback_strategy(self, path, base, nxt)
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/__init__.py b/contrib/python/deepmerge/py2/deepmerge/strategy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/__init__.py
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/core.py b/contrib/python/deepmerge/py2/deepmerge/strategy/core.py
new file mode 100644
index 0000000000..91fcfbb070
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/core.py
@@ -0,0 +1,38 @@
+from ..exception import StrategyNotFound, InvalidMerge
+from ..compat import string_type
+
+STRATEGY_END = object()
+
+
+class StrategyList(object):
+
+ NAME = None
+
+ def __init__(self, strategy_list):
+ if not isinstance(strategy_list, list):
+ strategy_list = [strategy_list]
+ self._strategies = [self._expand_strategy(s) for s in strategy_list]
+
+ @classmethod
+ def _expand_strategy(cls, strategy):
+ """
+ :param strategy: string or function
+
+ If the strategy is a string, attempt to resolve it
+ among the built in strategies.
+
+ Otherwise, return the value, implicitly assuming it's a function.
+ """
+ if isinstance(strategy, string_type):
+ method_name = "strategy_{0}".format(strategy)
+ if not hasattr(cls, method_name):
+ raise StrategyNotFound(strategy)
+ return getattr(cls, method_name)
+ return strategy
+
+ def __call__(self, *args, **kwargs):
+ for s in self._strategies:
+ ret_val = s(*args, **kwargs)
+ if ret_val is not STRATEGY_END:
+ return ret_val
+ raise InvalidMerge(self.NAME, args, kwargs)
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/dict.py b/contrib/python/deepmerge/py2/deepmerge/strategy/dict.py
new file mode 100644
index 0000000000..8f09eb984f
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/dict.py
@@ -0,0 +1,32 @@
+from .core import StrategyList
+
+
+class DictStrategies(StrategyList):
+ """
+ Contains the strategies provided for dictionaries.
+
+ """
+
+ NAME = "dict"
+
+ @staticmethod
+ def strategy_merge(config, path, base, nxt):
+ """
+ for keys that do not exists,
+ use them directly. if the key exists
+ in both dictionaries, attempt a value merge.
+ """
+ for k, v in nxt.items():
+ if k not in base:
+ base[k] = v
+ else:
+ base[k] = config.value_strategy(path + [k], base[k], v)
+ return base
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """
+ move all keys in nxt into base, overriding
+ conflicts.
+ """
+ return nxt
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/fallback.py b/contrib/python/deepmerge/py2/deepmerge/strategy/fallback.py
new file mode 100644
index 0000000000..714e8f9f59
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/fallback.py
@@ -0,0 +1,19 @@
+from .core import StrategyList
+
+
+class FallbackStrategies(StrategyList):
+ """
+ The StrategyList containing fallback strategies.
+ """
+
+ NAME = "fallback"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """use nxt, and ignore base."""
+ return nxt
+
+ @staticmethod
+ def strategy_use_existing(config, path, base, nxt):
+ """use base, and ignore next."""
+ return base
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/list.py b/contrib/python/deepmerge/py2/deepmerge/strategy/list.py
new file mode 100644
index 0000000000..2e42519fd5
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/list.py
@@ -0,0 +1,31 @@
+from .core import StrategyList
+from ..extended_set import ExtendedSet
+
+
+class ListStrategies(StrategyList):
+ """
+ Contains the strategies provided for lists.
+ """
+
+ NAME = "list"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """use the list nxt."""
+ return nxt
+
+ @staticmethod
+ def strategy_prepend(config, path, base, nxt):
+ """prepend nxt to base."""
+ return nxt + base
+
+ @staticmethod
+ def strategy_append(config, path, base, nxt):
+ """append nxt to base."""
+ return base + nxt
+
+ @staticmethod
+ def strategy_append_unique(config, path, base, nxt):
+ """append items without duplicates in nxt to base."""
+ base_as_set = ExtendedSet(base)
+ return base + [n for n in nxt if n not in base_as_set]
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/set.py b/contrib/python/deepmerge/py2/deepmerge/strategy/set.py
new file mode 100644
index 0000000000..55b26e3995
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/set.py
@@ -0,0 +1,30 @@
+from .core import StrategyList
+
+
+class SetStrategies(StrategyList):
+ """
+ Contains the strategies provided for sets.
+ """
+
+ NAME = "set"
+
+ @staticmethod
+ def strategy_union(config, path, base, nxt):
+ """
+ use all values in either base or nxt.
+ """
+ return base | nxt
+
+ @staticmethod
+ def strategy_intersect(config, path, base, nxt):
+ """
+ use all values in both base and nxt.
+ """
+ return base & nxt
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """
+ use the set nxt.
+ """
+ return nxt
diff --git a/contrib/python/deepmerge/py2/deepmerge/strategy/type_conflict.py b/contrib/python/deepmerge/py2/deepmerge/strategy/type_conflict.py
new file mode 100644
index 0000000000..ebbc77a4ee
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/strategy/type_conflict.py
@@ -0,0 +1,22 @@
+from .core import StrategyList
+
+
+class TypeConflictStrategies(StrategyList):
+ """contains the strategies provided for type conflicts."""
+
+ NAME = "type conflict"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """overrides the new object over the old object"""
+ return nxt
+
+ @staticmethod
+ def strategy_use_existing(config, path, base, nxt):
+ """uses the old object instead of the new object"""
+ return base
+
+ @staticmethod
+ def strategy_override_if_not_empty(config, path, base, nxt):
+ """overrides the new object over the old object only if the new object is not empty or null"""
+ return nxt if nxt else base
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/__init__.py b/contrib/python/deepmerge/py2/deepmerge/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/__init__.py
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/strategy/__init__.py b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/__init__.py
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_core.py b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_core.py
new file mode 100644
index 0000000000..afa76d7299
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_core.py
@@ -0,0 +1,41 @@
+from deepmerge.strategy.core import StrategyList
+from deepmerge import STRATEGY_END
+
+
+def return_true_if_foo(config, path, base, nxt):
+ if base == "foo":
+ return True
+ return STRATEGY_END
+
+
+def always_return_custom(config, path, base, nxt):
+ return "custom"
+
+
+def test_single_value_allowed():
+ """ """
+
+ def strat(name):
+ return name
+
+ sl = StrategyList(strat)
+ assert sl("foo") == "foo"
+
+
+def test_first_working_strategy_is_used():
+ """
+ In the case where the StrategyList has multiple values,
+ the first strategy which returns a valid value (i.e. not STRATEGY_END)
+ should be returned.
+ """
+ sl = StrategyList(
+ [
+ return_true_if_foo,
+ always_return_custom,
+ ]
+ )
+ # return_true_if_foo will take.
+ assert sl({}, [], "foo", "bar") is True
+ # return_true_if_foo will fail,
+ # which will then activea always_return_custom
+ assert sl({}, [], "bar", "baz") == "custom"
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_list.py b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_list.py
new file mode 100644
index 0000000000..7eb2d3bb9d
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_list.py
@@ -0,0 +1,33 @@
+import pytest
+from deepmerge.strategy.list import ListStrategies
+from deepmerge import Merger
+
+
+@pytest.fixture
+def custom_merger():
+ return Merger(
+ [(list, ListStrategies.strategy_append_unique)],
+ [],
+ [],
+ )
+
+
+def test_strategy_append_unique(custom_merger):
+ base = [1, 3, 2]
+ nxt = [3, 5, 4, 1, 2]
+
+ expected = [1, 3, 2, 5, 4]
+ actual = custom_merger.merge(base, nxt)
+ assert actual == expected
+
+
+def test_strategy_append_unique_nested_dict(custom_merger):
+ """append_unique should work even with unhashable objects
+ Like dicts.
+ """
+ base = [{"bar": ["bob"]}]
+ nxt = [{"bar": ["baz"]}]
+
+ result = custom_merger.merge(base, nxt)
+
+ assert result == [{"bar": ["bob"]}, {"bar": ["baz"]}]
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_set_merge.py b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_set_merge.py
new file mode 100644
index 0000000000..72df386cc0
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_set_merge.py
@@ -0,0 +1,13 @@
+from deepmerge.strategy.set import SetStrategies
+
+
+def test_union_unions():
+ assert SetStrategies.strategy_union({}, [], set("abc"), set("bcd")) == set("abcd")
+
+
+def test_intersect_intersects():
+ assert SetStrategies.strategy_intersect({}, [], set("abc"), set("bcd")) == set("bc")
+
+
+def test_override_overrides():
+ assert SetStrategies.strategy_override({}, [], set("abc"), set("bcd")) == set("bcd")
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_type_conflict.py b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_type_conflict.py
new file mode 100644
index 0000000000..a366351e4d
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_type_conflict.py
@@ -0,0 +1,22 @@
+from deepmerge.strategy.type_conflict import TypeConflictStrategies
+
+EMPTY_DICT = {}
+
+CONTENT_AS_LIST = [{"key": "val"}]
+
+
+def test_merge_if_not_empty():
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], EMPTY_DICT, CONTENT_AS_LIST
+ )
+ assert strategy == CONTENT_AS_LIST
+
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], CONTENT_AS_LIST, EMPTY_DICT
+ )
+ assert strategy == CONTENT_AS_LIST
+
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], CONTENT_AS_LIST, None
+ )
+ assert strategy == CONTENT_AS_LIST
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/test_full.py b/contrib/python/deepmerge/py2/deepmerge/tests/test_full.py
new file mode 100644
index 0000000000..c7710cdf91
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/test_full.py
@@ -0,0 +1,50 @@
+from deepmerge.exception import *
+import pytest
+
+
+from deepmerge import (
+ always_merger,
+ conservative_merger,
+ merge_or_raise,
+)
+
+
+def test_fill_missing_value():
+ base = {"foo": 0, "baz": 2}
+ nxt = {"bar": 1}
+ always_merger.merge(base, nxt)
+ assert base == {"foo": 0, "bar": 1, "baz": 2}
+
+
+def test_handles_set_values_via_union():
+ base = {"a": set("123"), "b": 3}
+ nxt = {"a": set("2345"), "c": 1}
+ always_merger.merge(base, nxt)
+ assert base == {"a": set("12345"), "b": 3, "c": 1}
+
+
+def test_merge_or_raise_raises_exception():
+ base = {"foo": 0, "baz": 2}
+ nxt = {"bar": 1, "foo": "a string!"}
+ with pytest.raises(InvalidMerge) as exc_info:
+ merge_or_raise.merge(base, nxt)
+ exc = exc_info.value
+ assert exc.strategy_list_name == "type conflict"
+ assert exc.merge_args == (merge_or_raise, ["foo"], 0, "a string!")
+ assert exc.merge_kwargs == {}
+
+
+@pytest.mark.parametrize(
+ "base, nxt, expected", [("dooby", "fooby", "dooby"), (-10, "goo", -10)]
+)
+def test_use_existing(base, nxt, expected):
+ assert conservative_merger.merge(base, nxt) == expected
+
+
+def test_example():
+ base = {"foo": "value", "baz": ["a"]}
+ next = {"bar": "value2", "baz": ["b"]}
+
+ always_merger.merge(base, next)
+
+ assert base == {"foo": "value", "bar": "value2", "baz": ["a", "b"]}
diff --git a/contrib/python/deepmerge/py2/deepmerge/tests/test_merger.py b/contrib/python/deepmerge/py2/deepmerge/tests/test_merger.py
new file mode 100644
index 0000000000..e28b571d6c
--- /dev/null
+++ b/contrib/python/deepmerge/py2/deepmerge/tests/test_merger.py
@@ -0,0 +1,30 @@
+import pytest
+from deepmerge import Merger
+
+
+@pytest.fixture
+def custom_merger():
+ def merge_sets(merger, path, base, nxt):
+ base |= nxt
+ return base
+
+ def merge_list(merger, path, base, nxt):
+ if len(nxt) > 0:
+ base.append(nxt[-1])
+ return base
+
+ return Merger(
+ [(list, merge_list), (dict, "merge"), (set, merge_sets)],
+ [],
+ [],
+ )
+
+
+def test_custom_merger_applied(custom_merger):
+ result = custom_merger.merge({"foo"}, {"bar"})
+ assert result == {"foo", "bar"}
+
+
+def test_custom_merger_list(custom_merger):
+ result = custom_merger.merge([1, 2, 3], [4, 5, 6])
+ assert result == [1, 2, 3, 6]
diff --git a/contrib/python/deepmerge/py2/tests/ya.make b/contrib/python/deepmerge/py2/tests/ya.make
new file mode 100644
index 0000000000..c0d3cfaeda
--- /dev/null
+++ b/contrib/python/deepmerge/py2/tests/ya.make
@@ -0,0 +1,25 @@
+PY2TEST()
+
+SUBSCRIBER(g:python-contrib)
+
+PEERDIR(
+ contrib/python/deepmerge
+)
+
+SRCDIR(
+ contrib/python/deepmerge/py2/deepmerge/tests
+)
+
+TEST_SRCS(
+ __init__.py
+ strategy/__init__.py
+ strategy/test_core.py
+ strategy/test_set_merge.py
+ strategy/test_type_conflict.py
+ test_full.py
+ test_merger.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/deepmerge/py2/ya.make b/contrib/python/deepmerge/py2/ya.make
new file mode 100644
index 0000000000..5ae850071c
--- /dev/null
+++ b/contrib/python/deepmerge/py2/ya.make
@@ -0,0 +1,40 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+SUBSCRIBER(g:python-contrib)
+
+VERSION(1.1.0)
+
+LICENSE(MIT)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ deepmerge/__init__.py
+ deepmerge/_version.py
+ deepmerge/compat.py
+ deepmerge/exception.py
+ deepmerge/extended_set.py
+ deepmerge/merger.py
+ deepmerge/strategy/__init__.py
+ deepmerge/strategy/core.py
+ deepmerge/strategy/dict.py
+ deepmerge/strategy/fallback.py
+ deepmerge/strategy/list.py
+ deepmerge/strategy/set.py
+ deepmerge/strategy/type_conflict.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/deepmerge/py2/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)
diff --git a/contrib/python/deepmerge/py3/.dist-info/METADATA b/contrib/python/deepmerge/py3/.dist-info/METADATA
new file mode 100644
index 0000000000..9312291976
--- /dev/null
+++ b/contrib/python/deepmerge/py3/.dist-info/METADATA
@@ -0,0 +1,80 @@
+Metadata-Version: 2.1
+Name: deepmerge
+Version: 1.1.1
+Summary: a toolset to deeply merge python dictionaries.
+Home-page: http://deepmerge.readthedocs.io/en/latest/
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 3
+License-File: LICENSE
+
+=========
+deepmerge
+=========
+
+.. image:: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml/badge.svg
+ :target: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml
+
+A tools to handle merging of
+nested data structures in python.
+
+------------
+Installation
+------------
+
+deepmerge is available on `pypi <https://pypi.python.org/>`_:
+
+.. code-block:: bash
+
+ pip install deepmerge
+
+-------
+Example
+-------
+
+**Generic Strategy**
+
+.. code-block:: python
+
+ from deepmerge import always_merger
+
+ base = {"foo": ["bar"]}
+ next = {"foo": ["baz"]}
+
+ expected_result = {'foo': ['bar', 'baz']}
+ result = always_merger.merge(base, next)
+
+ assert expected_result == result
+
+
+**Custom Strategy**
+
+.. code-block:: python
+
+ from deepmerge import Merger
+
+ my_merger = Merger(
+ # pass in a list of tuple, with the
+ # strategies you are looking to apply
+ # to each type.
+ [
+ (list, ["append"]),
+ (dict, ["merge"]),
+ (set, ["union"])
+ ],
+ # next, choose the fallback strategies,
+ # applied to all other types:
+ ["override"],
+ # finally, choose the strategies in
+ # the case where the types conflict:
+ ["override"]
+ )
+ base = {"foo": ["bar"]}
+ next = {"bar": "baz"}
+ my_merger.merge(base, next)
+ assert base == {"foo": ["bar"], "bar": "baz"}
+
+
+You can also pass in your own merge functions, instead of a string.
+
+For more information, see the `docs <https://deepmerge.readthedocs.io/en/latest/>`_
diff --git a/contrib/python/deepmerge/py3/.dist-info/top_level.txt b/contrib/python/deepmerge/py3/.dist-info/top_level.txt
new file mode 100644
index 0000000000..046e27c3a8
--- /dev/null
+++ b/contrib/python/deepmerge/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+deepmerge
diff --git a/contrib/python/deepmerge/py3/LICENSE b/contrib/python/deepmerge/py3/LICENSE
new file mode 100644
index 0000000000..7e0b51be12
--- /dev/null
+++ b/contrib/python/deepmerge/py3/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Yusuke Tsutsumi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/contrib/python/deepmerge/py3/README.rst b/contrib/python/deepmerge/py3/README.rst
new file mode 100644
index 0000000000..24050eb556
--- /dev/null
+++ b/contrib/python/deepmerge/py3/README.rst
@@ -0,0 +1,70 @@
+=========
+deepmerge
+=========
+
+.. image:: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml/badge.svg
+ :target: https://github.com/toumorokoshi/deepmerge/actions/workflows/python-package.yaml
+
+A tools to handle merging of
+nested data structures in python.
+
+------------
+Installation
+------------
+
+deepmerge is available on `pypi <https://pypi.python.org/>`_:
+
+.. code-block:: bash
+
+ pip install deepmerge
+
+-------
+Example
+-------
+
+**Generic Strategy**
+
+.. code-block:: python
+
+ from deepmerge import always_merger
+
+ base = {"foo": ["bar"]}
+ next = {"foo": ["baz"]}
+
+ expected_result = {'foo': ['bar', 'baz']}
+ result = always_merger.merge(base, next)
+
+ assert expected_result == result
+
+
+**Custom Strategy**
+
+.. code-block:: python
+
+ from deepmerge import Merger
+
+ my_merger = Merger(
+ # pass in a list of tuple, with the
+ # strategies you are looking to apply
+ # to each type.
+ [
+ (list, ["append"]),
+ (dict, ["merge"]),
+ (set, ["union"])
+ ],
+ # next, choose the fallback strategies,
+ # applied to all other types:
+ ["override"],
+ # finally, choose the strategies in
+ # the case where the types conflict:
+ ["override"]
+ )
+ base = {"foo": ["bar"]}
+ next = {"bar": "baz"}
+ my_merger.merge(base, next)
+ assert base == {"foo": ["bar"], "bar": "baz"}
+
+
+You can also pass in your own merge functions, instead of a string.
+
+For more information, see the `docs <https://deepmerge.readthedocs.io/en/latest/>`_ \ No newline at end of file
diff --git a/contrib/python/deepmerge/py3/deepmerge/__init__.py b/contrib/python/deepmerge/py3/deepmerge/__init__.py
new file mode 100644
index 0000000000..b7686cf91c
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/__init__.py
@@ -0,0 +1,34 @@
+from .merger import Merger
+from .strategy.core import STRATEGY_END # noqa
+
+# some standard mergers available
+
+DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES = [
+ (list, "append"),
+ (dict, "merge"),
+ (set, "union"),
+]
+
+# this merge will never raise an exception.
+# in the case of type mismatches,
+# the value from the second object
+# will override the previous one.
+always_merger = Merger(
+ DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, ["override"], ["override"]
+)
+
+# this merge strategies attempts
+# to merge (append for list, unify for dicts)
+# if possible, but raises an exception
+# in the case of type conflicts.
+merge_or_raise = Merger(DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, [], [])
+
+# a conservative merge tactic:
+# for data structures with a specific
+# strategy, keep the existing value.
+# similar to always_merger but instead
+# keeps existing values when faced
+# with a type conflict.
+conservative_merger = Merger(
+ DEFAULT_TYPE_SPECIFIC_MERGE_STRATEGIES, ["use_existing"], ["use_existing"]
+)
diff --git a/contrib/python/deepmerge/py3/deepmerge/_version.py b/contrib/python/deepmerge/py3/deepmerge/_version.py
new file mode 100644
index 0000000000..e602b7bb58
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/_version.py
@@ -0,0 +1,16 @@
+# file generated by setuptools_scm
+# 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
+
+version: str
+__version__: str
+__version_tuple__: VERSION_TUPLE
+version_tuple: VERSION_TUPLE
+
+__version__ = version = '1.1.1'
+__version_tuple__ = version_tuple = (1, 1, 1)
diff --git a/contrib/python/deepmerge/py3/deepmerge/compat.py b/contrib/python/deepmerge/py3/deepmerge/compat.py
new file mode 100644
index 0000000000..d2872a6edf
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/compat.py
@@ -0,0 +1,4 @@
+try:
+ string_type = basestring
+except:
+ string_type = str
diff --git a/contrib/python/deepmerge/py3/deepmerge/exception.py b/contrib/python/deepmerge/py3/deepmerge/exception.py
new file mode 100644
index 0000000000..ea36dac3ba
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/exception.py
@@ -0,0 +1,18 @@
+class DeepMergeException(Exception):
+ pass
+
+
+class StrategyNotFound(DeepMergeException):
+ pass
+
+
+class InvalidMerge(DeepMergeException):
+ def __init__(self, strategy_list_name, merge_args, merge_kwargs):
+ super(InvalidMerge, self).__init__(
+ "no more strategies found for {0} and arguments {1}, {2}".format(
+ strategy_list_name, merge_args, merge_kwargs
+ )
+ )
+ self.strategy_list_name = strategy_list_name
+ self.merge_args = merge_args
+ self.merge_kwargs = merge_kwargs
diff --git a/contrib/python/deepmerge/py3/deepmerge/extended_set.py b/contrib/python/deepmerge/py3/deepmerge/extended_set.py
new file mode 100644
index 0000000000..1d51b431b9
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/extended_set.py
@@ -0,0 +1,25 @@
+class ExtendedSet(set):
+ """
+ ExtendedSet is an extension of set, which allows for usage
+ of types that are typically not allowed in a set
+ (e.g. unhashable).
+
+ The following types that cannot be used in a set are supported:
+
+ - unhashable types
+ """
+
+ def __init__(self, elements):
+ self._values_by_hash = {self._hash(e): e for e in elements}
+
+ def _insert(self, element):
+ self._values_by_hash[self._hash(element)] = element
+
+ def _hash(self, element):
+ if getattr(element, "__hash__") is not None:
+ return hash(element)
+ else:
+ return hash(str(element))
+
+ def __contains__(self, obj):
+ return self._hash(obj) in self._values_by_hash
diff --git a/contrib/python/deepmerge/py3/deepmerge/merger.py b/contrib/python/deepmerge/py3/deepmerge/merger.py
new file mode 100644
index 0000000000..5ccd568755
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/merger.py
@@ -0,0 +1,44 @@
+from .strategy.list import ListStrategies
+from .strategy.dict import DictStrategies
+from .strategy.set import SetStrategies
+from .strategy.type_conflict import TypeConflictStrategies
+from .strategy.fallback import FallbackStrategies
+
+
+class Merger(object):
+ """
+ :param type_strategies, List[Tuple]: a list of (Type, Strategy) pairs
+ that should be used against incoming types. For example: (dict, "override").
+ """
+
+ PROVIDED_TYPE_STRATEGIES = {
+ list: ListStrategies,
+ dict: DictStrategies,
+ set: SetStrategies,
+ }
+
+ def __init__(self, type_strategies, fallback_strategies, type_conflict_strategies):
+ self._fallback_strategy = FallbackStrategies(fallback_strategies)
+
+ expanded_type_strategies = []
+ for typ, strategy in type_strategies:
+ if typ in self.PROVIDED_TYPE_STRATEGIES:
+ strategy = self.PROVIDED_TYPE_STRATEGIES[typ](strategy)
+ expanded_type_strategies.append((typ, strategy))
+ self._type_strategies = expanded_type_strategies
+
+ self._type_conflict_strategy = TypeConflictStrategies(type_conflict_strategies)
+
+ def merge(self, base, nxt):
+ return self.value_strategy([], base, nxt)
+
+ def type_conflict_strategy(self, *args):
+ return self._type_conflict_strategy(self, *args)
+
+ def value_strategy(self, path, base, nxt):
+ for typ, strategy in self._type_strategies:
+ if isinstance(base, typ) and isinstance(nxt, typ):
+ return strategy(self, path, base, nxt)
+ if not (isinstance(base, type(nxt)) or isinstance(nxt, type(base))):
+ return self.type_conflict_strategy(path, base, nxt)
+ return self._fallback_strategy(self, path, base, nxt)
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/__init__.py b/contrib/python/deepmerge/py3/deepmerge/strategy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/__init__.py
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/core.py b/contrib/python/deepmerge/py3/deepmerge/strategy/core.py
new file mode 100644
index 0000000000..91fcfbb070
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/core.py
@@ -0,0 +1,38 @@
+from ..exception import StrategyNotFound, InvalidMerge
+from ..compat import string_type
+
+STRATEGY_END = object()
+
+
+class StrategyList(object):
+
+ NAME = None
+
+ def __init__(self, strategy_list):
+ if not isinstance(strategy_list, list):
+ strategy_list = [strategy_list]
+ self._strategies = [self._expand_strategy(s) for s in strategy_list]
+
+ @classmethod
+ def _expand_strategy(cls, strategy):
+ """
+ :param strategy: string or function
+
+ If the strategy is a string, attempt to resolve it
+ among the built in strategies.
+
+ Otherwise, return the value, implicitly assuming it's a function.
+ """
+ if isinstance(strategy, string_type):
+ method_name = "strategy_{0}".format(strategy)
+ if not hasattr(cls, method_name):
+ raise StrategyNotFound(strategy)
+ return getattr(cls, method_name)
+ return strategy
+
+ def __call__(self, *args, **kwargs):
+ for s in self._strategies:
+ ret_val = s(*args, **kwargs)
+ if ret_val is not STRATEGY_END:
+ return ret_val
+ raise InvalidMerge(self.NAME, args, kwargs)
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/dict.py b/contrib/python/deepmerge/py3/deepmerge/strategy/dict.py
new file mode 100644
index 0000000000..8f09eb984f
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/dict.py
@@ -0,0 +1,32 @@
+from .core import StrategyList
+
+
+class DictStrategies(StrategyList):
+ """
+ Contains the strategies provided for dictionaries.
+
+ """
+
+ NAME = "dict"
+
+ @staticmethod
+ def strategy_merge(config, path, base, nxt):
+ """
+ for keys that do not exists,
+ use them directly. if the key exists
+ in both dictionaries, attempt a value merge.
+ """
+ for k, v in nxt.items():
+ if k not in base:
+ base[k] = v
+ else:
+ base[k] = config.value_strategy(path + [k], base[k], v)
+ return base
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """
+ move all keys in nxt into base, overriding
+ conflicts.
+ """
+ return nxt
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/fallback.py b/contrib/python/deepmerge/py3/deepmerge/strategy/fallback.py
new file mode 100644
index 0000000000..714e8f9f59
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/fallback.py
@@ -0,0 +1,19 @@
+from .core import StrategyList
+
+
+class FallbackStrategies(StrategyList):
+ """
+ The StrategyList containing fallback strategies.
+ """
+
+ NAME = "fallback"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """use nxt, and ignore base."""
+ return nxt
+
+ @staticmethod
+ def strategy_use_existing(config, path, base, nxt):
+ """use base, and ignore next."""
+ return base
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/list.py b/contrib/python/deepmerge/py3/deepmerge/strategy/list.py
new file mode 100644
index 0000000000..2e42519fd5
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/list.py
@@ -0,0 +1,31 @@
+from .core import StrategyList
+from ..extended_set import ExtendedSet
+
+
+class ListStrategies(StrategyList):
+ """
+ Contains the strategies provided for lists.
+ """
+
+ NAME = "list"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """use the list nxt."""
+ return nxt
+
+ @staticmethod
+ def strategy_prepend(config, path, base, nxt):
+ """prepend nxt to base."""
+ return nxt + base
+
+ @staticmethod
+ def strategy_append(config, path, base, nxt):
+ """append nxt to base."""
+ return base + nxt
+
+ @staticmethod
+ def strategy_append_unique(config, path, base, nxt):
+ """append items without duplicates in nxt to base."""
+ base_as_set = ExtendedSet(base)
+ return base + [n for n in nxt if n not in base_as_set]
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/set.py b/contrib/python/deepmerge/py3/deepmerge/strategy/set.py
new file mode 100644
index 0000000000..55b26e3995
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/set.py
@@ -0,0 +1,30 @@
+from .core import StrategyList
+
+
+class SetStrategies(StrategyList):
+ """
+ Contains the strategies provided for sets.
+ """
+
+ NAME = "set"
+
+ @staticmethod
+ def strategy_union(config, path, base, nxt):
+ """
+ use all values in either base or nxt.
+ """
+ return base | nxt
+
+ @staticmethod
+ def strategy_intersect(config, path, base, nxt):
+ """
+ use all values in both base and nxt.
+ """
+ return base & nxt
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """
+ use the set nxt.
+ """
+ return nxt
diff --git a/contrib/python/deepmerge/py3/deepmerge/strategy/type_conflict.py b/contrib/python/deepmerge/py3/deepmerge/strategy/type_conflict.py
new file mode 100644
index 0000000000..ebbc77a4ee
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/strategy/type_conflict.py
@@ -0,0 +1,22 @@
+from .core import StrategyList
+
+
+class TypeConflictStrategies(StrategyList):
+ """contains the strategies provided for type conflicts."""
+
+ NAME = "type conflict"
+
+ @staticmethod
+ def strategy_override(config, path, base, nxt):
+ """overrides the new object over the old object"""
+ return nxt
+
+ @staticmethod
+ def strategy_use_existing(config, path, base, nxt):
+ """uses the old object instead of the new object"""
+ return base
+
+ @staticmethod
+ def strategy_override_if_not_empty(config, path, base, nxt):
+ """overrides the new object over the old object only if the new object is not empty or null"""
+ return nxt if nxt else base
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/__init__.py b/contrib/python/deepmerge/py3/deepmerge/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/__init__.py
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/strategy/__init__.py b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/__init__.py
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_core.py b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_core.py
new file mode 100644
index 0000000000..afa76d7299
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_core.py
@@ -0,0 +1,41 @@
+from deepmerge.strategy.core import StrategyList
+from deepmerge import STRATEGY_END
+
+
+def return_true_if_foo(config, path, base, nxt):
+ if base == "foo":
+ return True
+ return STRATEGY_END
+
+
+def always_return_custom(config, path, base, nxt):
+ return "custom"
+
+
+def test_single_value_allowed():
+ """ """
+
+ def strat(name):
+ return name
+
+ sl = StrategyList(strat)
+ assert sl("foo") == "foo"
+
+
+def test_first_working_strategy_is_used():
+ """
+ In the case where the StrategyList has multiple values,
+ the first strategy which returns a valid value (i.e. not STRATEGY_END)
+ should be returned.
+ """
+ sl = StrategyList(
+ [
+ return_true_if_foo,
+ always_return_custom,
+ ]
+ )
+ # return_true_if_foo will take.
+ assert sl({}, [], "foo", "bar") is True
+ # return_true_if_foo will fail,
+ # which will then activea always_return_custom
+ assert sl({}, [], "bar", "baz") == "custom"
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_list.py b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_list.py
new file mode 100644
index 0000000000..7eb2d3bb9d
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_list.py
@@ -0,0 +1,33 @@
+import pytest
+from deepmerge.strategy.list import ListStrategies
+from deepmerge import Merger
+
+
+@pytest.fixture
+def custom_merger():
+ return Merger(
+ [(list, ListStrategies.strategy_append_unique)],
+ [],
+ [],
+ )
+
+
+def test_strategy_append_unique(custom_merger):
+ base = [1, 3, 2]
+ nxt = [3, 5, 4, 1, 2]
+
+ expected = [1, 3, 2, 5, 4]
+ actual = custom_merger.merge(base, nxt)
+ assert actual == expected
+
+
+def test_strategy_append_unique_nested_dict(custom_merger):
+ """append_unique should work even with unhashable objects
+ Like dicts.
+ """
+ base = [{"bar": ["bob"]}]
+ nxt = [{"bar": ["baz"]}]
+
+ result = custom_merger.merge(base, nxt)
+
+ assert result == [{"bar": ["bob"]}, {"bar": ["baz"]}]
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_set_merge.py b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_set_merge.py
new file mode 100644
index 0000000000..72df386cc0
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_set_merge.py
@@ -0,0 +1,13 @@
+from deepmerge.strategy.set import SetStrategies
+
+
+def test_union_unions():
+ assert SetStrategies.strategy_union({}, [], set("abc"), set("bcd")) == set("abcd")
+
+
+def test_intersect_intersects():
+ assert SetStrategies.strategy_intersect({}, [], set("abc"), set("bcd")) == set("bc")
+
+
+def test_override_overrides():
+ assert SetStrategies.strategy_override({}, [], set("abc"), set("bcd")) == set("bcd")
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_type_conflict.py b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_type_conflict.py
new file mode 100644
index 0000000000..a366351e4d
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_type_conflict.py
@@ -0,0 +1,22 @@
+from deepmerge.strategy.type_conflict import TypeConflictStrategies
+
+EMPTY_DICT = {}
+
+CONTENT_AS_LIST = [{"key": "val"}]
+
+
+def test_merge_if_not_empty():
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], EMPTY_DICT, CONTENT_AS_LIST
+ )
+ assert strategy == CONTENT_AS_LIST
+
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], CONTENT_AS_LIST, EMPTY_DICT
+ )
+ assert strategy == CONTENT_AS_LIST
+
+ strategy = TypeConflictStrategies.strategy_override_if_not_empty(
+ {}, [], CONTENT_AS_LIST, None
+ )
+ assert strategy == CONTENT_AS_LIST
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/test_full.py b/contrib/python/deepmerge/py3/deepmerge/tests/test_full.py
new file mode 100644
index 0000000000..799db192c1
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/test_full.py
@@ -0,0 +1,60 @@
+from deepmerge.exception import *
+from collections import OrderedDict, defaultdict
+import pytest
+
+
+from deepmerge import (
+ always_merger,
+ conservative_merger,
+ merge_or_raise,
+)
+
+
+def test_fill_missing_value():
+ base = {"foo": 0, "baz": 2}
+ nxt = {"bar": 1}
+ always_merger.merge(base, nxt)
+ assert base == {"foo": 0, "bar": 1, "baz": 2}
+
+
+def test_handles_set_values_via_union():
+ base = {"a": set("123"), "b": 3}
+ nxt = {"a": set("2345"), "c": 1}
+ always_merger.merge(base, nxt)
+ assert base == {"a": set("12345"), "b": 3, "c": 1}
+
+
+def test_merge_or_raise_raises_exception():
+ base = {"foo": 0, "baz": 2}
+ nxt = {"bar": 1, "foo": "a string!"}
+ with pytest.raises(InvalidMerge) as exc_info:
+ merge_or_raise.merge(base, nxt)
+ exc = exc_info.value
+ assert exc.strategy_list_name == "type conflict"
+ assert exc.merge_args == (merge_or_raise, ["foo"], 0, "a string!")
+ assert exc.merge_kwargs == {}
+
+
+@pytest.mark.parametrize(
+ "base, nxt, expected", [("dooby", "fooby", "dooby"), (-10, "goo", -10)]
+)
+def test_use_existing(base, nxt, expected):
+ assert conservative_merger.merge(base, nxt) == expected
+
+
+def test_example():
+ base = {"foo": "value", "baz": ["a"]}
+ next = {"bar": "value2", "baz": ["b"]}
+
+ always_merger.merge(base, next)
+
+ assert base == {"foo": "value", "bar": "value2", "baz": ["a", "b"]}
+
+
+def test_subtypes():
+ base = OrderedDict({"foo": "value", "baz": ["a"]})
+ next = defaultdict(str, {"bar": "value2", "baz": ["b"]})
+
+ result = always_merger.merge(base, next)
+
+ assert dict(result) == {"foo": "value", "bar": "value2", "baz": ["a", "b"]}
diff --git a/contrib/python/deepmerge/py3/deepmerge/tests/test_merger.py b/contrib/python/deepmerge/py3/deepmerge/tests/test_merger.py
new file mode 100644
index 0000000000..e28b571d6c
--- /dev/null
+++ b/contrib/python/deepmerge/py3/deepmerge/tests/test_merger.py
@@ -0,0 +1,30 @@
+import pytest
+from deepmerge import Merger
+
+
+@pytest.fixture
+def custom_merger():
+ def merge_sets(merger, path, base, nxt):
+ base |= nxt
+ return base
+
+ def merge_list(merger, path, base, nxt):
+ if len(nxt) > 0:
+ base.append(nxt[-1])
+ return base
+
+ return Merger(
+ [(list, merge_list), (dict, "merge"), (set, merge_sets)],
+ [],
+ [],
+ )
+
+
+def test_custom_merger_applied(custom_merger):
+ result = custom_merger.merge({"foo"}, {"bar"})
+ assert result == {"foo", "bar"}
+
+
+def test_custom_merger_list(custom_merger):
+ result = custom_merger.merge([1, 2, 3], [4, 5, 6])
+ assert result == [1, 2, 3, 6]
diff --git a/contrib/python/deepmerge/py3/tests/ya.make b/contrib/python/deepmerge/py3/tests/ya.make
new file mode 100644
index 0000000000..6117ded073
--- /dev/null
+++ b/contrib/python/deepmerge/py3/tests/ya.make
@@ -0,0 +1,25 @@
+PY3TEST()
+
+SUBSCRIBER(g:python-contrib)
+
+PEERDIR(
+ contrib/python/deepmerge
+)
+
+SRCDIR(
+ contrib/python/deepmerge/py3/deepmerge/tests
+)
+
+TEST_SRCS(
+ __init__.py
+ strategy/__init__.py
+ strategy/test_core.py
+ strategy/test_set_merge.py
+ strategy/test_type_conflict.py
+ test_full.py
+ test_merger.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/deepmerge/py3/ya.make b/contrib/python/deepmerge/py3/ya.make
new file mode 100644
index 0000000000..e30cae0333
--- /dev/null
+++ b/contrib/python/deepmerge/py3/ya.make
@@ -0,0 +1,40 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+SUBSCRIBER(g:python-contrib)
+
+VERSION(1.1.1)
+
+LICENSE(MIT)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ deepmerge/__init__.py
+ deepmerge/_version.py
+ deepmerge/compat.py
+ deepmerge/exception.py
+ deepmerge/extended_set.py
+ deepmerge/merger.py
+ deepmerge/strategy/__init__.py
+ deepmerge/strategy/core.py
+ deepmerge/strategy/dict.py
+ deepmerge/strategy/fallback.py
+ deepmerge/strategy/list.py
+ deepmerge/strategy/set.py
+ deepmerge/strategy/type_conflict.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/deepmerge/py3/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)
diff --git a/contrib/python/deepmerge/ya.make b/contrib/python/deepmerge/ya.make
new file mode 100644
index 0000000000..1e596fbb3e
--- /dev/null
+++ b/contrib/python/deepmerge/ya.make
@@ -0,0 +1,22 @@
+PY23_LIBRARY()
+
+LICENSE(Service-Py23-Proxy)
+
+VERSION(Service-proxy-version)
+
+SUBSCRIBER(g:python-contrib)
+
+IF (PYTHON2)
+ PEERDIR(contrib/python/deepmerge/py2)
+ELSE()
+ PEERDIR(contrib/python/deepmerge/py3)
+ENDIF()
+
+NO_LINT()
+
+END()
+
+RECURSE(
+ py2
+ py3
+)
diff --git a/contrib/python/mergedeep/.dist-info/METADATA b/contrib/python/mergedeep/.dist-info/METADATA
deleted file mode 100644
index 23452d786b..0000000000
--- a/contrib/python/mergedeep/.dist-info/METADATA
+++ /dev/null
@@ -1,154 +0,0 @@
-Metadata-Version: 2.1
-Name: mergedeep
-Version: 1.3.4
-Summary: A deep merge function for 🐍.
-Home-page: https://github.com/clarketm/mergedeep
-Author: Travis Clarke
-Author-email: travis.m.clarke@gmail.com
-License: UNKNOWN
-Platform: UNKNOWN
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Requires-Python: >=3.6
-Description-Content-Type: text/markdown
-
-# [mergedeep](https://mergedeep.readthedocs.io/en/latest/)
-
-[![PyPi release](https://img.shields.io/pypi/v/mergedeep.svg)](https://pypi.org/project/mergedeep/)
-[![PyPi versions](https://img.shields.io/pypi/pyversions/mergedeep.svg)](https://pypi.org/project/mergedeep/)
-[![Downloads](https://pepy.tech/badge/mergedeep)](https://pepy.tech/project/mergedeep)
-[![Conda Version](https://img.shields.io/conda/vn/conda-forge/mergedeep.svg)](https://anaconda.org/conda-forge/mergedeep)
-[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/mergedeep.svg)](https://anaconda.org/conda-forge/mergedeep)
-[![Documentation Status](https://readthedocs.org/projects/mergedeep/badge/?version=latest)](https://mergedeep.readthedocs.io/en/latest/?badge=latest)
-
-A deep merge function for 🐍.
-
-[Check out the mergedeep docs](https://mergedeep.readthedocs.io/en/latest/)
-
-## Installation
-
-```bash
-$ pip install mergedeep
-```
-
-## Usage
-
-```text
-merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping
-```
-
-Deep merge without mutating the source dicts.
-
-```python3
-from mergedeep import 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}}
-```
-
-Deep merge into an existing dict.
-```python3
-from mergedeep import merge
-
-a = {"keyA": 1}
-b = {"keyB": {"sub1": 10}}
-c = {"keyB": {"sub2": 20}}
-
-merge(a, b, c)
-
-print(a)
-# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
-```
-
-### Merge strategies:
-
-1. Replace (*default*)
-
-> `Strategy.REPLACE`
-
-```python3
-# When `destination` and `source` keys are the same, replace the `destination` value with one from `source` (default).
-
-# Note: with multiple sources, the `last` (i.e. rightmost) source value will be what appears in the merged result.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": [3, 4]}
-
-merge(dst, src, strategy=Strategy.REPLACE)
-# same as: merge(dst, src)
-
-print(dst)
-# {"key": [3, 4]}
-```
-
-2. Additive
-
-> `Strategy.ADDITIVE`
-
-```python3
-# When `destination` and `source` values are both the same additive collection type, extend `destination` by adding values from `source`.
-# Additive collection types include: `list`, `tuple`, `set`, and `Counter`
-
-# Note: if the values are not additive collections of the same type, then fallback to a `REPLACE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2], "count": Counter({"a": 1, "b": 1})}
-src = {"key": [3, 4], "count": Counter({"a": 1, "c": 1})}
-
-merge(dst, src, strategy=Strategy.ADDITIVE)
-
-print(dst)
-# {"key": [1, 2, 3, 4], "count": Counter({"a": 2, "b": 1, "c": 1})}
-```
-
-3. Typesafe replace
-
-> `Strategy.TYPESAFE_REPLACE` or `Strategy.TYPESAFE`
-
-```python3
-# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `REPLACE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": {3, 4}}
-
-merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE) # same as: `Strategy.TYPESAFE`
-# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"
-```
-
-4. Typesafe additive
-
-> `Strategy.TYPESAFE_ADDITIVE`
-
-```python3
-# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `ADDITIVE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": {3, 4}}
-
-merge(dst, src, strategy=Strategy.TYPESAFE_ADDITIVE)
-# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"
-```
-
-## License
-
-MIT &copy; [**Travis Clarke**](https://blog.travismclarke.com/)
-
-
diff --git a/contrib/python/mergedeep/.dist-info/top_level.txt b/contrib/python/mergedeep/.dist-info/top_level.txt
deleted file mode 100644
index 5413932b26..0000000000
--- a/contrib/python/mergedeep/.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-mergedeep
diff --git a/contrib/python/mergedeep/.yandex_meta/yamaker.yaml b/contrib/python/mergedeep/.yandex_meta/yamaker.yaml
deleted file mode 100644
index b88229ef48..0000000000
--- a/contrib/python/mergedeep/.yandex_meta/yamaker.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-mark_as_tests:
- - mergedeep/test_mergedeep.py
diff --git a/contrib/python/mergedeep/README.md b/contrib/python/mergedeep/README.md
deleted file mode 100644
index 86322730cd..0000000000
--- a/contrib/python/mergedeep/README.md
+++ /dev/null
@@ -1,133 +0,0 @@
-# [mergedeep](https://mergedeep.readthedocs.io/en/latest/)
-
-[![PyPi release](https://img.shields.io/pypi/v/mergedeep.svg)](https://pypi.org/project/mergedeep/)
-[![PyPi versions](https://img.shields.io/pypi/pyversions/mergedeep.svg)](https://pypi.org/project/mergedeep/)
-[![Downloads](https://pepy.tech/badge/mergedeep)](https://pepy.tech/project/mergedeep)
-[![Conda Version](https://img.shields.io/conda/vn/conda-forge/mergedeep.svg)](https://anaconda.org/conda-forge/mergedeep)
-[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/mergedeep.svg)](https://anaconda.org/conda-forge/mergedeep)
-[![Documentation Status](https://readthedocs.org/projects/mergedeep/badge/?version=latest)](https://mergedeep.readthedocs.io/en/latest/?badge=latest)
-
-A deep merge function for 🐍.
-
-[Check out the mergedeep docs](https://mergedeep.readthedocs.io/en/latest/)
-
-## Installation
-
-```bash
-$ pip install mergedeep
-```
-
-## Usage
-
-```text
-merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping
-```
-
-Deep merge without mutating the source dicts.
-
-```python3
-from mergedeep import 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}}
-```
-
-Deep merge into an existing dict.
-```python3
-from mergedeep import merge
-
-a = {"keyA": 1}
-b = {"keyB": {"sub1": 10}}
-c = {"keyB": {"sub2": 20}}
-
-merge(a, b, c)
-
-print(a)
-# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}
-```
-
-### Merge strategies:
-
-1. Replace (*default*)
-
-> `Strategy.REPLACE`
-
-```python3
-# When `destination` and `source` keys are the same, replace the `destination` value with one from `source` (default).
-
-# Note: with multiple sources, the `last` (i.e. rightmost) source value will be what appears in the merged result.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": [3, 4]}
-
-merge(dst, src, strategy=Strategy.REPLACE)
-# same as: merge(dst, src)
-
-print(dst)
-# {"key": [3, 4]}
-```
-
-2. Additive
-
-> `Strategy.ADDITIVE`
-
-```python3
-# When `destination` and `source` values are both the same additive collection type, extend `destination` by adding values from `source`.
-# Additive collection types include: `list`, `tuple`, `set`, and `Counter`
-
-# Note: if the values are not additive collections of the same type, then fallback to a `REPLACE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2], "count": Counter({"a": 1, "b": 1})}
-src = {"key": [3, 4], "count": Counter({"a": 1, "c": 1})}
-
-merge(dst, src, strategy=Strategy.ADDITIVE)
-
-print(dst)
-# {"key": [1, 2, 3, 4], "count": Counter({"a": 2, "b": 1, "c": 1})}
-```
-
-3. Typesafe replace
-
-> `Strategy.TYPESAFE_REPLACE` or `Strategy.TYPESAFE`
-
-```python3
-# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `REPLACE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": {3, 4}}
-
-merge(dst, src, strategy=Strategy.TYPESAFE_REPLACE) # same as: `Strategy.TYPESAFE`
-# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"
-```
-
-4. Typesafe additive
-
-> `Strategy.TYPESAFE_ADDITIVE`
-
-```python3
-# When `destination` and `source` values are of different types, raise `TypeError`. Otherwise, perform a `ADDITIVE` merge.
-
-from mergedeep import merge, Strategy
-
-dst = {"key": [1, 2]}
-src = {"key": {3, 4}}
-
-merge(dst, src, strategy=Strategy.TYPESAFE_ADDITIVE)
-# TypeError: destination type: <class 'list'> differs from source type: <class 'set'> for key: "key"
-```
-
-## License
-
-MIT &copy; [**Travis Clarke**](https://blog.travismclarke.com/)
diff --git a/contrib/python/mergedeep/mergedeep/__init__.py b/contrib/python/mergedeep/mergedeep/__init__.py
deleted file mode 100644
index 92d12f6c94..0000000000
--- a/contrib/python/mergedeep/mergedeep/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-__version__ = "1.3.4"
-
-from mergedeep.mergedeep import merge, Strategy
-
-__all__ = ["merge", "Strategy"]
diff --git a/contrib/python/mergedeep/mergedeep/mergedeep.py b/contrib/python/mergedeep/mergedeep/mergedeep.py
deleted file mode 100644
index 6dda8e82f3..0000000000
--- a/contrib/python/mergedeep/mergedeep/mergedeep.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from collections import Counter
-from collections.abc import Mapping
-from copy import deepcopy
-from enum import Enum
-from functools import reduce, partial
-from typing import MutableMapping
-
-
-class Strategy(Enum):
- # Replace `destination` item with one from `source` (default).
- REPLACE = 0
- # Combine `list`, `tuple`, `set`, or `Counter` types into one collection.
- ADDITIVE = 1
- # Alias to: `TYPESAFE_REPLACE`
- TYPESAFE = 2
- # Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `REPLACE` merge.
- TYPESAFE_REPLACE = 3
- # Raise `TypeError` when `destination` and `source` types differ. Otherwise, perform a `ADDITIVE` merge.
- TYPESAFE_ADDITIVE = 4
-
-
-def _handle_merge_replace(destination, source, key):
- if isinstance(destination[key], Counter) and isinstance(source[key], Counter):
- # Merge both destination and source `Counter` as if they were a standard dict.
- _deepmerge(destination[key], source[key])
- else:
- # If a key exists in both objects and the values are `different`, the value from the `source` object will be used.
- destination[key] = deepcopy(source[key])
-
-
-def _handle_merge_additive(destination, source, key):
- # Values are combined into one long collection.
- if isinstance(destination[key], list) and isinstance(source[key], list):
- # Extend destination if both destination and source are `list` type.
- destination[key].extend(deepcopy(source[key]))
- elif isinstance(destination[key], set) and isinstance(source[key], set):
- # Update destination if both destination and source are `set` type.
- destination[key].update(deepcopy(source[key]))
- elif isinstance(destination[key], tuple) and isinstance(source[key], tuple):
- # Update destination if both destination and source are `tuple` type.
- destination[key] = destination[key] + deepcopy(source[key])
- elif isinstance(destination[key], Counter) and isinstance(source[key], Counter):
- # Update destination if both destination and source are `Counter` type.
- destination[key].update(deepcopy(source[key]))
- else:
- _handle_merge[Strategy.REPLACE](destination, source, key)
-
-
-def _handle_merge_typesafe(destination, source, key, strategy):
- # Raise a TypeError if the destination and source types differ.
- if type(destination[key]) is not type(source[key]):
- raise TypeError(
- f'destination type: {type(destination[key])} differs from source type: {type(source[key])} for key: "{key}"'
- )
- else:
- _handle_merge[strategy](destination, source, key)
-
-
-_handle_merge = {
- Strategy.REPLACE: _handle_merge_replace,
- Strategy.ADDITIVE: _handle_merge_additive,
- Strategy.TYPESAFE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE),
- Strategy.TYPESAFE_REPLACE: partial(_handle_merge_typesafe, strategy=Strategy.REPLACE),
- Strategy.TYPESAFE_ADDITIVE: partial(_handle_merge_typesafe, strategy=Strategy.ADDITIVE),
-}
-
-
-def _is_recursive_merge(a, b):
- both_mapping = isinstance(a, Mapping) and isinstance(b, Mapping)
- both_counter = isinstance(a, Counter) and isinstance(b, Counter)
- return both_mapping and not both_counter
-
-
-def _deepmerge(dst, src, strategy=Strategy.REPLACE):
- for key in src:
- if key in dst:
- if _is_recursive_merge(dst[key], src[key]):
- # If the key for both `dst` and `src` are both Mapping types (e.g. dict), then recurse.
- _deepmerge(dst[key], src[key], strategy)
- elif dst[key] is src[key]:
- # If a key exists in both objects and the values are `same`, the value from the `dst` object will be used.
- pass
- else:
- _handle_merge.get(strategy)(dst, src, key)
- else:
- # If the key exists only in `src`, the value from the `src` object will be used.
- dst[key] = deepcopy(src[key])
- return dst
-
-
-def merge(destination: MutableMapping, *sources: Mapping, strategy: Strategy = Strategy.REPLACE) -> MutableMapping:
- """
- A deep merge function for 🐍.
-
- :param destination: The destination mapping.
- :param sources: The source mappings.
- :param strategy: The merge strategy.
- :return:
- """
- return reduce(partial(_deepmerge, strategy=strategy), sources, destination)
diff --git a/contrib/python/mergedeep/mergedeep/test_mergedeep.py b/contrib/python/mergedeep/mergedeep/test_mergedeep.py
deleted file mode 100644
index ef39728835..0000000000
--- a/contrib/python/mergedeep/mergedeep/test_mergedeep.py
+++ /dev/null
@@ -1,397 +0,0 @@
-"""mergedeep test module"""
-import inspect
-import unittest
-from collections import Counter
-from copy import deepcopy
-
-from mergedeep import merge, Strategy
-
-
-class test_mergedeep(unittest.TestCase):
- """mergedeep function tests."""
-
- ##############################################################################################################################
- # REPLACE
- ##############################################################################################################################
-
- def test_should_merge_3_dicts_into_new_dict_using_replace_strategy_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": [4, 5, 6],
- "g": (100, 200),
- "h": Counter({"a": 5, "b": 1, "c": 1}),
- "i": 2,
- "j": Counter({"z": 2}),
- "z": Counter({"a": 2}),
- }
-
- a = {
- "a": {"b": {"c": 5}},
- "d": 1,
- "e": {2: 3},
- "f": [1, 2, 3],
- "g": (2, 4, 6),
- "h": Counter({"a": 1, "b": 1}),
- "j": 1,
- }
- a_copy = deepcopy(a)
-
- b = {
- "a": {"B": {"C": 10}},
- "d": 2,
- "e": 2,
- "f": [4, 5, 6],
- "g": (100, 200),
- "h": Counter({"a": 5, "c": 1}),
- "i": Counter({"a": 1}),
- "z": Counter({"a": 2}),
- }
- b_copy = deepcopy(b)
-
- c = {
- "a": {"b": {"_c": 15}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "i": 2,
- "j": Counter({"z": 2}),
- "z": Counter({"a": 2}),
- }
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.REPLACE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_merge_2_dicts_into_existing_dict_using_replace_strategy_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": [4, 5, 6],
- "g": (100, 200),
- "h": Counter({"a": 1, "b": 1, "c": 1}),
- "i": 2,
- "j": Counter({"z": 2}),
- }
-
- a = {
- "a": {"b": {"c": 5}},
- "d": 1,
- "e": {2: 3},
- "f": [1, 2, 3],
- "g": (2, 4, 6),
- "h": Counter({"a": 1, "b": 1}),
- "j": 1,
- }
- a_copy = deepcopy(a)
-
- b = {
- "a": {"B": {"C": 10}},
- "d": 2,
- "e": 2,
- "f": [4, 5, 6],
- "g": (100, 200),
- "h": Counter({"a": 1, "c": 1}),
- "i": Counter({"a": 1}),
- }
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}, "i": 2, "j": Counter({"z": 2})}
- c_copy = deepcopy(c)
-
- actual = merge(a, b, c, strategy=Strategy.REPLACE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(actual, a)
- self.assertNotEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_have_default_strategy_of_replace(self):
- func_spec = inspect.getfullargspec(merge)
- default_strategy = Strategy.REPLACE
-
- self.assertEqual(func_spec.kwonlydefaults.get("strategy"), default_strategy)
-
- # mock_merge.method.assert_called_with(target, source, strategy=Strategy.REPLACE)
-
- ##############################################################################################################################
- # ADDITIVE
- ##############################################################################################################################
-
- def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_lists_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": [1, 2, 3, 4, 5, 6],
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_sets_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": {1, 2, 3, 4, 5, 6},
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": {1, 2, 3}}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": {4, 5, 6}}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_tuples_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": (1, 2, 3, 4, 5, 6),
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": (1, 2, 3)}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": (4, 5, 6)}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_counters_and_only_mutate_target(self,):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "f": Counter({"a": 2, "c": 1, "b": 1}),
- "i": 2,
- "j": Counter({"z": 2}),
- "z": Counter({"a": 4}),
- }
-
- a = {
- "a": {"b": {"c": 5}},
- "d": 1,
- "e": {2: 3},
- "f": Counter({"a": 1, "c": 1}),
- "i": Counter({"f": 9}),
- "j": Counter({"a": 1, "z": 4}),
- }
- a_copy = deepcopy(a)
-
- b = {
- "a": {"B": {"C": 10}},
- "d": 2,
- "e": 2,
- "f": Counter({"a": 1, "b": 1}),
- "j": [1, 2, 3],
- "z": Counter({"a": 2}),
- }
- b_copy = deepcopy(b)
-
- c = {
- "a": {"b": {"_c": 15}},
- "d": 3,
- "e": {1: 2, "a": {"f": 2}},
- "i": 2,
- "j": Counter({"z": 2}),
- "z": Counter({"a": 2}),
- }
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_not_copy_references(self):
- before = 1
- after = 99
-
- o1 = {"key1": before}
- o2 = {"key2": before}
-
- expected = {"list": deepcopy([o1, o2]), "tuple": deepcopy((o1, o2))}
-
- a = {"list": [o1], "tuple": (o1,)}
- b = {"list": [o2], "tuple": (o2,)}
-
- actual = merge({}, a, b, strategy=Strategy.ADDITIVE)
-
- o1["key1"] = after
- o2["key2"] = after
-
- self.assertEqual(actual, expected)
-
- # Copied dicts should `not` mutate
- self.assertEqual(actual["list"][0]["key1"], before)
- self.assertEqual(actual["list"][1]["key2"], before)
- self.assertEqual(actual["tuple"][0]["key1"], before)
- self.assertEqual(actual["tuple"][1]["key2"], before)
-
- # Non-copied dicts should mutate
- self.assertEqual(a["list"][0]["key1"], after)
- self.assertEqual(b["list"][0]["key2"], after)
- self.assertEqual(a["tuple"][0]["key1"], after)
- self.assertEqual(b["tuple"][0]["key2"], after)
-
- ##############################################################################################################################
- # TYPESAFE
- # TYPESAFE_REPLACE
- ##############################################################################################################################
-
- def test_should_raise_TypeError_using_typesafe_strategy_if_types_differ(self):
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
-
- with self.assertRaises(TypeError):
- merge({}, a, b, c, strategy=Strategy.TYPESAFE)
-
- def test_should_raise_TypeError_using_typesafe_replace_strategy_if_types_differ(self,):
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
-
- with self.assertRaises(TypeError):
- merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
-
- def test_should_merge_3_dicts_into_new_dict_using_typesafe_strategy_and_only_mutate_target_if_types_are_compatible(
- self,
- ):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "f": [4, 5, 6],
- "g": {2, 3, 4},
- "h": (1, 3),
- "z": Counter({"a": 1, "b": 1, "c": 1}),
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- def test_should_merge_3_dicts_into_new_dict_using_typesafe_replace_strategy_and_only_mutate_target_if_types_are_compatible(
- self,
- ):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "f": [4, 5, 6],
- "g": {2, 3, 4},
- "h": (1, 3),
- "z": Counter({"a": 1, "b": 1, "c": 1}),
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
- ##############################################################################################################################
- # TYPESAFE_ADDITIVE
- ##############################################################################################################################
-
- def test_should_raise_TypeError_using_typesafe_additive_strategy_if_types_differ(self,):
- a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
- b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
- c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
-
- with self.assertRaises(TypeError):
- merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
-
- def test_should_merge_3_dicts_into_new_dict_using_typesafe_additive_strategy_and_only_mutate_target_if_types_are_compatible(
- self,
- ):
- expected = {
- "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
- "d": 3,
- "f": [1, 2, 3, 4, 5, 6],
- "g": {1, 2, 3, 4},
- "h": (1, 1, 3),
- "z": Counter({"a": 2, "b": 1, "c": 1}),
- }
-
- a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
- a_copy = deepcopy(a)
-
- b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
- b_copy = deepcopy(b)
-
- c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
- c_copy = deepcopy(c)
-
- actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
-
- self.assertEqual(actual, expected)
- self.assertEqual(a, a_copy)
- self.assertEqual(b, b_copy)
- self.assertEqual(c, c_copy)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/contrib/python/mergedeep/tests/ya.make b/contrib/python/mergedeep/tests/ya.make
deleted file mode 100644
index a0a227f788..0000000000
--- a/contrib/python/mergedeep/tests/ya.make
+++ /dev/null
@@ -1,17 +0,0 @@
-PY3TEST()
-
-SUBSCRIBER(g:python-contrib)
-
-PEERDIR(
- contrib/python/mergedeep
-)
-
-SRCDIR(contrib/python/mergedeep/mergedeep)
-
-TEST_SRCS(
- test_mergedeep.py
-)
-
-NO_LINT()
-
-END()
diff --git a/contrib/python/mergedeep/ya.make b/contrib/python/mergedeep/ya.make
deleted file mode 100644
index ec7f03aaf4..0000000000
--- a/contrib/python/mergedeep/ya.make
+++ /dev/null
@@ -1,29 +0,0 @@
-# Generated by devtools/yamaker (pypi).
-
-PY3_LIBRARY()
-
-SUBSCRIBER(g:python-contrib)
-
-VERSION(1.3.4)
-
-LICENSE(MIT)
-
-NO_LINT()
-
-PY_SRCS(
- TOP_LEVEL
- mergedeep/__init__.py
- mergedeep/mergedeep.py
-)
-
-RESOURCE_FILES(
- PREFIX contrib/python/mergedeep/
- .dist-info/METADATA
- .dist-info/top_level.txt
-)
-
-END()
-
-RECURSE_FOR_TESTS(
- tests
-)