Permalink
Browse files

Implement object info protocol.

Not fully finished yet, but most things work.
  • Loading branch information...
1 parent dd0a1b4 commit e7e389c1052ceda15656f84edf8cff41f3640992 @fperez fperez committed Sep 3, 2010
Showing with 250 additions and 45 deletions.
  1. +4 −0 IPython/core/interactiveshell.py
  2. +235 −40 IPython/core/oinspect.py
  3. +4 −3 IPython/zmq/ipkernel.py
  4. +7 −2 docs/source/development/messaging.txt
@@ -1165,6 +1165,10 @@ def _inspect(self, meth, oname, namespaces=None, **kw):
def object_inspect(self, oname):
info = self._object_find(oname)
+ if info.found:
+ return self.inspector.info(info.obj, info=info)
+ else:
+ return {}
#-------------------------------------------------------------------------
# Things related to history management
@@ -25,6 +25,8 @@
import string
import sys
import types
+from collections import namedtuple
+from itertools import izip_longest
# IPython's own
from IPython.core import page
@@ -36,45 +38,6 @@
from IPython.utils.coloransi import *
#****************************************************************************
-# HACK!!! This is a crude fix for bugs in python 2.3's inspect module. We
-# simply monkeypatch inspect with code copied from python 2.4.
-if sys.version_info[:2] == (2,3):
- from inspect import ismodule, getabsfile, modulesbyfile
- def getmodule(object):
- """Return the module an object was defined in, or None if not found."""
- if ismodule(object):
- return object
- if hasattr(object, '__module__'):
- return sys.modules.get(object.__module__)
- try:
- file = getabsfile(object)
- except TypeError:
- return None
- if file in modulesbyfile:
- return sys.modules.get(modulesbyfile[file])
- for module in sys.modules.values():
- if hasattr(module, '__file__'):
- modulesbyfile[
- os.path.realpath(
- getabsfile(module))] = module.__name__
- if file in modulesbyfile:
- return sys.modules.get(modulesbyfile[file])
- main = sys.modules['__main__']
- if not hasattr(object, '__name__'):
- return None
- if hasattr(main, object.__name__):
- mainobject = getattr(main, object.__name__)
- if mainobject is object:
- return main
- builtin = sys.modules['__builtin__']
- if hasattr(builtin, object.__name__):
- builtinobject = getattr(builtin, object.__name__)
- if builtinobject is object:
- return builtin
-
- inspect.getmodule = getmodule
-
-#****************************************************************************
# Builtin color schemes
Colors = TermColors # just a shorthand
@@ -103,7 +66,30 @@ def getmodule(object):
'Linux')
#****************************************************************************
-# Auxiliary functions
+# Auxiliary functions and objects
+
+# See the messaging spec for the definition of all these fields. This list
+# effectively defines the order of display
+info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
+ 'length', 'file', 'definition', 'docstring', 'source',
+ 'init_definition', 'class_docstring', 'init_docstring',
+ 'call_def', 'call_docstring',
+ # These won't be printed but will be used to determine how to
+ # format the object
+ 'ismagic', 'isalias',
+ ]
+
+
+ObjectInfo = namedtuple('ObjectInfo', info_fields)
+
+
+def mk_object_info(kw):
+ """Make a f"""
+ infodict = dict(izip_longest(info_fields, [None]))
+ infodict.update(kw)
+ return ObjectInfo(**infodict)
+
+
def getdoc(obj):
"""Stable wrapper around inspect.getdoc.
@@ -553,6 +539,215 @@ def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0):
page.page(output)
# end pinfo
+ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
+ """Compute a dict with detailed information about an object.
+
+ Optional arguments:
+
+ - oname: name of the variable pointing to the object.
+
+ - formatter: special formatter for docstrings (see pdoc)
+
+ - info: a structure with some information fields which may have been
+ precomputed already.
+
+ - detail_level: if set to 1, more information is given.
+ """
+
+ obj_type = type(obj)
+
+ header = self.__head
+ if info is None:
+ ismagic = 0
+ isalias = 0
+ ospace = ''
+ else:
+ ismagic = info.ismagic
+ isalias = info.isalias
+ ospace = info.namespace
+ # Get docstring, special-casing aliases:
+ if isalias:
+ if not callable(obj):
+ try:
+ ds = "Alias to the system command:\n %s" % obj[1]
+ except:
+ ds = "Alias: " + str(obj)
+ else:
+ ds = "Alias to " + str(obj)
+ if obj.__doc__:
+ ds += "\nDocstring:\n" + obj.__doc__
+ else:
+ ds = getdoc(obj)
+ if ds is None:
+ ds = '<no docstring>'
+ if formatter is not None:
+ ds = formatter(ds)
+
+ # store output in a dict, we'll later convert it to an ObjectInfo
+ out = {}
+
+ string_max = 200 # max size of strings to show (snipped if longer)
+ shalf = int((string_max -5)/2)
+
+ if ismagic:
+ obj_type_name = 'Magic function'
+ elif isalias:
+ obj_type_name = 'System alias'
+ else:
+ obj_type_name = obj_type.__name__
+ out['type_name'] = obj_type_name
+
+ try:
+ bclass = obj.__class__
+ out['base_class'] = str(bclass)
+ except: pass
+
+ # String form, but snip if too long in ? form (full in ??)
+ if detail_level >= self.str_detail_level:
+ try:
+ ostr = str(obj)
+ str_head = 'string_form'
+ if not detail_level and len(ostr)>string_max:
+ ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
+ ostr = ("\n" + " " * len(str_head.expandtabs())).\
+ join(map(string.strip,ostr.split("\n")))
+ if ostr.find('\n') > -1:
+ # Print multi-line strings starting at the next line.
+ str_sep = '\n'
+ else:
+ str_sep = '\t'
+ out[str_head] = ostr
+ except:
+ pass
+
+ if ospace:
+ out['namespace'] = ospace
+
+ # Length (for strings and lists)
+ try:
+ out['length'] = str(len(obj))
+ except: pass
+
+ # Filename where object was defined
+ binary_file = False
+ try:
+ try:
+ fname = inspect.getabsfile(obj)
+ except TypeError:
+ # For an instance, the file that matters is where its class was
+ # declared.
+ if hasattr(obj,'__class__'):
+ fname = inspect.getabsfile(obj.__class__)
+ if fname.endswith('<string>'):
+ fname = 'Dynamically generated function. No source code available.'
+ if (fname.endswith('.so') or fname.endswith('.dll')):
+ binary_file = True
+ out['file'] = fname
+ except:
+ # if anything goes wrong, we don't want to show source, so it's as
+ # if the file was binary
+ binary_file = True
+
+ # reconstruct the function definition and print it:
+ defln = self._getdef(obj,oname)
+ if defln:
+ out['definition'] = self.format(defln)
+
+ # Docstrings only in detail 0 mode, since source contains them (we
+ # avoid repetitions). If source fails, we add them back, see below.
+ if ds and detail_level == 0:
+ out['docstring'] = indent(ds)
+
+ # Original source code for any callable
+ if detail_level:
+ # Flush the source cache because inspect can return out-of-date
+ # source
+ linecache.checkcache()
+ source_success = False
+ try:
+ try:
+ src = getsource(obj,binary_file)
+ except TypeError:
+ if hasattr(obj,'__class__'):
+ src = getsource(obj.__class__,binary_file)
+ if src is not None:
+ source = self.format(src)
+ out['source'] = source.rstrip()
+ source_success = True
+ except Exception, msg:
+ pass
+
+ # Constructor docstring for classes
+ if inspect.isclass(obj):
+ # reconstruct the function definition and print it:
+ try:
+ obj_init = obj.__init__
+ except AttributeError:
+ init_def = init_ds = None
+ else:
+ init_def = self._getdef(obj_init,oname)
+ init_ds = getdoc(obj_init)
+ # Skip Python's auto-generated docstrings
+ if init_ds and \
+ init_ds.startswith('x.__init__(...) initializes'):
+ init_ds = None
+
+ if init_def or init_ds:
+ if init_def:
+ out['init_definition'] = self.format(init_def)
+ if init_ds:
+ out['init_docstring'] = indent(init_ds)
+ # and class docstring for instances:
+ elif obj_type is types.InstanceType or \
+ isinstance(obj,object):
+
+ # First, check whether the instance docstring is identical to the
+ # class one, and print it separately if they don't coincide. In
+ # most cases they will, but it's nice to print all the info for
+ # objects which use instance-customized docstrings.
+ if ds:
+ try:
+ cls = getattr(obj,'__class__')
+ except:
+ class_ds = None
+ else:
+ class_ds = getdoc(cls)
+ # Skip Python's auto-generated docstrings
+ if class_ds and \
+ (class_ds.startswith('function(code, globals[,') or \
+ class_ds.startswith('instancemethod(function, instance,') or \
+ class_ds.startswith('module(name[,') ):
+ class_ds = None
+ if class_ds and ds != class_ds:
+ out['class_docstring'] = indent(class_ds)
+
+ # Next, try to show constructor docstrings
+ try:
+ init_ds = getdoc(obj.__init__)
+ # Skip Python's auto-generated docstrings
+ if init_ds and \
+ init_ds.startswith('x.__init__(...) initializes'):
+ init_ds = None
+ except AttributeError:
+ init_ds = None
+ if init_ds:
+ out['init_docstring'] = indent(init_ds)
+
+ # Call form docstring for callable instances
+ if hasattr(obj,'__call__'):
+ call_def = self._getdef(obj.__call__,oname)
+ if call_def is not None:
+ out['call_def'] = self.format(call_def)
+ call_ds = getdoc(obj.__call__)
+ # Skip Python's auto-generated docstrings
+ if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'):
+ call_ds = None
+ if call_ds:
+ out['call_docstring'] = indent(call_ds)
+
+ return mk_object_info(out)
+
+
def psearch(self,pattern,ns_table,ns_search=[],
ignore_case=False,show_all=False):
"""Search namespaces with wildcards for objects.
@@ -210,10 +210,11 @@ def complete_request(self, ident, parent):
io.raw_print(completion_msg)
def object_info_request(self, ident, parent):
- context = parent['content']['oname'].split('.')
- object_info = self._object_info(context)
+ ##context = parent['content']['oname'].split('.')
+ ##object_info = self._object_info(context)
+ object_info = self.shell.object_inspect(parent['content']['oname'])
msg = self.session.send(self.reply_socket, 'object_info_reply',
- object_info, parent, ident)
+ object_info._asdict(), parent, ident)
io.raw_print(msg)
def history_request(self, ident, parent):
@@ -450,13 +450,18 @@ Message type: ``object_info_reply``::
# For instances, provide the constructor and class docstrings
'init_docstring' : str,
'class_docstring' : str,
-
+
+ # If it's a callable object whose call method has a separate docstring and
+ # definition line:
+ 'call_def' : str,
+ 'call_docstring' : str,
+
# If detail_level was 1, we also try to find the source code that
# defines the object, if possible. The string 'None' will indicate
# that no source was found.
'source' : str,
}
-
+'
Complete
--------

0 comments on commit e7e389c

Please sign in to comment.