aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/_tblib.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/_tblib.py')
-rw-r--r--python/gevent/_tblib.py431
1 files changed, 431 insertions, 0 deletions
diff --git a/python/gevent/_tblib.py b/python/gevent/_tblib.py
new file mode 100644
index 0000000..91edb01
--- /dev/null
+++ b/python/gevent/_tblib.py
@@ -0,0 +1,431 @@
+# -*- coding: utf-8 -*-
+# A vendored version of part of https://github.com/ionelmc/python-tblib
+# pylint:disable=redefined-outer-name,reimported,function-redefined,bare-except,no-else-return,broad-except
+####
+# Copyright (c) 2013-2016, Ionel Cristian Mărieș
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+####
+
+# cpython.py
+
+"""
+Taken verbatim from Jinja2.
+
+https://github.com/mitsuhiko/jinja2/blob/master/jinja2/debug.py#L267
+"""
+#import platform # XXX: gevent cannot import platform at the top level; interferes with monkey patching
+import sys
+
+
+def _init_ugly_crap():
+ """This function implements a few ugly things so that we can patch the
+ traceback objects. The function returned allows resetting `tb_next` on
+ any python traceback object. Do not attempt to use this on non cpython
+ interpreters
+ """
+ import ctypes
+ from types import TracebackType
+
+ # figure out side of _Py_ssize_t
+ if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
+ _Py_ssize_t = ctypes.c_int64
+ else:
+ _Py_ssize_t = ctypes.c_int
+
+ # regular python
+ class _PyObject(ctypes.Structure):
+ pass
+
+ _PyObject._fields_ = [
+ ('ob_refcnt', _Py_ssize_t),
+ ('ob_type', ctypes.POINTER(_PyObject))
+ ]
+
+ # python with trace
+ if hasattr(sys, 'getobjects'):
+ class _PyObject(ctypes.Structure):
+ pass
+
+ _PyObject._fields_ = [
+ ('_ob_next', ctypes.POINTER(_PyObject)),
+ ('_ob_prev', ctypes.POINTER(_PyObject)),
+ ('ob_refcnt', _Py_ssize_t),
+ ('ob_type', ctypes.POINTER(_PyObject))
+ ]
+
+ class _Traceback(_PyObject):
+ pass
+
+ _Traceback._fields_ = [
+ ('tb_next', ctypes.POINTER(_Traceback)),
+ ('tb_frame', ctypes.POINTER(_PyObject)),
+ ('tb_lasti', ctypes.c_int),
+ ('tb_lineno', ctypes.c_int)
+ ]
+
+ def tb_set_next(tb, next):
+ """Set the tb_next attribute of a traceback object."""
+ if not (isinstance(tb, TracebackType) and (next is None or isinstance(next, TracebackType))):
+ raise TypeError('tb_set_next arguments must be traceback objects')
+ obj = _Traceback.from_address(id(tb))
+ if tb.tb_next is not None:
+ old = _Traceback.from_address(id(tb.tb_next))
+ old.ob_refcnt -= 1
+ if next is None:
+ obj.tb_next = ctypes.POINTER(_Traceback)()
+ else:
+ next = _Traceback.from_address(id(next))
+ next.ob_refcnt += 1
+ obj.tb_next = ctypes.pointer(next)
+
+ return tb_set_next
+
+
+tb_set_next = None
+#try:
+# if platform.python_implementation() == 'CPython':
+# tb_set_next = _init_ugly_crap()
+#except Exception as exc:
+# sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc))
+#del _init_ugly_crap
+
+# __init__.py
+import re
+from types import CodeType
+from types import TracebackType
+
+try:
+ from __pypy__ import tproxy
+except ImportError:
+ tproxy = None
+
+__version__ = '1.3.0'
+__all__ = ('Traceback',)
+
+PY3 = sys.version_info[0] == 3
+FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)(, in (?P<co_name>.+))?$')
+
+
+class _AttrDict(dict):
+ __slots__ = ()
+ __getattr__ = dict.__getitem__
+
+
+# noinspection PyPep8Naming
+class __traceback_maker(Exception):
+ pass
+
+
+class TracebackParseError(Exception):
+ pass
+
+
+class Code(object):
+ def __init__(self, code):
+ self.co_filename = code.co_filename
+ self.co_name = code.co_name
+ # gevent: copy more attributes
+ self.co_nlocals = code.co_nlocals
+ self.co_stacksize = code.co_stacksize
+ self.co_flags = code.co_flags
+ self.co_firstlineno = code.co_firstlineno
+
+
+class Frame(object):
+ def __init__(self, frame):
+ self.f_globals = dict([
+ (k, v)
+ for k, v in frame.f_globals.items()
+ if k in ("__file__", "__name__")
+ ])
+ self.f_code = Code(frame.f_code)
+
+ def clear(self):
+ # For compatibility with PyPy 3.5;
+ # clear was added to frame in Python 3.4
+ # and is called by traceback.clear_frames(), which
+ # in turn is called by unittest.TestCase.assertRaises
+ pass
+
+class Traceback(object):
+
+ tb_next = None
+
+ def __init__(self, tb):
+ self.tb_frame = Frame(tb.tb_frame)
+ # noinspection SpellCheckingInspection
+ self.tb_lineno = int(tb.tb_lineno)
+
+ # Build in place to avoid exceeding the recursion limit
+ tb = tb.tb_next
+ prev_traceback = self
+ cls = type(self)
+ while tb is not None:
+ traceback = object.__new__(cls)
+ traceback.tb_frame = Frame(tb.tb_frame)
+ traceback.tb_lineno = int(tb.tb_lineno)
+ prev_traceback.tb_next = traceback
+ prev_traceback = traceback
+ tb = tb.tb_next
+
+ def as_traceback(self):
+ if tproxy:
+ return tproxy(TracebackType, self.__tproxy_handler)
+ if not tb_set_next:
+ raise RuntimeError("Cannot re-create traceback !")
+
+ current = self
+ top_tb = None
+ tb = None
+ while current:
+ f_code = current.tb_frame.f_code
+ code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec')
+ if PY3:
+ code = CodeType(
+ 0, code.co_kwonlyargcount,
+ code.co_nlocals, code.co_stacksize, code.co_flags,
+ code.co_code, code.co_consts, code.co_names, code.co_varnames,
+ f_code.co_filename, f_code.co_name,
+ code.co_firstlineno, code.co_lnotab, (), ()
+ )
+ else:
+ code = CodeType(
+ 0,
+ code.co_nlocals, code.co_stacksize, code.co_flags,
+ code.co_code, code.co_consts, code.co_names, code.co_varnames,
+ f_code.co_filename.encode(), f_code.co_name.encode(),
+ code.co_firstlineno, code.co_lnotab, (), ()
+ )
+
+ # noinspection PyBroadException
+ try:
+ exec(code, current.tb_frame.f_globals, {})
+ except:
+ next_tb = sys.exc_info()[2].tb_next
+ if top_tb is None:
+ top_tb = next_tb
+ if tb is not None:
+ tb_set_next(tb, next_tb)
+ tb = next_tb
+ del next_tb
+
+ current = current.tb_next
+ try:
+ return top_tb
+ finally:
+ del top_tb
+ del tb
+
+
+ # noinspection SpellCheckingInspection
+ def __tproxy_handler(self, operation, *args, **kwargs):
+ if operation in ('__getattribute__', '__getattr__'):
+ if args[0] == 'tb_next':
+ return self.tb_next and self.tb_next.as_traceback()
+ else:
+ return getattr(self, args[0])
+ else:
+ return getattr(self, operation)(*args, **kwargs)
+
+ def to_dict(self):
+ """Convert a Traceback into a dictionary representation"""
+ if self.tb_next is None:
+ tb_next = None
+ else:
+ tb_next = self.tb_next.to_dict()
+
+ code = {
+ 'co_filename': self.tb_frame.f_code.co_filename,
+ 'co_name': self.tb_frame.f_code.co_name,
+ }
+ frame = {
+ 'f_globals': self.tb_frame.f_globals,
+ 'f_code': code,
+ }
+ return {
+ 'tb_frame': frame,
+ 'tb_lineno': self.tb_lineno,
+ 'tb_next': tb_next,
+ }
+
+ @classmethod
+ def from_dict(cls, dct):
+ if dct['tb_next']:
+ tb_next = cls.from_dict(dct['tb_next'])
+ else:
+ tb_next = None
+
+ code = _AttrDict(
+ co_filename=dct['tb_frame']['f_code']['co_filename'],
+ co_name=dct['tb_frame']['f_code']['co_name'],
+ )
+ frame = _AttrDict(
+ f_globals=dct['tb_frame']['f_globals'],
+ f_code=code,
+ )
+ tb = _AttrDict(
+ tb_frame=frame,
+ tb_lineno=dct['tb_lineno'],
+ tb_next=tb_next,
+ )
+ return cls(tb)
+
+ @classmethod
+ def from_string(cls, string, strict=True):
+ frames = []
+ header = strict
+
+ for line in string.splitlines():
+ line = line.rstrip()
+ if header:
+ if line == 'Traceback (most recent call last):':
+ header = False
+ continue
+ frame_match = FRAME_RE.match(line)
+ if frame_match:
+ frames.append(frame_match.groupdict())
+ elif line.startswith(' '):
+ pass
+ elif strict:
+ break # traceback ended
+
+ if frames:
+ previous = None
+ for frame in reversed(frames):
+ previous = _AttrDict(
+ frame,
+ tb_frame=_AttrDict(
+ frame,
+ f_globals=_AttrDict(
+ __file__=frame['co_filename'],
+ __name__='?',
+ ),
+ f_code=_AttrDict(frame),
+ ),
+ tb_next=previous,
+ )
+ return cls(previous)
+ else:
+ raise TracebackParseError("Could not find any frames in %r." % string)
+
+# pickling_support.py
+
+
+def unpickle_traceback(tb_frame, tb_lineno, tb_next):
+ ret = object.__new__(Traceback)
+ ret.tb_frame = tb_frame
+ ret.tb_lineno = tb_lineno
+ ret.tb_next = tb_next
+ return ret.as_traceback()
+
+
+def pickle_traceback(tb):
+ return unpickle_traceback, (Frame(tb.tb_frame), tb.tb_lineno, tb.tb_next and Traceback(tb.tb_next))
+
+
+def install():
+ try:
+ import copy_reg
+ except ImportError:
+ import copyreg as copy_reg
+
+ copy_reg.pickle(TracebackType, pickle_traceback)
+
+# Added by gevent
+
+# We have to defer the initialization, and especially the import of platform,
+# until runtime. If we're monkey patched, we need to be sure to use
+# the original __import__ to avoid switching through the hub due to
+# import locks on Python 2. See also builtins.py for details.
+
+
+def _unlocked_imports(f):
+ def g(a):
+ if sys is None: # pragma: no cover
+ # interpreter shutdown on Py2
+ return
+
+ gb = None
+ if 'gevent.builtins' in sys.modules:
+ gb = sys.modules['gevent.builtins']
+ gb._unlock_imports()
+ try:
+ return f(a)
+ finally:
+ if gb is not None:
+ gb._lock_imports()
+ g.__name__ = f.__name__
+ g.__module__ = f.__module__
+ return g
+
+
+def _import_dump_load():
+ global dumps
+ global loads
+ try:
+ import cPickle as pickle
+ except ImportError:
+ import pickle
+ dumps = pickle.dumps
+ loads = pickle.loads
+
+dumps = loads = None
+
+_installed = False
+
+
+def _init():
+ global _installed
+ global tb_set_next
+ if _installed:
+ return
+
+ _installed = True
+ import platform
+ try:
+ if platform.python_implementation() == 'CPython':
+ tb_set_next = _init_ugly_crap()
+ except Exception as exc:
+ sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc))
+
+ try:
+ from __pypy__ import tproxy
+ except ImportError:
+ tproxy = None
+
+ if not tb_set_next and not tproxy:
+ raise ImportError("Cannot use tblib. Runtime not supported.")
+ _import_dump_load()
+ install()
+
+
+@_unlocked_imports
+def dump_traceback(tb):
+ # Both _init and dump/load have to be unlocked, because
+ # copy_reg and pickle can do imports to resolve class names; those
+ # class names are in this module and greenlet safe though
+ _init()
+ return dumps(tb)
+
+
+@_unlocked_imports
+def load_traceback(s):
+ _init()
+ return loads(s)