[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