Quart – асинхронный аналог Flask

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Quart

Quart — это современный асинхронный веб-фреймворк для Python, который представляет собой эволюцию популярного Flask с полной поддержкой асинхронного программирования. Основанный на asyncio, Quart обеспечивает высокую производительность и масштабируемость, при этом сохраняя совместимость с Flask API. Это делает его идеальным выбором для разработчиков, которые хотят перейти от синхронного к асинхронному программированию без кардинальных изменений в архитектуре.

Что такое Quart и зачем он нужен

Quart разработан для решения ограничений Flask в области асинхронного программирования. Он позволяет использовать async def во всех частях приложения: обработчиках маршрутов, middleware, хуках и тестах. Это особенно важно для современных веб-приложений, которые часто работают с внешними API, базами данных и требуют высокой пропускной способности.

Основные преимущества Quart

Quart предоставляет разработчикам множество преимуществ по сравнению с традиционными синхронными фреймворками:

  • Полная асинхронность: Все операции могут быть выполнены асинхронно
  • Высокая производительность: Значительно лучшая производительность при высокой нагрузке
  • Встроенная поддержка WebSocket: Нативная поддержка WebSocket без дополнительных расширений
  • Совместимость с Flask: Легкая миграция существующих Flask-приложений
  • Современные веб-стандарты: Поддержка SSE, HTTP/2, и других современных технологий

Сравнение Quart с Flask

Характеристика Flask Quart
Асинхронность Ограниченная (с Flask 2.0+) Полная поддержка asyncio
WebSocket Требует расширений Встроенная поддержка
Совместимость с Flask Да Да (высокая)
Тип сервера WSGI ASGI (через Hypercorn/Uvicorn)
Производительность Средняя Высокая
Поддержка HTTP/2 Нет Да
Server-Sent Events Сложно Встроенная поддержка
Фоновые задачи Через Celery Нативно через asyncio

Установка и настройка окружения

Установка Quart

Для начала работы с Quart необходимо установить основной пакет:

pip install quart

Установка ASGI сервера

Quart требует ASGI сервер для запуска. Рекомендуется использовать Hypercorn:

pip install hypercorn

Альтернативно можно использовать Uvicorn:

pip install uvicorn

Дополнительные зависимости

Для полноценной работы с различными функциями Quart рекомендуется установить:

pip install quart[dotenv]  # Для работы с .env файлами
pip install quart-cors     # Для поддержки CORS
pip install quart-schema   # Для валидации данных

Создание первого приложения

Базовое приложение

from quart import Quart

app = Quart(__name__)

@app.route('/')
async def hello():
    return 'Привет от Quart!'

@app.route('/health')
async def health():
    return {'status': 'healthy', 'framework': 'Quart'}

if __name__ == '__main__':
    app.run(debug=True)

Запуск приложения

Есть несколько способов запуска Quart приложения:

Через встроенный сервер разработки:

python app.py

Через Hypercorn (рекомендуется):

hypercorn app:app --bind 0.0.0.0:8000

Через Uvicorn:

uvicorn app:app --host 0.0.0.0 --port 8000

Основные методы и функции Quart

Таблица основных методов Quart

Метод/Функция Описание Пример использования
app.route() Декоратор для определения маршрутов @app.route('/users/<int:id>')
app.websocket() Декоратор для WebSocket эндпоинтов @app.websocket('/ws')
app.before_request() Выполняется перед каждым запросом @app.before_request
app.after_request() Выполняется после каждого запроса @app.after_request
app.errorhandler() Обработчик ошибок @app.errorhandler(404)
app.before_serving() Выполняется при запуске сервера @app.before_serving
app.after_serving() Выполняется при остановке сервера @app.after_serving
request.args GET параметры request.args.get('name')
request.form POST данные формы await request.form
request.get_json() JSON данные await request.get_json()
request.files Загруженные файлы await request.files
request.headers HTTP заголовки request.headers.get('User-Agent')
request.cookies Куки request.cookies.get('session')
jsonify() Создание JSON ответа jsonify({'key': 'value'})
render_template() Рендеринг шаблона await render_template('index.html')
redirect() Перенаправление redirect('/login')
url_for() Генерация URL url_for('user_profile', id=123)
session Работа с сессией session['user_id'] = 123
websocket.receive() Получение WebSocket сообщения await websocket.receive()
websocket.send() Отправка WebSocket сообщения await websocket.send('Hello')
make_response() Создание кастомного ответа make_response('content', 200)
abort() Генерация HTTP ошибки abort(404)

