changeset 111:d51a18e5b0e3

Reimplement configmix.safe_merge() do to a deepcopy of all source configurations when merging. configmix.safe_merge() does now a deepcopy of all source configurations when merging. Changes in configuration instances after will not be reflected in the merged configuration any more.
author Franz Glasner <hg@dom66.de>
date Sat, 24 Mar 2018 18:47:54 +0100
parents 29cf359ddf4d
children c50ad93eb5dc
files CHANGES.txt configmix/__init__.py doc/changes.rst doc/conf.py
diffstat 4 files changed, 43 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Sat Mar 24 16:06:08 2018 +0100
+++ b/CHANGES.txt	Sat Mar 24 18:47:54 2018 +0100
@@ -1,7 +1,7 @@
 .. -*- coding: utf-8; mode: rst; indent-tabs-mode: nil; -*-
 
 ..
-.. Valid tags: doc, feature, bugfix, test
+.. Valid tags: doc, feature, bugfix, test, breaking
 ..
 
 .. _changelog:
@@ -16,6 +16,12 @@
    :version: 0.6
 
    .. change::
+      :tags: breaking, feature
+
+      Reimplement :py:func:`configmix.safe_merge` do to a deepcopy of all
+      source configurations when merging
+
+   .. change::
       :tags: doc
 
       Begin the documentation with `Sphinx <http://www.sphinx-doc.org>`_
@@ -30,6 +36,7 @@
 
       Build a tree of configuration settings from INI files
 
+
 .. changelog::
    :version: 0.5
    :released: 2016-04-19             
--- a/configmix/__init__.py	Sat Mar 24 16:06:08 2018 +0100
+++ b/configmix/__init__.py	Sat Mar 24 18:47:54 2018 +0100
@@ -124,17 +124,34 @@
 
 
 def safe_merge(user, default):
-    """A more safe version of :func:`merge` that makes shallow copies of
+    """A more safe version of :func:`merge` that makes deep copies of
     the returned container objects.
 
+    No given argument is ever changed inplace. Every object from `default`
+    is decoupled from the result -- so changing the `default` configuration
+    lates does not yield into a merged configuration later.
+
     """
     if user is None:
-        return copy.copy(default)
-    user = copy.copy(user)
+        return copy.deepcopy(default)
+    user = copy.deepcopy(user)
     if isinstance(user, dict) and isinstance(default, dict):
         for k, v in default.items():
             if k not in user:
-                user[k] = copy.copy(v)
+                user[k] = copy.deepcopy(v)
             else:
-                user[k] = _merge(user[k], v)
+                user[k] = _safe_merge(user[k], v)
     return user
+
+
+def _safe_merge(user, default):
+    """Recursion helper for :meth:`safe_merge`
+
+    """
+    if isinstance(user, dict) and isinstance(default, dict):
+        for k, v in default.items():
+            if k not in user:
+                user[k] = copy.deepcopy(v)
+            else:
+                user[k] = _safe_merge(user[k], v)
+    return user
--- a/doc/changes.rst	Sat Mar 24 16:06:08 2018 +0100
+++ b/doc/changes.rst	Sat Mar 24 18:47:54 2018 +0100
@@ -4,7 +4,7 @@
  Changes
 =========
 
-All major changes over the versions are listed here. For API breaking
+All major changes over the versions are listed here. For breaking
 changes have a look at :ref:`api-changes`, they are listed there in
 detail.
 
@@ -13,7 +13,15 @@
 
 .. _api-changes:
 
-API Breaking Changes
-====================
+Breaking Changes
+================
+
+0.6
+---
 
-No incompatible changes yet.
+- :py:func:`configmix.safe_merge` does now a deepcopy of all source
+  configurations when merging. Changes in configuration instances after
+  will not be reflected in the merged configuration any more.
+
+  The public signature of :py:func:`configmix.safe_merge` has *not*
+  changed.
--- a/doc/conf.py	Sat Mar 24 16:06:08 2018 +0100
+++ b/doc/conf.py	Sat Mar 24 18:47:54 2018 +0100
@@ -211,4 +211,4 @@
 
 # -- Options for changelog ---------------------------------------------------
 
-changelog_inner_tag_sort = ['feature', 'bugfix', 'test', 'doc']
+changelog_inner_tag_sort = ['breaking', 'feature', 'bugfix', 'test', 'doc']