From f23ace9b7c0c2e8578421e3e640e3d1cc0fe381b Mon Sep 17 00:00:00 2001 From: rekby Date: Fri, 16 Aug 2024 13:53:46 +0300 Subject: Export python deepmerge library instead of mergedeep to github.com/ydb-platform/ydb for support python2 98bbe613ba94337077da6f6bb9b519768fdef800 --- contrib/python/deepmerge/py2/.dist-info/METADATA | 80 +++++ .../python/deepmerge/py2/.dist-info/top_level.txt | 1 + contrib/python/deepmerge/py2/LICENSE | 21 ++ contrib/python/deepmerge/py2/README.rst | 70 ++++ contrib/python/deepmerge/py2/deepmerge/__init__.py | 34 ++ contrib/python/deepmerge/py2/deepmerge/_version.py | 5 + contrib/python/deepmerge/py2/deepmerge/compat.py | 4 + .../python/deepmerge/py2/deepmerge/exception.py | 18 + .../python/deepmerge/py2/deepmerge/extended_set.py | 25 ++ contrib/python/deepmerge/py2/deepmerge/merger.py | 44 +++ .../deepmerge/py2/deepmerge/strategy/__init__.py | 0 .../deepmerge/py2/deepmerge/strategy/core.py | 38 ++ .../deepmerge/py2/deepmerge/strategy/dict.py | 32 ++ .../deepmerge/py2/deepmerge/strategy/fallback.py | 19 + .../deepmerge/py2/deepmerge/strategy/list.py | 31 ++ .../python/deepmerge/py2/deepmerge/strategy/set.py | 30 ++ .../py2/deepmerge/strategy/type_conflict.py | 22 ++ .../deepmerge/py2/deepmerge/tests/__init__.py | 0 .../py2/deepmerge/tests/strategy/__init__.py | 0 .../py2/deepmerge/tests/strategy/test_core.py | 41 +++ .../py2/deepmerge/tests/strategy/test_list.py | 33 ++ .../py2/deepmerge/tests/strategy/test_set_merge.py | 13 + .../deepmerge/tests/strategy/test_type_conflict.py | 22 ++ .../deepmerge/py2/deepmerge/tests/test_full.py | 50 +++ .../deepmerge/py2/deepmerge/tests/test_merger.py | 30 ++ contrib/python/deepmerge/py2/tests/ya.make | 25 ++ contrib/python/deepmerge/py2/ya.make | 40 +++ contrib/python/deepmerge/py3/.dist-info/METADATA | 80 +++++ .../python/deepmerge/py3/.dist-info/top_level.txt | 1 + contrib/python/deepmerge/py3/LICENSE | 21 ++ contrib/python/deepmerge/py3/README.rst | 70 ++++ contrib/python/deepmerge/py3/deepmerge/__init__.py | 34 ++ contrib/python/deepmerge/py3/deepmerge/_version.py | 16 + contrib/python/deepmerge/py3/deepmerge/compat.py | 4 + .../python/deepmerge/py3/deepmerge/exception.py | 18 + .../python/deepmerge/py3/deepmerge/extended_set.py | 25 ++ contrib/python/deepmerge/py3/deepmerge/merger.py | 44 +++ .../deepmerge/py3/deepmerge/strategy/__init__.py | 0 .../deepmerge/py3/deepmerge/strategy/core.py | 38 ++ .../deepmerge/py3/deepmerge/strategy/dict.py | 32 ++ .../deepmerge/py3/deepmerge/strategy/fallback.py | 19 + .../deepmerge/py3/deepmerge/strategy/list.py | 31 ++ .../python/deepmerge/py3/deepmerge/strategy/set.py | 30 ++ .../py3/deepmerge/strategy/type_conflict.py | 22 ++ .../deepmerge/py3/deepmerge/tests/__init__.py | 0 .../py3/deepmerge/tests/strategy/__init__.py | 0 .../py3/deepmerge/tests/strategy/test_core.py | 41 +++ .../py3/deepmerge/tests/strategy/test_list.py | 33 ++ .../py3/deepmerge/tests/strategy/test_set_merge.py | 13 + .../deepmerge/tests/strategy/test_type_conflict.py | 22 ++ .../deepmerge/py3/deepmerge/tests/test_full.py | 60 ++++ .../deepmerge/py3/deepmerge/tests/test_merger.py | 30 ++ contrib/python/deepmerge/py3/tests/ya.make | 25 ++ contrib/python/deepmerge/py3/ya.make | 40 +++ contrib/python/deepmerge/ya.make | 22 ++ contrib/python/mergedeep/.dist-info/METADATA | 154 -------- contrib/python/mergedeep/.dist-info/top_level.txt | 1 - contrib/python/mergedeep/.yandex_meta/yamaker.yaml | 2 - contrib/python/mergedeep/LICENSE | 21 -- contrib/python/mergedeep/README.md | 133 ------- contrib/python/mergedeep/mergedeep/__init__.py | 5 - contrib/python/mergedeep/mergedeep/mergedeep.py | 100 ------ .../python/mergedeep/mergedeep/test_mergedeep.py | 397 --------------------- contrib/python/mergedeep/tests/ya.make | 17 - contrib/python/mergedeep/ya.make | 29 -- 65 files changed, 1499 insertions(+), 859 deletions(-) create mode 100644 contrib/python/deepmerge/py2/.dist-info/METADATA create mode 100644 contrib/python/deepmerge/py2/.dist-info/top_level.txt create mode 100644 contrib/python/deepmerge/py2/LICENSE create mode 100644 contrib/python/deepmerge/py2/README.rst create mode 100644 contrib/python/deepmerge/py2/deepmerge/__init__.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/_version.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/compat.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/exception.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/extended_set.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/merger.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/__init__.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/core.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/dict.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/fallback.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/list.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/set.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/strategy/type_conflict.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/__init__.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/strategy/__init__.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_core.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_list.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_set_merge.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/strategy/test_type_conflict.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/test_full.py create mode 100644 contrib/python/deepmerge/py2/deepmerge/tests/test_merger.py create mode 100644 contrib/python/deepmerge/py2/tests/ya.make create mode 100644 contrib/python/deepmerge/py2/ya.make create mode 100644 contrib/python/deepmerge/py3/.dist-info/METADATA create mode 100644 contrib/python/deepmerge/py3/.dist-info/top_level.txt create mode 100644 contrib/python/deepmerge/py3/LICENSE create mode 100644 contrib/python/deepmerge/py3/README.rst create mode 100644 contrib/python/deepmerge/py3/deepmerge/__init__.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/_version.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/compat.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/exception.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/extended_set.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/merger.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/__init__.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/core.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/dict.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/fallback.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/list.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/set.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/strategy/type_conflict.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/__init__.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/strategy/__init__.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_core.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_list.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_set_merge.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/strategy/test_type_conflict.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/test_full.py create mode 100644 contrib/python/deepmerge/py3/deepmerge/tests/test_merger.py create mode 100644 contrib/python/deepmerge/py3/tests/ya.make create mode 100644 contrib/python/deepmerge/py3/ya.make create mode 100644 contrib/python/deepmerge/ya.make delete mode 100644 contrib/python/mergedeep/.dist-info/METADATA delete mode 100644 contrib/python/mergedeep/.dist-info/top_level.txt delete mode 100644 contrib/python/mergedeep/.yandex_meta/yamaker.yaml delete mode 100644 contrib/python/mergedeep/LICENSE delete mode 100644 contrib/python/mergedeep/README.md delete mode 100644 contrib/python/mergedeep/mergedeep/__init__.py delete mode 100644 contrib/python/mergedeep/mergedeep/mergedeep.py delete mode 100644 contrib/python/mergedeep/mergedeep/test_mergedeep.py delete mode 100644 contrib/python/mergedeep/tests/ya.make delete mode 100644 contrib/python/mergedeep/ya.make (limited to 'contrib/python') diff --git a/contrib/python/deepmerge/py2/.dist-info/METADATA b/contrib/python/deepmerge/py2/.dist-info/METADATA new file mode 100644 index 00000000000..dd6807cf68a --- /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 `_: + +.. 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 `_ 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 00000000000..046e27c3a82 --- /dev/null +++ b/contrib/python/deepmerge/py2/.dist-info/top_level.txt @@ -0,0 +1 @@ +deepmerge diff --git a/contrib/python/deepmerge/py2/LICENSE b/contrib/python/deepmerge/py2/LICENSE new file mode 100644 index 00000000000..7e0b51be12e --- /dev/null +++ b/contrib/python/deepmerge/py2/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/py2/README.rst b/contrib/python/deepmerge/py2/README.rst new file mode 100644 index 00000000000..24050eb5563 --- /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 `_: + +.. 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 `_ \ 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 00000000000..b7686cf91c9 --- /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 00000000000..bef9f66601e --- /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 00000000000..d2872a6edf4 --- /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 00000000000..ea36dac3bab --- /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 00000000000..1d51b431b9e --- /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 00000000000..c1f747ef3df --- /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 00000000000..e69de29bb2d 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 00000000000..91fcfbb0709 --- /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 00000000000..8f09eb984f6 --- /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 00000000000..714e8f9f594 --- /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 00000000000..2e42519fd52 --- /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 00000000000..55b26e39951 --- /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 00000000000..ebbc77a4ee7 --- /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 00000000000..e69de29bb2d 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 00000000000..e69de29bb2d 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 00000000000..afa76d72999 --- /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 00000000000..7eb2d3bb9d4 --- /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 00000000000..72df386cc0e --- /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 00000000000..a366351e4de --- /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 00000000000..c7710cdf914 --- /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 00000000000..e28b571d6cb --- /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 00000000000..c0d3cfaeda5 --- /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 00000000000..5ae850071ca --- /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 00000000000..9312291976b --- /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 `_: + +.. 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 `_ 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 00000000000..046e27c3a82 --- /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 00000000000..7e0b51be12e --- /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 00000000000..24050eb5563 --- /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 `_: + +.. 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 `_ \ 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 00000000000..b7686cf91c9 --- /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 00000000000..e602b7bb58e --- /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 00000000000..d2872a6edf4 --- /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 00000000000..ea36dac3bab --- /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 00000000000..1d51b431b9e --- /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 00000000000..5ccd5687554 --- /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 00000000000..e69de29bb2d 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 00000000000..91fcfbb0709 --- /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 00000000000..8f09eb984f6 --- /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 00000000000..714e8f9f594 --- /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 00000000000..2e42519fd52 --- /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 00000000000..55b26e39951 --- /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 00000000000..ebbc77a4ee7 --- /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 00000000000..e69de29bb2d 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 00000000000..e69de29bb2d 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 00000000000..afa76d72999 --- /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 00000000000..7eb2d3bb9d4 --- /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 00000000000..72df386cc0e --- /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 00000000000..a366351e4de --- /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 00000000000..799db192c1a --- /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 00000000000..e28b571d6cb --- /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 00000000000..6117ded073a --- /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 00000000000..e30cae0333e --- /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 00000000000..1e596fbb3e4 --- /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 23452d786be..00000000000 --- 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: differs from source type: 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: differs from source type: for key: "key" -``` - -## License - -MIT © [**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 5413932b268..00000000000 --- 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 b88229ef483..00000000000 --- 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/LICENSE b/contrib/python/mergedeep/LICENSE deleted file mode 100644 index 45c64408162..00000000000 --- a/contrib/python/mergedeep/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 Travis Clarke (https://www.travismclarke.com/) - -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/mergedeep/README.md b/contrib/python/mergedeep/README.md deleted file mode 100644 index 86322730cd9..00000000000 --- 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: differs from source type: 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: differs from source type: for key: "key" -``` - -## License - -MIT © [**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 92d12f6c949..00000000000 --- 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 6dda8e82f38..00000000000 --- 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 ef397288350..00000000000 --- 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 a0a227f788e..00000000000 --- 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 ec7f03aaf4a..00000000000 --- 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 -) -- cgit v1.3