diff --git a/README.rst b/README.rst index cfb285c..6127db1 100644 --- a/README.rst +++ b/README.rst @@ -139,6 +139,11 @@ kwargs - ``mergelists``: boolean try to merge lists of dict (default: ``True``) +- ``none_behavior``: bit (one of the listed below): + + - ``hiyapyco.NONE_BEHAVIOR_DEFAULT``: attempt to merge the value with ``None`` and fail if this is not possible (default method) + - ``hiyapyco.NONE_BEHAVIOR_OVERRIDE``: ``None`` always overrides any other value. + - ``interpolate``: boolean : perform interpolation after the merge (default: ``False``) diff --git a/hiyapyco/__init__.py b/hiyapyco/__init__.py index 773ab1d..4e3b350 100644 --- a/hiyapyco/__init__.py +++ b/hiyapyco/__init__.py @@ -65,6 +65,10 @@ class HiYaPyCoImplementationException(Exception): METHOD_MERGE = METHODS['METHOD_MERGE'] METHOD_SUBSTITUTE = METHODS['METHOD_SUBSTITUTE'] +NONE_BEHAVIORS = {"NONE_BEHAVIOR_DEFAULT": 0x0001, "NONE_BEHAVIOR_OVERRIDE": 0x0002} +NONE_BEHAVIOR_DEFAULT = NONE_BEHAVIORS["NONE_BEHAVIOR_DEFAULT"] +NONE_BEHAVIOR_OVERRIDE = NONE_BEHAVIORS["NONE_BEHAVIOR_OVERRIDE"] + class HiYaPyCo: """Main class""" @@ -77,6 +81,7 @@ def __init__(self, *args, **kwargs): hiyapyco.METHOD_SIMPLE | hiyapyco.METHOD_MERGE | hiyapyco.METHOD_SUBSTITUTE * mergelists: boolean (default: True) try to merge lists (only makes sense if hiyapyco.METHOD_MERGE or hiyapyco.METHOD_SUBSTITUTE) + * none_behavior: one of hiyapyco.NONE_BEHAVIOR_DEFAULT | hiyapyco.NONE_BEHAVIOR_OVERRIDE * interpolate: boolean (default: False) * castinterpolated: boolean (default: False) try to cast values after interpolating * usedefaultyamlloader: boolean (default: False) @@ -122,6 +127,19 @@ def __init__(self, *args, **kwargs): self.mergelists = kwargs['mergelists'] del kwargs['mergelists'] + self.none_behavior = None + if "none_behavior" in kwargs: + logger.debug("parse kwarg method: %s ..." % kwargs["none_behavior"]) + if kwargs["none_behavior"] not in NONE_BEHAVIORS.values(): + raise HiYaPyCoInvocationException( + "undefined method used, must be one of: %s" + % " ".join(NONE_BEHAVIORS.keys()) + ) + self.none_behavior = kwargs["none_behavior"] + del kwargs["none_behavior"] + if self.none_behavior is None: + self.none_behavior = NONE_BEHAVIOR_DEFAULT + self.interpolate = False self.castinterpolated = False if 'interpolate' in kwargs: @@ -333,8 +351,12 @@ def _simplemerge(self, a, b): a = copy.deepcopy(a) b = copy.deepcopy(b) logger.debug('simplemerge %s (%s) and %s (%s)' % (a, type(a), b, type(b),)) - # FIXME: make None usage configurable if b is None: + # override -> None replaces object, no matter what. + if self.none_behavior == NONE_BEHAVIOR_OVERRIDE: + logger.debug('b is None + none_behavior in use => return None') + return None + # default behavior is to attempt merge or fail logger.debug('pass as b is None') pass elif isinstance(b, primitiveTypes): @@ -374,6 +396,12 @@ def _substmerge(self, a, b): logger.debug('substmerge %s and %s' % (a, b,)) # FIXME: make None usage configurable if b is None: + logger.debug('pass as b is None') + # override -> None replaces object, no matter what. + if self.none_behavior == NONE_BEHAVIOR_OVERRIDE: + logger.debug('b is None + none_behavior in use => return None') + return None + # default behavior is to attempt merge or fail logger.debug('pass as b is None') pass @@ -429,6 +457,11 @@ def _deepmerge(self, a, b, context = None): logger.debug('deepmerge %s and %s' % (a, b,)) # FIXME: make None usage configurable if b is None: + logger.debug('pass as b is None') + # override -> None replaces object, no matter what. + if self.none_behavior == NONE_BEHAVIOR_OVERRIDE: + return None + # default behavior is to attempt merge or fail logger.debug('pass as b is None') pass if a is None or isinstance(b, primitiveTypes): diff --git a/test/base_none_behavior.yaml b/test/base_none_behavior.yaml new file mode 100644 index 0000000..57cc183 --- /dev/null +++ b/test/base_none_behavior.yaml @@ -0,0 +1,15 @@ +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 smartindent +singel: null +int: null +array: null +hash: null +deeplist: + - d1: null + - d2: + d2k1: null + d2k2: x2 +deepmap: + l1k1: + l2k1: null + l2k2: abc + l1k2: null diff --git a/test/test_merge.py b/test/test_merge.py index 3a7cfe6..c199a8e 100755 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -173,6 +173,65 @@ except KeyError as e: assert '%s' % e == '\'nosuchelement\'' -print('passed test %s' % __file__) + +logger.info("test none behavior DEFAULT ...") +for method in [hiyapyco.METHOD_MERGE, hiyapyco.METHOD_SUBSTITUTE]: + try: + conf = hiyapyco.load( + os.path.join(basepath, "base.yaml"), + os.path.join(basepath, "base_none_behavior.yaml"), + method=method, + failonmissingfiles=True, + ) + except hiyapyco.HiYaPyCoImplementationException: + pass + else: + raise AssertionError("Default None behavior is expected to fail on this merge") + +logger.info("test none behavior OVERRIDE ...") +for method in hiyapyco.METHODS.values(): + conf = hiyapyco.load( + os.path.join(basepath, "base.yaml"), + os.path.join(basepath, "base_none_behavior.yaml"), + none_behavior=hiyapyco.NONE_BEHAVIOR_OVERRIDE, + method=hiyapyco.METHOD_MERGE, + failonmissingfiles=True, + ) + + t = conf["singel"] + logger.info("test single val ... %s" % t) + assert t is None + + t = conf["int"] + logger.info("test int val ... %s" % t) + assert t is None + + t = conf["array"] + logger.info("test list val ... %s" % t) + assert t is None + + t = conf["hash"] + logger.info("test simple dict ... %s" % t) + assert t is None + + t = conf["deeplist"] + logger.info("test deeplist ... %s" % t) + assert t == [ + {"d1": None}, + {"d2": {"d2k2": "x2", "d2k1": None}}, + { + "d32": {"a": "A2", "b": "B2"}, + "d31": {"a": "A", "c": "C", "b": "B"}, + }, + ] + + t = conf["deepmap"] + logger.info("test deepmap ... %s" % t) + assert t == { + "l1k1": {"l2k1": None, "l2k2": "abc"}, + "l1k2": None, + } + +print("passed test %s" % __file__) # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 smartindent nu