Kaydet (Commit) bb44fe0a authored tarafından Berker Peksag's avatar Berker Peksag

Issue #22389: Add contextlib.redirect_stderr().

üst ae553eb7
...@@ -172,6 +172,16 @@ Functions and classes provided: ...@@ -172,6 +172,16 @@ Functions and classes provided:
.. versionadded:: 3.4 .. versionadded:: 3.4
.. function:: redirect_stderr(new_target)
Similar to :func:`~contextlib.redirect_stdout` but redirecting
:data:`sys.stderr` to another file or file-like object.
This context manager is :ref:`reentrant <reentrant-cms>`.
.. versionadded:: 3.5
.. class:: ContextDecorator() .. class:: ContextDecorator()
A base class that enables a context manager to also be used as a decorator. A base class that enables a context manager to also be used as a decorator.
......
...@@ -148,6 +148,15 @@ compileall ...@@ -148,6 +148,15 @@ compileall
can now do parallel bytecode compilation. can now do parallel bytecode compilation.
(Contributed by Claudiu Popa in :issue:`16104`.) (Contributed by Claudiu Popa in :issue:`16104`.)
contextlib
----------
* The new :func:`contextlib.redirect_stderr` context manager(similar to
:func:`contextlib.redirect_stdout`) makes it easier for utility scripts to
handle inflexible APIs that write their output to :data:`sys.stderr` and
don't provide any options to redirect it.
(Contributed by Berker Peksag in :issue:`22389`.)
doctest doctest
------- -------
......
...@@ -5,7 +5,7 @@ from collections import deque ...@@ -5,7 +5,7 @@ from collections import deque
from functools import wraps from functools import wraps
__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack", __all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
"redirect_stdout", "suppress"] "redirect_stdout", "redirect_stderr", "suppress"]
class ContextDecorator(object): class ContextDecorator(object):
...@@ -151,8 +151,27 @@ class closing(object): ...@@ -151,8 +151,27 @@ class closing(object):
def __exit__(self, *exc_info): def __exit__(self, *exc_info):
self.thing.close() self.thing.close()
class redirect_stdout:
"""Context manager for temporarily redirecting stdout to another file class _RedirectStream:
_stream = None
def __init__(self, new_target):
self._new_target = new_target
# We use a list of old targets to make this CM re-entrant
self._old_targets = []
def __enter__(self):
self._old_targets.append(getattr(sys, self._stream))
setattr(sys, self._stream, self._new_target)
return self._new_target
def __exit__(self, exctype, excinst, exctb):
setattr(sys, self._stream, self._old_targets.pop())
class redirect_stdout(_RedirectStream):
"""Context manager for temporarily redirecting stdout to another file.
# How to send help() to stderr # How to send help() to stderr
with redirect_stdout(sys.stderr): with redirect_stdout(sys.stderr):
...@@ -164,18 +183,13 @@ class redirect_stdout: ...@@ -164,18 +183,13 @@ class redirect_stdout:
help(pow) help(pow)
""" """
def __init__(self, new_target): _stream = "stdout"
self._new_target = new_target
# We use a list of old targets to make this CM re-entrant
self._old_targets = []
def __enter__(self):
self._old_targets.append(sys.stdout)
sys.stdout = self._new_target
return self._new_target
def __exit__(self, exctype, excinst, exctb): class redirect_stderr(_RedirectStream):
sys.stdout = self._old_targets.pop() """Context manager for temporarily redirecting stderr to another file."""
_stream = "stderr"
class suppress: class suppress:
......
...@@ -718,60 +718,76 @@ class TestExitStack(unittest.TestCase): ...@@ -718,60 +718,76 @@ class TestExitStack(unittest.TestCase):
stack.push(cm) stack.push(cm)
self.assertIs(stack._exit_callbacks[-1], cm) self.assertIs(stack._exit_callbacks[-1], cm)
class TestRedirectStdout(unittest.TestCase):
class TestRedirectStream:
redirect_stream = None
orig_stream = None
@support.requires_docstrings @support.requires_docstrings
def test_instance_docs(self): def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings # Issue 19330: ensure context manager instances have good docstrings
cm_docstring = redirect_stdout.__doc__ cm_docstring = self.redirect_stream.__doc__
obj = redirect_stdout(None) obj = self.redirect_stream(None)
self.assertEqual(obj.__doc__, cm_docstring) self.assertEqual(obj.__doc__, cm_docstring)
def test_no_redirect_in_init(self): def test_no_redirect_in_init(self):
orig_stdout = sys.stdout orig_stdout = getattr(sys, self.orig_stream)
redirect_stdout(None) self.redirect_stream(None)
self.assertIs(sys.stdout, orig_stdout) self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
def test_redirect_to_string_io(self): def test_redirect_to_string_io(self):
f = io.StringIO() f = io.StringIO()
msg = "Consider an API like help(), which prints directly to stdout" msg = "Consider an API like help(), which prints directly to stdout"
orig_stdout = sys.stdout orig_stdout = getattr(sys, self.orig_stream)
with redirect_stdout(f): with self.redirect_stream(f):
print(msg) print(msg, file=getattr(sys, self.orig_stream))
self.assertIs(sys.stdout, orig_stdout) self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
s = f.getvalue().strip() s = f.getvalue().strip()
self.assertEqual(s, msg) self.assertEqual(s, msg)
def test_enter_result_is_target(self): def test_enter_result_is_target(self):
f = io.StringIO() f = io.StringIO()
with redirect_stdout(f) as enter_result: with self.redirect_stream(f) as enter_result:
self.assertIs(enter_result, f) self.assertIs(enter_result, f)
def test_cm_is_reusable(self): def test_cm_is_reusable(self):
f = io.StringIO() f = io.StringIO()
write_to_f = redirect_stdout(f) write_to_f = self.redirect_stream(f)
orig_stdout = sys.stdout orig_stdout = getattr(sys, self.orig_stream)
with write_to_f: with write_to_f:
print("Hello", end=" ") print("Hello", end=" ", file=getattr(sys, self.orig_stream))
with write_to_f: with write_to_f:
print("World!") print("World!", file=getattr(sys, self.orig_stream))
self.assertIs(sys.stdout, orig_stdout) self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
s = f.getvalue() s = f.getvalue()
self.assertEqual(s, "Hello World!\n") self.assertEqual(s, "Hello World!\n")
def test_cm_is_reentrant(self): def test_cm_is_reentrant(self):
f = io.StringIO() f = io.StringIO()
write_to_f = redirect_stdout(f) write_to_f = self.redirect_stream(f)
orig_stdout = sys.stdout orig_stdout = getattr(sys, self.orig_stream)
with write_to_f: with write_to_f:
print("Hello", end=" ") print("Hello", end=" ", file=getattr(sys, self.orig_stream))
with write_to_f: with write_to_f:
print("World!") print("World!", file=getattr(sys, self.orig_stream))
self.assertIs(sys.stdout, orig_stdout) self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
s = f.getvalue() s = f.getvalue()
self.assertEqual(s, "Hello World!\n") self.assertEqual(s, "Hello World!\n")
class TestRedirectStdout(TestRedirectStream, unittest.TestCase):
redirect_stream = redirect_stdout
orig_stream = "stdout"
class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
redirect_stream = redirect_stderr
orig_stream = "stderr"
class TestSuppress(unittest.TestCase): class TestSuppress(unittest.TestCase):
@support.requires_docstrings @support.requires_docstrings
......
...@@ -191,6 +191,8 @@ Core and Builtins ...@@ -191,6 +191,8 @@ Core and Builtins
Library Library
------- -------
- Issue #22389: Add contextlib.redirect_stderr().
- Issue #21356: Make ssl.RAND_egd() optional to support LibreSSL. The - Issue #21356: Make ssl.RAND_egd() optional to support LibreSSL. The
availability of the function is checked during the compilation. Patch written availability of the function is checked during the compilation. Patch written
by Bernard Spil. by Bernard Spil.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment