imharjyotbagga
imharjyotbagga

Reputation: 329

Synchronous API Calls using Tornado, Python

After the release of Tornado 5, we are unable to use synchronous HTTPClient while IOLoop is running. We would have to use AsyncHTTPClient instead.

Therefore, can someone help me out by showing me how can I make synchronous API calls using AsyncHTTPClient in Tornado(Python web framework), which uses async and await instead of the old traditional methods of yield.

I tried to implement it this way, and have been getting multiple errors.

class IndexHandler(tornado.web.RequestHandler):
    async def get_result(self, query):
        client = tornado.httpclient.AsyncHTTPClient()
        return await client.fetch("https://pokeapi.co/api/v2/pokemon/" + urllib.parse.urlencode(query))

    def get(self):
        q = self.get_argument('q')
        query = {'q': q}

        loop = asyncio.get_event_loop()
        response = loop.run_until_complete(self.get_result(query))
        loop.close()

        #response = client.fetch("https://pokeapi.co/api/v2/pokemon/" + urllib.parse.urlencode(query))
        #self.on_response(response)
        if response == "Not Found":
            self.write("Invalid Input")
            response_data = json.loads(response.body)
            pikachu_name = response_data['name']
            pikachu_order = response_data['order']
            self.write("""
                Pickachu Name: %s <br>
                Pickachu Order: %d <br>
                """ %(pikachu_name, pikachu_order))

But unfortunately, I've been getting a lot of errors.

[E 200410 02:40:48 web:1792] Uncaught exception GET /?q=pikachu (::1)
    HTTPServerRequest(protocol='http', host='localhost:8000', method='GET', uri='/?q=pikachu', version='HTTP/1.1', remote_ip='::1')
    Traceback (most recent call last):
      File "C:\Python\Python37\lib\site-packages\tornado\web.py", line 1701, in _execute
        result = method(*self.path_args, **self.path_kwargs)
      File "C:\Users\Bagga\Documents\tornado\Tweet Rate\handlers.py", line 25, in get
        response = loop.run_until_complete(self.get_result(query))
      File "C:\Python\Python37\lib\asyncio\base_events.py", line 571, in run_until_complete
        self.run_forever()
      File "C:\Python\Python37\lib\asyncio\base_events.py", line 526, in run_forever
        raise RuntimeError('This event loop is already running')
    RuntimeError: This event loop is already running
[E 200410 02:40:48 web:2250] 500 GET /?q=pikachu (::1) 3.33ms
[E 200410 02:40:49 base_events:1608] Task exception was never retrieved
    future: <Task finished coro=<IndexHandler.get_result() done, defined at C:\Users\Bagga\Documents\tornado\Tweet Rate\handlers.py:16> exception=HTTP 404: Not Found>
    Traceback (most recent call last):
      File "C:\Users\Bagga\Documents\tornado\Tweet Rate\handlers.py", line 18, in get_result
        return await client.fetch("https://pokeapi.co/api/v2/pokemon/" + urllib.parse.urlencode(query))
    tornado.httpclient.HTTPClientError: HTTP 404: Not Found

Upvotes: 0

Views: 656

Answers (1)

Ionut Ticus
Ionut Ticus

Reputation: 2789

I think there's some confusion regarding what a synchronous request is; a synchronous request blocks program execution (and therefore the whole IOLoop) before it can return a result; this is generally not what you want.
An asynchronous request returns before it is finished; it returns placeholder objects (Futures) which are usually transformed into their result with the await or yield keywords.

What I think you are looking for is a way to wait for the request to the API to complete, which as mentioned above is done with await or yield.

class IndexHandler(tornado.web.RequestHandler):
    async def get_result(self, query):
        client = tornado.httpclient.AsyncHTTPClient()
        response = await client.fetch("https://pokeapi.co/api/v2/pokemon/" + urllib.parse.urlencode(query))
        return response.body

    async def get(self):
        q = self.get_argument('q')
        query = {'q': q}
        response = await self.get_result(query)
        if response == b"Not Found":
            self.write("Invalid Input")
        else:
            response_data = json.loads(response.body)
            pikachu_name = response_data['name']
            ...

Notice the

async def get(self):

and the

response = await self.get_result(query)

bits.

Upvotes: 0

Related Questions