Hello community,

here is the log from the commit of package python-python-mpv for 
openSUSE:Factory checked in at 2020-07-20 21:02:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-mpv (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-mpv.new.3592 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-mpv"

Mon Jul 20 21:02:36 2020 rev:14 rq:821925 version:0.5.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-mpv/python-python-mpv.changes      
2020-07-17 20:50:46.756915856 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-python-mpv.new.3592/python-python-mpv.changes
    2020-07-20 21:04:36.981290325 +0200
@@ -1,0 +2,21 @@
+Mon Jul 20 14:50:15 UTC 2020 - aloi...@gmx.com
+
+- Update to version 0.5.1
+  * mpv.py: terminate: Raise warning when called from event
+    thread.
+  * mpv.py: add wait_for_shutdown
+  * mpv.py: add check_core_alive, check core in __getattr__,
+    __setattr__
+
+-------------------------------------------------------------------
+Mon Jul 20 07:38:56 UTC 2020 - Luigi Baldoni <aloi...@gmx.com>
+
+- Update to version 0.5.0
+  * mpv.py: add prepare_and_wait_for_property
+  * mpv.py: Update copyright date
+  * mpv.py: Add docstrings to new additions to API
+  * Sprinkle some thread safety over event loop, add
+    *wait_for_event
+  * mpv.py: improve shutdown handling, replace wait_for_playback
+
+-------------------------------------------------------------------

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

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

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

Other differences:
------------------
++++++ python-python-mpv.spec ++++++
--- /var/tmp/diff_new_pack.vh2SNY/_old  2020-07-20 21:04:39.661293043 +0200
+++ /var/tmp/diff_new_pack.vh2SNY/_new  2020-07-20 21:04:39.665293046 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-python-mpv
-Version:        0.4.8
+Version:        0.5.1
 Release:        0
 Summary:        Python interface to the mpv media player
 License:        AGPL-3.0-or-later

++++++ python-mpv-0.4.8.tar.gz -> python-mpv-0.5.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-0.4.8/PKG-INFO 
new/python-mpv-0.5.1/PKG-INFO
--- old/python-mpv-0.4.8/PKG-INFO       2020-07-16 21:06:35.000000000 +0200
+++ new/python-mpv-0.5.1/PKG-INFO       2020-07-20 14:20:52.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-mpv
-Version: 0.4.8
+Version: 0.5.1
 Summary: A python interface to the mpv media player
 Home-page: https://github.com/jaseg/python-mpv
 Author: jaseg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-0.4.8/mpv.py new/python-mpv-0.5.1/mpv.py
--- old/python-mpv-0.4.8/mpv.py 2020-07-16 19:19:44.000000000 +0200
+++ new/python-mpv-0.5.1/mpv.py 2020-07-19 22:29:09.000000000 +0200
@@ -2,7 +2,7 @@
 # vim: ts=4 sw=4 et
 #
 # Python MPV library module
-# Copyright (C) 2017 Sebastian Götte <c...@jaseg.net>
+# Copyright (C) 2017-2020 Sebastian Götte <c...@jaseg.net>
 #
 # This program is free software: you can redistribute it and/or modify it 
under the terms of the GNU Affero General
 # Public License as published by the Free Software Foundation, either version 
3 of the License, or (at your option) any
@@ -23,6 +23,7 @@
 import sys
 from warnings import warn
 from functools import partial, wraps
+from contextlib import contextmanager
 import collections
 import re
 import traceback
@@ -53,6 +54,9 @@
     fs_enc = sys.getfilesystemencoding()
 
 
+class ShutdownError(SystemError):
+    pass
+
 class MpvHandle(c_void_p):
     pass
 
@@ -633,41 +637,6 @@
         yield event
 
 
-def _event_loop(event_handle, playback_cond, event_callbacks, 
message_handlers, property_handlers, log_handler):
-    for event in _event_generator(event_handle):
-        try:
-            devent = event.as_dict(decoder=lazy_decoder) # copy data from 
ctypes
-            eid = devent['event_id']
-            for callback in event_callbacks:
-                callback(devent)
-
-            if eid in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE):
-                with playback_cond:
-                    playback_cond.notify_all()
-
-            if eid == MpvEventID.PROPERTY_CHANGE:
-                pc = devent['event']
-                name, value, _fmt = pc['name'], pc['value'], pc['format']
-                for handler in property_handlers[name]:
-                    handler(name, value)
-
-            if eid == MpvEventID.LOG_MESSAGE and log_handler is not None:
-                ev = devent['event']
-                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 = devent['event']['args']
-                if target in message_handlers:
-                    message_handlers[target](*args)
-
-            if eid == MpvEventID.SHUTDOWN:
-                _mpv_detach_destroy(event_handle)
-                return
-
-        except Exception as e:
-            traceback.print_exc()
-
 _py_to_mpv = lambda name: name.replace('_', '-')
 _mpv_to_py = lambda name: name.replace('-', '_')
 
@@ -840,6 +809,7 @@
 
         self.handle = _mpv_create()
         self._event_thread = None
