11stdio Manager
22=============
33
4- *Python context manager for mocking/wrapping stdin/ stdout/ stderr *
4+ *Python context manager for mocking/wrapping * `` stdin ``/`` stdout ``/`` stderr ``
55
66.. image :: https://travis-ci.org/bskinn/stdio-mgr.svg?branch=dev
77 :target: https://travis-ci.org/bskinn/stdio-mgr
@@ -17,39 +17,158 @@ stdio Manager
1717.. image :: https://img.shields.io/github/license/mashape/apistatus.svg
1818 :target: https://github.com/bskinn/stdio-mgr/blob/master/LICENSE.txt
1919
20- *README draft in progress. *
20+ ** Have a CLI Python application? * *
2121
22- Have a command-line Python application? Want to test *[...continued] *
22+ **Want to automate testing of the actual console input & output
23+ of your user-facing components? **
2324
25+ `stdio Manager ` can help.
2426
27+ While some functionality here is more or less duplicative of
28+ ``redirect_stdout `` and ``redirect_stderr `` in ``contextlib ``
29+ `within the standard library <https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout >`__,
30+ it provides (i) a much more concise way to mock both ``stdout `` and ``stderr `` at the same time,
31+ and (ii) a mechanism for mocking ``stdin ``, which is not available in ``contextlib ``.
2532
26- *[more about mocking stdio] *
33+ ** First, install: * *
2734
35+ .. code ::
36+
37+ $ pip install stdio-mgr
38+
39+ Then use!
40+
41+ All of the below examples assume ``stdio_mgr `` has already
42+ been imported via:
43+
44+ .. code ::
45+
46+ from stdio_mgr import stdio_mgr
47+
48+ **Mock ** ``stdout ``\ **: **
49+
50+ .. code ::
51+
52+ >>> with stdio_mgr() as (in_, out_, err_):
53+ ... print('foobar')
54+ ... out_cap = out_.getvalue()
55+ >>> out_cap
56+ 'foobar\n'
57+ >>> in_.closed and out_.closed and err_.closed
58+ True
59+
60+ By default ``print ``
61+ `appends a newline <https://docs.python.org/3/library/functions.html#print >`__
62+ after each argument, which is why ``out_cap `` is ``'foobar\n' ``
63+ and not just ``'foobar' ``.
64+
65+ As currently implemented, ``stdio_mgr `` closes all three mocked streams
66+ upon exiting the managed context.
67+
68+
69+ **Mock ** ``stderr ``\ **: **
70+
71+ .. code ::
72+
73+ >>> import warnings
74+ >>> with stdio_mgr() as (in_, out_, err_):
75+ ... warnings.warn("'foo' has no 'bar'")
76+ ... err_cap = err_.getvalue()
77+ >>> err_cap
78+ "...README.rst:2: UserWarning: 'foo' has no 'bar'\n =============\n"
79+
80+
81+ **Mock ** ``stdin ``\ **: **
82+
83+ The simulated user input has to be pre-loaded to the mocked stream.
84+ **Be sure to include newlines in the input to correspond to
85+ each mocked ** `Enter ` **keypress! **
86+ Otherwise, ``input `` will hang, waiting for a newline
87+ that will never come.
2888
29- In addition to mocking `stdio ` for testing, `stdio_mgr ` can also be used to
30- wrap functions that directly interact with `stdio `. Example:
89+ If the entirety of the input is known in advance,
90+ it can just be provided as an argument to ``stdio_mgr ``.
91+ Otherwise, ``.append() `` mocked input to ``in_ ``
92+ within the managed context as needed:
3193
3294.. code ::
3395
34- >>> def embellish(func):
96+ >>> with stdio_mgr('foobar\n') as (in_, out_, err_):
97+ ... print('baz')
98+ ... in_cap = input('??? ')
99+ ...
100+ ... _ = in_.append(in_cap[:3] + '\n')
101+ ... in_cap2 = input('??? ')
102+ ...
103+ ... out_cap = out_.getvalue()
104+ >>> in_cap
105+ 'foobar'
106+ >>> in_cap2
107+ 'foo'
108+ >>> out_cap
109+ 'baz\n??? foobar\n??? foo\n'
110+
111+ The ``_ = `` assignment suppresses ``print ``\ ing of the return value
112+ from the ``in_.append() `` call--otherwise, it would be interleaved
113+ in ``out_cap ``, since this example is shown for an interactive context.
114+ For non-interactive execution, as with ``unittest ``, ``pytest ``, etc.,
115+ these 'muting' assignments should not be necessary.
116+
117+ **Both ** the ``'??? ' `` prompts for ``input ``
118+ **and ** the mocked input strings
119+ are echoed to ``out_ ``, mimicking what a CLI user would see.
120+
121+ A subtlety: While the trailing newline on, e.g., ``'foobar\n' `` is stripped
122+ by ``input ``, it is *retained * in ``out_ ``.
123+ This is because ``in_ `` tees the content read from it to ``out_ ``
124+ *before * that content is passed to ``input ``.
125+
126+
127+ **Want to modify internal ** ``print `` **calls
128+ within a function or method? **
129+
130+ In addition to mocking, ``stdio_mgr `` can also be used to
131+ wrap functions that directly output to ``stdout ``/``stderr ``. A ``stdout `` example:
132+
133+ .. code ::
134+
135+ >>> def emboxen(func):
35136 ... def func_wrapper(s):
36137 ... from stdio_mgr import stdio_mgr
37138 ...
38- ... with stdio_mgr() as (i, o, e ):
139+ ... with stdio_mgr() as (in_, out_, err_ ):
39140 ... func(s)
40- ... content = o.getvalue()
41- ... newcontent = '*** ' + content.replace('\n', ' ***\n*** ')
42- ... newcontent = newcontent[:-5]
141+ ... content = out_.getvalue()
142+ ...
143+ ... max_len = max(map(len, content.splitlines()))
144+ ... fmt_str = '| {{: <{0}}} |\n'.format(max_len)
145+ ...
146+ ... newcontent = '=' * (max_len + 4) + '\n'
147+ ... for line in content.splitlines():
148+ ... newcontent += fmt_str.format(line)
149+ ... newcontent += '=' * (max_len + 4)
150+ ...
43151 ... print(newcontent)
152+ ...
44153 ... return func_wrapper
45154
46- >>> @embellish
155+ >>> @emboxen
47156 ... def testfunc(s):
48157 ... print(s)
49158
50159 >>> testfunc("""\
51160 ... Foo bar baz quux.
52- ... Lorem ipsum dolor sit amet....""")
53- *** Foo bar baz quux. ***
54- *** Lorem ipsum dolor sit amet.... ***
161+ ... Lorem ipsum dolor sit amet.""")
162+ ===============================
163+ | Foo bar baz quux. |
164+ | Lorem ipsum dolor sit amet. |
165+ ===============================
166+
167+
168+ **Feature requests or bug reports? **
169+
170+ Please submit them as GitHub `Issues <https://github.com/bskinn/stdio-mgr/issues >`__.
171+
172+ \( c) 2018 Brian Skinn
173+
55174
0 commit comments