Работа с маршрутами

from quart import Quart, request, jsonify

app = Quart(__name__)

# Простой маршрут
@app.route('/hello')
async def hello():
    return 'Привет, мир!'

# Маршрут с параметрами
@app.route('/user/<int:user_id>')
async def user_profile(user_id):
    return f'Профиль пользователя {user_id}'

# Маршрут с несколькими HTTP методами
@app.route('/data', methods=['GET', 'POST'])
async def handle_data():
    if request.method == 'POST':
        data = await request.get_json()
        return jsonify({'received': data})
    return jsonify({'message': 'Отправьте POST запрос'})

# Маршрут с опциональными параметрами
@app.route('/posts/')
@app.route('/posts/<int:page>')
async def posts(page=1):
    return f'Страница {page}'

Асинхронные возможности

Работа с асинхронными операциями

import asyncio
import aiohttp
from quart import Quart

app = Quart(__name__)

@app.route('/async-task')
async def async_task():
    # Имитация долгой операции
    await asyncio.sleep(1)
    return 'Асинхронная задача выполнена'

@app.route('/external-api')
async def external_api():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.github.com/users/octocat') as response:
            data = await response.json()
            return data

@app.route('/parallel-tasks')
async def parallel_tasks():
    # Выполнение нескольких задач параллельно
    tasks = [
        asyncio.create_task(fetch_data(i))
        for i in range(3)
    ]
    results = await asyncio.gather(*tasks)
    return {'results': results}

async def fetch_data(id):
    await asyncio.sleep(0.5)
    return f'Данные {id}'

Работа с шаблонами

Quart использует Jinja2 для рендеринга шаблонов, как и Flask:

from quart import render_template

@app.route('/profile/<name>')
async def profile(name):
    user_data = {
        'name': name,
        'age': 25,
        'email': f'{name}@example.com'
    }
    return await render_template('profile.html', user=user_data)

Файл templates/profile.html:

<!DOCTYPE html>
<html>
<head>
    <title>Профиль пользователя</title>
</head>
<body>
    <h1>Профиль: {{ user.name }}</h1>
    <p>Возраст: {{ user.age }}</p>
    <p>Email: {{ user.email }}</p>
</body>
</html>

Обработка форм и данных

Работа с формами

from quart import request, render_template

@app.route('/contact', methods=['GET', 'POST'])
async def contact():
    if request.method == 'POST':
        form_data = await request.form
        name = form_data.get('name')
        email = form_data.get('email')
        message = form_data.get('message')
        
        # Здесь можно обработать данные
        return f'Спасибо, {name}! Ваше сообщение получено.'
    
    return await render_template('contact.html')

Работа с файлами

@app.route('/upload', methods=['POST'])
async def upload_file():
    files = await request.files
    uploaded_file = files.get('file')
    
    if uploaded_file:
        # Сохранение файла
        await uploaded_file.save(f'uploads/{uploaded_file.filename}')
        return f'Файл {uploaded_file.filename} успешно загружен'
    
    return 'Файл не выбран'

WebSocket поддержка

Одна из ключевых особенностей Quart — встроенная поддержка WebSocket:

from quart import websocket

@app.websocket('/ws')
async def ws():
    while True:
        try:
            data = await websocket.receive()
            await websocket.send(f'Сервер получил: {data}')
        except Exception as e:
            print(f'WebSocket ошибка: {e}')
            break

@app.websocket('/chat')
async def chat():
    # Простой чат
    while True:
        message = await websocket.receive()
        # Здесь можно добавить логику рассылки всем подключенным клиентам
        await websocket.send(f'Чат: {message}')

Работа с JSON API

from quart import jsonify, request

@app.route('/api/users', methods=['GET'])
async def get_users():
    # Имитация получения данных из БД
    users = [
        {'id': 1, 'name': 'Алексей', 'email': 'alex@example.com'},
        {'id': 2, 'name': 'Мария', 'email': 'maria@example.com'}
    ]
    return jsonify(users)

