httpcore2 2.3.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httpcore2-2.3.0 → httpcore2-2.4.0}/CHANGELOG.md +8 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/PKG-INFO +11 -2
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_api.py +2 -1
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/http11.py +1 -5
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/http2.py +5 -8
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/http_proxy.py +4 -4
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/anyio.py +7 -5
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/base.py +1 -5
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_exceptions.py +5 -2
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_models.py +6 -7
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/http11.py +1 -5
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/http2.py +5 -8
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/http_proxy.py +4 -4
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_synchronization.py +1 -1
- {httpcore2-2.3.0 → httpcore2-2.4.0}/pyproject.toml +2 -1
- {httpcore2-2.3.0 → httpcore2-2.4.0}/.gitignore +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/LICENSE.md +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/README.md +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/__init__.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/__init__.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/connection.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/connection_pool.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/interfaces.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_async/socks_proxy.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/__init__.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/auto.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/mock.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/sync.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_backends/trio.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_ssl.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/__init__.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/connection.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/connection_pool.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/interfaces.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_sync/socks_proxy.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_trace.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/_utils.py +0 -0
- {httpcore2-2.3.0 → httpcore2-2.4.0}/httpcore2/py.typed +0 -0
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
6
|
|
|
7
|
+
## 2.4.0 (June 11th, 2026)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
* Move HTTP/2 stream events cleanup inside `_state_lock`. ([#1013](https://github.com/pydantic/httpx2/pull/1013))
|
|
12
|
+
* Release the HTTP/2 semaphore permit on `NoAvailableStreamIDError`. ([#1012](https://github.com/pydantic/httpx2/pull/1012))
|
|
13
|
+
* Use `RLock` instead of `Lock` to prevent a thread deadlock. ([#1008](https://github.com/pydantic/httpx2/pull/1008))
|
|
14
|
+
|
|
7
15
|
## 2.3.0 (June 1st, 2026)
|
|
8
16
|
|
|
9
17
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpcore2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: A minimal low-level HTTP client.
|
|
5
5
|
Project-URL: Changelog, https://github.com/pydantic/httpx2/blob/main/src/httpcore2/CHANGELOG.md
|
|
6
6
|
Project-URL: Homepage, https://github.com/pydantic/httpx2
|
|
@@ -9,8 +9,9 @@ Author-email: Tom Christie <tom@tomchristie.com>
|
|
|
9
9
|
Maintainer-email: "Pydantic Services Inc." <engineering@pydantic.dev>
|
|
10
10
|
License-Expression: BSD-3-Clause
|
|
11
11
|
License-File: LICENSE.md
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: AnyIO
|
|
14
15
|
Classifier: Framework :: AsyncIO
|
|
15
16
|
Classifier: Framework :: Trio
|
|
16
17
|
Classifier: Intended Audience :: Developers
|
|
@@ -150,6 +151,14 @@ All notable changes to this project will be documented in this file.
|
|
|
150
151
|
|
|
151
152
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
152
153
|
|
|
154
|
+
## 2.4.0 (June 11th, 2026)
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
|
|
158
|
+
* Move HTTP/2 stream events cleanup inside `_state_lock`. ([#1013](https://github.com/pydantic/httpx2/pull/1013))
|
|
159
|
+
* Release the HTTP/2 semaphore permit on `NoAvailableStreamIDError`. ([#1012](https://github.com/pydantic/httpx2/pull/1012))
|
|
160
|
+
* Use `RLock` instead of `Lock` to prevent a thread deadlock. ([#1008](https://github.com/pydantic/httpx2/pull/1008))
|
|
161
|
+
|
|
153
162
|
## 2.3.0 (June 1st, 2026)
|
|
154
163
|
|
|
155
164
|
### Changed
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import typing
|
|
5
|
+
from collections.abc import Generator
|
|
5
6
|
|
|
6
7
|
from ._models import URL, Extensions, HeaderTypes, Response
|
|
7
8
|
from ._sync.connection_pool import ConnectionPool
|
|
@@ -55,7 +56,7 @@ def stream(
|
|
|
55
56
|
headers: HeaderTypes = None,
|
|
56
57
|
content: bytes | typing.Iterator[bytes] | None = None,
|
|
57
58
|
extensions: Extensions | None = None,
|
|
58
|
-
) ->
|
|
59
|
+
) -> Generator[Response]:
|
|
59
60
|
"""
|
|
60
61
|
Sends an HTTP request, returning the response within a content manager.
|
|
61
62
|
|
|
@@ -28,11 +28,7 @@ logger = logging.getLogger("httpcore2.http11")
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
# A subset of `h11.Event` types supported by `_send_event`
|
|
31
|
-
H11SendEvent =
|
|
32
|
-
h11.Request,
|
|
33
|
-
h11.Data,
|
|
34
|
-
h11.EndOfMessage,
|
|
35
|
-
]
|
|
31
|
+
H11SendEvent = h11.Request | h11.Data | h11.EndOfMessage
|
|
36
32
|
|
|
37
33
|
|
|
38
34
|
class HTTPConnectionState(enum.IntEnum):
|
|
@@ -14,11 +14,7 @@ import h2.exceptions
|
|
|
14
14
|
import h2.settings
|
|
15
15
|
|
|
16
16
|
from .._backends.base import AsyncNetworkStream
|
|
17
|
-
from .._exceptions import
|
|
18
|
-
ConnectionNotAvailable,
|
|
19
|
-
LocalProtocolError,
|
|
20
|
-
RemoteProtocolError,
|
|
21
|
-
)
|
|
17
|
+
from .._exceptions import ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError
|
|
22
18
|
from .._models import Origin, Request, Response
|
|
23
19
|
from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation
|
|
24
20
|
from .._trace import Trace
|
|
@@ -29,7 +25,7 @@ logger = logging.getLogger("httpcore2.http2")
|
|
|
29
25
|
|
|
30
26
|
|
|
31
27
|
def has_body_headers(request: Request) -> bool:
|
|
32
|
-
return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k,
|
|
28
|
+
return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, _v in request.headers)
|
|
33
29
|
|
|
34
30
|
|
|
35
31
|
class HTTPConnectionState(enum.IntEnum):
|
|
@@ -123,6 +119,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface):
|
|
|
123
119
|
except h2.exceptions.NoAvailableStreamIDError: # pragma: no cover
|
|
124
120
|
self._used_all_stream_ids = True
|
|
125
121
|
self._request_count -= 1
|
|
122
|
+
await self._max_streams_semaphore.release()
|
|
126
123
|
raise ConnectionNotAvailable()
|
|
127
124
|
|
|
128
125
|
try:
|
|
@@ -275,7 +272,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface):
|
|
|
275
272
|
break
|
|
276
273
|
|
|
277
274
|
status_code = 200
|
|
278
|
-
headers = []
|
|
275
|
+
headers: list[tuple[bytes, bytes]] = []
|
|
279
276
|
assert event.headers is not None
|
|
280
277
|
for k, v in event.headers:
|
|
281
278
|
if k == b":status":
|
|
@@ -377,8 +374,8 @@ class AsyncHTTP2Connection(AsyncConnectionInterface):
|
|
|
377
374
|
|
|
378
375
|
async def _response_closed(self, stream_id: int) -> None:
|
|
379
376
|
await self._max_streams_semaphore.release()
|
|
380
|
-
del self._events[stream_id]
|
|
381
377
|
async with self._state_lock:
|
|
378
|
+
del self._events[stream_id]
|
|
382
379
|
if self._connection_terminated and not self._events:
|
|
383
380
|
await self.aclose()
|
|
384
381
|
|
|
@@ -24,8 +24,8 @@ from .connection_pool import AsyncConnectionPool
|
|
|
24
24
|
from .http11 import AsyncHTTP11Connection
|
|
25
25
|
from .interfaces import AsyncConnectionInterface
|
|
26
26
|
|
|
27
|
-
ByteOrStr =
|
|
28
|
-
HeadersAsSequence = typing.Sequence[
|
|
27
|
+
ByteOrStr = bytes | str
|
|
28
|
+
HeadersAsSequence = typing.Sequence[tuple[ByteOrStr, ByteOrStr]]
|
|
29
29
|
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
|
|
30
30
|
|
|
31
31
|
|
|
@@ -42,7 +42,7 @@ def merge_headers(
|
|
|
42
42
|
"""
|
|
43
43
|
default_headers = [] if default_headers is None else list(default_headers)
|
|
44
44
|
override_headers = [] if override_headers is None else list(override_headers)
|
|
45
|
-
has_override = set(key.lower() for key,
|
|
45
|
+
has_override = set(key.lower() for key, _value in override_headers)
|
|
46
46
|
default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override]
|
|
47
47
|
return default_headers + override_headers
|
|
48
48
|
|
|
@@ -278,7 +278,7 @@ class AsyncTunnelHTTPConnection(AsyncConnectionInterface):
|
|
|
278
278
|
if connect_response.status < 200 or connect_response.status > 299:
|
|
279
279
|
reason_bytes = connect_response.extensions.get("reason_phrase", b"")
|
|
280
280
|
reason_str = reason_bytes.decode("ascii", errors="ignore")
|
|
281
|
-
msg = "
|
|
281
|
+
msg = f"{connect_response.status} {reason_str}"
|
|
282
282
|
await self._connection.aclose()
|
|
283
283
|
raise ProxyError(msg)
|
|
284
284
|
|
|
@@ -4,6 +4,8 @@ import ssl
|
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
6
|
import anyio
|
|
7
|
+
import anyio.abc
|
|
8
|
+
import anyio.streams.tls
|
|
7
9
|
|
|
8
10
|
from .._exceptions import (
|
|
9
11
|
ConnectError,
|
|
@@ -23,7 +25,7 @@ class AnyIOStream(AsyncNetworkStream):
|
|
|
23
25
|
self._stream = stream
|
|
24
26
|
|
|
25
27
|
async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
|
|
26
|
-
exc_map = {
|
|
28
|
+
exc_map: dict[type[Exception], type[Exception]] = {
|
|
27
29
|
TimeoutError: ReadTimeout,
|
|
28
30
|
anyio.BrokenResourceError: ReadError,
|
|
29
31
|
anyio.ClosedResourceError: ReadError,
|
|
@@ -40,7 +42,7 @@ class AnyIOStream(AsyncNetworkStream):
|
|
|
40
42
|
if not buffer:
|
|
41
43
|
return
|
|
42
44
|
|
|
43
|
-
exc_map = {
|
|
45
|
+
exc_map: dict[type[Exception], type[Exception]] = {
|
|
44
46
|
TimeoutError: WriteTimeout,
|
|
45
47
|
anyio.BrokenResourceError: WriteError,
|
|
46
48
|
anyio.ClosedResourceError: WriteError,
|
|
@@ -58,7 +60,7 @@ class AnyIOStream(AsyncNetworkStream):
|
|
|
58
60
|
server_hostname: str | None = None,
|
|
59
61
|
timeout: float | None = None,
|
|
60
62
|
) -> AsyncNetworkStream:
|
|
61
|
-
exc_map = {
|
|
63
|
+
exc_map: dict[type[Exception], type[Exception]] = {
|
|
62
64
|
TimeoutError: ConnectTimeout,
|
|
63
65
|
anyio.BrokenResourceError: ConnectError,
|
|
64
66
|
anyio.EndOfStream: ConnectError,
|
|
@@ -105,7 +107,7 @@ class AnyIOBackend(AsyncNetworkBackend):
|
|
|
105
107
|
) -> AsyncNetworkStream: # pragma: no cover
|
|
106
108
|
if socket_options is None:
|
|
107
109
|
socket_options = []
|
|
108
|
-
exc_map = {
|
|
110
|
+
exc_map: dict[type[Exception], type[Exception]] = {
|
|
109
111
|
TimeoutError: ConnectTimeout,
|
|
110
112
|
OSError: ConnectError,
|
|
111
113
|
anyio.BrokenResourceError: ConnectError,
|
|
@@ -130,7 +132,7 @@ class AnyIOBackend(AsyncNetworkBackend):
|
|
|
130
132
|
) -> AsyncNetworkStream: # pragma: no cover
|
|
131
133
|
if socket_options is None:
|
|
132
134
|
socket_options = []
|
|
133
|
-
exc_map = {
|
|
135
|
+
exc_map: dict[type[Exception], type[Exception]] = {
|
|
134
136
|
TimeoutError: ConnectTimeout,
|
|
135
137
|
OSError: ConnectError,
|
|
136
138
|
anyio.BrokenResourceError: ConnectError,
|
|
@@ -4,11 +4,7 @@ import ssl
|
|
|
4
4
|
import time
|
|
5
5
|
import typing
|
|
6
6
|
|
|
7
|
-
SOCKET_OPTION =
|
|
8
|
-
typing.Tuple[int, int, int],
|
|
9
|
-
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
|
10
|
-
typing.Tuple[int, int, None, int],
|
|
11
|
-
]
|
|
7
|
+
SOCKET_OPTION = tuple[int, int, int] | tuple[int, int, bytes | bytearray] | tuple[int, int, None, int]
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
class NetworkStream:
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import contextlib
|
|
2
4
|
import typing
|
|
5
|
+
from collections.abc import Generator
|
|
3
6
|
|
|
4
|
-
ExceptionMapping = typing.Mapping[
|
|
7
|
+
ExceptionMapping = typing.Mapping[type[Exception], type[Exception]]
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
@contextlib.contextmanager
|
|
8
|
-
def map_exceptions(map: ExceptionMapping) ->
|
|
11
|
+
def map_exceptions(map: ExceptionMapping) -> Generator[None]:
|
|
9
12
|
try:
|
|
10
13
|
yield
|
|
11
14
|
except Exception as exc: # noqa: PIE786
|
|
@@ -11,10 +11,10 @@ from ._utils import safe_async_iterate
|
|
|
11
11
|
# Functions for typechecking...
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
ByteOrStr =
|
|
15
|
-
HeadersAsSequence = typing.Sequence[
|
|
14
|
+
ByteOrStr = bytes | str
|
|
15
|
+
HeadersAsSequence = typing.Sequence[tuple[ByteOrStr, ByteOrStr]]
|
|
16
16
|
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
|
|
17
|
-
HeaderTypes =
|
|
17
|
+
HeaderTypes = HeadersAsSequence | HeadersAsMapping | None
|
|
18
18
|
|
|
19
19
|
Extensions = typing.MutableMapping[str, typing.Any]
|
|
20
20
|
|
|
@@ -110,10 +110,10 @@ DEFAULT_PORTS = {
|
|
|
110
110
|
def include_request_headers(
|
|
111
111
|
headers: list[tuple[bytes, bytes]],
|
|
112
112
|
*,
|
|
113
|
-
url:
|
|
113
|
+
url: URL,
|
|
114
114
|
content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes],
|
|
115
115
|
) -> list[tuple[bytes, bytes]]:
|
|
116
|
-
headers_set = {k.lower() for k,
|
|
116
|
+
headers_set = {k.lower() for k, _v in headers}
|
|
117
117
|
|
|
118
118
|
if b"host" not in headers_set:
|
|
119
119
|
default_port = DEFAULT_PORTS.get(url.scheme)
|
|
@@ -417,8 +417,7 @@ class Response:
|
|
|
417
417
|
if self._stream_consumed:
|
|
418
418
|
raise RuntimeError("Attempted to call 'for ... in response.iter_stream()' more than once.")
|
|
419
419
|
self._stream_consumed = True
|
|
420
|
-
|
|
421
|
-
yield chunk
|
|
420
|
+
yield from self.stream
|
|
422
421
|
|
|
423
422
|
def close(self) -> None:
|
|
424
423
|
if not isinstance(self.stream, typing.Iterable): # pragma: no cover
|
|
@@ -28,11 +28,7 @@ logger = logging.getLogger("httpcore2.http11")
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
# A subset of `h11.Event` types supported by `_send_event`
|
|
31
|
-
H11SendEvent =
|
|
32
|
-
h11.Request,
|
|
33
|
-
h11.Data,
|
|
34
|
-
h11.EndOfMessage,
|
|
35
|
-
]
|
|
31
|
+
H11SendEvent = h11.Request | h11.Data | h11.EndOfMessage
|
|
36
32
|
|
|
37
33
|
|
|
38
34
|
class HTTPConnectionState(enum.IntEnum):
|
|
@@ -14,11 +14,7 @@ import h2.exceptions
|
|
|
14
14
|
import h2.settings
|
|
15
15
|
|
|
16
16
|
from .._backends.base import NetworkStream
|
|
17
|
-
from .._exceptions import
|
|
18
|
-
ConnectionNotAvailable,
|
|
19
|
-
LocalProtocolError,
|
|
20
|
-
RemoteProtocolError,
|
|
21
|
-
)
|
|
17
|
+
from .._exceptions import ConnectionNotAvailable, LocalProtocolError, RemoteProtocolError
|
|
22
18
|
from .._models import Origin, Request, Response
|
|
23
19
|
from .._synchronization import Lock, Semaphore, ShieldCancellation
|
|
24
20
|
from .._trace import Trace
|
|
@@ -29,7 +25,7 @@ logger = logging.getLogger("httpcore2.http2")
|
|
|
29
25
|
|
|
30
26
|
|
|
31
27
|
def has_body_headers(request: Request) -> bool:
|
|
32
|
-
return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k,
|
|
28
|
+
return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, _v in request.headers)
|
|
33
29
|
|
|
34
30
|
|
|
35
31
|
class HTTPConnectionState(enum.IntEnum):
|
|
@@ -123,6 +119,7 @@ class HTTP2Connection(ConnectionInterface):
|
|
|
123
119
|
except h2.exceptions.NoAvailableStreamIDError: # pragma: no cover
|
|
124
120
|
self._used_all_stream_ids = True
|
|
125
121
|
self._request_count -= 1
|
|
122
|
+
self._max_streams_semaphore.release()
|
|
126
123
|
raise ConnectionNotAvailable()
|
|
127
124
|
|
|
128
125
|
try:
|
|
@@ -275,7 +272,7 @@ class HTTP2Connection(ConnectionInterface):
|
|
|
275
272
|
break
|
|
276
273
|
|
|
277
274
|
status_code = 200
|
|
278
|
-
headers = []
|
|
275
|
+
headers: list[tuple[bytes, bytes]] = []
|
|
279
276
|
assert event.headers is not None
|
|
280
277
|
for k, v in event.headers:
|
|
281
278
|
if k == b":status":
|
|
@@ -377,8 +374,8 @@ class HTTP2Connection(ConnectionInterface):
|
|
|
377
374
|
|
|
378
375
|
def _response_closed(self, stream_id: int) -> None:
|
|
379
376
|
self._max_streams_semaphore.release()
|
|
380
|
-
del self._events[stream_id]
|
|
381
377
|
with self._state_lock:
|
|
378
|
+
del self._events[stream_id]
|
|
382
379
|
if self._connection_terminated and not self._events:
|
|
383
380
|
self.close()
|
|
384
381
|
|
|
@@ -24,8 +24,8 @@ from .connection_pool import ConnectionPool
|
|
|
24
24
|
from .http11 import HTTP11Connection
|
|
25
25
|
from .interfaces import ConnectionInterface
|
|
26
26
|
|
|
27
|
-
ByteOrStr =
|
|
28
|
-
HeadersAsSequence = typing.Sequence[
|
|
27
|
+
ByteOrStr = bytes | str
|
|
28
|
+
HeadersAsSequence = typing.Sequence[tuple[ByteOrStr, ByteOrStr]]
|
|
29
29
|
HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
|
|
30
30
|
|
|
31
31
|
|
|
@@ -42,7 +42,7 @@ def merge_headers(
|
|
|
42
42
|
"""
|
|
43
43
|
default_headers = [] if default_headers is None else list(default_headers)
|
|
44
44
|
override_headers = [] if override_headers is None else list(override_headers)
|
|
45
|
-
has_override = set(key.lower() for key,
|
|
45
|
+
has_override = set(key.lower() for key, _value in override_headers)
|
|
46
46
|
default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override]
|
|
47
47
|
return default_headers + override_headers
|
|
48
48
|
|
|
@@ -278,7 +278,7 @@ class TunnelHTTPConnection(ConnectionInterface):
|
|
|
278
278
|
if connect_response.status < 200 or connect_response.status > 299:
|
|
279
279
|
reason_bytes = connect_response.extensions.get("reason_phrase", b"")
|
|
280
280
|
reason_str = reason_bytes.decode("ascii", errors="ignore")
|
|
281
|
-
msg = "
|
|
281
|
+
msg = f"{connect_response.status} {reason_str}"
|
|
282
282
|
self._connection.close()
|
|
283
283
|
raise ProxyError(msg)
|
|
284
284
|
|
|
@@ -26,8 +26,9 @@ maintainers = [
|
|
|
26
26
|
{ name = "Pydantic Services Inc.", email = "engineering@pydantic.dev" },
|
|
27
27
|
]
|
|
28
28
|
classifiers = [
|
|
29
|
-
"Development Status ::
|
|
29
|
+
"Development Status :: 5 - Production/Stable",
|
|
30
30
|
"Environment :: Web Environment",
|
|
31
|
+
"Framework :: AnyIO",
|
|
31
32
|
"Framework :: AsyncIO",
|
|
32
33
|
"Framework :: Trio",
|
|
33
34
|
"Intended Audience :: Developers",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|