Proxy una clase en Python
-
21-12-2019 - |
Pregunta
Estoy usando el módulo python-mpd2 para controlar un reproductor multimedia en una Raspberry Pi en una aplicación GUI.Por lo tanto, me gustaría manejar con elegancia los errores de conexión y los tiempos de espera (el reproductor en cuestión desconecta las conexiones MPD después de 60 segundos) en segundo plano.Sin embargo, el módulo MPD no tiene un único punto de entrada a través del cual se envíen todos los comandos o se recupere la información que pueda parchear.
Me gustaría una clase que permita el acceso a todos los mismos métodos que mpd.MPDClient, pero permítanme agregar mi propio manejo de errores.En otras palabras, si hago:
client.play()
Y se produce un error de conexión, me gustaría detectarlo y reenviar el mismo comando.Aparte del pequeño retraso causado por tener que volver a conectarse al servidor, el usuario no debería notar que nada anda mal.
Hasta ahora, esta es la solución que se me ocurrió.Está funcionando en mi aplicación, pero realmente no cumple mis objetivos.
from functools import partial
from mpd import MPDClient, ConnectionError
class PersistentMPDClient(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.client = MPDClient()
self.client.connect(self.host, self.port)
def command(self, cmd, *args, **kwargs):
command_callable = partial(self.client.__getattribute__(cmd), *args, **kwargs)
try:
return command_callable()
except ConnectionError:
# Mopidy drops our connection after a while, so reconnect to send the command
self.client._soc = None
self.client.connect(self.host, self.port)
return command_callable()
Podría agregar un método a esta clase para cada comando MPD, por ejemplo:
def play(self):
return self.command("play")
Pero esto parece estar lejos de ser la mejor manera de lograrlo.
Solución
Si no le importa crear una lista de las 91 cadenas que se forman nombres de comando, puedes hacer algo parecido a esta respuesta.Creo que este enfoque tiene muchas ventajas porque implica menos magia.
OTOH, 91 es realmente mucho.Así que aquí tienes una solución automática, que hace uso de una configuración personalizada. __getattr__
, que devuelve un contenedor:
from functools import partial
import types
class DummyClient(object):
def connect(self, *a, **kw): print 'connecting %r %r' % (a, kw)
def play(self): print 'playing'
def stop(self): print 'stopping'
class PersistentMPDClient(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.client = DummyClient()
self.client.connect(self.host, self.port)
def __getattr__(self, attr, *args):
cmd = getattr(self.client, attr, *args)
if isinstance(cmd, types.MethodType):
# a method -- wrap
return lambda *a, **kw: self.command(attr, *a, **kw)
else:
# anything else -- return unchanged
return cmd
def command(self, cmd, *args, **kwargs):
command_callable = partial(self.client.__getattribute__(cmd), *args, **kwargs)
try:
return command_callable()
except ConnectionError:
# Mopidy drops our connection after a while, so reconnect to send the command
self.client._soc = None
self.client.connect(self.host, self.port)
return command_callable()
c = PersistentMPDClient(hostname, port)
c.play()
c.stop()
Mientras escribía esto, noté que @MatToufoutu había publicado una solución similar (aunque hay algunas diferencias).No sé por qué lo borró...Si esa respuesta se recupera, con mucho gusto le daría el crédito que se merece.