1# cython: auto_cpdef=True
2
3'''Compatiblity for Python versions.
4
5Some of this code is "lifted" from CherryPy.
6'''
7from __future__ import absolute_import
8import sys
9from struct import unpack
10
11import json
12
13_encoding = 'UTF-8'
14
15if sys.version_info >= (3, 0):
16    from io import BytesIO as MemoryIO
17    from io import StringIO as StringIO
18    xrange = range
19
20    def py3_btou(n, encoding=_encoding):
21        return n.decode(encoding)
22
23    def py3_utob(n, encoding=_encoding):
24        return bytes(n, encoding)
25
26    def py3_json_dump(obj, **kwargs):
27        json.dump(obj, sys.stdout, **kwargs)
28
29    def py3_iterkeys(obj):
30        return obj.keys()
31
32    def py3_itervalues(obj):
33        return obj.values()
34
35    def py3_iteritems(obj):
36        return obj.items()
37
38    def py3_is_str(obj):
39        return isinstance(obj, str)
40
41    def py3_mk_bits(bits):
42        return bytes([bits & 0xff])
43
44    def py3_bytes2ints(datum):
45        return list(datum)
46
47    def py3_fstint(datum):
48        return datum[0]
49
50    def py3_appendable(file_like):
51        if file_like.seekable() and file_like.tell() != 0:
52            if "<stdout>" == getattr(file_like, "name", ""):
53                # In OSX, sys.stdout is seekable and has a non-zero tell() but
54                # we wouldn't want to append to a stdout. In the python REPL,
55                # sys.stdout is named `<stdout>`
56                return False
57            if file_like.readable():
58                return True
59            else:
60                raise ValueError(
61                    "When appending to an avro file you must use the "
62                    + "'a+' mode, not just 'a'"
63                )
64        else:
65            return False
66
67    def py3_int_to_be_signed_bytes(num, bytes_req):
68        return num.to_bytes(bytes_req, byteorder='big', signed=True)
69
70    def py3_be_signed_bytes_to_int(data):
71        return int.from_bytes(data, byteorder='big', signed=True)
72
73    def py3_reraise(Err, msg):
74        raise Err(msg).with_traceback(sys.exc_info()[2])
75
76else:  # Python 2x
77    from cStringIO import StringIO as MemoryIO  # noqa
78    from cStringIO import StringIO as StringIO  # noqa
79    xrange = xrange
80
81    def py2_btou(n, encoding=_encoding):
82        return unicode(n, encoding)  # noqa
83
84    def py2_utob(n, encoding=_encoding):
85        return n.encode(encoding)
86
87    _outenc = getattr(sys.stdout, 'encoding', None) or _encoding
88
89    def py2_json_dump(obj, **kwargs):
90        json.dump(obj, sys.stdout, encoding=_outenc, **kwargs)
91
92    def py2_iterkeys(obj):
93        return obj.iterkeys()
94
95    def py2_itervalues(obj):
96        return obj.itervalues()
97
98    def py2_iteritems(obj):
99        return obj.iteritems()
100
101    def py2_is_str(obj):
102        return isinstance(obj, basestring)  # noqa
103
104    def py2_mk_bits(bits):
105        return chr(bits & 0xff)
106
107    def py2_str2ints(datum):
108        return map(lambda x: ord(x), datum)
109
110    def py2_fstint(datum):
111        return unpack('!b', datum[0])[0]
112
113    def _readable(file_like):
114        try:
115            file_like.read()
116        except Exception:
117            return False
118        return True
119
120    def py2_appendable(file_like):
121        # On Python 2 things are a mess. We basically just rely on looking at
122        # the mode. If that doesn't exist (like in the case of an io.BytesIO)
123        # then we check the position and readablility.
124        try:
125            file_like.mode
126        except AttributeError:
127            # This is probably some io stream so we rely on its tell() working
128            try:
129                if file_like.tell() != 0 and _readable(file_like):
130                    return True
131            except (OSError, IOError):
132                pass
133            return False
134
135        if "a" in file_like.mode:
136            if "+" in file_like.mode:
137                return True
138            else:
139                raise ValueError(
140                    "When appending to an avro file you must use the "
141                    + "'a+' mode, not just 'a'"
142                )
143        else:
144            return False
145
146    def py2_int_to_be_signed_bytes(num, bytes_req):
147        if num < 0:
148            num = 2 ** (8 * bytes_req) + num
149        hex_str = '%x' % num
150        hex_str = ((bytes_req * 2) - len(hex_str)) * '0' + hex_str
151        return hex_str.decode('hex')
152
153    def py2_be_signed_bytes_to_int(data):
154        output = int(data.encode('hex'), 16)
155        bitsize = len(data) * 8
156        if output < (2 ** (bitsize - 1)):
157            return output
158        return output - (2 ** bitsize)
159
160    def py2_reraise(Err, msg):
161        traceback = sys.exc_info()[2]
162        _locals = {'Err': Err, 'msg': msg, 'traceback': traceback}
163        exec('raise Err, msg, traceback', _locals)
164
165# We do it this way and not just redifine function since Cython do not like it
166if sys.version_info >= (3, 0):
167    btou = py3_btou
168    utob = py3_utob
169    json_dump = py3_json_dump
170    long = int
171    iterkeys = py3_iterkeys
172    itervalues = py3_itervalues
173    iteritems = py3_iteritems
174    is_str = py3_is_str
175    mk_bits = py3_mk_bits
176    str2ints = py3_bytes2ints
177    fstint = py3_fstint
178    appendable = py3_appendable
179    int_to_be_signed_bytes = py3_int_to_be_signed_bytes
180    be_signed_bytes_to_int = py3_be_signed_bytes_to_int
181    reraise = py3_reraise
182else:
183    btou = py2_btou
184    utob = py2_utob
185    json_dump = py2_json_dump
186    iterkeys = py2_iterkeys
187    itervalues = py2_itervalues
188    iteritems = py2_iteritems
189    long = long
190    is_str = py2_is_str
191    mk_bits = py2_mk_bits
192    str2ints = py2_str2ints
193    fstint = py2_fstint
194    appendable = py2_appendable
195    int_to_be_signed_bytes = py2_int_to_be_signed_bytes
196    be_signed_bytes_to_int = py2_be_signed_bytes_to_int
197    reraise = py2_reraise
198