Reputation: 4000
This seems to be possible because in app.Sanic.handle_request()
there's this snippet:
if isawaitable(response):
response = await response
And this is how awaitable
is checked by 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))
I know to use async def
to create an awaitable function, but I don't know how to create an awaitable HTTPResponse
instance. It would really help to see an example of an awaitable response with a simple await asyncio.sleep(5)
if possible.
Tried the solution by Mikhail, here's what I observed:
raise500
enters the asyncio.sleep()
ret500
does not enter the asyncio.sleep()
(bug)raise500
blocks other raise500
(bug)raise500
does not block ret500
ret500
would block other ret500
because it's too fast (not sleeping)Full code (run by saving as test.py
, then in shell python test.py
and going to 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()
Upvotes: 2
Views: 3364
Reputation: 12577
Since Mikhail's answer is the right one, I will only discuss the further edit
- raise500 blocks other raise500 (bug)
It does not seem to block. A simple test (added some query string to distinguish requests):
for i in `seq 2`;do curl http://127.0.0.1:8000/api/raise500&req=$i & done
From the log's datetime it looks that there is not delay (block) between requests
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 does not enter the asyncio.sleep() (bug)
It's because you return awaitable in awaitable function, but sanic await only for the first one:
@app.get("/api/ret500")
async def ret500(request):
return AsyncHTTPResponse(status=500)
The handle_request
does:
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
Solutions:
do not return awaitable, in other words await AsyncHTTPResponse
by yourself
@app.get("/api/ret500")
async def ret500(request):
res = await AsyncHTTPResponse(status=500)
return res
drop ret500's async
@app.get("/api/ret500")
def ret500(request):
return AsyncHTTPResponse(status=500)
Note: this technique is only valid if do not intend to call async functions in it.
Upvotes: 3
Reputation: 39546
Class that implements __await__
magic method becomes awaitable.
I didn't check if it'll work in your case, but here is example of creating awaitable custom class instance:
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()
Upvotes: 3