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

Reply via email to