def pretty_diff(title, titleA, dictA, titleB, dictB, onlydifferent=False):
    '\n    Generate a "pretty" printable diff of two Nodes or Groups\n    containing arbitrarily deep dict, list or string items.\n\n    Intended to be used for the "text" dicts in migrate_group()\n    and migrate_node().\n\n    :param title: overall title of the diff\n    :type title: string\n    :param titleA: title of the first dict\n    :type titleA: string\n    :param dictA: the first dict\n    :type dictA: dict\n    :param titleB: title of the second dict\n    :type titleB: string\n    :param dictB: the second dict\n    :type dictB: dict\n    :param onlydifferent: only output differing lines\n    :type onlydifferent: boolean\n    :returns: multi-line string, columnar diff of dicts\n    :rtype: string\n    '
    s = ('Diff of %s\n' % title)
    lines = []
    lines.append(['', titleA, titleB])
    lines.append(['', ('-' * len(titleA)), ('-' * len(titleB))])
    lines.append(['name', dictA.get('name', '<none>'), dictB.get('name', '<none>')])
    lines.append(['id', dictA.get('id', '<none>'), dictB.get('id', '<none>')])
    lines.append(['description', dictA.get('description', '<none>'), dictB.get('description', '<none>')])
    dictA.pop('name', None)
    dictA.pop('id', None)
    dictA.pop('description', None)
    dictB.pop('name', None)
    dictB.pop('id', None)
    dictB.pop('description', None)
    lines.append(['', '', ''])
    k = set.union(set(dictA.keys()), set(dictB.keys()))
    for p in sorted(k):
        lines.append([(p.capitalize() + ':'), '', ''])
        lines.extend(pretty_diff_obj('', dictA.get(p), dictB.get(p)))
    s += print_columns(lines, onlydifferent=onlydifferent)
    return s
Generate a "pretty" printable diff of two Nodes or Groups
containing arbitrarily deep dict, list or string items.

Intended to be used for the "text" dicts in migrate_group()
and migrate_node().

:param title: overall title of the diff
:type title: string
:param titleA: title of the first dict
:type titleA: string
:param dictA: the first dict
:type dictA: dict
:param titleB: title of the second dict
:type titleB: string
:param dictB: the second dict
:type dictB: dict
:param onlydifferent: only output differing lines
:type onlydifferent: boolean
:returns: multi-line string, columnar diff of dicts
:rtype: string
def pretty_diff(title, titleA, dictA, titleB, dictB, onlydifferent=False):
    '\n    Generate a "pretty" printable diff of two Nodes or Groups\n    containing arbitrarily deep dict, list or string items.\n\n    Intended to be used for the "text" dicts in migrate_group()\n    and migrate_node().\n\n    :param title: overall title of the diff\n    :type title: string\n    :param titleA: title of the first dict\n    :type titleA: string\n    :param dictA: the first dict\n    :type dictA: dict\n    :param titleB: title of the second dict\n    :type titleB: string\n    :param dictB: the second dict\n    :type dictB: dict\n    :param onlydifferent: only output differing lines\n    :type onlydifferent: boolean\n    :returns: multi-line string, columnar diff of dicts\n    :rtype: string\n    '
    s = ('Diff of %s\n' % title)
    lines = []
    lines.append([, titleA, titleB])
    lines.append([, ('-' * len(titleA)), ('-' * len(titleB))])
    lines.append(['name', dictA.get('name', '<none>'), dictB.get('name', '<none>')])
    lines.append(['id', dictA.get('id', '<none>'), dictB.get('id', '<none>')])
    lines.append(['description', dictA.get('description', '<none>'), dictB.get('description', '<none>')])
    dictA.pop('name', None)
    dictA.pop('id', None)
    dictA.pop('description', None)
    dictB.pop('name', None)
    dictB.pop('id', None)
    dictB.pop('description', None)
    lines.append([, , ])
    k = set.union(set(dictA.keys()), set(dictB.keys()))
    for p in sorted(k):
        lines.append([(p.capitalize() + ':'), , ])
        lines.extend(pretty_diff_obj(, dictA.get(p), dictB.get(p)))
    s += print_columns(lines, onlydifferent=onlydifferent)
    return s