Skip to content

Commit

Permalink
Prevent Client and AsyncClient from sending a request when it has bee…
Browse files Browse the repository at this point in the history
…n closed (#871)
  • Loading branch information
cdeler committed Aug 19, 2020
1 parent 03cd88c commit 5e2197c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
28 changes: 28 additions & 0 deletions httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@
KEEPALIVE_EXPIRY = 5.0


def check_not_closed(method: typing.Callable) -> typing.Callable:
@functools.wraps(method)
def wrapper(
self: "BaseClient", *args: typing.Any, **kwargs: typing.Any
) -> typing.Any:
if self.is_closed:
raise RuntimeError(f"{self!r} is closed. We cannot use the closed client!")
return method(self, *args, **kwargs)

return wrapper


class BaseClient:
def __init__(
self,
Expand All @@ -79,6 +91,14 @@ def __init__(
self.max_redirects = max_redirects
self._trust_env = trust_env
self._netrc = NetRCInfo()
self._is_closed = False

@property
def is_closed(self) -> bool:
"""
Check if the client being closed
"""
return self._is_closed

@property
def trust_env(self) -> bool:
Expand Down Expand Up @@ -619,6 +639,7 @@ def _transport_for_url(self, url: URL) -> httpcore.SyncHTTPTransport:

return self._transport

@check_not_closed
def request(
self,
method: str,
Expand Down Expand Up @@ -1015,6 +1036,7 @@ def delete(
timeout=timeout,
)

@check_not_closed
def close(self) -> None:
"""
Close transport and proxies.
Expand All @@ -1024,6 +1046,8 @@ def close(self) -> None:
if proxy is not None:
proxy.close()

self._is_closed = True

def __enter__(self) -> "Client":
return self

Expand Down Expand Up @@ -1220,6 +1244,7 @@ def _transport_for_url(self, url: URL) -> httpcore.AsyncHTTPTransport:

return self._transport

@check_not_closed
async def request(
self,
method: str,
Expand Down Expand Up @@ -1619,6 +1644,7 @@ async def delete(
timeout=timeout,
)

@check_not_closed
async def aclose(self) -> None:
"""
Close transport and proxies.
Expand All @@ -1628,6 +1654,8 @@ async def aclose(self) -> None:
if proxy is not None:
await proxy.aclose()

self._is_closed = True

async def __aenter__(self) -> "AsyncClient":
return self

Expand Down
37 changes: 37 additions & 0 deletions tests/client/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,40 @@ async def test_100_continue(server):

assert response.status_code == 200
assert response.content == data


@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_is_closed_after_aclose():
client = httpx.AsyncClient()

await client.aclose()

assert client.is_closed


@pytest.mark.usefixtures("async_environment")
async def test_that_async_client_cannot_be_closed_after_being_closed():
client = httpx.AsyncClient()

await client.aclose()

with pytest.raises(RuntimeError):
await client.aclose()


@pytest.mark.usefixtures("async_environment")
async def test_that_async_client_cannot_send_request_after_being_closed():
client = httpx.AsyncClient()

await client.aclose()

with pytest.raises(RuntimeError):
await client.get("http://example.com")
33 changes: 33 additions & 0 deletions tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,36 @@ def test_pool_limits_deprecated():

with pytest.warns(DeprecationWarning):
httpx.AsyncClient(pool_limits=limits)


def test_that_client_is_closed_after_with_block():
with httpx.Client() as client:
pass

assert client.is_closed


def test_that_client_is_closed_after_close():
client = httpx.Client()

client.close()

assert client.is_closed


def test_that_client_cannot_be_closed_after_being_closed():
client = httpx.Client()

client.close()

with pytest.raises(RuntimeError):
client.close()


def test_that_client_cannot_send_request_after_being_closed():
client = httpx.Client()

client.close()

with pytest.raises(RuntimeError):
client.get("http://example.com")

0 comments on commit 5e2197c

Please sign in to comment.