Another thing I noticed while reworking encode/httpx#998 and thinking about https://github.com/encode/httpx/issues/1145... (Actually, anyone that tries to solve https://github.com/encode/httpx/issues/1145 should arrive at the same conclusion than I'm exposing in this issue, since the HTTPX clients does the wrong thing there as well, which causes issues with exception handling.)
Right now when someone wants to implement a custom transport, we tell them:
- Implement
.request(...) -> .... - Optionally implement
.aclose() -> None.
I think we might want to consider switch to:
- Implement
.request(...) -> .... - Optionally implement
.__aenter__() -> None and __aexit__(...) -> None.
That is, making it clear to transport implementers that transports are just context managers. More specifically, that if they want their transport to wrap around an async resource, they can do so by forwarding __aenter__() and __aexit__() to that resource.
We can probably (?) keep .aclose() as a synonym of .__aexit__(None, None, None), but it's actually not clear to me whether keeping it wouldn't maintain some sort of confusion. If we really want to enforce context managed usage of transports (and it looks like we should), then transport = Transport() (plain instantiation) + calling .aclose() is not a correct option.
So, AsyncHTTPTransport would become this:
classAsyncHTTPTransport:
""" The base interface for sending HTTP requests. Concete implementations should subclass this class, and implement the `request` method, and optionally the `__aenter__` and/or `__aexit__` methods. """defrequest(
self,
method: bytes,
url: URL,
headers: Headers=None,
stream: AsyncByteStream=None,
timeout: TimeoutDict=None,
) ->AsyncContextManager[
Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], AsyncByteStream]
]:
""" The interface for sending a single HTTP request, and returning a response. **Parameters:** * **method** - `bytes` - The HTTP method, such as `b'GET'`. * **url** - `Tuple[bytes, bytes, Optional[int], bytes]` - The URL as a 4-tuple of (scheme, host, port, path). * **headers** - `Optional[List[Tuple[bytes, bytes]]]` - Any HTTP headers to send with the request. * **stream** - `Optional[AsyncByteStream]` - The body of the HTTP request. * **timeout** - `Optional[Dict[str, Optional[float]]]` - A dictionary of timeout values for I/O operations. Supported keys are "pool" for acquiring a connection from the connection pool, "read" for reading from the connection, "write" for writing to the connection and "connect" for opening the connection. Values are floating point seconds. ** Returns:** An asynchronous context manager returning a five-tuple of: * **http_version** - `bytes` - The HTTP version used by the server, such as `b'HTTP/1.1'`. * **status_code** - `int` - The HTTP status code, such as `200`. * **reason_phrase** - `bytes` - Any HTTP reason phrase, such as `b'OK'`. * **headers** - `List[Tuple[bytes, bytes]]` - Any HTTP headers included on the response. * **stream** - `AsyncByteStream` - The body of the HTTP response. """raiseNotImplementedError() # pragma: nocoverasyncdef__aenter__(self) ->"AsyncHTTPTransport":
""" Context manager hook implementation. I/O and concurrency resources managed by this transport should be opened here. """returnasyncdef__aexit__(
self,
exc_type: Type[BaseException] =None,
exc_value: BaseException=None,
traceback: TracebackType=None,
) ->None:
""" Close the implementation, which should close any outstanding I/O and concurrency resources managed by this transport. """asyncdefaclose(self) ->None:
awaitself.__aexit__(None, None, None)
Another thing I noticed while reworking encode/httpx#998 and thinking about https://github.com/encode/httpx/issues/1145... (Actually, anyone that tries to solve https://github.com/encode/httpx/issues/1145 should arrive at the same conclusion than I'm exposing in this issue, since the HTTPX clients does the wrong thing there as well, which causes issues with exception handling.)
Right now when someone wants to implement a custom transport, we tell them:
.request(...) -> .....aclose() -> None.I think we might want to consider switch to:
.request(...) -> .....__aenter__() -> Noneand__aexit__(...) -> None.That is, making it clear to transport implementers that transports are just context managers. More specifically, that if they want their transport to wrap around an async resource, they can do so by forwarding
__aenter__()and__aexit__()to that resource.We can probably (?) keep
.aclose()as a synonym of.__aexit__(None, None, None), but it's actually not clear to me whether keeping it wouldn't maintain some sort of confusion. If we really want to enforce context managed usage of transports (and it looks like we should), thentransport = Transport()(plain instantiation) + calling.aclose()is not a correct option.So,
AsyncHTTPTransportwould become this: