Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pulsectl for openSUSE:Factory 
checked in at 2021-04-17 00:02:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pulsectl (Old)
 and      /work/SRC/openSUSE:Factory/.python-pulsectl.new.12324 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pulsectl"

Sat Apr 17 00:02:13 2021 rev:7 rq:886021 version:21.3.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pulsectl/python-pulsectl.changes  
2021-03-05 13:45:15.799621485 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-pulsectl.new.12324/python-pulsectl.changes   
    2021-04-17 00:02:31.141665414 +0200
@@ -1,0 +2,7 @@
+Fri Apr 16 13:32:15 UTC 2021 - Mark??ta Machov?? <[email protected]>
+
+- update to 21.3.4
+  * Add timeout= option for connect() method [#48].
+  * Expose "base_volume" (alsa volume level) attribute for sinks/sources [#47].
+
+-------------------------------------------------------------------

Old:
----
  pulsectl-20.2.4.tar.gz

New:
----
  pulsectl-21.3.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pulsectl.spec ++++++
--- /var/tmp/diff_new_pack.nERxGN/_old  2021-04-17 00:02:31.637666199 +0200
+++ /var/tmp/diff_new_pack.nERxGN/_new  2021-04-17 00:02:31.641666205 +0200
@@ -19,23 +19,23 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %bcond_without test
 Name:           python-pulsectl
-Version:        20.2.4 
+Version:        21.3.4
 Release:        0
 Summary:        Python high-level interface and ctypes-based bindings for 
PulseAudio (libpulse)
 License:        MIT
 Group:          Development/Languages/Python
-URL:            http://github.com/mk-fg/python-pulse-control
+URL:            https://github.com/mk-fg/python-pulse-control
 Source:         
https://files.pythonhosted.org/packages/source/p/pulsectl/pulsectl-%{version}.tar.gz
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+Requires:       python-setuptools
+BuildArch:      noarch
 %if 0%{?sle_version} && 0%{?sle_version} < 150300
 Requires:       pulseaudio
 %else
 Requires:       pulseaudio-daemon
 %endif
-Requires:       python-setuptools
-BuildArch:      noarch
 %if %{with test}
 BuildRequires:  libpulse-devel
 BuildRequires:  pulseaudio
@@ -61,7 +61,7 @@
 
 %if %{with test}
 %check
-%python_exec -m unittest pulsectl.tests.all
+%pyunittest pulsectl.tests.all
 %endif
 
 %files %{python_files}

++++++ pulsectl-20.2.4.tar.gz -> pulsectl-21.3.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/CHANGES.rst 
new/pulsectl-21.3.4/CHANGES.rst
--- old/pulsectl-20.2.4/CHANGES.rst     2020-02-27 16:31:58.000000000 +0100
+++ new/pulsectl-21.3.4/CHANGES.rst     2021-03-08 06:31:56.000000000 +0100
@@ -8,10 +8,18 @@
 Each entry is a package version which change first appears in, followed by
 description of the change itself.
 
-Last synced/updated: 20.2.4
+Last synced/updated: 21.3.4
 
 ---------------------------------------------------------------------------
 
+- 21.3.4: Add timeout= option for connect() method [#48].
+
+- 21.3.2: Expose "base_volume" (alsa volume level) attribute for sinks/sources 
[#47].
+
+- 21.3.1: There is now https://pypi.org/project/pulsectl-asyncio/ module [#46].
+
+  It is maintained separately, and should provide similar bindings to use with 
async apps.
+
 - 20.2.4: Add pulse.get_card_by_name() wrapper [#38].
 
 - 20.2.2: Expose "corked" bool attr in PulseSinkInputInfo and 
PulseSourceOutputInfo [#37].
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/PKG-INFO new/pulsectl-21.3.4/PKG-INFO
--- old/pulsectl-20.2.4/PKG-INFO        2020-02-27 17:20:20.519531700 +0100
+++ new/pulsectl-21.3.4/PKG-INFO        2021-03-08 06:38:22.525997900 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pulsectl
-Version: 20.2.4
+Version: 21.3.4
 Summary: Python high-level interface and ctypes-based bindings for PulseAudio 
(libpulse)
 Home-page: http://github.com/mk-fg/python-pulse-control
 Author: George Filipkin, Mike Kazantsev
@@ -9,14 +9,19 @@
 Description: python-pulse-control (pulsectl module)
         ======================================
         
-        Python (3.x and 2.x) high-level interface and ctypes-based bindings for
-        PulseAudio_ (libpulse), mostly focused on mixer-like controls and
-        introspection-related operations (as opposed to e.g. submitting sound 
samples to
-        play, player-like client).
+        Python (3.x and 2.x) blocking high-level interface and ctypes-based 
bindings
+        for PulseAudio_ (libpulse), to use in a simple synchronous code.
+        
+        Wrappers are mostly for mixer-like controls and introspection-related 
operations,
+        as opposed to e.g. submitting sound samples to play and player-like 
client.
+        
+        For async version to use with asyncio_, see `pulsectl-asyncio`_ 
project instead.
         
         Originally forked from pulsemixer_ project, which had this code 
bundled.
         
         .. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+        .. _asyncio: https://docs.python.org/3/library/asyncio.html
+        .. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
         .. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
         
         |
@@ -259,7 +264,7 @@
         ````````````````````````````
         
         libpulse clients always work as an event loop, though this module 
kinda hides
-        it, presenting a more conventional blocking interface.
+        it, presenting a more old-style blocking interface.
         
         So what happens on any call (e.g. ``pulse.mute(...)``) is:
         
@@ -271,8 +276,8 @@
         and second step here.
         
         Which means that any pulse calls from callback function can't be used 
when
-        ``event_listen()`` (or any other pulse call through this module, for 
that
-        matter) waits for return value and runs libpulse loop already.
+        ``event_listen()`` (or any other pulse call through this module, for 
that matter)
+        waits for return value and runs libpulse loop already.
         
         One can raise PulseLoopStop exception there to make ``event_listen()`` 
return,
         run whatever pulse calls after that, then re-start the 
``event_listen()`` thing.
@@ -287,15 +292,13 @@
         to create a mutex around step-2 (run event loop) from the list above, 
so
         multiple threads won't do it at the same time.
         
-        For proper eventloop integration (think twisted or asyncio), 
``_pulse_get_list``
-        / ``_pulse_method_call`` wrappers should be overidden to not run pulse 
loop, but
-        rather return "future" object and register a set of fd's (as passed to
-        ``set_poll_func`` callback) with eventloop.
-        Never needed that, so not implemented in the module, but should be 
rather easy
-        to implement on top of it, as described.
+        For proper python eventloop integration (think twisted or asyncio),
+        use `pulsectl-asyncio`_ module instead.
         
-        See more info/discussion and code examples in `github #11
-        <https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+        There are also some tricks mentioned in `github #11
+        <https://github.com/mk-fg/python-pulse-control/issues/11>`_ to 
shoehorn this
+        module into async apps, but even with non-asyncio eventloop, starting 
from
+        pulsectl-asyncio would probably be much easier.
         
         
         Tests
@@ -408,6 +411,8 @@
         
         * pulsemixer_ - initial source for this project (embedded in the tool).
         
+        * `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for 
async python code.
+        
         * `libpulseaudio 
<https://github.com/thelinuxdude/python-pulseaudio/>`_ -
           different libpulse bindings module, more low-level, auto-generated 
from
           pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/README.rst 
new/pulsectl-21.3.4/README.rst
--- old/pulsectl-20.2.4/README.rst      2019-09-24 01:03:43.000000000 +0200
+++ new/pulsectl-21.3.4/README.rst      2021-02-26 17:12:09.000000000 +0100
@@ -1,14 +1,19 @@
 python-pulse-control (pulsectl module)
 ======================================
 
-Python (3.x and 2.x) high-level interface and ctypes-based bindings for
-PulseAudio_ (libpulse), mostly focused on mixer-like controls and
-introspection-related operations (as opposed to e.g. submitting sound samples 
to
-play, player-like client).
+Python (3.x and 2.x) blocking high-level interface and ctypes-based bindings
+for PulseAudio_ (libpulse), to use in a simple synchronous code.
+
+Wrappers are mostly for mixer-like controls and introspection-related 
operations,
+as opposed to e.g. submitting sound samples to play and player-like client.
+
+For async version to use with asyncio_, see `pulsectl-asyncio`_ project 
instead.
 
 Originally forked from pulsemixer_ project, which had this code bundled.
 
 .. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+.. _asyncio: https://docs.python.org/3/library/asyncio.html
+.. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
 .. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
 
 |
@@ -251,7 +256,7 @@
 ````````````````````````````
 
 libpulse clients always work as an event loop, though this module kinda hides
-it, presenting a more conventional blocking interface.
+it, presenting a more old-style blocking interface.
 
 So what happens on any call (e.g. ``pulse.mute(...)``) is:
 
@@ -263,8 +268,8 @@
 and second step here.
 
 Which means that any pulse calls from callback function can't be used when
-``event_listen()`` (or any other pulse call through this module, for that
-matter) waits for return value and runs libpulse loop already.
+``event_listen()`` (or any other pulse call through this module, for that 
matter)
+waits for return value and runs libpulse loop already.
 
 One can raise PulseLoopStop exception there to make ``event_listen()`` return,
 run whatever pulse calls after that, then re-start the ``event_listen()`` 
thing.
@@ -279,15 +284,13 @@
 to create a mutex around step-2 (run event loop) from the list above, so
 multiple threads won't do it at the same time.
 
-For proper eventloop integration (think twisted or asyncio), 
``_pulse_get_list``
-/ ``_pulse_method_call`` wrappers should be overidden to not run pulse loop, 
but
-rather return "future" object and register a set of fd's (as passed to
-``set_poll_func`` callback) with eventloop.
-Never needed that, so not implemented in the module, but should be rather easy
-to implement on top of it, as described.
+For proper python eventloop integration (think twisted or asyncio),
+use `pulsectl-asyncio`_ module instead.
 
-See more info/discussion and code examples in `github #11
-<https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+There are also some tricks mentioned in `github #11
+<https://github.com/mk-fg/python-pulse-control/issues/11>`_ to shoehorn this
+module into async apps, but even with non-asyncio eventloop, starting from
+pulsectl-asyncio would probably be much easier.
 
 
 Tests
@@ -400,6 +403,8 @@
 
 * pulsemixer_ - initial source for this project (embedded in the tool).
 
+* `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for async 
python code.
+
 * `libpulseaudio <https://github.com/thelinuxdude/python-pulseaudio/>`_ -
   different libpulse bindings module, more low-level, auto-generated from
   pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/_pulsectl.py 
new/pulsectl-21.3.4/pulsectl/_pulsectl.py
--- old/pulsectl-20.2.4/pulsectl/_pulsectl.py   2020-02-27 16:25:08.000000000 
+0100
+++ new/pulsectl-21.3.4/pulsectl/_pulsectl.py   2021-03-04 19:56:51.000000000 
+0100
@@ -209,7 +209,7 @@
                ('flags', c_int),
                ('proplist', POINTER(PA_PROPLIST)),
                ('configured_latency', PA_USEC_T),
-               ('base_volume', c_int),
+               ('base_volume', c_uint32),
                ('state', c_int),
                ('n_volume_steps', c_int),
                ('card', c_uint32),
@@ -256,7 +256,7 @@
                ('flags', c_int),
                ('proplist', POINTER(PA_PROPLIST)),
                ('configured_latency', PA_USEC_T),
-               ('base_volume', c_int),
+               ('base_volume', c_uint32),
                ('state', c_int),
                ('n_volume_steps', c_int),
                ('card', c_uint32),
@@ -479,6 +479,8 @@
        func_defs = dict(
                pa_strerror=([c_int], c_str_p),
                pa_runtime_path=([c_str_p], (c_char_p, 'not_null')),
+               pa_operation_unref=[POINTER(PA_OPERATION)],
+
                pa_mainloop_new=(POINTER(PA_MAINLOOP)),
                pa_mainloop_get_api=([POINTER(PA_MAINLOOP)], 
POINTER(PA_MAINLOOP_API)),
                pa_mainloop_run=([POINTER(PA_MAINLOOP), POINTER(c_int)], c_int),
@@ -490,9 +492,11 @@
                pa_mainloop_set_poll_func=[POINTER(PA_MAINLOOP), 
PA_POLL_FUNC_T, c_void_p],
                pa_mainloop_quit=([POINTER(PA_MAINLOOP), c_int]),
                pa_mainloop_free=[POINTER(PA_MAINLOOP)],
+
                pa_signal_init=([POINTER(PA_MAINLOOP_API)], 'int_check_ge0'),
                pa_signal_new=([c_int, PA_SIGNAL_CB_T, 
POINTER(PA_SIGNAL_EVENT)]),
                pa_signal_done=None,
+
                pa_context_errno=([POINTER(PA_CONTEXT)], c_int),
                pa_context_new=([POINTER(PA_MAINLOOP_API), c_str_p], 
POINTER(PA_CONTEXT)),
                pa_context_set_state_callback=([POINTER(PA_CONTEXT), 
PA_STATE_CB_T, c_void_p]),
@@ -562,7 +566,6 @@
                        [POINTER(PA_CONTEXT), c_uint32, PA_CLIENT_INFO_CB_T, 
c_void_p] ),
                pa_context_get_server_info=( 'pa_op',
                        [POINTER(PA_CONTEXT), PA_SERVER_INFO_CB_T, c_void_p] ),
-               pa_operation_unref=[POINTER(PA_OPERATION)],
                pa_context_get_card_info_by_index=( 'pa_op',
                        [POINTER(PA_CONTEXT), c_uint32, PA_CARD_INFO_CB_T, 
c_void_p] ),
                pa_context_get_card_info_by_name=( 'pa_op',
@@ -581,6 +584,13 @@
                        [POINTER(PA_CONTEXT), c_uint32, 
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
                pa_context_subscribe=( 'pa_op',
                        [POINTER(PA_CONTEXT), c_int, PA_CONTEXT_SUCCESS_CB_T, 
c_void_p] ),
+               pa_context_set_subscribe_callback=[POINTER(PA_CONTEXT), 
PA_SUBSCRIBE_CB_T, c_void_p],
+               pa_context_play_sample=( 'pa_op',
+                       [POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32, 
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
+               pa_context_play_sample_with_proplist=( 'pa_op',
+                       [ POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
+                               POINTER(PA_PROPLIST), PA_CONTEXT_SUCCESS_CB_T, 
c_void_p ] ),
+
                pa_ext_stream_restore_test=( 'pa_op',
                        [POINTER(PA_CONTEXT), PA_EXT_STREAM_RESTORE_TEST_CB_T, 
c_void_p] ),
                pa_ext_stream_restore_read=( 'pa_op',
@@ -590,9 +600,12 @@
                        c_uint, c_int, PA_CONTEXT_SUCCESS_CB_T, c_void_p ] ),
                pa_ext_stream_restore_delete=( 'pa_op',
                        [POINTER(PA_CONTEXT), POINTER(c_char_p), 
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
-               pa_context_set_subscribe_callback=[POINTER(PA_CONTEXT), 
PA_SUBSCRIBE_CB_T, c_void_p],
+
+               pa_proplist_from_string=([c_str_p], POINTER(PA_PROPLIST)),
                pa_proplist_iterate=([POINTER(PA_PROPLIST), POINTER(c_void_p)], 
c_str_p),
                pa_proplist_gets=([POINTER(PA_PROPLIST), c_str_p], c_str_p),
+               pa_proplist_free=[POINTER(PA_PROPLIST)],
+
                pa_channel_map_init_mono=(
                        [POINTER(PA_CHANNEL_MAP)], (POINTER(PA_CHANNEL_MAP), 
'not_null') ),
                pa_channel_map_init_stereo=(
@@ -600,8 +613,7 @@
                pa_channel_map_snprint=([c_str_p, c_int, 
POINTER(PA_CHANNEL_MAP)], c_str_p),
                pa_channel_map_parse=(
                        [POINTER(PA_CHANNEL_MAP), c_str_p], 
(POINTER(PA_CHANNEL_MAP), 'not_null') ),
-               pa_proplist_from_string=([c_str_p], POINTER(PA_PROPLIST)),
-               pa_proplist_free=[POINTER(PA_PROPLIST)],
+
                pa_stream_new_with_proplist=(
                        [ POINTER(PA_CONTEXT), c_str_p,
                                POINTER(PA_SAMPLE_SPEC), 
POINTER(PA_CHANNEL_MAP), POINTER(PA_PROPLIST) ],
@@ -614,12 +626,7 @@
                pa_stream_peek=(
                        [POINTER(PA_STREAM), POINTER(c_void_p), 
POINTER(c_int)], 'int_check_ge0' ),
                pa_stream_drop=([POINTER(PA_STREAM)], 'int_check_ge0'),
-               pa_stream_disconnect=([POINTER(PA_STREAM)], 'int_check_ge0'),
-               pa_context_play_sample=( 'pa_op',
-                       [POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32, 
PA_CONTEXT_SUCCESS_CB_T, c_void_p] ),
-               pa_context_play_sample_with_proplist=( 'pa_op',
-                       [ POINTER(PA_CONTEXT), c_str_p, c_str_p, c_uint32,
-                               POINTER(PA_PROPLIST), PA_CONTEXT_SUCCESS_CB_T, 
c_void_p ] ) )
+               pa_stream_disconnect=([POINTER(PA_STREAM)], 'int_check_ge0') )
 
        class CallError(Exception): pass
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/pulsectl.py 
new/pulsectl-21.3.4/pulsectl/pulsectl.py
--- old/pulsectl-20.2.4/pulsectl/pulsectl.py    2020-02-27 16:27:49.000000000 
+0100
+++ new/pulsectl-21.3.4/pulsectl/pulsectl.py    2021-03-08 06:20:11.000000000 
+0100
@@ -127,6 +127,8 @@
                                        self.proplist[c.force_str(k)] = 
c.force_str(c.pa.proplist_gets(struct.proplist, k))
                        if hasattr(struct, 'volume'):
                                self.volume = 
self._get_wrapper(PulseVolumeInfo)(struct.volume)
+                       if hasattr(struct, 'base_volume'):
+                               self.base_volume = struct.base_volume / 
c.PA_VOLUME_NORM
                        if hasattr(struct, 'n_ports'):
                                cls_port = self._get_wrapper(PulsePortInfo)
                                self.port_list = list(
@@ -385,10 +387,11 @@
                c.pa.context_set_state_callback(self._ctx, self._pa_state_cb, 
None)
                c.pa.context_set_subscribe_callback(self._ctx, 
self._pa_subscribe_cb, None)
 
-       def connect(self, autospawn=False, wait=False):
+       def connect(self, autospawn=False, wait=False, timeout=None):
                '''Connect to pulseaudio server.
                        "autospawn" option will start new pulse daemon, if 
necessary.
-                       Specifying "wait" option will make function block until 
pulseaudio server appears.'''
+                       Specifying "wait" option will make function block until 
pulseaudio server appears.
+                       "timeout" (in seconds) will raise PulseError if 
connection not established within it.'''
                if self._loop_closed:
                        raise PulseError('Eventloop object was already'
                                ' destroyed and cannot be reused from this 
instance.')
@@ -398,7 +401,19 @@
                if wait: flags |= c.PA_CONTEXT_NOFAIL
                try: c.pa.context_connect(self._ctx, self.server, flags, None)
                except c.pa.CallError: self.connected = False
-               while self.connected is None: self._pulse_iterate()
+               if not timeout: # simplier process
+                       while self.connected is None: self._pulse_iterate()
+               else:
+                       self._loop_stop, delta, ts_deadline = True, 1, 
c.mono_time() + timeout
+                       while self.connected is None:
+                               delta = ts_deadline - c.mono_time()
+                               self._pulse_poll(delta)
+                               if delta <= 0: break
+                       self._loop_stop = False
+                       if not self.connected:
+                               c.pa.context_disconnect(self._ctx)
+                               while self.connected is not False: 
self._pulse_iterate()
+                               raise PulseError('Timed-out connecting to 
pulseaudio server [{:,.1f}s]'.format(timeout))
                if self.connected is False: raise PulseError('Failed to connect 
to pulseaudio server')
 
        def disconnect(self):
@@ -490,7 +505,7 @@
                        ts_deadline = timeout and (ts + timeout)
                        while True:
                                delay = max(0, int((ts_deadline - ts) * 1000)) 
if ts_deadline else -1
-                               c.pa.mainloop_prepare(loop, delay) # usec
+                               c.pa.mainloop_prepare(loop, delay) # delay in ms
                                c.pa.mainloop_poll(loop)
                                if self._loop_closed: break # interrupted by 
close() or such
                                c.pa.mainloop_dispatch(loop)
@@ -512,7 +527,7 @@
                else: data_list.append(info_cls(info[0]))
 
        def _pulse_get_list(cb_t, pulse_func, info_cls, singleton=False, 
index_arg=True):
-               def _wrapper(self, index=None):
+               def _wrapper_method(self, index=None):
                        data = list()
                        with self._pulse_op_cb(raw=True) as cb:
                                cb = cb_t(
@@ -525,21 +540,11 @@
                        if index is not None or singleton:
                                if not data: raise PulseIndexError(index)
                                data, = data
-                       return _wrapper.func(self, data) if _wrapper.func else 
data
-               _wrapper.func = None
-               def _add_wrap_doc(func):
-                       func.__name__ = '...'
-                       func.__doc__ = 'Signature: func({})'.format(
-                               '' if pulse_func.__name__.endswith('_list') or 
singleton or not index_arg else 'index' )
-               def _decorator_or_method(func_or_self=None, index=None):
-                       if func_or_self.__class__.__name__ == 'Pulse':
-                               return _wrapper(func_or_self, index)
-                       elif func_or_self: _wrapper.func = func_or_self
-                       assert index is None, index
-                       return _wrapper
-               _add_wrap_doc(_wrapper)
-               _add_wrap_doc(_decorator_or_method)
-               return _decorator_or_method
+                       return data
+               _wrapper_method.__name__ = '...'
+               _wrapper_method.__doc__ = 'Signature: func({})'.format(
+                       '' if pulse_func.__name__.endswith('_list') or 
singleton or not index_arg else 'index' )
+               return _wrapper_method
 
        get_sink_by_name = _pulse_get_list(
                c.PA_SINK_INFO_CB_T,
@@ -780,6 +785,10 @@
                self.volume_set(obj, obj.volume)
 
        def volume_get_all_chans(self, obj):
+               # Purpose of this func can be a bit confusing, being here next 
to set/change ones
+               '''Get "flat" volume float value for info-object as a mean of 
all channel values.
+                       Note that this DOES NOT query any kind of updated 
values from libpulse,
+                               and simply returns value(s) stored in passed 
object, i.e. same ones for same object.'''
                assert_pulse_object(obj)
                return obj.volume.value_flat
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl/tests/dummy_instance.py 
new/pulsectl-21.3.4/pulsectl/tests/dummy_instance.py
--- old/pulsectl-20.2.4/pulsectl/tests/dummy_instance.py        2020-02-27 
16:30:24.000000000 +0100
+++ new/pulsectl-21.3.4/pulsectl/tests/dummy_instance.py        2021-03-08 
06:30:11.000000000 +0100
@@ -2,7 +2,7 @@
 from __future__ import unicode_literals, print_function
 
 import itertools as it, operator as op, functools as ft
-import unittest, contextlib, atexit, signal
+import unittest, contextlib, atexit, signal, threading, select, errno
 import os, sys, time, subprocess, tempfile, shutil, socket
 
 if sys.version_info.major > 2: unicode = str
@@ -20,6 +20,59 @@
                super(adict, self).__init__(*args, **kws)
                self.__dict__ = self
 
+
+def start_sock_delay_thread(*args):
+       # Simple py2/py3 hack to simulate slow network and test conn timeouts
+       thread = threading.Thread(target=_sock_delay_thread, args=args)
+       thread.daemon = True
+       thread.start()
+       return thread
+
+def _sock_delay_thread(
+               ev_ready, ev_done, ev_disco, bind, connect, delay, block=0.1 ):
+       sl = s = c = None
+       try:
+               sl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+               sl.bind(bind)
+               sl.listen(1)
+               ev_ready.set()
+               sl.settimeout(block)
+               while True:
+                       ev_disco.clear()
+                       while True:
+                               try: s, addr = sl.accept()
+                               except socket.timeout: pass
+                               else: break
+                               if ev_done.is_set(): return
+                       ts0 = time.time()
+                       c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                       c.connect(connect)
+                       s.setblocking(False)
+                       c.setblocking(False)
+                       time.sleep(min(delay, max(0, delay - (time.time() - 
ts0))))
+                       def _send_data(src, dst, bs=8*2**10):
+                               while True:
+                                       try:
+                                               buff = src.recv(bs)
+                                               if not buff: break
+                                               dst.sendall(buff) # just 
assuming it won't get full here
+                                       except socket.error as err:
+                                               if err.errno != errno.EAGAIN: 
return True
+                                               break
+                       while True:
+                               r,w,x = select.select([s,c], [], [s,c], block)
+                               if x or ev_done.is_set(): return
+                               if ev_disco.is_set(): break
+                               if not (r or x): continue
+                               if c in r and _send_data(c, s): break
+                               if s in r and _send_data(s, c): break
+                       s, c = s.close(), c.close()
+       finally:
+               if c: c.close()
+               if s: s.close()
+               if sl: sl.close()
+
+
 def dummy_pulse_init(info=None):
        if not info: info = adict(proc=None, tmp_dir=None)
        try: _dummy_pulse_init(info)
@@ -39,6 +92,7 @@
        #  t2% PA_TMPDIR=/tmp/pulsectl-tests PA_REUSE=1234,1235 python -m -m 
unittest pulsectl.tests.all
        env_tmpdir, env_debug, env_reuse = map(
                os.environ.get, ['PA_TMPDIR', 'PA_DEBUG', 'PA_REUSE'] )
+       if not os.environ.get('PATH'): os.environ['PATH'] = 
'/usr/local/bin:/usr/bin:/bin'
 
        tmp_base = env_tmpdir or info.get('tmp_dir')
        if not tmp_base:
@@ -49,8 +103,9 @@
 
        # Pick some random available localhost ports
        if not info.get('sock_unix'):
-               bind = ( ['127.0.0.1', 0, socket.AF_INET],
-                       ['::1', 0, socket.AF_INET6], ['127.0.0.1', 0, 
socket.AF_INET] )
+               bind = (
+                       ['127.0.0.1', 0, socket.AF_INET], ['::1', 0, 
socket.AF_INET6],
+                       ['127.0.0.1', 0, socket.AF_INET], ['127.0.0.1', 0, 
socket.AF_INET] )
                for n, spec in enumerate(bind):
                        if env_reuse:
                                spec[1] = int(env_reuse.split(':')[n])
@@ -64,15 +119,29 @@
                        sock_unix='unix:{}'.format(tmp_path('pulse', 'native')),
                        sock_tcp4='tcp4:{}:{}'.format(bind[0][0], bind[0][1]),
                        sock_tcp6='tcp6:[{}]:{}'.format(bind[1][0], bind[1][1]),
-                       sock_tcp_cli=tuple(bind[2][:2]) )
+                       sock_tcp_delay='tcp4:{}:{}'.format(bind[2][0], 
bind[2][1]),
+                       sock_tcp_delay_src=tuple(bind[2][:2]),
+                       sock_tcp_delay_dst=tuple(bind[0][:2]),
+                       sock_tcp_cli=tuple(bind[3][:2]) )
+
+       if not info.get('sock_delay_thread'):
+               ev_ready, ev_exit, ev_disco = (threading.Event() for n in 
range(3))
+               delay = info.sock_delay = 0.5
+               info.sock_delay_thread_ready = ev_ready
+               info.sock_delay_thread_disco = ev_disco
+               info.sock_delay_thread_exit = ev_exit
+               info.sock_delay_thread = start_sock_delay_thread(
+                       ev_ready, ev_exit, ev_disco,
+                       info.sock_tcp_delay_src, info.sock_tcp_delay_dst, delay 
)
 
        if info.proc and info.proc.poll() is not None: info.proc = None
        if not env_reuse and not info.get('proc'):
-               env = dict(XDG_RUNTIME_DIR=tmp_base, PULSE_STATE_PATH=tmp_base)
+               env = dict( PATH=os.environ['PATH'],
+                       XDG_RUNTIME_DIR=tmp_base, PULSE_STATE_PATH=tmp_base )
                log_level = 'error' if not env_debug else 'debug'
                info.proc = subprocess.Popen(
-                       [ 'pulseaudio', '--daemonize=no', '--fail',
-                               '-nF', '/dev/stdin', '--exit-idle-time=-1', 
'--log-level={}'.format(log_level) ],
+                       ['pulseaudio', '--daemonize=no', '--fail',
+                               '-nF', '/dev/stdin', '--exit-idle-time=-1', 
'--log-level={}'.format(log_level)],
                        env=env, stdin=subprocess.PIPE )
                bind4, bind6 = info.sock_tcp4.split(':'), 
info.sock_tcp6.rsplit(':', 1)
                bind4, bind6 = (bind4[1], bind4[2]), (bind6[0].split(':', 
1)[1].strip('[]'), bind6[1])
@@ -80,7 +149,6 @@
                                'module-augment-properties',
 
                                'module-default-device-restore',
-                               'module-rescue-streams',
                                'module-always-sink',
                                'module-intended-roles',
                                'module-suspend-on-idle',
@@ -126,6 +194,9 @@
                        except OSError: pass
                info.proc.wait()
                info.proc = None
+       if info.get('sock_delay_thread'):
+               info.sock_delay_thread_exit.set()
+               info.sock_delay_thread = info.sock_delay_thread.join()
        if info.tmp_dir:
                shutil.rmtree(info.tmp_dir, ignore_errors=True)
                info.tmp_dir = None
@@ -149,6 +220,7 @@
        @classmethod
        def tearDownClass(cls):
                dummy_pulse_cleanup(cls.instance_info)
+               cls.proc = cls.tmp_dir = None
 
 
        # Fuzzy float comparison is necessary for volume,
@@ -182,6 +254,34 @@
                with pulsectl.Pulse('t', server=self.sock_tcp6) as pulse: si6 = 
pulse.server_info()
                self.assertEqual(vars(si), vars(si6))
 
+       def test_connect_timeout(self):
+               self.sock_delay_thread_ready.wait(timeout=2)
+               with pulsectl.Pulse('t', server=self.sock_unix) as pulse: si = 
pulse.server_info()
+
+               with pulsectl.Pulse('t', server=self.sock_tcp_delay) as pulse: 
sid = pulse.server_info()
+               self.assertEqual(vars(si), vars(sid))
+               self.sock_delay_thread_disco.set()
+
+               with pulsectl.Pulse('t', server=self.sock_tcp_delay, 
connect=False) as pulse:
+                       pulse.connect()
+                       sid = pulse.server_info()
+               self.assertEqual(vars(si), vars(sid))
+               self.sock_delay_thread_disco.set()
+
+               with pulsectl.Pulse('t', server=self.sock_tcp_delay, 
connect=False) as pulse:
+                       pulse.connect(1.0)
+                       sid = pulse.server_info()
+               self.assertEqual(vars(si), vars(sid))
+               self.sock_delay_thread_disco.set()
+
+               with pulsectl.Pulse('t', server=self.sock_tcp_delay, 
connect=False) as pulse:
+                       with self.assertRaises(pulsectl.PulseError): 
pulse.connect(timeout=0.1)
+                       self.sock_delay_thread_disco.set()
+                       pulse.connect(timeout=1.0)
+                       sid = pulse.server_info()
+               self.assertEqual(vars(si), vars(sid))
+               self.sock_delay_thread_disco.set()
+
        def test_server_info(self):
                with pulsectl.Pulse('t', server=self.sock_unix) as pulse:
                        si, srcs, sinks = pulse.server_info(), 
pulse.source_list(), pulse.sink_list()
@@ -344,7 +444,8 @@
                        pulse.event_callback_set(stream_ev_cb)
 
                        paplay = subprocess.Popen(
-                               ['paplay', '--raw', '/dev/zero'], 
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+                               ['paplay', '--raw', '/dev/zero'], env=dict(
+                                       PATH=os.environ['PATH'], 
XDG_RUNTIME_DIR=self.tmp_dir ) )
                        try:
                                if not stream_started: pulse.event_listen()
                                self.assertTrue(bool(stream_started))
@@ -443,7 +544,8 @@
                        pulse.event_callback_set(stream_ev_cb)
 
                        paplay = subprocess.Popen(
-                               ['paplay', '--raw', '/dev/zero'], 
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+                               ['paplay', '--raw', '/dev/zero'], env=dict(
+                                       PATH=os.environ['PATH'], 
XDG_RUNTIME_DIR=self.tmp_dir ) )
                        try:
                                if not stream_started: pulse.event_listen()
                                stream_idx, = stream_started
@@ -488,7 +590,8 @@
                        pulse.event_callback_set(stream_ev_cb)
 
                        paplay = subprocess.Popen(
-                               ['paplay', '--raw', '/dev/urandom'], 
env=dict(XDG_RUNTIME_DIR=self.tmp_dir) )
+                               ['paplay', '--raw', '/dev/urandom'], env=dict(
+                                       PATH=os.environ['PATH'], 
XDG_RUNTIME_DIR=self.tmp_dir ) )
                        try:
                                if not stream_started: pulse.event_listen()
                                stream_idx, = stream_started
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/pulsectl.egg-info/PKG-INFO 
new/pulsectl-21.3.4/pulsectl.egg-info/PKG-INFO
--- old/pulsectl-20.2.4/pulsectl.egg-info/PKG-INFO      2020-02-27 
17:20:20.000000000 +0100
+++ new/pulsectl-21.3.4/pulsectl.egg-info/PKG-INFO      2021-03-08 
06:38:22.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pulsectl
-Version: 20.2.4
+Version: 21.3.4
 Summary: Python high-level interface and ctypes-based bindings for PulseAudio 
(libpulse)
 Home-page: http://github.com/mk-fg/python-pulse-control
 Author: George Filipkin, Mike Kazantsev
@@ -9,14 +9,19 @@
 Description: python-pulse-control (pulsectl module)
         ======================================
         
-        Python (3.x and 2.x) high-level interface and ctypes-based bindings for
-        PulseAudio_ (libpulse), mostly focused on mixer-like controls and
-        introspection-related operations (as opposed to e.g. submitting sound 
samples to
-        play, player-like client).
+        Python (3.x and 2.x) blocking high-level interface and ctypes-based 
bindings
+        for PulseAudio_ (libpulse), to use in a simple synchronous code.
+        
+        Wrappers are mostly for mixer-like controls and introspection-related 
operations,
+        as opposed to e.g. submitting sound samples to play and player-like 
client.
+        
+        For async version to use with asyncio_, see `pulsectl-asyncio`_ 
project instead.
         
         Originally forked from pulsemixer_ project, which had this code 
bundled.
         
         .. _PulseAudio: https://wiki.freedesktop.org/www/Software/PulseAudio/
+        .. _asyncio: https://docs.python.org/3/library/asyncio.html
+        .. _pulsectl-asyncio: https://pypi.org/project/pulsectl-asyncio/
         .. _pulsemixer: https://github.com/GeorgeFilipkin/pulsemixer/
         
         |
@@ -259,7 +264,7 @@
         ````````````````````````````
         
         libpulse clients always work as an event loop, though this module 
kinda hides
-        it, presenting a more conventional blocking interface.
+        it, presenting a more old-style blocking interface.
         
         So what happens on any call (e.g. ``pulse.mute(...)``) is:
         
@@ -271,8 +276,8 @@
         and second step here.
         
         Which means that any pulse calls from callback function can't be used 
when
-        ``event_listen()`` (or any other pulse call through this module, for 
that
-        matter) waits for return value and runs libpulse loop already.
+        ``event_listen()`` (or any other pulse call through this module, for 
that matter)
+        waits for return value and runs libpulse loop already.
         
         One can raise PulseLoopStop exception there to make ``event_listen()`` 
return,
         run whatever pulse calls after that, then re-start the 
``event_listen()`` thing.
@@ -287,15 +292,13 @@
         to create a mutex around step-2 (run event loop) from the list above, 
so
         multiple threads won't do it at the same time.
         
-        For proper eventloop integration (think twisted or asyncio), 
``_pulse_get_list``
-        / ``_pulse_method_call`` wrappers should be overidden to not run pulse 
loop, but
-        rather return "future" object and register a set of fd's (as passed to
-        ``set_poll_func`` callback) with eventloop.
-        Never needed that, so not implemented in the module, but should be 
rather easy
-        to implement on top of it, as described.
+        For proper python eventloop integration (think twisted or asyncio),
+        use `pulsectl-asyncio`_ module instead.
         
-        See more info/discussion and code examples in `github #11
-        <https://github.com/mk-fg/python-pulse-control/issues/11>`_.
+        There are also some tricks mentioned in `github #11
+        <https://github.com/mk-fg/python-pulse-control/issues/11>`_ to 
shoehorn this
+        module into async apps, but even with non-asyncio eventloop, starting 
from
+        pulsectl-asyncio would probably be much easier.
         
         
         Tests
@@ -408,6 +411,8 @@
         
         * pulsemixer_ - initial source for this project (embedded in the tool).
         
+        * `pulsectl-asyncio`_ - similar libpulse wrapper to this one, but for 
async python code.
+        
         * `libpulseaudio 
<https://github.com/thelinuxdude/python-pulseaudio/>`_ -
           different libpulse bindings module, more low-level, auto-generated 
from
           pulseaudio header files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pulsectl-20.2.4/setup.py new/pulsectl-21.3.4/setup.py
--- old/pulsectl-20.2.4/setup.py        2020-02-27 16:30:38.000000000 +0100
+++ new/pulsectl-21.3.4/setup.py        2021-03-08 06:31:07.000000000 +0100
@@ -13,7 +13,7 @@
 setup(
 
        name = 'pulsectl',
-       version = '20.2.4',
+       version = '21.3.4',
        author = 'George Filipkin, Mike Kazantsev',
        author_email = '[email protected]',
        license = 'MIT',

Reply via email to