+        self._core_shutdown = False
 
         _mpv_set_option_string(self.handle, b'audio-display', b'no')
         istr = lambda o: ('yes' if o else 'no') if type(o) is bool else str(o)
@@ -858,13 +828,13 @@
         self.lazy   = _DecoderPropertyProxy(self, lazy_decoder)
 
         self._event_callbacks = []
+        self._event_handler_lock = threading.Lock()
         self._property_handlers = collections.defaultdict(lambda: [])
+        self._quit_handlers = set()
         self._message_handlers = {}
         self._key_binding_handlers = {}
-        self._playback_cond = threading.Condition()
         self._event_handle = _mpv_create_client(self.handle, 
b'py_event_handler')
-        self._loop = partial(_event_loop, self._event_handle, 
self._playback_cond, self._event_callbacks,
-                self._message_handlers, self._property_handlers, log_handler)
+        self._log_handler = log_handler
         self._stream_protocol_cbs = {}
         self._stream_protocol_frontends = collections.defaultdict(lambda: {})
         self.register_stream_protocol('python', self._python_stream_open)
@@ -881,32 +851,157 @@
         else:
             self._event_thread = None
 
-    def wait_for_playback(self):
-        """Waits until playback of the current title is paused or done."""
-        with self._playback_cond:
-            self._playback_cond.wait()
+    def _loop(self):
+        for event in _event_generator(self._event_handle):
+            try:
+                devent = event.as_dict(decoder=lazy_decoder) # copy data from 
ctypes
+                eid = devent['event_id']
+
+                with self._event_handler_lock:
+                    if eid == MpvEventID.SHUTDOWN:
+                        self._core_shutdown = True
+
+                for callback in self._event_callbacks:
+                    callback(devent)
+
+                if eid == MpvEventID.PROPERTY_CHANGE:
+                    pc = devent['event']
+                    name, value, _fmt = pc['name'], pc['value'], pc['format']
+                    for handler in self._property_handlers[name]:
+                        handler(name, value)
+
+                if eid == MpvEventID.LOG_MESSAGE and self._log_handler is not 
None:
+                    ev = devent['event']
+                    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 = devent['event']['args']
+                    if target in self._message_handlers:
+                        self._message_handlers[target](*args)
+
+                if eid == MpvEventID.SHUTDOWN:
+                    _mpv_detach_destroy(self._event_handle)
+                    return
+
+            except Exception as e:
+                print('Exception inside python-mpv event loop:', 
file=sys.stderr)
+                traceback.print_exc()
+
+    @property
+    def core_shutdown(self):
+        """Property indicating whether the core has been shut down. Possible 
causes for this are e.g. the `quit` command
+        or a user closing the mpv window."""
+        return self._core_shutdown
+
+    def check_core_alive(self):
+        """ This method can be used as a sanity check to tests whether the 
core is still alive at the time it is
+        called."""
+        if self._core_shutdown:
+            raise ShutdownError('libmpv core has been shutdown')
 
     def wait_until_paused(self):
-        """Waits until playback of the current title is paused or done."""
+        """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')
 
+    def wait_for_playback(self):
+        """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')
+
     def wait_until_playing(self):
-        """Waits until playback of the current title has started."""
+        """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)
 
     def wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=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
+        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):
+            pass
+
+    def wait_for_shutdown(self):
+        '''Wait for core to shutdown (e.g. through quit() or terminate()).'''
         sema = threading.Semaphore(value=0)
+
+        @self.event_callback('shutdown')
+        def shutdown_handler(event):
+            sema.release()
+
+        sema.acquire()
+        shutdown_handler.unregister_mpv_events()
+
+    @contextmanager
+    def prepare_and_wait_for_property(self, name, cond=lambda val: val, 
level_sensitive=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.
+        """
+        sema = threading.Semaphore(value=0)
+
         def observer(name, val):
             if cond(val):
                 sema.release()
         self.observe_property(name, observer)
+
+        @self.event_callback('shutdown')
+        def shutdown_handler(event):
+            sema.release()
+
+        yield
         if not level_sensitive or not cond(getattr(self, name.replace('-', 
'_'))):
             sema.acquire()
+
+        self.check_core_alive()
+
+        shutdown_handler.unregister_mpv_events()
         self.unobserve_property(name, observer)
 
