From 832cefe0529b46e639a0a70c4f53d8ef39adf35c Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:08:19 -0500 Subject: [PATCH 01/39] Remove Travis CI --- .travis.yml | 19 ------------------- requirements-travis.txt | 7 ------- 2 files changed, 26 deletions(-) delete mode 100644 .travis.yml delete mode 100644 requirements-travis.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e1e276b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -install: - - 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 diff --git a/requirements-travis.txt b/requirements-travis.txt deleted file mode 100644 index 49968d6..0000000 --- a/requirements-travis.txt +++ /dev/null @@ -1,7 +0,0 @@ -attrs>=17 -codecov -flake8==3.4.1 -flake8-docstrings==1.1.0 -pytest -pytest-cov - From d5a57f4bc1732a008eeb6768969f0c8041c0f62b Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:08:29 -0500 Subject: [PATCH 02/39] Fix doctest for Python 3.12 --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c030aa1..4c95e46 100644 --- a/README.rst +++ b/README.rst @@ -84,7 +84,7 @@ upon exiting the managed context. ... warnings.warn("'foo' has no 'bar'") ... err_cap = err_.getvalue() >>> err_cap - "...UserWarning: 'foo' has no 'bar'\n..." + '... UserWarning: \'foo\' has no \'bar\'\n...' **Mock** ``stdin``\ **:** From 394a920d01f9c9fc19c710cff9ef2e9fc9144d8d Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:09:48 -0500 Subject: [PATCH 03/39] Freshen Pythons in tox.ini and add some envs --- tox.ini | 64 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/tox.ini b/tox.ini index d5bb123..6f4602c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,8 +10,6 @@ envlist= [testenv] commands= - #python --version - #python tests.py -a pytest deps= attrs_17_1: attrs==17.1 @@ -31,24 +29,66 @@ deps= [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 + py313: python3.13 + py312: python3.12 + py311: python3.11 + py310: python3.10 [testenv:linux] platform=linux basepython= - py37: python3.7 - py36: python3.6 - py35: python3.5 - py34: python3.4 - py33: python3.3 + py314: python3.14 + py313: python3.13 + py312: python3.12 + py311: python3.11 + py310: python3.10 [testenv:sdist_install] commands= python -c "import stdio_mgr" +[testenv:flake8] +skip_install=False +deps= + -rrequirements-flake8.txt +commands= + flake8 --version + flake8 tests src + +[testenv:black] +skip_install=True +deps=black +commands= + black {posargs} . + +[testenv:isort] +description=Sort, group, and coalesce imports +skip_install=True +deps=isort +commands= + isort --version + isort {posargs} src tests + +[testenv:check] +description=Run isort, black, and flake8 +skip_install=True +deps= + isort + black + -r requirements-flake8.txt +commands= + isort --version + isort {posargs} src tests + black {posargs} . + flake8 --version + flake8 tests src + +[testenv:build] +description=Build project to wheel and sdist +skip_install=True +deps=build +commands= + python -m build + [pytest] addopts = -p no:warnings --doctest-glob="README.rst" From 013679b62fb76698d9b39e9ddcb794b1ba61fe7c Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:18:57 -0500 Subject: [PATCH 04/39] Add flake8 config to tox.ini --- tox.ini | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tox.ini b/tox.ini index 6f4602c..f9667eb 100644 --- a/tox.ini +++ b/tox.ini @@ -92,3 +92,29 @@ commands= [pytest] addopts = -p no:warnings --doctest-glob="README.rst" + +[flake8] +# W503: black formats binary operators to start of line +# RST30[56]: Ignore non-default substitutions/targets; use '$ make html O=-n' to find typos +ignore = W503,RST305,RST306 +show_source = True +max_line_length = 88 +# The 'bright black' (gray) requires ANSI escapes, which require literal ESC +# characters when writing the escapes +format = %(cyan)s%(path)s%(reset)s:%(yellow)s%(row)d%(reset)s:%(green)s%(col)d%(reset)s %(red)s(%(code)s)%(reset)s %(text)s +per_file_ignores = +# S101: pytest uses asserts liberally + tests/*: S101 + conftest.py: S101 +# F401: MANY things imported but unused in __init__.py + __init__.py: F401 + +# flake8-import-order +import-order-style = smarkets +application-import-names = stdio_mgr + +# flake8-rst-docstrings (requires >=0.0.11) +# These declare directives/roles to be treated as 'known', +# in addition to those in 'core' reST. +rst-roles = + attr,class,exc,func,meth,mod,obj,cls From 72e54b140d998ce190bb485b2411009250b986da Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:19:05 -0500 Subject: [PATCH 05/39] Bump black max line length to 88 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 68780f8..1772ed1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["wheel", "setuptools", "attrs>=17.1"] build-backend = "setuptools.build_meta" [tool.black] -line-length = 79 +line-length = 88 include = ''' ( ^/tests/ From 8e209aad56b8d12d11313e995108ee2d641323dd Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:19:16 -0500 Subject: [PATCH 06/39] Restore test code to in-scope for coverage --- .coveragerc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index 4880d9b..4336ad9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,10 +1,10 @@ [run] -omit = - # Don't do coverage on test code + +include = + src/* tests/* - conftest.py - # Don't cover code in the env (Termux only?) +omit = env*/* [report] From d1c472acc2b320650238c0afdafa6b39389f9a09 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:19:44 -0500 Subject: [PATCH 07/39] Modernize requirements files --- requirements-ci.txt | 5 +++++ requirements-dev.txt | 5 +---- requirements-flake8.txt | 12 ++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 requirements-ci.txt create mode 100644 requirements-flake8.txt diff --git a/requirements-ci.txt b/requirements-ci.txt new file mode 100644 index 0000000..e0bd0f0 --- /dev/null +++ b/requirements-ci.txt @@ -0,0 +1,5 @@ +attrs>=17 +pytest +pytest-cov +tox +-e . diff --git a/requirements-dev.txt b/requirements-dev.txt index a51dfa6..ba1426b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,4 @@ attrs>=17 -black -flake8==3.4.1 -flake8-docstrings==1.1.0 ipython pytest pytest-cov @@ -9,4 +6,4 @@ restview tox twine wget --e . \ No newline at end of file +-e . diff --git a/requirements-flake8.txt b/requirements-flake8.txt new file mode 100644 index 0000000..698e823 --- /dev/null +++ b/requirements-flake8.txt @@ -0,0 +1,12 @@ +flake8>=3.7 +flake8-bandit +flake8-black +flake8-bugbear +flake8-builtins +flake8-comprehensions +flake8-docstrings +flake8-eradicate +flake8-import-order +flake8-pie +flake8-rst-docstrings +pep8-naming From 972dd794f6297e4d8d418588fbb6a1d63bbbe3e8 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:19:54 -0500 Subject: [PATCH 08/39] Blacken & fix lints --- src/stdio_mgr/__init__.py | 1 - tests/test_stdiomgr_base.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 937f3a6..14f995d 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -26,7 +26,6 @@ """ - __all__ = ["stdio_mgr"] from .stdio_mgr import stdio_mgr diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index 799d2fa..0468d1a 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -27,7 +27,7 @@ """ -def test_CaptureStdout(): +def test_CaptureStdout(): # noqa: N802 """Confirm stdout capture.""" from stdio_mgr import stdio_mgr @@ -39,20 +39,20 @@ def test_CaptureStdout(): assert s + "\n" == o.getvalue() -def test_CaptureStderr(): +def test_CaptureStderr(): # noqa: N802 """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) + warnings.warn(w, stacklevel=2) # Warning text comes at the end of a line; newline gets added assert w + "\n" in e.getvalue() -def test_DefaultStdin(): +def test_DefaultStdin(): # noqa: N802 """Confirm stdin default-populate.""" from stdio_mgr import stdio_mgr @@ -71,7 +71,7 @@ def test_DefaultStdin(): assert in_str[:-1] == out_str -def test_ManagedStdin(): +def test_ManagedStdin(): # noqa: N802 """Confirm stdin populate within context.""" from stdio_mgr import stdio_mgr From 03c8a0b5f8a5dd469fdec74b65dc12abad76347b Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:20:02 -0500 Subject: [PATCH 09/39] Add workflows --- .github/workflows/all_core_tests.yml | 73 +++++++++++++++++++ .github/workflows/release_platform_tests.yml | 49 +++++++++++++ .github/workflows/release_sdist_test.yml | 71 ++++++++++++++++++ .../workflows/release_testdir_coverage.yml | 38 ++++++++++ 4 files changed, 231 insertions(+) create mode 100644 .github/workflows/all_core_tests.yml create mode 100644 .github/workflows/release_platform_tests.yml create mode 100644 .github/workflows/release_sdist_test.yml create mode 100644 .github/workflows/release_testdir_coverage.yml diff --git a/.github/workflows/all_core_tests.yml b/.github/workflows/all_core_tests.yml new file mode 100644 index 0000000..5c600f2 --- /dev/null +++ b/.github/workflows/all_core_tests.yml @@ -0,0 +1,73 @@ +name: 'PR: Run core tests' + +on: pull_request + +jobs: + all_checks: + name: plus lints, etc. on Python 3.12 + runs-on: ubuntu-latest + if: ${{ !contains(toJson(github.event), '[skip ci]') }} + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: | + requirements-ci.txt + requirements-flake8.txt + + - name: Update pip & setuptools + run: python -m pip install -U pip setuptools + + - name: Install & report CI dependencies + run: | + python -m pip install -U --force-reinstall -r requirements-ci.txt -r requirements-flake8.txt + python --version + pip list + + - name: Run tests + run: | + pytest --cov + tox -e sdist_install + + - name: Lint code + run: tox -e flake8 + + + just_tests: + name: for Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.10', '3.11', '3.13', '3.14'] + if: '!contains(github.event.head_commit.message, ''[skip ci]'')' + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + cache-dependency-path: requirements-ci.txt + + - name: Update pip & setuptools + run: python -m pip install -U pip setuptools + + - name: Install & report CI dependencies + run: | + python -m pip install -U --force-reinstall -r requirements-ci.txt + python --version + pip list + + - name: Run tests + run: | + pytest --cov + tox -e sdist_install diff --git a/.github/workflows/release_platform_tests.yml b/.github/workflows/release_platform_tests.yml new file mode 100644 index 0000000..1ccb6f7 --- /dev/null +++ b/.github/workflows/release_platform_tests.yml @@ -0,0 +1,49 @@ +name: 'RELEASE: Run cross-platform tests' + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + branches: + - stable + +jobs: + just_tests: + name: ${{ matrix.os }} python ${{ matrix.py }} + runs-on: ${{ matrix.os }} + concurrency: + group: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.py }}-${{ github.ref }} + cancel-in-progress: true + if: ${{ !github.event.pull_request.draft && !contains(toJson(github.event), '[skip ci]') }} + strategy: + matrix: + os: ['windows-latest', 'macos-latest'] + py: ['3.10', '3.11', '3.12', '3.13'] + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.py }} + cache: 'pip' + cache-dependency-path: requirements-ci.txt + + - name: Update pip & setuptools + run: python -m pip install -U pip setuptools + + - name: Install & report CI dependencies + run: | + python -m pip install -U --force-reinstall -r requirements-ci.txt + python --version + pip list + + - name: Run tests + run: | + pytest --cov + tox -e sdist_install diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml new file mode 100644 index 0000000..10e76ef --- /dev/null +++ b/.github/workflows/release_sdist_test.yml @@ -0,0 +1,71 @@ +name: 'RELEASE: Check sdist' + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + branches: + - stable + +jobs: + sdist_build_and_check: + name: builds & is testable + runs-on: 'ubuntu-latest' + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + if: ${{ !github.event.pull_request.draft && !contains(toJson(github.event), '[skip ci]')}} + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: | + requirements-dev.txt + requirements-flake8.txt + + - name: Build sdist + run: | + tox -e build + ls -lah dist + + - name: Create sandbox + run: mkdir sandbox + + - name: Unpack sdist in sandbox + run: | + cp dist/*.gz sandbox/ + cd sandbox + tar xvf *.gz + + - name: Create venv + run: | + cd sandbox + python -m venv env + + # Only the dir of the unpacked sdist will have a digit in its name + - name: Store sdist unpack path + run: echo "UNPACK_PATH=$( find sandbox -maxdepth 1 -type d -regex 'sandbox/.+[0-9].+' )" >> $GITHUB_ENV + + - name: Report sdist unpack path + run: echo $UNPACK_PATH + + - name: Install dev req'ts to venv + run: | + source sandbox/env/bin/activate + cd "$UNPACK_PATH" + python -m pip install -r requirements-dev.txt + + - name: Run test suite in sandbox + run: | + source sandbox/env/bin/activate + cd "$UNPACK_PATH" + pytest diff --git a/.github/workflows/release_testdir_coverage.yml b/.github/workflows/release_testdir_coverage.yml new file mode 100644 index 0000000..94fffaf --- /dev/null +++ b/.github/workflows/release_testdir_coverage.yml @@ -0,0 +1,38 @@ +name: 'RELEASE: Ensure all tests ran' + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + branches: + - stable + +jobs: + testdir_coverage: + name: via coverage check + runs-on: 'ubuntu-latest' + if: ${{ !github.event.pull_request.draft && !contains(toJson(github.event), '[skip ci]')}} + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: | + requirements-ci.txt + + - name: Install CI requirements + run: pip install -r requirements-ci.txt + + - name: Run pytest with coverage + run: pytest --cov + + - name: Check 100% test execution + run: coverage report --include="tests/*" --fail-under=100 From 0c6cbec3fee3576d35bc534a264dce8ae6707b23 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:20:31 -0500 Subject: [PATCH 10/39] Temporarily activate sdist test on PRs to main So that I can test it...! --- .github/workflows/release_sdist_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml index 10e76ef..785a5a9 100644 --- a/.github/workflows/release_sdist_test.yml +++ b/.github/workflows/release_sdist_test.yml @@ -9,6 +9,7 @@ on: - ready_for_review branches: - stable + - main jobs: sdist_build_and_check: From 8bd62dbf8c5df48766ad569485571d6008d20ac0 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:24:27 -0500 Subject: [PATCH 11/39] Disable regular tests while fixing sdist to save times --- .github/workflows/all_core_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/all_core_tests.yml b/.github/workflows/all_core_tests.yml index 5c600f2..6aba720 100644 --- a/.github/workflows/all_core_tests.yml +++ b/.github/workflows/all_core_tests.yml @@ -6,7 +6,7 @@ jobs: all_checks: name: plus lints, etc. on Python 3.12 runs-on: ubuntu-latest - if: ${{ !contains(toJson(github.event), '[skip ci]') }} + if: ${{ false && !contains(toJson(github.event), '[skip ci]') }} steps: - name: Check out repo @@ -45,7 +45,7 @@ jobs: strategy: matrix: python: ['3.10', '3.11', '3.13', '3.14'] - if: '!contains(github.event.head_commit.message, ''[skip ci]'')' + if: ${{ false && !contains(toJson(github.event), '[skip ci]') }} steps: - name: Check out repo From 534dd163fcc5d9e8e3b03ffb866cf7e995ee85aa Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:25:34 -0500 Subject: [PATCH 12/39] Add tox install to sdist test job --- .github/workflows/release_sdist_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml index 785a5a9..c67d6bf 100644 --- a/.github/workflows/release_sdist_test.yml +++ b/.github/workflows/release_sdist_test.yml @@ -35,6 +35,7 @@ jobs: - name: Build sdist run: | + pip install tox tox -e build ls -lah dist From 6c6f7529db85b4efd602c074e3233d61df1c1980 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:27:55 -0500 Subject: [PATCH 13/39] Add req'ts files and tox.init to MANIFEST.in --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index de0a14a..35c07fc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,3 @@ include LICENSE.txt README.rst CHANGELOG.md pyproject.toml +include requirements-dev.txt requirements-ci.txt requirements-flake8.txt +include tox.ini From 54764a63a492ebedf93039215a48202b119d7207 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:31:36 -0500 Subject: [PATCH 14/39] Add conftest.py to MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 35c07fc..dedfa43 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE.txt README.rst CHANGELOG.md pyproject.toml include requirements-dev.txt requirements-ci.txt requirements-flake8.txt include tox.ini +include conftest.py From 373d3247a44c4930923ef9719cda5ca61c405ccd Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:33:53 -0500 Subject: [PATCH 15/39] Re-enable tests in CI --- .github/workflows/all_core_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/all_core_tests.yml b/.github/workflows/all_core_tests.yml index 6aba720..61d9344 100644 --- a/.github/workflows/all_core_tests.yml +++ b/.github/workflows/all_core_tests.yml @@ -6,7 +6,7 @@ jobs: all_checks: name: plus lints, etc. on Python 3.12 runs-on: ubuntu-latest - if: ${{ false && !contains(toJson(github.event), '[skip ci]') }} + if: ${{ !contains(toJson(github.event), '[skip ci]') }} steps: - name: Check out repo @@ -45,7 +45,7 @@ jobs: strategy: matrix: python: ['3.10', '3.11', '3.13', '3.14'] - if: ${{ false && !contains(toJson(github.event), '[skip ci]') }} + if: ${{ !contains(toJson(github.event), '[skip ci]') }} steps: - name: Check out repo From bf7f42c3afefcdcb8d491d42503096fb0dd67ca7 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:34:33 -0500 Subject: [PATCH 16/39] Turn on all workflows for `main` to run a full test --- .github/workflows/release_platform_tests.yml | 1 + .github/workflows/release_testdir_coverage.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/release_platform_tests.yml b/.github/workflows/release_platform_tests.yml index 1ccb6f7..9b8164e 100644 --- a/.github/workflows/release_platform_tests.yml +++ b/.github/workflows/release_platform_tests.yml @@ -9,6 +9,7 @@ on: - ready_for_review branches: - stable + - main jobs: just_tests: diff --git a/.github/workflows/release_testdir_coverage.yml b/.github/workflows/release_testdir_coverage.yml index 94fffaf..a6ff303 100644 --- a/.github/workflows/release_testdir_coverage.yml +++ b/.github/workflows/release_testdir_coverage.yml @@ -9,6 +9,7 @@ on: - ready_for_review branches: - stable + - main jobs: testdir_coverage: From bdcccdd4f4d93d37fbcb09aeeaca55ff4f207f75 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:37:16 -0500 Subject: [PATCH 17/39] Remove 'module not executable' stanzas --- src/stdio_mgr/stdio_mgr.py | 4 ---- tests/test_stdiomgr_base.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/stdio_mgr/stdio_mgr.py b/src/stdio_mgr/stdio_mgr.py index a4a8616..b413b70 100644 --- a/src/stdio_mgr/stdio_mgr.py +++ b/src/stdio_mgr/stdio_mgr.py @@ -195,7 +195,3 @@ def stdio_mgr(in_str=""): new_stdin.close() new_stdout.close() new_stderr.close() - - -if __name__ == "__main__": # pragma: no cover - print("Module not executable.") diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index 0468d1a..88277a2 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -102,7 +102,3 @@ def test_ManagedStdin(): # noqa: N802 # '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.") From 1fada0eeba011498c3f974f1392bb494c5d8d5da Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 00:38:42 -0500 Subject: [PATCH 18/39] Return cross-platform & sdist workflows to stable only --- .github/workflows/release_platform_tests.yml | 1 - .github/workflows/release_sdist_test.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/release_platform_tests.yml b/.github/workflows/release_platform_tests.yml index 9b8164e..1ccb6f7 100644 --- a/.github/workflows/release_platform_tests.yml +++ b/.github/workflows/release_platform_tests.yml @@ -9,7 +9,6 @@ on: - ready_for_review branches: - stable - - main jobs: just_tests: diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml index c67d6bf..09dd07f 100644 --- a/.github/workflows/release_sdist_test.yml +++ b/.github/workflows/release_sdist_test.yml @@ -9,7 +9,6 @@ on: - ready_for_review branches: - stable - - main jobs: sdist_build_and_check: From 1e93b7ce87e5947acf3a6068f19768111703d792 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 10:50:13 -0500 Subject: [PATCH 19/39] Update tox version matrix --- tox.ini | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tox.ini b/tox.ini index f9667eb..2f1c6f9 100644 --- a/tox.ini +++ b/tox.ini @@ -2,30 +2,32 @@ 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 + py3{10,11,12,13,14}-attrs_latest + py312-attrs_{17_1,17_2,17_3,18_1,18_2} + py312-attrs_{18,19,20,21,22,23,24}_x sdist_install [testenv] commands= pytest deps= + pytest + 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_18_x: attrs<19 + attrs_19_x: attrs<20 + attrs_20_x: attrs<21 + attrs_21_x: attrs<22 + attrs_22_x: attrs<23 + attrs_23_x: attrs<24 + attrs_24_x: attrs<25 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= From b5ded819971fc27a4ad2c1f33543c4de0cc2230d Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 16:01:04 -0500 Subject: [PATCH 20/39] Switch testdir_coverage to only-stable & sdist-test to main-also --- .github/workflows/release_sdist_test.yml | 1 + .github/workflows/release_testdir_coverage.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml index 09dd07f..c67d6bf 100644 --- a/.github/workflows/release_sdist_test.yml +++ b/.github/workflows/release_sdist_test.yml @@ -9,6 +9,7 @@ on: - ready_for_review branches: - stable + - main jobs: sdist_build_and_check: diff --git a/.github/workflows/release_testdir_coverage.yml b/.github/workflows/release_testdir_coverage.yml index a6ff303..94fffaf 100644 --- a/.github/workflows/release_testdir_coverage.yml +++ b/.github/workflows/release_testdir_coverage.yml @@ -9,7 +9,6 @@ on: - ready_for_review branches: - stable - - main jobs: testdir_coverage: From d9589085627c38c363559dd5cad2fb6cd2e07a54 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 17:22:12 -0500 Subject: [PATCH 21/39] Start setup.py conversion to pyproject.toml [skip ci] --- pyproject.toml | 9 ++++++++- setup.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1772ed1..fcb06cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,14 @@ [build-system] -requires = ["wheel", "setuptools", "attrs>=17.1"] +requires = [ + "wheel", + "setuptools>77", +] build-backend = "setuptools.build_meta" +[project] +name = "stdio-mgr" +description = "Context manager for mocking/wrapping stdin/stdout/stderr" + [tool.black] line-length = 88 include = ''' diff --git a/setup.py b/setup.py index 5050682..25d8f27 100644 --- a/setup.py +++ b/setup.py @@ -45,3 +45,50 @@ def readme(): "Development Status :: 5 - Production/Stable", ], ) + + +# ##### BREAK + +import re +from pathlib import Path +from typing import Any, cast + +from setuptools import setup + +NAME = "stdio-mgr" + +exec_ns: dict[str, Any] = {} +exec(Path("src", "sphobjinv", "version.py").read_text(encoding="utf-8"), exec_ns) +__version__ = cast(str, exec_ns["__version__"]) + +version_override: str | None = None + + +def readme(): + content = Path("README.md").read_text(encoding="utf-8") + + new_ver = version_override if version_override else __version__ + + # Helper function + def content_update(content, pattern, sub): + return re.sub(pattern, sub, content, flags=re.M | re.I) + + # Docs reference updates to current release version, for PyPI + # This one gets the badge image + content = content_update( + content, r"(?<=/readthedocs/{0}/)\S+?(?=\.svg$)".format(NAME), "v" + new_ver + ) + + # This one gets the RtD links + content = content_update( + content, r"(?<={0}\.readthedocs\.io/en/)\S+?(?=/)".format(NAME), "v" + new_ver + ) + + return content + + +setup( + name=NAME, + long_description=readme(), + long_description_content_type="text/markdown", +) From ca6cb73a6b884aed13bd32fb54db72480396865a Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:24:06 -0500 Subject: [PATCH 22/39] Finish setup.py/pyproject.toml conversions --- pyproject.toml | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 57 +++--------------------------------------------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fcb06cb..ac9b3e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,65 @@ build-backend = "setuptools.build_meta" [project] name = "stdio-mgr" description = "Context manager for mocking/wrapping stdin/stdout/stderr" +authors = [ + { name = "Brian Skinn", email = "brian.skinn@gmail.com" }, +] +license = "MIT" +license-files = [ + "LICENSE.txt", +] +classifiers = [ + "Natural Language :: English", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Testing", + "Development Status :: 5 - Production/Stable", +] +keywords = [ + "stdin", + "stdout", + "stderr", + "mock", +] +requires-python = ">=3.10" +dependencies = [ + "attrs>=17.1", +] +dynamic = [ + "version", + "readme", +] + +[project.urls] +Homepage = "https://github.com/bskinn/stdio-mgr" +Changelog = "https://github.com/bskinn/stdio-mgr/blob/main/CHANGELOG.md" +Donate = "https://github.com/sponsors/bskinn" + +[tool.setuptools] +platforms = [ + "any", +] +include-package-data = false + +[tool.setuptools.dynamic] +version = {attr = "stdio_mgr.version.__version__"} + +[tool.setuptools.package-dir] +"" = "src" + +[tool.setuptools.packages.find] +where = [ + "src", +] +namespaces = false [tool.black] line-length = 88 diff --git a/setup.py b/setup.py index 25d8f27..f9c5b89 100644 --- a/setup.py +++ b/setup.py @@ -1,54 +1,3 @@ -import 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", - ], -) - - -# ##### BREAK - import re from pathlib import Path from typing import Any, cast @@ -58,14 +7,14 @@ def readme(): NAME = "stdio-mgr" exec_ns: dict[str, Any] = {} -exec(Path("src", "sphobjinv", "version.py").read_text(encoding="utf-8"), exec_ns) +exec(Path("src", "stdio_mgr", "version.py").read_text(encoding="utf-8"), exec_ns) __version__ = cast(str, exec_ns["__version__"]) version_override: str | None = None def readme(): - content = Path("README.md").read_text(encoding="utf-8") + content = Path("README.rst").read_text(encoding="utf-8") new_ver = version_override if version_override else __version__ @@ -90,5 +39,5 @@ def content_update(content, pattern, sub): setup( name=NAME, long_description=readme(), - long_description_content_type="text/markdown", + long_description_content_type="text/x-rst", ) From 7d8f8856da667df959574fcc71e3065f37b50c7f Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:26:22 -0500 Subject: [PATCH 23/39] Refactor __version__ to new version.py --- src/stdio_mgr/__init__.py | 6 ++---- src/stdio_mgr/version.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/stdio_mgr/version.py diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 14f995d..27675dc 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -28,7 +28,5 @@ __all__ = ["stdio_mgr"] -from .stdio_mgr import stdio_mgr - - -__version__ = "1.0.1" +from stdio_mgr import stdio_mgr +from stdio_mgr.version import __version__ diff --git a/src/stdio_mgr/version.py b/src/stdio_mgr/version.py new file mode 100644 index 0000000..b5ecf8c --- /dev/null +++ b/src/stdio_mgr/version.py @@ -0,0 +1,29 @@ +r"""``stdio_mgr`` *version 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** + 28 Nov 2025 + +**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** + +""" + +__version__ = "1.0.2.dev0" From 850e6cece5a43f7c4de9c323ef2d5f4234f15792 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:26:33 -0500 Subject: [PATCH 24/39] Add flake8-absolute-import to flake8 req'ts --- requirements-flake8.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-flake8.txt b/requirements-flake8.txt index 698e823..47d68ac 100644 --- a/requirements-flake8.txt +++ b/requirements-flake8.txt @@ -1,4 +1,5 @@ flake8>=3.7 +flake8-absolute-import flake8-bandit flake8-black flake8-bugbear From cd4a9d4acf2ee67e4df99bb47486677b82f7efa2 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:30:53 -0500 Subject: [PATCH 25/39] Fix primary stdio_mgr import --- src/stdio_mgr/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 27675dc..e39d8f6 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -26,7 +26,5 @@ """ -__all__ = ["stdio_mgr"] - -from stdio_mgr import stdio_mgr +from stdio_mgr.stdio_mgr import stdio_mgr from stdio_mgr.version import __version__ From 4d5a7df0c3ed51c40e64838bed1aada9ca8fe76f Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:31:08 -0500 Subject: [PATCH 26/39] Refactor imports --- src/stdio_mgr/__init__.py | 2 ++ src/stdio_mgr/stdio_mgr.py | 11 ++++------- tests/test_stdiomgr_base.py | 13 ++++--------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index e39d8f6..873ec56 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -28,3 +28,5 @@ from stdio_mgr.stdio_mgr import stdio_mgr from stdio_mgr.version import __version__ + +__all__ = ["stdio_mgr"] diff --git a/src/stdio_mgr/stdio_mgr.py b/src/stdio_mgr/stdio_mgr.py index b413b70..83a4d03 100644 --- a/src/stdio_mgr/stdio_mgr.py +++ b/src/stdio_mgr/stdio_mgr.py @@ -26,8 +26,9 @@ """ +import sys from contextlib import contextmanager -from io import StringIO, TextIOBase +from io import SEEK_END, SEEK_SET, StringIO, TextIOBase import attr @@ -67,8 +68,6 @@ class TeeStdin(StringIO): """ - 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)) @@ -128,9 +127,9 @@ def append(self, text): """ pos = self.tell() - self.seek(0, self.SEEK_END) + self.seek(0, SEEK_END) retval = self.write(text) - self.seek(pos, self.SEEK_SET) + self.seek(pos, SEEK_SET) return retval @@ -172,8 +171,6 @@ def stdio_mgr(in_str=""): initially empty. """ - import sys - old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index 88277a2..5f4b824 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -26,11 +26,13 @@ """ +import warnings + +from stdio_mgr import stdio_mgr + def test_CaptureStdout(): # noqa: N802 """Confirm stdout capture.""" - from stdio_mgr import stdio_mgr - with stdio_mgr() as (i, o, e): s = "test str" print(s) @@ -41,9 +43,6 @@ def test_CaptureStdout(): # noqa: N802 def test_CaptureStderr(): # noqa: N802 """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, stacklevel=2) @@ -54,8 +53,6 @@ def test_CaptureStderr(): # noqa: N802 def test_DefaultStdin(): # noqa: N802 """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): @@ -73,8 +70,6 @@ def test_DefaultStdin(): # noqa: N802 def test_ManagedStdin(): # noqa: N802 """Confirm stdin populate within context.""" - from stdio_mgr import stdio_mgr - str1 = "This is a test string." str2 = "This is another test string.\n" From 48cdfb41b52635eedd7f7627cb3d1d553bf4f10f Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Fri, 28 Nov 2025 22:36:09 -0500 Subject: [PATCH 27/39] Restore sdist test to only PRs to stable --- .github/workflows/release_sdist_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release_sdist_test.yml b/.github/workflows/release_sdist_test.yml index c67d6bf..09dd07f 100644 --- a/.github/workflows/release_sdist_test.yml +++ b/.github/workflows/release_sdist_test.yml @@ -9,7 +9,6 @@ on: - ready_for_review branches: - stable - - main jobs: sdist_build_and_check: From b3770f6e80f2846916c3743979f237fe8619598a Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:04:14 -0500 Subject: [PATCH 28/39] Add explanatory comment about pytest opts in tox.ini --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 2f1c6f9..2bd3e40 100644 --- a/tox.ini +++ b/tox.ini @@ -93,6 +93,8 @@ commands= python -m build [pytest] +# Disable pytest processing of warnings since we want to capture them in stderr +# Must specify the README for doctesting addopts = -p no:warnings --doctest-glob="README.rst" [flake8] From b471f6d2e2de69d7451f2202b845039aa8b02edd Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:04:33 -0500 Subject: [PATCH 29/39] Update CHANGELOG --- CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ee248..c097aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### [Unreleased] -... +#### Internal + +- Convert project build config (mostly) from `setup.py` to `pyproject.toml` + ([#108]). + - The dynamic README stays in `setup.py`. +- Convert CI to GitHub Actions and diversify ([#108]). + - Ubuntu tests across Pythons on every PR. + - Cross-platform tests across Pythons on PRs to `stable`. + - Ensuring testability of sdist in PRs to `stable`. + - Augment `MANIFEST.in` until tests run successfully on unpacked sdist. + - Checking all tests ran in PRs to `stable`. + - `[skip ci]` implemented in all. + +- Refactor `__version__` to new `version.py` ([#108]). + +- Set up `black`, `flake`, `isort` with `tox` envs and run/fix ([#108]). + +- Update Python & deps versions in `tox` env matrix ([#108]). + ### [1.0.1] - 2019-02-11 @@ -29,3 +47,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. which adds content to the end of the stream without changing the current seek position. + +[#108]: https://github.com/bskinn/stdio-mgr/pull/108 From 9e1597020660ff558ad6d9b84a4d3d2155a98e33 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:12:48 -0500 Subject: [PATCH 30/39] Run core CI tests on commits on `main`, also --- .github/workflows/all_core_tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/all_core_tests.yml b/.github/workflows/all_core_tests.yml index 61d9344..937bedc 100644 --- a/.github/workflows/all_core_tests.yml +++ b/.github/workflows/all_core_tests.yml @@ -1,6 +1,10 @@ name: 'PR: Run core tests' -on: pull_request +on: + pull_request: + push: + branches: + - main jobs: all_checks: From b5734cd37b157e38ada5772f248905e5ad5797f5 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:14:26 -0500 Subject: [PATCH 31/39] Rename README to .md --- README.rst => README.md | 0 setup.py | 4 ++-- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename README.rst => README.md (100%) diff --git a/README.rst b/README.md similarity index 100% rename from README.rst rename to README.md diff --git a/setup.py b/setup.py index f9c5b89..344300d 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def readme(): - content = Path("README.rst").read_text(encoding="utf-8") + content = Path("README.md").read_text(encoding="utf-8") new_ver = version_override if version_override else __version__ @@ -39,5 +39,5 @@ def content_update(content, pattern, sub): setup( name=NAME, long_description=readme(), - long_description_content_type="text/x-rst", + long_description_content_type="text/markdown", ) diff --git a/tox.ini b/tox.ini index 2bd3e40..99c671e 100644 --- a/tox.ini +++ b/tox.ini @@ -95,7 +95,7 @@ commands= [pytest] # Disable pytest processing of warnings since we want to capture them in stderr # Must specify the README for doctesting -addopts = -p no:warnings --doctest-glob="README.rst" +addopts = -p no:warnings --doctest-glob="README.md" [flake8] # W503: black formats binary operators to start of line From f2136b49f2d2362db950f762eb7cfb5b0e294d65 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:43:42 -0500 Subject: [PATCH 32/39] Convert README to Markdown --- README.md | 341 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 180 insertions(+), 161 deletions(-) diff --git a/README.md b/README.md index 4c95e46..87c2867 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,208 @@ -stdio Manager: Context manager for mocking/wrapping ``stdin``/``stdout``/``stderr`` -=================================================================================== +## stdio-mgr: Context manager for mocking/wrapping `stdin`/`stdout`/`stderr` -**Current Development Version:** +#### Current Development Version: -.. image:: https://travis-ci.org/bskinn/stdio-mgr.svg?branch=dev - :target: https://travis-ci.org/bskinn/stdio-mgr +[![GitHub Workflow Status][workflow badge]][workflow link target] -.. 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 -**Most Recent Stable Release:** +[![PyPI Version][pypi badge]][pypi link target] +![Python Versions][python versions badge] -.. image:: https://img.shields.io/pypi/v/stdio_mgr.svg - :target: https://pypi.org/project/stdio-mgr +#### Info -.. image:: https://img.shields.io/pypi/pyversions/stdio-mgr.svg +[![MIT License][license badge]][license link target] +[![black formatted][black badge]][black link target] +[![PePY stats][pepy badge]][pepy link target] -**Info:** +---- + +### Have a CLI Python application? + +_Want to automate testing of the actual console input & output of your +user-facing components?_ + +#### `stdio-mgr` can help + +`stdio-mgr` is a context manager for mocking/managing all three standard I/O +streams: `stdout`, `stderr`, and `stdin`. While some functionality here is more +or less duplicative of `redirect_stdout` and `redirect_stderr` in +`contextlib` [within the standard library][stdlib 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`. + +##### First, install: -.. image:: https://img.shields.io/github/license/mashape/apistatus.svg - :target: https://github.com/bskinn/stdio-mgr/blob/master/LICENSE.txt +```bash +$ pip install stdio-mgr +``` -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black +Then use! + +All of the below examples assume `stdio_mgr` has already been imported via: + +```py +from stdio_mgr import stdio_mgr +``` + +##### Mock `stdout`: + +```py +>>> 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][print 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`: + +```py +>>> 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: + +```py +>>> 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: + +```py +>>> 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. | +=============================== + +``` ---- -**Have a CLI Python application?** +Available on [PyPI][pypi link target] (`pip install stdio-mgr`). -**Want to automate testing of the actual console input & output -of your user-facing components?** +Source on [GitHub][gh repo]. Bug reports and feature requests are welcomed at +the [Issues][gh issues] page there. -`stdio Manager` can help. +Copyright \(c) 2018-2019 Brian Skinn -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``. +The `stdio-mgr` documentation (currently docstrings and README) is licensed +under a [Creative Commons Attribution 4.0 International License][cc-by] (CC-BY). +The `stdio-mgr` codebase is released under the [MIT License]. See +[`LICENSE.txt`] for full license terms. -**First, install:** -.. code:: +[`LICENSE.txt`]: https://github.com/bskinn/flake8-absolute-import/blob/main/LICENSE.txt - $ pip install stdio-mgr +[black badge]: https://img.shields.io/badge/code%20style-black-000000.svg +[black link target]: https://github.com/psf/black -Then use! +[cc-by]: http://creativecommons.org/licenses/by/4.0/ -All of the below examples assume ``stdio_mgr`` has already -been imported via: +[gh issues]: https://github.com/bskinn/stdio-mgr/issues +[gh repo]: https://github.com/bskinn/stdio-mgr -.. code:: +[license badge]: https://img.shields.io/github/license/mashape/apistatus.svg +[license link target]: https://github.com/bskinn/stdio-mgr/blob/stable/LICENSE.txt - 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. | - =============================== +[MIT License]: https://opensource.org/licenses/MIT ----- +[pepy badge]: https://pepy.tech/badge/stdio-mgr/month +[pepy link target]: https://pepy.tech/projects/stdio-mgr?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=1.0.1%2C1.0.1.1 -Available on `PyPI `__ -(``pip install stdio-mgr``). +[print newline]: https://docs.python.org/3/library/functions.html#print -Source on `GitHub `__. Bug reports -and feature requests are welcomed at the -`Issues `__ page there. +[pypi badge]: https://img.shields.io/pypi/v/stdio-mgr.svg?logo=pypi +[pypi link target]: https://pypi.org/project/stdio-mgr -Copyright \(c) 2018-2019 Brian Skinn +[python versions badge]: https://img.shields.io/pypi/pyversions/stdio-mgr.svg?logo=python -License: The MIT License. See `LICENSE.txt `__ -for full license terms. +[stdlib redirect_stdout]: https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout +[workflow badge]: https://img.shields.io/github/actions/workflow/status/bskinn/stdio-mgr/all_core_tests.yml?branch=main&logo=github +[workflow link target]: https://github.com/bskinn/stdio-mgr/actions From b63fa743369cb9c68123dd7b902a9dbf028fcbfb Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:49:01 -0500 Subject: [PATCH 33/39] Update docstring license blocks in src/test files --- src/stdio_mgr/__init__.py | 6 +++++- src/stdio_mgr/stdio_mgr.py | 6 +++++- src/stdio_mgr/version.py | 6 +++++- tests/test_stdiomgr_base.py | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 873ec56..225e01e 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -20,7 +20,11 @@ See README.rst at the GitHub repository **License** - The MIT License; see |license_txt|_ for full license terms + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. **Members** diff --git a/src/stdio_mgr/stdio_mgr.py b/src/stdio_mgr/stdio_mgr.py index 83a4d03..602c3d6 100644 --- a/src/stdio_mgr/stdio_mgr.py +++ b/src/stdio_mgr/stdio_mgr.py @@ -20,7 +20,11 @@ See README.rst at the GitHub repository **License** - The MIT License; see |license_txt|_ for full license terms + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. **Members** diff --git a/src/stdio_mgr/version.py b/src/stdio_mgr/version.py index b5ecf8c..d018fce 100644 --- a/src/stdio_mgr/version.py +++ b/src/stdio_mgr/version.py @@ -20,7 +20,11 @@ See README.rst at the GitHub repository **License** - The MIT License; see |license_txt|_ for full license terms + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. **Members** diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index 5f4b824..9efe18c 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -20,7 +20,11 @@ See README.rst at the GitHub repository **License** - The MIT License; see |license_txt|_ for full license terms + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. **Members** From 2b97879b6a4b1b69519e7641ce0b6bf530552fdc Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:52:19 -0500 Subject: [PATCH 34/39] Update classifiers --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ac9b3e7..f418b0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,9 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", - "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Testing :: Mocking", + "Topic :: Software Development :: User Interfaces", "Development Status :: 5 - Production/Stable", ] keywords = [ From d1852e406653e9909c9b948c7a96c7d1f862f3af Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:55:26 -0500 Subject: [PATCH 35/39] Set version to v1.0.1.1 target --- src/stdio_mgr/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdio_mgr/version.py b/src/stdio_mgr/version.py index d018fce..69bea69 100644 --- a/src/stdio_mgr/version.py +++ b/src/stdio_mgr/version.py @@ -30,4 +30,4 @@ """ -__version__ = "1.0.2.dev0" +__version__ = "1.0.1.1" From 7d6ded59d7ff50d1b1776b258f46d724a858855f Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 00:57:10 -0500 Subject: [PATCH 36/39] Update copyright range end years --- LICENSE.txt | 2 +- README.md | 2 +- conftest.py | 2 +- src/stdio_mgr/__init__.py | 2 +- src/stdio_mgr/stdio_mgr.py | 2 +- src/stdio_mgr/version.py | 2 +- tests/test_stdiomgr_base.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 074cfdb..51d6dad 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2019 Brian Skinn +Copyright (c) 2018-2025 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 diff --git a/README.md b/README.md index 87c2867..24775ef 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Available on [PyPI][pypi link target] (`pip install stdio-mgr`). Source on [GitHub][gh repo]. Bug reports and feature requests are welcomed at the [Issues][gh issues] page there. -Copyright \(c) 2018-2019 Brian Skinn +Copyright \(c) 2018-2025 Brian Skinn The `stdio-mgr` documentation (currently docstrings and README) is licensed under a [Creative Commons Attribution 4.0 International License][cc-by] (CC-BY). diff --git a/conftest.py b/conftest.py index 70bcd56..d12554d 100644 --- a/conftest.py +++ b/conftest.py @@ -11,7 +11,7 @@ 6 Feb 2019 **Copyright** - \(c) Brian Skinn 2018-2019 + \(c) Brian Skinn 2018-2025 **Source Repository** http://www.github.com/bskinn/stdio-mgr diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 225e01e..3a451c4 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -11,7 +11,7 @@ 24 Mar 2018 **Copyright** - \(c) Brian Skinn 2018-2019 + \(c) Brian Skinn 2018-2025 **Source Repository** http://www.github.com/bskinn/stdio-mgr diff --git a/src/stdio_mgr/stdio_mgr.py b/src/stdio_mgr/stdio_mgr.py index 602c3d6..4098c3b 100644 --- a/src/stdio_mgr/stdio_mgr.py +++ b/src/stdio_mgr/stdio_mgr.py @@ -11,7 +11,7 @@ 24 Mar 2018 **Copyright** - \(c) Brian Skinn 2018-2019 + \(c) Brian Skinn 2018-2025 **Source Repository** http://www.github.com/bskinn/stdio-mgr diff --git a/src/stdio_mgr/version.py b/src/stdio_mgr/version.py index 69bea69..d4f85f5 100644 --- a/src/stdio_mgr/version.py +++ b/src/stdio_mgr/version.py @@ -11,7 +11,7 @@ 28 Nov 2025 **Copyright** - \(c) Brian Skinn 2018-2019 + \(c) Brian Skinn 2018-2025 **Source Repository** http://www.github.com/bskinn/stdio-mgr diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index 9efe18c..b12c9da 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -11,7 +11,7 @@ 24 Mar 2018 **Copyright** - \(c) Brian Skinn 2018-2019 + \(c) Brian Skinn 2018-2025 **Source Repository** http://www.github.com/bskinn/stdio-mgr From 1f7a68b6481dc83e5c6fa2b2dff0540255e4cb52 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 01:00:20 -0500 Subject: [PATCH 37/39] Clean up some lingering README.rst points of use --- MANIFEST.in | 2 +- conftest.py | 2 +- src/stdio_mgr/__init__.py | 2 +- src/stdio_mgr/stdio_mgr.py | 2 +- src/stdio_mgr/version.py | 2 +- tests/test_stdiomgr_base.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index dedfa43..9cc84b4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE.txt README.rst CHANGELOG.md pyproject.toml +include LICENSE.txt README.md CHANGELOG.md pyproject.toml include requirements-dev.txt requirements-ci.txt requirements-flake8.txt include tox.ini include conftest.py diff --git a/conftest.py b/conftest.py index d12554d..1901fc5 100644 --- a/conftest.py +++ b/conftest.py @@ -17,7 +17,7 @@ http://www.github.com/bskinn/stdio-mgr **Documentation** - See README.rst at the GitHub repository + See README.md at the GitHub repository **License** The MIT License; see |license_txt|_ for full license terms diff --git a/src/stdio_mgr/__init__.py b/src/stdio_mgr/__init__.py index 3a451c4..c7a4358 100644 --- a/src/stdio_mgr/__init__.py +++ b/src/stdio_mgr/__init__.py @@ -17,7 +17,7 @@ http://www.github.com/bskinn/stdio-mgr **Documentation** - See README.rst at the GitHub repository + See README.md at the GitHub repository **License** Code: `MIT License`_ diff --git a/src/stdio_mgr/stdio_mgr.py b/src/stdio_mgr/stdio_mgr.py index 4098c3b..e94046e 100644 --- a/src/stdio_mgr/stdio_mgr.py +++ b/src/stdio_mgr/stdio_mgr.py @@ -17,7 +17,7 @@ http://www.github.com/bskinn/stdio-mgr **Documentation** - See README.rst at the GitHub repository + See README.md at the GitHub repository **License** Code: `MIT License`_ diff --git a/src/stdio_mgr/version.py b/src/stdio_mgr/version.py index d4f85f5..5da15fc 100644 --- a/src/stdio_mgr/version.py +++ b/src/stdio_mgr/version.py @@ -17,7 +17,7 @@ http://www.github.com/bskinn/stdio-mgr **Documentation** - See README.rst at the GitHub repository + See README.md at the GitHub repository **License** Code: `MIT License`_ diff --git a/tests/test_stdiomgr_base.py b/tests/test_stdiomgr_base.py index b12c9da..e7ef36f 100644 --- a/tests/test_stdiomgr_base.py +++ b/tests/test_stdiomgr_base.py @@ -17,7 +17,7 @@ http://www.github.com/bskinn/stdio-mgr **Documentation** - See README.rst at the GitHub repository + See README.md at the GitHub repository **License** Code: `MIT License`_ From f709e749e4745b3e5e41608a07753c2a9a177499 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 01:03:23 -0500 Subject: [PATCH 38/39] Don't use h5 in .md, apparently Either renders funny, or doesn't render at all, depending on the Markdown flavor. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 24775ef..f82931d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ 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: +**First, install:** ```bash $ pip install stdio-mgr @@ -46,7 +46,7 @@ All of the below examples assume `stdio_mgr` has already been imported via: from stdio_mgr import stdio_mgr ``` -##### Mock `stdout`: +**Mock `stdout`:** ```py >>> with stdio_mgr() as (in_, out_, err_): @@ -66,7 +66,7 @@ As currently implemented, `stdio_mgr` closes all three mocked streams upon exiting the managed context. -##### Mock `stderr`: +**Mock `stderr`:** ```py >>> import warnings @@ -79,7 +79,7 @@ exiting the managed context. ``` -##### Mock `stdin`: +**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` From a29d625c747bf91d87cbeb53f590e47f0e0dfa60 Mon Sep 17 00:00:00 2001 From: Brian Skinn Date: Sat, 29 Nov 2025 01:12:33 -0500 Subject: [PATCH 39/39] Add flake8-isort to flake8 req'ts --- requirements-flake8.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-flake8.txt b/requirements-flake8.txt index 47d68ac..5d40486 100644 --- a/requirements-flake8.txt +++ b/requirements-flake8.txt @@ -8,6 +8,7 @@ flake8-comprehensions flake8-docstrings flake8-eradicate flake8-import-order +flake8-isort flake8-pie flake8-rst-docstrings pep8-naming