Я сталкиваюсь с проблемами при потоковой передаче ответа в 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 есть что-то, что вызывает проблему. Буду очень признателен за вашу поддержку. Спасибо!
Вам необходимо сгенерировать ответ в виде строки и передать его клиенту. Вы можете сделать это, изменив 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')
Объяснение:
Итак, я наконец смог решить эту проблему. Основной причиной проблемы был декоратор @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')