Skip to content

Commit 46558f7

Browse files
committed
Retire map_exceptions in httpcore2
`try: except:` blocks are free on modern Pythons when exceptions aren't raised. Entering a `@contextmanager`'d block is much less free, and there are hot paths (e.g. reading from a sync socket) where it's worth avoiding that overhead. For consistency, this retires the use of `map_exceptions` everywhere. New exception blocks are marked nocover, since they weren't actually tested before either.
1 parent 718d096 commit 46558f7

7 files changed

Lines changed: 121 additions & 117 deletions

File tree

‎src/httpcore2/httpcore2/_async/http11.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
LocalProtocolError,
1717
RemoteProtocolError,
1818
WriteError,
19-
map_exceptions,
2019
)
2120
from .._models import Origin, Request, Response
2221
from .._synchronization import AsyncLock, AsyncShieldCancellation
@@ -130,12 +129,14 @@ async def _send_request_headers(self, request: Request) -> None:
130129
timeouts = request.extensions.get("timeout", {})
131130
timeout = timeouts.get("write", None)
132131

133-
with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
132+
try:
134133
event = h11.Request(
135134
method=request.method,
136135
target=request.url.target,
137136
headers=request.headers,
138137
)
138+
except h11.LocalProtocolError as exc:
139+
raise LocalProtocolError(exc) from exc
139140
await self._send_event(event, timeout=timeout)
140141

141142
async def _send_request_body(self, request: Request) -> None:
@@ -193,8 +194,10 @@ async def _receive_response_body(self, request: Request) -> AsyncGenerator[bytes
193194

194195
async def _receive_event(self, timeout: float | None = None) -> h11.Event | type[h11.PAUSED]:
195196
while True:
196-
with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
197+
try:
197198
event = self._h11_state.next_event()
199+
except h11.RemoteProtocolError as exc:
200+
raise RemoteProtocolError(exc) from exc
198201

199202
if event is h11.NEED_DATA:
200203
data = await self._network_stream.read(self.READ_NUM_BYTES, timeout=timeout)

‎src/httpcore2/httpcore2/_backends/anyio.py‎

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
ReadTimeout,
1515
WriteError,
1616
WriteTimeout,
17-
map_exceptions,
1817
)
1918
from .._utils import is_socket_readable
2019
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
@@ -25,31 +24,35 @@ def __init__(self, stream: anyio.abc.ByteStream) -> None:
2524
self._stream = stream
2625

2726
async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
28-
exc_map: dict[type[Exception], type[Exception]] = {
29-
TimeoutError: ReadTimeout,
30-
anyio.BrokenResourceError: ReadError,
31-
anyio.ClosedResourceError: ReadError,
32-
anyio.EndOfStream: ReadError,
33-
}
34-
with map_exceptions(exc_map):
27+
try:
3528
with anyio.fail_after(timeout):
3629
try:
3730
return await self._stream.receive(max_bytes=max_bytes)
3831
except anyio.EndOfStream: # pragma: no cover
3932
return b""
33+
except TimeoutError as exc: # pragma: no cover
34+
raise ReadTimeout(exc) from exc
35+
except (
36+
anyio.BrokenResourceError,
37+
anyio.ClosedResourceError,
38+
anyio.EndOfStream,
39+
) as exc: # pragma: no cover
40+
raise ReadError(exc) from exc
4041

4142
async def write(self, buffer: bytes, timeout: float | None = None) -> None:
4243
if not buffer:
4344
return
4445

45-
exc_map: dict[type[Exception], type[Exception]] = {
46-
TimeoutError: WriteTimeout,
47-
anyio.BrokenResourceError: WriteError,
48-
anyio.ClosedResourceError: WriteError,
49-
}
50-
with map_exceptions(exc_map):
46+
try:
5147
with anyio.fail_after(timeout):
5248
await self._stream.send(item=buffer)
49+
except TimeoutError as exc: # pragma: no cover
50+
raise WriteTimeout(exc) from exc
51+
except (
52+
anyio.BrokenResourceError,
53+
anyio.ClosedResourceError,
54+
) as exc: # pragma: no cover
55+
raise WriteError(exc) from exc
5356

