'''
Provide a class to serialize/deserialize objects if a safe manner
for transfering them over network connections.
One needs to subclass this and set the _allowed attribute of the
subclass to a dictionary containing mappings from a

Copyright 2005 Dan Pascu
'''

__all__ = ['SafeSerializer', 'SerializeError', 'UnserializeError']

import  cPickle
try:    from cStringIO import StringIO
except: from StringIO  import StringIO

SerializeError = cPickle.PicklingError
UnserializeError = cPickle.UnpicklingError

class SafeSerializer(object):
    '''
    Safe pickling/unpickling of objects. Serializing is not restricted,
    being able to serialize anything the pickle module can pickle.
    Unserialize on the other hand is restriced to the basic builtin types:
    str, unicode, tuple, list and dict
    
    By subclassing this and setting the _allowed attribute in the subclass
    to a dictionary (in which the key is a module name and the value is a
    list of classes from that module that are allowed to unserialize) it can
    be made to allow custom deserializing where in addition to the basic
    bulting types that are permitted by the SafeSerializer class, it will
    also permit these extra classes specified by the _allowed attribute.
    (the _allowed dictionary can contain multiple entries, for multiple
    modules)
    
    Example:
    class RequestSerializer(SafeSerializer):
        _allowed = {'request': ['Request', 'RequestInfo', 'GhostedStructure']}
    
    will permit serializing/deserializing of Request objects from the request
    module. The other classes are necessary because Request uses them.
    '''
    _allowed = {}
    def __filter(self, module, cls):
        if module == '__builtin__' and cls in ('str', 'unicode', 'tuple', 'list', 'dict'):
            return getattr(__import__(module), cls)
        elif module == 'copy_reg' and cls == '_reconstructor':
            return getattr(__import__(module), cls)
        elif module in self._allowed and cls in self._allowed[module]:
            return getattr(__import__(module), cls)
        else:
            msg = "cannot unserialize %s.%s (not allowed)" % (module, cls)
            raise UnserializeError(msg)
    def dump(self, obj):
        return cPickle.dumps(obj)
    def load(self, stream):
        file = StringIO(stream)
        unpickler = cPickle.Unpickler(file)
        unpickler.find_global = self.__filter
        return unpickler.load()

