-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Permit streaming_callback of AsyncHTTPClient to be a coroutine. #3471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Permit streaming_callback of AsyncHTTPClient to be a coroutine. #3471
Conversation
@bdarnell , if it is any consolation, I've run |
I guess one other consideration for this PR; |
Thank you for your patience with this; it originally came in as I was trying to finalize some things for the 6.5 release and I'm just now coming back to it. I like how simply it fits in with the existing coroutine support of data_received. It is possible but more difficult to support streaming callbacks on curl_httpclient. I'd be OK with just returning an error if streaming_callback is awaitable in that case. Do we have any precedent for passing an asynchronous function as a callback? It feels a little different from Tornado's current interface designs. If I was designing this from scratch I'd probably have something like
But I think that's going too far away from the way things work today. An intermediate step might be to populate a Queue which the caller could read from. But that still feels kind of clunky to use and I'm not sure it's any better than what you have here. The test isn't telling us much because there's no way to tell whether or when the callback resumes after waiting for |
I can look into this or otherwise raise an error. I just did not want to add complexity where it wasn't needed.
Yes, I agree. This is the interface that async def request_streamer(url) -> AsyncGenerator[bytes, None, None]:
client = AsyncHTTPClient() # Initialized however.
# Should set a maximum size to the queue to nominally bound it.
body = asyncio.Queue(maxsize=1)
request_task = client.fetch(url, streaming_callback=body.put)
data_task = asyncio.create_task(body.get())
pending = [request_task, data_task]
while True:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
for fut in done:
if fut is request_task:
# Check if both futures are done, and yield the result here first.
if data_task.done():
yield data_task.result()
while not body.empty():
yield body.get_nowait()
# Let any exceptions raise.
fut.result()
return
elif fut is data_task:
yield fut.result()
data_task = asyncio.create_task(body.get())
pending.add(data_task) Anyway, this probably needs to be tuned, (edit: I tailored this example to work a bit better) but is fairly simple enough to give a similar interface and not add another dependency on
I think there is still value in this; you have already proposed the use of a
Partially true. The test verifies that either a standard callback, or a coroutine can successfully be passed to the client without issue. I agree that it doesn't deal with the finer points of how the callback is expected to behave. If you want me to add more tests, I can when I have more time. |
Raising an error is fine.
Right. What I suggested in the linked comment, populating a queue from streaming_callback, wouldn't work for this reason; you'd need some way to push back on the HTTP client. What I was thinking of here was something more integrated: when you fetch in "queue mode", you don't pass a streaming_callback, but instead a Queue is provided for you to read from. The HTTP client would internally handle awaiting as necessary without changing the public streaming_callback interface. But as I write this up it doesn't seem very appealing in comparison to the more straightforward change to allow asynchronous streaming_callback.
Let's do three small changes to the test:
|
Looks good! Thanks for your patience with this. |
This PR adds the ability for
streaming_callback
ofAsyncHTTPClient
to be a coroutine. In addition to the small change, this adds a nominal test to verify that things work as expected, at least as best as thestreaming_callback
approach can permit.This PR is intended to address a few separate issues listed below: