Overview
Flask supports async and await syntax, allowing you to write asynchronous view functions and handlers. This enables concurrent I/O operations while maintaining Flask’s simple programming model.
Flask’s async support is designed for WSGI servers. For true async performance, consider using an ASGI server with an async framework like Quart (Flask’s async cousin).
Basic Async Views
Simple Async View
from flask import Flask
import asyncio
app = Flask(__name__)
@app.route('/')
async def index():
"""Async view function."""
await asyncio.sleep(1)
return 'Hello, Async World!'
Async with Database
import aiohttp
@app.route('/users/<int:user_id>')
async def get_user(user_id):
"""Fetch user from async database."""
user = await User.get(user_id)
return jsonify(user.to_dict())
@app.route('/users', methods=['POST'])
async def create_user():
"""Create user asynchronously."""
data = await request.get_json()
user = await User.create(**data)
return jsonify(user.to_dict()), 201
How It Works
The ensure_sync Method
Flask automatically wraps async functions to work with WSGI:
from inspect import iscoroutinefunction
def ensure_sync(self, func):
"""Ensure that the function is synchronous for WSGI workers.
Plain def functions are returned as-is. Async def functions
are wrapped to run and wait for the response.
"""
if iscoroutinefunction(func):
return self.async_to_sync(func)
return func
The async_to_sync Method
def async_to_sync(self, func):
"""Return a sync function that will run the coroutine function.
Requires the 'async' extra: pip install 'flask[async]'
"""
from asgiref.sync import async_to_sync as asgiref_async_to_sync
return asgiref_async_to_sync(func)
Installation
Install Flask with async support:
pip install 'flask[async]'
This installs asgiref, which provides the async-to-sync adapter.
Async Patterns
Concurrent API Calls
import aiohttp
import asyncio
@app.route('/aggregate')
async def aggregate_data():
"""Fetch data from multiple APIs concurrently."""
async with aiohttp.ClientSession() as session:
# Run requests concurrently
tasks = [
fetch_users(session),
fetch_posts(session),
fetch_comments(session)
]
users, posts, comments = await asyncio.gather(*tasks)
return jsonify({
'users': users,
'posts': posts,
'comments': comments
})
async def fetch_users(session):
async with session.get('https://api.example.com/users') as resp:
return await resp.json()
async def fetch_posts(session):
async with session.get('https://api.example.com/posts') as resp:
return await resp.json()
async def fetch_comments(session):
async with session.get('https://api.example.com/comments') as resp:
return await resp.json()
Async Database Queries
from databases import Database
database = Database('postgresql://user:pass@localhost/db')
@app.before_serving
async def connect_db():
await database.connect()
@app.after_serving
async def disconnect_db():
await database.disconnect()
@app.route('/users')
async def list_users():
"""Query database asynchronously."""
query = "SELECT * FROM users ORDER BY created_at DESC LIMIT 10"
results = await database.fetch_all(query)
return jsonify([dict(row) for row in results])
@app.route('/users/<int:user_id>')
async def get_user(user_id):
query = "SELECT * FROM users WHERE id = :user_id"
result = await database.fetch_one(query, {'user_id': user_id})
if result is None:
abort(404)
return jsonify(dict(result))
Background Tasks
import asyncio
from datetime import datetime
@app.route('/send-email', methods=['POST'])
async def send_email():
"""Send email asynchronously."""
data = await request.get_json()
# Start background task
asyncio.create_task(send_email_async(
to=data['to'],
subject=data['subject'],
body=data['body']
))
return jsonify({'status': 'Email queued'}), 202
async def send_email_async(to, subject, body):
"""Actually send the email."""
await asyncio.sleep(2) # Simulate email sending
print(f'Email sent to {to}')
Streaming Responses
import asyncio
@app.route('/stream')
async def stream():
"""Stream data asynchronously."""
async def generate():
for i in range(10):
await asyncio.sleep(1)
yield f'data: {i}\n\n'
return generate(), {'Content-Type': 'text/event-stream'}
Async Class-Based Views
MethodView with Async
from flask.views import MethodView
class UserAPI(MethodView):
async def get(self, user_id):
"""Async GET handler."""
if user_id is None:
users = await User.all()
return jsonify([u.to_dict() for u in users])
user = await User.get(user_id)
if user is None:
abort(404)
return jsonify(user.to_dict())
async def post(self):
"""Async POST handler."""
data = await request.get_json()
user = await User.create(**data)
return jsonify(user.to_dict()), 201
async def put(self, user_id):
"""Async PUT handler."""
user = await User.get(user_id)
if user is None:
abort(404)
data = await request.get_json()
await user.update(**data)
return jsonify(user.to_dict())
async def delete(self, user_id):
"""Async DELETE handler."""
user = await User.get(user_id)
if user is None:
abort(404)
await user.delete()
return '', 204
# Register the view
user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users', defaults={'user_id': None},
view_func=user_view, methods=['GET'])
app.add_url_rule('/users', view_func=user_view, methods=['POST'])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
methods=['GET', 'PUT', 'DELETE'])
Request Hooks
Async request hooks are supported:
@app.before_request
async def before_request():
"""Async before request hook."""
g.start_time = time.time()
g.db = await database.connect()
@app.after_request
async def after_request(response):
"""Async after request hook."""
duration = time.time() - g.start_time
response.headers['X-Request-Duration'] = str(duration)
await g.db.close()
return response
@app.teardown_request
async def teardown_request(exception=None):
"""Async teardown hook."""
if hasattr(g, 'db'):
await g.db.close()
Error Handlers
Async error handlers work too:
@app.errorhandler(404)
async def not_found(error):
"""Async error handler."""
await log_error(error)
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(Exception)
async def handle_exception(error):
"""Async exception handler."""
await send_error_to_monitoring(error)
return jsonify({'error': 'Internal server error'}), 500
Limitations
WSGI vs ASGI
Flask runs on WSGI, which is synchronous:
# Flask converts async to sync automatically
@app.route('/')
async def index():
# This runs in a thread pool, not true async
await asyncio.sleep(1)
return 'Hello'
For true async performance, use ASGI with Quart:
# Quart (Flask's async cousin) on ASGI
from quart import Quart
app = Quart(__name__)
@app.route('/')
async def index():
# True async on ASGI
await asyncio.sleep(1)
return 'Hello'
Background Tasks
Background tasks created with asyncio.create_task may not complete:
# Bad: Task may be cancelled when request ends
@app.route('/process')
async def process():
asyncio.create_task(long_running_task())
return 'Started'
# Good: Use a task queue instead
@app.route('/process')
def process():
celery.send_task('long_running_task')
return 'Started'
Best Practices
1. Use Async for I/O Operations
# Good: Async for I/O-bound operations
@app.route('/data')
async def get_data():
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com/data') as resp:
return await resp.json()
# Bad: Async for CPU-bound operations
@app.route('/compute')
async def compute():
# This blocks other requests!
result = expensive_computation()
return str(result)
2. Mix Sync and Async Carefully
# You can have both sync and async views
@app.route('/sync')
def sync_view():
return 'Sync response'
@app.route('/async')
async def async_view():
await asyncio.sleep(1)
return 'Async response'
3. Handle Exceptions Properly
@app.route('/safe')
async def safe_operation():
try:
result = await risky_operation()
return jsonify(result)
except Exception as e:
app.logger.error(f'Error: {e}')
abort(500)
4. Use Timeouts
import asyncio
@app.route('/with-timeout')
async def with_timeout():
try:
result = await asyncio.wait_for(
slow_operation(),
timeout=5.0
)
return jsonify(result)
except asyncio.TimeoutError:
abort(504) # Gateway Timeout
5. Consider Using Quart for True Async
For applications that benefit from async, consider Quart:
from quart import Quart, jsonify
app = Quart(__name__)
@app.route('/')
async def index():
# True async with ASGI
return jsonify({'message': 'Hello'})
if __name__ == '__main__':
app.run()
Testing Async Views
import pytest
@pytest.mark.asyncio
async def test_async_view(client):
"""Test async view."""
response = await client.get('/async')
assert response.status_code == 200
def test_async_view_sync(client):
"""Test async view with sync client."""
# Flask test client works with async views
response = client.get('/async')
assert response.status_code == 200
When to Use Async
Good Use Cases
- Making multiple external API calls
- Database queries (with async DB driver)
- File I/O operations
- Network operations
- WebSocket connections
Not Recommended
- CPU-intensive calculations
- Simple CRUD operations
- Applications with mostly synchronous code
- When you need maximum compatibility