aboutsummaryrefslogtreecommitdiffstats
path: root/python/attr/_compat.py
blob: 5bb065932c3cfb0454ee61a32e607a6f937b9256 (plain)
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from __future__ import absolute_import, division, print_function

import platform
import sys
import types
import warnings


PY2 = sys.version_info[0] == 2
PYPY = platform.python_implementation() == "PyPy"


if PYPY or sys.version_info[:2] >= (3, 6):
    ordered_dict = dict
else:
    from collections import OrderedDict

    ordered_dict = OrderedDict


if PY2:
    from UserDict import IterableUserDict

    # We 'bundle' isclass instead of using inspect as importing inspect is
    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
    def isclass(klass):
        return isinstance(klass, (type, types.ClassType))

    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
    TYPE = "type"

    def iteritems(d):
        return d.iteritems()

    # Python 2 is bereft of a read-only dict proxy, so we make one!
    class ReadOnlyDict(IterableUserDict):
        """
        Best-effort read-only dict wrapper.
        """

        def __setitem__(self, key, val):
            # We gently pretend we're a Python 3 mappingproxy.
            raise TypeError(
                "'mappingproxy' object does not support item assignment"
            )

        def update(self, _):
            # We gently pretend we're a Python 3 mappingproxy.
            raise AttributeError(
                "'mappingproxy' object has no attribute 'update'"
            )

        def __delitem__(self, _):
            # We gently pretend we're a Python 3 mappingproxy.
            raise TypeError(
                "'mappingproxy' object does not support item deletion"
            )

        def clear(self):
            # We gently pretend we're a Python 3 mappingproxy.
            raise AttributeError(
                "'mappingproxy' object has no attribute 'clear'"
            )

        def pop(self, key, default=None):
            # We gently pretend we're a Python 3 mappingproxy.
            raise AttributeError(
                "'mappingproxy' object has no attribute 'pop'"
            )

        def popitem(self):
            # We gently pretend we're a Python 3 mappingproxy.
            raise AttributeError(
                "'mappingproxy' object has no attribute 'popitem'"
            )

        def setdefault(self, key, default=None):
            # We gently pretend we're a Python 3 mappingproxy.
            raise AttributeError(
                "'mappingproxy' object has no attribute 'setdefault'"
            )

        def __repr__(self):
            # Override to be identical to the Python 3 version.
            return "mappingproxy(" + repr(self.data) + ")"

    def metadata_proxy(d):
        res = ReadOnlyDict()
        res.data.update(d)  # We blocked update, so we have to do it like this.
        return res


else:

    def isclass(klass):
        return isinstance(klass, type)

    TYPE = "class"

    def iteritems(d):
        return d.items()

    def metadata_proxy(d):
        return types.MappingProxyType(dict(d))


def import_ctypes():
    """
    Moved into a function for testability.
    """
    import ctypes

    return ctypes


if not PY2:

    def just_warn(*args, **kw):
        """
        We only warn on Python 3 because we are not aware of any concrete
        consequences of not setting the cell on Python 2.
        """
        warnings.warn(
            "Missing ctypes.  Some features like bare super() or accessing "
            "__class__ will not work with slots classes.",
            RuntimeWarning,
            stacklevel=2,
        )


else:

    def just_warn(*args, **kw):  # pragma: nocover
        """
        We only warn on Python 3 because we are not aware of any concrete
        consequences of not setting the cell on Python 2.
        """


def make_set_closure_cell():
    """
    Moved into a function for testability.
    """
    if PYPY:  # pragma: no cover

        def set_closure_cell(cell, value):
            cell.__setstate__((value,))

    else:
        try:
            ctypes = import_ctypes()

            set_closure_cell = ctypes.pythonapi.PyCell_Set
            set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
            set_closure_cell.restype = ctypes.c_int
        except Exception:
            # We try best effort to set the cell, but sometimes it's not
            # possible.  For example on Jython or on GAE.
            set_closure_cell = just_warn
    return set_closure_cell


set_closure_cell = make_set_closure_cell()