[Pkg-mpd-commits] [python-mpd] 02/91: Introduce MPDClientBase class and inherit MPDClient from it

Simon McVittie smcv at debian.org
Sat Feb 24 14:55:26 UTC 2018


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch debian/master
in repository python-mpd.

commit ff08806c0a41c2576337a7a54108a634298abb60
Author: Robert Niederreiter <office at squarewave.at>
Date:   Thu May 19 11:36:08 2016 +0200

    Introduce MPDClientBase class and inherit MPDClient from it
    
    fixes #73
---
 README.rst      |  10 ++
 doc/changes.rst |  35 +++++-
 mpd.py          | 336 +++++++++++++++++++++++++++++++++-----------------------
 3 files changed, 242 insertions(+), 139 deletions(-)

diff --git a/README.rst b/README.rst
index ddee276..59493e4 100644
--- a/README.rst
+++ b/README.rst
@@ -8,6 +8,7 @@ python-mpd2
 *python-mpd2* is a Python library which provides a client interface for
 the `Music Player Daemon <http://musicpd.org>`__.
 
+
 Difference with python-mpd
 --------------------------
 
@@ -35,6 +36,7 @@ The following features were added:
 -  improved support for sticker
 -  improved support for ranges
 
+
 Getting the latest source code
 ------------------------------
 
@@ -43,12 +45,14 @@ copy of the development version from Git by running the command::
 
     $ git clone git://github.com/Mic92/python-mpd2.git
 
+
 Getting the latest release
 --------------------------
 
 The latest stable release of *python-mpd2* can be found on
 `PyPI <http://pypi.python.org/pypi?:action=display&name=python-mpd2>`__
 
+
 PyPI:
 ~~~~~
 
@@ -56,6 +60,7 @@ PyPI:
 
     $ pip install python-mpd2
 
+
 Installation in Linux/BSD distributions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -64,6 +69,7 @@ packages to test your applications:
 
 See `INSTALL.rst <INSTALL.rst>`__
 
+
 Installing from source
 ----------------------
 
@@ -74,6 +80,7 @@ To install *python-mpd2* from source, simply run the command::
 You can use the *--help* switch to *setup.py* for a complete list of commands
 and their options. See the `Installing Python Modules <http://docs.python.org/inst/inst.html>`__ document for more details.
 
+
 Documentation
 -------------
 
@@ -85,6 +92,7 @@ Documentation
 
 `Examples <examples>`__
 
+
 Testing
 -------
 
@@ -96,6 +104,7 @@ This will install `Tox <http://tox.testrun.org/>`__. Tox will take care of
 testing against all the supported Python versions (at least available) on our
 computer, with the required dependencies
 
+
 Building Documentation
 ----------------------
 
@@ -112,6 +121,7 @@ In order to update it, install python-lxml and run the following command::
 
     $ python ./doc/generate_command_reference.py > ./doc/topics/commands.rst
 
+
 Contacting the author
 ---------------------
 
diff --git a/doc/changes.rst b/doc/changes.rst
index 084b6a6..4feb88f 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -1,39 +1,66 @@
 python-mpd2 Changes List
 ========================
 
+Changes in v0.6.0 (unreleased)
+------------------------------
+
+* Use @property and @property.setter for MPDClient.timeout
+* Introduce MPDClientBase class which provides common MPD communication related
+  helpers. Used as base for synchronous and asynchronous clients
+* Introduce lookup_func which also searches on base classes for hooking
+  commands
+
+
 Changes in v0.5.5
 -----------------
+
 * fix sended newlines on windows systems
 * include tests in source distribution
 
+
 Changes in v0.5.4
 -----------------
-* support for listfiles, rangeid, addtagid, cleartagid, mount, umount, listmounts, listneighbors
+
+* support for listfiles, rangeid, addtagid, cleartagid, mount, umount,
+  listmounts, listneighbors
+
 
 Changes in v0.5.3
 -----------------
+
 * noidle command does returns pending changes now
 
+
 Changes in v0.5.2
 -----------------
+
 * add support for readcomments and toggleoutput
 
+
 Changes in v0.5.1
 -----------------
+
 * add support for ranges
 
+
 Changes in 0.5.0
 ----------------
+
 * improved support for sticker
 
+
 Changes in 0.4.6
 ----------------
+
 * enforce utf8 for encoding/decoding in python3
 
+
 Changes in 0.4.5
 ----------------
+
 * support for logging
 
+
 Changes in 0.4.4
 ----------------
 
