import Queue
import threading
import sys

# This file provides a class that will wrap an object and serialize
# all calls to its methods.  All calls to that object will be queued
# and executed from a single thread, regardless of which thread makes
# the call.

# Based partially on http://stackoverflow.com/questions/2642515/

class SerializerThread(threading.Thread):
    """Thread that retrieves call information from the queue, makes the
    call, and returns the results."""
    def __init__(self, call_queue):
        threading.Thread.__init__(self)
        self.call_queue = call_queue

    def run(self):
        while True:
            result_queue, func, args, kwargs = self.call_queue.get()
            # Terminate if result_queue is None
            if result_queue is None:
                return
            try:
                result = func(*args, **kwargs) # wrapped
            except:
                result_queue.put((sys.exc_info(), None))
            else:
                result_queue.put((None, result))

class WrapCall(object):
    """Wrap a callable using the given queues"""

    def __init__(self, call_queue, result_queue, func):
        self.call_queue = call_queue
        self.result_queue = result_queue
        self.func = func

    def __call__(self, *args, **kwargs):
        self.call_queue.put((self.result_queue, self.func, args, kwargs))
        ( exc_info, result ) = self.result_queue.get()
        if exc_info is None:
            return result
        else:
            raise exc_info[0], exc_info[1], exc_info[2]

class WrapObject(object):
    """Wrap all calls to methods in a target object with WrapCall"""

    def __init__(self, target):
        self.__wrap_target = target
        self.__wrap_call_queue = Queue.Queue()
        self.__wrap_serializer = SerializerThread(self.__wrap_call_queue)
        self.__wrap_serializer.daemon = True
        self.__wrap_serializer.start()

    def __getattr__(self, key):
        """Wrap methods of self.__wrap_target in a WrapCall instance"""
        func = getattr(self.__wrap_target, key)
        if not callable(func):
            raise TypeError("Can't serialize attribute %r (type: %s)"
                            % (key, type(func)))
        result_queue = Queue.Queue()
        return WrapCall(self.__wrap_call_queue, result_queue, func)

    def __del__(self):
        self.__wrap_call_queue.put((None, None, None, None))
        self.__wrap_serializer.join()