@app.route('/api/users', methods=['POST'])
async def create_user():
    data = await request.get_json()
    
    # Валидация данных
    if not data or 'name' not in data:
        return jsonify({'error': 'Имя обязательно'}), 400
    
    # Создание пользователя
    new_user = {
        'id': 3,
        'name': data['name'],
        'email': data.get('email', '')
    }
    
    return jsonify(new_user), 201

Работа с сессиями и аутентификацией

from quart import session, redirect, url_for

app.secret_key = 'your-secret-key'

@app.route('/login', methods=['GET', 'POST'])
async def login():
    if request.method == 'POST':
        form = await request.form
        username = form.get('username')
        password = form.get('password')
        
        # Проверка учетных данных
        if username == 'admin' and password == 'secret':
            session['user_id'] = username
            return redirect(url_for('dashboard'))
        
        return 'Неверные учетные данные'
    
    return await render_template('login.html')

@app.route('/dashboard')
async def dashboard():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    
    return f'Добро пожаловать, {session["user_id"]}!'

@app.route('/logout')
async def logout():
    session.pop('user_id', None)
    return redirect(url_for('login'))

Обработка ошибок

@app.errorhandler(404)
async def not_found(error):
    return await render_template('404.html'), 404

@app.errorhandler(500)
async def internal_error(error):
    return jsonify({'error': 'Внутренняя ошибка сервера'}), 500

@app.errorhandler(Exception)
async def handle_exception(e):
    # Логирование ошибки
    app.logger.error(f'Необработанная ошибка: {e}')
    return jsonify({'error': 'Что-то пошло не так'}), 500

Middleware и хуки

@app.before_request
async def before_request():
    # Выполняется перед каждым запросом
    print(f'Получен запрос: {request.method} {request.path}')

@app.after_request
async def after_request(response):
    # Выполняется после каждого запроса
    response.headers['X-Custom-Header'] = 'Quart App'
    return response

@app.before_serving
async def before_serving():
    # Выполняется при запуске сервера
    print('Сервер запускается...')

@app.after_serving
async def after_serving():
    # Выполняется при остановке сервера
    print('Сервер останавливается...')

Работа с базами данных

Подключение к PostgreSQL

import asyncpg
from quart import g

DATABASE_URL = "postgresql://user:password@localhost/dbname"

@app.before_serving
async def create_db_pool():
    app.db_pool = await asyncpg.create_pool(DATABASE_URL)

@app.after_serving
async def close_db_pool():
    await app.db_pool.close()

@app.route('/users')
async def get_users():
    async with app.db_pool.acquire() as connection:
        users = await connection.fetch("SELECT id, name, email FROM users")
        return jsonify([dict(user) for user in users])

Работа с SQLAlchemy

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

engine = create_async_engine('postgresql+asyncpg://user:pass@localhost/db')
async_session = sessionmaker(engine, class_=AsyncSession)

@app.route('/users/<int:user_id>')
async def get_user(user_id):
    async with async_session() as session:
        user = await session.get(User, user_id)
        if user:
            return jsonify({'id': user.id, 'name': user.name})
        return jsonify({'error': 'Пользователь не найден'}), 404

Фоновые задачи

import asyncio

@app.route('/start-background-task')
async def start_background_task():
    # Запуск фоновой задачи
    asyncio.create_task(background_worker())
    return 'Фоновая задача запущена'

async def background_worker():
    while True:
        # Выполнение фоновой работы
        print('Выполняется фоновая задача...')
        await asyncio.sleep(60)  # Ждем 60 секунд

@app.route('/send-email')
async def send_email():
    # Отправка email в фоне
    asyncio.create_task(send_email_task('user@example.com', 'Тема', 'Текст'))
    return 'Email отправляется...'

async def send_email_task(to, subject, body):
    # Имитация отправки email
    await asyncio.sleep(2)
    print(f'Email отправлен на {to}')

Статические файлы и CORS

Настройка статических файлов

app = Quart(__name__, static_folder='static', static_url_path='/static')

# Файлы будут доступны по адресу /static/filename

Настройка CORS

from quart_cors import cors

app = cors(app, allow_origin="*")

# Или более детальная настройка
app = cors(app, allow_origin="http://localhost:3000", allow_methods=["GET", "POST"])

Тестирование приложений

import pytest
from app import app

@pytest.fixture
def client():
    return app.test_client()

