Last active
August 29, 2015 14:13
-
-
Save sjlongland/d0867dec944fd858ea09 to your computer and use it in GitHub Desktop.
Arithmetic and method calls to Future objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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