How to generalize a function call which may be async, tornado coroutine, or normal?
<p>I have an application which has a library in multiple configurations:</p>

<ul>
<li>Python2.7 native</li>
<li>Python2.7 tornado</li>
<li>Python3.5 asyncio</li>
</ul>

<p>Currently, I have code that is nearly identical against all three, but there are minor differences in how each function call are invoked. This means I have a ton of code duplication, because I have stuff like the following in many places:</p>

<pre><code>#Python2.7native.py
def main(client):
   client.foo(args)
   client.bar(args)

#Python2.7tornado.py
@gen.coroutine
def main(client):
    yield client.foo(args)
    yield client.bar(args)

#Python3.5asyncio.py
async def main(client):
    await client.foo(args)
    await client.bar(args)
</code></pre>

<p>where <code>client</code> is a language specific implementation, supporting native python, asyncio, and tornado respectively. The API method calls are identical.</p>

<p>I am hoping to be able to somehow generalize this into a single method I can include in a shared file, which appropriately calls the various methods</p>

<p>I've thought about defining the methods in a separate file and using <code>getattr</code> to invoke the test properly, but this seems really messy.</p>

<p>Is there a good way to do this?</p>

<p>You can't do all of this in one function - how is <code>client.foo()</code> supposed to know whether it's being called from a "normal" synchronous application, or whether its caller is going to use <code>yield</code> or <code>await</code>. However, as long as you're willing to have Tornado as a dependency, you can avoid duplicating all your code three times.</p>

<p>In one module, <code>client_async.py</code>, implement your function(s) using Tornado's <code>@gen.coroutine</code>:</p>

<pre><code>@gen.coroutine
def foo(args):
    yield something()
    yield something_else()
    raise gen.Return(another_thing())
</code></pre>

<p>In another, <code>client_sync.py</code>, wrap each of the functions from <code>client_async.py</code> in <code>IOLoop.run_sync()</code> for a thread-local IOLoop like this:</p>

<pre><code>import client_async
import threading
import tornado.ioloop

class _LocalIOLoop(threading.local):
    def __init__(self):
        self.value = tornado.ioloop.IOLoop()
local_ioloop = _LocalIOLoop()

def foo(args):
    return local_ioloop.value.run_sync(lambda: foo_async.my_func(args))
</code></pre>

<p>Now you can use this code from all three environments. From normal synchronous code:</p>

<pre><code>import client_sync

def main():
    x = client_sync.foo(args)
</code></pre>

<p>From Tornado <code>@gen.coroutine</code>:</p>

<pre><code>import client_async

@gen.coroutine
def main():
    x = yield client_async.foo(args)
</code></pre>

<p>From <code>async def</code> and <code>asyncio</code> (note that the two are not synonymous - it is possible to use <code>async def</code> with Tornado without <code>asyncio</code>):</p>

<pre><code># one-time initialization for Tornado/asyncio integration
import tornado.platform.asyncio
tornado.platform.asyncio.AsyncIOMainLoop().install()

import client_async

async def main():
    x = await client_async.foo(args)
</code></pre>

python python-2.7 tornado python-3.5 python-asyncio