From 58eace43f91f33945f59fb6dd362c3d5a6e05240 Mon Sep 17 00:00:00 2001 From: guido Date: Wed, 31 Jan 2007 16:29:18 +0100 Subject: [PATCH] [svn r37674] Added document 'code.txt' that describes py.code, added docstrings to py.code public items. --HG-- branch : trunk --- py/code/code.py | 9 +++ py/code/excinfo.py | 8 +++ py/code/frame.py | 15 +++++ py/code/traceback2.py | 32 +++++++++- py/doc/code.txt | 141 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 py/doc/code.txt diff --git a/py/code/code.py b/py/code/code.py index 6e1adc403..a8760ec4d 100644 --- a/py/code/code.py +++ b/py/code/code.py @@ -1,6 +1,7 @@ import py class Code(object): + """ wrapper around Python code objects """ def __init__(self, rawcode): rawcode = getattr(rawcode, 'im_func', rawcode) rawcode = getattr(rawcode, 'func_code', rawcode) @@ -57,6 +58,7 @@ class Code(object): ) def path(self): + """ return a py.path.local object wrapping the source of the code """ try: return self.raw.co_filename.__path__ except AttributeError: @@ -64,6 +66,8 @@ class Code(object): path = property(path, None, None, "path of this code object") def fullsource(self): + """ return a py.code.Source object for the full source file of the code + """ fn = self.raw.co_filename try: return fn.__source__ @@ -73,11 +77,16 @@ class Code(object): "full source containing this code object") def source(self): + """ return a py.code.Source object for the code object's source only + """ # return source only for that part of code import inspect return py.code.Source(inspect.getsource(self.raw)) def getargs(self): + """ return a tuple with the argument names for the code object + """ # handfull shortcut for getting args raw = self.raw return raw.co_varnames[:raw.co_argcount] + diff --git a/py/code/excinfo.py b/py/code/excinfo.py index cde86cd55..eddd8e69a 100644 --- a/py/code/excinfo.py +++ b/py/code/excinfo.py @@ -22,6 +22,13 @@ class ExceptionInfo(object): self.traceback = py.code.Traceback(tb) 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) text = ''.join(lines) if text.endswith('\n'): @@ -32,6 +39,7 @@ class ExceptionInfo(object): return text def errisinstance(self, exc): + """ return True if the exception is an instance of exc """ return isinstance(self.value, exc) def __str__(self): diff --git a/py/code/frame.py b/py/code/frame.py index 0411d9116..8b67ba27d 100644 --- a/py/code/frame.py +++ b/py/code/frame.py @@ -18,23 +18,38 @@ class Frame(object): "statement this frame is at") 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.update(vars) return eval(code, self.f_globals, f_locals) def exec_(self, code, **vars): + """ exec 'code' in the frame + + 'vars' are optiona; additional local variables + """ f_locals = self.f_locals.copy() f_locals.update(vars) exec code in self.f_globals, f_locals def repr(self, object): + """ return a 'safe' (non-recursive, one-line) string repr for 'object' + """ return py.__.code.safe_repr._repr(object) def is_true(self, object): return object def getargs(self): + """ return a list of tuples (name, value) for all arguments + """ retval = [] for arg in self.code.getargs(): retval.append((arg, self.f_locals[arg])) return retval + diff --git a/py/code/traceback2.py b/py/code/traceback2.py index 5acdf3e6c..e07fdeff2 100644 --- a/py/code/traceback2.py +++ b/py/code/traceback2.py @@ -2,6 +2,8 @@ from __future__ import generators import py class TracebackEntry(object): + """ a single entry in a traceback """ + exprinfo = None def __init__(self, rawentry): @@ -14,6 +16,7 @@ class TracebackEntry(object): return "" %(self.frame.code.path, self.lineno+1) def statement(self): + """ return a py.code.Source object for the current statement """ source = self.frame.code.fullsource return source.getstatement(self.lineno) statement = property(statement, None, None, @@ -63,6 +66,11 @@ class TracebackEntry(object): source = property(getsource) def ishidden(self): + """ return True if the current frame has a var __tracebackhide__ + resolving to True + + mostly for internal use + """ try: return self.frame.eval("__tracebackhide__") except (SystemExit, KeyboardInterrupt): @@ -103,6 +111,15 @@ class Traceback(list): list.__init__(self, tb) 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: if ((path is None or x.frame.code.path == path) and (lineno is None or x.lineno == lineno) and @@ -114,9 +131,18 @@ class Traceback(list): val = super(Traceback, self).__getitem__(key) if isinstance(key, type(slice(0))): val = self.__class__(val) - return val + return val 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)) def getcrashentry(self): @@ -129,6 +155,9 @@ class Traceback(list): return tb[-1] def recursionindex(self): + """ return the index of the frame/TracebackItem where recursion + originates if appropriate, None if no recursion occurred + """ cache = {} for i, entry in py.builtin.enumerate(self): key = entry.frame.code.path, entry.lineno @@ -155,3 +184,4 @@ class Traceback(list): co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', '?', 'eval') + diff --git a/py/doc/code.txt b/py/doc/code.txt new file mode 100644 index 000000000..d62ca04c9 --- /dev/null +++ b/py/doc/code.txt @@ -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" +