from typing import Mapping


def deep_update(d1, d2):
    """
    Recursively updates `d1` with `d2`

    :param d1: A dictionary (possibly nested) to be updated.
    :type d1: dict

    :param d2: A dictionary (possibly nested) which will be used to update d1.
    :type d2: dict

    :return: An updated version of d1, where d2 values were used to update the values of d1. Will
             add d2 keys if not present in d1. If a key does exist in d1, that key's value will be
             overwritten by the d2 value. Works recursively to update nested dictionaries.
    :rtype: dict
    """
    if all((isinstance(d, Mapping) for d in (d1, d2))):
        for k, v in d2.items():
            d1[k] = deep_update(d1.get(k), v)
        return d1
    return d2