@@ -41,6 +68,7 @@ Changes in 0.4.4
 * deprecate timeout parameter added in v0.4.2
 * add timeout and idletimeout property
 
+
 Changes in 0.4.3
 ----------------
 
@@ -48,12 +76,14 @@ Changes in 0.4.3
 * fix commands without a callback function
 * transform MPDClient to new style class
 
+
 Changes in 0.4.2
 ----------------
 
 * backward compatible unicode handling
 * added optional socket timeout parameter
 
+
 Changes in 0.4.1
 ----------------
 
@@ -61,6 +91,7 @@ Changes in 0.4.1
 * added config command
 * remove deprecated volume command
 
+
 Changes in 0.4.0
 ----------------
 
@@ -90,11 +121,13 @@ Changes in 0.3.0
 * added idle and noidle commands
 * added listplaylists command
 
+
 Changes in 0.2.1
 ----------------
 
 * connect() no longer broken on Windows
 
+
 Changes in 0.2.0
 ----------------
 
diff --git a/mpd.py b/mpd.py
index 484f99b..7932693 100644
--- a/mpd.py
+++ b/mpd.py
@@ -21,7 +21,7 @@ import socket
 import warnings
 from collections import Callable
 
-VERSION = (0, 5, 5)
+VERSION = (0, 6, 0)
 HELLO_PREFIX = "OK MPD "
 ERROR_PREFIX = "ACK "
 SUCCESS = "OK"
@@ -204,32 +204,128 @@ _commands = {
 }
 
 
-class MPDClient(object):
+class MPDClientBase(object):
+
     def __init__(self, use_unicode=False):
         self.iterate = False
         self.use_unicode = use_unicode
         self._reset()
 
+    def disconnect(self):
+        raise NotImplementedError(
+            "MPDClientBase does not implement disconnect")
+
+    def _reset(self):
+        self.mpd_version = None
+        self._pending = []
+        self._command_list = None
+
+    def _parse_line(self, line):
+        if self.use_unicode:
+            line = decode_str(line)
+        if not line.endswith("\n"):
+            self.disconnect()
+            raise ConnectionError("Connection lost while reading line")
+        line = line.rstrip("\n")
+        if line.startswith(ERROR_PREFIX):
+            error = line[len(ERROR_PREFIX):].strip()
+            raise CommandError(error)
+        if self._command_list is not None:
+            if line == NEXT:
+                return
+            if line == SUCCESS:
+                raise ProtocolError("Got unexpected '{}'".format(SUCCESS))
+        elif line == SUCCESS:
+            return
+        return line
+
+    def _parse_pair(self, line, separator):
+        if line is None:
+            return
+        pair = line.split(separator, 1)
+        if len(pair) < 2:
+            raise ProtocolError("Could not parse pair: '{}'".format(line))
+        return pair
+
+    def _parse_pairs(self, lines, separator=": "):
+        for line in lines:
+            yield self._parse_pair(line, separator)
+
+    def _parse_list(self, lines):
+        seen = None
+        for key, value in self._parse_pairs(lines):
+            if key != seen:
+                if seen is not None:
+                    raise ProtocolError(
+                        "Expected key '{}', got '{}'".format(seen, key))
+                seen = key
+            yield value
+
+    def _parse_playlist(self, lines):
+        for key, value in self._parse_pairs(lines, ":"):
+            yield value
+
+    def _parse_objects(self, lines, delimiters=[]):
+        obj = {}
+        for key, value in self._parse_pairs(lines):
+            key = key.lower()
+            if obj:
+                if key in delimiters:
+                    yield obj
+                    obj = {}
+                elif key in obj:
+                    if not isinstance(obj[key], list):
+                        obj[key] = [obj[key], value]
+                    else:
+                        obj[key].append(value)
+                    continue
+            obj[key] = value
+        if obj:
+            yield obj
+
+    def _parse_stickers(self, lines):
+        for key, sticker in self._parse_pairs(lines):
+            value = sticker.split('=', 1)
+            if len(value) < 2:
+                raise ProtocolError(
+                    "Could not parse sticker: {}".format(repr(sticker)))
+            yield tuple(value)
+
+
+class MPDClient(MPDClientBase):
+    idletimeout = None
+    _timeout = None
+
+    def __init__(self, use_unicode=False):
+        super(MPDClient, self).__init__(use_unicode=use_unicode)
+
+    def _reset(self):
+        super(MPDClient, self)._reset()
+        self._iterating = False
+        self._sock = None
+        self._rfile = _NotConnected()
+        self._wfile = _NotConnected()
+
     def _send(self, command, args, retval):
         if self._command_list is not None:
