1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
import datetime
import decimal
from platform import python_version
import re
import uuid
try:
from bson import decimal128, Regex
_HAVE_PYMONGO = True
except ImportError:
_HAVE_PYMONGO = False
class _NO_VALUE(object):
pass
# we don't use NOTHING because it might be returned from various APIs
NO_VALUE = _NO_VALUE()
_SUPPORTED_BASE_TYPES = (
float, bool, str, datetime.datetime, type(None), uuid.UUID, int, bytes, type,
type(re.compile('')),)
if _HAVE_PYMONGO:
_SUPPORTED_TYPES = _SUPPORTED_BASE_TYPES + (decimal.Decimal, decimal128.Decimal128)
else:
_SUPPORTED_TYPES = _SUPPORTED_BASE_TYPES
if python_version() < '3.0':
dict_type = dict
else:
from collections import abc
dict_type = abc.Mapping
def diff(a, b, path=None):
path = _make_path(path)
if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
return _diff_sequences(a, b, path)
if type(a).__name__ == 'SON':
a = dict(a)
if type(b).__name__ == 'SON':
b = dict(b)
if type(a).__name__ == 'DBRef':
a = a.as_doc()
if type(b).__name__ == 'DBRef':
b = b.as_doc()
if isinstance(a, dict_type) and isinstance(b, dict_type):
return _diff_dicts(a, b, path)
if type(a).__name__ == 'ObjectId':
a = str(a)
if type(b).__name__ == 'ObjectId':
b = str(b)
if type(a).__name__ == 'Int64':
a = int(a)
if type(b).__name__ == 'Int64':
b = int(b)
if _HAVE_PYMONGO and isinstance(a, Regex):
a = a.try_compile()
if _HAVE_PYMONGO and isinstance(b, Regex):
b = b.try_compile()
if isinstance(a, (list, tuple)) or isinstance(b, (list, tuple)) or \
isinstance(a, dict_type) or isinstance(b, dict_type):
return [(path[:], a, b)]
if not isinstance(a, _SUPPORTED_TYPES):
raise NotImplementedError(
'Unsupported diff type: {0}'.format(type(a))) # pragma: no cover
if not isinstance(b, _SUPPORTED_TYPES):
raise NotImplementedError(
'Unsupported diff type: {0}'.format(type(b))) # pragma: no cover
if a != b:
return [(path[:], a, b)]
return []
def _diff_dicts(a, b, path):
if not isinstance(a, type(b)):
return [(path[:], type(a), type(b))]
returned = []
for key in set(a) | set(b):
a_value = a.get(key, NO_VALUE)
b_value = b.get(key, NO_VALUE)
path.append(key)
if a_value is NO_VALUE or b_value is NO_VALUE:
returned.append((path[:], a_value, b_value))
else:
returned.extend(diff(a_value, b_value, path))
path.pop()
return returned
def _diff_sequences(a, b, path):
if len(a) != len(b):
return [(path[:], a, b)]
returned = []
for i, a_i in enumerate(a):
path.append(i)
returned.extend(diff(a_i, b[i], path))
path.pop()
return returned
def _make_path(path):
if path is None:
return []
return path
|