AttributeError: 'granian._granian.PyFutureAwaitable' object has no attribute 'throw'
I got this exception in my streaming response API. It seems there's a method is not implemented for granian to handle FastAPI Streaming Response. And BTW, I run my service on unix socket in this cast.
2025-09-26T01:33:46.840801Z [error ] Application callable raised an exception
Traceback (most recent call last):
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/granian/_futures.py", line 15, in future_watcher
await inner(watcher.scope, watcher.proto)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/fastapi/applications.py", line 1082, in __call__
await super().__call__(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/applications.py", line 113, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app
await route.handle(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle
await self.app(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/routing.py", line 78, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
raise exc
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
await app(scope, receive, sender)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
await response(scope, receive, send)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/responses.py", line 270, in __call__
with collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
File "/Users/hermeschen/.local/share/uv/python/cpython-3.12-macos-aarch64-none/lib/python3.12/contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/responses.py", line 274, in wrap
await func()
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/starlette/responses.py", line 254, in stream_response
async for chunk in self.body_iterator:
File "/Users/hermeschen/Repo/work/taiwan-license-plate-recognition/.venv/lib/python3.12/site-packages/stream_server/routers/Main.py", line 134, in frame_streamer
AttributeError: 'granian._granian.PyFutureAwaitable' object has no attribute 'throw'
It seems like something in your code is treating Granian futures as coroutines. Can you provide a MRE?
Here's my API. The error occurred when I tried to use httpx to request this API.
@router.get(
"/capture/stream",
status_code=status.HTTP_200_OK,
responses={
"200": {"description": "回傳串流擷取結果"},
"404": {"description": "不存在的串流"},
"422": {"description": "錯誤的 UUID 格式"},
"503": {"description": "擷取串流時出現錯誤"},
},
description="擷取指定串流畫面",
)
async def stream_frame(
request: Request,
stream_id: typing.Annotated[
uuid.UUID, Query(description="串流來源 ID", example="07c9f116-feb1-5e24-97fc-f400c03e935a")
],
):
"""擷取串流畫面"""
if not request.app.state.streams:
await LOGGER.awarning(msg := "🏞️ 無任何串流")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
if request.app.state.streams.get(stream_id) is None:
await LOGGER.awarning(msg := f"🏞️ 串流(stream_id:{stream_id})不存在")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
async def frame_streamer():
frame_queue: CyclicPriorityQueue = await request.app.state.frame_buffer.register()
try:
while not (await request.is_disconnected()):
_, frames = await frame_queue.get()
if (image := frames[stream_id].frame) is None:
continue
yield (
b"--frame\r\nContent-Type: image/webp\r\n\r\n"
+ image
+ b"\r\nX-TIMESTAMP: "
+ frames[stream_id].timestamp.isoformat().encode("utf-8")
+ b"\r\nX-STREAM-ID: "
+ str(frames[stream_id].stream_id).encode("utf-8")
+ b"\r\n"
)
await LOGGER.ainfo(f"🏞️ 擷取串流畫面({frames[stream_id].timestamp})")
except asyncio.CancelledError as e:
await LOGGER.awarning(f"⚠️ Client({request.client})中斷連接")
raise e
finally:
await request.app.state.frame_buffer.unregister(frame_queue)
return StreamingResponse(frame_streamer(), media_type="multipart/x-mixed-replace; boundary=frame")
I think the problematic line is while not (await request.is_disconnected()):
Not a starlette/FastAPI expert, but it seems (for reasons) on exceptions the receive future from Granian gets treated as a coroutine (but it's a future, so shouldn't be iterated with send() and throw() calls, it should be cancelled with cancel() instead).
Maybe @Kludex or @tiangolo can help further here.
Okay, thank you.