summaryrefslogtreecommitdiff
path: root/redis/_compat.py
blob: 566945479de759eb541453524251e64bf443dce7 (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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""Internal module for Python 2 backwards compatibility."""
import errno
import socket
import sys


def sendall(sock, *args, **kwargs):
    return sock.sendall(*args, **kwargs)


def shutdown(sock, *args, **kwargs):
    return sock.shutdown(*args, **kwargs)


def ssl_wrap_socket(context, sock, *args, **kwargs):
    return context.wrap_socket(sock, *args, **kwargs)


# For Python older than 3.5, retry EINTR.
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and
                               sys.version_info[1] < 5):
    # Adapted from https://bugs.python.org/review/23863/patch/14532/54418
    import time

    # Wrapper for handling interruptable system calls.
    def _retryable_call(s, func, *args, **kwargs):
        # Some modules (SSL) use the _fileobject wrapper directly and
        # implement a smaller portion of the socket interface, thus we
        # need to let them continue to do so.
        timeout, deadline = None, 0.0
        attempted = False
        try:
            timeout = s.gettimeout()
        except AttributeError:
            pass

        if timeout:
            deadline = time.time() + timeout

        try:
            while True:
                if attempted and timeout:
                    now = time.time()
                    if now >= deadline:
                        raise socket.error(errno.EWOULDBLOCK, "timed out")
                    else:
                        # Overwrite the timeout on the socket object
                        # to take into account elapsed time.
                        s.settimeout(deadline - now)
                try:
                    attempted = True
                    return func(*args, **kwargs)
                except socket.error as e:
                    if e.args[0] == errno.EINTR:
                        continue
                    raise
        finally:
            # Set the existing timeout back for future
            # calls.
            if timeout:
                s.settimeout(timeout)

    def recv(sock, *args, **kwargs):
        return _retryable_call(sock, sock.recv, *args, **kwargs)

    def recv_into(sock, *args, **kwargs):
        return _retryable_call(sock, sock.recv_into, *args, **kwargs)

else:  # Python 3.5 and above automatically retry EINTR
    def recv(sock, *args, **kwargs):
        return sock.recv(*args, **kwargs)

    def recv_into(sock, *args, **kwargs):
        return sock.recv_into(*args, **kwargs)

if sys.version_info[0] < 3:
    # In Python 3, the ssl module raises socket.timeout whereas it raises
    # SSLError in Python 2. For compatibility between versions, ensure
    # socket.timeout is raised for both.
    import functools

    try:
        from ssl import SSLError as _SSLError
    except ImportError:
        class _SSLError(Exception):
            """A replacement in case ssl.SSLError is not available."""
            pass

    _EXPECTED_SSL_TIMEOUT_MESSAGES = (
        "The handshake operation timed out",
        "The read operation timed out",
        "The write operation timed out",
    )

    def _handle_ssl_timeout(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except _SSLError as e:
                if any(x in e.message for x in _EXPECTED_SSL_TIMEOUT_MESSAGES):
                    # Raise socket.timeout for compatibility with Python 3.
                    raise socket.timeout(*e.args)
                raise
        return wrapper

    recv = _handle_ssl_timeout(recv)
    recv_into = _handle_ssl_timeout(recv_into)
    sendall = _handle_ssl_timeout(sendall)
    shutdown = _handle_ssl_timeout(shutdown)
    ssl_wrap_socket = _handle_ssl_timeout(ssl_wrap_socket)

if sys.version_info[0] < 3:
    from urllib import unquote
    from urlparse import parse_qs, urlparse
    from itertools import imap, izip
    from string import letters as ascii_letters
    from Queue import Queue

    # special unicode handling for python2 to avoid UnicodeDecodeError
    def safe_unicode(obj, *args):
        """ return the unicode representation of obj """
        try:
            return unicode(obj, *args)
        except UnicodeDecodeError:
            # obj is byte string
            ascii_text = str(obj).encode('string_escape')
            return unicode(ascii_text)

    def iteritems(x):
        return x.iteritems()

    def iterkeys(x):
        return x.iterkeys()

    def itervalues(x):
        return x.itervalues()

    def nativestr(x):
        return x if isinstance(x, str) else x.encode('utf-8', 'replace')

    def next(x):
        return x.next()

    def byte_to_chr(x):
        return x

    unichr = unichr
    xrange = xrange
    basestring = basestring
    unicode = unicode
    long = long
    BlockingIOError = socket.error
else:
    from urllib.parse import parse_qs, unquote, urlparse
    from string import ascii_letters
    from queue import Queue

    def iteritems(x):
        return iter(x.items())

    def iterkeys(x):
        return iter(x.keys())

    def itervalues(x):
        return iter(x.values())

    def byte_to_chr(x):
        return chr(x)

    def nativestr(x):
        return x if isinstance(x, str) else x.decode('utf-8', 'replace')

    next = next
    unichr = chr
    imap = map
    izip = zip
    xrange = range
    basestring = str
    unicode = str
    safe_unicode = str
    long = int
    BlockingIOError = BlockingIOError

try:  # Python 3
    from queue import LifoQueue, Empty, Full
except ImportError:  # Python 2
    from Queue import LifoQueue, Empty, Full