5457
async def aclose(self) -> None:
5558
await self._stream.aclose()
@@ -60,13 +63,7 @@ async def start_tls(
6063
server_hostname: str | None = None,
6164
timeout: float | None = None,
6265
) -> AsyncNetworkStream:
63-
exc_map: dict[type[Exception], type[Exception]] = {
64-
TimeoutError: ConnectTimeout,
65-
anyio.BrokenResourceError: ConnectError,
66-
anyio.EndOfStream: ConnectError,
67-
ssl.SSLError: ConnectError,
68-
}
69-
with map_exceptions(exc_map):
66+
try:
7067
try:
7168
with anyio.fail_after(timeout):
7269
ssl_stream = await anyio.streams.tls.TLSStream.wrap(
@@ -79,6 +76,14 @@ async def start_tls(
7976
except Exception as exc: # pragma: no cover
8077
await self.aclose()
8178
raise exc
79+
except TimeoutError as exc: # pragma: no cover
80+
raise ConnectTimeout(exc) from exc
81+
except (
82+
anyio.BrokenResourceError,
83+
anyio.EndOfStream,
84+
ssl.SSLError,
85+
) as exc: # pragma: no cover
86+
raise ConnectError(exc) from exc
8287
return AnyIOStream(ssl_stream)
8388

8489
def get_extra_info(self, info: str) -> typing.Any:
@@ -107,12 +112,7 @@ async def connect_tcp(
107112
) -> AsyncNetworkStream: # pragma: no cover
108113
if socket_options is None:
109114
socket_options = []
110-
exc_map: dict[type[Exception], type[Exception]] = {
111-
TimeoutError: ConnectTimeout,
112-
OSError: ConnectError,
113-
anyio.BrokenResourceError: ConnectError,
114-
}
115-
with map_exceptions(exc_map):
115+
try:
116116
with anyio.fail_after(timeout):
117117
stream: anyio.abc.ByteStream = await anyio.connect_tcp(
118118
remote_host=host,
@@ -122,6 +122,10 @@ async def connect_tcp(
122122
# By default TCP sockets opened in `asyncio` include TCP_NODELAY.
123123
for option in socket_options:
124124
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
125+
except TimeoutError as exc:
126+
raise ConnectTimeout(exc) from exc
127+
except (OSError, anyio.BrokenResourceError) as exc:
128+
raise ConnectError(exc) from exc
125129
return AnyIOStream(stream)
126130

127131
async def connect_unix_socket(
@@ -132,16 +136,15 @@ async def connect_unix_socket(
132136
) -> AsyncNetworkStream: # pragma: no cover
133137
if socket_options is None:
134138
socket_options = []
135-
exc_map: dict[type[Exception], type[Exception]] = {
136-
TimeoutError: ConnectTimeout,
137-
OSError: ConnectError,
138-
anyio.BrokenResourceError: ConnectError,
139-
}
140-
with map_exceptions(exc_map):
139+
try:
141140
with anyio.fail_after(timeout):
142141
stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
143142
for option in socket_options:
144143
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
144+
except TimeoutError as exc:
145+
raise ConnectTimeout(exc) from exc
146+
except (OSError, anyio.BrokenResourceError) as exc:
147+
raise ConnectError(exc) from exc
145148
return AnyIOStream(stream)
146149

147150
async def sleep(self, seconds: float) -> None:

‎src/httpcore2/httpcore2/_backends/sync.py‎

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
from .._exceptions import (
1010
ConnectError,
1111
ConnectTimeout,
12-
ExceptionMapping,
1312
ReadError,
1413
ReadTimeout,
1514
WriteError,
1615
WriteTimeout,
17-
map_exceptions,
1816
)
1917
from .._utils import is_socket_readable
2018
from .base import SOCKET_OPTION, NetworkBackend, NetworkStream
@@ -77,19 +75,25 @@ def _perform_io(
7775
return ret
7876

7977
def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
80-
exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
81-
with map_exceptions(exc_map):
78+
try:
8279
self._sock.settimeout(timeout)
8380
return typing.cast(bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes)))
81+
except TimeoutError as exc:
82+
raise ReadTimeout(exc) from exc
83+
except OSError as exc:
84+
raise ReadError(exc) from exc
8485

8586
def write(self, buffer: bytes, timeout: float | None = None) -> None:
86-
exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
87-
with map_exceptions(exc_map):
87+
try:
8888
self._sock.settimeout(timeout)
8989
view = memoryview(buffer) # zero-copy slicing; avoids copies
9090
while view:
9191
nsent = self._perform_io(functools.partial(self.ssl_obj.write, view))
9292
view = view[nsent:]
93+
except TimeoutError as exc:
94+
raise WriteTimeout(exc) from exc
95+
except OSError as exc:
96+
raise WriteError(exc) from exc
9397

9498
def close(self) -> None:
9599
self._sock.close()
@@ -121,22 +125,28 @@ def __init__(self, sock: socket.socket) -> None:
121125
self._sock = sock
122126

123127
def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
124-
exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
125-
with map_exceptions(exc_map):
128+
try:
126129
self._sock.settimeout(timeout)
127130
return self._sock.recv(max_bytes)
131+
except TimeoutError as exc: # pragma: no cover
132+
raise ReadTimeout(exc) from exc
133+
except OSError as exc: # pragma: no cover
134+
raise ReadError(exc) from exc
128135

129136
def write(self, buffer: bytes, timeout: float | None = None) -> None:
130137
if not buffer:
131138
return
132139

133-
exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
134-
with map_exceptions(exc_map):
140+
try:
135141
view = memoryview(buffer) # zero-copy slicing; avoids copies
136142
while view:
137143
self._sock.settimeout(timeout)
138144
n = self._sock.send(view)
139145
view = view[n:]
146+
except TimeoutError as exc: # pragma: no cover
147+
raise WriteTimeout(exc) from exc
148+
except OSError as exc: # pragma: no cover
149+
raise WriteError(exc) from exc
140150

141151
def close(self) -> None:
142152
self._sock.close()
@@ -147,11 +157,7 @@ def start_tls(
147157
server_hostname: str | None = None,
148158
timeout: float | None = None,
149159
) -> NetworkStream:
150-
exc_map: ExceptionMapping = {
151-
socket.timeout: ConnectTimeout,
152-
OSError: ConnectError,
153-
}
154-
with map_exceptions(exc_map):
160+
try:
155161
try:
156162
if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover
157163
# If the underlying socket has already been upgraded
@@ -164,6 +170,10 @@ def start_tls(
164170
except Exception as exc: # pragma: no cover
165171
self.close()
166172
raise exc
173+
except TimeoutError as exc: # pragma: no cover
174+
raise ConnectTimeout(exc) from exc
175+
except OSError as exc: # pragma: no cover
176+
raise ConnectError(exc) from exc
167177
return SyncStream(sock)
168178

169179
def get_extra_info(self, info: str) -> typing.Any:
@@ -195,12 +205,8 @@ def connect_tcp(
195205
socket_options = [] # pragma: no cover
196206
address = (host, port)
197207
source_address = None if local_address is None else (local_address, 0)
198-
exc_map: ExceptionMapping = {
199-
socket.timeout: ConnectTimeout,
200-
OSError: ConnectError,
201-
}
202208

203-
with map_exceptions(exc_map):
209+
try:
204210
sock = socket.create_connection(
205211
address,
206212
timeout,
@@ -209,6 +215,10 @@ def connect_tcp(
209215
for option in socket_options:
210216
sock.setsockopt(*option) # pragma: no cover
211217
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
218+
except TimeoutError as exc: # pragma: no cover
219+
raise ConnectTimeout(exc) from exc
220+
except OSError as exc: # pragma: no cover
221+
raise ConnectError(exc) from exc
212222
return SyncStream(sock)
213223

214224
def connect_unix_socket(
@@ -222,14 +232,14 @@ def connect_unix_socket(
222232
if socket_options is None:
223233
socket_options = []
224234

225-
exc_map: ExceptionMapping = {
226-
socket.timeout: ConnectTimeout,
227-
OSError: ConnectError,
228-
}
229-
with map_exceptions(exc_map):
235+
try:
230236
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
231237
for option in socket_options:
232238
sock.setsockopt(*option)
233239
sock.settimeout(timeout)
234240
sock.connect(path)
241+
except TimeoutError as exc:
242+
raise ConnectTimeout(exc) from exc
243+
except OSError as exc:
244+
raise ConnectError(exc) from exc
235245
return SyncStream(sock)

0 commit comments

Comments
 (0)