-            raise CommandListError("Cannot use send_%s in a command list" %
-                                   command)
+            raise CommandListError(
+                "Cannot use send_{} in a command list".format(command))
         self._write_command(command, args)
         if retval is not None:
             self._pending.append(command)
 
     def _fetch(self, command, args, retval):
         if self._command_list is not None:
-            raise CommandListError("Cannot use fetch_%s in a command list" %
-                                   command)
+            raise CommandListError(
+                "Cannot use fetch_{} in a command list".format(command))
         if self._iterating:
-            raise IteratingError("Cannot use fetch_%s while iterating" %
-                                 command)
+            raise IteratingError(
+                "Cannot use fetch_{} while iterating".format(command))
         if not self._pending:
             raise PendingCommandError("No pending commands to fetch")
         if self._pending[0] != command:
-            raise PendingCommandError("'%s' is not the currently "
-                                      "pending command" % command)
+            raise PendingCommandError(
+                "'{}' is not the currently pending command".format(command))
         del self._pending[0]
         if isinstance(retval, Callable):
             return retval()
@@ -237,15 +333,15 @@ class MPDClient(object):
 
     def _execute(self, command, args, retval):
         if self._iterating:
-            raise IteratingError("Cannot execute '%s' while iterating" %
-                                 command)
+            raise IteratingError(
+                "Cannot execute '{}' while iterating".format(command))
         if self._pending:
-            raise PendingCommandError("Cannot execute '%s' with "
-                                      "pending commands" % command)
+            raise PendingCommandError(
+                "Cannot execute '{}' with pending commands".format(command))
         if self._command_list is not None:
             if not isinstance(retval, Callable):
-                raise CommandListError("'%s' not allowed in command list" %
-                                       command)
+                raise CommandListError(
+                    "'{}' not allowed in command list".format(command))
             self._write_command(command, args)
             self._command_list.append(retval)
         else:
@@ -255,7 +351,7 @@ class MPDClient(object):
             return retval
 
     def _write_line(self, line):
-        self._wfile.write("%s\n" % line)
+        self._wfile.write("{}\n".format(line))
         self._wfile.flush()
 
     def _write_command(self, command, args=[]):
@@ -265,11 +361,11 @@ class MPDClient(object):
                 if len(arg) == 0:
                     parts.append('":"')
                 elif len(arg) == 1:
-                    parts.append('"%d:"' % int(arg[0]))
+                    parts.append('"{}:"'.format(int(arg[0])))
                 else:
-                    parts.append('"%d:%d"' % (int(arg[0]), int(arg[1])))
+                    parts.append('"{}:{}"'.format(int(arg[0]), int(arg[1])))
             else:
-                parts.append('"%s"' % escape(encode_str(arg)))
+                parts.append('"{}"'.format(escape(encode_str(arg))))
         # Minimize logging cost if the logging is not activated.
         if logger.isEnabledFor(logging.DEBUG):
             if command == "password":
@@ -278,72 +374,35 @@ class MPDClient(object):
                 logger.debug("Calling MPD %s%r", command, args)
         self._write_line(" ".join(parts))
 
+    ##################
+    # response helpers
+
     def _read_line(self):
-        line = self._rfile.readline()
-        if self.use_unicode:
-            line = decode_str(line)
-        if not line.endswith("\n"):
-            self.disconnect()
-            raise ConnectionError("Connection lost while reading line")
-        line = line.rstrip("\n")
-        if line.startswith(ERROR_PREFIX):
-            error = line[len(ERROR_PREFIX):].strip()
-            raise CommandError(error)
-        if self._command_list is not None:
-            if line == NEXT:
-                return
-            if line == SUCCESS:
-                raise ProtocolError("Got unexpected '%s'" % SUCCESS)
-        elif line == SUCCESS:
-            return
-        return line
+        return self._parse_line(self._rfile.readline())
 
-    def _read_pair(self, separator):
+    def _read_lines(self):
         line = self._read_line()
-        if line is None:
-            return
-        pair = line.split(separator, 1)
-        if len(pair) < 2:
-            raise ProtocolError("Could not parse pair: '%s'" % line)
-        return pair
+        while line is not None:
+            yield line
+            line = self._read_line()
+
+    def _read_pair(self, separator):
+        return self._parse_pair(self._read_line(), separator)
 
     def _read_pairs(self, separator=": "):
-        pair = self._read_pair(separator)
-        while pair:
-            yield pair
-            pair = self._read_pair(separator)
+        return self._parse_pairs(self._read_lines(), separator)
 
     def _read_list(self):
