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