Skip to content

Commit

Permalink
Merge branch 'release-1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
bskinn committed Apr 2, 2018
2 parents ae101ff + 828037c commit 233cedb
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 32 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### [Unreleased]

### [1.0.0] - 2018-03-26
### [1.0.0] - 2018-04-01

#### Features

Expand Down
149 changes: 134 additions & 15 deletions README.rst
@@ -1,7 +1,7 @@
stdio Manager
=============

*Python context manager for mocking/wrapping stdin/stdout/stderr*
*Python context manager for mocking/wrapping* ``stdin``/``stdout``/``stderr``

.. image:: https://travis-ci.org/bskinn/stdio-mgr.svg?branch=dev
:target: https://travis-ci.org/bskinn/stdio-mgr
Expand All @@ -17,39 +17,158 @@ stdio Manager
.. image:: https://img.shields.io/github/license/mashape/apistatus.svg
:target: https://github.com/bskinn/stdio-mgr/blob/master/LICENSE.txt

*README draft in progress.*
**Have a CLI Python application?**

Have a command-line Python application? Want to test *[...continued]*
**Want to automate testing of the actual console input & output
of your user-facing components?**

`stdio Manager` can help.

While some functionality here is more or less duplicative of
``redirect_stdout`` and ``redirect_stderr`` in ``contextlib``
`within the standard library <https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout>`__,
it provides (i) a much more concise way to mock both ``stdout`` and ``stderr`` at the same time,
and (ii) a mechanism for mocking ``stdin``, which is not available in ``contextlib``.

*[more about mocking stdio]*
**First, install:**

.. code::
$ pip install stdio-mgr
Then use!

All of the below examples assume ``stdio_mgr`` has already
been imported via:

.. code::
from stdio_mgr import stdio_mgr
**Mock** ``stdout``\ **:**

.. code::
>>> with stdio_mgr() as (in_, out_, err_):
... print('foobar')
... out_cap = out_.getvalue()
>>> out_cap
'foobar\n'
>>> in_.closed and out_.closed and err_.closed
True
By default ``print``
`appends a newline <https://docs.python.org/3/library/functions.html#print>`__
after each argument, which is why ``out_cap`` is ``'foobar\n'``
and not just ``'foobar'``.

As currently implemented, ``stdio_mgr`` closes all three mocked streams
upon exiting the managed context.


**Mock** ``stderr``\ **:**

.. code ::
>>> import warnings
>>> with stdio_mgr() as (in_, out_, err_):
... warnings.warn("'foo' has no 'bar'")
... err_cap = err_.getvalue()
>>> err_cap
"...README.rst:2: UserWarning: 'foo' has no 'bar'\n =============\n"
**Mock** ``stdin``\ **:**

The simulated user input has to be pre-loaded to the mocked stream.
**Be sure to include newlines in the input to correspond to
each mocked** `Enter` **keypress!**
Otherwise, ``input`` will hang, waiting for a newline
that will never come.

In addition to mocking `stdio` for testing, `stdio_mgr` can also be used to
wrap functions that directly interact with `stdio`. Example:
If the entirety of the input is known in advance,
it can just be provided as an argument to ``stdio_mgr``.
Otherwise, ``.append()`` mocked input to ``in_``
within the managed context as needed:

.. code::
>>> def embellish(func):
>>> with stdio_mgr('foobar\n') as (in_, out_, err_):
... print('baz')
... in_cap = input('??? ')
...
... _ = in_.append(in_cap[:3] + '\n')
... in_cap2 = input('??? ')
...
... out_cap = out_.getvalue()
>>> in_cap
'foobar'
>>> in_cap2
'foo'
>>> out_cap
'baz\n??? foobar\n??? foo\n'
The ``_ =`` assignment suppresses ``print``\ ing of the return value
from the ``in_.append()`` call--otherwise, it would be interleaved
in ``out_cap``, since this example is shown for an interactive context.
For non-interactive execution, as with ``unittest``, ``pytest``, etc.,
these 'muting' assignments should not be necessary.

**Both** the ``'??? '`` prompts for ``input``
**and** the mocked input strings
are echoed to ``out_``, mimicking what a CLI user would see.

A subtlety: While the trailing newline on, e.g., ``'foobar\n'`` is stripped
by ``input``, it is *retained* in ``out_``.
This is because ``in_`` tees the content read from it to ``out_``
*before* that content is passed to ``input``.


**Want to modify internal** ``print`` **calls
within a function or method?**

In addition to mocking, ``stdio_mgr`` can also be used to
wrap functions that directly output to ``stdout``/``stderr``. A ``stdout`` example:

.. code::
>>> def emboxen(func):
... def func_wrapper(s):
... from stdio_mgr import stdio_mgr
...
... with stdio_mgr() as (i, o, e):
... with stdio_mgr() as (in_, out_, err_):
... func(s)
... content = o.getvalue()
... newcontent = '*** ' + content.replace('\n', ' ***\n*** ')
... newcontent = newcontent[:-5]
... content = out_.getvalue()
...
... max_len = max(map(len, content.splitlines()))
... fmt_str = '| {{: <{0}}} |\n'.format(max_len)
...
... newcontent = '=' * (max_len + 4) + '\n'
... for line in content.splitlines():
... newcontent += fmt_str.format(line)
... newcontent += '=' * (max_len + 4)
...
... print(newcontent)
...
... return func_wrapper
>>> @embellish
>>> @emboxen
... def testfunc(s):
... print(s)
>>> testfunc("""\
... Foo bar baz quux.
... Lorem ipsum dolor sit amet....""")
*** Foo bar baz quux. ***
*** Lorem ipsum dolor sit amet.... ***
... Lorem ipsum dolor sit amet.""")
===============================
| Foo bar baz quux. |
| Lorem ipsum dolor sit amet. |
===============================
**Feature requests or bug reports?**

Please submit them as GitHub `Issues <https://github.com/bskinn/stdio-mgr/issues>`__.

\(c) 2018 Brian Skinn


1 change: 1 addition & 0 deletions requirements-dev.txt
Expand Up @@ -4,4 +4,5 @@ flake8==3.4.1
flake8-docstrings==1.1.0
ipython
restview
twine
wget
5 changes: 3 additions & 2 deletions setup.py
Expand Up @@ -14,7 +14,7 @@ def readme():
packages=['stdio_mgr'],
provides=['stdio_mgr'],
requires=['attrs (>=17.1)'],
install_requires=['attrs>=17'],
install_requires=['attrs>=17.1'],
python_requires='>=3',
url='https://www.github.com/bskinn/stdio-mgr',
license='MIT License',
Expand All @@ -27,10 +27,11 @@ def readme():
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Testing',
'Development Status :: 4 - Beta'],
'Development Status :: 5 - Production/Stable'],
)
4 changes: 2 additions & 2 deletions stdio_mgr/__init__.py
Expand Up @@ -17,7 +17,7 @@
http://www.github.com/bskinn/stdio-mgr
**Documentation**
[pending]
See README.rst at the GitHub repository
**License**
The MIT License; see |license_txt|_ for full license terms
Expand All @@ -34,4 +34,4 @@
from .stdio_mgr import stdio_mgr


__version__ = '1.0rc1'
__version__ = '1.0'

0 comments on commit 233cedb

Please sign in to comment.