@pytest.mark.asyncio
async def test_home_page(client):
    response = await client.get('/')
    assert response.status_code == 200
    assert await response.get_data() == b'Привет от Quart!'

@pytest.mark.asyncio
async def test_api_endpoint(client):
    response = await client.post('/api/users', json={'name': 'Тест'})
    assert response.status_code == 201
    data = await response.get_json()
    assert data['name'] == 'Тест'

Развертывание в продакшене

Запуск с Hypercorn

# Базовый запуск
hypercorn app:app --bind 0.0.0.0:8000

# Продакшн настройки
hypercorn app:app --bind 0.0.0.0:8000 --workers 4 --log-level info --access-log -

# С SSL
hypercorn app:app --bind 0.0.0.0:443 --certfile cert.pem --keyfile key.pem

Конфигурация через переменные окружения

import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
    DATABASE_URL = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
    DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'

app.config.from_object(Config)

Примеры практического применения

Создание REST API

from quart import Quart, jsonify, request

app = Quart(__name__)

# Имитация базы данных
todos = []

@app.route('/api/todos', methods=['GET'])
async def get_todos():
    return jsonify(todos)

@app.route('/api/todos', methods=['POST'])
async def create_todo():
    data = await request.get_json()
    todo = {
        'id': len(todos) + 1,
        'title': data['title'],
        'completed': False
    }
    todos.append(todo)
    return jsonify(todo), 201

@app.route('/api/todos/<int:todo_id>', methods=['PUT'])
async def update_todo(todo_id):
    data = await request.get_json()
    for todo in todos:
        if todo['id'] == todo_id:
            todo.update(data)
            return jsonify(todo)
    return jsonify({'error': 'Задача не найдена'}), 404

Создание чат-приложения

import asyncio
from quart import Quart, websocket, render_template

app = Quart(__name__)
connected_clients = set()

@app.route('/')
async def index():
    return await render_template('chat.html')

@app.websocket('/ws')
async def ws():
    connected_clients.add(websocket._get_current_object())
    try:
        while True:
            message = await websocket.receive()
            # Рассылка сообщения всем подключенным клиентам
            for client in connected_clients.copy():
                try:
                    await client.send(message)
                except:
                    connected_clients.discard(client)
    except:
        pass
    finally:
        connected_clients.discard(websocket._get_current_object())

Часто задаваемые вопросы

Что такое Quart и чем он отличается от Flask?

Quart — это асинхронный веб-фреймворк, который предоставляет тот же API, что и Flask, но с полной поддержкой async/await. Основные отличия: асинхронность, встроенная поддержка WebSocket, работа с ASGI вместо WSGI.

Можно ли использовать расширения Flask с Quart?

Большинство расширений Flask совместимы с Quart, но для полной функциональности рекомендуется использовать асинхронные версии или специальные расширения для Quart.

Поддерживает ли Quart шаблоны Jinja2?

Да, Quart полностью поддерживает шаблоны Jinja2. Единственное отличие — использование await при вызове render_template().

Какой сервер лучше использовать для Quart?

Рекомендуется использовать Hypercorn, который разрабатывается той же командой, что и Quart. Также можно использовать Uvicorn или другие ASGI-совместимые серверы.

Подходит ли Quart для создания API?

Да, Quart отлично подходит для создания как REST API, так и API с WebSocket. Асинхронная природа делает его особенно эффективным для API, которые взаимодействуют с внешними сервисами.

Как мигрировать с Flask на Quart?

Миграция относительно проста: добавьте async к определениям функций, используйте await для операций с request, render_template() и других асинхронных операций. Замените WSGI сервер на ASGI.

Можно ли использовать Quart для больших приложений?

Да, Quart подходит для приложений любого размера. Он поддерживает блюпринты, фабрики приложений и другие паттерны для структурирования больших приложений.

Как обрабатывать ошибки в Quart?

Используйте декоратор @app.errorhandler() для обработки специфических ошибок или общий обработчик для всех исключений. Все обработчики ошибок должны быть асинхронными.

Quart представляет собой мощный и современный фреймворк для создания веб-приложений на Python. Его асинхронная природа и совместимость с Flask делают его отличным выбором для разработчиков, которые хотят создавать высокопроизводительные веб-приложения с минимальными изменениями в существующем коде.

 

Новости