-
-
Notifications
You must be signed in to change notification settings - Fork 839
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
Add support for streaming responses to ASGITransport
#998
Conversation
a953447
to
c70d345
Compare
I actually started doing something similar, but when I got to the point of calling However, in general this seems like it should work and is a very cool hack to work around the API limitations. Also returning the I personally think we should probably go with this approach and consider supporting an internal context manager for transports later on (which in the case of trio could be a nursery). Only suggestion for improvement is that we might want to call the aclose in some finally block just to make sure that it always gets called. Also, I see |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me, nice job! 💯
My only nitpick would be the request
method is getting a bit long and hard to follow, but I don't have any suggestions on how to best break it up atm. In any case I'd rather land this as it stands and revisit later if we feel it's worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I approve this pull request as well. 😄
I'm going to have to be really awkward, and say that I'm not sure I'm comfortable with us jumping in on this one, at least without spending a chunk of review that I've not managed to make for it yet. |
@yeraydiazdiaz Yeah I sort of agree; though the only refactor I can think of is a class, with the stuff we initialize as attributes and functions we define as methods. I’m not convinced it would be shorter/easier to read (especially since I predict some new difficulties due to the fact we won’t be able to just use closures), but defo something to explore. @tomchristie Sure, happy for this to wait on some more reviews. I’d actually probably want to give this a new look myself... |
5db032e
to
e6d6c6c
Compare
I reworked this PR into something I'm more confident with, using a channel API inspired by trio's for the response body… I think it makes better sense now, and there's no more low-level event-based synchronization. Happy to have a new pair of 👀 on this. I reckon there are some bits that are not strictly relevant to adding streaming response support (reorganization of variables, run tests on trio too, etc), I'll see if I can add those as separate PRs. |
So I'm pretty cautious against us including a case where we're calling directly into a nursery I think the streaming ASGI case is a good point for use to really make sure that our transport API also properly encapsulates the case of transports that need to be used in a strictly context-managed manner, and this is a neat side-step and functional, but also bypasses us taking a "here's how a context-managed transport implementation looks" approach. So. Not necessarily 100% a no on this now, but also not sufficiently convinced that we oughta roll it in. I'm kinda okay that our ASGI use-case has the non-streaming constraint at the moment, at least until we've got an approach that feels like it's more properly in line with structured concurrency. In the meantime users who do strictly need this could feasibly package it up as an alternate transport implementation. Hoping I've managed to explain this sufficiently clearly? Perhaps something I could try to spend some time thrashing out more fully in the next few days? |
@tomchristie I must say I'm a bit confused, and I don't think I have a vision for what you're thinking of. So…
I think that would help. :-) |
Just for the record, I'm using this version internally as a custom transport and it's working alright. What's a bit inconvenient on that side is that I had to basically duplicate I'm not sure, but I think I get the idea that @tomchristie is saying about transports are encapsulated in a context-managed manner, in case it means that the transports should have a clear lifecycle and they would be (async) context managers themselves. I need to point out that in this case it would still require calling |
@juhovh-aiven Thanks, there's some good observations there. Something to note is that we don't really need to be using ContentStream here. They provide for optionally replay-able streams, which we use when sending requests. Really we probably ought to switch the implementation over to instead just using the plain Similarly I don't think you should need to be importing
That's about it, yup. Essentially two things here:
|
Also, the reason I'm using |
111ed19
to
dc940e7
Compare
dc940e7
to
633fc94
Compare
This is pretty elegant actually. I’m a bit cautious wrt. calling directly into The alternative here would be a task group instance that is scoped to the transport I haven’t quite gotten my head around if there’s an alternative to the Transport API, that would more neatly also encapsulate context management around the response lifetime, rather than the rather loose “you’ve got to call |
I also read through the changes and like this version quite a bit, anyio seems to nicely hide the complexity behind supporting both asyncio and trio, and making the I personally think the Basically the only thing I can think of that we could do is to push the Even in that case we might want to consider merging this (assuming anyio is alright), since refactoring would be fairly trivial and the current solution is pretty low risk. |
Transports are technically already context managers ( Lucky as we are, Python's So, such a proposal would mean transports would be used like this… async with ASGITransport(app=app) as transport:
async with transport.request(method, url, headers, stream) as (
http_version,
status_code,
reason_phrase,
headers,
stream,
):
pass There are several benefits to this:
from contextlib import asynccontextmanager
import httpcore
class ASGITransport(httpcore.AsyncHTTPTransport):
@asynccontextmanager
async def request(self, ...):
async with anyio.create_task_group() as tg:
await tg.spawn(app, ...)
yield (http_version, status_code, reason_phrase, headers, stream) Lemme pitch this in the HTTPCore repo. It's a pretty fundamental idea since it changes the transport API a bit, which is something we ought to do before HTTPX reaches 1.0. |
-> Filed encode/httpcore#145 |
So, the tricky bit here that I've not figured out is how that would work together with the internal client flow, in particular dealing with redirects. (Or if it even could work there.) |
Haven't thought about that either, and there's probably going to be some refactoring required to accomodate this change. :-) But I'm having the strong intuition that this change would lead to a more correct API, that we can then accomodate in HTTPX. (HTTPX does a good job of separating the streaming case, which enforces the |
Righty. We'll need to update this given the 0.18 Transport API, then it's probably time to think about getting this in. |
Dropping the milestone on this. We want to get it in for sure, but it's not API changing so it doesn't really need to block our 0.18 release. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
cc @juhovh-aiven
An alternative for #994, with support for asyncio and trio.
@tomchristie I took a different approach than the "nursery on the transport" approach you mentioned in #994 (comment). As it turns out, we can get away with a per-
.request()
"background task manager" kind of thing. As a result, we don't need any__aenter__()
/__aexit__()
on transports.