-
-
Notifications
You must be signed in to change notification settings - Fork 860
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
Context managed client.send() #1516
Conversation
Potential API alternative... Keep the So, this continues to work the same before and after... request = client.build_request("GET", "https://www.example.com")
response = client.send(request) But request = client.build_request("GET", "https://www.example.com")
with client.send_and_stream_response(request) as response:
... |
self.is_closed = True | ||
if self._on_close is not None: | ||
self._on_close(self) | ||
self.is_closed = True |
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.
It seems we still have on_close: Callable = None
as part of the Response constructor for now, right? (Probably an oversight given the PR description.)
try: | ||
from contextlib import asynccontextmanager # type: ignore | ||
except ImportError: # pragma: nocover | ||
from async_generator import asynccontextmanager # type: ignore |
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.
Thoughts on the _compat.py
pattern? Move this bit there and then do from ._compat import asynccontextmanager
everywhere. Always found it ends up being worth doing eventually, and keeps compat code in a clear area.
@@ -705,7 +667,8 @@ def request( | |||
|
|||
```python | |||
request = client.build_request(...) | |||
response = client.send(request, ...) | |||
with client.send(request, ...) as response: | |||
response.read() |
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.
I think this might be worth discussing.
If we give folks client.send()
and this returns a response
context manager, shouldn't they expect response
to be readily-usable, eg response.text
returns the text right away?
Why would we want .send()
to return a context manager, if we don't actually need that interface except in the streaming case? Having to add response.read()
each time as boilerplate seems like a smell, right?
Also, how do we help users understand what the effect of not calling response.read()
would be?
Given the pre-existing Requests model of ".request() gives me a response with the body readily available", I wonder about the implications of this.
That's why I ended up re-working #1355 as #1491 — in the end as a user it didn't seem necessary.
Now I'd understand if we define .send()
to be the entrypoint for the "HTTPX Transport API". It would be a lower-level thing for advanced use cases that .request()
or .stream()
don't fulfill. But then there are rather simple cases like "I want a custom/manual Request
and send it as usual" which become more unusual, where you get this context manager rather than a Response
immediately.
Anyway, curious to see how you'd see the docs evolve. :-)
So I'm going to close this off, but a little discussion to illuminate first... To expand on the motivation of why we might have wanted to do this: Following in the footsteps of trio's structured concurrency constraints but wrt. resources... making sure that when any OS resource is opened we only do so within a If we wanted to follow that constraint it'd imply that we shouldn't expose any "return an opened response" API, which either means dropping But the downsides here are that our call stack gets wrapped in decorators all the way through. Tracebacks are harder to follow, and it's not necessarily true that it's easier for developers to follow the code flow. There's also the downside, that in some contexts a framework might not provide the constructs that we need in order to be able to work within a context managed fashion. See eg. using starlette to proxy and stream a response, which currently requires a close-callback style. (Which is a pointer to a design failure there, but nonetheless, having to point devs to a "if you really need to do this, here's how to call In any case the So short story. I can't see this as a worthwhile trade off. The "resources MUST be context managed" approach clearly has some nice design properties, but I think it backs us into too much of a corner. (And #998 helps show that we can bridge to that neatly enough for some parts if we really need to.) |
Reworking of #1355 in order to keep thinking a few things over.
There's a bunch of stuff for us to discuss around 1.0 and these last bits of "should we be using a context managed interface for (1) the Transport API and (2) the send() method."
For me the important step forward is the core idea in #1515, that the Transport API should be an HTTPX interface, in the HTTPX package. The really nice thing there is that once we do that, the
httpcore
implementation and API doesn't strictly have to define what we're presenting to the end-user in httpx, which just makes everything easier, and allows use to consider each of these three separate issues in isolation from one another...I need to follow up on this one with a bit more docs & chat, but parking it here for now.