[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:
guido 2007-01-31 16:29:18 +01:00
parent 65f51efa55
commit 58eace43f9
5 changed files with 204 additions and 1 deletions

View File

@ -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]

View File

@ -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):

View File

@ -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

View File

@ -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')

141
py/doc/code.txt Normal file
View File

@ -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"