Python Tornado gen.engine exception handling - python

I am using Tornado 2.4, and I am trying to integrate async call.
Lets say I need to access to a remote resource through a HTTP call, so I made this function in a tornado.web.RequestHandler:
#tornado.web.asynchronous
def get(self, *args):
try:
self.remote_call()
return 'OK'
except Exception, e:
self.handle_exception(e)
#gen.engine
def remote_call(self):
http_client = httpclient.AsyncHTTPClient()
response = yield gen.Task(http_client.fetch, 'http://google.com')
self.process(response)
So my problem is, since remote_call is yielding a Task, it will obviously exit the remote_call function and continue the get function. Then when my task is complete, the engine will process the response.
But if an error happen in the self.process(response), it will not be catch by my except, since this part of the code is not actually called here, but inside the engine where I do have no control.
So my question is, can I have some control on this engine? Can I handle error, can I ask to perform some specific task at the end the function?
I could do this directly in the function like this
#tornado.web.asynchronous
def get(self, *args):
self.remote_call()
return 'OK'
#gen.engine
def remote_call(self):
http_client = httpclient.AsyncHTTPClient()
response = yield gen.Task(http_client.fetch, 'http://google.com')
try:
self.process(response)
except:
self.handle_exception(e)
But I want to make the handle exception generic and not copy pasting this on every of my Handler.
So do I have a way to access to the engine of Tornado?
Note that I am using Tornado 2.4 but I can migrate to 3.0 if needed.
Thanks

You can handle it in 2.4 by decorating your get call with #gen.engine, wrapping the call to self.remote_call in a gen.Task, and then yielding from that:
#tornado.web.asynchronous
#gen.engine
def get(self, *args):
try:
yield gen.Task(self.remote_call)
except Exception, e:
self.handle_exception(e)
self.finish() # Make sure you call this when `get` is asynchronous.
#gen.engine
def remote_call(self):
http_client = httpclient.AsyncHTTPClient()
response = yield gen.Task(http_client.fetch, 'http://google.com')
self.process(response)
This will allow you to handle the exception in get, though you'll still see a traceback from the exception being raise in remote_call.
However, I highly recommend you upgrade. Tornado is now on version 4.0. With 3.0 or later, you can use gen.coroutine instead of gen.engine and web.asynchronous:
#gen.coroutine
def get(self, *args):
try:
yield self.remote_call()
except Exception, e:
self.handle_exception(e)
self.finish()
#gen.coroutine
def remote_call(self):
http_client = httpclient.AsyncHTTPClient()
response = yield http_client.fetch('http://google.com')
self.process(response)
coroutine properly supresses the traceback from any exception thrown in remote_call, as well as letting you handle it in get.

Ok thanks it works. I had to do this however:
#tornado.web.asynchronous
#gen.engine
def get(self, *args):
try:
yield gen.Task(lambda cb: self.remote_call())
except Exception, e:
self.handle_exception(e)
self.finish() # Make sure you call this when `get` is asynchronous.
#gen.engine
def remote_call(self):
http_client = httpclient.AsyncHTTPClient()
response = yield gen.Task(http_client.fetch, 'http://google.com')
self.process(response)

Related

Write back through the callback attached to IOLoop in Tornado

