Проблемы с потоковой передачей ответа от flask-smorest

Я сталкиваюсь с проблемами при потоковой передаче ответа в flask-smorest. Я следую инструкциям здесь — https://flask.palletsprojects.com/en/2.3.x/patterns/streaming/ для потоковой передачи ответов из моего приложения flask-smorest. Ниже приведена версия моего кода MRE. Предположим, мое приложение получает курсы валют за последние 1000 дней для любой валюты, запрошенной конечным пользователем.

Это версия без использования потоковой передачи. Он отлично работает и возвращает список ответов json:

from flask import request, Response
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from marshmallow import Schema, fields
import asyncio

class CurrencySchema(Schema):
    name = fields.Str()
    rate = fields.Str()
    date = fields.Str()
    source = fields.Str()

blp = Blueprint("test",__name__, description = "test")

@blp.route("/test")
class Test(MethodView):
    @blp.response(200, CurrencySchema(many=True))
    def get(self):
        currency = request.args.get('currency')
        results = asyncio.run(func_that_fetches_currency_rates_from_three_APIs(
            currency))  # returns a list of dictionaries
        return results

Когда я запускаю это, он успешно запускается и возвращает список ответов json в моем браузере, например:

[{'name': 'USD', 'rate': '1.2333', 'date': 'Mar 21, 2024', 'source': 'currency.com'}, 
 {'name': 'USD', 'rate': '1.2121', 'date': 'Mar 22, 2024', 'source': 'currency.com'}, 
 .................so on and so forth up to 1000 jsons]

Теперь наступает момент, когда я пытаюсь транслировать ответы. Я вношу следующие изменения в свой код:

@blp.route("/test")
class Test(MethodView):
    @blp.response(200, CurrencySchema(many=True))
    def get(self):
        currency = request.args.get('currency')        
        results = asyncio.run(func_that_fetches_currency_rates_from_three_APIs(
            currency))  # returns a list of dictionaries
        def generate_rates():
             batch_size = 100
             for i in range(0, len(results), batch_size):
                  yield results[i:i+batch_size]
        return generate_rates()

Как ни странно, это возвращает список из 50 пустых ответов JSON:

[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
 {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
 {}, {}, {}, {}, {}, {}, {}, {}]

Я тоже попробовал это, но с тем же результатом, то есть списком пустых ответов json, но дополнительно flask-smorest дал мне:

AssertionError: приложения должны записывать байты

Похоже, что файл werkzeugserving.py вызывал проблемы.

@blp.route("/test")
class Test(MethodView):
    @blp.response(200, CurrencySchema(many=True))
    def get(self):
        currency = request.args.get('currency')        
        results = asyncio.run(func_that_fetches_currency_rates_from_three_APIs(
            currency))  # returns a list of dictionaries
        def generate_rates():
             batch_size = 100
             for i in range(0, len(results), batch_size):
                  yield results[i:i+batch_size]
        return Response(generate_rates(), mimetype = 'application/json')

Все мое приложение готово, и это последний момент, который вызывает проблемы. Я хочу передать ответы, и в flask-smorest есть что-то, что вызывает проблему. Буду очень признателен за вашу поддержку. Спасибо!

Новые приложения с использованием ChatGPT
Новые приложения с использованием ChatGPT
Я собираюсь вернуться к теме, которую уже освещал ранее, - чатгпт.
Развертывание модели машинного обучения с помощью Flask - Angular в Kubernetes
Развертывание модели машинного обучения с помощью Flask - Angular в Kubernetes
Kubernetes - это портативная, расширяемая платформа с открытым исходным кодом для управления контейнерными рабочими нагрузками и сервисами, которая...
Другой маршрут в Flask Python
Другой маршрут в Flask Python
Flask - это фреймворк, который поддерживает веб-приложения. В этой статье я покажу, как мы можем использовать @app .route в flask, чтобы иметь другую...
1
0
160
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вам необходимо сгенерировать ответ в виде строки и передать его клиенту. Вы можете сделать это, изменив API:

@blp.route("/test")
class Test(MethodView):
    @blp.response(200, CurrencySchema(many=True))
    def get(self):
        currency = request.args.get('currency')        
        results = asyncio.run(func_that_fetches_currency_rates_from_three_APIs(
            currency))  # returns a list of dictionaries
        def generate_rates():
             batch_size = 100
             yield '['

             previous_val = CurrencySchema(many=True).dumps(results[0:batch_size])[1:-1]

             for i in range(batch_size, len(results), batch_size):
                  next_val = CurrencySchema(many=True).dumps(results[i:i+batch_size])[1:-1]
                  yield previous_val + ', '
                  previous_val = next_val
             yield previous_val
             yield ']'
        return Response(generate_rates(), status=200, content_type='application/json')

Решение: измените последнюю строку на эту.

return Response(json.dumps(next(generate_rates())),mimetype='application/json')

Объяснение:

  • Ответ принимает Iterable[bytes], bytes, Iterable[str], str, и мы передали ему генератор, который не будет работать.
  • Другое дело, что для доступа к значениям генератора вам нужно использовать next(), который возвращает следующий элемент из итератора, следовательно, получая доступ к каждому значению.
  • Затем вы используете json.dumps для преобразования его в строку в формате json. Который в конечном итоге вы возвращаете в браузер как json, используя mimetype.
Ответ принят как подходящий

Итак, я наконец смог решить эту проблему. Основной причиной проблемы был декоратор @blp.response, который не принимал ответы генератора. В конце концов я удалил декоратор из моего кода и вручную сериализовал результаты. Вот окончательный код-

@blp.route("/test")
class Test(MethodView):
    #@blp.response(200, CurrencySchema(many=True))           #removing the decorator
    def get(self):
        currency = request.args.get('currency')
        schema = CurrencySchema()

        results = asyncio.run(func_that_fetches_currency_rates_from_three_APIs(
            currency))  # returns a list of dictionaries 

        @stream_with_context
        def generate_results():
            yield '['
            for result in results[:-1]:
                yield schema.dumps(result)
                yield ', '
            yield schema.dumps(results[-1])
            yield ']'

        return Response(generate_results(), mimetype='application/json') 

Другие вопросы по теме

Похожие вопросы

Даже если установлен SQLALCHEMY_POOL_RECYCLE, Flask-SQLAlchemy не будет автоматически перерабатывать соединения MySQL
Почему фляга требует перенаправления после POST?
Flask Postgres Kubernetes Нет такого файла
Совместное использование переменных между двумя отдельно запускаемыми скриптами Python
Используя Postgres/Flask, как я могу запросить следующее появление запланированной задачи, когда дни/часы/минуты хранятся как целые числа в своих собственных столбцах?
Почему мои таблицы не отображаются в списке командой .table?
Как создать внешний URL-адрес с параметрами в flask/jinja2?
Как отображать документы PDF/изображения, хранящиеся в хранилище Azure, с помощью Flask + Flutter
Попытка получить уникальный список значений из словаря
Проблема с веб-приложением или Azure?: в этом регионе для вашей подписки предусмотрена квота в 0 ядер PremiumV2. Попробуйте выбрать другой регион или артикул