Skip to content

Commit 233cedb

Browse files
committed
Merge branch 'release-1.0'
2 parents ae101ff + 828037c commit 233cedb

File tree

7 files changed

+256
-32
lines changed

7 files changed

+256
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
### [Unreleased]
99

10-
### [1.0.0] - 2018-03-26
10+
### [1.0.0] - 2018-04-01
1111

1212
#### Features
1313

README.rst

Lines changed: 134 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
stdio 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

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ flake8==3.4.1
44
flake8-docstrings==1.1.0
55
ipython
66
restview
7+
twine
78
wget

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def readme():
1414
packages=['stdio_mgr'],
1515
provides=['stdio_mgr'],
1616
requires=['attrs (>=17.1)'],
17-
install_requires=['attrs>=17'],
17+
install_requires=['attrs>=17.1'],
1818
python_requires='>=3',
1919
url='https://www.github.com/bskinn/stdio-mgr',
2020
license='MIT License',
@@ -27,10 +27,11 @@ def readme():
2727
'Intended Audience :: Developers',
2828
'Operating System :: OS Independent',
2929
'Programming Language :: Python :: 3 :: Only',
30+
'Programming Language :: Python :: 3.3',
3031
'Programming Language :: Python :: 3.4',
3132
'Programming Language :: Python :: 3.5',
3233
'Programming Language :: Python :: 3.6',
3334
'Topic :: Software Development :: Libraries :: Python Modules',
3435
'Topic :: Software Development :: Testing',
35-
'Development Status :: 4 - Beta'],
36+
'Development Status :: 5 - Production/Stable'],
3637
)

stdio_mgr/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
http://www.github.com/bskinn/stdio-mgr
1818
1919
**Documentation**
20-
[pending]
20+
See README.rst at the GitHub repository
2121
2222
**License**
2323
The MIT License; see |license_txt|_ for full license terms
@@ -34,4 +34,4 @@
3434
from .stdio_mgr import stdio_mgr
3535

3636

37-
__version__ = '1.0rc1'
37+
__version__ = '1.0'

0 commit comments

Comments
 (0)