There is a tricky post handler, sometimes it can take a lots of time (depending on a input values), sometimes not.
What I want is to write back whenever 1 second passes, dynamically allocating the response.
def post():
def callback():
self.write('too-late')
self.finish()
timeout_obj = IOLoop.current().add_timeout(
dt.timedelta(seconds=1),
callback,
)
# some asynchronous operations
if not self.request.connection.stream.closed():
self.write('here is your response')
self.finish()
IOLoop.current().remove_timeout(timeout_obj)
Turns out I can't do much from within callback.
Even raising an exception is suppressed by the inner context and won't be passed through the post method.
Any other ways to achieve the goal?
Thank you.
UPD 2020-05-15:
I found similar question
Thanks #ionut-ticus, using with_timeout() is much more convenient.
After some tries, I think I came really close to what i'm looking for:
def wait(fn):
#gen.coroutine
#wraps(fn)
def wrap(*args):
try:
result = yield gen.with_timeout(
dt.timedelta(seconds=20),
IOLoop.current().run_in_executor(None, fn, *args),
)
raise gen.Return(result)
except gen.TimeoutError:
logging.error('### TOO LONG')
raise gen.Return('Next time, bro')
return wrap
#wait
def blocking_func(item):
time.sleep(30)
# this is not a Subprocess.
# It is a file IO and DB
return 'we are done here'
Still not sure, should wait() decorator being wrapped in a
coroutine?
Some times in a chain of calls of a blocking_func(), there can
be another ThreadPoolExecutor. I have a concern, would this work
without making "mine" one global, and passing to the
Tornado's run_in_executor()?
Tornado: v5.1.1
An example of usage of tornado.gen.with_timeout. Keep in mind the task needs to be async or else the IOLoop will be blocked and won't be able to process the timeout:
#gen.coroutine
def async_task():
# some async code
#gen.coroutine
def get(self):
delta = datetime.timedelta(seconds=1)
try:
task = self.async_task()
result = yield gen.with_timeout(delta, task)
self.write("success")
except gen.TimeoutError:
self.write("timeout")
I'd advise to use https://github.com/aio-libs/async-timeout:
import asyncio
import async_timeout
def post():
try:
async with async_timeout.timeout(1):
# some asynchronous operations
if not self.request.connection.stream.closed():
self.write('here is your response')
self.finish()
IOLoop.current().remove_timeout(timeout_obj)
except asyncio.TimeoutError:
self.write('too-late')
self.finish()

Python AsyncHttpClient inside tornado RequestHandler throws exception

I'm going to call an endpoint by tornado AsyncHttpClient in RequestHandler, but it throws runtime exception This event loop is already running
class RegistrationHandler(tornado.web.RequestHandler):
def post(self, *args, **kwargs):
call_async_register("some params")
def call_async_register(parameters):
def call():
http_client = AsyncHTTPClient()
future = Future()
http_request = HTTPRequest(url, request_type.name, headers={'X-Peering': '1'}, body=body)
def handle_future(f: Future):
future.set_result(f.result())
fetched_future = http_client.fetch(http_request)
fetched_future.add_done_callback(handle_future)
return future
try:
instance = io_loop.IOLoop.current()
response = instance.run_sync(call)
return response.body.decode()
except Exception as err:
self.logger.exception("Account Request Failed: {}".format(err))
return None
Here's the problem:
instance = io_loop.IOLoop.current()
response = instance.run_sync(call)
run_sync itself tries to start the ioloop. But as apparent from your code, instance is already running. So you get the error.
If you want to send the value returned by call() method back to the user, convert your methods to coroutines (use async/await syntax).
Example:
class RegistrationHandler(tornado.web.RequestHandler):
async def post(self, *args, **kwargs):
response = await call_async_register("some params")
self.write(response)
async def call_async_register(parameters):
http_client = AsyncHTTPClient()
http_request = HTTPRequest(url, request_type.name, headers={'X-Peering': '1'}, body=body)
try:
response = await http_client.fetch(http_request)
return response.body.decode()
except Exception as err:
self.logger.exception("Account Request Failed: {}".format(err))
return None

Python Tornado: Delete request never ends

