
__author__ = 'Miroslav Bauer'


import zmq
import pickle
from logger import Logger

log = Logger('dmorph.rpcproxy')

def addMethod(self, name):
	def method(self, *args):
		return self.invoke(name, *args)
	setattr(self, name, method)


class RpcProxy:
	''' This class serves as proxy object for zmqthread.RpcZmqThread
	    objects. It has the same methods set as the original object which
	    invokes original methods on it synchronously using zmq.REQ socket.
	'''

	def __init__(self, context, cls):
		self._context  = context
		self._requests = None
		# Create proxy methods for cls class 'public' methods
		for m in cls.__dict__.keys():
			if not m.startswith('_'):
				# By using 'currying' technique, prepare method
				# named as m which just calls self.invoke()
				# with prepared argument methodname=m,
				# then bind this method to current self instance
				# so it becomes instance bound method
				addMethod(self, m)
				setattr(self, m, self.__dict__[m].__get__(self, self.__class__))

	def connect(self, endpoint):
		''' Connects zmq.REQ socket for requests to RpcZmqThread instance,
		    which is identified by an endpoint it is listening on.
		'''
		self._requests = self._context.socket(zmq.REQ)
		try:
			self._requests.connect('inproc://%s' % endpoint)
		except IOError:
			log.err('Could not connect proxy to %s' % endpoint)
			self.terminate()
			raise

	def invoke(self, methodname, *args):
		''' Invoke method on a RpcZmqThread instance and return results '''
		self._requests.send_multipart([methodname, pickle.dumps(args)])
		result = pickle.loads(self._requests.recv())
		if isinstance(result, Exception): raise result
		return result
	
	def _terminate(self):
		''' Closes the socket used for requests '''
		if self._requests:
			self._requests.close()	
