================================================================================
py.code: higher level python code and introspection objects
================================================================================

``py.code`` provides higher level APIs and objects for Code, Frame, Traceback,
ExceptionInfo and source code construction.  The ``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.

Contents of the library
=======================

Every object in the ``py.code`` library wraps a code Python object related
to code objects, source code, frames and tracebacks: the ``py.code.Code``
class wraps code objects, ``py.code.Source`` source snippets,
``py.code.Traceback` exception tracebacks, ``py.code.Frame`` frame
objects (as found in e.g. tracebacks) and ``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
``py.code.compile()`` that provides the same functionality as Python's
built-in 'compile()' function, but returns a wrapped code object.

The wrappers
============

``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='r'):"

.. autoclass:: py.code.Code
    :members:
    :inherited-members:


``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"'

.. autoclass:: py.code.Source
    :members:


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

.. autoclass:: py.code.Traceback
    :members:

``py.code.Frame``
--------------------

Frame wrappers are used in ``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 ``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)
  True
  >>> [namevalue[0] for namevalue in frame.getargs()]
  ['cls', 'path']

.. autoclass:: py.code.Frame
    :members:

``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
  'NameError'
  >>> isinstance(excinfo.traceback, py.code.Traceback)
  True
  >>> excinfo.exconly()
  "NameError: name 'foobar' is not defined"

.. autoclass:: py.code.ExceptionInfo
    :members:

.. autoclass:: py.code.Traceback
    :members:

