Search code examples
pythonpython-3.xtornado

Python Future Composition


Suppose a library has two functions, the first function takes a url and returns a Future of a Connection:

def create_connection(url)
"""
:return tornado.gen.Future containing connection
"""

And a second function which takes a Connection, not a Future of Connection, and returns a Future of a Channel:

def create_channel(connection):
"""
:return tornado.gen.Future containing channel
""" 

How do I bind those two functions together to create a Future of a Channel given a url (without using await)?

Something of the form:

url = "doesn't matter"

channel_future = create_connection(url).bind(lambda c: create_channel(c))

Thank you in advance for your consideration and response.


Solution

  • You can create a coroutine (decorated with gen.coroutine) and yield the futures returned by create_connection and create_channel.

    @gen.coroutine
    def bind_things_together():
        connection = yield create_connection(url)
        channel = yield create_channel(connection)
    
        # do something else ...
    

    In the code example above, the connection and channel variables are not futures, but actual connection and channel objects, because yielding a Future returns it's result.

    When you set the result on the connection future, Tornado will call bind_things_together.next() to move the coroutine forward. Then in the next line, you pass connection to create_channel. When you set the result on channel future, Tornado will again call .next() to move the coroutine forward. At which point you can do other things.

    EDIT: On reading your question again, it seems that you want to access the future for channel. In that case, you don't have to yield create_channel():

    @gen.coroutine
    def bind_things...():
        ...
        channel_future = create_channel(connection)
        # if later you want the channel object, just yield channel_future
    

    NOTE: If you call bind_things_together() from another function, you will need to decorate that function with gen.coroutine as well. Also, any function decorated with gen.coroutine will automatically return a Future. So, you have to use yield keyword in the caller to get the result of bind_things_together().