Skip to content

Commit 9a3eacc

Browse files
committed
base: Add direct commands for async overriding
This adds a distinct (but for synchronous and twisted use identical) _parse_objects_direct method that can gets used everywhere the result is returned right away. Such methods also get an is_direct flag in their decorator, which allows the asyncio implementation to work without an explicit list like _wrap_iterator_parsers.
1 parent 12b4783 commit 9a3eacc

File tree

2 files changed

+48
-48
lines changed

2 files changed

+48
-48
lines changed

mpd/asyncio.py

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1+
"""Asynchronous access to MPD using the asyncio methods of Python 3.
2+
3+
Interaction happens over the mpd.asyncio.MPDClient class, whose connect and
4+
command methods are coroutines.
5+
6+
Some commands (eg. listall) additionally support the asynchronous iteration
7+
(aiter, `async for`) interface; using it allows the library user to obtain
8+
items of result as soon as they arrive.
9+
10+
The .idle() method works as expected, but there .noidle() method is not
11+
implemented pending a notifying (and automatically idling on demand) interface.
12+
The asynchronous .idle() method is thus only suitable for clients which only
13+
want to send commands after an idle returned (eg. current song notification
14+
pushers).
15+
16+
Command lists are currently not supported.
17+
"""
18+
119
import asyncio
220
from functools import partial
321

422
from mpd.base import HELLO_PREFIX, ERROR_PREFIX, SUCCESS
523
from mpd.base import MPDClientBase
624
from mpd.base import MPDClient as SyncMPDClient
725
from mpd.base import ProtocolError, ConnectionError, CommandError
8-
from mpd.base import mpd_command_provider, mpd_commands
26+
from mpd.base import mpd_command_provider
927

1028
class BaseCommandResult(asyncio.Future):
1129
"""A future that carries its command/args/callback with it for the
@@ -163,8 +181,7 @@ async def __read_output_line(self):
163181
return None
164182
return line
165183

166-
async def _parse_objects(self, lines, delimiters=[]):
167-
"""Like _parse_objects, but waits for lines"""
184+
async def _parse_objects_direct(self, lines, delimiters=[]):
168185
obj = {}
169186
while True:
170187
line = await lines.get()
@@ -188,36 +205,11 @@ async def _parse_objects(self, lines, delimiters=[]):
188205
if obj:
189206
yield obj
190207

191-
# as the above works for everyone who calls `return _parse_objects` but
192-
# *not* for those that return list(_parse_objects(...))[0], that single
193-
# function is rewritten here to use the original _parse_objects
194-
195-
@mpd_commands('count', 'currentsong', 'readcomments', 'stats', 'status')
196-
def _parse_object(self, lines):
197-
objs = list(SyncMPDClient._parse_objects(self, lines))
198-
if not objs:
199-
return {}
200-
return objs[0]
201-
202208
# command provider interface
203209

204-
__wrap_async_iterator_parsers = [
205-
# the very ones that return _parse_object directly
206-
SyncMPDClient._parse_changes,
207-
SyncMPDClient._parse_database,
208-
SyncMPDClient._parse_messages,
209-
SyncMPDClient._parse_mounts,
210-
SyncMPDClient._parse_neighbors,
211-
SyncMPDClient._parse_outputs,
212-
SyncMPDClient._parse_playlists,
213-
SyncMPDClient._parse_plugins,
214-
SyncMPDClient._parse_songs,
215-
]
216-
217210
@classmethod
218211
def add_command(cls, name, callback):
219-
wrap_result = callback in cls.__wrap_async_iterator_parsers
220-
command_class = CommandResultIterable if wrap_result else CommandResult
212+
command_class = CommandResultIterable if callback.mpd_commands_direct else CommandResult
221213
if hasattr(cls, name):
222214
# twisted silently ignores them; probably, i'll make an
223215
# experience that'll make me take the same router at some point.

mpd/base.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,13 @@ class mpd_commands(object):
117117
callback.
118118
"""
119119

120-
def __init__(self, *commands):
120+
def __init__(self, *commands, is_direct=False):
121121
self.commands = commands
122+
self.is_direct = is_direct
122123

123124
def __call__(self, ob):
124125
ob.mpd_commands = self.commands
126+
ob.mpd_commands_direct = self.is_direct
125127
return ob
126128

