Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-wurlitzer for openSUSE:Factory checked in at 2022-04-16 00:14:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-wurlitzer (Old) and /work/SRC/openSUSE:Factory/.python-wurlitzer.new.1941 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-wurlitzer" Sat Apr 16 00:14:25 2022 rev:6 rq:970225 version:3.0.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-wurlitzer/python-wurlitzer.changes 2020-07-24 09:57:40.773510058 +0200 +++ /work/SRC/openSUSE:Factory/.python-wurlitzer.new.1941/python-wurlitzer.changes 2022-04-16 00:14:44.901689087 +0200 @@ -1,0 +2,7 @@ +Thu Apr 14 09:47:12 UTC 2022 - pgaj...@suse.com + +- version update to 3.0.2 + * no upstream changelog file found +- python-mock is not required for build + +------------------------------------------------------------------- Old: ---- wurlitzer-2.0.1.tar.gz New: ---- wurlitzer-3.0.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-wurlitzer.spec ++++++ --- /var/tmp/diff_new_pack.UWMJjw/_old 2022-04-16 00:14:45.473689851 +0200 +++ /var/tmp/diff_new_pack.UWMJjw/_new 2022-04-16 00:14:45.477689856 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-wurlitzer # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,13 +19,12 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-wurlitzer -Version: 2.0.1 +Version: 3.0.2 Release: 0 Summary: Python package to capture C-level output in context managers License: MIT URL: https://github.com/minrk/wurlitzer Source: https://files.pythonhosted.org/packages/source/w/wurlitzer/wurlitzer-%{version}.tar.gz -BuildRequires: %{python_module mock} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} BuildRequires: fdupes ++++++ wurlitzer-2.0.1.tar.gz -> wurlitzer-3.0.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/.bumpversion.cfg new/wurlitzer-3.0.2/.bumpversion.cfg --- old/wurlitzer-2.0.1/.bumpversion.cfg 2020-07-06 10:45:30.000000000 +0200 +++ new/wurlitzer-3.0.2/.bumpversion.cfg 1970-01-01 01:00:00.000000000 +0100 @@ -1,19 +0,0 @@ -[bumpversion] -current_version = 2.0.1 -parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z0-9]+))? -tag_name = {new_version} -allow_dirty = True -commit = True -tag = False -serialize = - {major}.{minor}.{patch}.{release} - {major}.{minor}.{patch} - -[bumpversion:file:wurlitzer.py] - -[bumpversion:part:release] -optional_value = stable -values = - dev - stable - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/.gitignore new/wurlitzer-3.0.2/.gitignore --- old/wurlitzer-2.0.1/.gitignore 2018-05-20 19:43:32.000000000 +0200 +++ new/wurlitzer-3.0.2/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,64 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -MANIFEST -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.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 -.pytest_cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -#Ipython Notebook -.ipynb_checkpoints diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/.travis.yml new/wurlitzer-3.0.2/.travis.yml --- old/wurlitzer-2.0.1/.travis.yml 2019-06-13 10:48:40.000000000 +0200 +++ new/wurlitzer-3.0.2/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,43 +0,0 @@ -language: python -python: - - 2.7 - - 3.4 - - 3.5 - - 3.6 - - nightly -branches: - only: - - master -before_install: - - | - # setup mac virtualenv - if [[ $(uname) == "Darwin" ]]; then - brew install python - python3 -m pip install virtualenv - virtualenv -p $(which python$PY) ./test-env - source ./test-env/bin/activate - fi -install: - - pip install --upgrade setuptools pip - - pip install --upgrade . -r dev-requirements.txt - - pip freeze -script: - - py.test --cov wurlitzer test.py -after_success: - - codecov -env: - global: - - HOMEBREW_NO_AUTO_UPDATE=1 -matrix: - include: - - os: osx - language: generic - env: - - PY=2 - - os: osx - language: generic - env: - - PY=3 - - os: linux - dist: xenial - python: 3.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/CHANGELOG.md new/wurlitzer-3.0.2/CHANGELOG.md --- old/wurlitzer-2.0.1/CHANGELOG.md 2020-07-06 10:45:14.000000000 +0200 +++ new/wurlitzer-3.0.2/CHANGELOG.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,83 +0,0 @@ -### Changelog - -All notable changes to this project will be documented in this file. Dates are displayed in UTC. - -Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - -#### [Unreleased](https://github.com/minrk/wurlitzer/compare/2.0.1...HEAD) - -#### [2.0.1](https://github.com/minrk/wurlitzer/compare/2.0.0...2.0.1) - -> 6 July 2020 - -- Merge pull request #38 from minrk/flush-on-exit [`d24f50c`](https://github.com/minrk/wurlitzer/commit/d24f50c611164a3468622ca2ed80efc3abec8641) -- flush sys streams on enter/exit [`6d9b49d`](https://github.com/minrk/wurlitzer/commit/6d9b49dac73e59d5d10a6588ae02ed585779d042) -- flush sys streams in flush thread as well [`2682eb4`](https://github.com/minrk/wurlitzer/commit/2682eb4ef34c4268d21ebf75a5f146770a676cbc) - -### [2.0.0](https://github.com/minrk/wurlitzer/compare/1.0.3...2.0.0) - -> 25 October 2019 - -- use selectors instead of select.poll [`#34`](https://github.com/minrk/wurlitzer/pull/34) - -#### [1.0.3](https://github.com/minrk/wurlitzer/compare/1.0.2...1.0.3) - -> 13 June 2019 - -- PR: Add thread lock [`#30`](https://github.com/minrk/wurlitzer/pull/30) -- update packages on travis [`#31`](https://github.com/minrk/wurlitzer/pull/31) -- test on mac [`#25`](https://github.com/minrk/wurlitzer/pull/25) -- select.poll timeout is in milliseconds [`#26`](https://github.com/minrk/wurlitzer/pull/26) -- using poll instead of select in forwarder [`#24`](https://github.com/minrk/wurlitzer/pull/24) -- setup.py improvements [`#19`](https://github.com/minrk/wurlitzer/pull/19) -- Link blogpost about redirecting stdout/stderr [`#18`](https://github.com/minrk/wurlitzer/pull/18) -- fixes #27? [`#27`](https://github.com/minrk/wurlitzer/issues/27) - -#### [1.0.2](https://github.com/minrk/wurlitzer/compare/1.0.1...1.0.2) - -> 20 May 2018 - -- move fflush to a thread [`#16`](https://github.com/minrk/wurlitzer/pull/16) - -#### [1.0.1](https://github.com/minrk/wurlitzer/compare/1.0.0...1.0.1) - -> 22 January 2018 - -- Test more Pythons [`#13`](https://github.com/minrk/wurlitzer/pull/13) -- avoid unnecessary close of original FDs [`#11`](https://github.com/minrk/wurlitzer/pull/11) - -### [1.0.0](https://github.com/minrk/wurlitzer/compare/0.2.0...1.0.0) - -> 22 June 2017 - -- use control pipe to signal closure [`#8`](https://github.com/minrk/wurlitzer/pull/8) -- import warnings [`#3`](https://github.com/minrk/wurlitzer/pull/3) -- Do nothing if loaded in terminal IPython [`#2`](https://github.com/minrk/wurlitzer/pull/2) - -#### [0.2.0](https://github.com/minrk/wurlitzer/compare/0.1.2...0.2.0) - -> 14 March 2016 - -- Make it an IPython extension [`5aa2237`](https://github.com/minrk/wurlitzer/commit/5aa22375de5516915bb1cb9168e04430933e86a6) - -#### [0.1.2](https://github.com/minrk/wurlitzer/compare/0.1.1...0.1.2) - -> 13 March 2016 - -- readme more [`e905543`](https://github.com/minrk/wurlitzer/commit/e9055432933b29a70246299f2534e44af01c7edb) -- flush before entering wurlitzer [`a8b3a85`](https://github.com/minrk/wurlitzer/commit/a8b3a856a576fe50e8771fddad7fcf3b21ae3285) -- bump patch on release [`841cf92`](https://github.com/minrk/wurlitzer/commit/841cf922a77fd1a954ff968530d096ecfc1879aa) - -#### [0.1.1](https://github.com/minrk/wurlitzer/compare/0.1.0...0.1.1) - -> 9 March 2016 - -- fix names in README, long_description [`7f95a69`](https://github.com/minrk/wurlitzer/commit/7f95a690985e9ff2e7360c2c433fa9b9187f8758) - -#### 0.1.0 - -> 9 March 2016 - -- init package [`88e28b7`](https://github.com/minrk/wurlitzer/commit/88e28b7685806006fdd3c9a2021705be1b9fbbed) -- Add demo notebook [`ea70e0e`](https://github.com/minrk/wurlitzer/commit/ea70e0e1f82ccb2e3283b6baf2c1d91c0b05ac8a) -- Initial commit [`9646cf2`](https://github.com/minrk/wurlitzer/commit/9646cf2417cc46c61d1f6437f8f76efa56ccf2d8) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/PKG-INFO new/wurlitzer-3.0.2/PKG-INFO --- old/wurlitzer-2.0.1/PKG-INFO 2020-07-06 10:45:51.000000000 +0200 +++ new/wurlitzer-3.0.2/PKG-INFO 2021-08-25 09:59:24.916883500 +0200 @@ -1,78 +1,80 @@ Metadata-Version: 2.1 Name: wurlitzer -Version: 2.0.1 +Version: 3.0.2 Summary: Capture C-level output in context managers Home-page: https://github.com/minrk/wurlitzer Author: Min RK Author-email: benjami...@gmail.com License: MIT -Description: # Wurlitzer - - Capture C-level stdout/stderr pipes in Python via `os.dup2`. - - For more details on why this is needed, please read [this blog post]( https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ ). - - ## Install - - pip install wurlitzer - - ## Usage - - Capture stdout/stderr in pipes: - - ```python - from wurlitzer import pipes - - with pipes() as (out, err): - call_some_c_function() - - stdout = out.read() - ``` - - Capture stdout/stderr in StringIO: - - ```python - from io import StringIO - from wurlitzer import pipes, STDOUT - - out = StringIO() - with pipes(stdout=out, stderr=STDOUT): - call_some_c_function() - - stdout = out.getvalue() - ``` - - Forward C-level stdout/stderr to Python sys.stdout/stderr, - which may already be forwarded somewhere by the environment, e.g. IPython: - - ```python - from wurlitzer import sys_pipes - - with sys_pipes(): - call_some_c_function() - ``` - - Or even simpler, enable it as an IPython extension: - - ``` - %load_ext wurlitzer - ``` - - To forward all C-level output to IPython during execution. - - ## Acknowledgments - - This package is based on stuff we learned with @takluyver and @karies while working on capturing output from the [Cling Kernel](https://github.com/root-mirror/cling/tree/master/tools/Jupyter/kernel) for Jupyter. - - ## Wurlitzer?! - - [Wurlitzer](https://en.wikipedia.org/wiki/Wurlitzer) makes pipe organs. Get it? Pipes? Naming is hard. - Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Requires-Python: >=2.7 +Requires-Python: >=3.5 Description-Content-Type: text/markdown +License-File: LICENSE + +# Wurlitzer + +Capture C-level stdout/stderr pipes in Python via `os.dup2`. + +For more details on why this is needed, please read [this blog post](https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/). + +## Install + + pip install wurlitzer + +## Usage + +Capture stdout/stderr in pipes: + +```python +from wurlitzer import pipes + +with pipes() as (out, err): + call_some_c_function() + +stdout = out.read() +``` + +Capture stdout/stderr in StringIO: + +```python +from io import StringIO +from wurlitzer import pipes, STDOUT + +out = StringIO() +with pipes(stdout=out, stderr=STDOUT): + call_some_c_function() + +stdout = out.getvalue() +``` + +Forward C-level stdout/stderr to Python sys.stdout/stderr, +which may already be forwarded somewhere by the environment, e.g. IPython: + +```python +from wurlitzer import sys_pipes + +with sys_pipes(): + call_some_c_function() +``` + +Or even simpler, enable it as an IPython extension: + +``` +%load_ext wurlitzer +``` + +To forward all C-level output to IPython during execution. + +## Acknowledgments + +This package is based on stuff we learned with @takluyver and @karies while working on capturing output from the [Cling Kernel](https://github.com/root-mirror/cling/tree/master/tools/Jupyter/kernel) for Jupyter. + +## Wurlitzer?! + +[Wurlitzer](https://en.wikipedia.org/wiki/Wurlitzer) makes pipe organs. Get it? Pipes? Naming is hard. + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/README.md new/wurlitzer-3.0.2/README.md --- old/wurlitzer-2.0.1/README.md 2018-12-10 12:49:44.000000000 +0100 +++ new/wurlitzer-3.0.2/README.md 2021-08-25 09:59:17.000000000 +0200 @@ -2,7 +2,7 @@ Capture C-level stdout/stderr pipes in Python via `os.dup2`. -For more details on why this is needed, please read [this blog post]( https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ ). +For more details on why this is needed, please read [this blog post](https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/). ## Install diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/dev-requirements.txt new/wurlitzer-3.0.2/dev-requirements.txt --- old/wurlitzer-2.0.1/dev-requirements.txt 2016-03-14 16:07:04.000000000 +0100 +++ new/wurlitzer-3.0.2/dev-requirements.txt 2021-08-25 09:59:17.000000000 +0200 @@ -1,4 +1,4 @@ codecov -pytest-cov +mock # python_version < '3.0' pytest>=2.8 -mock \ No newline at end of file +pytest-cov diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/pyproject.toml new/wurlitzer-3.0.2/pyproject.toml --- old/wurlitzer-2.0.1/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/wurlitzer-3.0.2/pyproject.toml 2021-08-25 09:59:17.000000000 +0200 @@ -0,0 +1,11 @@ +[tool.isort] +profile = "black" + +[tool.black] +skip-string-normalization = true +target_version = [ + "py27", + "py36", + "py37", + "py38", +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/release.sh new/wurlitzer-3.0.2/release.sh --- old/wurlitzer-2.0.1/release.sh 2016-03-09 15:31:50.000000000 +0100 +++ new/wurlitzer-3.0.2/release.sh 1970-01-01 01:00:00.000000000 +0100 @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -bumpversion release --tag -py.test test.py -python setup.py sdist bdist_wheel -twine upload dist/* -bumpversion patch - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/setup.cfg new/wurlitzer-3.0.2/setup.cfg --- old/wurlitzer-2.0.1/setup.cfg 2020-07-06 10:45:51.000000000 +0200 +++ new/wurlitzer-3.0.2/setup.cfg 2021-08-25 09:59:24.916883500 +0200 @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/setup.py new/wurlitzer-3.0.2/setup.py --- old/wurlitzer-2.0.1/setup.py 2019-10-25 13:41:32.000000000 +0200 +++ new/wurlitzer-3.0.2/setup.py 2021-08-25 09:59:17.000000000 +0200 @@ -33,12 +33,11 @@ author="Min RK", author_email="benjami...@gmail.com", description="Capture C-level output in context managers", - install_requires=["selectors2; python_version<'3.4'"], long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/minrk/wurlitzer", py_modules=["wurlitzer"], - python_requires=">=2.7", + python_requires=">=3.5", license="MIT", cmdclass={ "bdist_egg": bdist_egg if "bdist_egg" in sys.argv else bdist_egg_disabled @@ -47,7 +46,6 @@ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/test.py new/wurlitzer-3.0.2/test.py --- old/wurlitzer-2.0.1/test.py 2019-10-17 16:49:50.000000000 +0200 +++ new/wurlitzer-3.0.2/test.py 2021-08-25 09:59:17.000000000 +0200 @@ -3,26 +3,40 @@ import io import os -from tempfile import TemporaryFile +import platform +import sys import time +from fcntl import fcntl +from tempfile import TemporaryFile +from unittest import mock -import mock +import pytest +import wurlitzer from wurlitzer import ( - libc, pipes, STDOUT, PIPE, c_stderr_p, c_stdout_p, - sys_pipes, sys_pipes_forever, - stop_sys_pipes, + PIPE, + STDOUT, Wurlitzer, + c_stderr_p, + c_stdout_p, + libc, + pipes, + stop_sys_pipes, + sys_pipes, + sys_pipes_forever, ) + def printf(msg): """Call C printf""" libc.printf((msg + '\n').encode('utf8')) + def printf_err(msg): """Cal C fprintf on stderr""" libc.fprintf(c_stderr_p, (msg + '\n').encode('utf8')) + def test_pipes(): with pipes(stdout=PIPE, stderr=PIPE) as (stdout, stderr): printf(u"Hell??") @@ -31,6 +45,7 @@ assert stdout.read() == u"Hell??\n" assert stderr.read() == u"Hi, std??rr\n" + def test_pipe_bytes(): with pipes(encoding=None) as (stdout, stderr): printf(u"Hell??") @@ -39,6 +54,7 @@ assert stdout.read() == u"Hell??\n".encode('utf8') assert stderr.read() == u"Hi, std??rr\n".encode('utf8') + def test_forward(): stdout = io.StringIO() stderr = io.StringIO() @@ -51,6 +67,7 @@ assert stdout.getvalue() == u"Hell??\n" assert stderr.getvalue() == u"Hi, std??rr\n" + def test_pipes_stderr(): stdout = io.StringIO() with pipes(stdout=stdout, stderr=STDOUT) as (_stdout, _stderr): @@ -63,6 +80,7 @@ assert stdout.getvalue() == u"Hell??\nHi, std??rr\n" + def test_flush(): stdout = io.StringIO() w = Wurlitzer(stdout=stdout, stderr=STDOUT) @@ -71,16 +89,20 @@ time.sleep(0.5) assert stdout.getvalue().strip() == u"Hell??" + def test_sys_pipes(): stdout = io.StringIO() stderr = io.StringIO() - with mock.patch('sys.stdout', stdout), mock.patch('sys.stderr', stderr), sys_pipes(): + with mock.patch('sys.stdout', stdout), mock.patch( + 'sys.stderr', stderr + ), sys_pipes(): printf(u"Hell??") printf_err(u"Hi, std??rr") assert stdout.getvalue() == u"Hell??\n" assert stderr.getvalue() == u"Hi, std??rr\n" + def test_redirect_everything(): stdout = io.StringIO() stderr = io.StringIO() @@ -118,8 +140,40 @@ def test_buffer_full(): with pipes(stdout=None, stderr=io.StringIO()) as (stdout, stderr): - long_string = "x" * 1000000 # create a very long string + long_string = "x" * 100000 # create a long string (longer than 65536) printf_err(long_string) # Test never reaches here as the process hangs. assert stderr.getvalue() == long_string + "\n" + + +def test_buffer_full_default(): + with pipes() as (stdout, stderr): + long_string = "x" * 100000 # create a long string (longer than 65536) + printf(long_string) + + # Test never reaches here as the process hangs. + assert stdout.read() == long_string + "\n" + + +def test_pipe_max_size(): + max_pipe_size = wurlitzer._get_max_pipe_size() + if platform.system() == 'Linux': + assert 65535 <= max_pipe_size <= 1024 * 1024 + else: + assert max_pipe_size is None + + +@pytest.mark.skipif( + wurlitzer._get_max_pipe_size() is None, reason="requires _get_max_pipe_size" +) +def test_bufsize(): + default_bufsize = wurlitzer._get_max_pipe_size() + with wurlitzer.pipes() as (stdout, stderr): + assert fcntl(sys.__stdout__, wurlitzer.F_GETPIPE_SZ) == default_bufsize + assert fcntl(sys.__stderr__, wurlitzer.F_GETPIPE_SZ) == default_bufsize + + bufsize = 32768 # seems to only accept powers of two? + with wurlitzer.pipes(bufsize=bufsize) as (stdout, stderr): + assert fcntl(sys.__stdout__, wurlitzer.F_GETPIPE_SZ) == bufsize + assert fcntl(sys.__stderr__, wurlitzer.F_GETPIPE_SZ) == bufsize diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/wurlitzer.egg-info/PKG-INFO new/wurlitzer-3.0.2/wurlitzer.egg-info/PKG-INFO --- old/wurlitzer-2.0.1/wurlitzer.egg-info/PKG-INFO 2020-07-06 10:45:51.000000000 +0200 +++ new/wurlitzer-3.0.2/wurlitzer.egg-info/PKG-INFO 2021-08-25 09:59:24.000000000 +0200 @@ -1,78 +1,80 @@ Metadata-Version: 2.1 Name: wurlitzer -Version: 2.0.1 +Version: 3.0.2 Summary: Capture C-level output in context managers Home-page: https://github.com/minrk/wurlitzer Author: Min RK Author-email: benjami...@gmail.com License: MIT -Description: # Wurlitzer - - Capture C-level stdout/stderr pipes in Python via `os.dup2`. - - For more details on why this is needed, please read [this blog post]( https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ ). - - ## Install - - pip install wurlitzer - - ## Usage - - Capture stdout/stderr in pipes: - - ```python - from wurlitzer import pipes - - with pipes() as (out, err): - call_some_c_function() - - stdout = out.read() - ``` - - Capture stdout/stderr in StringIO: - - ```python - from io import StringIO - from wurlitzer import pipes, STDOUT - - out = StringIO() - with pipes(stdout=out, stderr=STDOUT): - call_some_c_function() - - stdout = out.getvalue() - ``` - - Forward C-level stdout/stderr to Python sys.stdout/stderr, - which may already be forwarded somewhere by the environment, e.g. IPython: - - ```python - from wurlitzer import sys_pipes - - with sys_pipes(): - call_some_c_function() - ``` - - Or even simpler, enable it as an IPython extension: - - ``` - %load_ext wurlitzer - ``` - - To forward all C-level output to IPython during execution. - - ## Acknowledgments - - This package is based on stuff we learned with @takluyver and @karies while working on capturing output from the [Cling Kernel](https://github.com/root-mirror/cling/tree/master/tools/Jupyter/kernel) for Jupyter. - - ## Wurlitzer?! - - [Wurlitzer](https://en.wikipedia.org/wiki/Wurlitzer) makes pipe organs. Get it? Pipes? Naming is hard. - Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Requires-Python: >=2.7 +Requires-Python: >=3.5 Description-Content-Type: text/markdown +License-File: LICENSE + +# Wurlitzer + +Capture C-level stdout/stderr pipes in Python via `os.dup2`. + +For more details on why this is needed, please read [this blog post](https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/). + +## Install + + pip install wurlitzer + +## Usage + +Capture stdout/stderr in pipes: + +```python +from wurlitzer import pipes + +with pipes() as (out, err): + call_some_c_function() + +stdout = out.read() +``` + +Capture stdout/stderr in StringIO: + +```python +from io import StringIO +from wurlitzer import pipes, STDOUT + +out = StringIO() +with pipes(stdout=out, stderr=STDOUT): + call_some_c_function() + +stdout = out.getvalue() +``` + +Forward C-level stdout/stderr to Python sys.stdout/stderr, +which may already be forwarded somewhere by the environment, e.g. IPython: + +```python +from wurlitzer import sys_pipes + +with sys_pipes(): + call_some_c_function() +``` + +Or even simpler, enable it as an IPython extension: + +``` +%load_ext wurlitzer +``` + +To forward all C-level output to IPython during execution. + +## Acknowledgments + +This package is based on stuff we learned with @takluyver and @karies while working on capturing output from the [Cling Kernel](https://github.com/root-mirror/cling/tree/master/tools/Jupyter/kernel) for Jupyter. + +## Wurlitzer?! + +[Wurlitzer](https://en.wikipedia.org/wiki/Wurlitzer) makes pipe organs. Get it? Pipes? Naming is hard. + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/wurlitzer.egg-info/SOURCES.txt new/wurlitzer-3.0.2/wurlitzer.egg-info/SOURCES.txt --- old/wurlitzer-2.0.1/wurlitzer.egg-info/SOURCES.txt 2020-07-06 10:45:51.000000000 +0200 +++ new/wurlitzer-3.0.2/wurlitzer.egg-info/SOURCES.txt 2021-08-25 09:59:24.000000000 +0200 @@ -1,19 +1,13 @@ -.bumpversion.cfg -.gitignore -.travis.yml -CHANGELOG.md Demo.ipynb LICENSE MANIFEST.in README.md dev-requirements.txt -release.sh -setup.cfg +pyproject.toml setup.py test.py wurlitzer.py wurlitzer.egg-info/PKG-INFO wurlitzer.egg-info/SOURCES.txt wurlitzer.egg-info/dependency_links.txt -wurlitzer.egg-info/requires.txt wurlitzer.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/wurlitzer.egg-info/requires.txt new/wurlitzer-3.0.2/wurlitzer.egg-info/requires.txt --- old/wurlitzer-2.0.1/wurlitzer.egg-info/requires.txt 2020-07-06 10:45:51.000000000 +0200 +++ new/wurlitzer-3.0.2/wurlitzer.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,3 +0,0 @@ - -[:python_version < "3.4"] -selectors2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wurlitzer-2.0.1/wurlitzer.py new/wurlitzer-3.0.2/wurlitzer.py --- old/wurlitzer-2.0.1/wurlitzer.py 2020-07-06 10:45:30.000000000 +0200 +++ new/wurlitzer-3.0.2/wurlitzer.py 2021-08-25 09:59:17.000000000 +0200 @@ -4,7 +4,7 @@ """ from __future__ import print_function -__version__ = '2.0.1' +__version__ = '3.0.2' __all__ = [ 'pipes', @@ -14,38 +14,81 @@ 'Wurlitzer', ] -from contextlib import contextmanager import ctypes import errno -from fcntl import fcntl, F_GETFL, F_SETFL import io import os - -try: - from queue import Queue -except ImportError: - from Queue import Queue - -try: - import selectors -except ImportError: - # py < 3.4 - import selectors2 as selectors - +import platform +import selectors import sys import threading import time import warnings +from contextlib import contextmanager +from fcntl import F_GETFL, F_SETFL, fcntl +from functools import lru_cache +from queue import Queue + +try: + from fcntl import F_GETPIPE_SZ, F_SETPIPE_SZ +except ImportError: + # ref: linux uapi/linux/fcntl.h + F_SETPIPE_SZ = 1024 + 7 + F_GETPIPE_SZ = 1024 + 8 libc = ctypes.CDLL(None) + +def _get_streams_cffi(): + """Use CFFI to lookup stdout/stderr pointers + + Should work ~everywhere, but requires compilation + """ + try: + import cffi + except ImportError: + raise ImportError( + "Failed to lookup stdout symbols in libc. Fallback requires cffi." + ) + + try: + _ffi = cffi.FFI() + _ffi.cdef("const size_t c_stdout_p();") + _ffi.cdef("const size_t c_stderr_p();") + _lib = _ffi.verify( + '\n'.join( + [ + "#include <stdio.h>", + "const size_t c_stdout_p() { return (size_t) (void*) stdout; }", + "const size_t c_stderr_p() { return (size_t) (void*) stderr; }", + ] + ) + ) + c_stdout_p = ctypes.c_void_p(_lib.c_stdout_p()) + c_stderr_p = ctypes.c_void_p(_lib.c_stderr_p()) + except Exception as e: + warnings.warn( + "Failed to lookup stdout with cffi: {}.\nStreams may not be flushed.".format( + e + ) + ) + return (None, None) + else: + return c_stdout_p, c_stderr_p + + +c_stdout_p = c_stderr_p = None try: c_stdout_p = ctypes.c_void_p.in_dll(libc, 'stdout') c_stderr_p = ctypes.c_void_p.in_dll(libc, 'stderr') -except ValueError: # pragma: no cover - # libc.stdout is has a funny name on OS X - c_stdout_p = ctypes.c_void_p.in_dll(libc, '__stdoutp') # pragma: no cover - c_stderr_p = ctypes.c_void_p.in_dll(libc, '__stderrp') # pragma: no cover +except ValueError: + # libc.stdout has a funny name on macOS + try: + c_stdout_p = ctypes.c_void_p.in_dll(libc, '__stdoutp') + c_stderr_p = ctypes.c_void_p.in_dll(libc, '__stderrp') + except ValueError: + c_stdout_p, c_stderr_p = _get_streams_cffi() + STDOUT = 2 PIPE = 3 @@ -53,7 +96,7 @@ _default_encoding = getattr(sys.stdin, 'encoding', None) or 'utf8' if _default_encoding.lower() == 'ascii': # don't respect ascii - _default_encoding = 'utf8' # pragma: no cover + _default_encoding = 'utf8' # pragma: no cover def dup2(a, b, timeout=3): @@ -73,14 +116,56 @@ raise dup_err -class Wurlitzer(object): +@lru_cache() +def _get_max_pipe_size(): + """Get max pipe size + + Reads /proc/sys/fs/pipe-max-size on Linux. + Always returns None elsewhere. + + Returns integer (up to 1MB), + or None if no value can be determined. + + Adapted from wal-e, (c) 2018, WAL-E Contributors + used under BSD-3-clause + """ + if platform.system() != 'Linux': + return + + # If Linux procfs (or something that looks like it) exposes its + # maximum F_SETPIPE_SZ, adjust the default buffer sizes. + try: + with open('/proc/sys/fs/pipe-max-size', 'r') as f: + # Figure out OS max pipe size + pipe_max_size = int(f.read()) + except Exception: + pass + else: + if pipe_max_size > 1024 * 1024: + # avoid unusually large values, limit to 1MB + return 1024 * 1024 + elif pipe_max_size <= 65536: + # smaller than default, don't do anything + return None + else: + return pipe_max_size + + +class Wurlitzer: """Class for Capturing Process-level FD output via dup2 - - Typically used via `wurlitzer.capture` + + Typically used via `wurlitzer.pipes` """ + flush_interval = 0.2 - - def __init__(self, stdout=None, stderr=None, encoding=_default_encoding): + + def __init__( + self, + stdout=None, + stderr=None, + encoding=_default_encoding, + bufsize=_get_max_pipe_size(), + ): """ Parameters ---------- @@ -90,6 +175,10 @@ The stream for forwarding stderr. encoding: str or None The encoding to use, if streams should be interpreted as text. + bufsize: int or None + Set pipe buffer size using fcntl F_SETPIPE_SZ (linux only) + default: use /proc/sys/fs/pipe-max-size up to a max of 1MB + if 0, will do nothing. """ self._stdout = stdout if stderr == STDOUT: @@ -97,6 +186,9 @@ else: self._stderr = stderr self.encoding = encoding + if bufsize is None: + bufsize = _get_max_pipe_size() + self._bufsize = bufsize self._save_fds = {} self._real_fds = {} self._handlers = {} @@ -107,34 +199,41 @@ real_fd = getattr(sys, '__%s__' % name).fileno() save_fd = os.dup(real_fd) self._save_fds[name] = save_fd - + pipe_out, pipe_in = os.pipe() + # set max pipe buffer size (linux only) + if self._bufsize: + try: + fcntl(pipe_in, F_SETPIPE_SZ, self._bufsize) + except OSError: + warnings.warn("Failed to set pipe buffer size", RuntimeWarning) + dup2(pipe_in, real_fd) os.close(pipe_in) self._real_fds[name] = real_fd - + # make pipe_out non-blocking flags = fcntl(pipe_out, F_GETFL) - fcntl(pipe_out, F_SETFL, flags|os.O_NONBLOCK) + fcntl(pipe_out, F_SETFL, flags | os.O_NONBLOCK) return pipe_out - + def _decode(self, data): """Decode data, if any - + Called before passing to stdout/stderr streams """ if self.encoding: data = data.decode(self.encoding, 'replace') return data - + def _handle_stdout(self, data): if self._stdout: self._stdout.write(self._decode(data)) - + def _handle_stderr(self, data): if self._stderr: self._stderr.write(self._decode(data)) - + def _setup_handle(self): """Setup handle for output, if any""" self.handle = (self._stdout, self._stderr) @@ -150,8 +249,11 @@ if self._stderr and sys.stderr: sys.stderr.flush() - libc.fflush(c_stdout_p) - libc.fflush(c_stderr_p) + if c_stdout_p is not None: + libc.fflush(c_stdout_p) + + if c_stderr_p is not None: + libc.fflush(c_stderr_p) def __enter__(self): # flush anything out before starting @@ -241,6 +343,7 @@ flush_thread.join() # cleanup pipes [os.close(pipe) for pipe in pipes] + poller.close() self.thread = threading.Thread(target=forwarder) self.thread.daemon = True @@ -267,11 +370,19 @@ @contextmanager -def pipes(stdout=PIPE, stderr=PIPE, encoding=_default_encoding): +def pipes(stdout=PIPE, stderr=PIPE, encoding=_default_encoding, bufsize=None): """Capture C-level stdout/stderr in a context manager. The return value for the context manager is (stdout, stderr). + .. versionchanged:: 3.0 + + when using `PIPE` (default), the type of captured output + is `io.StringIO/BytesIO` instead of an OS pipe. + This eliminates max buffer size issues (and hang when output exceeds 65536 bytes), + but also means the buffer cannot be read with `.read()` methods + until after the context exits. + Examples -------- @@ -280,14 +391,13 @@ ... output = stdout.read() """ stdout_pipe = stderr_pipe = False + if encoding: + PipeIO = io.StringIO + else: + PipeIO = io.BytesIO # setup stdout if stdout == PIPE: - stdout_r, stdout_w = os.pipe() - stdout_w = os.fdopen(stdout_w, 'wb') - if encoding: - stdout_r = io.open(stdout_r, 'r', encoding=encoding) - else: - stdout_r = os.fdopen(stdout_r, 'rb') + stdout_r = stdout_w = PipeIO() stdout_pipe = True else: stdout_r = stdout_w = stdout @@ -296,46 +406,39 @@ stderr_r = None stderr_w = stdout_w elif stderr == PIPE: - stderr_r, stderr_w = os.pipe() - stderr_w = os.fdopen(stderr_w, 'wb') - if encoding: - stderr_r = io.open(stderr_r, 'r', encoding=encoding) - else: - stderr_r = os.fdopen(stderr_r, 'rb') + stderr_r = stderr_w = PipeIO() stderr_pipe = True else: stderr_r = stderr_w = stderr - if stdout_pipe or stderr_pipe: - capture_encoding = None - else: - capture_encoding = encoding - w = Wurlitzer(stdout=stdout_w, stderr=stderr_w, encoding=capture_encoding) + w = Wurlitzer(stdout=stdout_w, stderr=stderr_w, encoding=encoding, bufsize=bufsize) try: with w: yield stdout_r, stderr_r finally: # close pipes if stdout_pipe: - stdout_w.close() + # seek to 0 so that it can be read after exit + stdout_r.seek(0) if stderr_pipe: - stderr_w.close() + # seek to 0 so that it can be read after exit + stderr_r.seek(0) -def sys_pipes(encoding=_default_encoding): +def sys_pipes(encoding=_default_encoding, bufsize=None): """Redirect C-level stdout/stderr to sys.stdout/stderr - + This is useful of sys.sdout/stderr are already being forwarded somewhere. - + DO NOT USE THIS if sys.stdout and sys.stderr are not already being forwarded. """ - return pipes(sys.stdout, sys.stderr, encoding=encoding) + return pipes(sys.stdout, sys.stderr, encoding=encoding, bufsize=bufsize) _mighty_wurlitzer = None _mighty_lock = threading.Lock() -def sys_pipes_forever(encoding=_default_encoding): +def sys_pipes_forever(encoding=_default_encoding, bufsize=None): """Redirect all C output to sys.stdout/err This is not a context manager; it turns on C-forwarding permanently. @@ -343,7 +446,7 @@ global _mighty_wurlitzer with _mighty_lock: if _mighty_wurlitzer is None: - _mighty_wurlitzer = sys_pipes(encoding) + _mighty_wurlitzer = sys_pipes(encoding, bufsize) _mighty_wurlitzer.__enter__() @@ -356,30 +459,45 @@ _mighty_wurlitzer = None +_extension_enabled = False + + def load_ipython_extension(ip): """Register me as an IPython extension - + Captures all C output during execution and forwards to sys. - + Does nothing on terminal IPython. - + Use: %load_ext wurlitzer """ - if not getattr(ip, 'kernel'): - warnings.warn( - "wurlitzer extension doesn't do anything in terminal IPython" - ) + global _extension_enabled + + if not getattr(ip, 'kernel', None): + warnings.warn("wurlitzer extension doesn't do anything in terminal IPython") return + for name in ("__stdout__", "__stderr__"): + if getattr(sys, name) is None: + warnings.warn("sys.{} is None. Wurlitzer can't capture output without it.") + return + ip.events.register('pre_execute', sys_pipes_forever) ip.events.register('post_execute', stop_sys_pipes) + _extension_enabled = True def unload_ipython_extension(ip): """Unload me as an IPython extension - + Use: %unload_ext wurlitzer """ - if not getattr(ip, 'kernel'): + global _extension_enabled + if not _extension_enabled: return + ip.events.unregister('pre_execute', sys_pipes_forever) ip.events.unregister('post_execute', stop_sys_pipes) + # sys_pipes_forever was called in pre_execute + # after unregister we need to call it explicitly: + stop_sys_pipes() + _extension_enabled = False