21
loading...
This website collects cookies to deliver better user experience
This is a cross-post from my blog. If you enjoy my content you can subscribe via Email or RSS.
async
and await
within a view function. This allows you to use other async APIs when building a web application with Flask.# app.py
from flask import Flask, jsonify
from elasticsearch import AsyncElasticsearch
app = Flask(__name__)
# Create the AsyncElasticsearch instance in the global scope.
es = AsyncElasticsearch(
"https://localhost:9200",
api_key="..."
)
@app.route("/", methods=["GET"])
async def async_view():
return jsonify(**(await es.info()))
gunicorn
via $gunicorn app:app
and then visiting the app via http://localhost:8000
. After the first request everything looks fine:{
"cluster_name": "d31d9d6abb334a398210484d7ac8567b",
"cluster_uuid": "K5uyniiMT9u2grNBmsSt_Q",
"name": "instance-0000000001",
"tagline": "You Know, for Search",
"version": {
"build_date": "2021-04-20T20:56:39.040728659Z",
"build_flavor": "default",
"build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7",
"build_snapshot": false,
"build_type": "docker",
"lucene_version": "8.8.0",
"minimum_index_compatibility_version": "6.0.0-beta1",
"minimum_wire_compatibility_version": "6.8.0",
"number": "7.13.1"
}
}
InternalError
and the following traceback:Traceback (most recent call last):
...
File "/app/app.py", line 13, in async_route
return jsonify(**(await es.info()))
File "/app/venv/lib/...", line 288, in info
return await self.transport.perform_request(
File "/app/venv/lib/...", line 327, in perform_request
raise e
File "/app/venv/lib/...", line 296, in perform_request
status, headers, data = await connection.perform_request(
File "/app/venv/lib/...", line 312, in perform_request
raise ConnectionError("N/A", str(e), e)
ConnectionError(Event loop is closed) caused by:
RuntimeError(Event loop is closed)
AsyncElasticsearch
is implemented and how async views in Flask work.async
or await
can't execute without an event loop that is "running". The unfortunate thing is that there's no running event loop right when you start Python (ie, the global scope).async def f():
print("I'm async!")
# Can't do this!
await f()
asyncio.run()
and typically an async
main/entrypoint function to use await
like so:import asyncio
async def f():
print("I'm async!")
async def main():
await f()
# asyncio starts an event loop here:
asyncio.run(main())
python -m asyncio
/ IPython
, but really this is running the REPL after starting an event loop)AsyncElasticsearch
instance in the global scope?AsyncElasticsearch
is delaying the full initialization of calling asyncio.get_running_loop()
, creating aiohttp.Session
, sniffing, etc until after we've received our first async
call. Once an async
call is made we can almost guarantee that there's a running event loop, because if there wasn't a running event loop the request wouldn't work out anyways.AsyncElasticsearch
instance in the global scope how users create their synchronous Elasticsearch
client in the global scope.aiohttp
in the process for no (?) benefit, so we don't support this configuration. Now how does this break when used with Flask's new async views?$ gunicorn app:app -k uvicorn.workers.UvicornWorker
WsgiToAsgi
that converts a WSGI application to an ASGI application.from flask import Flask, jsonify
from elasticsearch import AsyncElasticsearch
# Same definition as above...
wsgi_app = Flask(__name__)
es = AsyncElasticsearch(
"https://localhost:9200",
api_key="..."
)
@wsgi_app.route("/", methods=["GET"])
async def async_view():
return jsonify(**(await es.info()))
# Convert the WSGI application to ASGI
from asgiref.wsgi import WsgiToAsgi
asgi_app = WsgiToAsgi(wsgi_app)
wsgi_app
into an ASGI application asgi_app
which means when we run the application a single event loop will be used for every request instead of a new event loop per request.