127129

@@ -232,6 +234,10 @@ def _parse_objects(self, lines, delimiters=[]):
232234
if obj:
233235
yield obj
234236

237+
# Use this instead of _parse_objects whenever the result is returned
238+
# immediately in a command implementation
239+
_parse_objects_direct = _parse_objects
240+
235241
def _parse_raw_stickers(self, lines):
236242
for key, sticker in self._parse_pairs(lines):
237243
value = sticker.split('=', 1)
@@ -242,13 +248,14 @@ def _parse_raw_stickers(self, lines):
242248

243249
NOOP = mpd_commands('close', 'kill')(Noop())
244250

245-
@mpd_commands('plchangesposid')
251+
@mpd_commands('plchangesposid', is_direct=True)
246252
def _parse_changes(self, lines):
247-
return self._parse_objects(lines, ["cpos"])
253+
return self._parse_objects_direct(lines, ["cpos"])
248254

249-
@mpd_commands('listall', 'listallinfo', 'listfiles', 'lsinfo')
255+
@mpd_commands('listall', 'listallinfo', 'listfiles', 'lsinfo',
256+
is_direct=True)
250257
def _parse_database(self, lines):
251-
return self._parse_objects(lines, ["file", "directory", "playlist"])
258+
return self._parse_objects_direct(lines, ["file", "directory", "playlist"])
252259

253260
@mpd_commands('idle')
254261
def _parse_idle(self, lines):
@@ -274,17 +281,17 @@ def _parse_list(self, lines):
274281
seen = key
275282
yield value
276283

277-
@mpd_commands('readmessages')
284+
@mpd_commands('readmessages', is_direct=True)
278285
def _parse_messages(self, lines):
279-
return self._parse_objects(lines, ["channel"])
286+
return self._parse_objects_direct(lines, ["channel"])
280287

281-
@mpd_commands('listmounts')
288+
@mpd_commands('listmounts', is_direct=True)
282289
def _parse_mounts(self, lines):
283-
return self._parse_objects(lines, ["mount"])
290+
return self._parse_objects_direct(lines, ["mount"])
284291

285-
@mpd_commands('listneighbors')
292+
@mpd_commands('listneighbors', is_direct=True)
286293
def _parse_neighbors(self, lines):
287-
return self._parse_objects(lines, ["neighbor"])
294+
return self._parse_objects_direct(lines, ["neighbor"])
288295

289296
@mpd_commands(
290297
'add', 'addtagid', 'clear', 'clearerror', 'cleartagid', 'consume',
@@ -309,28 +316,29 @@ def _parse_object(self, lines):
309316
return {}
310317
return objs[0]
311318

312-
@mpd_commands('outputs')
319+
@mpd_commands('outputs', is_direct=True)
313320
def _parse_outputs(self, lines):
314-
return self._parse_objects(lines, ["outputid"])
321+
return self._parse_objects_direct(lines, ["outputid"])
315322

316323
@mpd_commands('playlist')
317324
def _parse_playlist(self, lines):
318325
for key, value in self._parse_pairs(lines, ":"):
319326
yield value
320327

321-
@mpd_commands('listplaylists')
328+
@mpd_commands('listplaylists', is_direct=True)
322329
def _parse_playlists(self, lines):
323-
return self._parse_objects(lines, ["playlist"])
330+
return self._parse_objects_direct(lines, ["playlist"])
324331

325-
@mpd_commands('decoders')
332+
@mpd_commands('decoders', is_direct=True)
326333
def _parse_plugins(self, lines):
327-
return self._parse_objects(lines, ["plugin"])
334+
return self._parse_objects_direct(lines, ["plugin"])
328335

329336
@mpd_commands(
330337
'find', 'listplaylistinfo', 'playlistfind', 'playlistid',
331-
'playlistinfo', 'playlistsearch', 'plchanges', 'search', 'sticker find')
338+
'playlistinfo', 'playlistsearch', 'plchanges', 'search',
339+
'sticker find', is_direct=True)
332340
def _parse_songs(self, lines):
333-
return self._parse_objects(lines, ["file"])
341+
return self._parse_objects_direct(lines, ["file"])
334342

335343
@mpd_commands('sticker get')
336344
def _parse_sticker(self, lines):

0 commit comments

Comments
 (0)