+    def wait_for_event(self, *event_types, cond=lambda evt: 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.
+        """
+        with self.prepare_and_wait_for_event(*event_types, cond=cond):
+            pass
+
+    @contextmanager
+    def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: 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.
+
+        Compared to wait_for_event this handles the case where a thread waits 
for an event it itself causes in a
+        thread-safe way. An example from the testsuite is:
+
+        with self.m.prepare_and_wait_for_event('client_message'):
+            self.m.keypress(key)
+
+        Using just wait_for_event it would be impossible to ensure the event 
is caught since it may already have been
+        handled in the interval between keypress(...) running and a subsequent 
wait_for_event(...) call.
+        """
+        sema = threading.Semaphore(value=0)
+
+        @self.event_callback('shutdown')
+        def shutdown_handler(event):
+            sema.release()
+
+        @self.event_callback(*event_types)
+        def target_handler(evt):
+            if cond(evt):
+                sema.release()
+
+        yield
+        sema.acquire()
+
+        self.check_core_alive()
+
+        shutdown_handler.unregister_mpv_events()
+        target_handler.unregister_mpv_events()
+
     def __del__(self):
         if self.handle:
             self.terminate()
@@ -914,13 +1009,16 @@
     def terminate(self):
         """Properly terminates this player instance. Preferably use this 
instead of relying on python's garbage
         collector to cause this to be called from the object's destructor.
+
+        This method will detach the main libmpv handle and wait for mpv to 
shut down and the event thread to finish.
         """
         self.handle, handle = None, self.handle
         if threading.current_thread() is self._event_thread:
-            # Handle special case to allow event handle to be detached.
-            # This is necessary since otherwise the event thread would 
deadlock itself.
-            grim_reaper = threading.Thread(target=lambda: 
_mpv_terminate_destroy(handle))
-            grim_reaper.start()
+            raise UserWarning('terminate() should not be called from event 
thread (e.g. from a callback function). If '
+                    'you want to terminate mpv from here, please call quit() 
instead, then sync the main thread '
+                    'against the event thread using e.g. wait_for_shutdown(), 
then terminate() from the main thread. '
+                    'This call has been transformed into a call to quit().')
+            self.quit()
         else:
             _mpv_terminate_destroy(handle)
             if self._event_thread:
@@ -1229,6 +1327,9 @@
                 print("It's loud!", volume)
 
             my_handler.unregister_mpv_properties()
+
+        exit_handler is a function taking no arguments that is called when the 
underlying mpv handle is terminated (e.g.
+        from calling MPV.terminate() or issuing a "quit" input command).
         """
         self._property_handlers[name].append(handler)
         _mpv_observe_property(self._event_handle, 
hash(name)&0xffffffffffffffff, name.encode('utf-8'), MpvFormat.NODE)
@@ -1342,14 +1443,16 @@
             my_handler.unregister_mpv_events()
         """
         def register(callback):
-            types = [MpvEventID.from_str(t) if isinstance(t, str) else t for t 
in event_types] or MpvEventID.ANY
-            @wraps(callback)
-            def wrapper(event, *args, **kwargs):
-                if event['event_id'] in types:
-                    callback(event, *args, **kwargs)
-            self._event_callbacks.append(wrapper)
-            wrapper.unregister_mpv_events = 
partial(self.unregister_event_callback, wrapper)
-            return wrapper
+            with self._event_handler_lock:
+                self.check_core_alive()
+                types = [MpvEventID.from_str(t) if isinstance(t, str) else t 
for t in event_types] or MpvEventID.ANY
+                @wraps(callback)
+                def wrapper(event, *args, **kwargs):
+                    if event['event_id'] in types:
+                        callback(event, *args, **kwargs)
+                self._event_callbacks.append(wrapper)
+                wrapper.unregister_mpv_events = 
partial(self.unregister_event_callback, wrapper)
+                return wrapper
         return register
 
     @staticmethod
@@ -1645,6 +1748,7 @@
 
     # Property accessors
     def _get_property(self, name, decoder=strict_decoder, fmt=MpvFormat.NODE):
+        self.check_core_alive()
         out = create_string_buffer(sizeof(MpvNode))
         try:
             cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, 
out)
@@ -1661,6 +1765,7 @@
             return None
 
     def _set_property(self, name, value):
+        self.check_core_alive()
         ename = name.encode('utf-8')
         if isinstance(value, (list, set, dict)):
             _1, _2, _3, pointer = _make_node_str_list(value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-0.4.8/python_mpv.egg-info/PKG-INFO 
new/python-mpv-0.5.1/python_mpv.egg-info/PKG-INFO
--- old/python-mpv-0.4.8/python_mpv.egg-info/PKG-INFO   2020-07-16 
21:06:35.000000000 +0200
+++ new/python-mpv-0.5.1/python_mpv.egg-info/PKG-INFO   2020-07-20 
14:20:52.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-mpv
-Version: 0.4.8
+Version: 0.5.1
 Summary: A python interface to the mpv media player
 Home-page: https://github.com/jaseg/python-mpv
 Author: jaseg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-mpv-0.4.8/setup.py 
new/python-mpv-0.5.1/setup.py
--- old/python-mpv-0.4.8/setup.py       2020-07-16 21:06:00.000000000 +0200
+++ new/python-mpv-0.5.1/setup.py       2020-07-20 14:20:10.000000000 +0200
@@ -3,7 +3,7 @@
 from setuptools import setup
 setup(
     name = 'python-mpv',
-    version = '0.4.8',
+    version = '0.5.1',
     py_modules = ['mpv'],
     description = 'A python interface to the mpv media player',
     url = 'https://github.com/jaseg/python-mpv',


Reply via email to