Skip to content

Instantly share code, notes, and snippets.

@sjlongland
Last active August 29, 2015 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjlongland/d0867dec944fd858ea09 to your computer and use it in GitHub Desktop.
Save sjlongland/d0867dec944fd858ea09 to your computer and use it in GitHub Desktop.
Arithmetic and method calls to Future objects
import tornado
import tornado.gen
import tornado.concurrent
'''
A proof-of-concept library that allows a Future to be treated like the object
it will eventually yield to. Attributes and method calls are translated, with
Future and FutureObject references yielded before calling.
Most standard Python routines are supported.
(C) 2015 Stuart Longland
Released under the terms of the Apache Public License 2.0
'''
@tornado.gen.coroutine
def yield_struct(struct):
'''
Yield the contents of a data structure, reconstructing it with the yielded
values.
This seeks Future and FutureObject instances in the given data structure,
which is scanned recursively. Any found are yielded and replaced with the
output values.
'''
if isinstance(struct, list) or \
isinstance(struct, tuple) or \
isinstance(struct, set):
# Make a note of what data type we're dealing with. We'll temporarily
# turn this into a list while we yield the contents.
ret_type = type(struct)
ret_values = []
for v in struct:
ret_values.append((yield yield_struct(v)))
# Convert it back into whatever it started out as.
result = ret_type(ret_values)
elif isinstance(struct, dict):
result = {}
for k, v in struct.iteritems():
result[(yield yield_struct(k))] = yield yield_struct(v)
elif isinstance(struct, slice):
result = slice(
(yield yield_struct(struct.start)),
(yield yield_struct(struct.stop)),
(yield yield_struct(struct.step)))
elif isinstance(struct, tornado.concurrent.Future):
result = yield struct
elif isinstance(struct, FutureObject):
result = yield struct.f
else:
result = struct
raise tornado.gen.Return(result)
def wrap_callable(fn):
'''
Wrap a function so that it can handle Future and FutureObject instances in
its arguments.
'''
def fn_wrapped(*args, **kwargs):
@tornado.gen.coroutine
def _call_fn(fn, args, kwargs):
args = yield yield_struct(args)
kwargs = yield yield_struct(kwargs)
result = fn(*args, **kwargs)
if hasattr(result, '__call__'):
# Wrap the resulting function
result = wrap_callable(result)
raise tornado.gen.Return(result)
return FutureObject(_call_fn(fn, args, kwargs))
return fn_wrapped
class FutureObject(object):
'''
A placeholder object to represent an object to be yielded from a Future.
This object wraps a Tornado "Future" object, exposing methods of the
to-be-obtained object for use in expressions.
This object has one attribute and some methods. The 'f' method is the
future being wrapped, yielding this will give you the raw object.
In the unlikely event that 'f' is a legitimate attribute in the target
object, a single-letter method, 'a', exists: this works like __getattr__.
If the object returned from __getattr__ is a callable, it is wrapped in a
function that returns a FutureObject.
'''
def __init__(self, future):
self.f = future
def a(self, attr):
'''
Return the attribute named by 'attr'. This is a helper function just
in case the object returned by 'future' happens to have a 'f' or 'a'
attribute you were counting on having access to.
'''
log = logging.getLogger('%s.FutureObject.a' % self.__module__)
log.debug('Asked for %s', attr)
@tornado.gen.coroutine
def get_a():
o = yield(self.__dict__['f'])
a = getattr(o, attr)
log.debug('Got a = %s %s', a, type(a))
if hasattr(a, '__call__'):
# This is a callable function, we need to wrap it.
raise tornado.gen.Return(wrap_callable(a))
else:
# We should be safe enough returning the raw object
raise tornado.gen.Return(a)
return get_a()
def __repr__(self):
return 'FutureObject(%s)' % self.f
def __str__(self):
return repr(self)
# Standard Python functions start here
def __getattr__(self, attr):
'''
Retrieve a named attribute.
'''
if attr in self.__dict__:
return self.__dict__[a]
return self.a(attr)
def __lt__(self, other):
return wrap_callable(lambda a, b: a < b)(self.f,other)
def __le__(self, other):
return wrap_callable(lambda a, b: a <= b)(self.f,other)
def __eq__(self, other):
return wrap_callable(lambda a, b: a == b)(self.f,other)
def __ne__(self, other):
return wrap_callable(lambda a, b: a != b)(self.f,other)
def __gt__(self, other):
return wrap_callable(lambda a, b: a > b)(self.f,other)
def __ge__(self, other):
return wrap_callable(lambda a, b: a >= b)(self.f,other)
def __cmp__(self, other):
return wrap_callable(lambda a, b: cmp(a,b))(self.f,other)
def __getitem__(self, other):
return wrap_callable(lambda a, b: a[b])(self.f,other)
def __contains__(self, other):
return wrap_callable(lambda a, b: b in a)(self.f,other)
def __add__(self, other):
return wrap_callable(lambda a, b: a + b)(self.f,other)
def __sub__(self, other):
return wrap_callable(lambda a, b: a - b)(self.f,other)
def __mul__(self, other):
return wrap_callable(lambda a, b: a * b)(self.f,other)
def __floordiv__(self, other):
return wrap_callable(lambda a, b: a // b)(self.f,other)
def __mod__(self, other):
return wrap_callable(lambda a, b: a % b)(self.f,other)
def __divmod__(self, other):
return wrap_callable(lambda a, b: divmod(a,b))(self.f,other)
def __pow__(self, other, modulo=None):
return wrap_callable(lambda a, b, m: pow(a,b,m))(self.f,other,modulo)
def __lshift__(self, other):
return wrap_callable(lambda a, b: a << b)(self.f,other)
def __rshift__(self, other):
return wrap_callable(lambda a, b: a >> b)(self.f,other)
def __and__(self, other):
return wrap_callable(lambda a, b: a & b)(self.f,other)
def __xor__(self, other):
return wrap_callable(lambda a, b: a ^ b)(self.f,other)
def __or__(self, other):
return wrap_callable(lambda a, b: a | b)(self.f,other)
def __div__(self, other):
return wrap_callable(lambda a, b: a / b)(self.f,other)
def __truediv__(self, other):
return wrap_callable(lambda a, b: a / b)(self.f,other)
class WrappedModule(object):
'''
Wrap a library module so that method calls are processed as coroutines.
'''
def __init__(self, module):
self._m = module
def a(self, attr):
'''
Return the attribute named by 'attr'. This is a helper function just
in case the object returned by 'future' happens to have a '_m' or 'a'
attribute you were counting on having access to.
'''
log = logging.getLogger('%s.WrappedModule.a' % self.__module__)
log.debug('Asked for %s', attr)
a = getattr(self.__dict__['_m'], attr)
log.debug('Got a = %s %s', a, type(a))
if hasattr(a, '__call__'):
# This is a callable function, we need to wrap it.
return wrap_callable(a)
else:
# We should be safe enough returning the raw object
return a
def __repr__(self):
return 'WrappedModule(%s)' % self._m
def __str__(self):
return repr(self)
# Standard Python functions start here
def __getattr__(self, attr):
'''
Retrieve a named attribute.
'''
if attr in self.__dict__:
return self.__dict__[a]
return self.a(attr)
if __name__ == '__main__':
# A test program:
import logging
logging.basicConfig(level=logging.DEBUG)
io_loop = tornado.ioloop.IOLoop.instance()
@tornado.gen.coroutine
def func_a():
'''
This function just returns a number.
'''
raise tornado.gen.Return(123)
@tornado.gen.coroutine
def func_b():
'''
This function just returns a number.
'''
raise tornado.gen.Return(456)
str_wrapped = wrap_callable(str)
@tornado.gen.coroutine
def run():
try:
logging.debug( 'Creating FutureObject objects for the results of '\
'func_a and func_b' )
res_a = FutureObject(func_a())
res_b = FutureObject(func_b())
logging.debug( 'Creating FutureObject that represents the sum of '\
'func_a and func_b results' )
res = res_a + res_b
logging.debug( 'Creating FutureObject to get the first digit' )
res_s = str_wrapped(res)[0]
logging.debug( 'Yielding the resulting FutureObject (%s)', res_s )
value = yield res_s.f
logging.debug( 'Result: %s %s', value, type(value))
except:
logging.exception('FAIL')
io_loop.stop()
io_loop.add_callback(run)
io_loop.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment