artyomboyko
artyomboyko

Reputation: 2871

Raise exception or return gen.Return object in Tornado coroutine

We have a Python 2 project where we actively use coroutines. We can't find any guidelines on exceptions handling inside coroutines.

For example, here lead developer of Tornado mentioned that coroutines should never raise an exception, but it is not clear why. Looks like this approach works and heavily used in Tornado.web itself:

https://github.com/tornadoweb/tornado/blob/master/demos/blog/blog.py#L180

class AuthCreateHandler(BaseHandler):
    def get(self):
        self.render("create_author.html")

    @gen.coroutine
    def post(self):
        if self.any_author_exists():
            raise tornado.web.HTTPError(400, "author already created")
        hashed_password = yield executor.submit(
            bcrypt.hashpw, tornado.escape.utf8(self.get_argument("password")),
            bcrypt.gensalt())

tornado.web.HTTPError just extends base Exception class. Also, discussion here https://github.com/tornadoweb/tornado/issues/759#issuecomment-91817197 suggests that raising exception inside coroutine is appropriate.

Also here, active Tornado contributor suggests that raising exceptions is fine:

class PostHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, slug):
        post = yield db.posts.find_one({'slug': slug})
        if not post:
            raise tornado.web.HTTPError(404)

        self.render('post.html', post=post)

Is there any downsides to raising exceptions inside Tornado coroutines or should we raise gen.Return(exception_object) ?

Upvotes: 1

Views: 2841

Answers (2)

Ben Darnell
Ben Darnell

Reputation: 22154

Raising exceptions inside coroutines is perfectly normal. When I said "coroutines should never raise an exception" I was referring to what happens when you call a coroutine without yield or await: The exception is captured and held until the coroutine's return value is either yielded or awaited.

Upvotes: 4

A. Jesse Jiryu Davis
A. Jesse Jiryu Davis

Reputation: 24007

In Python 2, only use raise gen.Return(value) to return a normal value, not to raise an exception. It is exactly the equivalent of return value in a coroutine in Python 3.

To raise an exception from a coroutine, a normal raise Exception() is correct. The wonderful thing about coroutines is their exception-handling semantics are pretty much the same as regular functions.

Upvotes: 8

Related Questions