Skip to content

Commit 828c491

Browse files
Simplify patch set
1 parent 81b6b1f commit 828c491

File tree

12 files changed

+88
-122
lines changed

12 files changed

+88
-122
lines changed

docs/exceptions.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ The following exceptions may be raised when sending a request:
99
* `httpcore.WriteTimeout`
1010
* `httpcore.NetworkError`
1111
* `httpcore.ConnectError`
12-
* `httpcore.BrokenSocketError`
1312
* `httpcore.ReadError`
1413
* `httpcore.WriteError`
1514
* `httpcore.ProtocolError`

httpcore/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
AsyncSOCKSProxy,
1010
)
1111
from ._backends.asyncio import AsyncioBackend
12-
from ._backends.auto import AutoBackend
1312
from ._backends.base import (
1413
SOCKET_OPTION,
1514
AsyncNetworkBackend,
@@ -20,7 +19,6 @@
2019
from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
2120
from ._backends.sync import SyncBackend
2221
from ._exceptions import (
23-
BrokenSocketError,
2422
ConnectError,
2523
ConnectionNotAvailable,
2624
ConnectTimeout,
@@ -100,7 +98,6 @@ def __init__(self, *args, **kwargs): # type: ignore
10098
"SOCKSProxy",
10199
# network backends, implementations
102100
"SyncBackend",
103-
"AutoBackend",
104101
"AsyncioBackend",
105102
"AnyIOBackend",
106103
"TrioBackend",
@@ -131,7 +128,6 @@ def __init__(self, *args, **kwargs): # type: ignore
131128
"WriteTimeout",
132129
"NetworkError",
133130
"ConnectError",
134-
"BrokenSocketError",
135131
"ReadError",
136132
"WriteError",
137133
]

httpcore/_backends/anyio.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import anyio
55

66
from .._exceptions import (
7-
BrokenSocketError,
87
ConnectError,
98
ConnectTimeout,
109
ReadError,
@@ -83,9 +82,6 @@ async def start_tls(
8382
return AnyIOStream(ssl_stream)
8483

8584
def get_extra_info(self, info: str) -> typing.Any:
86-
if info == "is_readable":
87-
sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
88-
return is_socket_readable(sock)
8985
if info == "ssl_object":
9086
return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None)
9187
if info == "client_addr":
@@ -94,6 +90,9 @@ def get_extra_info(self, info: str) -> typing.Any:
9490
return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None)
9591
if info == "socket":
9692
return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
93+
if info == "is_readable":
94+
sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
95+
return is_socket_readable(sock)
9796
return None
9897

9998

@@ -106,6 +105,8 @@ async def connect_tcp(
106105
local_address: typing.Optional[str] = None,
107106
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
108107
) -> AsyncNetworkStream:
108+
if socket_options is None:
109+
socket_options = [] # pragma: no cover
109110
exc_map = {
110111
TimeoutError: ConnectTimeout,
111112
OSError: ConnectError,
@@ -119,15 +120,18 @@ async def connect_tcp(
119120
local_host=local_address,
120121
)
121122
# By default TCP sockets opened in `asyncio` include TCP_NODELAY.
122-
self._set_socket_options(stream, socket_options)
123+
for option in socket_options:
124+
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
123125
return AnyIOStream(stream)
124126

125127
async def connect_unix_socket(
126128
self,
127129
path: str,
128130
timeout: typing.Optional[float] = None,
129131
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
130-
) -> AsyncNetworkStream:
132+
) -> AsyncNetworkStream: # pragma: nocover
133+
if socket_options is None:
134+
socket_options = []
131135
exc_map = {
132136
TimeoutError: ConnectTimeout,
133137
OSError: ConnectError,
@@ -136,23 +140,9 @@ async def connect_unix_socket(
136140
with map_exceptions(exc_map):
137141
with anyio.fail_after(timeout):
138142
stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
139-
self._set_socket_options(stream, socket_options)
143+
for option in socket_options:
144+
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
140145
return AnyIOStream(stream)
141146

142147
async def sleep(self, seconds: float) -> None:
143148
await anyio.sleep(seconds) # pragma: nocover
144-
145-
def _set_socket_options(
146-
self,
147-
stream: anyio.abc.ByteStream,
148-
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
149-
) -> None:
150-
if not socket_options:
151-
return
152-
153-
sock = stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
154-
if sock is None:
155-
raise BrokenSocketError() # pragma: nocover
156-
157-
for option in socket_options:
158-
sock.setsockopt(*option)

httpcore/_backends/asyncio.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Any, Dict, Iterable, Optional, Type
55

66
from .._exceptions import (
7-
BrokenSocketError,
87
ConnectError,
98
ConnectTimeout,
109
ReadError,
@@ -144,8 +143,9 @@ def get_extra_info(self, info: str) -> Any:
144143
return self._sslobj
145144
if info in ("client_addr", "server_addr"):
146145
sock = self._raw_socket
147-
if sock is None:
148-
raise BrokenSocketError() # pragma: nocover
146+
if sock is None: # pragma: nocover
147+
# TODO replace with an explicit error such as BrokenSocketError
148+
raise ConnectError()
149149
return sock.getsockname() if info == "client_addr" else sock.getpeername()
150150
if info == "socket":
151151
return self._raw_socket
@@ -220,8 +220,9 @@ def _set_socket_options(
220220
return
221221

222222
sock = stream.get_extra_info("socket")
223-
if sock is None:
224-
raise BrokenSocketError() # pragma: nocover
223+
if sock is None: # pragma: nocover
224+
# TODO replace with an explicit error such as BrokenSocketError
225+
raise ConnectError()
225226

226227
for option in socket_options:
227228
sock.setsockopt(*option)

httpcore/_backends/auto.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,22 @@
11
import typing
2-
from importlib.util import find_spec
3-
from typing import Optional, Type
2+
from typing import Optional
43

5-
from .._synchronization import current_async_backend
4+
from .._synchronization import current_async_library
65
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
76

8-
HAS_ANYIO = find_spec("anyio") is not None
9-
107

118
class AutoBackend(AsyncNetworkBackend):
12-
@staticmethod
13-
def set_default_backend(backend_class: Optional[Type[AsyncNetworkBackend]]) -> None:
14-
setattr(AutoBackend, "_default_backend_class", backend_class)
15-
169
async def _init_backend(self) -> None:
17-
if hasattr(self, "_backend"):
18-
return
19-
20-
default_backend_class: Optional[Type[AsyncNetworkBackend]] = getattr(
21-
AutoBackend, "_default_backend_class", None
22-
)
23-
if default_backend_class is not None:
24-
self._backend = default_backend_class()
25-
return
26-
27-
if current_async_backend() == "trio":
28-
from .trio import TrioBackend
29-
30-
self._backend = TrioBackend()
31-
elif HAS_ANYIO:
32-
from .anyio import AnyIOBackend
10+
if not (hasattr(self, "_backend")):
11+
backend = current_async_library()
12+
if backend == "trio":
13+
from .trio import TrioBackend
3314

34-
self._backend = AnyIOBackend()
35-
else:
36-
from .asyncio import AsyncioBackend
15+
self._backend: AsyncNetworkBackend = TrioBackend()
16+
else:
17+
from .anyio import AnyIOBackend
3718

38-
self._backend = AsyncioBackend()
19+
self._backend = AnyIOBackend()
3920

4021
async def connect_tcp(
4122
self,

httpcore/_backends/trio.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ async def connect_tcp(
117117
local_address: typing.Optional[str] = None,
118118
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
119119
) -> AsyncNetworkStream:
120+
# By default for TCP sockets, trio enables TCP_NODELAY.
121+
# https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream
122+
if socket_options is None:
123+
socket_options = [] # pragma: no cover
120124
timeout_or_inf = float("inf") if timeout is None else timeout
121125
exc_map: ExceptionMapping = {
122126
trio.TooSlowError: ConnectTimeout,
@@ -128,15 +132,18 @@ async def connect_tcp(
128132
stream: trio.abc.Stream = await trio.open_tcp_stream(
129133
host=host, port=port, local_address=local_address
130134
)
131-
self._set_socket_options(stream, socket_options)
135+
for option in socket_options:
136+
stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
132137
return TrioStream(stream)
133138

134139
async def connect_unix_socket(
135140
self,
136141
path: str,
137142
timeout: typing.Optional[float] = None,
138143
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
139-
) -> AsyncNetworkStream:
144+
) -> AsyncNetworkStream: # pragma: nocover
145+
if socket_options is None:
146+
socket_options = []
140147
timeout_or_inf = float("inf") if timeout is None else timeout
141148
exc_map: ExceptionMapping = {
142149
trio.TooSlowError: ConnectTimeout,
@@ -146,20 +153,9 @@ async def connect_unix_socket(
146153
with map_exceptions(exc_map):
147154
with trio.fail_after(timeout_or_inf):
148155
stream: trio.abc.Stream = await trio.open_unix_socket(path)
149-
self._set_socket_options(stream, socket_options)
156+
for option in socket_options:
157+
stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
150158
return TrioStream(stream)
151159

152160
async def sleep(self, seconds: float) -> None:
153161
await trio.sleep(seconds) # pragma: nocover
154-
155-
def _set_socket_options(
156-
self,
157-
stream: trio.abc.Stream,
158-
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
159-
) -> None:
160-
# By default for TCP sockets, trio enables TCP_NODELAY.
161-
# https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream
162-
if not socket_options:
163-
return
164-
for option in socket_options:
165-
stream.setsockopt(*option) # type: ignore[attr-defined]

httpcore/_exceptions.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,6 @@ class ConnectError(NetworkError):
7373
pass
7474

7575

76-
class BrokenSocketError(ConnectError):
77-
pass
78-
79-
8076
class ReadError(NetworkError):
8177
pass
8278

httpcore/_synchronization.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import threading
22
from types import TracebackType
3-
from typing import Literal, Optional, Type
3+
from typing import Optional, Type
44

55
from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions
66

@@ -18,28 +18,30 @@
1818
anyio = None # type: ignore
1919

2020

21-
AsyncBackend = Literal["asyncio", "trio"]
22-
23-
24-
def current_async_backend() -> AsyncBackend:
21+
def current_async_library() -> str:
2522
# Determine if we're running under trio or asyncio.
2623
# See https://sniffio.readthedocs.io/en/latest/
2724
try:
2825
import sniffio
2926
except ImportError: # pragma: nocover
30-
backend: AsyncBackend = "asyncio"
27+
environment = "asyncio"
3128
else:
32-
backend = sniffio.current_async_library() # type: ignore[assignment]
29+
environment = sniffio.current_async_library()
3330

34-
if backend not in ("asyncio", "trio"): # pragma: nocover
31+
if environment not in ("asyncio", "trio"): # pragma: nocover
3532
raise RuntimeError("Running under an unsupported async environment.")
3633

37-
if backend == "trio" and trio is None: # pragma: nocover
34+
if environment == "asyncio" and anyio is None: # pragma: nocover
35+
raise RuntimeError(
36+
"Running with asyncio requires installation of 'httpcore[asyncio]'."
37+
)
38+
39+
if environment == "trio" and trio is None: # pragma: nocover
3840
raise RuntimeError(
3941
"Running with trio requires installation of 'httpcore[trio]'."
4042
)
4143

42-
return backend
44+
return environment
4345

4446

4547
class AsyncLock:
@@ -58,7 +60,7 @@ def setup(self) -> None:
5860
Detect if we're running under 'asyncio' or 'trio' and create
5961
a lock with the correct implementation.
6062
"""
61-
self._backend = current_async_backend()
63+
self._backend = current_async_library()
6264
if self._backend == "trio":
6365
self._trio_lock = trio.Lock()
6466
elif self._backend == "asyncio":
@@ -116,7 +118,7 @@ def setup(self) -> None:
116118
Detect if we're running under 'asyncio' or 'trio' and create
117119
a lock with the correct implementation.
118120
"""
119-
self._backend = current_async_backend()
121+
self._backend = current_async_library()
120122
if self._backend == "trio":
121123
self._trio_event = trio.Event()
122124
elif self._backend == "asyncio":
@@ -158,7 +160,7 @@ def setup(self) -> None:
158160
Detect if we're running under 'asyncio' or 'trio' and create
159161
a semaphore with the correct implementation.
160162
"""
161-
self._backend = current_async_backend()
163+
self._backend = current_async_library()
162164
if self._backend == "trio":
163165
self._trio_semaphore = trio.Semaphore(
164166
initial_value=self._bound, max_value=self._bound
@@ -197,7 +199,7 @@ def __init__(self) -> None:
197199
Detect if we're running under 'asyncio' or 'trio' and create
198200
a shielded scope with the correct implementation.
199201
"""
200-
self._backend = current_async_backend()
202+
self._backend = current_async_library()
201203

202204
if self._backend == "trio":
203205
self._trio_shield = trio.CancelScope(shield=True)

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ ignore_missing_imports = true
9191

9292
[tool.pytest.ini_options]
9393
addopts = ["-rxXs", "--strict-config", "--strict-markers"]
94-
markers = ["copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup"]
94+
markers = [
95+
"copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup",
96+
"no_auto_backend_patch", # TODO remove this marker once we have a way to define the asyncio backend in AutoBackend
97+
]
9598
filterwarnings = ["error"]
9699

97100
[tool.coverage.run]

tests/benchmark/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import os
23
import sys
34
import time
45
from concurrent.futures import ThreadPoolExecutor
@@ -20,8 +21,7 @@
2021
CONCURRENCY = 20
2122
POOL_LIMIT = 100
2223
PROFILE = False
23-
httpcore.AutoBackend.set_default_backend(httpcore.AsyncioBackend)
24-
# httpcore.AutoBackend.set_default_backend(httpcore.AnyIOBackend)
24+
os.environ["HTTPCORE_PREFER_ANYIO"] = "0"
2525

2626

2727
def duration(start: float) -> int:

0 commit comments

Comments
 (0)