-        seen = None
-        for key, value in self._read_pairs():
-            if key != seen:
-                if seen is not None:
-                    raise ProtocolError("Expected key '%s', got '%s'" %
-                                        (seen, key))
-                seen = key
-            yield value
+        return self._parse_list(self._read_lines())
 
     def _read_playlist(self):
-        for key, value in self._read_pairs(":"):
-            yield value
+        return self._parse_playlist(self._read_lines())
 
     def _read_objects(self, delimiters=[]):
-        obj = {}
-        for key, value in self._read_pairs():
-            key = key.lower()
-            if obj:
-                if key in delimiters:
-                    yield obj
-                    obj = {}
-                elif key in obj:
-                    if not isinstance(obj[key], list):
-                        obj[key] = [obj[key], value]
-                    else:
-                        obj[key].append(value)
-                    continue
-            obj[key] = value
-        if obj:
-            yield obj
+        return self._parse_objects(self._read_lines(), delimiters)
+
+    def _read_stickers(self):
+        return self._parse_stickers(self._read_lines())
 
     def _read_command_list(self):
         try:
@@ -353,15 +412,6 @@ class MPDClient(object):
             self._command_list = None
         self._fetch_nothing()
 
-    def _read_stickers(self):
-        for key, sticker in self._read_pairs():
-            value = sticker.split('=', 1)
-
-            if len(value) < 2:
-                raise ProtocolError("Could not parse sticker: %r" % sticker)
-
-            yield tuple(value)
-
     def _iterator_wrapper(self, iterator):
         try:
             for item in iterator:
@@ -375,10 +425,14 @@ class MPDClient(object):
         self._iterating = True
         return self._iterator_wrapper(iterator)
 
+    ####################
+    # response callbacks
+
     def _fetch_nothing(self):
         line = self._read_line()
         if line is not None:
-            raise ProtocolError("Got unexpected return value: '%s'" % line)
+            raise ProtocolError(
+                "Got unexpected return value: '{}'".format(line))
 
     def _fetch_item(self):
         pairs = list(self._read_pairs())
@@ -445,13 +499,8 @@ class MPDClient(object):
     def _fetch_command_list(self):
         return self._wrap_iterator(self._read_command_list())
 
-    def noidle(self):
-        if not self._pending or self._pending[0] != 'idle':
-            msg = 'cannot send noidle if send_idle was not called'
-            raise CommandError(msg)
-        del self._pending[0]
-        self._write_command("noidle")
-        return self._fetch_list()
+    # end response callbacks
+    ########################
 
     def _hello(self):
         line = self._rfile.readline()
@@ -460,22 +509,13 @@ class MPDClient(object):
             raise ConnectionError("Connection lost while reading MPD hello")
         line = line.rstrip("\n")
         if not line.startswith(HELLO_PREFIX):
-            raise ProtocolError("Got invalid MPD hello: '%s'" % line)
+            raise ProtocolError("Got invalid MPD hello: '{}'".format(line))
         self.mpd_version = line[len(HELLO_PREFIX):].strip()
 
-    def _reset(self):
-        self.mpd_version = None
-        self._iterating = False
-        self._pending = []
-        self._command_list = None
-        self._sock = None
-        self._rfile = _NotConnected()
-        self._wfile = _NotConnected()
-
     def _connect_unix(self, path):
         if not hasattr(socket, "AF_UNIX"):
-            raise ConnectionError("Unix domain sockets not supported "
-                                  "on this platform")
+            raise ConnectionError(
+                "Unix domain sockets not supported on this platform")
         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         sock.settimeout(self.timeout)
         sock.connect(path)
@@ -507,33 +547,31 @@ class MPDClient(object):
         else:
             raise ConnectionError("getaddrinfo returns an empty list")
 
-    def _settimeout(self, timeout):
+    @property
+    def timeout(self):
+        return self._timeout
+
+    @timeout.setter
+    def timeout(self, timeout):
         self._timeout = timeout
         if self._sock is not None:
             self._sock.settimeout(timeout)
 
-    def _gettimeout(self):
-        return self._timeout
-
-    timeout = property(_gettimeout, _settimeout)
-    _timeout = None
-    idletimeout = None
-
     def connect(self, host, port, timeout=None):
-        logger.info("Calling MPD connect(%r, %r, timeout=%r)", host,
-                    port, timeout)
+        logger.info(
+            "Calling MPD connect(%r, %r, timeout=%r)", host, port, timeout)
         if self._sock is not None:
             raise ConnectionError("Already connected")
         if timeout is not None:
