1# util/compat.py
2# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
8"""Handle Python version/platform incompatibilities."""
9
10import sys
11
12try:
13    import threading
14except ImportError:
15    import dummy_threading as threading
16
17py36 = sys.version_info >= (3, 6)
18py33 = sys.version_info >= (3, 3)
19py32 = sys.version_info >= (3, 2)
20py3k = sys.version_info >= (3, 0)
21py2k = sys.version_info < (3, 0)
22py265 = sys.version_info >= (2, 6, 5)
23jython = sys.platform.startswith('java')
24pypy = hasattr(sys, 'pypy_version_info')
25win32 = sys.platform.startswith('win')
26cpython = not pypy and not jython  # TODO: something better for this ?
27
28import collections
29next = next
30
31if py3k:
32    import pickle
33else:
34    try:
35        import cPickle as pickle
36    except ImportError:
37        import pickle
38
39# work around http://bugs.python.org/issue2646
40if py265:
41    safe_kwarg = lambda arg: arg
42else:
43    safe_kwarg = str
44
45ArgSpec = collections.namedtuple("ArgSpec",
46                                 ["args", "varargs", "keywords", "defaults"])
47
48if py3k:
49    import builtins
50
51    from inspect import getfullargspec as inspect_getfullargspec
52    from urllib.parse import (quote_plus, unquote_plus,
53                              parse_qsl, quote, unquote)
54    import configparser
55    from io import StringIO
56
57    from io import BytesIO as byte_buffer
58
59    def inspect_getargspec(func):
60        return ArgSpec(
61            *inspect_getfullargspec(func)[0:4]
62        )
63
64    string_types = str,
65    binary_types = bytes,
66    binary_type = bytes
67    text_type = str
68    int_types = int,
69    iterbytes = iter
70
71    def u(s):
72        return s
73
74    def ue(s):
75        return s
76
77    def b(s):
78        return s.encode("latin-1")
79
80    if py32:
81        callable = callable
82    else:
83        def callable(fn):
84            return hasattr(fn, '__call__')
85
86    def cmp(a, b):
87        return (a > b) - (a < b)
88
89    from functools import reduce
90
91    print_ = getattr(builtins, "print")
92
93    import_ = getattr(builtins, '__import__')
94
95    import itertools
96    itertools_filterfalse = itertools.filterfalse
97    itertools_filter = filter
98    itertools_imap = map
99    from itertools import zip_longest
100
101    import base64
102
103    def b64encode(x):
104        return base64.b64encode(x).decode('ascii')
105
106    def b64decode(x):
107        return base64.b64decode(x.encode('ascii'))
108
109else:
110    from inspect import getargspec as inspect_getfullargspec
111    inspect_getargspec = inspect_getfullargspec
112    from urllib import quote_plus, unquote_plus, quote, unquote
113    from urlparse import parse_qsl
114    import ConfigParser as configparser
115    from StringIO import StringIO
116    from cStringIO import StringIO as byte_buffer
117
118    string_types = basestring,
119    binary_types = bytes,
120    binary_type = str
121    text_type = unicode
122    int_types = int, long
123
124    def iterbytes(buf):
125        return (ord(byte) for byte in buf)
126
127    def u(s):
128        # this differs from what six does, which doesn't support non-ASCII
129        # strings - we only use u() with
130        # literal source strings, and all our source files with non-ascii
131        # in them (all are tests) are utf-8 encoded.
132        return unicode(s, "utf-8")
133
134    def ue(s):
135        return unicode(s, "unicode_escape")
136
137    def b(s):
138        return s
139
140    def import_(*args):
141        if len(args) == 4:
142            args = args[0:3] + ([str(arg) for arg in args[3]],)
143        return __import__(*args)
144
145    callable = callable
146    cmp = cmp
147    reduce = reduce
148
149    import base64
150    b64encode = base64.b64encode
151    b64decode = base64.b64decode
152
153    def print_(*args, **kwargs):
154        fp = kwargs.pop("file", sys.stdout)
155        if fp is None:
156            return
157        for arg in enumerate(args):
158            if not isinstance(arg, basestring):
159                arg = str(arg)
160            fp.write(arg)
161
162    import itertools
163    itertools_filterfalse = itertools.ifilterfalse
164    itertools_filter = itertools.ifilter
165    itertools_imap = itertools.imap
166    from itertools import izip_longest as zip_longest
167
168
169import time
170if win32 or jython:
171    time_func = time.clock
172else:
173    time_func = time.time
174
175from collections import namedtuple
176from operator import attrgetter as dottedgetter
177
178
179if py3k:
180    def reraise(tp, value, tb=None, cause=None):
181        if cause is not None:
182            assert cause is not value, "Same cause emitted"
183            value.__cause__ = cause
184        if value.__traceback__ is not tb:
185            raise value.with_traceback(tb)
186        raise value
187
188else:
189    # not as nice as that of Py3K, but at least preserves
190    # the code line where the issue occurred
191    exec("def reraise(tp, value, tb=None, cause=None):\n"
192         "    if cause is not None:\n"
193         "        assert cause is not value, 'Same cause emitted'\n"
194         "    raise tp, value, tb\n")
195
196
197def raise_from_cause(exception, exc_info=None):
198    if exc_info is None:
199        exc_info = sys.exc_info()
200    exc_type, exc_value, exc_tb = exc_info
201    cause = exc_value if exc_value is not exception else None
202    reraise(type(exception), exception, tb=exc_tb, cause=cause)
203
204if py3k:
205    exec_ = getattr(builtins, 'exec')
206else:
207    def exec_(func_text, globals_, lcl=None):
208        if lcl is None:
209            exec('exec func_text in globals_')
210        else:
211            exec('exec func_text in globals_, lcl')
212
213
214def with_metaclass(meta, *bases):
215    """Create a base class with a metaclass.
216
217    Drops the middle class upon creation.
218
219    Source: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/
220
221    """
222
223    class metaclass(meta):
224        __call__ = type.__call__
225        __init__ = type.__init__
226
227        def __new__(cls, name, this_bases, d):
228            if this_bases is None:
229                return type.__new__(cls, name, (), d)
230            return meta(name, bases, d)
231    return metaclass('temporary_class', None, {})
232
233
234from contextlib import contextmanager
235
236try:
237    from contextlib import nested
238except ImportError:
239    # removed in py3k, credit to mitsuhiko for
240    # workaround
241
242    @contextmanager
243    def nested(*managers):
244        exits = []
245        vars = []
246        exc = (None, None, None)
247        try:
248            for mgr in managers:
249                exit = mgr.__exit__
250                enter = mgr.__enter__
251                vars.append(enter())
252                exits.append(exit)
253            yield vars
254        except:
255            exc = sys.exc_info()
256        finally:
257            while exits:
258                exit = exits.pop()
259                try:
260                    if exit(*exc):
261                        exc = (None, None, None)
262                except:
263                    exc = sys.exc_info()
264            if exc != (None, None, None):
265                reraise(exc[0], exc[1], exc[2])
266