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
|
||||
frame of the callstack, including values of locals and neighboring
|
||||
lines of code.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
@ -100,10 +99,13 @@ class Callpoint(object):
|
|||
|
||||
@classmethod
|
||||
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
|
||||
# failed in the try block, not the line currently being
|
||||
# executed (in the except block)
|
||||
"""\
|
||||
Create a Callpoint from the traceback of the current
|
||||
exception. Main difference with :meth:`from_frame` is that
|
||||
``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
|
||||
lineno = tb.tb_lineno
|
||||
lasti = tb.tb_lasti
|
||||
|
@ -122,6 +124,12 @@ class Callpoint(object):
|
|||
return '%s(%s)' % (cn, ', '.join([repr(a) for a in args]))
|
||||
|
||||
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,
|
||||
self.lineno,
|
||||
self.func_name)
|
||||
|
@ -131,6 +139,20 @@ class Callpoint(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):
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
|
@ -172,7 +194,17 @@ class _DeferredLine(object):
|
|||
|
||||
# TODO: dedup frames, look at __eq__ on _DeferredLine
|
||||
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
|
||||
|
||||
def __init__(self, frames):
|
||||
|
@ -180,9 +212,19 @@ class TracebackInfo(object):
|
|||
|
||||
@classmethod
|
||||
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 = []
|
||||
if frame is None:
|
||||
frame = sys._getframe(1) # cross-impl yadayada
|
||||
frame = sys._getframe(1)
|
||||
if limit is None:
|
||||
limit = getattr(sys, 'tracebacklimit', 1000)
|
||||
n = 0
|
||||
|
@ -196,6 +238,16 @@ class TracebackInfo(object):
|
|||
|
||||
@classmethod
|
||||
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 = []
|
||||
if limit is None:
|
||||
limit = getattr(sys, 'tracebacklimit', 1000)
|
||||
|
@ -209,9 +261,14 @@ class TracebackInfo(object):
|
|||
|
||||
@classmethod
|
||||
def from_dict(cls, d):
|
||||
"Complements :meth:`TracebackInfo.to_dict`."
|
||||
# TODO: check this.
|
||||
return cls(d['frames'])
|
||||
|
||||
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]}
|
||||
|
||||
def __len__(self):
|
||||
|
@ -234,12 +291,42 @@ class TracebackInfo(object):
|
|||
return self.get_formatted()
|
||||
|
||||
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 += ''.join([f.tb_frame_str() for f in self.frames])
|
||||
return ret
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
@ -264,6 +351,10 @@ class ExceptionInfo(object):
|
|||
return cls.from_exc_info(*sys.exc_info())
|
||||
|
||||
def to_dict(self):
|
||||
"""\
|
||||
Get a :class:`dict` representation of the ExceptionInfo, suitable
|
||||
for JSON serialization.
|
||||
"""
|
||||
return {'exc_type': self.exc_type,
|
||||
'exc_msg': self.exc_msg,
|
||||
'exc_tb': self.tb_info.to_dict()}
|
||||
|
@ -280,12 +371,25 @@ class ExceptionInfo(object):
|
|||
return '<%s [%s: %s] (%s frames%s)>' % args
|
||||
|
||||
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
|
||||
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)])
|
||||
|
||||
|
||||
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):
|
||||
self.local_reprs = kw.pop('local_reprs', {})
|
||||
self.pre_lines = kw.pop('pre_lines', [])
|
||||
|
@ -294,6 +398,7 @@ class ContextualCallpoint(Callpoint):
|
|||
|
||||
@classmethod
|
||||
def from_frame(cls, frame):
|
||||
"Identical to :meth:`Callpoint.from_frame`"
|
||||
ret = super(ContextualCallpoint, cls).from_frame(frame)
|
||||
ret._populate_local_reprs(frame.f_locals)
|
||||
ret._populate_context_lines()
|
||||
|
@ -301,6 +406,7 @@ class ContextualCallpoint(Callpoint):
|
|||
|
||||
@classmethod
|
||||
def from_tb(cls, tb):
|
||||
"Identical to :meth:`Callpoint.from_tb`"
|
||||
ret = super(ContextualCallpoint, cls).from_tb(tb)
|
||||
ret._populate_local_reprs(tb.tb_frame.f_locals)
|
||||
ret._populate_context_lines()
|
||||
|
@ -332,6 +438,24 @@ class ContextualCallpoint(Callpoint):
|
|||
return
|
||||
|
||||
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['locals'] = dict(self.local_reprs)
|
||||
|
||||
|
@ -361,10 +485,25 @@ class ContextualCallpoint(Callpoint):
|
|||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -464,6 +603,11 @@ def print_exception(etype, value, tb, limit=None, file=None):
|
|||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -473,8 +617,15 @@ _se_frame_re = re.compile(r'^File "(?P<filepath>.+)", line (?P<lineno>\d+)')
|
|||
|
||||
|
||||
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):
|
||||
self.exc_type = exc_type_name
|
||||
|
@ -483,15 +634,20 @@ class ParsedTB(object):
|
|||
|
||||
@property
|
||||
def source_file(self):
|
||||
"""
|
||||
The file path of module containing the function that raised the
|
||||
exception, or None if not available.
|
||||
"""
|
||||
try:
|
||||
return self.frames[-1]['filepath']
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def to_dict(self):
|
||||
"Get a copy as a JSON-serializable :class:`dict`"
|
||||
return {'exc_type': self.exc_type,
|
||||
'exc_msg': self.exc_msg,
|
||||
'frames': self.frames}
|
||||
'frames': self.frames} # TODO: copy?
|
||||
|
||||
def __repr__(self):
|
||||
cn = self.__class__.__name__
|
||||
|
@ -500,11 +656,24 @@ class ParsedTB(object):
|
|||
|
||||
@classmethod
|
||||
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):
|
||||
tb_str = tb_str.decode('utf-8')
|
||||
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
|
||||
# collection
|
||||
while tb_lines:
|
||||
|
@ -513,11 +682,13 @@ class ParsedTB(object):
|
|||
tb_lines.pop()
|
||||
else:
|
||||
break
|
||||
|
||||
if tb_lines and tb_lines[0].strip() == 'Traceback (most recent call last):':
|
||||
start_line = 1
|
||||
frame_re = _frame_re
|
||||
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
|
||||
frame_re = _se_frame_re
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue