Как ждать 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://stackru.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()
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://stackru.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()
Поскольку ответ Михаила правильный, я буду обсуждать только дальнейшие правки
- рейз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() (ошибка)
Это потому, что вы возвращаетесь в ожидании в ожидаемой функции, но здравый смысл ждет только для первой:
@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
Решения:
не возвращайся ожидаемым, другими словами
await AsyncHTTPResponse
самостоятельно@app.get("/api/ret500") async def ret500(request): res = await AsyncHTTPResponse(status=500) return res
падение ret500
async
@app.get("/api/ret500") def ret500(request): return AsyncHTTPResponse(status=500)
Примечание: этот метод действителен только в том случае, если вы не собираетесь вызывать в нем асинхронные функции.