pax_global_header00006660000000000000000000000064134303701110014503gustar00rootroot0000000000000052 comment=909e0cd8c1470b3fc3a61579ee012f716ef40d32 bskinn-stdio-mgr-909e0cd/000077500000000000000000000000001343037011100153115ustar00rootroot00000000000000bskinn-stdio-mgr-909e0cd/.coveragerc000066400000000000000000000003131343037011100174270ustar00rootroot00000000000000[run] omit = # Don't do coverage on test code tests/* conftest.py # Don't cover code in the env (Termux only?) env*/* [report] exclude_lines = pragma: no cover ^\s*pass\s*$ bskinn-stdio-mgr-909e0cd/.gitignore000066400000000000000000000023051343037011100173010ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env*/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .xcovrc # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Temp/backup editor files *.bak bskinn-stdio-mgr-909e0cd/.travis.yml000066400000000000000000000011231343037011100174170ustar00rootroot00000000000000install: - pip install -U --force-reinstall -r requirements-travis.txt - is_py33=$( echo $TRAVIS_PYTHON_VERSION | grep -e '^3\.3' | wc -l ) - if [ $is_py33 -gt 0 ]; then pip install 'setuptools==39.0.0'; fi - pip install -e . language: python python: - 3.3 - 3.4 - 3.5 - 3.6 - 3.7-dev script: - python --version - pip list - pytest --cov=src/stdio_mgr - do_rest=$( echo $TRAVIS_PYTHON_VERSION | grep -e '^3\.6' | wc -l ) - if [ $do_rest -gt 0 ]; then codecov; else echo "No codecov."; fi - if [ $do_rest -gt 0 ]; then pip install black; black --check .; else echo "No black."; fi bskinn-stdio-mgr-909e0cd/CHANGELOG.md000066400000000000000000000017541343037011100171310ustar00rootroot00000000000000## CHANGELOG: stdio-mgr stdin/stdout/stderr mock/wrap context manager All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ### [Unreleased] ... ### [1.0.1] - 2019-02-11 #### Changed * `TeeStdin` is now a `slots=False` attrs class, to avoid errors arising from some manner of change in the vicinity of attrs v18.1/v18.2. ### [1.0.0] - 2018-04-01 #### Features * `stdio_mgr` context manager with capability for mocking/wrapping all three of `stdin`/`stdout`/`stderr` * `stdin` mocking/wrapping is implemented with the custom `TeeStdin`, a subclass of `StringIO`, which tees all content read from itself into the mocked/wrapped `stdout` * `TeeStdin` is extended from `StringIO` by an `.append()` method, which adds content to the end of the stream without changing the current seek position. bskinn-stdio-mgr-909e0cd/LICENSE.txt000066400000000000000000000020611343037011100171330ustar00rootroot00000000000000MIT License Copyright (c) 2018-2019 Brian Skinn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bskinn-stdio-mgr-909e0cd/MANIFEST.in000066400000000000000000000000731343037011100170470ustar00rootroot00000000000000include LICENSE.txt README.rst CHANGELOG.md pyproject.toml bskinn-stdio-mgr-909e0cd/README.rst000066400000000000000000000131201343037011100167750ustar00rootroot00000000000000stdio Manager: Context manager for mocking/wrapping ``stdin``/``stdout``/``stderr`` =================================================================================== **Current Development Version:** .. image:: https://travis-ci.org/bskinn/stdio-mgr.svg?branch=dev :target: https://travis-ci.org/bskinn/stdio-mgr .. image:: https://codecov.io/gh/bskinn/stdio-mgr/branch/dev/graph/badge.svg :target: https://codecov.io/gh/bskinn/stdio-mgr **Most Recent Stable Release:** .. image:: https://img.shields.io/pypi/v/stdio_mgr.svg :target: https://pypi.org/project/stdio-mgr .. image:: https://img.shields.io/pypi/pyversions/stdio-mgr.svg **Info:** .. image:: https://img.shields.io/github/license/mashape/apistatus.svg :target: https://github.com/bskinn/stdio-mgr/blob/master/LICENSE.txt .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black ---- **Have a CLI Python application?** **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 `__, 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``. **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 `__ 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 "...UserWarning: 'foo' has no 'bar'\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. 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:: >>> 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 (in_, out_, err_): ... func(s) ... 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 >>> @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. | =============================== ---- Available on `PyPI `__ (``pip install stdio-mgr``). Source on `GitHub `__. Bug reports and feature requests are welcomed at the `Issues `__ page there. Copyright \(c) 2018-2019 Brian Skinn License: The MIT License. See `LICENSE.txt `__ for full license terms. bskinn-stdio-mgr-909e0cd/conftest.py000066400000000000000000000013061343037011100175100ustar00rootroot00000000000000r"""*pytest configuration for the* ``stdio_mgr`` *test suite*. ``stdio_mgr`` provides a context manager for convenient mocking and/or wrapping of ``stdin``/``stdout``/``stderr`` interactions. **Author** Brian Skinn (bskinn@alum.mit.edu) **File Created** 6 Feb 2019 **Copyright** \(c) Brian Skinn 2018-2019 **Source Repository** http://www.github.com/bskinn/stdio-mgr **Documentation** See README.rst at the GitHub repository **License** The MIT License; see |license_txt|_ for full license terms **Members** """ import pytest from stdio_mgr import stdio_mgr @pytest.fixture(autouse=True) def add_stdio_mgr(doctest_namespace): doctest_namespace["stdio_mgr"] = stdio_mgr bskinn-stdio-mgr-909e0cd/pyproject.toml000066400000000000000000000004121343037011100202220ustar00rootroot00000000000000[build-system] requires = ["wheel", "setuptools", "attrs>=17.1"] build-backend = "setuptools.build_meta" [tool.black] line-length = 79 include = ''' ( ^/tests/ | ^/src/stdio_mgr/ | ^/setup[.]py | ^/conftest[.]py ) ''' exclude = ''' ( __pycache__ ) '''bskinn-stdio-mgr-909e0cd/requirements-dev.txt000066400000000000000000000001551343037011100213520ustar00rootroot00000000000000attrs>=17 black flake8==3.4.1 flake8-docstrings==1.1.0 ipython pytest pytest-cov restview tox twine wget -e .bskinn-stdio-mgr-909e0cd/requirements-travis.txt000066400000000000000000000001141343037011100220770ustar00rootroot00000000000000attrs>=17 codecov flake8==3.4.1 flake8-docstrings==1.1.0 pytest pytest-cov bskinn-stdio-mgr-909e0cd/setup.py000066400000000000000000000026621343037011100170310ustar00rootroot00000000000000import os, sys from setuptools import setup, find_packages sys.path.append(os.path.abspath("src")) from stdio_mgr import __version__ sys.path.pop() def readme(): with open("README.rst", "r") as f: return f.read() setup( name="stdio-mgr", version=__version__, packages=find_packages("src"), package_dir={"": "src"}, provides=["stdio_mgr"], requires=["attrs (>=17.1)"], install_requires=["attrs>=17.1"], python_requires=">=3", url="https://www.github.com/bskinn/stdio-mgr", license="MIT License", author="Brian Skinn", author_email="bskinn@alum.mit.edu", description="Context manager for mocking/wrapping stdin/stdout/stderr", long_description=readme(), classifiers=[ "License :: OSI Approved :: MIT License", "Natural Language :: English", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "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", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Testing", "Development Status :: 5 - Production/Stable", ], ) bskinn-stdio-mgr-909e0cd/src/000077500000000000000000000000001343037011100161005ustar00rootroot00000000000000bskinn-stdio-mgr-909e0cd/src/stdio_mgr/000077500000000000000000000000001343037011100200675ustar00rootroot00000000000000bskinn-stdio-mgr-909e0cd/src/stdio_mgr/__init__.py000066400000000000000000000011461343037011100222020ustar00rootroot00000000000000r"""``stdio_mgr`` *package definition module*. ``stdio_mgr`` provides a context manager for convenient mocking and/or wrapping of ``stdin``/``stdout``/``stderr`` interactions. **Author** Brian Skinn (bskinn@alum.mit.edu) **File Created** 24 Mar 2018 **Copyright** \(c) Brian Skinn 2018-2019 **Source Repository** http://www.github.com/bskinn/stdio-mgr **Documentation** See README.rst at the GitHub repository **License** The MIT License; see |license_txt|_ for full license terms **Members** """ __all__ = ["stdio_mgr"] from .stdio_mgr import stdio_mgr __version__ = "1.0.1" bskinn-stdio-mgr-909e0cd/src/stdio_mgr/stdio_mgr.py000066400000000000000000000115761343037011100224420ustar00rootroot00000000000000r"""``stdio_mgr`` *code module*. ``stdio_mgr`` provides a context manager for convenient mocking and/or wrapping of ``stdin``/``stdout``/``stderr`` interactions. **Author** Brian Skinn (bskinn@alum.mit.edu) **File Created** 24 Mar 2018 **Copyright** \(c) Brian Skinn 2018-2019 **Source Repository** http://www.github.com/bskinn/stdio-mgr **Documentation** See README.rst at the GitHub repository **License** The MIT License; see |license_txt|_ for full license terms **Members** """ from contextlib import contextmanager from io import StringIO, TextIOBase import attr @attr.s(slots=False) class TeeStdin(StringIO): """Class to tee contents to a side buffer on read. Subclass of :cls:`~io.StringIO` that overrides :meth:`~io.StringIO.read` and :meth:`~io.StringIO.readline` to tee all content *read* from the stream to `tee`. The canonical use-case is with :func:`stdio_mgr`, where `tee` is the mocked stream for `stdin`. To emphasize: teeing occurs on content *read*, **not write**. This class also provides the method :meth:`TeeStdin.append`, which is not available for the base :cls:`~io.StringIO` type. This method adds new content to the end of the stream while leaving the read position unchanged. Instantiation takes two arguments: `tee` :cls:`~io.TextIOBase` -- Text stream to receive content teed from :cls:`TeeStdin` upon read `init_text` |str| *(optional)* -- Text to use as the initial contents of the underlying :cls:`~io.StringIO`. `init_text` is passed directly to the :cls:~io.StringIO` instantiation call. Default is an empty |str|. """ from io import SEEK_SET, SEEK_END tee = attr.ib(validator=attr.validators.instance_of(TextIOBase)) init_text = attr.ib(default="", validator=attr.validators.instance_of(str)) def __attrs_post_init__(self): """Call normal __init__ on superclass.""" super().__init__(self.init_text) def read(self, size=None): # pragma: no cover """Tee text to side buffer when read. Overrides :meth:`io.StringIO.read ` to implement the teeing. Parameters ---------- size |int| or |None| *(optional)* -- Number of characters to return; a negative or |None| value reads to EOF. """ text = super().read(size) self.tee.write(text) return text def readline(self, size=-1): """Tee text to side buffer when read. Overrides :meth:`io.StringIO.readline ` to implement the teeing. Parameters ---------- size |int| *(optional)* -- Number of characters to return; a negative value reads an entire line, regardless of length """ text = super().readline(size) self.tee.write(text) return text def append(self, text): """Write to end of stream while maintaining seek position. Actually stores the current position; seeks to end; writes `text`; and seeks to prior position. Parameters ---------- text |str| -- Text to append to the current stream contents. """ pos = self.tell() self.seek(0, self.SEEK_END) retval = self.write(text) self.seek(pos, self.SEEK_SET) return retval @contextmanager def stdio_mgr(in_str=""): r"""Subsitute temporary text buffers for `stdio` in a managed context. Context manager. Substitutes empty :cls:`~io.StringIO`\ s for :cls:`sys.stdout` and :cls:`sys.stderr`, and a :cls:`TeeStdin` for :cls:`sys.stdin` within the managed context. Upon exiting the context, the original stream objects are restored within :mod:`sys`, and the temporary streams are closed. Parameters ---------- in_str |str| *(optional)* -- Initialization text for the :cls:`TeeStdin` substitution for `stdin`. Default is an empty string. Yields ------ in_ :cls:`TeeStdin` -- Temporary stream for `stdin`. out_ :cls:`~io.StringIO` -- Temporary stream for `stdout`, initially empty. err_ :cls:`~io.StringIO` -- Temporary stream for `stderr`, initially empty. """ import sys old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr new_stdout = StringIO() new_stderr = StringIO() new_stdin = TeeStdin(new_stdout, in_str) sys.stdin = new_stdin sys.stdout = new_stdout sys.stderr = new_stderr yield new_stdin, new_stdout, new_stderr sys.stdin = old_stdin sys.stdout = old_stdout sys.stderr = old_stderr new_stdin.close() new_stdout.close() new_stderr.close() if __name__ == "__main__": # pragma: no cover print("Module not executable.") bskinn-stdio-mgr-909e0cd/tests/000077500000000000000000000000001343037011100164535ustar00rootroot00000000000000bskinn-stdio-mgr-909e0cd/tests/test_stdiomgr_base.py000066400000000000000000000054071343037011100227140ustar00rootroot00000000000000r"""*Base submodule for the* ``stdio_mgr`` *test suite*. ``stdio_mgr`` provides a context manager for convenient mocking and/or wrapping of ``stdin``/``stdout``/``stderr`` interactions. **Author** Brian Skinn (bskinn@alum.mit.edu) **File Created** 24 Mar 2018 **Copyright** \(c) Brian Skinn 2018-2019 **Source Repository** http://www.github.com/bskinn/stdio-mgr **Documentation** See README.rst at the GitHub repository **License** The MIT License; see |license_txt|_ for full license terms **Members** """ def test_CaptureStdout(): """Confirm stdout capture.""" from stdio_mgr import stdio_mgr with stdio_mgr() as (i, o, e): s = "test str" print(s) # 'print' automatically adds a newline assert s + "\n" == o.getvalue() def test_CaptureStderr(): """Confirm stderr capture.""" import warnings from stdio_mgr import stdio_mgr with stdio_mgr() as (i, o, e): w = "This is a warning" warnings.warn(w) # Warning text comes at the end of a line; newline gets added assert w + "\n" in e.getvalue() def test_DefaultStdin(): """Confirm stdin default-populate.""" from stdio_mgr import stdio_mgr in_str = "This is a test string.\n" with stdio_mgr(in_str) as (i, o, e): assert in_str == i.getvalue() out_str = input() # TeeStdin tees the stream contents, *including* the newline, # to the managed stdout assert in_str == o.getvalue() # 'input' strips the trailing newline before returning assert in_str[:-1] == out_str def test_ManagedStdin(): """Confirm stdin populate within context.""" from stdio_mgr import stdio_mgr str1 = "This is a test string." str2 = "This is another test string.\n" with stdio_mgr() as (i, o, e): # Preload str1 to stdout, and check. As above, 'print' # appends a newline print(str1) assert str1 + "\n" == o.getvalue() # Use custom method .append to add the contents # without moving the seek position; check stdin contents. # The newline remains, since the stream contents were not # run through 'input' i.append(str2) assert str2 == i.getvalue() # Pull the contents of stdin to variable out_str = input() # stdout should have both strings. The newline of str2 is # *retained* here, because str2 was teed from stdin upon # the read of stdin by the above 'input' call. assert str1 + "\n" + str2 == o.getvalue() # 'input' should just have put str2 to out_str, *without* # the trailing newline, per normal 'input' behavior. assert str2[:-1] == out_str if __name__ == "__main__": print("Module not executable.") bskinn-stdio-mgr-909e0cd/tox.ini000066400000000000000000000021431343037011100166240ustar00rootroot00000000000000[tox] minversion=2.0 isolated_build=True envlist= py3{3,4,5,6,7}-attrs_{17_4,18_2} py36-attrs_{17_1,17_2,17_3,18_1,latest} py33-attrs_17_3 py37-attrs_latest sdist_install [testenv] commands= #python --version #python tests.py -a pytest deps= attrs_17_1: attrs==17.1 attrs_17_2: attrs==17.2 attrs_17_3: attrs==17.3 attrs_17_4: attrs==17.4 attrs_18_1: attrs==18.1 attrs_18_2: attrs==18.2 attrs_latest: attrs attrs_17_1: pytest==3.2.5 attrs_17_{2,3}: pytest==3.4.2 attrs_17_4: pytest attrs_18_{1,2}: pytest attrs_latest: pytest [testenv:win] platform=win basepython= py37: C:\python37\python.exe py36: C:\python36\python.exe py35: C:\python35\python.exe py34: C:\python34\python.exe py33: C:\python33\python.exe [testenv:linux] platform=linux basepython= py37: python3.7 py36: python3.6 py35: python3.5 py34: python3.4 py33: python3.3 [testenv:sdist_install] commands= python -c "import stdio_mgr" [pytest] addopts = -p no:warnings --doctest-glob="README.rst"