-            warnings.warn("The timeout parameter in connect() is deprecated! "
-                          "Use MPDClient.timeout = yourtimeout instead.",
-                          DeprecationWarning)
+            warnings.warn(
+                "The timeout parameter in connect() is deprecated! "
+                "Use MPDClient.timeout = yourtimeout instead.",
+                DeprecationWarning)
             self.timeout = timeout
         if host.startswith("/"):
             self._sock = self._connect_unix(host)
         else:
             self._sock = self._connect_tcp(host, port)
-
         if IS_PYTHON2:
             self._rfile = self._sock.makefile("r")
             self._wfile = self._sock.makefile("w")
@@ -542,13 +580,14 @@ class MPDClient(object):
             #   locale.
             # - by setting newline explicit, we force to send '\n' also on
             #   windows
-            self._rfile = self._sock.makefile("r",
-                                              encoding="utf-8",
-                                              newline="\n")
-            self._wfile = self._sock.makefile("w",
-                                              encoding="utf-8",
-                                              newline="\n")
-
+            self._rfile = self._sock.makefile(
+                "r",
+                encoding="utf-8",
+                newline="\n")
+            self._wfile = self._sock.makefile(
+                "w",
+                encoding="utf-8",
+                newline="\n")
         try:
             self._hello()
         except:
@@ -572,14 +611,22 @@ class MPDClient(object):
             raise ConnectionError("Not connected")
         return self._sock.fileno()
 
+    def noidle(self):
+        if not self._pending or self._pending[0] != 'idle':
+            msg = 'cannot send noidle if send_idle was not called'
+            raise CommandError(msg)
+        del self._pending[0]
+        self._write_command("noidle")
+        return self._fetch_list()
+
     def command_list_ok_begin(self):
         if self._command_list is not None:
             raise CommandListError("Already in command list")
         if self._iterating:
             raise IteratingError("Cannot begin command list while iterating")
         if self._pending:
-            raise PendingCommandError("Cannot begin command list "
-                                      "with pending commands")
+            raise PendingCommandError(
+                "Cannot begin command list with pending commands")
         self._write_command("command_list_ok_begin")
         self._command_list = []
 
@@ -593,10 +640,9 @@ class MPDClient(object):
 
     @classmethod
     def add_command(cls, name, callback):
-        method = newFunction(cls._execute, name, callback)
-        send_method = newFunction(cls._send, name, callback)
-        fetch_method = newFunction(cls._fetch, name, callback)
-
+        method = new_function(cls._execute, name, callback)
+        send_method = new_function(cls._send, name, callback)
+        fetch_method = new_function(cls._fetch, name, callback)
         # create new mpd commands as function in three flavors:
         # normal, with "send_"-prefix and with "fetch_"-prefix
         escaped_name = name.replace(" ", "_")
@@ -607,7 +653,8 @@ class MPDClient(object):
     @classmethod
     def remove_command(cls, name):
         if not hasattr(cls, name):
-            raise ValueError("Can't remove not existent '%s' command" % name)
+            raise ValueError(
+                "Can't remove not existent '{}' command".format(name))
         name = name.replace(" ", "_")
         delattr(cls, str(name))
         delattr(cls, str("send_" + name))
@@ -615,23 +662,36 @@ class MPDClient(object):
 
 
 def bound_decorator(self, function):
-    """ bind decorator to self """
+    """Bind decorator to self.
+    """
     if not isinstance(function, Callable):
         return None
-
     def decorator(*args, **kwargs):
         return function(self, *args, **kwargs)
     return decorator
 
 
-def newFunction(wrapper, name, returnValue):
+def new_function(wrapper, name, return_value):
     def decorator(self, *args):
-        return wrapper(self, name, args, bound_decorator(self, returnValue))
+        return wrapper(self, name, args, bound_decorator(self, return_value))
     return decorator
 
+
+def lookup_func(cls, name):
+    func = None
+    if name in cls.__dict__:
+        func = cls.__dict__[name]
+    else:
+        for base in cls.__bases__:
+            func = lookup_func(base, name)
+            if func is not None:
+                break
+    return func
+
+
 for key, value in _commands.items():
-    returnValue = None if value is None else MPDClient.__dict__[value]
-    MPDClient.add_command(key, returnValue)
+    return_value = lookup_func(MPDClient, value)
+    MPDClient.add_command(key, return_value)
 
 
 def escape(text):

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mpd/python-mpd.git



More information about the Pkg-mpd-commits mailing list