[svn r37674] Added document 'code.txt' that describes py.code, added docstrings to py.code
public items. --HG-- branch : trunk
This commit is contained in:
parent
65f51efa55
commit
58eace43f9
|
@ -1,6 +1,7 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
class Code(object):
|
class Code(object):
|
||||||
|
""" wrapper around Python code objects """
|
||||||
def __init__(self, rawcode):
|
def __init__(self, rawcode):
|
||||||
rawcode = getattr(rawcode, 'im_func', rawcode)
|
rawcode = getattr(rawcode, 'im_func', rawcode)
|
||||||
rawcode = getattr(rawcode, 'func_code', rawcode)
|
rawcode = getattr(rawcode, 'func_code', rawcode)
|
||||||
|
@ -57,6 +58,7 @@ class Code(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def path(self):
|
def path(self):
|
||||||
|
""" return a py.path.local object wrapping the source of the code """
|
||||||
try:
|
try:
|
||||||
return self.raw.co_filename.__path__
|
return self.raw.co_filename.__path__
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -64,6 +66,8 @@ class Code(object):
|
||||||
path = property(path, None, None, "path of this code object")
|
path = property(path, None, None, "path of this code object")
|
||||||
|
|
||||||
def fullsource(self):
|
def fullsource(self):
|
||||||
|
""" return a py.code.Source object for the full source file of the code
|
||||||
|
"""
|
||||||
fn = self.raw.co_filename
|
fn = self.raw.co_filename
|
||||||
try:
|
try:
|
||||||
return fn.__source__
|
return fn.__source__
|
||||||
|
@ -73,11 +77,16 @@ class Code(object):
|
||||||
"full source containing this code object")
|
"full source containing this code object")
|
||||||
|
|
||||||
def source(self):
|
def source(self):
|
||||||
|
""" return a py.code.Source object for the code object's source only
|
||||||
|
"""
|
||||||
# return source only for that part of code
|
# return source only for that part of code
|
||||||
import inspect
|
import inspect
|
||||||
return py.code.Source(inspect.getsource(self.raw))
|
return py.code.Source(inspect.getsource(self.raw))
|
||||||
|
|
||||||
def getargs(self):
|
def getargs(self):
|
||||||
|
""" return a tuple with the argument names for the code object
|
||||||
|
"""
|
||||||
# handfull shortcut for getting args
|
# handfull shortcut for getting args
|
||||||
raw = self.raw
|
raw = self.raw
|
||||||
return raw.co_varnames[:raw.co_argcount]
|
return raw.co_varnames[:raw.co_argcount]
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,13 @@ class ExceptionInfo(object):
|
||||||
self.traceback = py.code.Traceback(tb)
|
self.traceback = py.code.Traceback(tb)
|
||||||
|
|
||||||
def exconly(self, tryshort=False):
|
def exconly(self, tryshort=False):
|
||||||
|
""" return the exception as a string
|
||||||
|
|
||||||
|
when 'tryshort' resolves to True, and the exception is a
|
||||||
|
py.magic.AssertionError, only the actual exception part of
|
||||||
|
the exception representation is returned (so 'AssertionError: ' is
|
||||||
|
removed from the beginning)
|
||||||
|
"""
|
||||||
lines = py.std.traceback.format_exception_only(self.type, self.value)
|
lines = py.std.traceback.format_exception_only(self.type, self.value)
|
||||||
text = ''.join(lines)
|
text = ''.join(lines)
|
||||||
if text.endswith('\n'):
|
if text.endswith('\n'):
|
||||||
|
@ -32,6 +39,7 @@ class ExceptionInfo(object):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def errisinstance(self, exc):
|
def errisinstance(self, exc):
|
||||||
|
""" return True if the exception is an instance of exc """
|
||||||
return isinstance(self.value, exc)
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -18,23 +18,38 @@ class Frame(object):
|
||||||
"statement this frame is at")
|
"statement this frame is at")
|
||||||
|
|
||||||
def eval(self, code, **vars):
|
def eval(self, code, **vars):
|
||||||
|
""" evaluate 'code' in the frame
|
||||||
|
|
||||||
|
'vars' are optional additional local variables
|
||||||
|
|
||||||
|
returns the result of the evaluation
|
||||||
|
"""
|
||||||
f_locals = self.f_locals.copy()
|
f_locals = self.f_locals.copy()
|
||||||
f_locals.update(vars)
|
f_locals.update(vars)
|
||||||
return eval(code, self.f_globals, f_locals)
|
return eval(code, self.f_globals, f_locals)
|
||||||
|
|
||||||
def exec_(self, code, **vars):
|
def exec_(self, code, **vars):
|
||||||
|
""" exec 'code' in the frame
|
||||||
|
|
||||||
|
'vars' are optiona; additional local variables
|
||||||
|
"""
|
||||||
f_locals = self.f_locals.copy()
|
f_locals = self.f_locals.copy()
|
||||||
f_locals.update(vars)
|
f_locals.update(vars)
|
||||||
exec code in self.f_globals, f_locals
|
exec code in self.f_globals, f_locals
|
||||||
|
|
||||||
def repr(self, object):
|
def repr(self, object):
|
||||||
|
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||||
|
"""
|
||||||
return py.__.code.safe_repr._repr(object)
|
return py.__.code.safe_repr._repr(object)
|
||||||
|
|
||||||
def is_true(self, object):
|
def is_true(self, object):
|
||||||
return object
|
return object
|
||||||
|
|
||||||
def getargs(self):
|
def getargs(self):
|
||||||
|
""" return a list of tuples (name, value) for all arguments
|
||||||
|
"""
|
||||||
retval = []
|
retval = []
|
||||||
for arg in self.code.getargs():
|
for arg in self.code.getargs():
|
||||||
retval.append((arg, self.f_locals[arg]))
|
retval.append((arg, self.f_locals[arg]))
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ from __future__ import generators
|
||||||
import py
|
import py
|
||||||
|
|
||||||
class TracebackEntry(object):
|
class TracebackEntry(object):
|
||||||
|
""" a single entry in a traceback """
|
||||||
|
|
||||||
exprinfo = None
|
exprinfo = None
|
||||||
|
|
||||||
def __init__(self, rawentry):
|
def __init__(self, rawentry):
|
||||||
|
@ -14,6 +16,7 @@ class TracebackEntry(object):
|
||||||
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
||||||
|
|
||||||
def statement(self):
|
def statement(self):
|
||||||
|
""" return a py.code.Source object for the current statement """
|
||||||
source = self.frame.code.fullsource
|
source = self.frame.code.fullsource
|
||||||
return source.getstatement(self.lineno)
|
return source.getstatement(self.lineno)
|
||||||
statement = property(statement, None, None,
|
statement = property(statement, None, None,
|
||||||
|
@ -63,6 +66,11 @@ class TracebackEntry(object):
|
||||||
source = property(getsource)
|
source = property(getsource)
|
||||||
|
|
||||||
def ishidden(self):
|
def ishidden(self):
|
||||||
|
""" return True if the current frame has a var __tracebackhide__
|
||||||
|
resolving to True
|
||||||
|
|
||||||
|
mostly for internal use
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self.frame.eval("__tracebackhide__")
|
return self.frame.eval("__tracebackhide__")
|
||||||
except (SystemExit, KeyboardInterrupt):
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
@ -103,6 +111,15 @@ class Traceback(list):
|
||||||
list.__init__(self, tb)
|
list.__init__(self, tb)
|
||||||
|
|
||||||
def cut(self, path=None, lineno=None, firstlineno=None):
|
def cut(self, path=None, lineno=None, firstlineno=None):
|
||||||
|
""" return a Traceback instance wrapping part of this Traceback
|
||||||
|
|
||||||
|
by provding any combination of path, lineno and firstlineno, the
|
||||||
|
first frame to start the to-be-returned traceback is determined
|
||||||
|
|
||||||
|
this allows cutting the first part of a Traceback instance e.g.
|
||||||
|
for formatting reasons (removing some uninteresting bits that deal
|
||||||
|
with handling of the exception/traceback)
|
||||||
|
"""
|
||||||
for x in self:
|
for x in self:
|
||||||
if ((path is None or x.frame.code.path == path) and
|
if ((path is None or x.frame.code.path == path) and
|
||||||
(lineno is None or x.lineno == lineno) and
|
(lineno is None or x.lineno == lineno) and
|
||||||
|
@ -114,9 +131,18 @@ class Traceback(list):
|
||||||
val = super(Traceback, self).__getitem__(key)
|
val = super(Traceback, self).__getitem__(key)
|
||||||
if isinstance(key, type(slice(0))):
|
if isinstance(key, type(slice(0))):
|
||||||
val = self.__class__(val)
|
val = self.__class__(val)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def filter(self, fn=lambda x: not x.ishidden()):
|
def filter(self, fn=lambda x: not x.ishidden()):
|
||||||
|
""" return a Traceback instance with certain items removed
|
||||||
|
|
||||||
|
fn is a function that gets a single argument, a TracebackItem
|
||||||
|
instance, and should return True when the item should be added
|
||||||
|
to the Traceback, False when not
|
||||||
|
|
||||||
|
by default this removes all the TracebackItems which are hidden
|
||||||
|
(see ishidden() above)
|
||||||
|
"""
|
||||||
return Traceback(filter(fn, self))
|
return Traceback(filter(fn, self))
|
||||||
|
|
||||||
def getcrashentry(self):
|
def getcrashentry(self):
|
||||||
|
@ -129,6 +155,9 @@ class Traceback(list):
|
||||||
return tb[-1]
|
return tb[-1]
|
||||||
|
|
||||||
def recursionindex(self):
|
def recursionindex(self):
|
||||||
|
""" return the index of the frame/TracebackItem where recursion
|
||||||
|
originates if appropriate, None if no recursion occurred
|
||||||
|
"""
|
||||||
cache = {}
|
cache = {}
|
||||||
for i, entry in py.builtin.enumerate(self):
|
for i, entry in py.builtin.enumerate(self):
|
||||||
key = entry.frame.code.path, entry.lineno
|
key = entry.frame.code.path, entry.lineno
|
||||||
|
@ -155,3 +184,4 @@ class Traceback(list):
|
||||||
|
|
||||||
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
||||||
'?', 'eval')
|
'?', 'eval')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
==============
|
||||||
|
:api:`py.code`
|
||||||
|
==============
|
||||||
|
|
||||||
|
The :api:`py.code` part of the 'py lib' contains some functionality to help
|
||||||
|
dealing with Python code objects. Even though working with Python's internal
|
||||||
|
code objects (as found on frames and callables) can be very powerful, it's
|
||||||
|
usually also quite cumbersome, because the API provided by core Python is
|
||||||
|
relatively low level and not very accessible.
|
||||||
|
|
||||||
|
The :api:`py.code` library tries to simplify accessing the code objects as well
|
||||||
|
as creating them. There is a small set of interfaces a user needs to deal with,
|
||||||
|
all nicely bundled together, and with a rich set of 'Pythonic' functionality.
|
||||||
|
|
||||||
|
source: :source:`py/code/`
|
||||||
|
|
||||||
|
Contents of the library
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Every object in the :api:`py.code` library wraps a code Python object related
|
||||||
|
to code objects, source code, frames and tracebacks: the :api:`py.code.Code`
|
||||||
|
class wraps code objects, :api:`py.code.Source` source snippets,
|
||||||
|
:api:`py.code.Traceback` exception tracebacks, :api:`py.code.Frame` frame
|
||||||
|
objects (as found in e.g. tracebacks) and :api:`py.code.ExceptionInfo` the
|
||||||
|
tuple provided by sys.exc_info() (containing exception and traceback
|
||||||
|
information when an exception occurs). Also in the library is a helper function
|
||||||
|
:api:`py.code.compile()` that provides the same functionality as Python's
|
||||||
|
built-in 'compile()' function, but returns a wrapped code object.
|
||||||
|
|
||||||
|
The wrappers
|
||||||
|
============
|
||||||
|
|
||||||
|
:api:`py.code.Code`
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Code objects are instantiated with a code object or a callable as argument,
|
||||||
|
and provide functionality to compare themselves with other Code objects, get to
|
||||||
|
the source file or its contents, create new Code objects from scratch, etc.
|
||||||
|
|
||||||
|
A quick example::
|
||||||
|
|
||||||
|
>>> import py
|
||||||
|
>>> c = py.code.Code(py.path.local.read)
|
||||||
|
>>> c.path.basename
|
||||||
|
'common.py'
|
||||||
|
>>> isinstance(c.source(), py.code.Source)
|
||||||
|
True
|
||||||
|
>>> str(c.source()).split('\n')[0]
|
||||||
|
"def read(self, mode='rb'):"
|
||||||
|
|
||||||
|
source: :source:`py/code/code.py`
|
||||||
|
|
||||||
|
:api:`py.code.Source`
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Source objects wrap snippets of Python source code, providing a simple yet
|
||||||
|
powerful interface to read, deindent, slice, compare, compile and manipulate
|
||||||
|
them, things that are not so easy in core Python.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> s = py.code.Source("""\
|
||||||
|
... def foo():
|
||||||
|
... print "foo"
|
||||||
|
... """)
|
||||||
|
>>> str(s).startswith('def') # automatic de-indentation!
|
||||||
|
True
|
||||||
|
>>> s.isparseable()
|
||||||
|
True
|
||||||
|
>>> sub = s.getstatement(1) # get the statement starting at line 1
|
||||||
|
>>> str(sub).strip() # XXX why is the strip() required?!?
|
||||||
|
'print "foo"'
|
||||||
|
|
||||||
|
source: :source:`py/code/source.py`
|
||||||
|
|
||||||
|
:api:`py.code.Traceback`
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Tracebacks are usually not very easy to examine, you need to access certain
|
||||||
|
somewhat hidden attributes of the traceback's items (resulting in expressions
|
||||||
|
such as 'fname = tb.tb_next.tb_frame.f_code.co_filename'). The Traceback
|
||||||
|
interface (and its TracebackItem children) tries to improve this.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> import sys
|
||||||
|
>>> try:
|
||||||
|
... py.path.local(100) # illegal argument
|
||||||
|
... except:
|
||||||
|
... exc, e, tb = sys.exc_info()
|
||||||
|
>>> t = py.code.Traceback(tb)
|
||||||
|
>>> first = t[1] # get the second entry (first is in this doc)
|
||||||
|
>>> first.path.basename # second is in py/path/local.py
|
||||||
|
'local.py'
|
||||||
|
>>> isinstance(first.statement, py.code.Source)
|
||||||
|
True
|
||||||
|
>>> str(first.statement).strip().startswith('raise ValueError')
|
||||||
|
True
|
||||||
|
|
||||||
|
source: :source:`py/code/traceback2.py`
|
||||||
|
|
||||||
|
:api:`py.code.Frame`
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Frame wrappers are used in :api:`py.code.Traceback` items, and will usually not
|
||||||
|
directly be instantiated. They provide some nice methods to evaluate code
|
||||||
|
'inside' the frame (using the frame's local variables), get to the underlying
|
||||||
|
code (frames have a code attribute that points to a :api:`py.code.Code` object)
|
||||||
|
and examine the arguments.
|
||||||
|
|
||||||
|
Example (using the 'first' TracebackItem instance created above)::
|
||||||
|
|
||||||
|
>>> frame = first.frame
|
||||||
|
>>> isinstance(frame.code, py.code.Code)
|
||||||
|
True
|
||||||
|
>>> isinstance(frame.eval('self'), py.__.path.local.local.LocalPath)
|
||||||
|
True
|
||||||
|
>>> [namevalue[0] for namevalue in frame.getargs()]
|
||||||
|
['cls', 'path']
|
||||||
|
|
||||||
|
:api:`py.code.ExceptionInfo`
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
A wrapper around the tuple returned by sys.exc_info() (will call sys.exc_info()
|
||||||
|
itself if the tuple is not provided as an argument), provides some handy
|
||||||
|
attributes to easily access the traceback and exception string.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> import sys
|
||||||
|
>>> try:
|
||||||
|
... foobar()
|
||||||
|
... except:
|
||||||
|
... excinfo = py.code.ExceptionInfo()
|
||||||
|
>>> excinfo.typename
|
||||||
|
'exceptions.NameError'
|
||||||
|
>>> isinstance(excinfo.traceback, py.code.Traceback)
|
||||||
|
True
|
||||||
|
>>> excinfo.exconly()
|
||||||
|
"NameError: name 'foobar' is not defined"
|
||||||
|
|
Loading…
Reference in New Issue