Issue warning on unclosed AsyncClient. (#1197)
* Made Client and AsyncClient checking for being closed in __del__ (#871) * Changed the AsyncClient closing warning type from ResourceWarning (which is too quiet) to UserWarning (#871) * Fixed tests and client's __exit__ and __aexit__ after the difficult merge (#871) * Update test_proxies.py * Update test_proxies.py Co-authored-by: Tom Christie <tom@tomchristie.com>
This commit is contained in:
parent
ec06ba244e
commit
def9f1c320
@ -1,5 +1,6 @@
|
||||
import functools
|
||||
import typing
|
||||
import warnings
|
||||
from types import TracebackType
|
||||
|
||||
import httpcore
|
||||
@ -79,6 +80,14 @@ class BaseClient:
|
||||
self.max_redirects = max_redirects
|
||||
self._trust_env = trust_env
|
||||
self._netrc = NetRCInfo()
|
||||
self._is_closed = True
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""
|
||||
Check if the client being closed
|
||||
"""
|
||||
return self._is_closed
|
||||
|
||||
@property
|
||||
def trust_env(self) -> bool:
|
||||
@ -696,6 +705,8 @@ class Client(BaseClient):
|
||||
|
||||
[0]: /advanced/#request-instances
|
||||
"""
|
||||
self._is_closed = False
|
||||
|
||||
timeout = self.timeout if isinstance(timeout, UnsetType) else Timeout(timeout)
|
||||
|
||||
auth = self._build_request_auth(request, auth)
|
||||
@ -1029,16 +1040,20 @@ class Client(BaseClient):
|
||||
"""
|
||||
Close transport and proxies.
|
||||
"""
|
||||
self._transport.close()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
proxy.close()
|
||||
if not self.is_closed:
|
||||
self._is_closed = True
|
||||
|
||||
self._transport.close()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
proxy.close()
|
||||
|
||||
def __enter__(self) -> "Client":
|
||||
self._transport.__enter__()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
proxy.__enter__()
|
||||
self._is_closed = False
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
@ -1047,10 +1062,16 @@ class Client(BaseClient):
|
||||
exc_value: BaseException = None,
|
||||
traceback: TracebackType = None,
|
||||
) -> None:
|
||||
self._transport.__exit__(exc_type, exc_value, traceback)
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
proxy.__exit__(exc_type, exc_value, traceback)
|
||||
if not self.is_closed:
|
||||
self._is_closed = True
|
||||
|
||||
self._transport.__exit__(exc_type, exc_value, traceback)
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
proxy.__exit__(exc_type, exc_value, traceback)
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.close()
|
||||
|
||||
|
||||
class AsyncClient(BaseClient):
|
||||
@ -1305,6 +1326,8 @@ class AsyncClient(BaseClient):
|
||||
|
||||
[0]: /advanced/#request-instances
|
||||
"""
|
||||
self._is_closed = False
|
||||
|
||||
timeout = self.timeout if isinstance(timeout, UnsetType) else Timeout(timeout)
|
||||
|
||||
auth = self._build_request_auth(request, auth)
|
||||
@ -1640,16 +1663,20 @@ class AsyncClient(BaseClient):
|
||||
"""
|
||||
Close transport and proxies.
|
||||
"""
|
||||
await self._transport.aclose()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
await proxy.aclose()
|
||||
if not self.is_closed:
|
||||
self._is_closed = True
|
||||
|
||||
await self._transport.aclose()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
await proxy.aclose()
|
||||
|
||||
async def __aenter__(self) -> "AsyncClient":
|
||||
await self._transport.__aenter__()
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
await proxy.__aenter__()
|
||||
self._is_closed = False
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
@ -1658,10 +1685,20 @@ class AsyncClient(BaseClient):
|
||||
exc_value: BaseException = None,
|
||||
traceback: TracebackType = None,
|
||||
) -> None:
|
||||
await self._transport.__aexit__(exc_type, exc_value, traceback)
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
await proxy.__aexit__(exc_type, exc_value, traceback)
|
||||
if not self.is_closed:
|
||||
self._is_closed = True
|
||||
await self._transport.__aexit__(exc_type, exc_value, traceback)
|
||||
for proxy in self._proxies.values():
|
||||
if proxy is not None:
|
||||
await proxy.__aexit__(exc_type, exc_value, traceback)
|
||||
|
||||
def __del__(self) -> None:
|
||||
if not self.is_closed:
|
||||
warnings.warn(
|
||||
f"Unclosed {self!r}. "
|
||||
"See https://www.python-httpx.org/async/#opening-and-closing-clients "
|
||||
"for details."
|
||||
)
|
||||
|
||||
|
||||
class StreamContextManager:
|
||||
|
||||
@ -206,3 +206,45 @@ async def test_context_managed_transport():
|
||||
"transport.aclose",
|
||||
"transport.__aexit__",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_that_async_client_is_closed_by_default():
|
||||
client = httpx.AsyncClient()
|
||||
|
||||
assert client.is_closed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_that_send_cause_async_client_to_be_not_closed():
|
||||
client = httpx.AsyncClient()
|
||||
|
||||
await client.get("http://example.com")
|
||||
|
||||
assert not client.is_closed
|
||||
|
||||
await client.aclose()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_that_async_client_is_not_closed_in_with_block():
|
||||
async with httpx.AsyncClient() as client:
|
||||
assert not client.is_closed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_that_async_client_is_closed_after_with_block():
|
||||
async with httpx.AsyncClient() as client:
|
||||
pass
|
||||
|
||||
assert client.is_closed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("async_environment")
|
||||
async def test_that_async_client_caused_warning_when_being_deleted():
|
||||
async_client = httpx.AsyncClient()
|
||||
|
||||
await async_client.get("http://example.com")
|
||||
|
||||
with pytest.warns(UserWarning):
|
||||
del async_client
|
||||
|
||||
@ -247,3 +247,29 @@ def test_context_managed_transport():
|
||||
"transport.close",
|
||||
"transport.__exit__",
|
||||
]
|
||||
|
||||
|
||||
def test_that_client_is_closed_by_default():
|
||||
client = httpx.Client()
|
||||
|
||||
assert client.is_closed
|
||||
|
||||
|
||||
def test_that_send_cause_client_to_be_not_closed():
|
||||
client = httpx.Client()
|
||||
|
||||
client.get("http://example.com")
|
||||
|
||||
assert not client.is_closed
|
||||
|
||||
|
||||
def test_that_client_is_not_closed_in_with_block():
|
||||
with httpx.Client() as client:
|
||||
assert not client.is_closed
|
||||
|
||||
|
||||
def test_that_client_is_closed_after_with_block():
|
||||
with httpx.Client() as client:
|
||||
pass
|
||||
|
||||
assert client.is_closed
|
||||
|
||||
@ -123,14 +123,16 @@ def test_transport_for_request(url, proxies, expected):
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_proxy_close():
|
||||
try:
|
||||
client = httpx.AsyncClient(proxies={"all://": PROXY_URL})
|
||||
client = httpx.AsyncClient(proxies={"https://": PROXY_URL})
|
||||
await client.get("http://example.com")
|
||||
finally:
|
||||
await client.aclose()
|
||||
|
||||
|
||||
def test_sync_proxy_close():
|
||||
try:
|
||||
client = httpx.Client(proxies={"all://": PROXY_URL})
|
||||
client = httpx.Client(proxies={"https://": PROXY_URL})
|
||||
client.get("http://example.com")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user