Source code for pyscripts.display
'''
Functions to manage the display or redirection of the streams to files,
stdout, stderr, etc.
'''
__author__ = ['Miguel Ramos Pernas']
__email__ = ['miguel.ramos.pernas@cern.ch']
# Python
import ctypes
import functools
import io
import os
import sys
import tempfile
from contextlib import contextmanager
__all__ = ['redirect_stdstream']
def decorate( deco ):
'''
Decorate using the given function, preserving the docstring.
:param deco: decorator.
:type deco: function
:returns: function used to decorate.
:rtype: function
'''
def _wrapper( f ):
'''
Wrap the decorator.
'''
@functools.wraps(f)
def __wrapper( *args, **kwargs ):
'''
Inner wrapper which actually calls the function.
'''
return deco(f)(*args, **kwargs)
return __wrapper
return _wrapper
[docs]@decorate(contextmanager)
def redirect_stdstream( istream = 'stdout', ostream = None, stream_type = 't' ):
'''
Redirect the information going to "istream" to the given stream.
:param istream: where to redirect the output from. It can be any of \
"stdout" or "stderr".
:type istream: str
:param ostream: object to collect the output stream.
:type ostream: file
:param stream_type: type of stream. 'b' if it works with byte objects or \
't' if it does it with strings.
:type stream_type: str
:returns: output stream (:class:`io.StringIO` by default).
:rtype: io.StringIO or file
:raises ValueError: if an incorrect input stream name or type is provided.
The call to this function opens a new context, so you can write:
>>> with redirect_stdstream() as out:
... # Whatever is printed here will go to "out"
... print('Hello')
...
>>> out.seek(0)
>>> captured = out.read()
>>> print(captured)
'Hello'
By default, it returns an :class:`io.StringIO` object.
If you are working with the :mod:`logging` package, beware that you might
have to define a special :class:`logging.Logger` instance for the messages
sent within the duration of the context.
For example, take a look at the following code:
>>> with pyscripts.redirect_stdstream('stdout') as stdout, pyscripts.redirect_stdstream('stderr') as stderr:
... #
... # Define the logger
... #
... logger = logging.getLogger('test')
... logger.setLevel(logging.INFO)
... #
... # Define the handler for "stdout"
... #
... hi = logging.StreamHandler(stdout)
... hi.setLevel(logging.INFO)
... #
... # Define the handler for "stderr"
... #
... he = logging.StreamHandler(stderr)
... he.setLevel(logging.WARNING)
... #
... # Define the formatter
... #
... f = logging.Formatter('%(levelname)s: %(message)s')
... hi.setFormatter(f)
... he.setFormatter(f)
... #
... # Add handlers
... #
... logger.addHandler(hi)
... logger.addHandler(he)
... #
... # Display some messages
... #
... logger.info('information')
... logger.error('error')
...
>>> stdout.seek(0)
>>> stdout.readlines()
['INFO: information\\n', 'ERROR: error\\n']
>>> stderr.seek(0)
>>> stderr.readlines()
['ERROR: error\\n']
Beware that the :mod:`logging` package uses strings.
If a byte-like working object is passed to :func:`redirect_stdstream`, the
execution will probably crash.
.. warning::
The call to this function will temporary close "sys.stdout" or
"sys.stderr", and will assign a new stream to them. Any object doing
operations on the old streams will most likely cause an error.
'''
choices = ('stdout', 'stderr')
if istream not in choices:
raise ValueError('Unknown stream "{}"; please select any of '\
'the following: {}'.format(istream, choices))
choices = ('t', 'b')
if stream_type not in choices:
raise ValueError('Unknown stream type "{}"; please select any of '\
'the following: {}'.format(stream_type, choices))
ostream = ostream if ostream is not None else io.StringIO()
# The original fd stream points to
original_stream_fd = getattr(sys, istream).fileno()
def _redirect( to_fd, istream ):
'''
Redirect "istream" to the given file descriptor.
'''
# Flush the C-level buffer stream
libc = ctypes.CDLL(None)
c_stream = ctypes.c_void_p.in_dll(libc, istream)
libc.fflush(c_stream)
# Flush and close sys.<stream> - also closes the file descriptor (fd)
getattr(sys, istream).close()
# Make original_stream_fd point to the same file as to_fd
os.dup2(to_fd, original_stream_fd)
# Create a new sys.<stream> that points to the redirected fd
c = io.FileIO(original_stream_fd, 'wb')
setattr(sys, istream, io.TextIOWrapper(c))
# Save a copy of the original stream fd in saved_stream_fd
saved_stream_fd = os.dup(original_stream_fd)
try:
# Create a temporary file and redirect stream to it
tfile = tempfile.TemporaryFile(mode='w+{}'.format(stream_type))
_redirect(tfile.fileno(), istream)
# Yield to caller
yield ostream
# Redirect stream back to the saved fd
_redirect(saved_stream_fd, istream)
# Copy contents of temporary file to the given stream
tfile.flush()
tfile.seek(0, io.SEEK_SET)
ostream.write(tfile.read())
finally:
tfile.close()
os.close(saved_stream_fd)