Source code for ubelt.util_stream

# -*- coding: utf-8 -*-
"""
Functions for capturing and redirecting IO streams.
"""
from __future__ import print_function, division, absolute_import, unicode_literals
import sys
import six
import io
# import codecs
# from six.moves import cStringIO


[docs]class TeeStringIO(io.StringIO): """ An IO object that writes to itself and another IO stream. Attributes: redirect (io.IOBase): The other stream to write to. Example: >>> redirect = io.StringIO() >>> self = TeeStringIO(redirect) """ def __init__(self, redirect=None): self.redirect = redirect super(TeeStringIO, self).__init__()
[docs] def isatty(self): # nocover """ Returns true of the redirect is a terminal. Notes: Needed for IPython.embed to work properly when this class is used to override stdout / stderr. """ return (self.redirect is not None and hasattr(self.redirect, 'isatty') and self.redirect.isatty())
@property def encoding(self): """ Gets the encoding of the `redirect` IO object Doctest: >>> redirect = io.StringIO() >>> assert TeeStringIO(redirect).encoding is None >>> assert TeeStringIO(None).encoding is None >>> assert TeeStringIO(sys.stdout).encoding is sys.stdout.encoding >>> redirect = io.TextIOWrapper(io.StringIO()) >>> assert TeeStringIO(redirect).encoding is redirect.encoding """ if self.redirect is not None: return self.redirect.encoding else: return super(TeeStringIO, self).encoding
[docs] def write(self, msg): """ Write to this and the redirected stream """ if self.redirect is not None: self.redirect.write(msg) if six.PY2: from xdoctest.utils.util_str import ensure_unicode msg = ensure_unicode(msg) super(TeeStringIO, self).write(msg)
[docs] def flush(self): # nocover """ Flush to this and the redirected stream """ if self.redirect is not None: self.redirect.flush() super(TeeStringIO, self).flush()
[docs]class CaptureStream(object): """ Generic class for capturing streaming output from stdout or stderr """
[docs]class CaptureStdout(CaptureStream): r""" Context manager that captures stdout and stores it in an internal stream Args: supress (bool, default=True): if True, stdout is not printed while captured enabled (bool, default=True): does nothing if this is False Example: >>> self = CaptureStdout(supress=True) >>> print('dont capture the table flip (╯°□°)╯︵ ┻━┻') >>> with self: ... text = 'capture the heart ♥' ... print(text) >>> print('dont capture look of disapproval ಠ_ಠ') >>> assert isinstance(self.text, six.text_type) >>> assert self.text == text + '\n', 'failed capture text' Example: >>> self = CaptureStdout(supress=False) >>> with self: ... print('I am captured and printed in stdout') >>> assert self.text.strip() == 'I am captured and printed in stdout' Example: >>> self = CaptureStdout(supress=True, enabled=False) >>> with self: ... print('dont capture') >>> assert self.text is None """ def __init__(self, supress=True, enabled=True): self.enabled = enabled self.supress = supress self.orig_stdout = sys.stdout if supress: redirect = None else: redirect = self.orig_stdout self.cap_stdout = TeeStringIO(redirect) self.text = None self._pos = 0 # keep track of how much has been logged self.parts = [] self.started = False
[docs] def log_part(self): """ Log what has been captured so far """ self.cap_stdout.seek(self._pos) text = self.cap_stdout.read() self._pos = self.cap_stdout.tell() self.parts.append(text) self.text = text
[docs] def start(self): if self.enabled: self.text = '' self.started = True sys.stdout = self.cap_stdout
[docs] def stop(self): """ Doctest: >>> CaptureStdout(enabled=False).stop() >>> CaptureStdout(enabled=True).stop() """ if self.enabled: self.started = False sys.stdout = self.orig_stdout
def __enter__(self): self.start() return self def __del__(self): # nocover if self.started: self.stop() if self.cap_stdout is not None: self.close()
[docs] def close(self): self.cap_stdout.close() self.cap_stdout = None
def __exit__(self, type_, value, trace): if self.enabled: try: self.log_part() except Exception: # nocover raise finally: self.stop() if trace is not None: return False # return a falsey value on error
# class CaptureStdoutOrig(object): # r""" # Context manager that captures stdout and stores it in an internal stream # Args: # enabled (bool): (default = True) # CommandLine: # python -m ubelt.util_str CaptureStdout # TODO: # - [ ] use version of this class coded in xdoctest. # - [ ] rework to handle stdout, stderr or any other stream. # Example: # >>> from ubelt.util_str import * # NOQA # >>> self = CaptureStdout(enabled=True) # >>> print('dont capture the table flip (╯°□°)╯︵ ┻━┻') # >>> with self: # >>> print('capture the heart ♥') # >>> print('dont capture look of disapproval ಠ_ಠ') # >>> assert isinstance(self.text, six.text_type) # >>> assert self.text == 'capture the heart ♥\n', 'failed capture text' # """ # def __init__(self, enabled=True): # self.enabled = enabled # self.orig_stdout = sys.stdout # self.cap_stdout = cStringIO() # if six.PY2: # # http://stackoverflow.com/questions/1817695/stringio-accept-utf8 # codecinfo = codecs.lookup('utf8') # self.cap_stdout = codecs.StreamReaderWriter( # self.cap_stdout, codecinfo.streamreader, # codecinfo.streamwriter) # self.text = None # def __enter__(self): # if self.enabled: # sys.stdout = self.cap_stdout # return self # def __exit__(self, type_, value, trace): # if self.enabled: # try: # self.cap_stdout.seek(0) # self.text = self.cap_stdout.read() # if six.PY2: # self.text = self.text.decode('utf8') # except Exception: # nocover # pass # finally: # self.cap_stdout.close() # sys.stdout = self.orig_stdout # if trace is not None: # return False # return a falsey value on error