# Path: invoke/config.py
# def merge_dicts(base, updates):
#     """
#     Recursively merge dict ``updates`` into dict ``base`` (mutating ``base``.)
# 
#     * Values which are themselves dicts will be recursed into.
#     * Values which are a dict in one input and *not* a dict in the other input
#       (e.g. if our inputs were ``{'foo': 5}`` and ``{'foo': {'bar': 5}}``) are
#       irreconciliable and will generate an exception.
#     * Non-dict leaf values are run through `copy.copy` to avoid state bleed.
# 
#     .. note::
#         This is effectively a lightweight `copy.deepcopy` which offers
#         protection from mismatched types (dict vs non-dict) and avoids some
#         core deepcopy problems (such as how it explodes on certain object
#         types).
# 
#     :returns:
#         The value of ``base``, which is mostly useful for wrapper functions
#         like `copy_dict`.
# 
#     .. versionadded:: 1.0
#     """
#     # TODO: for chrissakes just make it return instead of mutating?
#     for key, value in (updates or {}).items():
#         # Dict values whose keys also exist in 'base' -> recurse
#         # (But only if both types are dicts.)
#         if key in base:
#             if isinstance(value, dict):
#                 if isinstance(base[key], dict):
#                     merge_dicts(base[key], value)
#                 else:
#                     raise _merge_error(base[key], value)
#             else:
#                 if isinstance(base[key], dict):
#                     raise _merge_error(base[key], value)
#                 # Fileno-bearing objects are probably 'real' files which do not
#                 # copy well & must be passed by reference. Meh.
#                 elif hasattr(value, "fileno"):
#                     base[key] = value
#                 else:
#                     base[key] = copy.copy(value)
#         # New values get set anew
#         else:
#             # Dict values get reconstructed to avoid being references to the
#             # updates dict, which can lead to nasty state-bleed bugs otherwise
#             if isinstance(value, dict):
#                 base[key] = copy_dict(value)
#             # Fileno-bearing objects are probably 'real' files which do not
#             # copy well & must be passed by reference. Meh.
#             elif hasattr(value, "fileno"):
#                 base[key] = value
#             # Non-dict values just get set straight
#             else:
#                 base[key] = copy.copy(value)
#     return base
# 
# def copy_dict(source):
#     """
#     Return a fresh copy of ``source`` with as little shared state as possible.
# 
#     Uses `merge_dicts` under the hood, with an empty ``base`` dict; see its
#     documentation for details on behavior.
# 
#     .. versionadded:: 1.0
#     """
#     return merge_dicts({}, source)
# 
# class AmbiguousMergeError(ValueError):
#     pass


from pytest import raises
from invoke.config import merge_dicts, copy_dict, AmbiguousMergeError
    # sometimes it's necessary to more easily stamp out certain bugs.

    def merging_data_onto_empty_dict(self):
        d1 = {}
        d2 = {"foo": "bar"}
        merge_dicts(d1, d2)
        assert d1 == d2

    def updating_with_None_acts_like_merging_empty_dict(self):
        # When bug present, AttributeError is raised on a None.items()
        d1 = {"my": "data"}
        d2 = None
        merge_dicts(d1, d2)
        assert d1 == {"my": "data"}

    def orthogonal_data_merges(self):
        d1 = {"foo": "bar"}
        d2 = {"biz": "baz"}
        merge_dicts(d1, d2)
        assert d1 == {"foo": "bar", "biz": "baz"}

    def updates_arg_values_win(self):
        d1 = {"foo": "bar"}
        d2 = {"foo": "notbar"}
        merge_dicts(d1, d2)
        assert d1 == {"foo": "notbar"}

    def non_dict_type_mismatch_overwrites_ok(self):
        d1 = {"foo": "bar"}
        d2 = {"foo": [1, 2, 3]}

# Path: invoke/config.py
# def merge_dicts(base, updates):
#     """
#     Recursively merge dict ``updates`` into dict ``base`` (mutating ``base``.)
# 
#     * Values which are themselves dicts will be recursed into.
#     * Values which are a dict in one input and *not* a dict in the other input
#       (e.g. if our inputs were ``{'foo': 5}`` and ``{'foo': {'bar': 5}}``) are
#       irreconciliable and will generate an exception.
#     * Non-dict leaf values are run through `copy.copy` to avoid state bleed.
# 
#     .. note::
#         This is effectively a lightweight `copy.deepcopy` which offers
#         protection from mismatched types (dict vs non-dict) and avoids some
#         core deepcopy problems (such as how it explodes on certain object
#         types).
# 
#     :returns:
#         The value of ``base``, which is mostly useful for wrapper functions
#         like `copy_dict`.
# 
#     .. versionadded:: 1.0
#     """
#     # TODO: for chrissakes just make it return instead of mutating?
#     for key, value in (updates or {}).items():
#         # Dict values whose keys also exist in 'base' -> recurse
#         # (But only if both types are dicts.)
#         if key in base:
#             if isinstance(value, dict):
#                 if isinstance(base[key], dict):
#                     merge_dicts(base[key], value)
#                 else:
#                     raise _merge_error(base[key], value)
#             else:
#                 if isinstance(base[key], dict):
#                     raise _merge_error(base[key], value)
#                 # Fileno-bearing objects are probably 'real' files which do not
#                 # copy well & must be passed by reference. Meh.
#                 elif hasattr(value, "fileno"):
#                     base[key] = value
#                 else:
#                     base[key] = copy.copy(value)
#         # New values get set anew
#         else:
#             # Dict values get reconstructed to avoid being references to the
#             # updates dict, which can lead to nasty state-bleed bugs otherwise
#             if isinstance(value, dict):
#                 base[key] = copy_dict(value)
#             # Fileno-bearing objects are probably 'real' files which do not
#             # copy well & must be passed by reference. Meh.
#             elif hasattr(value, "fileno"):
#                 base[key] = value
#             # Non-dict values just get set straight
#             else:
#                 base[key] = copy.copy(value)
#     return base
# 
# def copy_dict(source):
#     """
#     Return a fresh copy of ``source`` with as little shared state as possible.
# 
#     Uses `merge_dicts` under the hood, with an empty ``base`` dict; see its
#     documentation for details on behavior.
# 
#     .. versionadded:: 1.0
#     """
#     return merge_dicts({}, source)
# 
# class AmbiguousMergeError(ValueError):
#     pass


# Path: tests/merge_dicts.py
from pytest import raises
from invoke.config import merge_dicts, copy_dict, AmbiguousMergeError

    # sometimes it's necessary to more easily stamp out certain bugs.

    def merging_data_onto_empty_dict(self):
        d1 = {}
        d2 = {"foo": "bar"}
        merge_dicts(d1, d2)
        assert d1 == d2

    def updating_with_None_acts_like_merging_empty_dict(self):
        # When bug present, AttributeError is raised on a None.items()
        d1 = {"my": "data"}
        d2 = None
        merge_dicts(d1, d2)
        assert d1 == {"my": "data"}

    def orthogonal_data_merges(self):
        d1 = {"foo": "bar"}
        d2 = {"biz": "baz"}
        merge_dicts(d1, d2)
        assert d1 == {"foo": "bar", "biz": "baz"}

    def updates_arg_values_win(self):
        d1 = {"foo": "bar"}
        d2 = {"foo": "notbar"}
        merge_dicts(d1, d2)
        assert d1 == {"foo": "notbar"}

    def non_dict_type_mismatch_overwrites_ok(self):
        d1 = {"foo": "bar"}
        d2 = {"foo": [1, 2, 3]}

        merge_dicts(d1, d2)