Как подождать 5 секунд (без блокировки) перед отправкой ответа?

Это кажется возможным, потому что в app.Sanic.handle_request() есть этот фрагмент:

            if isawaitable(response):
                response = await response

А вот как awaitable проверяется Python:

def isawaitable(object):
    """Return true if object can be passed to an ``await`` expression."""
    return (isinstance(object, types.CoroutineType) or
            isinstance(object, types.GeneratorType) and
                bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
            isinstance(object, collections.abc.Awaitable))

Я знаю, как использовать async def для создания ожидаемой функции, но я не знаю, как создать ожидаемую HTTPResponse. Было бы действительно полезно увидеть пример ожидаемого ответа с простым await asyncio.sleep(5), если это возможно.


Пробовал решение Михаила, вот что заметил:

  • raise500 входит в asyncio.sleep()
  • ret500 не входит в asyncio.sleep() (баг)
  • raise500 блокирует другие raise500 (баг)
  • raise500 не блокирует ret500
  • Не могу сказать, будет ли ret500 блокировать другие ret500, потому что это слишком быстро (не спит)

Полный код (запустить, сохранив как test.py, затем в оболочке python test.py и перейти к http://127.0.0.1:8000/api/test):

import asyncio
from sanic import Sanic
from sanic.response import HTTPResponse
from sanic.handlers import ErrorHandler


class AsyncHTTPResponse(HTTPResponse):  # make it awaitable
    def __await__(self):
        return self._coro().__await__()  # see https://stackoverflow.com/a/33420721/1113207

    async def _coro(self):
        print('Sleeping')
        await asyncio.sleep(5)
        print('Slept 5 seconds')
        return self


class CustomErrorHandler(ErrorHandler):
    def response(self, request, exception):
        return AsyncHTTPResponse(status=500)


app = Sanic(__name__, error_handler=CustomErrorHandler())


@app.get("/api/test")
async def test(request):
    return HTTPResponse(status=204)


@app.get("/api/raise500")
async def raise500(request):
    raise Exception


@app.get("/api/ret500")
async def ret500(request):
    return AsyncHTTPResponse(status=500)

if __name__ == "__main__":
    app.run()

person davidtgq    schedule 24.11.2017    source источник


Ответы (2)


Класс, реализующий магический метод __await__, становится ожидаемым.

Я не проверял, будет ли это работать в вашем случае, но вот пример создания ожидаемого экземпляра пользовательского класса:

import asyncio
from inspect import isawaitable


class HTTPResponse:  # class we have
    pass


class AsyncHTTPResponse(HTTPResponse):  # make it awaitable
    def __await__(self):
        return self._coro().__await__()  # see https://stackoverflow.com/a/33420721/1113207

    async def _coro(self):
        await asyncio.sleep(2)
        return self


async def main():
    resp = AsyncHTTPResponse()

    if isinstance(resp, HTTPResponse):
        print('It is HTTPResponse class ...')

    if isawaitable(resp):
        print('... which is also awaitable.')

    print('Let us see how it works.')
    await resp


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()
person Mikhail Gerasimov    schedule 24.11.2017
comment
Частично работает при некоторых условиях, я отредактировал ОП со своими наблюдениями - person davidtgq; 24.11.2017

Поскольку ответ Михаила правильный, я буду обсуждать только дальнейшее редактирование

  • Рэйс500 блокирует других Рэйз500 (баг)

Вроде не блокирует. Простой тест (добавлена ​​строка запроса для различения запросов):

for i in `seq 2`;do curl http://127.0.0.1:8000/api/raise500&req=$i & done

Из даты и времени журнала видно, что между запросами нет задержки (блокировки).

Sleeping
Sleeping
Slept 5 seconds
2017-11-26 01:01:49 - (network)[INFO][127.0.0.1:37310]: GET http://127.0.0.1:8000/api/raise500?req=1  500 0
Slept 5 seconds
2017-11-26 01:01:49 - (network)[INFO][127.0.0.1:37308]: GET http://127.0.0.1:8000/api/raise500?req=2  500 0
  • ret500 не входит в asyncio.sleep() (ошибка)

Это потому, что вы возвращаете awaitable в ожидаемой функции, но sanic await только для первого:

@app.get("/api/ret500")
async def ret500(request):
    return AsyncHTTPResponse(status=500)

handle_request делает:

response = ret500(request)  # call `async def ret500` and returns awaitable
if isawaitable(response):
    response = await response  # resolve and returns another awaitable - AsyncHTTPResponse object

# note to wait 5 seconds sanic would need again await for it
# response = await response

Решения:

  1. не возвращайте ожидаемое, другими словами await AsyncHTTPResponse самостоятельно

    @app.get("/api/ret500")
    async def ret500(request):
        res = await AsyncHTTPResponse(status=500)
        return res
    
  2. сбросить рет500 async

    @app.get("/api/ret500")
    def ret500(request):
        return AsyncHTTPResponse(status=500)
    

    Примечание: этот метод действителен только в том случае, если вы не собираетесь вызывать в нем асинхронные функции.

person kwarunek    schedule 26.11.2017