Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-mpv for 
openSUSE:Factory checked in at 2023-02-28 12:48:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-mpv (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-mpv.new.31432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-mpv"

Tue Feb 28 12:48:41 2023 rev:19 rq:1068003 version:1.0.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-mpv/python-python-mpv.changes      
2023-02-27 12:55:22.047422153 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-python-mpv.new.31432/python-python-mpv.changes
   2023-02-28 12:49:02.588636741 +0100
@@ -1,0 +2,6 @@
+Mon Feb 27 11:45:28 UTC 2023 - Luigi Baldoni <aloi...@gmx.com>
+
+- Update to version 1.0.3
+  * Move to new old pypi project name
+
+-------------------------------------------------------------------

Old:
----
  python-mpv-1.0.2.tar.gz

New:
----
  python-mpv-1.0.3.tar.gz

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

Other differences:
------------------
++++++ python-python-mpv.spec ++++++
--- /var/tmp/diff_new_pack.Tl3XLE/_old  2023-02-28 12:49:03.172640536 +0100
+++ /var/tmp/diff_new_pack.Tl3XLE/_new  2023-02-28 12:49:03.180640588 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-python-mpv
-Version:        1.0.2
+Version:        1.0.3
 Release:        0
 Summary:        Python interface to the mpv media player
 License:        GPL-2.0-or-later OR LGPL-2.1-or-later

++++++ python-mpv-1.0.2.tar.gz -> python-mpv-1.0.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/PKG-INFO 
new/python-mpv-1.0.3/PKG-INFO
--- old/python-mpv-1.0.2/PKG-INFO       2023-02-26 15:12:58.527600800 +0100
+++ new/python-mpv-1.0.3/PKG-INFO       2023-02-27 10:31:30.520204800 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-mpv
-Version: 1.0.2
+Version: 1.0.3
 Summary: A python interface to the mpv media player
 Author-email: jaseg <m...@jaseg.de>
 License: GPLv2+ or LGPLv2.1+
@@ -40,7 +40,7 @@
 
 .. code:: bash
 
-    pip install python-mpv
+    pip install mpv
 
 
 ...though you can also realistically just copy `mpv.py`_ into your project as 
it's all nicely contained in one file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/README.rst 
new/python-mpv-1.0.3/README.rst
--- old/python-mpv-1.0.2/README.rst     2023-02-26 13:13:42.000000000 +0100
+++ new/python-mpv-1.0.3/README.rst     2023-02-27 10:26:59.000000000 +0100
@@ -11,7 +11,7 @@
 
 .. code:: bash
 
-    pip install python-mpv
+    pip install mpv
 
 
 ...though you can also realistically just copy `mpv.py`_ into your project as 
it's all nicely contained in one file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/mpv.py new/python-mpv-1.0.3/mpv.py
--- old/python-mpv-1.0.2/mpv.py 2023-02-26 13:13:42.000000000 +0100
+++ new/python-mpv-1.0.3/mpv.py 2023-02-27 10:26:59.000000000 +0100
@@ -20,6 +20,7 @@
 from ctypes import *
 import ctypes.util
 import threading
+import queue
 import os
 import sys
 from warnings import warn
@@ -880,6 +881,7 @@
         self.register_stream_protocol('python', self._python_stream_open)
         self._python_streams = {}
         self._python_stream_catchall = None
+        self._exception_futures = set()
         self.overlay_ids = set()
         self.overlays = {}
         if loglevel is not None or log_handler is not None:
@@ -891,6 +893,20 @@
         else:
             self._event_thread = None
 
+    @contextmanager
+    def _enqueue_exceptions(self):
+        try:
+            yield
+        except Exception as e:
+            for fut in self._exception_futures:
+                try:
+                    fut.set_exception(e)
+                    break
+                except InvalidStateError:
+                    pass
+            else:
+                warn(f'Unhandled exception on python-mpv event loop: 
{e}\n{traceback.format_exc()}', RuntimeWarning)
+
     def _loop(self):
         for event in _event_generator(self._event_handle):
             try:
@@ -901,45 +917,51 @@
                         self._core_shutdown = True
 
                 for callback in self._event_callbacks:
-                    callback(event)
+                    with self._enqueue_exceptions():
+                        callback(event)
 
                 if eid == MpvEventID.PROPERTY_CHANGE:
                     pc = event.data
                     name, value, _fmt = pc.name, pc.value, pc.format
                     for handler in self._property_handlers[name]:
-                        handler(name, value)
+                        with self._enqueue_exceptions():
+                            handler(name, value)
 
                 if eid == MpvEventID.LOG_MESSAGE and self._log_handler is not 
None:
                     ev = event.data
-                    self._log_handler(ev.level, ev.prefix, ev.text)
+                    with self._enqueue_exceptions():
+                        self._log_handler(ev.level, ev.prefix, ev.text)
 
                 if eid == MpvEventID.CLIENT_MESSAGE:
                     # {'event': {'args': ['key-binding', 'foo', 'u-', 'g']}, 
'reply_userdata': 0, 'error': 0, 'event_id': 16}
                     target, *args = event.data.args
                     target = target.decode("utf-8")
                     if target in self._message_handlers:
-                        self._message_handlers[target](*args)
+                        with self._enqueue_exceptions():
+                            self._message_handlers[target](*args)
 
                 if eid == MpvEventID.COMMAND_REPLY:
                     key = event.reply_userdata
                     callback = self._command_reply_callbacks.pop(key, None)
                     if callback:
-                        callback(ErrorCode.exception_for_ec(event.error), 
event.data)
+                        with self._enqueue_exceptions():
+                            callback(ErrorCode.exception_for_ec(event.error), 
event.data)
 
                 if eid == MpvEventID.QUEUE_OVERFLOW:
                     # cache list, since error handlers will unregister 
themselves
                     for cb in list(self._command_reply_callbacks.values()):
-                        cb(EventOverflowError('libmpv event queue has flown 
over because events have not been processed fast enough'), None)
+                        with self._enqueue_exceptions():
+                            cb(EventOverflowError('libmpv event queue has 
flown over because events have not been processed fast enough'), None)
 
                 if eid == MpvEventID.SHUTDOWN:
                     _mpv_destroy(self._event_handle)
                     for cb in list(self._command_reply_callbacks.values()):
-                        cb(ShutdownError('libmpv core has been shutdown'), 
None)
+                        with self._enqueue_exceptions():
+                            cb(ShutdownError('libmpv core has been shutdown'), 
None)
                     return
 
             except Exception as e:
-                print('Exception inside python-mpv event loop:', 
file=sys.stderr)
-                traceback.print_exc()
+                warn(f'Unhandled {e} inside python-mpv event 
loop!\n{traceback.format_exc()}', RuntimeWarning)
 
     @property
     def core_shutdown(self):
@@ -953,35 +975,35 @@
         if self._core_shutdown:
             raise ShutdownError('libmpv core has been shutdown')
 
-    def wait_until_paused(self, timeout=None):
+    def wait_until_paused(self, timeout=None, catch_errors=True):
         """Waits until playback of the current title is paused or done. Raises 
a ShutdownError if the core is shutdown while
         waiting."""
-        self.wait_for_property('core-idle', timeout=timeout)
+        self.wait_for_property('core-idle', timeout=timeout, 
catch_errors=catch_errors)
 
-    def wait_for_playback(self, timeout=None):
+    def wait_for_playback(self, timeout=None, catch_errors=True):
         """Waits until playback of the current title is finished. Raises a 
ShutdownError if the core is shutdown while
         waiting.
         """
-        self.wait_for_event('end_file', timeout=timeout)
+        self.wait_for_event('end_file', timeout=timeout, 
catch_errors=catch_errors)
 
-    def wait_until_playing(self, timeout=None):
+    def wait_until_playing(self, timeout=None, catch_errors=True):
         """Waits until playback of the current title has started. Raises a 
ShutdownError if the core is shutdown while
         waiting."""
-        self.wait_for_property('core-idle', lambda idle: not idle, 
timeout=timeout)
+        self.wait_for_property('core-idle', lambda idle: not idle, 
timeout=timeout, catch_errors=catch_errors)
 
-    def wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=True, timeout=None):
+    def wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=True, timeout=None, catch_errors=True):
         """Waits until ``cond`` evaluates to a truthy value on the named 
property. This can be used to wait for
         properties such as ``idle_active`` indicating the player is done with 
regular playback and just idling around.
         Raises a ShutdownError when the core is shutdown while waiting.
         """
-        with self.prepare_and_wait_for_property(name, cond, level_sensitive, 
timeout=timeout) as result:
+        with self.prepare_and_wait_for_property(name, cond, level_sensitive, 
timeout=timeout, catch_errors=catch_errors) as result:
             pass
         return result.result()
 
-    def wait_for_shutdown(self, timeout=None):
+    def wait_for_shutdown(self, timeout=None, catch_errors=True):
         '''Wait for core to shutdown (e.g. through quit() or terminate()).'''
         try:
-            self.wait_for_event(None, timeout=timeout)
+            self.wait_for_event(None, timeout=timeout, 
catch_errors=catch_errors)
         except ShutdownError:
             return
 
@@ -999,7 +1021,7 @@
         return shutdown_handler.unregister_mpv_events
 
     @contextmanager
-    def prepare_and_wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=True, timeout=None):
+    def prepare_and_wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=True, timeout=None, catch_errors=True):
         """Context manager that waits until ``cond`` evaluates to a truthy 
value on the named property. See
         prepare_and_wait_for_event for usage.
         Raises a ShutdownError when the core is shutdown while waiting. 
Re-raises any errors inside ``cond``.
@@ -1023,6 +1045,9 @@
 
         try:
             result.set_running_or_notify_cancel()
+            if catch_errors:
+                self._exception_futures.add(result)
+
             yield result
 
             rv = cond(getattr(self, name.replace('-', '_')))
@@ -1035,18 +1060,19 @@
         finally:
             err_unregister()
             self.unobserve_property(name, observer)
+            self._exception_futures.discard(result)
 
-    def wait_for_event(self, *event_types, cond=lambda evt: True, 
timeout=None):
+    def wait_for_event(self, *event_types, cond=lambda evt: True, 
timeout=None, catch_errors=True):
         """Waits for the indicated event(s). If cond is given, waits until 
cond(event) is true. Raises a ShutdownError
         if the core is shutdown while waiting. This also happens when 
'shutdown' is in event_types. Re-raises any error
         inside ``cond``.
         """
-        with self.prepare_and_wait_for_event(*event_types, cond=cond, 
timeout=timeout) as result:
+        with self.prepare_and_wait_for_event(*event_types, cond=cond, 
timeout=timeout, catch_errors=catch_errors) as result:
             pass
         return result.result()
 
     @contextmanager
-    def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: True, 
timeout=None):
+    def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: True, 
timeout=None, catch_errors=True):
         """Context manager that waits for the indicated event(s) like 
wait_for_event after running. If cond is given,
         waits until cond(event) is true. Raises a ShutdownError if the core is 
shutdown while waiting. This also happens
         when 'shutdown' is in event_types. Re-raises any error inside ``cond``.
@@ -1064,7 +1090,6 @@
 
         @self.event_callback(*event_types)
         def target_handler(evt):
-
             try:
                 rv = cond(evt)
                 if rv:
@@ -1081,13 +1106,18 @@
 
         try:
             result.set_running_or_notify_cancel()
+            if catch_errors:
+                self._exception_futures.add(result)
+
             yield result
+
             self.check_core_alive()
             result.result(timeout)
 
         finally:
             err_unregister()
             target_handler.unregister_mpv_events()
+            self._exception_futures.discard(result)
 
     def __del__(self):
         if self.handle:
@@ -1772,32 +1802,68 @@
                     frontend = open_fn(uri.decode('utf-8'))
                 except ValueError:
                     return ErrorCode.LOADING_FAILED
+                except Exception as e:
+                    for fut in self._exception_futures:
+                        try:
+                            fut.set_exception(e)
+                            break
+                        except InvalidStateError:
+                            pass
+                    else:
+                        warnings.warn(f'Unhandled exception {e} inside stream 
open callback for URI {uri}\n{traceback.format_exc()}')
+
 
-                def read_backend(_userdata, buf, bufsize):
-                    data = frontend.read(bufsize)
-                    for i in range(len(data)):
-                        buf[i] = data[i]
-                    return len(data)
+
+                    return ErrorCode.LOADING_FAILED
 
                 cb_info.contents.cookie = None
+
+                def read_backend(_userdata, buf, bufsize):
+                    with self._enqueue_exceptions():
+                        data = frontend.read(bufsize)
+                        for i in range(len(data)):
+                            buf[i] = data[i]
+                        return len(data)
+                    return -1
                 read = cb_info.contents.read = StreamReadFn(read_backend)
-                close = cb_info.contents.close = StreamCloseFn(lambda 
_userdata: frontend.close())
+
+                def close_backend(_userdata):
+                    with self._enqueue_exceptions():
+                        del self._stream_protocol_frontends[proto][uri]
+                        if hasattr(frontend, 'close'):
+                            frontend.close()
+                close = cb_info.contents.close = StreamCloseFn(close_backend)
 
                 seek, size, cancel = None, None, None
+
                 if hasattr(frontend, 'seek'):
-                    seek = cb_info.contents.seek = StreamSeekFn(lambda 
_userdata, offx: frontend.seek(offx))
+                    def seek_backend(_userdata, offx):
+                        with self._enqueue_exceptions():
+                            return frontend.seek(offx)
+                        return ErrorCode.GENERIC
+                    seek = cb_info.contents.seek = StreamSeekFn(seek_backend)
+
                 if hasattr(frontend, 'size') and frontend.size is not None:
-                    size = cb_info.contents.size = StreamSizeFn(lambda 
_userdata: frontend.size)
+                    def size_backend(_userdata):
+                        with self._enqueue_exceptions():
+                            return frontend.size
+                        return 0
+                    size = cb_info.contents.size = StreamSizeFn(size_backend)
+
                 if hasattr(frontend, 'cancel'):
-                    cancel = cb_info.contents.cancel = StreamCancelFn(lambda 
_userdata: frontend.cancel())
+                    def cancel_backend(_userdata):
+                        with self._enqueue_exceptions():
+                            frontend.cancel()
+                    cancel = cb_info.contents.cancel = 
StreamCancelFn(cancel_backend)
 
-                # keep frontend and callbacks in memory forever (TODO)
+                # keep frontend and callbacks in memory until closed
                 frontend._registered_callbacks = [read, close, seek, size, 
cancel]
                 self._stream_protocol_frontends[proto][uri] = frontend
                 return 0
 
             if proto in self._stream_protocol_cbs:
                 raise KeyError('Stream protocol already registered')
+            # keep backend in memory forever
             self._stream_protocol_cbs[proto] = [open_backend]
             _mpv_stream_cb_add_ro(self.handle, proto.encode('utf-8'), 
c_void_p(), open_backend)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/pyproject.toml 
new/python-mpv-1.0.3/pyproject.toml
--- old/python-mpv-1.0.2/pyproject.toml 2023-02-26 14:06:41.000000000 +0100
+++ new/python-mpv-1.0.3/pyproject.toml 2023-02-27 10:31:22.000000000 +0100
@@ -7,7 +7,7 @@
 
 [project]
 name = "python-mpv"
-version = "v1.0.2"
+version = "v1.0.3"
 description = "A python interface to the mpv media player"
 readme = "README.rst"
 authors = [{name = "jaseg", email = "m...@jaseg.de"}]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/python_mpv.egg-info/PKG-INFO 
new/python-mpv-1.0.3/python_mpv.egg-info/PKG-INFO
--- old/python-mpv-1.0.2/python_mpv.egg-info/PKG-INFO   2023-02-26 
15:12:58.000000000 +0100
+++ new/python-mpv-1.0.3/python_mpv.egg-info/PKG-INFO   2023-02-27 
10:31:30.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-mpv
-Version: 1.0.2
+Version: 1.0.3
 Summary: A python interface to the mpv media player
 Author-email: jaseg <m...@jaseg.de>
 License: GPLv2+ or LGPLv2.1+
@@ -40,7 +40,7 @@
 
 .. code:: bash
 
-    pip install python-mpv
+    pip install mpv
 
 
 ...though you can also realistically just copy `mpv.py`_ into your project as 
it's all nicely contained in one file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-1.0.2/tests/test_mpv.py 
new/python-mpv-1.0.3/tests/test_mpv.py
--- old/python-mpv-1.0.2/tests/test_mpv.py      2023-02-26 13:13:42.000000000 
+0100
+++ new/python-mpv-1.0.3/tests/test_mpv.py      2023-02-27 10:26:59.000000000 
+0100
@@ -535,6 +535,115 @@
         m.terminate()
         disp.stop()
 
+    def test_stream_open_exception(self):
+        disp = Xvfb()
+        disp.start()
+        m = mpv.MPV(vo=testvo, video=False)
+
+        @m.register_stream_protocol('raiseerror')
+        def open_fn(uri):
+            raise SystemError()
+
+        waiting = threading.Semaphore()
+        result = Future()
+        def run():
+            result.set_running_or_notify_cancel()
+            try:
+                waiting.release()
+                m.wait_for_playback()
+                result.set_result(False)
+            except SystemError:
+                result.set_result(True)
+            except Exception:
+                result.set_result(False)
+
+        t = threading.Thread(target=run, daemon=True)
+        t.start()
+
+        with waiting:
+            time.sleep(0.2)
+            m.play('raiseerror://foo')
+
+        m.wait_for_playback(catch_errors=False)
+        try:
+            assert result.result()
+        finally:
+            m.terminate()
+            disp.stop()
+
+    def test_python_stream_exception(self):
+        disp = Xvfb()
+        disp.start()
+        m = mpv.MPV(vo=testvo)
+
+        @m.python_stream('foo')
+        def foo_gen():
+            with open(TESTVID, 'rb') as f:
+                yield f.read(100)
+                raise SystemError()
+
+        waiting = threading.Semaphore()
+        result = Future()
+        def run():
+            result.set_running_or_notify_cancel()
+            try:
+                waiting.release()
+                m.wait_for_playback()
+                result.set_result(False)
+            except SystemError:
+                result.set_result(True)
+            except Exception:
+                result.set_result(False)
+
+        t = threading.Thread(target=run, daemon=True)
+        t.start()
+
+        with waiting:
+            time.sleep(0.2)
+            m.play('python://foo')
+
+        m.wait_for_playback(catch_errors=False)
+        try:
+            assert result.result()
+        finally:
+            m.terminate()
+            disp.stop()
+
+    def test_stream_open_forward(self):
+        disp = Xvfb()
+        disp.start()
+        m = mpv.MPV(vo=testvo, video=False)
+
+        @m.register_stream_protocol('raiseerror')
+        def open_fn(uri):
+            raise ValueError()
+
+        waiting = threading.Semaphore()
+        result = Future()
+        def run():
+            result.set_running_or_notify_cancel()
+            try:
+                waiting.release()
+                m.wait_for_playback()
+                result.set_result(True)
+            except Exception:
+                result.set_result(False)
+
+        t = threading.Thread(target=run, daemon=True)
+        t.start()
+
+        with waiting:
+            time.sleep(0.2)
+            m.play('raiseerror://foo')
+
+        m.wait_for_playback(catch_errors=False)
+        try:
+            assert result.result()
+        finally:
+            m.terminate()
+            disp.stop()
+
+
 
 class TestLifecycle(unittest.TestCase):
     def test_create_destroy(self):

Reply via email to