mirror of https://github.com/mahmoud/boltons.git
tbutils docstrings 90% done
This commit is contained in:
parent
8e55d2b676
commit
e16a2b4bc5
|
@ -19,7 +19,6 @@ There is also the :class:`ContextualTracebackInfo` variant of
|
||||||
:class:`TracebackInfo`, which includes much more information from each
|
:class:`TracebackInfo`, which includes much more information from each
|
||||||
frame of the callstack, including values of locals and neighboring
|
frame of the callstack, including values of locals and neighboring
|
||||||
lines of code.
|
lines of code.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
@ -100,10 +99,13 @@ class Callpoint(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_tb(cls, tb):
|
def from_tb(cls, tb):
|
||||||
# main difference with from_frame is that lineno and lasti
|
"""\
|
||||||
# come from the traceback, which is to say the line that
|
Create a Callpoint from the traceback of the current
|
||||||
# failed in the try block, not the line currently being
|
exception. Main difference with :meth:`from_frame` is that
|
||||||
# executed (in the except block)
|
``lineno`` and ``lasti`` come from the traceback, which is to
|
||||||
|
say the line that failed in the try block, not the line
|
||||||
|
currently being executed (in the except block).
|
||||||
|
"""
|
||||||
func_name = tb.tb_frame.f_code.co_name
|
func_name = tb.tb_frame.f_code.co_name
|
||||||
lineno = tb.tb_lineno
|
lineno = tb.tb_lineno
|
||||||
lasti = tb.tb_lasti
|
lasti = tb.tb_lasti
|
||||||
|
@ -122,6 +124,12 @@ class Callpoint(object):
|
||||||
return '%s(%s)' % (cn, ', '.join([repr(a) for a in args]))
|
return '%s(%s)' % (cn, ', '.join([repr(a) for a in args]))
|
||||||
|
|
||||||
def tb_frame_str(self):
|
def tb_frame_str(self):
|
||||||
|
"""\
|
||||||
|
Render the Callpoint as it would appear in a standard printed
|
||||||
|
Python traceback. Returns a string with filename, line number,
|
||||||
|
function name, and the actual code line of the error on up to
|
||||||
|
two lines.
|
||||||
|
"""
|
||||||
ret = ' File "%s", line %s, in %s\n' % (self.module_path,
|
ret = ' File "%s", line %s, in %s\n' % (self.module_path,
|
||||||
self.lineno,
|
self.lineno,
|
||||||
self.func_name)
|
self.func_name)
|
||||||
|
@ -131,6 +139,20 @@ class Callpoint(object):
|
||||||
|
|
||||||
|
|
||||||
class _DeferredLine(object):
|
class _DeferredLine(object):
|
||||||
|
"""\
|
||||||
|
The _DeferredLine type allows Callpoints and TracebackInfos to be
|
||||||
|
constructed without potentially hitting the filesystem, as is the
|
||||||
|
normal behavior of the standard Python :mod:`traceback` and
|
||||||
|
:mod:`linecache` modules. Calling :func:`str` fetches and caches
|
||||||
|
the line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): the path of the file containing the line
|
||||||
|
lineno (int): the number of the line in question
|
||||||
|
module_globals (dict): an optional dict of module globals,
|
||||||
|
used to handle advanced use cases using custom module loaders.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, filename, lineno, module_globals=None):
|
def __init__(self, filename, lineno, module_globals=None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
|
@ -172,7 +194,17 @@ class _DeferredLine(object):
|
||||||
|
|
||||||
# TODO: dedup frames, look at __eq__ on _DeferredLine
|
# TODO: dedup frames, look at __eq__ on _DeferredLine
|
||||||
class TracebackInfo(object):
|
class TracebackInfo(object):
|
||||||
|
"""
|
||||||
|
The TracebackInfo class provides a basic representation of a stack
|
||||||
|
trace, be it from an exception being handled or just part of
|
||||||
|
normal execution. It is basically a wrapper around a list of
|
||||||
|
:class:`Callpoint` objects representing frames.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frames (list): A list of frame objects in the stack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: from current and from current exc
|
||||||
callpoint_type = Callpoint
|
callpoint_type = Callpoint
|
||||||
|
|
||||||
def __init__(self, frames):
|
def __init__(self, frames):
|
||||||
|
@ -180,9 +212,19 @@ class TracebackInfo(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_frame(cls, frame, limit=None):
|
def from_frame(cls, frame, limit=None):
|
||||||
|
"""\
|
||||||
|
Create a new TracebackInfo from the frame passed by recurring up
|
||||||
|
in the stack up to *limit* times.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (types.FrameType): frame object from
|
||||||
|
:func:`sys._getframe` or elsewhere.
|
||||||
|
limit (int): max number of parent frames to extract
|
||||||
|
(defaults to :ref:`sys.tracebacklimit`)
|
||||||
|
"""
|
||||||
ret = []
|
ret = []
|
||||||
if frame is None:
|
if frame is None:
|
||||||
frame = sys._getframe(1) # cross-impl yadayada
|
frame = sys._getframe(1)
|
||||||
if limit is None:
|
if limit is None:
|
||||||
limit = getattr(sys, 'tracebacklimit', 1000)
|
limit = getattr(sys, 'tracebacklimit', 1000)
|
||||||
n = 0
|
n = 0
|
||||||
|
@ -196,6 +238,16 @@ class TracebackInfo(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_traceback(cls, tb, limit=None):
|
def from_traceback(cls, tb, limit=None):
|
||||||
|
"""\
|
||||||
|
Create a new TracebackInfo from the traceback passed by recurring up
|
||||||
|
in the stack up to *limit* times.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (types.FrameType): frame object from
|
||||||
|
:func:`sys.exc_info` or elsewhere.
|
||||||
|
limit (int): max number of parent frames to extract
|
||||||
|
(defaults to :ref:`sys.tracebacklimit`)
|
||||||
|
"""
|
||||||
ret = []
|
ret = []
|
||||||
if limit is None:
|
if limit is None:
|
||||||
limit = getattr(sys, 'tracebacklimit', 1000)
|
limit = getattr(sys, 'tracebacklimit', 1000)
|
||||||
|
@ -209,9 +261,14 @@ class TracebackInfo(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, d):
|
def from_dict(cls, d):
|
||||||
|
"Complements :meth:`TracebackInfo.to_dict`."
|
||||||
|
# TODO: check this.
|
||||||
return cls(d['frames'])
|
return cls(d['frames'])
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"""Returns a dict with a list of :class:`Callpoint` frames converted
|
||||||
|
to dicts.
|
||||||
|
"""
|
||||||
return {'frames': [f.to_dict() for f in self.frames]}
|
return {'frames': [f.to_dict() for f in self.frames]}
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
@ -234,12 +291,42 @@ class TracebackInfo(object):
|
||||||
return self.get_formatted()
|
return self.get_formatted()
|
||||||
|
|
||||||
def get_formatted(self):
|
def get_formatted(self):
|
||||||
|
"""\
|
||||||
|
Returns a string as formatted in the traditional Python
|
||||||
|
builtin style observable when an exception is not caught. In
|
||||||
|
other words, mimics :func:`traceback.format_tb` and
|
||||||
|
:func:`traceback.format_stack`.
|
||||||
|
"""
|
||||||
ret = 'Traceback (most recent call last):\n'
|
ret = 'Traceback (most recent call last):\n'
|
||||||
ret += ''.join([f.tb_frame_str() for f in self.frames])
|
ret += ''.join([f.tb_frame_str() for f in self.frames])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class ExceptionInfo(object):
|
class ExceptionInfo(object):
|
||||||
|
"""\
|
||||||
|
An ExceptionInfo object ties together three main fields suitable
|
||||||
|
for representing an instance of an exception: The exception type
|
||||||
|
name, a string representation of the exception itself (the
|
||||||
|
exception message), and information about the traceback (stored as
|
||||||
|
a :class:`TracebackInfo` object).
|
||||||
|
|
||||||
|
These fields line up with :func:`sys.exc_info`, but unlike the
|
||||||
|
values returned by that function, ExceptionInfo does not hold any
|
||||||
|
references to the real exception or traceback. This property makes
|
||||||
|
it suitable for serialization or long-term retention, without
|
||||||
|
worrying about formatting pitfalls, circular references, or leaking memory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
exc_type (str): The exception type name.
|
||||||
|
exc_msg (str): String representation of the exception value.
|
||||||
|
tb_info (TracebackInfo): Information about the stack trace of the
|
||||||
|
exception.
|
||||||
|
|
||||||
|
Like the TracebackInfo, ExceptionInfo is most commonly
|
||||||
|
instantiated from one of its classmethods: :meth:`from_exc_info`
|
||||||
|
or :meth:`from_current`.
|
||||||
|
"""
|
||||||
|
|
||||||
tb_info_type = TracebackInfo
|
tb_info_type = TracebackInfo
|
||||||
|
|
||||||
|
@ -264,6 +351,10 @@ class ExceptionInfo(object):
|
||||||
return cls.from_exc_info(*sys.exc_info())
|
return cls.from_exc_info(*sys.exc_info())
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"""\
|
||||||
|
Get a :class:`dict` representation of the ExceptionInfo, suitable
|
||||||
|
for JSON serialization.
|
||||||
|
"""
|
||||||
return {'exc_type': self.exc_type,
|
return {'exc_type': self.exc_type,
|
||||||
'exc_msg': self.exc_msg,
|
'exc_msg': self.exc_msg,
|
||||||
'exc_tb': self.tb_info.to_dict()}
|
'exc_tb': self.tb_info.to_dict()}
|
||||||
|
@ -280,12 +371,25 @@ class ExceptionInfo(object):
|
||||||
return '<%s [%s: %s] (%s frames%s)>' % args
|
return '<%s [%s: %s] (%s frames%s)>' % args
|
||||||
|
|
||||||
def get_formatted(self):
|
def get_formatted(self):
|
||||||
|
"""\
|
||||||
|
Returns a string formatted in the traditional Python
|
||||||
|
builtin style observable when an exception is not caught. In
|
||||||
|
other words, mimics :func:`traceback.format_exception`.
|
||||||
|
"""
|
||||||
# TODO: add SyntaxError formatting
|
# TODO: add SyntaxError formatting
|
||||||
tb_str = str(self.tb_info)
|
tb_str = self.tb_info.get_formatted()
|
||||||
return ''.join([tb_str, '%s: %s' % (self.exc_type, self.exc_msg)])
|
return ''.join([tb_str, '%s: %s' % (self.exc_type, self.exc_msg)])
|
||||||
|
|
||||||
|
|
||||||
class ContextualCallpoint(Callpoint):
|
class ContextualCallpoint(Callpoint):
|
||||||
|
"""The ContextualCallpoint is a :class:`Callpoint` subtype with the
|
||||||
|
exact same API and storing two additional values:
|
||||||
|
|
||||||
|
1. :func:`repr` outputs for local variables from the Callpoint's scope
|
||||||
|
2. A number of lines before and after the Callpoint's line of code
|
||||||
|
|
||||||
|
The ContextualCallpoint is used by the :class:`ContextualTracebackInfo`.
|
||||||
|
"""
|
||||||
def __init__(self, *a, **kw):
|
def __init__(self, *a, **kw):
|
||||||
self.local_reprs = kw.pop('local_reprs', {})
|
self.local_reprs = kw.pop('local_reprs', {})
|
||||||
self.pre_lines = kw.pop('pre_lines', [])
|
self.pre_lines = kw.pop('pre_lines', [])
|
||||||
|
@ -294,6 +398,7 @@ class ContextualCallpoint(Callpoint):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_frame(cls, frame):
|
def from_frame(cls, frame):
|
||||||
|
"Identical to :meth:`Callpoint.from_frame`"
|
||||||
ret = super(ContextualCallpoint, cls).from_frame(frame)
|
ret = super(ContextualCallpoint, cls).from_frame(frame)
|
||||||
ret._populate_local_reprs(frame.f_locals)
|
ret._populate_local_reprs(frame.f_locals)
|
||||||
ret._populate_context_lines()
|
ret._populate_context_lines()
|
||||||
|
@ -301,6 +406,7 @@ class ContextualCallpoint(Callpoint):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_tb(cls, tb):
|
def from_tb(cls, tb):
|
||||||
|
"Identical to :meth:`Callpoint.from_tb`"
|
||||||
ret = super(ContextualCallpoint, cls).from_tb(tb)
|
ret = super(ContextualCallpoint, cls).from_tb(tb)
|
||||||
ret._populate_local_reprs(tb.tb_frame.f_locals)
|
ret._populate_local_reprs(tb.tb_frame.f_locals)
|
||||||
ret._populate_context_lines()
|
ret._populate_context_lines()
|
||||||
|
@ -332,6 +438,24 @@ class ContextualCallpoint(Callpoint):
|
||||||
return
|
return
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
Same principle as :meth:`Callpoint.to_dict`, but with the added
|
||||||
|
contextual values. With ``ContextualCallpoint.to_dict()``,
|
||||||
|
each frame will now be represented like::
|
||||||
|
|
||||||
|
{'func_name': 'print_example',
|
||||||
|
'lineno': 0,
|
||||||
|
'module_name': 'example_module',
|
||||||
|
'module_path': '/home/example/example_module.pyc',
|
||||||
|
'lasti': 0,
|
||||||
|
'line': 'print "example"',
|
||||||
|
'locals': {'variable': '"value"'},
|
||||||
|
'pre_lines': ['variable = "value"'],
|
||||||
|
'post_lines': []}
|
||||||
|
|
||||||
|
The locals dictionary and line lists are copies and can be mutated
|
||||||
|
freely.
|
||||||
|
"""
|
||||||
ret = super(ContextualCallpoint, self).to_dict()
|
ret = super(ContextualCallpoint, self).to_dict()
|
||||||
ret['locals'] = dict(self.local_reprs)
|
ret['locals'] = dict(self.local_reprs)
|
||||||
|
|
||||||
|
@ -361,10 +485,25 @@ class ContextualCallpoint(Callpoint):
|
||||||
|
|
||||||
|
|
||||||
class ContextualTracebackInfo(TracebackInfo):
|
class ContextualTracebackInfo(TracebackInfo):
|
||||||
|
"""\
|
||||||
|
The ContextualTracebackInfo type is a :class:`TracebackInfo`
|
||||||
|
subtype that is used by :class:`ContextualExceptionInfo` and uses
|
||||||
|
the :class:`ContextualCallpoint` as its frame-representing
|
||||||
|
primitive.
|
||||||
|
"""
|
||||||
callpoint_type = ContextualCallpoint
|
callpoint_type = ContextualCallpoint
|
||||||
|
|
||||||
|
|
||||||
class ContextualExceptionInfo(ExceptionInfo):
|
class ContextualExceptionInfo(ExceptionInfo):
|
||||||
|
"""\
|
||||||
|
The ContextualTracebackInfo type is a :class:`TracebackInfo`
|
||||||
|
subtype that uses the :class:`ContextualCallpoint` as its
|
||||||
|
frame-representing primitive.
|
||||||
|
|
||||||
|
It carries with it most of the exception information required to
|
||||||
|
recreate the widely recognizable "500" page for debugging Django
|
||||||
|
applications.
|
||||||
|
"""
|
||||||
tb_info_type = ContextualTracebackInfo
|
tb_info_type = ContextualTracebackInfo
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,6 +603,11 @@ def print_exception(etype, value, tb, limit=None, file=None):
|
||||||
|
|
||||||
|
|
||||||
def fix_print_exception():
|
def fix_print_exception():
|
||||||
|
"""
|
||||||
|
Sets the default exception hook :func:`sys.excepthook` to the
|
||||||
|
:func:`tbutils.print_exception` that uses all the ``tbutils``
|
||||||
|
facilities to provide slightly more correct output behavior.
|
||||||
|
"""
|
||||||
sys.excepthook = print_exception
|
sys.excepthook = print_exception
|
||||||
|
|
||||||
|
|
||||||
|
@ -473,8 +617,15 @@ _se_frame_re = re.compile(r'^File "(?P<filepath>.+)", line (?P<lineno>\d+)')
|
||||||
|
|
||||||
|
|
||||||
class ParsedTB(object):
|
class ParsedTB(object):
|
||||||
"""
|
"""\
|
||||||
Parses a traceback string as typically output by sys.excepthook.
|
Stores a parsed traceback and exception as would be typically
|
||||||
|
output by :func:`sys.excepthook` or
|
||||||
|
:func:`traceback.print_exception`.
|
||||||
|
|
||||||
|
.. note:
|
||||||
|
|
||||||
|
Does not currently store SyntaxError details such as column.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, exc_type_name, exc_msg, frames=None):
|
def __init__(self, exc_type_name, exc_msg, frames=None):
|
||||||
self.exc_type = exc_type_name
|
self.exc_type = exc_type_name
|
||||||
|
@ -483,15 +634,20 @@ class ParsedTB(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_file(self):
|
def source_file(self):
|
||||||
|
"""
|
||||||
|
The file path of module containing the function that raised the
|
||||||
|
exception, or None if not available.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self.frames[-1]['filepath']
|
return self.frames[-1]['filepath']
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"Get a copy as a JSON-serializable :class:`dict`"
|
||||||
return {'exc_type': self.exc_type,
|
return {'exc_type': self.exc_type,
|
||||||
'exc_msg': self.exc_msg,
|
'exc_msg': self.exc_msg,
|
||||||
'frames': self.frames}
|
'frames': self.frames} # TODO: copy?
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
cn = self.__class__.__name__
|
cn = self.__class__.__name__
|
||||||
|
@ -500,11 +656,24 @@ class ParsedTB(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, tb_str):
|
def from_string(cls, tb_str):
|
||||||
|
"""Parse a traceback and exception from the text *tb_str*. This text
|
||||||
|
is expected to have been decoded, otherwise it will be
|
||||||
|
interpreted as UTF-8.
|
||||||
|
|
||||||
|
This method does not search a larger body of text for
|
||||||
|
tracebacks. If the first line of the text passed does not
|
||||||
|
match one of the known patterns, a :exc:`ValueError` will be
|
||||||
|
raised. This method will ignore trailing text after the end of
|
||||||
|
the first traceback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tb_str (str): The traceback text (:class:`unicode` or UTF-8 bytes)
|
||||||
|
"""
|
||||||
if not isinstance(tb_str, unicode):
|
if not isinstance(tb_str, unicode):
|
||||||
tb_str = tb_str.decode('utf-8')
|
tb_str = tb_str.decode('utf-8')
|
||||||
tb_lines = tb_str.lstrip().splitlines()
|
tb_lines = tb_str.lstrip().splitlines()
|
||||||
|
|
||||||
# first off, handle some ignored exceptions. these can be the
|
# First off, handle some ignored exceptions. These can be the
|
||||||
# result of exceptions raised by __del__ during garbage
|
# result of exceptions raised by __del__ during garbage
|
||||||
# collection
|
# collection
|
||||||
while tb_lines:
|
while tb_lines:
|
||||||
|
@ -513,11 +682,13 @@ class ParsedTB(object):
|
||||||
tb_lines.pop()
|
tb_lines.pop()
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
if tb_lines and tb_lines[0].strip() == 'Traceback (most recent call last):':
|
if tb_lines and tb_lines[0].strip() == 'Traceback (most recent call last):':
|
||||||
start_line = 1
|
start_line = 1
|
||||||
frame_re = _frame_re
|
frame_re = _frame_re
|
||||||
elif len(tb_lines) > 1 and tb_lines[-2].lstrip().startswith('^'):
|
elif len(tb_lines) > 1 and tb_lines[-2].lstrip().startswith('^'):
|
||||||
|
# This is to handle the slight formatting difference
|
||||||
|
# associated with SyntaxErrors, which also don't really
|
||||||
|
# have tracebacks
|
||||||
start_line = 0
|
start_line = 0
|
||||||
frame_re = _se_frame_re
|
frame_re = _se_frame_re
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue