1# -*- coding: utf-8 -*-
2
3"""
4    thriftpy._compat
5    ~~~~~~~~~~~~~
6
7    py2/py3 compatibility support.
8"""
9
10from __future__ import absolute_import
11
12import platform
13import sys
14import types
15
16PY3 = sys.version_info[0] == 3
17PYPY = "__pypy__" in sys.modules
18JYTHON = sys.platform.startswith("java")
19
20UNIX = platform.system() in ("Linux", "Darwin")
21CYTHON = False  # Cython always disabled in pypy and windows
22
23# only python2.7.9 and python 3.4 or above have true ssl context
24MODERN_SSL = (2, 7, 9) <= sys.version_info < (3, 0, 0) or \
25    sys.version_info >= (3, 4)
26
27if PY3:
28    text_type = str
29    string_types = (str,)
30
31    def u(s):
32        return s
33else:
34    text_type = unicode  # noqa
35    string_types = (str, unicode)  # noqa
36
37    def u(s):
38        if not isinstance(s, text_type):
39            s = s.decode("utf-8")
40        return s
41
42# `LOAD_ATTR` constants of `org.python.core.Opcode` class differs in Jython 2.7.0 and Jython 2.7.1
43# <= Jython 2.7.1b3
44# `Opcode` class in Jython 2.7.0 has the comment: "derived from CPython 2.5.2 Include/opcode.h"
45JYTHON_2_7_0_LOAD_ATTR = 105
46# >= Jython 2.7.1rc1
47# `Opcode` class in Jython 2.7.1 has the comment: "derived from CPython 2.7.12 Include/opcode.h"
48JYTHON_2_7_1_LOAD_ATTR = 106
49
50
51def with_metaclass(meta, *bases):
52    """Create a base class with a metaclass for py2 & py3
53
54    This code snippet is copied from six."""
55    # This requires a bit of explanation: the basic idea is to make a
56    # dummy metaclass for one level of class instantiation that replaces
57    # itself with the actual metaclass.  Because of internal type checks
58    # we also need to make sure that we downgrade the custom metaclass
59    # for one level to something closer to type (that's why __call__ and
60    # __init__ comes back from type etc.).
61    class metaclass(meta):
62        __call__ = type.__call__
63        __init__ = type.__init__
64
65        def __new__(cls, name, this_bases, d):
66            if this_bases is None:
67                return type.__new__(cls, name, (), d)
68            return meta(name, bases, d)
69    return metaclass('temporary_class', None, {})
70
71
72def init_func_generator(spec):
73    """Generate `__init__` function based on TPayload.default_spec
74
75    For example::
76
77        spec = [('name', 'Alice'), ('number', None)]
78
79    will generate::
80
81        def __init__(self, name='Alice', number=None):
82            kwargs = locals()
83            kwargs.pop('self')
84            self.__dict__.update(kwargs)
85
86    TODO: The `locals()` part may need refine.
87    """
88    if not spec:
89        def __init__(self):
90            pass
91        return __init__
92
93    varnames, defaults = zip(*spec)
94    varnames = ('self', ) + varnames
95
96    def init(self):
97        self.__dict__ = locals().copy()
98        del self.__dict__['self']
99
100    code = init.__code__
101    if PY3:
102        args = [
103            len(varnames),
104            0,
105            len(varnames),
106            code.co_stacksize,
107            code.co_flags,
108            code.co_code,
109            code.co_consts,
110            code.co_names,
111            varnames,
112            code.co_filename,
113            "__init__",
114            code.co_firstlineno,
115            code.co_lnotab,
116            code.co_freevars,
117            code.co_cellvars
118        ]
119        if sys.version_info >= (3, 8, 0):
120            # Python 3.8 and above supports positional-only parameters. The number of such
121            # parameters is passed to the constructor as the second argument.
122            args.insert(1, 0)
123        new_code = types.CodeType(*args)
124    elif JYTHON:
125        from org.python.core import PyBytecode
126
127        # the following attributes are not available for `code` in Jython
128
129        co_stacksize = 2
130        if sys.version_info < (2, 7, 1):
131            load_attr = JYTHON_2_7_0_LOAD_ATTR
132        else:
133            load_attr = JYTHON_2_7_1_LOAD_ATTR
134
135        #  0 LOAD_GLOBAL              0 (locals)
136        #  3 CALL_FUNCTION            0
137        #  6 LOAD_ATTR                1 (copy)
138        #  9 CALL_FUNCTION            0
139        # 12 LOAD_FAST                0 (self)
140        # 15 STORE_ATTR               2 (__dict__)
141        #
142        # 18 LOAD_FAST                0 (self)
143        # 21 LOAD_ATTR                2 (__dict__)
144        # 24 LOAD_CONST               1 ('self')
145        # 27 DELETE_SUBSCR
146        # 28 LOAD_CONST               0 (None)
147        # 31 RETURN_VALUE
148
149        co_code = b't\x00\x00\x83\x00\x00{0:c}\x01\x00\x83\x00\x00|\x00\x00_\x02\x00' \
150                  b'|\x00\x00{0:c}\x02\x00d\x01\x00=d\x00\x00S'.format(load_attr)
151        co_consts = (None, 'self')
152        co_names = ('locals', 'copy', '__dict__')
153        co_lnotab = b'\x00\x01\x12\x01'
154
155        new_code = PyBytecode(len(varnames),
156                              len(varnames),
157                              co_stacksize,
158                              code.co_flags,
159                              co_code,
160                              co_consts,
161                              co_names,
162                              varnames,
163                              code.co_filename,
164                              "__init__",
165                              code.co_firstlineno,
166                              co_lnotab,
167                              code.co_freevars,
168                              code.co_cellvars)
169    else:
170        new_code = types.CodeType(len(varnames),
171                                  len(varnames),
172                                  code.co_stacksize,
173                                  code.co_flags,
174                                  code.co_code,
175                                  code.co_consts,
176                                  code.co_names,
177                                  varnames,
178                                  code.co_filename,
179                                  "__init__",
180                                  code.co_firstlineno,
181                                  code.co_lnotab,
182                                  code.co_freevars,
183                                  code.co_cellvars)
184
185    return types.FunctionType(new_code,
186                              {"__builtins__": __builtins__},
187                              argdefs=defaults)
188