Swirl

Some sugar for asynchronous Tornado code
Download

Swirl Ranking & Summary

Advertisement

  • Rating:
  • License:
  • MIT/X Consortium Lic...
  • Price:
  • FREE
  • Publisher Name:
  • Eric Naeseth
  • Publisher web site:
  • http://naeseth.com

Swirl Tags


Swirl Description

Some sugar for asynchronous Tornado code Tornado is a non-blocking server and Web framework from Facebook. One of the nice features of Tornado is its ability to respond to requests asynchronously. The Tornado tutorial includes this example of a request handler that builds its results by calling on the FriendFeed API:class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): http = tornado.httpclient.AsyncHTTPClient() http.fetch("http://friendfeed-api.com/v2/feed/bret", callback=self.async_callback(self.on_response)) def on_response(self, response): if response.error: raise tornado.web.HTTPError(500) json = tornado.escape.json_decode(response.body) self.write("Fetched %d entries from the FriendFeed API" % len(json)) self.finish()The tornado.web.asynchronous decorator on get instructs Tornado to not automatically close the response when get returns. Tornado's asynchronous HTTP client automatically calls on_response when a response has been received from FriendFeed, and that method fills in the response to the browser, and manually finishes it.It works, but this approach has some warts. The need to use an explicit callback function breaks up the flow of your code, your callbacks must manually check for errors, and you have to remember to call the finish method, or the user's browser will hang indefinitely waiting for the request to complete.The Swirl library uses Python's support for coroutines to remove these warts, letting you write the above request handler much like you would write it with a synchronous HTTP request.Coroutines?Normal functions in Python have a single entry point, and stop executing as soon as they return a value. Callers pass in some arguments, the function executes until it reaches a return statement, and the function exits.But it doesn't always have to be this way. Python includes a very handy mechanism for creating sequences: the yield statement. When a function uses yield, the function is able to "return" as many values as it wants. A simple example is a function that iteratively returns the squares of all numbers from one to n.def squares(n): for i in range(1, n): yield i * ifor s in squares(5): print s,# outputs: 1 4 9 16We can use a for loop to iterate over these values, just as if squares had returned a list of them.What's so neat about yield is that it transfers program flow in and out of our squares function. We start in the first line of the for loop, and enter squares until it yields a value, and then return the body of the loop. We go back into squares to see if it has another value, and if it does, flow returns to the loop to print the new value.This transfer of flow is exactly what needs to happen to generate an asynchronous response in Tornado. Tornado calls a request handler, which starts some task whose results it needs to generate the response. Flow returns to Tornado's I/O loop, which executes the task, and then calls (another part of) the request handler to complete the response.We can use Python's yield statement to implement this transfer of flow more elegantly than Tornado does.Swirl's SolutionHere is the Swirl version of the FriendFeed API example:import swirlclass MainHandler(tornado.web.RequestHandler): @swirl.asynchronous def get(self): http = tornado.httpclient.AsyncHTTPClient() uri = 'http://friendfeed-api.com/v2/feed/bret' try: response = yield lambda cb: http.fetch(uri, cb) json = tornado.escape.json_decode(response.body) self.write('Fetched %d entries from the FriendFeed API.' % len(json)) except tornado.httpclient.HTTPError: raise tornado.web.HTTPError(500, 'API request failed')The strangest-looking part of this code is the first line after the try. What's happening there? 1. We use lambda to create an anonymous function which executes the work that we want done in the background on Tornado's I/O loop. (We'll refer to this function as the "worker function".) 2. We yield the worker function. By yielding, we suspend execution of our handler and pass the worker function to Swirl. 3. Swirl creates a function to serve as the callback for our task. It calls our worker function, passing in this callback as the cb parameter. The worker function calls http.fetch, passing it the Swirl callback function and starting the HTTP request. 4. Swirl returns control to the Tornado I/O loop, letting it continue to handle incoming requests and do other tasks (like executing our FriendFeed API request). 5. When the FriendFeed API request finishes, the asynchronous HTTP client calls Swirl's callback function. Swirl interprets the values that were passed to this function, and passes it into our request handler. The yield expression returns these values, which get assigned to the response variable, and the handler proceeds to interpret the response, count the JSON entries, and send its own response.With just a little bit of yield and lambda boilerplate, we have eliminated all of the trickery of writing an asynchronous request handler in Tornado. The response is implicitly finished when get reaches its end and stops yielding tasks, so there's no need to explicitly call self.finish(). We no longer have to split our handler into two functions, and we can handle API errors the normal way: using a try/except block.Callback Argument HandlingSwirl automatically supports two ways for asynchronous tasks to indicate exceptions to callbacks. * The callback is passed an object as its first argument that has an error attribute. (AsyncHTTPClient does this.) * The callback is passed an Exception object as its last argument if there was an error, or None if there was no error.When an exception is detected using one of these methods, Swirl raises it at the point of the yield statement that yielded the task that caused it. This exception (as shown in the above example) can be caught using a standard except block.When no exception is detected, Swirl returns the tuple of the arguments passed to the callback from the yield statement. If the last argument is None, this is treated as an empty error argument, and it is omitted. If the callback was only passed one non-error argument the tuple is forgone and the argument value is returned directly.I/O LoopsThe swirl.asynchronous decorator runs worker functions on Tornado's default I/O loop (the one accessible from ioloop.IOLoop.instance()). If your application is using a different loop, you can create a decorator function that runs workers on your loop by calling swirl.make_asynchronous_decorator:from swirl import make_asynchronous_decoratormy_loop = tornado.ioloop.IOLoop()asynchronous = make_asynchronous_decorator(my_loop)# use asynchronous as a decorator, as in the above FriendFeed example Requirements: · Python


Swirl Related Software