# -*- coding: iso-8859-1 -*- """ ``ipipe`` provides classes to be used in an interactive Python session. Doing a ``from ipipe import *`` is the preferred way to do this. The name of all objects imported this way starts with ``i`` to minimize collisions. ``ipipe`` supports "pipeline expressions", which is something resembling Unix pipes. An example is: >>> ienv | isort("key.lower()") This gives a listing of all environment variables sorted by name. There are three types of objects in a pipeline expression: * ``Table``s: These objects produce items. Examples are ``ils`` (listing the current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing user accounts) and ``igrp`` (listing user groups). A ``Table`` must be the first object in a pipe expression. * ``Pipe``s: These objects sit in the middle of a pipe expression. They transform the input in some way (e.g. filtering or sorting it). Examples are: ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input pipe) and ``ieval`` (which evaluates a function or expression for each object in the input pipe). * ``Display``s: These objects can be put as the last object in a pipeline expression. There are responsible for displaying the result of the pipeline expression. If a pipeline expression doesn't end in a display object a default display objects will be used. One example is ``ibrowse`` which is a ``curses`` based browser. Adding support for pipeline expressions to your own objects can be done through three extensions points (all of them optional): * An object that will be displayed as a row by a ``Display`` object should implement the method ``__xattrs__(self, mode)`` method or register an implementation of the generic function ``xattrs``. For more info see ``xattrs``. * When an object ``foo`` is displayed by a ``Display`` object, the generic function ``xrepr`` is used. * Objects that can be iterated by ``Pipe``s must iterable. For special cases, where iteration for display is different than the normal iteration a special implementation can be registered with the generic function ``xiter``. This makes it possible to use dictionaries and modules in pipeline expressions, for example: >>> import sys >>> sys | ifilter("isinstance(value, int)") | idump key |value api_version| 1012 dllhandle | 503316480 hexversion | 33817328 maxint |2147483647 maxunicode | 65535 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()") ... Note: The expression strings passed to ``ifilter()`` and ``isort()`` can refer to the object to be filtered or sorted via the variable ``_`` and to any of the attributes of the object, i.e.: >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()") does the same as >>> sys.modules | ifilter("value is not None") | isort("key.lower()") In addition to expression strings, it's possible to pass callables (taking the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``: >>> sys | ifilter(lambda _:isinstance(_.value, int)) \ ... | ieval(lambda _: (_.key, hex(_.value))) | idump 0 |1 api_version|0x3f4 dllhandle |0x1e000000 hexversion |0x20402f0 maxint |0x7fffffff maxunicode |0xffff """ import sys, os, os.path, stat, glob, new, csv, datetime, types import itertools, mimetypes try: # Python 2.3 compatibility import collections except ImportError: deque = list else: deque = collections.deque try: # Python 2.3 compatibility set except NameError: import sets set = sets.Set try: # Python 2.3 compatibility sorted except NameError: def sorted(iterator, key=None, reverse=False): items = list(iterator) if key is not None: items.sort(lambda i1, i2: cmp(key(i1), key(i2))) else: items.sort() if reverse: items.reverse() return items try: import pwd except ImportError: pwd = None try: import grp except ImportError: grp = None from IPython.external import simplegeneric import path try: from IPython import genutils, ipapi except ImportError: genutils = None ipapi = None import astyle __all__ = [ "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp", "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv", "idump", "iless" ] os.stat_float_times(True) # enable microseconds class AttrNamespace(object): """ Helper class that is used for providing a namespace for evaluating expressions containing attribute names of an object. """ def __init__(self, wrapped): self.wrapped = wrapped def __getitem__(self, name): if name == "_": return self.wrapped try: return getattr(self.wrapped, name) except AttributeError: raise KeyError(name) # Python 2.3 compatibility # use eval workaround to find out which names are used in the # eval string and put them into the locals. This works for most # normal uses case, bizarre ones like accessing the locals() # will fail try: eval("_", None, AttrNamespace(None)) except TypeError: real_eval = eval def eval(codestring, _globals, _locals): """ eval(source[, globals[, locals]]) -> value Evaluate the source in the context of globals and locals. The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mappping. This function is a workaround for the shortcomings of Python 2.3's eval. """ if isinstance(codestring, basestring): code = compile(codestring, "_eval", "eval") else: code = codestring newlocals = {} for name in code.co_names: try: newlocals[name] = _locals[name] except KeyError: pass return real_eval(code, _globals, newlocals) noitem = object() def item(iterator, index, default=noitem): """ Return the ``index``th item from the iterator ``iterator``. ``index`` must be an integer (negative integers are relative to the end (i.e. the last items produced by the iterator)). If ``default`` is given, this will be the default value when the iterator doesn't contain an item at this position. Otherwise an ``IndexError`` will be raised. Note that using this function will partially or totally exhaust the iterator. """ i = index if i>=0: for item in iterator: if not i: return item i -= 1 else: i = -index cache = deque() for item in iterator: cache.append(item) if len(cache)>i: cache.popleft() if len(cache)==i: return cache.popleft() if default is noitem: raise IndexError(index) else: return default def getglobals(g): """ Return the global namespace that is used for expression strings in ``ifilter`` and others. This is ``g`` or (if ``g`` is ``None``) IPython's user namespace. """ if g is None: if ipapi is not None: api = ipapi.get() if api is not None: return api.user_ns return globals() return g class Descriptor(object): """ A ``Descriptor`` object is used for describing the attributes of objects. """ def __hash__(self): return hash(self.__class__) ^ hash(self.key()) def __eq__(self, other): return self.__class__ is other.__class__ and self.key() == other.key() def __ne__(self, other): return self.__class__ is not other.__class__ or self.key() != other.key() def key(self): pass def name(self): """ Return the name of this attribute for display by a ``Display`` object (e.g. as a column title). """ key = self.key() if key is None: return "_" return str(key) def attrtype(self, obj): """ Return the type of this attribute (i.e. something like "attribute" or "method"). """ def valuetype(self, obj): """ Return the type of this attribute value of the object ``obj``. """ def value(self, obj): """ Return the value of this attribute of the object ``obj``. """ def doc(self, obj): """ Return the documentation for this attribute. """ def shortdoc(self, obj): """ Return a short documentation for this attribute (defaulting to the first line). """ doc = self.doc(obj) if doc is not None: doc = doc.strip().splitlines()[0].strip() return doc def iter(self, obj): """ Return an iterator for this attribute of the object ``obj``. """ return xiter(self.value(obj)) class SelfDescriptor(Descriptor): """ A ``SelfDescriptor`` describes the object itself. """ def key(self): return None def attrtype(self, obj): return "self" def valuetype(self, obj): return type(obj) def value(self, obj): return obj def __repr__(self): return "Self" selfdescriptor = SelfDescriptor() # there's no need for more than one class AttributeDescriptor(Descriptor): """ An ``AttributeDescriptor`` describes a simple attribute of an object. """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): self._name = name self._doc = doc def key(self): return self._name def doc(self, obj): return self._doc def attrtype(self, obj): return "attr" def valuetype(self, obj): return type(getattr(obj, self._name)) def value(self, obj): return getattr(obj, self._name) def __repr__(self): if self._doc is None: return "Attribute(%r)" % self._name else: return "Attribute(%r, %r)" % (self._name, self._doc) class IndexDescriptor(Descriptor): """ An ``IndexDescriptor`` describes an "attribute" of an object that is fetched via ``__getitem__``. """ __slots__ = ("_index",) def __init__(self, index): self._index = index def key(self): return self._index def attrtype(self, obj): return "item" def valuetype(self, obj): return type(obj[self._index]) def value(self, obj): return obj[self._index] def __repr__(self): return "Index(%r)" % self._index class MethodDescriptor(Descriptor): """ A ``MethodDescriptor`` describes a method of an object that can be called without argument. Note that this method shouldn't change the object. """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): self._name = name self._doc = doc def key(self): return self._name def doc(self, obj): if self._doc is None: return getattr(obj, self._name).__doc__ return self._doc def attrtype(self, obj): return "method" def valuetype(self, obj): return type(self.value(obj)) def value(self, obj): return getattr(obj, self._name)() def __repr__(self): if self._doc is None: return "Method(%r)" % self._name else: return "Method(%r, %r)" % (self._name, self._doc) class IterAttributeDescriptor(Descriptor): """ An ``IterAttributeDescriptor`` works like an ``AttributeDescriptor`` but doesn't return an attribute values (because this value might be e.g. a large list). """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): self._name = name self._doc = doc def key(self): return self._name def doc(self, obj): return self._doc def attrtype(self, obj): return "iter" def valuetype(self, obj): return noitem def value(self, obj): return noitem def iter(self, obj): return xiter(getattr(obj, self._name)) def __repr__(self): if self._doc is None: return "IterAttribute(%r)" % self._name else: return "IterAttribute(%r, %r)" % (self._name, self._doc) class IterMethodDescriptor(Descriptor): """ An ``IterMethodDescriptor`` works like an ``MethodDescriptor`` but doesn't return an attribute values (because this value might be e.g. a large list). """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): self._name = name self._doc = doc def key(self): return self._name def doc(self, obj): if self._doc is None: return getattr(obj, self._name).__doc__ return self._doc def attrtype(self, obj): return "itermethod" def valuetype(self, obj): return noitem def value(self, obj): return noitem def iter(self, obj): return xiter(getattr(obj, self._name)()) def __repr__(self): if self._doc is None: return "IterMethod(%r)" % self._name else: return "IterMethod(%r, %r)" % (self._name, self._doc) class FunctionDescriptor(Descriptor): """ A ``FunctionDescriptor`` turns a function into a descriptor. The function will be called with the object to get the type and value of the attribute. """ __slots__ = ("_function", "_name", "_doc") def __init__(self, function, name=None, doc=None): self._function = function self._name = name self._doc = doc def key(self): return self._function def name(self): if self._name is not None: return self._name return getattr(self._function, "__xname__", self._function.__name__) def doc(self, obj): if self._doc is None: return self._function.__doc__ return self._doc def attrtype(self, obj): return "function" def valuetype(self, obj): return type(self._function(obj)) def value(self, obj): return self._function(obj) def __repr__(self): if self._doc is None: return "Function(%r)" % self._name else: return "Function(%r, %r)" % (self._name, self._doc) class Table(object): """ A ``Table`` is an object that produces items (just like a normal Python iterator/generator does) and can be used as the first object in a pipeline expression. The displayhook will open the default browser for such an object (instead of simply printing the ``repr()`` result). """ # We want to support ``foo`` and ``foo()`` in pipeline expression: # So we implement the required operators (``|`` and ``+``) in the metaclass, # instantiate the class and forward the operator to the instance class __metaclass__(type): def __iter__(self): return iter(self()) def __or__(self, other): return self() | other def __add__(self, other): return self() + other def __radd__(self, other): return other + self() def __getitem__(self, index): return self()[index] def __getitem__(self, index): return item(self, index) def __contains__(self, item): for haveitem in self: if item == haveitem: return True return False def __or__(self, other): # autoinstantiate right hand side if isinstance(other, type) and issubclass(other, (Table, Display)): other = other() # treat simple strings and functions as ``ieval`` instances elif not isinstance(other, Display) and not isinstance(other, Table): other = ieval(other) # forward operations to the right hand side return other.__ror__(self) def __add__(self, other): # autoinstantiate right hand side if isinstance(other, type) and issubclass(other, Table): other = other() return ichain(self, other) def __radd__(self, other): # autoinstantiate left hand side if isinstance(other, type) and issubclass(other, Table): other = other() return ichain(other, self) class Pipe(Table): """ A ``Pipe`` is an object that can be used in a pipeline expression. It processes the objects it gets from its input ``Table``/``Pipe``. Note that a ``Pipe`` object can't be used as the first object in a pipeline expression, as it doesn't produces items itself. """ class __metaclass__(Table.__metaclass__): def __ror__(self, input): return input | self() def __ror__(self, input): # autoinstantiate left hand side if isinstance(input, type) and issubclass(input, Table): input = input() self.input = input return self def xrepr(item, mode="default"): """ Generic function that adds color output and different display modes to ``repr``. The result of an ``xrepr`` call is iterable and consists of ``(style, string)`` tuples. The ``style`` in this tuple must be a ``Style`` object from the ``astring`` module. To reconfigure the output the first yielded tuple can be a ``(aligment, full)`` tuple instead of a ``(style, string)`` tuple. ``alignment`` can be -1 for left aligned, 0 for centered and 1 for right aligned (the default is left alignment). ``full`` is a boolean that specifies whether the complete output must be displayed or the ``Display`` object is allowed to stop output after enough text has been produced (e.g. a syntax highlighted text line would use ``True``, but for a large data structure (i.e. a nested list, tuple or dictionary) ``False`` would be used). The default is full output. There are four different possible values for ``mode`` depending on where the ``Display`` object will display ``item``: * ``"header"``: ``item`` will be displayed in a header line (this is used by ``ibrowse``). * ``"footer"``: ``item`` will be displayed in a footer line (this is used by ``ibrowse``). * ``"cell"``: ``item`` will be displayed in a table cell/list. * ``"default"``: default mode. If an ``xrepr`` implementation recursively outputs objects, ``"default"`` must be passed in the recursive calls to ``xrepr``. If no implementation is registered for ``item``, ``xrepr`` will try the ``__xrepr__`` method on ``item``. If ``item`` doesn't have an ``__xrepr__`` method it falls back to ``repr``/``__repr__`` for all modes. """ try: func = item.__xrepr__ except AttributeError: yield (astyle.style_default, repr(item)) else: try: for x in func(mode): yield x except (KeyboardInterrupt, SystemExit): raise except Exception: yield (astyle.style_default, repr(item)) xrepr = simplegeneric.generic(xrepr) def xrepr_none(self, mode="default"): yield (astyle.style_type_none, repr(self)) xrepr.when_object(None)(xrepr_none) def xrepr_bool(self, mode="default"): yield (astyle.style_type_bool, repr(self)) xrepr.when_type(bool)(xrepr_bool) def xrepr_str(self, mode="default"): if mode == "cell": yield (astyle.style_default, repr(self.expandtabs(tab))[1:-1]) else: yield (astyle.style_default, repr(self)) xrepr.when_type(str)(xrepr_str) def xrepr_unicode(self, mode="default"): if mode == "cell": yield (astyle.style_default, repr(self.expandtabs(tab))[2:-1]) else: yield (astyle.style_default, repr(self)) xrepr.when_type(unicode)(xrepr_unicode) def xrepr_number(self, mode="default"): yield (1, True) yield (astyle.style_type_number, repr(self)) xrepr.when_type(int)(xrepr_number) xrepr.when_type(long)(xrepr_number) xrepr.when_type(float)(xrepr_number) def xrepr_complex(self, mode="default"): yield (astyle.style_type_number, repr(self)) xrepr.when_type(complex)(xrepr_number) def xrepr_datetime(self, mode="default"): if mode == "cell": # Don't use strftime() here, as this requires year >= 1900 yield (astyle.style_type_datetime, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \ (self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond), ) else: yield (astyle.style_type_datetime, repr(self)) xrepr.when_type(datetime.datetime)(xrepr_datetime) def xrepr_date(self, mode="default"): if mode == "cell": yield (astyle.style_type_datetime, "%04d-%02d-%02d" % (self.year, self.month, self.day)) else: yield (astyle.style_type_datetime, repr(self)) xrepr.when_type(datetime.date)(xrepr_date) def xrepr_time(self, mode="default"): if mode == "cell": yield (astyle.style_type_datetime, "%02d:%02d:%02d.%06d" % \ (self.hour, self.minute, self.second, self.microsecond)) else: yield (astyle.style_type_datetime, repr(self)) xrepr.when_type(datetime.time)(xrepr_time) def xrepr_timedelta(self, mode="default"): yield (astyle.style_type_datetime, repr(self)) xrepr.when_type(datetime.timedelta)(xrepr_timedelta) def xrepr_type(self, mode="default"): if self.__module__ == "__builtin__": yield (astyle.style_type_type, self.__name__) else: yield (astyle.style_type_type, "%s.%s" % (self.__module__, self.__name__)) xrepr.when_type(type)(xrepr_type) def xrepr_exception(self, mode="default"): if self.__class__.__module__ == "exceptions": classname = self.__class__.__name__ else: classname = "%s.%s" % \ (self.__class__.__module__, self.__class__.__name__) if mode == "header" or mode == "footer": yield (astyle.style_error, "%s: %s" % (classname, self)) else: yield (astyle.style_error, classname) xrepr.when_type(Exception)(xrepr_exception) def xrepr_listtuple(self, mode="default"): if mode == "header" or mode == "footer": if self.__class__.__module__ == "__builtin__": classname = self.__class__.__name__ else: classname = "%s.%s" % \ (self.__class__.__module__,self.__class__.__name__) yield (astyle.style_default, "<%s object with %d items at 0x%x>" % \ (classname, len(self), id(self))) else: yield (-1, False) if isinstance(self, list): yield (astyle.style_default, "[") end = "]" else: yield (astyle.style_default, "(") end = ")" for (i, subself) in enumerate(self): if i: yield (astyle.style_default, ", ") for part in xrepr(subself, "default"): yield part yield (astyle.style_default, end) xrepr.when_type(list)(xrepr_listtuple) xrepr.when_type(tuple)(xrepr_listtuple) def xrepr_dict(self, mode="default"): if mode == "header" or mode == "footer": if self.__class__.__module__ == "__builtin__": classname = self.__class__.__name__ else: classname = "%s.%s" % \ (self.__class__.__module__,self.__class__.__name__) yield (astyle.style_default, "<%s object with %d items at 0x%x>" % \ (classname, len(self), id(self))) else: yield (-1, False) if isinstance(self, dict): yield (astyle.style_default, "{") end = "}" else: yield (astyle.style_default, "dictproxy((") end = "})" for (i, (key, value)) in enumerate(self.iteritems()): if i: yield (astyle.style_default, ", ") for part in xrepr(key, "default"): yield part yield (astyle.style_default, ": ") for part in xrepr(value, "default"): yield part yield (astyle.style_default, end) xrepr.when_type(dict)(xrepr_dict) xrepr.when_type(types.DictProxyType)(xrepr_dict) def upgradexattr(attr): """ Convert an attribute descriptor string to a real descriptor object. If attr already is a descriptor object return if unmodified. A ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"`` returns an ``AttributeDescriptor`` for the attribute named ``"foo"``. ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``. ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor`` for the method named ``"foo"``. Furthermore integer will return the appropriate ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``. """ if attr is None: return selfdescriptor elif isinstance(attr, Descriptor): return attr elif isinstance(attr, str): if attr.endswith("()"): if attr.startswith("-"): return IterMethodDescriptor(attr[1:-2]) else: return MethodDescriptor(attr[:-2]) else: if attr.startswith("-"): return IterAttributeDescriptor(attr[1:]) else: return AttributeDescriptor(attr) elif isinstance(attr, (int, long)): return IndexDescriptor(attr) elif callable(attr): return FunctionDescriptor(attr) else: raise TypeError("can't handle descriptor %r" % attr) def xattrs(item, mode="default"): """ Generic function that returns an iterable of attribute descriptors to be used for displaying the attributes ob the object ``item`` in display mode ``mode``. There are two possible modes: * ``"detail"``: The ``Display`` object wants to display a detailed list of the object attributes. * ``"default"``: The ``Display`` object wants to display the object in a list view. If no implementation is registered for the object ``item`` ``xattrs`` falls back to trying the ``__xattrs__`` method of the object. If this doesn't exist either, ``dir(item)`` is used for ``"detail"`` mode and ``(None,)`` for ``"default"`` mode. The implementation must yield attribute descriptor (see the class ``Descriptor`` for more info). The ``__xattrs__`` method may also return attribute descriptor string (and ``None``) which will be converted to real descriptors by ``upgradexattr()``. """ try: func = item.__xattrs__ except AttributeError: if mode == "detail": for attrname in dir(item): yield AttributeDescriptor(attrname) else: yield selfdescriptor else: for attr in func(mode): yield upgradexattr(attr) xattrs = simplegeneric.generic(xattrs) def xattrs_complex(self, mode="default"): if mode == "detail": return (AttributeDescriptor("real"), AttributeDescriptor("imag")) return (selfdescriptor,) xattrs.when_type(complex)(xattrs_complex) def _isdict(item): try: itermeth = item.__class__.__iter__ except (AttributeError, TypeError): return False return itermeth is dict.__iter__ or itermeth is types.DictProxyType.__iter__ def _isstr(item): if not isinstance(item, basestring): return False try: itermeth = item.__class__.__iter__ except AttributeError: return True return False # ``__iter__`` has been redefined def xiter(item): """ Generic function that implements iteration for pipeline expression. If no implementation is registered for ``item`` ``xiter`` falls back to ``iter``. """ try: func = item.__xiter__ except AttributeError: if _isdict(item): def items(item): fields = ("key", "value") for (key, value) in item.iteritems(): yield Fields(fields, key=key, value=value) return items(item) elif isinstance(item, new.module): def items(item): fields = ("key", "value") for key in sorted(item.__dict__): yield Fields(fields, key=key, value=getattr(item, key)) return items(item) elif _isstr(item): if not item: raise ValueError("can't enter empty string") return iter(item.splitlines()) return iter(item) else: return iter(func()) # iter() just to be safe xiter = simplegeneric.generic(xiter) class ichain(Pipe): """ Chains multiple ``Table``s into one. """ def __init__(self, *iters): self.iters = iters def __iter__(self): return itertools.chain(*self.iters) def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": for (i, item) in enumerate(self.iters): if i: yield (astyle.style_default, "+") if isinstance(item, Pipe): yield (astyle.style_default, "(") for part in xrepr(item, mode): yield part if isinstance(item, Pipe): yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): args = ", ".join([repr(it) for it in self.iters]) return "%s.%s(%s)" % \ (self.__class__.__module__, self.__class__.__name__, args) class ifile(path.path): """ file (or directory) object. """ def getmode(self): return self.stat().st_mode mode = property(getmode, None, None, "Access mode") def gettype(self): data = [ (stat.S_ISREG, "file"), (stat.S_ISDIR, "dir"), (stat.S_ISCHR, "chardev"), (stat.S_ISBLK, "blockdev"), (stat.S_ISFIFO, "fifo"), (stat.S_ISLNK, "symlink"), (stat.S_ISSOCK,"socket"), ] lstat = self.lstat() if lstat is not None: types = set([text for (func, text) in data if func(lstat.st_mode)]) else: types = set() m = self.mode types.update([text for (func, text) in data if func(m)]) return ", ".join(types) type = property(gettype, None, None, "file type (file, directory, link, etc.)") def getmodestr(self): m = self.mode data = [ (stat.S_IRUSR, "-r"), (stat.S_IWUSR, "-w"), (stat.S_IXUSR, "-x"), (stat.S_IRGRP, "-r"), (stat.S_IWGRP, "-w"), (stat.S_IXGRP, "-x"), (stat.S_IROTH, "-r"), (stat.S_IWOTH, "-w"), (stat.S_IXOTH, "-x"), ] return "".join([text[bool(m&bit)] for (bit, text) in data]) modestr = property(getmodestr, None, None, "Access mode as string") def getblocks(self): return self.stat().st_blocks blocks = property(getblocks, None, None, "File size in blocks") def getblksize(self): return self.stat().st_blksize blksize = property(getblksize, None, None, "Filesystem block size") def getdev(self): return self.stat().st_dev dev = property(getdev) def getnlink(self): return self.stat().st_nlink nlink = property(getnlink, None, None, "Number of links") def getuid(self): return self.stat().st_uid uid = property(getuid, None, None, "User id of file owner") def getgid(self): return self.stat().st_gid gid = property(getgid, None, None, "Group id of file owner") def getowner(self): stat = self.stat() try: return pwd.getpwuid(stat.st_uid).pw_name except KeyError: return stat.st_uid owner = property(getowner, None, None, "Owner name (or id)") def getgroup(self): stat = self.stat() try: return grp.getgrgid(stat.st_gid).gr_name except KeyError: return stat.st_gid group = property(getgroup, None, None, "Group name (or id)") def getadate(self): return datetime.datetime.utcfromtimestamp(self.atime) adate = property(getadate, None, None, "Access date") def getcdate(self): return datetime.datetime.utcfromtimestamp(self.ctime) cdate = property(getcdate, None, None, "Creation date") def getmdate(self): return datetime.datetime.utcfromtimestamp(self.mtime) mdate = property(getmdate, None, None, "Modification date") def mimetype(self): """ Return MIME type guessed from the extension. """ return mimetypes.guess_type(self.basename())[0] def encoding(self): """ Return guessed compression (like "compress" or "gzip"). """ return mimetypes.guess_type(self.basename())[1] def __repr__(self): return "ifile(%s)" % path._base.__repr__(self) if sys.platform == "win32": defaultattrs = (None, "type", "size", "modestr", "mdate") else: defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate") def __xattrs__(self, mode="default"): if mode == "detail": return ( "name", "basename()", "abspath()", "realpath()", "type", "mode", "modestr", "stat()", "lstat()", "uid", "gid", "owner", "group", "dev", "nlink", "ctime", "mtime", "atime", "cdate", "mdate", "adate", "size", "blocks", "blksize", "isdir()", "islink()", "mimetype()", "encoding()", "-listdir()", "-dirs()", "-files()", "-walk()", "-walkdirs()", "-walkfiles()", ) else: return self.defaultattrs def xiter_ifile(self): if self.isdir(): yield (self / os.pardir).abspath() for child in sorted(self.listdir()): yield child else: f = self.open("rb") for line in f: yield line f.close() xiter.when_type(ifile)(xiter_ifile) # We need to implement ``xrepr`` for ``ifile`` as a generic function, because # otherwise ``xrepr_str`` would kick in. def xrepr_ifile(self, mode="default"): try: if self.isdir(): name = "idir" style = astyle.style_dir else: name = "ifile" style = astyle.style_file except IOError: name = "ifile" style = astyle.style_default if mode == "cell" or mode in "header" or mode == "footer": abspath = repr(path._base(self.normpath())) if abspath.startswith("u"): abspath = abspath[2:-1] else: abspath = abspath[1:-1] if mode == "cell": yield (style, abspath) else: yield (style, "%s(%s)" % (name, abspath)) else: yield (style, repr(self)) xrepr.when_type(ifile)(xrepr_ifile) class ils(Table): """ List the current (or a specified) directory. Examples: >>> ils >>> ils("/usr/local/lib/python2.4") >>> ils("~") """ def __init__(self, base=os.curdir, dirs=True, files=True): self.base = os.path.expanduser(base) self.dirs = dirs self.files = files def __iter__(self): base = ifile(self.base) yield (base / os.pardir).abspath() for child in base.listdir(): if self.dirs: if self.files: yield child else: if child.isdir(): yield child elif self.files: if not child.isdir(): yield child def __xrepr__(self, mode="default"): return xrepr(ifile(self.base), mode) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.base) class iglob(Table): """ List all files and directories matching a specified pattern. (See ``glob.glob()`` for more info.). Examples: >>> iglob("*.py") """ def __init__(self, glob): self.glob = glob def __iter__(self): for name in glob.glob(self.glob): yield ifile(name) def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.glob)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.glob) class iwalk(Table): """ List all files and directories in a directory and it's subdirectory. >>> iwalk >>> iwalk("/usr/local/lib/python2.4") >>> iwalk("~") """ def __init__(self, base=os.curdir, dirs=True, files=True): self.base = os.path.expanduser(base) self.dirs = dirs self.files = files def __iter__(self): for (dirpath, dirnames, filenames) in os.walk(self.base): if self.dirs: for name in sorted(dirnames): yield ifile(os.path.join(dirpath, name)) if self.files: for name in sorted(filenames): yield ifile(os.path.join(dirpath, name)) def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.base)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.base) class ipwdentry(object): """ ``ipwdentry`` objects encapsulate entries in the Unix user account and password database. """ def __init__(self, id): self._id = id self._entry = None def _getentry(self): if self._entry is None: if isinstance(self._id, basestring): self._entry = pwd.getpwnam(self._id) else: self._entry = pwd.getpwuid(self._id) return self._entry def getname(self): if isinstance(self._id, basestring): return self._id else: return self._getentry().pw_name name = property(getname, None, None, "User name") def getpasswd(self): return self._getentry().pw_passwd passwd = property(getpasswd, None, None, "Password") def getuid(self): if isinstance(self._id, basestring): return self._getentry().pw_uid else: return self._id uid = property(getuid, None, None, "User id") def getgid(self): return self._getentry().pw_gid gid = property(getgid, None, None, "Primary group id") def getgroup(self): return igrpentry(self.gid) group = property(getgroup, None, None, "Group") def getgecos(self): return self._getentry().pw_gecos gecos = property(getgecos, None, None, "Information (e.g. full user name)") def getdir(self): return self._getentry().pw_dir dir = property(getdir, None, None, "$HOME directory") def getshell(self): return self._getentry().pw_shell shell = property(getshell, None, None, "Login shell") def __xattrs__(self, mode="default"): return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell") def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self._id) class ipwd(Table): """ List all entries in the Unix user account and password database. Example: >>> ipwd | isort("uid") """ def __iter__(self): for entry in pwd.getpwall(): yield ipwdentry(entry.pw_name) def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class igrpentry(object): """ ``igrpentry`` objects encapsulate entries in the Unix group database. """ def __init__(self, id): self._id = id self._entry = None def _getentry(self): if self._entry is None: if isinstance(self._id, basestring): self._entry = grp.getgrnam(self._id) else: self._entry = grp.getgrgid(self._id) return self._entry def getname(self): if isinstance(self._id, basestring): return self._id else: return self._getentry().gr_name name = property(getname, None, None, "Group name") def getpasswd(self): return self._getentry().gr_passwd passwd = property(getpasswd, None, None, "Password") def getgid(self): if isinstance(self._id, basestring): return self._getentry().gr_gid else: return self._id gid = property(getgid, None, None, "Group id") def getmem(self): return self._getentry().gr_mem mem = property(getmem, None, None, "Members") def __xattrs__(self, mode="default"): return ("name", "passwd", "gid", "mem") def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "group ") try: yield (astyle.style_default, self.name) except KeyError: if isinstance(self._id, basestring): yield (astyle.style_default, self.name_id) else: yield (astyle.style_type_number, str(self._id)) else: yield (astyle.style_default, repr(self)) def __iter__(self): for member in self.mem: yield ipwdentry(member) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self._id) class igrp(Table): """ This ``Table`` lists all entries in the Unix group database. """ def __iter__(self): for entry in grp.getgrall(): yield igrpentry(entry.gr_name) def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class Fields(object): def __init__(self, fieldnames, **fields): self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames] for (key, value) in fields.iteritems(): setattr(self, key, value) def __xattrs__(self, mode="default"): return self.__fieldnames def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "cell": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f.name()) yield (astyle.style_default, "=") for part in xrepr(getattr(self, f), "default"): yield part yield (astyle.style_default, ")") elif mode == "footer": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f.name()) yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) class FieldTable(Table, list): def __init__(self, *fields): Table.__init__(self) list.__init__(self) self.fields = fields def add(self, **fields): self.append(Fields(self.fields, **fields)) def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "footer": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f) yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s object with fields=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, ", ".join(map(repr, self.fields)), id(self)) class List(list): def __xattrs__(self, mode="default"): return xrange(len(self)) def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "cell" or mode == "footer" or mode == "default": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, item) in enumerate(self): if i: yield (astyle.style_default, ", ") for part in xrepr(item, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) class ienv(Table): """ List environment variables. Example: >>> ienv """ def __iter__(self): fields = ("key", "value") for (key, value) in os.environ.iteritems(): yield Fields(fields, key=key, value=value) def __xrepr__(self, mode="default"): if mode == "header" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class icsv(Pipe): """ This ``Pipe`` lists turn the input (with must be a pipe outputting lines or an ``ifile``) into lines of CVS columns. """ def __init__(self, **csvargs): """ Create an ``icsv`` object. ``cvsargs`` will be passed through as keyword arguments to ``cvs.reader()``. """ self.csvargs = csvargs def __iter__(self): input = self.input if isinstance(input, ifile): input = input.open("rb") reader = csv.reader(input, **self.csvargs) for line in reader: yield List(line) def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for (i, (name, value)) in enumerate(self.csvargs.iteritems()): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, name) yield (astyle.style_default, "=") for part in xrepr(value, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()]) return "<%s.%s %s at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, args, id(self)) class ix(Table): """ Execute a system command and list its output as lines (similar to ``os.popen()``). Examples: >>> ix("ps x") >>> ix("find .") | ifile """ def __init__(self, cmd): self.cmd = cmd self._pipeout = None def __iter__(self): (_pipein, self._pipeout) = os.popen4(self.cmd) _pipein.close() for l in self._pipeout: yield l.rstrip("\r\n") self._pipeout.close() self._pipeout = None def __del__(self): if self._pipeout is not None and not self._pipeout.closed: self._pipeout.close() self._pipeout = None def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.cmd)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.cmd) class ifilter(Pipe): """ Filter an input pipe. Only objects where an expression evaluates to true (and doesn't raise an exception) are listed. Examples: >>> ils | ifilter("_.isfile() and size>1000") >>> igrp | ifilter("len(mem)") >>> sys.modules | ifilter(lambda _:_.value is not None) """ def __init__(self, expr, globals=None, errors="raiseifallfail"): """ Create an ``ifilter`` object. ``expr`` can be a callable or a string containing an expression. ``globals`` will be used as the global namespace for calling string expressions (defaulting to IPython's user namespace). ``errors`` specifies how exception during evaluation of ``expr`` are handled: * ``drop``: drop all items that have errors; * ``keep``: keep all items that have errors; * ``keeperror``: keep the exception of all items that have errors; * ``raise``: raise the exception; * ``raiseifallfail``: raise the first exception if all items have errors; otherwise drop those with errors (this is the default). """ self.expr = expr self.globals = globals self.errors = errors def __iter__(self): if callable(self.expr): test = self.expr else: g = getglobals(self.globals) expr = compile(self.expr, "ipipe-expression", "eval") def test(item): return eval(expr, g, AttrNamespace(item)) ok = 0 exc_info = None for item in xiter(self.input): try: if test(item): yield item ok += 1 except (KeyboardInterrupt, SystemExit): raise except Exception, exc: if self.errors == "drop": pass # Ignore errors elif self.errors == "keep": yield item elif self.errors == "keeperror": yield exc elif self.errors == "raise": raise elif self.errors == "raiseifallfail": if exc_info is None: exc_info = sys.exc_info() if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.expr, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s expr=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.expr, id(self)) class ieval(Pipe): """ Evaluate an expression for each object in the input pipe. Examples: >>> ils | ieval("_.abspath()") >>> sys.path | ieval(ifile) """ def __init__(self, expr, globals=None, errors="raiseifallfail"): """ Create an ``ieval`` object. ``expr`` can be a callable or a string containing an expression. For the meaning of ``globals`` and ``errors`` see ``ifilter``. """ self.expr = expr self.globals = globals self.errors = errors def __iter__(self): if callable(self.expr): do = self.expr else: g = getglobals(self.globals) expr = compile(self.expr, "ipipe-expression", "eval") def do(item): return eval(expr, g, AttrNamespace(item)) ok = 0 exc_info = None for item in xiter(self.input): try: yield do(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: if self.errors == "drop": pass # Ignore errors elif self.errors == "keep": yield item elif self.errors == "keeperror": yield exc elif self.errors == "raise": raise elif self.errors == "raiseifallfail": if exc_info is None: exc_info = sys.exc_info() if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.expr, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s expr=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.expr, id(self)) class ienum(Pipe): """ Enumerate the input pipe (i.e. wrap each input object in an object with ``index`` and ``object`` attributes). Examples: >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object") """ def __iter__(self): fields = ("index", "object") for (index, object) in enumerate(xiter(self.input)): yield Fields(fields, index=index, object=object) class isort(Pipe): """ Sorts the input pipe. Examples: >>> ils | isort("size") >>> ils | isort("_.isdir(), _.lower()", reverse=True) """ def __init__(self, key=None, globals=None, reverse=False): """ Create an ``isort`` object. ``key`` can be a callable or a string containing an expression (or ``None`` in which case the items themselves will be sorted). If ``reverse`` is true the sort order will be reversed. For the meaning of ``globals`` see ``ifilter``. """ self.key = key self.globals = globals self.reverse = reverse def __iter__(self): if self.key is None: items = sorted(xiter(self.input), reverse=self.reverse) elif callable(self.key): items = sorted(xiter(self.input), key=self.key, reverse=self.reverse) else: g = getglobals(self.globals) key = compile(self.key, "ipipe-expression", "eval") def realkey(item): return eval(key, g, AttrNamespace(item)) items = sorted(xiter(self.input), key=realkey, reverse=self.reverse) for item in items: yield item def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.key, "default"): yield part if self.reverse: yield (astyle.style_default, ", ") for part in xrepr(True, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s key=%r reverse=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.key, self.reverse, id(self)) tab = 3 # for expandtabs() def _format(field): if isinstance(field, str): text = repr(field.expandtabs(tab))[1:-1] elif isinstance(field, unicode): text = repr(field.expandtabs(tab))[2:-1] elif isinstance(field, datetime.datetime): # Don't use strftime() here, as this requires year >= 1900 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \ (field.year, field.month, field.day, field.hour, field.minute, field.second, field.microsecond) elif isinstance(field, datetime.date): text = "%04d-%02d-%02d" % (field.year, field.month, field.day) else: text = repr(field) return text class Display(object): class __metaclass__(type): def __ror__(self, input): return input | self() def __ror__(self, input): self.input = input return self def display(self): pass class iless(Display): cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS" def display(self): try: pager = os.popen(self.cmd, "w") try: for item in xiter(self.input): first = False for attr in xattrs(item, "default"): if first: first = False else: pager.write(" ") attr = upgradexattr(attr) if not isinstance(attr, SelfDescriptor): pager.write(attr.name()) pager.write("=") pager.write(str(attr.value(item))) pager.write("\n") finally: pager.close() except Exception, exc: print "%s: %s" % (exc.__class__.__name__, str(exc)) def xformat(value, mode, maxlength): align = None full = True width = 0 text = astyle.Text() for (style, part) in xrepr(value, mode): # only consider the first result if align is None: if isinstance(style, int): # (style, text) really is (alignment, stop) align = style full = part continue else: align = -1 full = True if not isinstance(style, int): text.append((style, part)) width += len(part) if width >= maxlength and not full: text.append((astyle.style_ellisis, "...")) width += 3 break if align is None: # default to left alignment align = -1 return (align, width, text) class idump(Display): # The approximate maximum length of a column entry maxattrlength = 200 # Style for column names style_header = astyle.Style.fromstr("white:black:bold") def __init__(self, *attrs): self.attrs = [upgradexattr(attr) for attr in attrs] self.headerpadchar = " " self.headersepchar = "|" self.datapadchar = " " self.datasepchar = "|" def display(self): stream = genutils.Term.cout allattrs = [] attrset = set() colwidths = {} rows = [] for item in xiter(self.input): row = {} attrs = self.attrs if not attrs: attrs = xattrs(item, "default") for attr in attrs: if attr not in attrset: allattrs.append(attr) attrset.add(attr) colwidths[attr] = len(attr.name()) try: value = attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: value = exc (align, width, text) = xformat(value, "cell", self.maxattrlength) colwidths[attr] = max(colwidths[attr], width) # remember alignment, length and colored parts row[attr] = (align, width, text) rows.append(row) stream.write("\n") for (i, attr) in enumerate(allattrs): attrname = attr.name() self.style_header(attrname).write(stream) spc = colwidths[attr] - len(attrname) if i < len(colwidths)-1: stream.write(self.headerpadchar*spc) stream.write(self.headersepchar) stream.write("\n") for row in rows: for (i, attr) in enumerate(allattrs): (align, width, text) = row[attr] spc = colwidths[attr] - width if align == -1: text.write(stream) if i < len(colwidths)-1: stream.write(self.datapadchar*spc) elif align == 0: spc = colwidths[attr] - width spc1 = spc//2 spc2 = spc-spc1 stream.write(self.datapadchar*spc1) text.write(stream) if i < len(colwidths)-1: stream.write(self.datapadchar*spc2) else: stream.write(self.datapadchar*spc) text.write(stream) if i < len(colwidths)-1: stream.write(self.datasepchar) stream.write("\n") class AttributeDetail(Table): """ ``AttributeDetail`` objects are use for displaying a detailed list of object attributes. """ def __init__(self, object, descriptor): self.object = object self.descriptor = descriptor def __iter__(self): return self.descriptor.iter(self.object) def name(self): return self.descriptor.name() def attrtype(self): return self.descriptor.attrtype(self.object) def valuetype(self): return self.descriptor.valuetype(self.object) def doc(self): return self.descriptor.doc(self.object) def shortdoc(self): return self.descriptor.shortdoc(self.object) def value(self): return self.descriptor.value(self.object) def __xattrs__(self, mode="default"): attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()") if mode == "detail": attrs += ("doc()",) return attrs def __xrepr__(self, mode="default"): yield (-1, True) valuetype = self.valuetype() if valuetype is not noitem: for part in xrepr(valuetype): yield part yield (astyle.style_default, " ") yield (astyle.style_default, self.attrtype()) yield (astyle.style_default, " ") yield (astyle.style_default, self.name()) yield (astyle.style_default, " of ") for part in xrepr(self.object): yield part try: from ibrowse import ibrowse except ImportError: # No curses (probably Windows) => use ``idump`` as the default display. defaultdisplay = idump else: defaultdisplay = ibrowse __all__.append("ibrowse") # If we're running under IPython, install an IPython displayhook that # returns the object from Display.display(), else install a displayhook # directly as sys.displayhook api = None if ipapi is not None: try: api = ipapi.get() except AttributeError: pass if api is not None: def displayhook(self, obj): if isinstance(obj, type) and issubclass(obj, Table): obj = obj() if isinstance(obj, Table): obj = obj | defaultdisplay if isinstance(obj, Display): return obj.display() else: raise ipapi.TryNext api.set_hook("result_display", displayhook) else: def installdisplayhook(): _originalhook = sys.displayhook def displayhook(obj): if isinstance(obj, type) and issubclass(obj, Table): obj = obj() if isinstance(obj, Table): obj = obj | defaultdisplay if isinstance(obj, Display): return obj.display() else: _originalhook(obj) sys.displayhook = displayhook installdisplayhook()