File: diff.py

package info (click to toggle)
python-mongomock 4.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,028 kB
  • sloc: python: 16,412; makefile: 24
file content (107 lines) | stat: -rw-r--r-- 2,986 bytes parent folder | download | duplicates (2)
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