Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions src/httpx2/httpx2/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,10 +1369,12 @@ def close(self) -> None:
if self._state != ClientState.CLOSED:
self._state = ClientState.CLOSED

self._transport.close()
for transport in self._mounts.values():
if transport is not None:
transport.close()
try:
self._transport.close()
finally:
for transport in self._mounts.values():
if transport is not None:
transport.close()

def __enter__(self: T) -> T:
if self._state != ClientState.UNOPENED:
Expand All @@ -1398,10 +1400,12 @@ def __exit__(
) -> None:
self._state = ClientState.CLOSED

self._transport.__exit__(exc_type, exc_value, traceback)
for transport in self._mounts.values():
if transport is not None:
transport.__exit__(exc_type, exc_value, traceback)
try:
self._transport.__exit__(exc_type, exc_value, traceback)
finally:
for transport in self._mounts.values():
if transport is not None:
transport.__exit__(exc_type, exc_value, traceback)


class AsyncClient(BaseClient):
Expand Down Expand Up @@ -2206,10 +2210,12 @@ async def aclose(self) -> None:
if self._state != ClientState.CLOSED:
self._state = ClientState.CLOSED

await self._transport.aclose()
for proxy in self._mounts.values():
if proxy is not None:
await proxy.aclose()
try:
await self._transport.aclose()
finally:
for proxy in self._mounts.values():
if proxy is not None:
await proxy.aclose()

async def __aenter__(self: U) -> U:
if self._state != ClientState.UNOPENED:
Expand All @@ -2235,7 +2241,9 @@ async def __aexit__(
) -> None:
self._state = ClientState.CLOSED

await self._transport.__aexit__(exc_type, exc_value, traceback)
for proxy in self._mounts.values():
if proxy is not None:
await proxy.__aexit__(exc_type, exc_value, traceback)
try:
await self._transport.__aexit__(exc_type, exc_value, traceback)
finally:
for proxy in self._mounts.values():
if proxy is not None:
await proxy.__aexit__(exc_type, exc_value, traceback)
44 changes: 44 additions & 0 deletions tests/httpx2/client/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,50 @@ async def __aexit__(self, *args: typing.Any) -> None:
]


@pytest.mark.anyio
async def test_aclose_still_closes_mount_when_transport_raises() -> None:
class RaisingTransport(httpx2.AsyncBaseTransport):
async def aclose(self) -> None:
raise RuntimeError("boom")

class Transport(httpx2.AsyncBaseTransport):
def __init__(self) -> None:
self.closed = False

async def aclose(self) -> None:
self.closed = True

mounted = Transport()
client = httpx2.AsyncClient(transport=RaisingTransport(), mounts={"http://www.example.org": mounted})

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

assert mounted.closed


@pytest.mark.anyio
async def test_aexit_still_closes_mount_when_transport_raises() -> None:
class RaisingTransport(httpx2.AsyncBaseTransport):
async def aclose(self) -> None:
raise RuntimeError("boom")

class Transport(httpx2.AsyncBaseTransport):
def __init__(self) -> None:
self.closed = False

async def aclose(self) -> None:
self.closed = True

mounted = Transport()

with pytest.raises(RuntimeError):
async with httpx2.AsyncClient(transport=RaisingTransport(), mounts={"http://www.example.org": mounted}):
pass

assert mounted.closed


def hello_world(request: httpx2.Request) -> httpx2.Response:
return httpx2.Response(200, text="Hello, world!")

Expand Down
42 changes: 42 additions & 0 deletions tests/httpx2/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,48 @@ def __exit__(self, *args: typing.Any) -> None:
]


def test_close_still_closes_mount_when_transport_raises() -> None:
class RaisingTransport(httpx2.BaseTransport):
def close(self) -> None:
raise RuntimeError("boom")

class Transport(httpx2.BaseTransport):
def __init__(self) -> None:
self.closed = False

def close(self) -> None:
self.closed = True

mounted = Transport()
client = httpx2.Client(transport=RaisingTransport(), mounts={"http://www.example.org": mounted})

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

assert mounted.closed


def test_exit_still_closes_mount_when_transport_raises() -> None:
class RaisingTransport(httpx2.BaseTransport):
def close(self) -> None:
raise RuntimeError("boom")

class Transport(httpx2.BaseTransport):
def __init__(self) -> None:
self.closed = False

def close(self) -> None:
self.closed = True

mounted = Transport()

with pytest.raises(RuntimeError):
with httpx2.Client(transport=RaisingTransport(), mounts={"http://www.example.org": mounted}):
pass

assert mounted.closed


def hello_world(request: httpx2.Request) -> httpx2.Response:
return httpx2.Response(200, text="Hello, world!")

Expand Down
Loading