I am having problems with a delete request in Tornado. The request arrives to the server and everything in the handler work great, but it never returns the response to the client.
I have tried returning something, with only the "return" and even without the "return" and the result is always the same.
I am using Python 3.4, Tornado 4.1 and the RestClient of Firefox.
#web.asynchronous
#gen.coroutine
def delete(self, _id):
try:
model = Model()
model.delete(_id)
self.set_status(204)
except Exception as e:
logging.error(e)
self.set_status(500)
return
Tornado documentation (tornado.web.asynchronous):
If this decorator is given, the response is not finished when the method > returns. It is up to the request handler to call self.finish() to finish > the HTTP request.
You need to call tornado.web.RequestHandler.finish method. This will work:
#web.asynchronous
#gen.coroutine
def delete(self, _id):
try:
model = Model()
model.delete(_id)
self.set_status(204)
except Exception as e:
logging.error(e)
self.set_status(500)
self.finish()
return
However, you don't need asynchronous approach in this example. This will work also in the same way:
def delete(self, _id):
try:
model = Model()
model.delete(_id)
self.set_status(204)
except Exception as e:
logging.error(e)
self.set_status(500)
return
Also, if you are using #gen.coroutine decorator, you don't need to use #web.asynchronous decorator. Simply use only #gen.coroutine, it is the correct way and much more elegant.
Lastly, I think you should read this article for understanding asynchronous programming in Tornado.

Bottle middleware to catch exceptions of a certain type?

Given this simple Bottle code:
def bar(i):
if i%2 == 0:
return i
raise MyError
#route('/foo')
def foo():
try:
return bar()
except MyError as e:
response.status_code = e.pop('status_code')
return e
How would one write Bottle middleware so the same exception handling is done implicitly, so that code like this can work identically to above:
#route('/foo')
def foo():
return bar()
You can do this elegantly with a plugin leveraging abort:
from bottle import abort
def error_translation(func):
def wrapper(*args,**kwargs):
try:
func(*args,**kwargs)
except ValueError as e:
abort(400, e.message)
return wrapper
app.install(error_translation)
Bottle respect the wsgi spec. You can use a classic wsgi middleware
from bottle import route, default_app, run, request
# push an application in the AppStack
default_app.push()
#route('/foo')
def foo():
raise KeyError()
# error view
#route('/error')
def error():
return 'Sorry an error occured %(myapp.error)r' % request.environ
# get the bottle application. can be a Bottle() instance too
app = default_app.pop()
app.catchall = False
def error_catcher(environ, start_response):
# maybe better to fake the start_response callable but this work
try:
return app.wsgi(environ, start_response)
except Exception as e:
# redirect to the error view if an exception is raised
environ['PATH_INFO'] = '/error'
environ['myapp.error'] = e
return app.wsgi(environ, start_response)
# serve the middleware instead of the applicatio
run(app=error_catcher)
You can use this instead:
from bottle import error, run, route
#error(500)
def error_handler_500(error):
return json.dumps({"status": "error", "message": str(error.exception)})
#route("/")
def index():
a = {}
a['aaa']
run()

Refactoring a Tornado Request Handler

So I have repeating code that I do for many GETs -- checking whether the response was cached previously and returning that if it is available.
The code I'd like to get working looks like this:
class Handler(web.RequestHandler):
#gen.coroutine
def get_cache(self):
try:
response = yield gen.Task(get_redis)
except:
logging.log()
if response:
self.finish(response)
raise gen.Return()
#gen.coroutine
#asynchronous
def get(self):
self.get_cache()
response = do_sql_get()
self.set_cache(key, response)
self.finish(response)
What's happening now is that it gets the cache if there but continues running the rest of the code in self.get. That it does this makes sense to me, but I'm not sure how to refactor it properly with it stopping as soon as self.finish is called in the self.get_cache method.
get_cache should return a value that indicates whether it finished the request or not (or it should return the cached data and leave it to the caller to finish the request). I would do one of the following:
#gen.coroutine
def serve_from_cache(self):
response = yield gen.Task(get_redis)
if response:
self.finish(response)
raise gen.Return(True)
else:
raise gen.Return(False)
#gen.coroutine
def get(self):
if (yield self.serve_from_cache()):
return
# do work
yield self.set_cache(...)
or
#gen.coroutine
def get_cache(self):
return yield gen.Task(get_redis)
#gen.coroutine
def get(self):
resp = yield self.get_cache()
if resp:
self.finish(resp)
return
# do work...

Categories

Resources