saranwrap: remove saranwrap which was deprecated in 2010-02
This commit is contained in:
@@ -1,715 +0,0 @@
|
||||
import cPickle as Pickle
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from eventlet.processes import Process, DeadProcess
|
||||
from eventlet import pools
|
||||
|
||||
import warnings
|
||||
warnings.warn("eventlet.saranwrap is deprecated due to underuse. If you love "
|
||||
"it, let us know by emailing eventletdev@lists.secondlife.com",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
|
||||
# debugging hooks
|
||||
_g_debug_mode = False
|
||||
if _g_debug_mode:
|
||||
import traceback
|
||||
import tempfile
|
||||
|
||||
def pythonpath_sync():
|
||||
"""
|
||||
apply the current ``sys.path`` to the environment variable ``PYTHONPATH``,
|
||||
so that child processes have the same paths as the caller does.
|
||||
"""
|
||||
pypath = os.pathsep.join(sys.path)
|
||||
os.environ['PYTHONPATH'] = pypath
|
||||
|
||||
def wrap(obj, dead_callback = None):
|
||||
"""
|
||||
wrap in object in another process through a saranwrap proxy
|
||||
:param object: The object to wrap.
|
||||
:dead_callback: A callable to invoke if the process exits.
|
||||
"""
|
||||
|
||||
if type(obj).__name__ == 'module':
|
||||
return wrap_module(obj.__name__, dead_callback)
|
||||
pythonpath_sync()
|
||||
if _g_debug_mode:
|
||||
p = Process(sys.executable,
|
||||
["-W", "ignore", __file__, '--child',
|
||||
'--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')],
|
||||
dead_callback)
|
||||
else:
|
||||
p = Process(sys.executable, ["-W", "ignore", __file__, '--child'], dead_callback)
|
||||
prox = Proxy(ChildProcess(p, p))
|
||||
prox.obj = obj
|
||||
return prox.obj
|
||||
|
||||
def wrap_module(fqname, dead_callback = None):
|
||||
"""
|
||||
wrap a module in another process through a saranwrap proxy
|
||||
|
||||
:param fqname: The fully qualified name of the module.
|
||||
:param dead_callback: A callable to invoke if the process exits.
|
||||
"""
|
||||
pythonpath_sync()
|
||||
global _g_debug_mode
|
||||
if _g_debug_mode:
|
||||
p = Process(sys.executable,
|
||||
["-W", "ignore", __file__, '--module', fqname,
|
||||
'--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')],
|
||||
dead_callback)
|
||||
else:
|
||||
p = Process(sys.executable,
|
||||
["-W", "ignore", __file__, '--module', fqname,], dead_callback)
|
||||
prox = Proxy(ChildProcess(p,p))
|
||||
return prox
|
||||
|
||||
def status(proxy):
|
||||
"""
|
||||
get the status from the server through a proxy
|
||||
|
||||
:param proxy: a :class:`eventlet.saranwrap.Proxy` object connected to a
|
||||
server.
|
||||
"""
|
||||
return proxy.__local_dict['_cp'].make_request(Request('status', {}))
|
||||
|
||||
class BadResponse(Exception):
|
||||
"""This exception is raised by an saranwrap client when it could
|
||||
parse but cannot understand the response from the server."""
|
||||
pass
|
||||
|
||||
class BadRequest(Exception):
|
||||
"""This exception is raised by a saranwrap server when it could parse
|
||||
but cannot understand the response from the server."""
|
||||
pass
|
||||
|
||||
class UnrecoverableError(Exception):
|
||||
pass
|
||||
|
||||
class Request(object):
|
||||
"A wrapper class for proxy requests to the server."
|
||||
def __init__(self, action, param):
|
||||
self._action = action
|
||||
self._param = param
|
||||
def __str__(self):
|
||||
return "Request `"+self._action+"` "+str(self._param)
|
||||
def __getitem__(self, name):
|
||||
return self._param[name]
|
||||
def get(self, name, default = None):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
return default
|
||||
def action(self):
|
||||
return self._action
|
||||
|
||||
def _read_lp_hunk(stream):
|
||||
len_bytes = stream.read(4)
|
||||
if len_bytes == '':
|
||||
raise EOFError("No more data to read from %s" % stream)
|
||||
length = struct.unpack('I', len_bytes)[0]
|
||||
body = stream.read(length)
|
||||
return body
|
||||
|
||||
def _read_response(id, attribute, input, cp):
|
||||
"""local helper method to read respones from the rpc server."""
|
||||
try:
|
||||
str = _read_lp_hunk(input)
|
||||
_prnt(repr(str))
|
||||
response = Pickle.loads(str)
|
||||
except (AttributeError, DeadProcess, Pickle.UnpicklingError), e:
|
||||
raise UnrecoverableError(e)
|
||||
_prnt("response: %s" % response)
|
||||
if response[0] == 'value':
|
||||
return response[1]
|
||||
elif response[0] == 'callable':
|
||||
return CallableProxy(id, attribute, cp)
|
||||
elif response[0] == 'object':
|
||||
return ObjectProxy(cp, response[1])
|
||||
elif response[0] == 'exception':
|
||||
exp = response[1]
|
||||
raise exp
|
||||
else:
|
||||
raise BadResponse(response[0])
|
||||
|
||||
def _write_lp_hunk(stream, hunk):
|
||||
write_length = struct.pack('I', len(hunk))
|
||||
stream.write(write_length + hunk)
|
||||
if hasattr(stream, 'flush'):
|
||||
stream.flush()
|
||||
|
||||
def _write_request(param, output):
|
||||
_prnt("request: %s" % param)
|
||||
str = Pickle.dumps(param)
|
||||
_write_lp_hunk(output, str)
|
||||
|
||||
def _is_local(attribute):
|
||||
"Return ``True`` if the attribute should be handled locally"
|
||||
# return attribute in ('_in', '_out', '_id', '__getattribute__',
|
||||
# '__setattr__', '__dict__')
|
||||
# good enough for now. :)
|
||||
if '__local_dict' in attribute:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _prnt(message):
|
||||
global _g_debug_mode
|
||||
if _g_debug_mode:
|
||||
print message
|
||||
|
||||
_g_logfile = None
|
||||
def _log(message):
|
||||
global _g_logfile
|
||||
if _g_logfile:
|
||||
_g_logfile.write(str(os.getpid()) + ' ' + message + '\n')
|
||||
_g_logfile.flush()
|
||||
|
||||
def _unmunge_attr_name(name):
|
||||
""" Sometimes attribute names come in with classname prepended, not sure why.
|
||||
This function removes said classname, because we're huge hackers and we didn't
|
||||
find out what the true right thing to do is. *TODO: find out. """
|
||||
if(name.startswith('_Proxy')):
|
||||
name = name[len('_Proxy'):]
|
||||
if(name.startswith('_ObjectProxy')):
|
||||
name = name[len('_ObjectProxy'):]
|
||||
|
||||
return name
|
||||
|
||||
class ChildProcess(object):
|
||||
"""
|
||||
This class wraps a remote python process, presumably available in an
|
||||
instance of a :class:`Server`.
|
||||
"""
|
||||
def __init__(self, instr, outstr, dead_list = None):
|
||||
"""
|
||||
:param instr: a file-like object which supports ``read()``.
|
||||
:param outstr: a file-like object which supports ``write()`` and
|
||||
``flush()``.
|
||||
:param dead_list: a list of ids of remote objects that are dead
|
||||
"""
|
||||
# default dead_list inside the function because all objects in method
|
||||
# argument lists are init-ed only once globally
|
||||
_prnt("ChildProcess::__init__")
|
||||
if dead_list is None:
|
||||
dead_list = set()
|
||||
self._dead_list = dead_list
|
||||
self._in = instr
|
||||
self._out = outstr
|
||||
self._lock = pools.TokenPool(max_size=1)
|
||||
|
||||
def make_request(self, request, attribute=None):
|
||||
_id = request.get('id')
|
||||
|
||||
t = self._lock.get()
|
||||
try:
|
||||
_write_request(request, self._out)
|
||||
retval = _read_response(_id, attribute, self._in, self)
|
||||
finally:
|
||||
self._lock.put(t)
|
||||
|
||||
return retval
|
||||
|
||||
def __del__(self):
|
||||
self._in.close()
|
||||
|
||||
|
||||
class Proxy(object):
|
||||
"""
|
||||
This is the class you will typically use as a client to a child
|
||||
process.
|
||||
|
||||
Simply instantiate one around a file-like interface and start calling
|
||||
methods on the thing that is exported. The ``dir()`` builtin is not
|
||||
supported, so you have to know what has been exported.
|
||||
"""
|
||||
def __init__(self, cp):
|
||||
"""
|
||||
:param cp: :class:`ChildProcess` instance that wraps the i/o to the
|
||||
child process.
|
||||
"""
|
||||
#_prnt("Proxy::__init__")
|
||||
self.__local_dict = dict(
|
||||
_cp = cp,
|
||||
_id = None)
|
||||
|
||||
def __getattribute__(self, attribute):
|
||||
#_prnt("Proxy::__getattr__: %s" % attribute)
|
||||
if _is_local(attribute):
|
||||
# call base class getattribute so we actually get the local variable
|
||||
attribute = _unmunge_attr_name(attribute)
|
||||
return super(Proxy, self).__getattribute__(attribute)
|
||||
elif attribute in ('__deepcopy__', '__copy__'):
|
||||
# redirect copy function calls to our own versions instead of
|
||||
# to the proxied object
|
||||
return super(Proxy, self).__getattribute__('__deepcopy__')
|
||||
else:
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
|
||||
_dead_list = my_cp._dead_list
|
||||
for dead_object in _dead_list.copy():
|
||||
request = Request('del', {'id':dead_object})
|
||||
|
||||
my_cp.make_request(request)
|
||||
try:
|
||||
_dead_list.remove(dead_object)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Pass all public attributes across to find out if it is
|
||||
# callable or a simple attribute.
|
||||
request = Request('getattr', {'id':my_id, 'attribute':attribute})
|
||||
return my_cp.make_request(request, attribute=attribute)
|
||||
|
||||
def __setattr__(self, attribute, value):
|
||||
#_prnt("Proxy::__setattr__: %s" % attribute)
|
||||
if _is_local(attribute):
|
||||
# It must be local to this actual object, so we have to apply
|
||||
# it to the dict in a roundabout way
|
||||
attribute = _unmunge_attr_name(attribute)
|
||||
super(Proxy, self).__getattribute__('__dict__')[attribute]=value
|
||||
else:
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
# Pass the set attribute across
|
||||
request = Request('setattr',
|
||||
{'id':my_id, 'attribute':attribute, 'value':value})
|
||||
return my_cp.make_request(request, attribute=attribute)
|
||||
|
||||
class ObjectProxy(Proxy):
|
||||
"""
|
||||
This class wraps a remote object in the :class:`Server`
|
||||
|
||||
This class will be created during normal operation, and users should
|
||||
not need to deal with this class directly.
|
||||
"""
|
||||
|
||||
def __init__(self, cp, _id):
|
||||
"""
|
||||
:param cp: A :class:`ChildProcess` object that wraps the i/o of a child
|
||||
process.
|
||||
:param _id: an identifier for the remote object. humans do not provide
|
||||
this.
|
||||
"""
|
||||
Proxy.__init__(self, cp)
|
||||
self.__local_dict['_id'] = _id
|
||||
#_prnt("ObjectProxy::__init__ %s" % _id)
|
||||
|
||||
def __del__(self):
|
||||
my_id = self.__local_dict['_id']
|
||||
#_prnt("ObjectProxy::__del__ %s" % my_id)
|
||||
self.__local_dict['_cp']._dead_list.add(my_id)
|
||||
|
||||
def __getitem__(self, key):
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('getitem', {'id':my_id, 'key':key})
|
||||
return my_cp.make_request(request, attribute=key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('setitem', {'id':my_id, 'key':key, 'value':value})
|
||||
return my_cp.make_request(request, attribute=key)
|
||||
|
||||
def __eq__(self, rhs):
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('eq', {'id':my_id, 'rhs':rhs.__local_dict['_id']})
|
||||
return my_cp.make_request(request)
|
||||
|
||||
def __repr__(self):
|
||||
# apparently repr(obj) skips the whole getattribute thing and just calls __repr__
|
||||
# directly. Therefore we just pass it through the normal call pipeline, and
|
||||
# tack on a little header so that you can tell it's an object proxy.
|
||||
val = self.__repr__()
|
||||
return "saran:%s" % val
|
||||
|
||||
def __str__(self):
|
||||
# see description for __repr__, because str(obj) works the same. We don't
|
||||
# tack anything on to the return value here because str values are used as data.
|
||||
return self.__str__()
|
||||
|
||||
def __nonzero__(self):
|
||||
# bool(obj) is another method that skips __getattribute__.
|
||||
# There's no good way to just pass
|
||||
# the method on, so we use a special message.
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('nonzero', {'id':my_id})
|
||||
return my_cp.make_request(request)
|
||||
|
||||
def __len__(self):
|
||||
# see description for __repr__, len(obj) is the same.
|
||||
return self.__len__()
|
||||
|
||||
def __contains__(self, item):
|
||||
# another special name that is normally called without recours to __getattribute__
|
||||
return self.__contains__(item)
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
"""Copies the entire external object and returns its
|
||||
value. Will only work if the remote object is pickleable."""
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('copy', {'id':my_id})
|
||||
return my_cp.make_request(request)
|
||||
|
||||
# since the remote object is being serialized whole anyway,
|
||||
# there's no semantic difference between copy and deepcopy
|
||||
__copy__ = __deepcopy__
|
||||
|
||||
|
||||
def proxied_type(self):
|
||||
""" Returns the type of the object in the child process.
|
||||
|
||||
Calling type(obj) on a saranwrapped object will always return
|
||||
<class saranwrap.ObjectProxy>, so this is a way to get at the
|
||||
'real' type value."""
|
||||
if type(self) is not ObjectProxy:
|
||||
return type(self)
|
||||
|
||||
my_cp = self.__local_dict['_cp']
|
||||
my_id = self.__local_dict['_id']
|
||||
request = Request('type', {'id':my_id})
|
||||
return my_cp.make_request(request)
|
||||
|
||||
|
||||
def getpid(self):
|
||||
""" Returns the pid of the child process. The argument should be
|
||||
a saranwrapped object."""
|
||||
my_cp = self.__local_dict['_cp']
|
||||
return my_cp._in.getpid()
|
||||
|
||||
|
||||
class CallableProxy(object):
|
||||
"""
|
||||
This class wraps a remote function in the :class:`Server`
|
||||
|
||||
This class will be created by an :class:`Proxy` during normal operation,
|
||||
and users should not need to deal with this class directly.
|
||||
"""
|
||||
|
||||
def __init__(self, object_id, name, cp):
|
||||
#_prnt("CallableProxy::__init__: %s, %s" % (object_id, name))
|
||||
self._object_id = object_id
|
||||
self._name = name
|
||||
self._cp = cp
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
#_prnt("CallableProxy::__call__: %s, %s" % (args, kwargs))
|
||||
|
||||
# Pass the call across. We never build a callable without
|
||||
# having already checked if the method starts with '_' so we
|
||||
# can safely pass this one to the remote object.
|
||||
#_prnt("calling %s %s" % (self._object_id, self._name)
|
||||
request = Request('call', {'id':self._object_id,
|
||||
'name':self._name,
|
||||
'args':args, 'kwargs':kwargs})
|
||||
return self._cp.make_request(request, attribute=self._name)
|
||||
|
||||
class Server(object):
|
||||
def __init__(self, input, output, export):
|
||||
"""
|
||||
:param input: a file-like object which supports ``read()``.
|
||||
:param output: a file-like object which supports ``write()`` and
|
||||
``flush()``.
|
||||
:param export: an object, function, or map which is exported to clients
|
||||
when the id is ``None``.
|
||||
"""
|
||||
#_log("Server::__init__")
|
||||
self._in = input
|
||||
self._out = output
|
||||
self._export = export
|
||||
self._next_id = 1
|
||||
self._objects = {}
|
||||
|
||||
def handle_status(self, obj, req):
|
||||
return {
|
||||
'object_count':len(self._objects),
|
||||
'next_id':self._next_id,
|
||||
'pid':os.getpid()}
|
||||
|
||||
def handle_getattr(self, obj, req):
|
||||
try:
|
||||
return getattr(obj, req['attribute'])
|
||||
except AttributeError, e:
|
||||
if hasattr(obj, "__getitem__"):
|
||||
return obj[req['attribute']]
|
||||
else:
|
||||
raise e
|
||||
#_log('getattr: %s' % str(response))
|
||||
|
||||
def handle_setattr(self, obj, req):
|
||||
try:
|
||||
return setattr(obj, req['attribute'], req['value'])
|
||||
except AttributeError, e:
|
||||
if hasattr(obj, "__setitem__"):
|
||||
return obj.__setitem__(req['attribute'], req['value'])
|
||||
else:
|
||||
raise e
|
||||
|
||||
def handle_getitem(self, obj, req):
|
||||
return obj[req['key']]
|
||||
|
||||
def handle_setitem(self, obj, req):
|
||||
obj[req['key']] = req['value']
|
||||
return None # *TODO figure out what the actual return value
|
||||
# of __setitem__ should be
|
||||
|
||||
def handle_eq(self, obj, req):
|
||||
#_log("__eq__ %s %s" % (obj, req))
|
||||
rhs = None
|
||||
try:
|
||||
rhs = self._objects[req['rhs']]
|
||||
except KeyError:
|
||||
return False
|
||||
return (obj == rhs)
|
||||
|
||||
def handle_call(self, obj, req):
|
||||
#_log("calling %s " % (req['name']))
|
||||
try:
|
||||
fn = getattr(obj, req['name'])
|
||||
except AttributeError, e:
|
||||
if hasattr(obj, "__setitem__"):
|
||||
fn = obj[req['name']]
|
||||
else:
|
||||
raise e
|
||||
|
||||
return fn(*req['args'],**req['kwargs'])
|
||||
|
||||
def handle_del(self, obj, req):
|
||||
id = req['id']
|
||||
_log("del %s from %s" % (id, self._objects))
|
||||
|
||||
# *TODO what does __del__ actually return?
|
||||
try:
|
||||
del self._objects[id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def handle_type(self, obj, req):
|
||||
return type(obj)
|
||||
|
||||
def handle_nonzero(self, obj, req):
|
||||
return bool(obj)
|
||||
|
||||
def handle_copy(self, obj, req):
|
||||
return obj
|
||||
|
||||
def loop(self):
|
||||
"""Loop forever and respond to all requests."""
|
||||
_log("Server::loop")
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
str_ = _read_lp_hunk(self._in)
|
||||
except EOFError:
|
||||
if _g_debug_mode:
|
||||
_log("Exiting normally")
|
||||
sys.exit(0)
|
||||
|
||||
request = Pickle.loads(str_)
|
||||
_log("request: %s (%s)" % (request, self._objects))
|
||||
req = request
|
||||
id = None
|
||||
obj = None
|
||||
try:
|
||||
id = req['id']
|
||||
if id:
|
||||
id = int(id)
|
||||
obj = self._objects[id]
|
||||
#_log("id, object: %d %s" % (id, obj))
|
||||
except Exception, e:
|
||||
#_log("Exception %s" % str(e))
|
||||
pass
|
||||
if obj is None or id is None:
|
||||
id = None
|
||||
obj = self._export()
|
||||
#_log("found object %s" % str(obj))
|
||||
|
||||
# Handle the request via a method with a special name on the server
|
||||
handler_name = 'handle_%s' % request.action()
|
||||
|
||||
try:
|
||||
handler = getattr(self, handler_name)
|
||||
except AttributeError:
|
||||
raise BadRequest, request.action()
|
||||
|
||||
response = handler(obj, request)
|
||||
|
||||
# figure out what to do with the response, and respond
|
||||
# apprpriately.
|
||||
if request.action() in ['status', 'type', 'copy']:
|
||||
# have to handle these specially since we want to
|
||||
# pickle up the actual value and not return a proxy
|
||||
self.respond(['value', response])
|
||||
elif callable(response):
|
||||
#_log("callable %s" % response)
|
||||
self.respond(['callable'])
|
||||
elif self.is_value(response):
|
||||
self.respond(['value', response])
|
||||
else:
|
||||
self._objects[self._next_id] = response
|
||||
#_log("objects: %s" % str(self._objects))
|
||||
self.respond(['object', self._next_id])
|
||||
self._next_id += 1
|
||||
except (KeyboardInterrupt, SystemExit), e:
|
||||
raise e
|
||||
except Exception, e:
|
||||
self.write_exception(e)
|
||||
|
||||
def is_value(self, value):
|
||||
"""
|
||||
Test if *value* should be serialized as a simple dataset.
|
||||
|
||||
:param value: The value to test.
|
||||
:return: Returns ``True`` if *value* is a simple serializeable set of
|
||||
data.
|
||||
"""
|
||||
return type(value) in (str,unicode,int,float,long,bool,type(None))
|
||||
|
||||
def respond(self, body):
|
||||
_log("responding with: %s" % body)
|
||||
#_log("objects: %s" % self._objects)
|
||||
s = Pickle.dumps(body)
|
||||
_log(repr(s))
|
||||
_write_lp_hunk(self._out, s)
|
||||
|
||||
def write_exception(self, e):
|
||||
"""Helper method to respond with an exception."""
|
||||
#_log("exception: %s" % sys.exc_info()[0])
|
||||
# TODO: serialize traceback using generalization of code from mulib.htmlexception
|
||||
|
||||
global _g_debug_mode
|
||||
if _g_debug_mode:
|
||||
_log("traceback: %s" % traceback.format_tb(sys.exc_info()[2]))
|
||||
|
||||
self.respond(['exception', e])
|
||||
|
||||
|
||||
# test function used for testing return of unpicklable exceptions
|
||||
def raise_an_unpicklable_error():
|
||||
class Unpicklable(Exception):
|
||||
pass
|
||||
raise Unpicklable()
|
||||
|
||||
# test function used for testing return of picklable exceptions
|
||||
def raise_standard_error():
|
||||
raise FloatingPointError()
|
||||
|
||||
# test function to make sure print doesn't break the wrapper
|
||||
def print_string(str):
|
||||
print str
|
||||
|
||||
# test function to make sure printing on stdout doesn't break the
|
||||
# wrapper
|
||||
def err_string(str):
|
||||
print >>sys.stderr, str
|
||||
|
||||
def named(name):
|
||||
"""Return an object given its name.
|
||||
|
||||
The name uses a module-like syntax, eg::
|
||||
|
||||
os.path.join
|
||||
|
||||
or::
|
||||
|
||||
mulib.mu.Resource
|
||||
"""
|
||||
toimport = name
|
||||
obj = None
|
||||
import_err_strings = []
|
||||
while toimport:
|
||||
try:
|
||||
obj = __import__(toimport)
|
||||
break
|
||||
except ImportError, err:
|
||||
# print 'Import error on %s: %s' % (toimport, err) # debugging spam
|
||||
import_err_strings.append(err.__str__())
|
||||
toimport = '.'.join(toimport.split('.')[:-1])
|
||||
if obj is None:
|
||||
raise ImportError(
|
||||
'%s could not be imported. Import errors: %r' % (name, import_err_strings))
|
||||
for seg in name.split('.')[1:]:
|
||||
try:
|
||||
obj = getattr(obj, seg)
|
||||
except AttributeError:
|
||||
dirobj = dir(obj)
|
||||
dirobj.sort()
|
||||
raise AttributeError(
|
||||
'attribute %r missing from %r (%r) %r. Import errors: %r' % (
|
||||
seg, obj, dirobj, name, import_err_strings))
|
||||
return obj
|
||||
|
||||
|
||||
def main():
|
||||
import optparse
|
||||
parser = optparse.OptionParser(
|
||||
usage="usage: %prog [options]",
|
||||
description="Simple saranwrap.Server wrapper")
|
||||
parser.add_option(
|
||||
'-c', '--child', default=False, action='store_true',
|
||||
help='Wrap an object serialized via setattr.')
|
||||
parser.add_option(
|
||||
'-m', '--module', type='string', dest='module', default=None,
|
||||
help='a module to load and export.')
|
||||
parser.add_option(
|
||||
'-l', '--logfile', type='string', dest='logfile', default=None,
|
||||
help='file to log to.')
|
||||
options, args = parser.parse_args()
|
||||
global _g_logfile
|
||||
if options.logfile:
|
||||
_g_logfile = open(options.logfile, 'a')
|
||||
|
||||
from eventlet import tpool
|
||||
base_obj = [None]
|
||||
if options.module:
|
||||
def get_module():
|
||||
if base_obj[0] is None:
|
||||
base_obj[0] = named(options.module)
|
||||
return base_obj[0]
|
||||
server = Server(tpool.Proxy(sys.stdin),
|
||||
tpool.Proxy(sys.stdout),
|
||||
get_module)
|
||||
elif options.child:
|
||||
def get_base():
|
||||
if base_obj[0] is None:
|
||||
base_obj[0] = {}
|
||||
return base_obj[0]
|
||||
server = Server(tpool.Proxy(sys.stdin),
|
||||
tpool.Proxy(sys.stdout),
|
||||
get_base)
|
||||
|
||||
# *HACK: some modules may emit on stderr, which breaks everything.
|
||||
class NullSTDOut(object):
|
||||
def noop(*args):
|
||||
pass
|
||||
def log_write(self, message):
|
||||
self.message = getattr(self, 'message', '') + message
|
||||
if '\n' in message:
|
||||
_log(self.message.rstrip())
|
||||
self.message = ''
|
||||
write = noop
|
||||
read = noop
|
||||
flush = noop
|
||||
|
||||
sys.stderr = NullSTDOut()
|
||||
sys.stdout = NullSTDOut()
|
||||
if _g_debug_mode:
|
||||
sys.stdout.write = sys.stdout.log_write
|
||||
sys.stderr.write = sys.stderr.log_write
|
||||
|
||||
# Loop until EOF
|
||||
server.loop()
|
||||
if _g_logfile:
|
||||
_g_logfile.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,387 +0,0 @@
|
||||
import warnings
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
from eventlet import saranwrap
|
||||
warnings.simplefilter('default', DeprecationWarning)
|
||||
from eventlet import greenpool, sleep
|
||||
|
||||
import os
|
||||
import eventlet
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from tests import LimitedTestCase, main, skip_on_windows, skip_with_pyevent
|
||||
import re
|
||||
import StringIO
|
||||
|
||||
# random test stuff
|
||||
def list_maker():
|
||||
return [0,1,2]
|
||||
|
||||
one = 1
|
||||
two = 2
|
||||
three = 3
|
||||
|
||||
class CoroutineCallingClass(object):
|
||||
def __init__(self):
|
||||
self._my_dict = {}
|
||||
|
||||
def run_coroutine(self):
|
||||
eventlet.spawn_n(self._add_random_key)
|
||||
|
||||
def _add_random_key(self):
|
||||
self._my_dict['random'] = 'yes, random'
|
||||
|
||||
def get_dict(self):
|
||||
return self._my_dict
|
||||
|
||||
|
||||
class TestSaranwrap(LimitedTestCase):
|
||||
TEST_TIMEOUT=8
|
||||
def assert_server_exists(self, prox):
|
||||
self.assert_(saranwrap.status(prox))
|
||||
prox.foo = 0
|
||||
self.assertEqual(0, prox.foo)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_tuple(self):
|
||||
my_tuple = (1, 2)
|
||||
prox = saranwrap.wrap(my_tuple)
|
||||
self.assertEqual(prox[0], 1)
|
||||
self.assertEqual(prox[1], 2)
|
||||
self.assertEqual(len(my_tuple), 2)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_string(self):
|
||||
my_object = "whatever"
|
||||
prox = saranwrap.wrap(my_object)
|
||||
self.assertEqual(str(my_object), str(prox))
|
||||
self.assertEqual(len(my_object), len(prox))
|
||||
self.assertEqual(my_object.join(['a', 'b']), prox.join(['a', 'b']))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_uniterable(self):
|
||||
# here we're treating the exception as just a normal class
|
||||
prox = saranwrap.wrap(FloatingPointError())
|
||||
def index():
|
||||
prox[0]
|
||||
def key():
|
||||
prox['a']
|
||||
|
||||
self.assertRaises(IndexError, index)
|
||||
self.assertRaises(TypeError, key)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_dict(self):
|
||||
my_object = {'a':1}
|
||||
prox = saranwrap.wrap(my_object)
|
||||
self.assertEqual('a', prox.keys()[0])
|
||||
self.assertEqual(1, prox['a'])
|
||||
self.assertEqual(str(my_object), str(prox))
|
||||
self.assertEqual('saran:' + repr(my_object), repr(prox))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_module_class(self):
|
||||
prox = saranwrap.wrap(re)
|
||||
self.assertEqual(saranwrap.Proxy, type(prox))
|
||||
exp = prox.compile('.')
|
||||
self.assertEqual(exp.flags, 0)
|
||||
self.assert_(repr(prox.compile))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_eq(self):
|
||||
prox = saranwrap.wrap(re)
|
||||
exp1 = prox.compile('.')
|
||||
exp2 = prox.compile(exp1.pattern)
|
||||
self.assertEqual(exp1, exp2)
|
||||
exp3 = prox.compile('/')
|
||||
self.assert_(exp1 != exp3)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_nonzero(self):
|
||||
prox = saranwrap.wrap(re)
|
||||
exp1 = prox.compile('.')
|
||||
self.assert_(bool(exp1))
|
||||
prox2 = saranwrap.Proxy([1, 2, 3])
|
||||
self.assert_(bool(prox2))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_multiple_wraps(self):
|
||||
prox1 = saranwrap.wrap(re)
|
||||
prox2 = saranwrap.wrap(re)
|
||||
x1 = prox1.compile('.')
|
||||
x2 = prox1.compile('.')
|
||||
del x2
|
||||
x3 = prox2.compile('.')
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_dict_passthru(self):
|
||||
prox = saranwrap.wrap(StringIO)
|
||||
x = prox.StringIO('a')
|
||||
self.assertEqual(type(x.__dict__), saranwrap.ObjectProxy)
|
||||
# try it all on one line just for the sake of it
|
||||
self.assertEqual(type(saranwrap.wrap(StringIO).StringIO('a').__dict__), saranwrap.ObjectProxy)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_is_value(self):
|
||||
server = saranwrap.Server(None, None, None)
|
||||
self.assert_(server.is_value(None))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_getitem(self):
|
||||
prox = saranwrap.wrap([0,1,2])
|
||||
self.assertEqual(prox[0], 0)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_wrap_setitem(self):
|
||||
prox = saranwrap.wrap([0,1,2])
|
||||
prox[1] = 2
|
||||
self.assertEqual(prox[1], 2)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_raising_exceptions(self):
|
||||
prox = saranwrap.wrap(re)
|
||||
def nofunc():
|
||||
prox.never_name_a_function_like_this()
|
||||
self.assertRaises(AttributeError, nofunc)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_unpicklable_server_exception(self):
|
||||
prox = saranwrap.wrap(saranwrap)
|
||||
def unpickle():
|
||||
prox.raise_an_unpicklable_error()
|
||||
|
||||
self.assertRaises(saranwrap.UnrecoverableError, unpickle)
|
||||
|
||||
# It's basically dead
|
||||
#self.assert_server_exists(prox)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_pickleable_server_exception(self):
|
||||
prox = saranwrap.wrap(saranwrap)
|
||||
def fperror():
|
||||
prox.raise_standard_error()
|
||||
|
||||
self.assertRaises(FloatingPointError, fperror)
|
||||
self.assert_server_exists(prox)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_print_does_not_break_wrapper(self):
|
||||
prox = saranwrap.wrap(saranwrap)
|
||||
prox.print_string('hello')
|
||||
self.assert_server_exists(prox)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_stderr_does_not_break_wrapper(self):
|
||||
prox = saranwrap.wrap(saranwrap)
|
||||
prox.err_string('goodbye')
|
||||
self.assert_server_exists(prox)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_status(self):
|
||||
prox = saranwrap.wrap(time)
|
||||
a = prox.gmtime(0)
|
||||
status = saranwrap.status(prox)
|
||||
self.assertEqual(status['object_count'], 1)
|
||||
self.assertEqual(status['next_id'], 2)
|
||||
self.assert_(status['pid']) # can't guess what it will be
|
||||
# status of an object should be the same as the module
|
||||
self.assertEqual(saranwrap.status(a), status)
|
||||
# create a new one then immediately delete it
|
||||
prox.gmtime(1)
|
||||
is_id = prox.ctime(1) # sync up deletes
|
||||
status = saranwrap.status(prox)
|
||||
self.assertEqual(status['object_count'], 1)
|
||||
self.assertEqual(status['next_id'], 3)
|
||||
prox2 = saranwrap.wrap(re)
|
||||
self.assert_(status['pid'] != saranwrap.status(prox2)['pid'])
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_del(self):
|
||||
prox = saranwrap.wrap(time)
|
||||
delme = prox.gmtime(0)
|
||||
status_before = saranwrap.status(prox)
|
||||
#print status_before['objects']
|
||||
del delme
|
||||
# need to do an access that doesn't create an object
|
||||
# in order to sync up the deleted objects
|
||||
prox.ctime(1)
|
||||
status_after = saranwrap.status(prox)
|
||||
#print status_after['objects']
|
||||
self.assertLessThan(status_after['object_count'], status_before['object_count'])
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_contains(self):
|
||||
prox = saranwrap.wrap({'a':'b'})
|
||||
self.assert_('a' in prox)
|
||||
self.assert_('x' not in prox)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_variable_and_keyword_arguments_with_function_calls(self):
|
||||
import optparse
|
||||
prox = saranwrap.wrap(optparse)
|
||||
parser = prox.OptionParser()
|
||||
z = parser.add_option('-n', action='store', type='string', dest='n')
|
||||
opts,args = parser.parse_args(["-nfoo"])
|
||||
self.assertEqual(opts.n, 'foo')
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_original_proxy_going_out_of_scope(self):
|
||||
def make_re():
|
||||
prox = saranwrap.wrap(re)
|
||||
# after this function returns, prox should fall out of scope
|
||||
return prox.compile('.')
|
||||
tid = make_re()
|
||||
self.assertEqual(tid.flags, 0)
|
||||
def make_list():
|
||||
from tests import saranwrap_test
|
||||
prox = saranwrap.wrap(saranwrap_test.list_maker)
|
||||
# after this function returns, prox should fall out of scope
|
||||
return prox()
|
||||
proxl = make_list()
|
||||
self.assertEqual(proxl[2], 2)
|
||||
|
||||
def test_status_of_none(self):
|
||||
try:
|
||||
saranwrap.status(None)
|
||||
self.assert_(False)
|
||||
except AttributeError, e:
|
||||
pass
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_not_inheriting_pythonpath(self):
|
||||
# construct a fake module in the temp directory
|
||||
temp_dir = tempfile.mkdtemp("saranwrap_test")
|
||||
fp = open(os.path.join(temp_dir, "tempmod.py"), "w")
|
||||
fp.write("""import os, sys
|
||||
pypath = os.environ['PYTHONPATH']
|
||||
sys_path = sys.path""")
|
||||
fp.close()
|
||||
|
||||
# this should fail because we haven't stuck the temp_dir in our path yet
|
||||
prox = saranwrap.wrap_module('tempmod')
|
||||
try:
|
||||
prox.pypath
|
||||
self.fail()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# now try to saranwrap it
|
||||
sys.path.append(temp_dir)
|
||||
try:
|
||||
import tempmod
|
||||
prox = saranwrap.wrap(tempmod)
|
||||
self.assert_(prox.pypath.count(temp_dir))
|
||||
self.assert_(prox.sys_path.count(temp_dir))
|
||||
finally:
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
sys.path.remove(temp_dir)
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_contention(self):
|
||||
from tests import saranwrap_test
|
||||
prox = saranwrap.wrap(saranwrap_test)
|
||||
|
||||
pool = greenpool.GreenPool(4)
|
||||
pool.spawn_n(lambda: self.assertEquals(prox.one, 1))
|
||||
pool.spawn_n(lambda: self.assertEquals(prox.two, 2))
|
||||
pool.spawn_n(lambda: self.assertEquals(prox.three, 3))
|
||||
pool.waitall()
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_copy(self):
|
||||
import copy
|
||||
compound_object = {'a':[1,2,3]}
|
||||
prox = saranwrap.wrap(compound_object)
|
||||
def make_assertions(copied):
|
||||
self.assert_(isinstance(copied, dict))
|
||||
self.assert_(isinstance(copied['a'], list))
|
||||
self.assertEquals(copied, compound_object)
|
||||
self.assertNotEqual(id(compound_object), id(copied))
|
||||
|
||||
make_assertions(copy.copy(prox))
|
||||
make_assertions(copy.deepcopy(prox))
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_list_of_functions(self):
|
||||
return # this test is known to fail, we can implement it sometime in the future if we wish
|
||||
from tests import saranwrap_test
|
||||
prox = saranwrap.wrap([saranwrap_test.list_maker])
|
||||
self.assertEquals(list_maker(), prox[0]())
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_under_the_hood_coroutines(self):
|
||||
# so, we want to write a class which uses a coroutine to call
|
||||
# a function. Then we want to saranwrap that class, have
|
||||
# the object call the coroutine and verify that it ran
|
||||
|
||||
from tests import saranwrap_test
|
||||
mod_proxy = saranwrap.wrap(saranwrap_test)
|
||||
obj_proxy = mod_proxy.CoroutineCallingClass()
|
||||
obj_proxy.run_coroutine()
|
||||
|
||||
# sleep for a bit to make sure out coroutine ran by the time
|
||||
# we check the assert below
|
||||
sleep(0.1)
|
||||
|
||||
self.assert_(
|
||||
'random' in obj_proxy.get_dict(),
|
||||
'Coroutine in saranwrapped object did not run')
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_child_process_death(self):
|
||||
prox = saranwrap.wrap({})
|
||||
pid = saranwrap.getpid(prox)
|
||||
self.assertEqual(os.kill(pid, 0), None) # assert that the process is running
|
||||
del prox # removing all references to the proxy should kill the child process
|
||||
sleep(0.1) # need to let the signal handler run
|
||||
self.assertRaises(OSError, os.kill, pid, 0) # raises OSError if pid doesn't exist
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_detection_of_server_crash(self):
|
||||
# make the server crash here
|
||||
pass
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_equality_with_local_object(self):
|
||||
# we'll implement this if there's a use case for it
|
||||
pass
|
||||
|
||||
@skip_on_windows
|
||||
@skip_with_pyevent
|
||||
def test_non_blocking(self):
|
||||
# here we test whether it's nonblocking
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user