From 1a493a537b86cf2cb191e39590ed94df8e731064 Mon Sep 17 00:00:00 2001 From: Kaliko Jack Date: Mon, 10 Apr 2023 20:05:20 +0200 Subject: [PATCH] Some documentation refactoring, improvements --- README.rst | 6 ++- doc/source/commands.rst | 5 +-- doc/source/commands.txt | 9 ++++- doc/source/doc.rst | 11 ++---- doc/source/index.rst | 7 ++-- doc/source/use.rst | 82 +++++++++++++++++++++++++++++------------ musicpd.py | 82 ++++++++++++++++++++--------------------- 7 files changed, 122 insertions(+), 80 deletions(-) diff --git a/README.rst b/README.rst index 88c7430..4a4a737 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Python MusicPlayerDaemon client module -*************************************** +Music Player Daemon client module +********************************* An MPD (Music Player Daemon) client library written in pure Python. @@ -11,3 +11,5 @@ An MPD (Music Player Daemon) client library written in pure Python. :Dependencies: None :Compatibility: Python 3.6+ :Licence: GNU LGPLv3 + +---- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index e335013..95ae748 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -1,4 +1,4 @@ -.. SPDX-FileCopyrightText: 2018-2021 kaliko +.. SPDX-FileCopyrightText: 2018-2023 kaliko .. SPDX-License-Identifier: LGPL-3.0-or-later .. _commands: @@ -13,7 +13,6 @@ Get current available commands: import musicpd print(' '.join([cmd for cmd in musicpd.MPDClient()._commands.keys()])) - -List, last updated for v0.6.0: +List, last updated for v0.8.0: .. literalinclude:: commands.txt diff --git a/doc/source/commands.txt b/doc/source/commands.txt index b9dc4a2..22c2f39 100644 --- a/doc/source/commands.txt +++ b/doc/source/commands.txt @@ -7,7 +7,7 @@ status -> fetch_object stats -> fetch_object == Playback Option Commands -consume -> fetch_nothing +consume -> fetch_nothing crossfade -> fetch_nothing mixrampdb -> fetch_nothing mixrampdelay -> fetch_nothing @@ -133,3 +133,10 @@ commands -> fetch_list notcommands -> fetch_list urlhandlers -> fetch_list decoders -> fetch_plugins + +== Client to Client +subscribe -> self._fetch_nothing, +unsubscribe -> self._fetch_nothing, +channels -> self._fetch_list, +readmessages -> self._fetch_messages, +sendmessage -> self._fetch_nothing, diff --git a/doc/source/doc.rst b/doc/source/doc.rst index 230a0d1..22cec3b 100644 --- a/doc/source/doc.rst +++ b/doc/source/doc.rst @@ -1,15 +1,12 @@ -.. SPDX-FileCopyrightText: 2018-2021 kaliko +.. SPDX-FileCopyrightText: 2018-2023 kaliko .. SPDX-License-Identifier: LGPL-3.0-or-later musicpd namespace ================= -.. autodata:: musicpd.CONNECTION_TIMEOUT - -.. autodata:: musicpd.SOCKET_TIMEOUT - -.. autoclass:: musicpd.MPDClient - :members: +.. automodule:: musicpd + :members: + :no-undoc-members: .. vim: spell spelllang=en diff --git a/doc/source/index.rst b/doc/source/index.rst index ac14145..9a91ee4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,4 +1,4 @@ -.. SPDX-FileCopyrightText: 2018-2021 kaliko +.. SPDX-FileCopyrightText: 2018-2023 kaliko .. SPDX-License-Identifier: LGPL-3.0-or-later .. include:: ../../README.rst @@ -20,7 +20,7 @@ Installation Library overview ----------------- +================ Here is a snippet allowing to list the last modified artists in the media library: .. code:: python3 @@ -46,7 +46,7 @@ Here is a snippet allowing to list the last modified artists in the media librar Build documentation --------------------- +=================== .. code:: bash @@ -67,6 +67,7 @@ Contents .. toctree:: :maxdepth: 2 + self use.rst doc.rst commands.rst diff --git a/doc/source/use.rst b/doc/source/use.rst index 213491e..119de2d 100644 --- a/doc/source/use.rst +++ b/doc/source/use.rst @@ -16,43 +16,78 @@ The client library can be used as follows: # test ${XDG_RUNTIME_DIR}/mpd/socket for existence # fallback to localhost:6600 # connect support host/port argument as well - print(client.mpd_version) # print the mpd protocol version - print(client.cmd('foo', 42)) # print result of the request "cmd foo 42" - # (nb. for actual command, see link to the protocol below) + print(client.mpd_version) # print the MPD protocol version + client.setvol('42') # sets the volume client.disconnect() # disconnect from the server -In the example above `cmd` in not an actual MPD command, for a list of -supported commands, their arguments (as MPD currently understands -them), and the functions used to parse their responses see :ref:`commands`. +The MPD command protocol exchanges line-based text records. The client emits a +command with optional arguments. In the example above the client sends a +`setvol` command with the string argument `42`. -See the `MPD protocol documentation`_ for more details. +MPD commands are exposed as :py:class:`musicpd.MPDClient` methods. Methods +**arguments are python strings**. Some commands are composed of more than one word +(ie "**tagtypes [disable|enable|all]**"), for these use a `snake case`_ style to +access the method. Then **"tagtypes enable"** command is called with +**"tagtypes_enable"**. + +Remember MPD protocol is text based, then all MPD command arguments are UTF-8 +strings. In the example above, an integer can be used as argument for the +`setvol` command, but it is then evaluated as a string when the command is +written to the socket. To avoid confusion use regular string instead of relying +on object string representation. + +:py:class:`musicpd.MPDClient` methods returns different kinds of objects depending on the command. Could be :py:obj:`None`, a single object as a :py:obj:`str` or a :py:obj:`dict`, a list of :py:obj:`dict`. + +For more about the protocol and MPD commands see the `MPD protocol +documentation`_. + +For a list of currently supported commands in this python module see +:ref:`commands`. .. _environment_variables: Environment variables --------------------- -The client honors the following environment variables: +:py:class:`musicpd.MPDClient` honors the following environment variables: + +.. envvar:: MPD_HOST + + MPD host (:abbr:`FQDN (fully qualified domain name)`, IP, socket path or abstract socket) and password. - * ``MPD_HOST`` MPD host (:abbr:`FQDN (fully qualified domain name)`, socket path or abstract socket) and password. + | To define a **password** set :envvar:`MPD_HOST` to "*password@host*" (password only "*password@*") + | For **abstract socket** use "@" as prefix : "*@socket*" and then with a password "*pass@@socket*" + | Regular **unix socket** are set with an absolute path: "*/run/mpd/socket*" - | To define a password set MPD_HOST to "`password@host`" (password only "`password@`") - | For abstract socket use "@" as prefix : "`@socket`" and then with a password "`pass@@socket`" - | Regular unix socket are set with an absolute path: "`/run/mpd/socket`" - * ``MPD_PORT`` MPD port, relevant for TCP socket only, ie with :abbr:`FQDN (fully qualified domain name)` defined host - * ``MPD_TIMEOUT`` timeout for connecting to MPD and waiting for MPD’s response in seconds - * ``XDG_RUNTIME_DIR`` path to look for potential socket: ``${XDG_RUNTIME_DIR}/mpd/socket`` +.. envvar:: MPD_PORT + + MPD port, relevant for TCP socket only + +.. envvar:: MPD_TIMEOUT + + socket timeout when connecting to MPD and waiting for MPD’s response (in seconds) + +.. envvar:: XDG_RUNTIME_DIR + + path to look for potential socket .. _default_settings: Default settings ---------------- - * If ``MPD_HOST`` is not set, then look for a socket in ``${XDG_RUNTIME_DIR}/mpd/socket`` - * If there is no socket use ``localhost`` - * If ``MPD_PORT`` is not set, then use ``6600`` - * If ``MPD_TIMEOUT`` is not set, then uses :py:obj:`musicpd.CONNECTION_TIMEOUT` +Default host: + * use :envvar:`MPD_HOST` environment variable if set, extract password if present, + * else looks for an existing file in :envvar:`${XDG_RUNTIME_DIR:-/run/}/mpd/socket` + * else set host to ``localhost`` + +Default port: + * use :envvar:`MPD_PORT` environment variable is set + * else use ``6600`` +Default timeout: + * use :envvar:`MPD_TIMEOUT` is set + * else use :py:obj:`musicpd.CONNECTION_TIMEOUT` Context manager --------------- @@ -90,7 +125,7 @@ Some commands (e.g. delete) allow specifying a range in the form `"START:END"` ( Possible ranges are: `"START:END"`, `"START:"` and `":"` : -Instead of giving the plain string as `"START:END"`, you **can** provide a :py:obj:`tuple` as `(START,END)`. The module is then ensuring the format is correct and raises an :py:obj:`musicpd.CommandError` exception otherwise. Empty start or end can be specified as en empty string `''` or :py:obj:`None`. +Instead of giving the plain string as `"START:END"`, you **can** provide a :py:obj:`tuple` as `(START,END)`. The module is then ensuring the format is correct and raises an :py:obj:`musicpd.CommandError` exception otherwise. Empty start or end can be specified as en empty string ``''`` or :py:obj:`None`. .. code-block:: python @@ -102,8 +137,8 @@ Instead of giving the plain string as `"START:END"`, you **can** provide a :py:o # missing end interpreted as highest value possible, pay attention still need a tuple. client.delete((pos,)) # purge queue from current to the end -A notable case is the `rangeid` command allowing an empty range specified -as a single colon as argument (i.e. sending just ":"): +A notable case is the *rangeid* command allowing an empty range specified +as a single colon as argument (i.e. sending just ``":"``): .. code-block:: python @@ -112,7 +147,7 @@ as a single colon as argument (i.e. sending just ":"): Empty start in range (i.e. ":END") are not possible and will raise a CommandError. -Remember the of the tuple is optional, range can still be specified as single string `START:END`. In case of malformed range a CommandError is still raised. +Remember the of the tuple is optional, range can still be specified as single string ``"START:END"``. Iterators ---------- @@ -274,4 +309,5 @@ couple of seconds for these commands should never occur). .. _MPD protocol documentation: http://www.musicpd.org/doc/protocol/ +.. _snake case: https://en.wikipedia.org/wiki/Snake_case .. vim: spell spelllang=en diff --git a/musicpd.py b/musicpd.py index 35be751..ed2e01e 100644 --- a/musicpd.py +++ b/musicpd.py @@ -1,10 +1,11 @@ +# -*- coding: utf-8 -*- # SPDX-FileCopyrightText: 2012-2023 kaliko # SPDX-FileCopyrightText: 2021 Wonko der Verständige # SPDX-FileCopyrightText: 2019 Naglis Jonaitis # SPDX-FileCopyrightText: 2019 Bart Van Loon # SPDX-FileCopyrightText: 2008-2010 J. Alexander Treuman # SPDX-License-Identifier: LGPL-3.0-or-later -"""python-musicpd: Python Music Player Daemon client library""" +"""Python Music Player Daemon client library""" import socket @@ -16,9 +17,10 @@ HELLO_PREFIX = "OK MPD " ERROR_PREFIX = "ACK " SUCCESS = "OK" NEXT = "list_OK" +#: Module version VERSION = '0.9.0b0' #: Seconds before a connection attempt times out -#: (overriden by MPD_TIMEOUT env. var.) +#: (overriden by :envvar:`MPD_TIMEOUT` env. var.) CONNECTION_TIMEOUT = 30 #: Socket timeout in second (Default is None for no timeout) SOCKET_TIMEOUT = None @@ -44,31 +46,31 @@ def iterator_wrapper(func): class MPDError(Exception): - pass + """Main musicpd Exception""" class ConnectionError(MPDError): - pass + """Fatal Connection Error, cannot recover from it.""" class ProtocolError(MPDError): - pass + """Fatal Protocol Error, cannot recover from it""" class CommandError(MPDError): - pass + """Malformed command, socket should be fine, can reuse it""" class CommandListError(MPDError): - pass + """""" class PendingCommandError(MPDError): - pass + """""" class IteratingError(MPDError): - pass + """""" class Range: @@ -123,14 +125,15 @@ class _NotConnected: class MPDClient: - """MPDClient instance will look for ``MPD_HOST``/``MPD_PORT``/``XDG_RUNTIME_DIR`` environment - variables and set instance attribute ``host``, ``port`` and ``pwd`` - accordingly. Regarding ``MPD_HOST`` format to expose password refer - MPD client manual :manpage:`mpc (1)`. + """MPDClient instance will look for :envvar:`MPD_HOST`/:envvar:`MPD_PORT`/:envvar:`XDG_RUNTIME_DIR` environment + variables and set instance attribute :py:attr:`host`, :py:attr:`port` and :py:obj:`pwd` + accordingly. - Then :py:obj:`musicpd.MPDClient.connect` will use ``host`` and ``port`` as defaults if not provided as args. + Then :py:obj:`musicpd.MPDClient.connect` will use :py:obj:`host` and + :py:obj:`port` as defaults if not provided as args. - Cf. :py:obj:`musicpd.MPDClient.connect` for details. + Regarding :envvar:`MPD_HOST` format to expose password refer this module + documentation or MPD client manual :manpage:`mpc (1)`. >>> from os import environ >>> environ['MPD_HOST'] = 'pass@mpdhost' @@ -141,21 +144,28 @@ class MPDClient: True >>> cli.connect() # will use host/port as set in MPD_HOST/MPD_PORT - :ivar str host: host used with the current connection - :ivar str,int port: port used with the current connection - :ivar str pwd: password detected in ``MPD_HOST`` environment variable + .. note:: - .. warning:: Instance attribute host/port/pwd + default host: + * use :envvar:`MPD_HOST` environment variable if set, extract password if present, + * else use :envvar:`XDG_RUNTIME_DIR` to looks for an existing file in ``${XDG_RUNTIME_DIR:-/run/}/mpd/socket`` + * else set host to ``localhost`` - While :py:attr:`musicpd.MPDClient().host` and - :py:attr:`musicpd.MPDClient().port` keep track of current connection - host and port, :py:attr:`musicpd.MPDClient().pwd` is set once with + default port: + * use :envvar:`MPD_PORT` environment variable is set + * else use ``6600`` + + .. warning:: **Instance attribute host/port/pwd** + + While :py:attr:`musicpd.MPDClient.host` and + :py:attr:`musicpd.MPDClient.port` keep track of current connection + host and port, :py:attr:`musicpd.MPDClient.pwd` is set once with password extracted from environment variable. - Calling :py:meth:`musicpd.MPDClient().password()` with a new password - won't update :py:attr:`musicpd.MPDClient().pwd` value. + Calling :py:meth:`password` methode with a new password + won't update :py:attr:`musicpd.MPDClient.pwd` value. - Moreover, :py:attr:`musicpd.MPDClient().pwd` is only an helper attribute - exposing password extracted from ``MPD_HOST`` environment variable, it + Moreover, :py:attr:`musicpd.MPDClient.pwd` is only an helper attribute + exposing password extracted from :envvar:`MPD_HOST` environment variable, it will not be used as default value for the :py:meth:`password` method """ @@ -665,29 +675,19 @@ class MPDClient: def connect(self, host=None, port=None): """Connects the MPD server - :param str host: hostname, IP or FQDN (defaults to `localhost` or socket, see below for details) - :param port: port number (defaults to 6600) + :param str host: hostname, IP or FQDN (defaults to *localhost* or socket) + :param port: port number (defaults to *6600*) :type port: str or int - The connect method honors MPD_HOST/MPD_PORT environment variables. + If host/port are :py:obj:`None` the socket uses :py:attr:`host`/:py:attr:`port` + attributes as defaults. Cf. :py:obj:`MPDClient` for the logic behind default host/port. - The underlying socket also honors MPD_TIMEOUT environment variable + The underlying socket also honors :envvar:`MPD_TIMEOUT` environment variable and defaults to :py:obj:`musicpd.CONNECTION_TIMEOUT` (connect command only). If you want to have a timeout for each command once you got connected, set its value in :py:obj:`MPDClient.socket_timeout` (in second) or at module level in :py:obj:`musicpd.SOCKET_TIMEOUT`. - - .. note:: Default host/port - - If host evaluate to :py:obj:`False` - * use ``MPD_HOST`` environment variable if set, extract password if present, - * else looks for an existing file in ``${XDG_RUNTIME_DIR:-/run/}/mpd/socket`` - * else set host to ``localhost`` - - If port evaluate to :py:obj:`False` - * if ``MPD_PORT`` environment variable is set, use it for port - * else use ``6600`` """ if not host: host = self.host -- 2.39.2