From e65a33d3cff0323a15d894ded16cb3c501a8f717 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Mar 2021 15:06:05 +0000 Subject: [PATCH] Neater close logic (#1541) --- httpx/_client.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/httpx/_client.py b/httpx/_client.py index 691111ba..7f8ce531 100644 --- a/httpx/_client.py +++ b/httpx/_client.py @@ -69,8 +69,16 @@ ACCEPT_ENCODING = ", ".join( class ClientState(enum.Enum): + # UNOPENED: + # The client has been instantiated, but has not been used to send a request, + # or been opened by entering the context of a `with` block. UNOPENED = 1 + # OPENED: + # The client has either sent a request, or is within a `with` block. OPENED = 2 + # CLOSED: + # The client has either exited the `with` block, or `close()` has + # been called explicitly. CLOSED = 3 @@ -1140,7 +1148,8 @@ class Client(BaseClient): transport.__exit__(exc_type, exc_value, traceback) def __del__(self) -> None: - self.close() + if self._state == ClientState.OPENED: + self.close() class AsyncClient(BaseClient): @@ -1784,6 +1793,26 @@ class AsyncClient(BaseClient): def __del__(self) -> None: if self._state == ClientState.OPENED: + # Unlike the sync case, we cannot silently close the client when + # it is garbage collected, because `.aclose()` is an async operation, + # but `__del__` is not. + # + # For this reason we require explicit close management for + # `AsyncClient`, and issue a warning on unclosed clients. + # + # The context managed style is usually preferable, because it neatly + # ensures proper resource cleanup: + # + # async with httpx.AsyncClient() as client: + # ... + # + # However, an explicit call to `aclose()` is also sufficient: + # + # client = httpx.AsyncClient() + # try: + # ... + # finally: + # await client.aclose() warnings.warn( f"Unclosed {self!r}. " "See https://www.python-httpx.org/async/#opening-and-closing-clients "