httpx2 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.
- {httpx2-2.3.0 → httpx2-2.4.0}/CHANGELOG.md +16 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/PKG-INFO +16 -11
- {httpx2-2.3.0 → httpx2-2.4.0}/README.md +1 -1
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_api.py +2 -1
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_auth.py +2 -2
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_client.py +4 -4
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_config.py +9 -4
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_content.py +4 -11
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_decoders.py +10 -5
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_exceptions.py +12 -3
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_main.py +5 -5
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_models.py +13 -21
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_multipart.py +3 -4
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/asgi.py +1 -1
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/default.py +7 -11
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/wsgi.py +1 -2
- httpx2-2.4.0/httpx2/_types.py +86 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_urlparse.py +1 -1
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_urls.py +10 -4
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_utils.py +0 -4
- {httpx2-2.3.0 → httpx2-2.4.0}/pyproject.toml +5 -3
- httpx2-2.3.0/httpx2/_types.py +0 -110
- {httpx2-2.3.0 → httpx2-2.4.0}/.gitignore +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/LICENSE.md +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/__init__.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/__version__.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_status_codes.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/__init__.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/base.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/_transports/mock.py +0 -0
- {httpx2-2.3.0 → httpx2-2.4.0}/httpx2/py.typed +0 -0
|
@@ -4,6 +4,22 @@ 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
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add `HTTPXDeprecationWarning`, a `UserWarning` subclass shown by default so deprecations are visible without enabling warnings. ([#1029](https://github.com/pydantic/httpx2/pull/1029))
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* Limit the number of chained `Content-Encoding` decoders to 5. ([#1027](https://github.com/pydantic/httpx2/pull/1027))
|
|
16
|
+
* Allow version 15 of `rich` in the `cli` extra. ([#1015](https://github.com/pydantic/httpx2/pull/1015))
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
* Parse an empty `Digest` auth realm without crashing. ([#1023](https://github.com/pydantic/httpx2/pull/1023))
|
|
21
|
+
* Decode IDNA labels in non-leading host positions. ([#1018](https://github.com/pydantic/httpx2/pull/1018))
|
|
22
|
+
|
|
7
23
|
## 2.3.0 (June 1st, 2026)
|
|
8
24
|
|
|
9
25
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpx2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: The next generation HTTP client.
|
|
5
5
|
Project-URL: Changelog, https://github.com/pydantic/httpx2/blob/main/src/httpx2/CHANGELOG.md
|
|
6
6
|
Project-URL: Homepage, https://github.com/pydantic/httpx2
|
|
@@ -8,8 +8,9 @@ Project-URL: Source, https://github.com/pydantic/httpx2
|
|
|
8
8
|
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
|
-
Classifier: Development Status ::
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: AnyIO
|
|
13
14
|
Classifier: Framework :: AsyncIO
|
|
14
15
|
Classifier: Framework :: Trio
|
|
15
16
|
Classifier: Intended Audience :: Developers
|
|
@@ -25,16 +26,17 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
25
26
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
27
|
Requires-Python: >=3.10
|
|
27
28
|
Requires-Dist: anyio
|
|
28
|
-
Requires-Dist: httpcore2==2.
|
|
29
|
-
Requires-Dist: idna
|
|
29
|
+
Requires-Dist: httpcore2==2.4.0
|
|
30
|
+
Requires-Dist: idna>=3.18
|
|
30
31
|
Requires-Dist: truststore>=0.10
|
|
32
|
+
Requires-Dist: typing-extensions>=4.5.0; python_version < '3.13'
|
|
31
33
|
Provides-Extra: brotli
|
|
32
34
|
Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'
|
|
33
35
|
Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'
|
|
34
36
|
Provides-Extra: cli
|
|
35
37
|
Requires-Dist: click==8.*; extra == 'cli'
|
|
36
38
|
Requires-Dist: pygments==2.*; extra == 'cli'
|
|
37
|
-
Requires-Dist: rich<
|
|
39
|
+
Requires-Dist: rich<16,>=10; extra == 'cli'
|
|
38
40
|
Provides-Extra: http2
|
|
39
41
|
Requires-Dist: h2<5,>=3; extra == 'http2'
|
|
40
42
|
Provides-Extra: socks
|
|
@@ -52,7 +54,7 @@ Description-Content-Type: text/markdown
|
|
|
52
54
|
<a href="https://pypi.org/project/httpx2/"><img src="https://badge.fury.io/py/httpx2.svg" alt="Package version"></a>
|
|
53
55
|
</p>
|
|
54
56
|
|
|
55
|
-
HTTPX2 is a fully featured HTTP client library for Python
|
|
57
|
+
HTTPX2 is a fully featured HTTP client library for Python. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.
|
|
56
58
|
|
|
57
59
|
> [!NOTE]
|
|
58
60
|
> HTTPX2 is a continuation of the wonderful work started by [@lovelydinosaur](https://github.com/lovelydinosaur) and the broader HTTPX community. We're enormously grateful for everything that has gone into HTTPX over the years - it has been a foundational piece of the modern Python ecosystem, and this project would not exist without it.
|
|
@@ -184,16 +186,19 @@ inspiration around the lower-level networking details.
|
|
|
184
186
|
|
|
185
187
|
## Release Information
|
|
186
188
|
|
|
189
|
+
### Added
|
|
190
|
+
|
|
191
|
+
* Add `HTTPXDeprecationWarning`, a `UserWarning` subclass shown by default so deprecations are visible without enabling warnings. ([#1029](https://github.com/pydantic/httpx2/pull/1029))
|
|
192
|
+
|
|
187
193
|
### Changed
|
|
188
194
|
|
|
189
|
-
*
|
|
195
|
+
* Limit the number of chained `Content-Encoding` decoders to 5. ([#1027](https://github.com/pydantic/httpx2/pull/1027))
|
|
196
|
+
* Allow version 15 of `rich` in the `cli` extra. ([#1015](https://github.com/pydantic/httpx2/pull/1015))
|
|
190
197
|
|
|
191
198
|
### Fixed
|
|
192
199
|
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* Make the `zstd` import optional on Python 3.14. ([#1000](https://github.com/pydantic/httpx2/pull/1000))
|
|
196
|
-
* Store elapsed time on the stream wrapper to avoid reference cycles. ([#948](https://github.com/pydantic/httpx2/pull/948))
|
|
200
|
+
* Parse an empty `Digest` auth realm without crashing. ([#1023](https://github.com/pydantic/httpx2/pull/1023))
|
|
201
|
+
* Decode IDNA labels in non-leading host positions. ([#1018](https://github.com/pydantic/httpx2/pull/1018))
|
|
197
202
|
|
|
198
203
|
|
|
199
204
|
---
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="https://pypi.org/project/httpx2/"><img src="https://badge.fury.io/py/httpx2.svg" alt="Package version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
HTTPX2 is a fully featured HTTP client library for Python
|
|
10
|
+
HTTPX2 is a fully featured HTTP client library for Python. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.
|
|
11
11
|
|
|
12
12
|
> [!NOTE]
|
|
13
13
|
> HTTPX2 is a continuation of the wonderful work started by [@lovelydinosaur](https://github.com/lovelydinosaur) and the broader HTTPX community. We're enormously grateful for everything that has gone into HTTPX over the years - it has been a foundational piece of the modern Python ecosystem, and this project would not exist without it.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
|
+
from collections.abc import Generator
|
|
4
5
|
from contextlib import contextmanager
|
|
5
6
|
|
|
6
7
|
from ._client import Client
|
|
@@ -125,7 +126,7 @@ def stream(
|
|
|
125
126
|
follow_redirects: bool = False,
|
|
126
127
|
verify: ssl.SSLContext | str | bool = True,
|
|
127
128
|
trust_env: bool = True,
|
|
128
|
-
) ->
|
|
129
|
+
) -> Generator[Response]:
|
|
129
130
|
"""
|
|
130
131
|
Alternative to `httpx2.request()` that streams the response body
|
|
131
132
|
instead of loading it into memory at once.
|
|
@@ -10,7 +10,7 @@ from urllib.request import parse_http_list
|
|
|
10
10
|
|
|
11
11
|
from ._exceptions import ProtocolError
|
|
12
12
|
from ._models import Cookies, Request, Response
|
|
13
|
-
from ._utils import to_bytes, to_str
|
|
13
|
+
from ._utils import to_bytes, to_str
|
|
14
14
|
|
|
15
15
|
if typing.TYPE_CHECKING:
|
|
16
16
|
from hashlib import _Hash
|
|
@@ -225,7 +225,7 @@ class DigestAuth(Auth):
|
|
|
225
225
|
header_dict: dict[str, str] = {}
|
|
226
226
|
for field in parse_http_list(fields):
|
|
227
227
|
key, value = field.strip().split("=", 1)
|
|
228
|
-
header_dict[key] =
|
|
228
|
+
header_dict[key] = value.strip('"')
|
|
229
229
|
|
|
230
230
|
try:
|
|
231
231
|
realm = header_dict["realm"].encode()
|
|
@@ -6,6 +6,7 @@ import logging
|
|
|
6
6
|
import time
|
|
7
7
|
import typing
|
|
8
8
|
import warnings
|
|
9
|
+
from collections.abc import AsyncGenerator, Generator
|
|
9
10
|
from contextlib import asynccontextmanager, contextmanager
|
|
10
11
|
from types import TracebackType
|
|
11
12
|
|
|
@@ -143,8 +144,7 @@ class BoundSyncStream(SyncByteStream):
|
|
|
143
144
|
self.elapsed: datetime.timedelta | None = None
|
|
144
145
|
|
|
145
146
|
def __iter__(self) -> typing.Iterator[bytes]:
|
|
146
|
-
|
|
147
|
-
yield chunk
|
|
147
|
+
yield from self._stream
|
|
148
148
|
|
|
149
149
|
def close(self) -> None:
|
|
150
150
|
self.elapsed = datetime.timedelta(seconds=time.perf_counter() - self._start)
|
|
@@ -810,7 +810,7 @@ class Client(BaseClient):
|
|
|
810
810
|
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
|
811
811
|
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
|
812
812
|
extensions: RequestExtensions | None = None,
|
|
813
|
-
) ->
|
|
813
|
+
) -> Generator[Response]:
|
|
814
814
|
"""
|
|
815
815
|
Alternative to `httpx2.request()` that streams the response body
|
|
816
816
|
instead of loading it into memory at once.
|
|
@@ -1513,7 +1513,7 @@ class AsyncClient(BaseClient):
|
|
|
1513
1513
|
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
|
1514
1514
|
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
|
1515
1515
|
extensions: RequestExtensions | None = None,
|
|
1516
|
-
) ->
|
|
1516
|
+
) -> AsyncGenerator[Response]:
|
|
1517
1517
|
"""
|
|
1518
1518
|
Alternative to `httpx2.request()` that streams the response body
|
|
1519
1519
|
instead of loading it into memory at once.
|
|
@@ -89,6 +89,11 @@ class Timeout:
|
|
|
89
89
|
# 5s timeout elsewhere.
|
|
90
90
|
"""
|
|
91
91
|
|
|
92
|
+
connect: float | None
|
|
93
|
+
read: float | None
|
|
94
|
+
write: float | None
|
|
95
|
+
pool: float | None
|
|
96
|
+
|
|
92
97
|
def __init__(
|
|
93
98
|
self,
|
|
94
99
|
timeout: TimeoutTypes | UnsetType = UNSET,
|
|
@@ -104,10 +109,10 @@ class Timeout:
|
|
|
104
109
|
assert read is UNSET
|
|
105
110
|
assert write is UNSET
|
|
106
111
|
assert pool is UNSET
|
|
107
|
-
self.connect = timeout.connect
|
|
108
|
-
self.read = timeout.read
|
|
109
|
-
self.write = timeout.write
|
|
110
|
-
self.pool = timeout.pool
|
|
112
|
+
self.connect = timeout.connect
|
|
113
|
+
self.read = timeout.read
|
|
114
|
+
self.write = timeout.write
|
|
115
|
+
self.pool = timeout.pool
|
|
111
116
|
elif isinstance(timeout, tuple):
|
|
112
117
|
# Passed as a tuple.
|
|
113
118
|
self.connect = timeout[0]
|
|
@@ -2,14 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import warnings
|
|
5
|
+
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator, Mapping
|
|
5
6
|
from json import dumps as json_dumps
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
8
|
-
AsyncIterable,
|
|
9
|
-
AsyncIterator,
|
|
10
|
-
Iterable,
|
|
11
|
-
Iterator,
|
|
12
|
-
Mapping,
|
|
13
9
|
)
|
|
14
10
|
from urllib.parse import urlencode
|
|
15
11
|
|
|
@@ -60,8 +56,7 @@ class IteratorByteStream(SyncByteStream):
|
|
|
60
56
|
chunk = self._stream.read(self.CHUNK_SIZE)
|
|
61
57
|
else:
|
|
62
58
|
# Otherwise iterate.
|
|
63
|
-
|
|
64
|
-
yield part
|
|
59
|
+
yield from self._stream
|
|
65
60
|
|
|
66
61
|
|
|
67
62
|
class AsyncIteratorByteStream(AsyncByteStream):
|
|
@@ -133,10 +128,8 @@ def encode_content(
|
|
|
133
128
|
raise TypeError(f"Unexpected type for 'content', {type(content)!r}")
|
|
134
129
|
|
|
135
130
|
|
|
136
|
-
def encode_urlencoded_data(
|
|
137
|
-
|
|
138
|
-
) -> tuple[dict[str, str], ByteStream]:
|
|
139
|
-
plain_data = []
|
|
131
|
+
def encode_urlencoded_data(data: RequestData) -> tuple[dict[str, str], ByteStream]:
|
|
132
|
+
plain_data: list[tuple[str, str]] = []
|
|
140
133
|
for key, value in data.items():
|
|
141
134
|
if isinstance(value, (list, tuple)):
|
|
142
135
|
plain_data.extend([(key, primitive_value_to_str(item)) for item in value])
|
|
@@ -39,7 +39,7 @@ if typing.TYPE_CHECKING:
|
|
|
39
39
|
|
|
40
40
|
ZstdDecompressor = functools.partial(_ZstdDecompressor().decompressobj)
|
|
41
41
|
|
|
42
|
-
_zstandard_installed: bool
|
|
42
|
+
_zstandard_installed: bool = False
|
|
43
43
|
else: # pragma: no cover
|
|
44
44
|
_zstandard_installed = False
|
|
45
45
|
try:
|
|
@@ -230,13 +230,18 @@ class MultiDecoder(ContentDecoder):
|
|
|
230
230
|
Handle the case where multiple encodings have been applied.
|
|
231
231
|
"""
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
max_decode_links: typing.ClassVar[int] = 5
|
|
234
|
+
|
|
235
|
+
def __init__(self, encodings: typing.Sequence[str]) -> None:
|
|
234
236
|
"""
|
|
235
|
-
'
|
|
237
|
+
'encodings' should be the content codings in the order in which
|
|
236
238
|
each was applied.
|
|
237
239
|
"""
|
|
240
|
+
codings = [encoding for encoding in encodings if encoding in SUPPORTED_DECODERS]
|
|
241
|
+
if len(codings) > self.max_decode_links:
|
|
242
|
+
raise DecodingError(f"Cannot apply more than {self.max_decode_links} content encodings.")
|
|
238
243
|
# Note that we reverse the order for decoding.
|
|
239
|
-
self.children =
|
|
244
|
+
self.children: list[ContentDecoder] = [SUPPORTED_DECODERS[coding]() for coding in reversed(codings)]
|
|
240
245
|
|
|
241
246
|
def decode(self, data: bytes) -> bytes:
|
|
242
247
|
for child in self.children:
|
|
@@ -397,7 +402,7 @@ class LineDecoder:
|
|
|
397
402
|
return lines
|
|
398
403
|
|
|
399
404
|
|
|
400
|
-
SUPPORTED_DECODERS = {
|
|
405
|
+
SUPPORTED_DECODERS: dict[str, type[ContentDecoder]] = {
|
|
401
406
|
"identity": IdentityDecoder,
|
|
402
407
|
"gzip": GZipDecoder,
|
|
403
408
|
"deflate": DeflateDecoder,
|
|
@@ -35,6 +35,7 @@ from __future__ import annotations
|
|
|
35
35
|
|
|
36
36
|
import contextlib
|
|
37
37
|
import typing
|
|
38
|
+
from collections.abc import Generator
|
|
38
39
|
|
|
39
40
|
if typing.TYPE_CHECKING:
|
|
40
41
|
from ._models import Request, Response # pragma: no cover
|
|
@@ -71,6 +72,16 @@ __all__ = [
|
|
|
71
72
|
]
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
class HTTPXDeprecationWarning(UserWarning):
|
|
76
|
+
"""A custom deprecation warning for HTTPX.
|
|
77
|
+
|
|
78
|
+
Unlike the built-in `DeprecationWarning`, this inherits from `UserWarning` to ensure it is visible by default,
|
|
79
|
+
helping users discover deprecated features without needing to enable warnings explicitly.
|
|
80
|
+
|
|
81
|
+
Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
74
85
|
class HTTPError(Exception):
|
|
75
86
|
"""
|
|
76
87
|
Base class for `RequestError` and `HTTPStatusError`.
|
|
@@ -356,9 +367,7 @@ class RequestNotRead(StreamError):
|
|
|
356
367
|
|
|
357
368
|
|
|
358
369
|
@contextlib.contextmanager
|
|
359
|
-
def request_context(
|
|
360
|
-
request: Request | None = None,
|
|
361
|
-
) -> typing.Iterator[None]:
|
|
370
|
+
def request_context(request: Request | None = None) -> Generator[None]:
|
|
362
371
|
"""
|
|
363
372
|
A context manager that can be used to attach the given request context
|
|
364
373
|
to any `RequestError` exceptions that are raised within the block.
|
|
@@ -168,13 +168,13 @@ def print_response(response: Response) -> None:
|
|
|
168
168
|
console.print(f"<{len(response.content)} bytes of binary data>")
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
_PCTRTT =
|
|
172
|
-
_PCTRTTT =
|
|
173
|
-
_PeerCertRetDictType =
|
|
171
|
+
_PCTRTT = tuple[tuple[str, str], ...]
|
|
172
|
+
_PCTRTTT = tuple[_PCTRTT, ...]
|
|
173
|
+
_PeerCertRetDictType = dict[str, str | _PCTRTTT | _PCTRTT]
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover
|
|
177
|
-
lines = []
|
|
177
|
+
lines: list[str] = []
|
|
178
178
|
for key, value in cert.items():
|
|
179
179
|
if isinstance(value, (list, tuple)):
|
|
180
180
|
lines.append(f"* {key}:")
|
|
@@ -462,7 +462,7 @@ def main(
|
|
|
462
462
|
params=list(params),
|
|
463
463
|
content=content,
|
|
464
464
|
data=dict(data),
|
|
465
|
-
files=files, # type: ignore
|
|
465
|
+
files=files, # type: ignore[arg-type]
|
|
466
466
|
json=json,
|
|
467
467
|
headers=headers,
|
|
468
468
|
cookies=dict(cookies),
|
|
@@ -12,7 +12,6 @@ from http.cookiejar import Cookie, CookieJar
|
|
|
12
12
|
|
|
13
13
|
from ._content import ByteStream, UnattachedStream, encode_request, encode_response
|
|
14
14
|
from ._decoders import (
|
|
15
|
-
SUPPORTED_DECODERS,
|
|
16
15
|
ByteChunker,
|
|
17
16
|
ContentDecoder,
|
|
18
17
|
IdentityDecoder,
|
|
@@ -146,7 +145,7 @@ class Headers(typing.MutableMapping[str, str]):
|
|
|
146
145
|
headers: HeaderTypes | None = None,
|
|
147
146
|
encoding: str | None = None,
|
|
148
147
|
) -> None:
|
|
149
|
-
self._list
|
|
148
|
+
self._list: list[tuple[bytes, bytes, bytes]] = []
|
|
150
149
|
|
|
151
150
|
if isinstance(headers, Headers):
|
|
152
151
|
self._list = list(headers._list)
|
|
@@ -200,7 +199,7 @@ class Headers(typing.MutableMapping[str, str]):
|
|
|
200
199
|
return [(raw_key, value) for raw_key, _, value in self._list]
|
|
201
200
|
|
|
202
201
|
def keys(self) -> typing.KeysView[str]:
|
|
203
|
-
return {key.decode(self.encoding): None for _, key,
|
|
202
|
+
return {key.decode(self.encoding): None for _, key, _value in self._list}.keys()
|
|
204
203
|
|
|
205
204
|
def values(self) -> typing.ValuesView[str]:
|
|
206
205
|
values_dict: dict[str, str] = {}
|
|
@@ -263,7 +262,7 @@ class Headers(typing.MutableMapping[str, str]):
|
|
|
263
262
|
if not split_commas:
|
|
264
263
|
return values
|
|
265
264
|
|
|
266
|
-
split_values = []
|
|
265
|
+
split_values: list[str] = []
|
|
267
266
|
for value in values:
|
|
268
267
|
split_values.extend([item.strip() for item in value.split(",")])
|
|
269
268
|
return split_values
|
|
@@ -679,20 +678,13 @@ class Response:
|
|
|
679
678
|
content, depending on the Content-Encoding used in the response.
|
|
680
679
|
"""
|
|
681
680
|
if not hasattr(self, "_decoder"):
|
|
682
|
-
decoders: list[ContentDecoder] = []
|
|
683
681
|
values = self.headers.get_list("content-encoding", split_commas=True)
|
|
684
|
-
for value in values
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
continue
|
|
691
|
-
|
|
692
|
-
if len(decoders) == 1:
|
|
693
|
-
self._decoder = decoders[0]
|
|
694
|
-
elif len(decoders) > 1:
|
|
695
|
-
self._decoder = MultiDecoder(children=decoders)
|
|
682
|
+
encodings = [value.strip().lower() for value in values]
|
|
683
|
+
decoder = MultiDecoder([encoding for encoding in encodings if encoding != "identity"])
|
|
684
|
+
if len(decoder.children) == 1:
|
|
685
|
+
self._decoder = decoder.children[0]
|
|
686
|
+
elif decoder.children:
|
|
687
|
+
self._decoder = decoder
|
|
696
688
|
else:
|
|
697
689
|
self._decoder = IdentityDecoder()
|
|
698
690
|
|
|
@@ -1161,7 +1153,7 @@ class Cookies(typing.MutableMapping[str, str]):
|
|
|
1161
1153
|
Delete all cookies. Optionally include a domain and path in
|
|
1162
1154
|
order to only delete a subset of all the cookies.
|
|
1163
1155
|
"""
|
|
1164
|
-
args = []
|
|
1156
|
+
args: list[str] = []
|
|
1165
1157
|
if domain is not None:
|
|
1166
1158
|
args.append(domain)
|
|
1167
1159
|
if path is not None:
|
|
@@ -1218,9 +1210,9 @@ class Cookies(typing.MutableMapping[str, str]):
|
|
|
1218
1210
|
)
|
|
1219
1211
|
self.request = request
|
|
1220
1212
|
|
|
1221
|
-
def add_unredirected_header(self, key: str,
|
|
1222
|
-
super().add_unredirected_header(key,
|
|
1223
|
-
self.request.headers[key] =
|
|
1213
|
+
def add_unredirected_header(self, key: str, val: str) -> None:
|
|
1214
|
+
super().add_unredirected_header(key, val)
|
|
1215
|
+
self.request.headers[key] = val
|
|
1224
1216
|
|
|
1225
1217
|
class _CookieCompatResponse:
|
|
1226
1218
|
"""
|
|
@@ -22,7 +22,7 @@ from ._utils import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"}
|
|
25
|
-
_HTML5_FORM_ENCODING_REPLACEMENTS.update({chr(c): "%{:02X}"
|
|
25
|
+
_HTML5_FORM_ENCODING_REPLACEMENTS.update({chr(c): f"%{c:02X}" for c in range(0x1F + 1) if c != 0x1B})
|
|
26
26
|
_HTML5_FORM_ENCODING_RE = re.compile(r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()]))
|
|
27
27
|
|
|
28
28
|
|
|
@@ -219,7 +219,7 @@ class MultipartStream(SyncByteStream, AsyncByteStream):
|
|
|
219
219
|
boundary = os.urandom(16).hex().encode("ascii")
|
|
220
220
|
|
|
221
221
|
self.boundary = boundary
|
|
222
|
-
self.content_type = "multipart/form-data; boundary
|
|
222
|
+
self.content_type = f"multipart/form-data; boundary={boundary.decode('ascii')}"
|
|
223
223
|
self.fields = list(self._iter_fields(data, files))
|
|
224
224
|
|
|
225
225
|
def _iter_fields(self, data: RequestData, files: RequestFiles) -> typing.Iterator[FileField | DataField]:
|
|
@@ -271,8 +271,7 @@ class MultipartStream(SyncByteStream, AsyncByteStream):
|
|
|
271
271
|
return {"Content-Length": str(content_length), "Content-Type": content_type}
|
|
272
272
|
|
|
273
273
|
def __iter__(self) -> typing.Iterator[bytes]:
|
|
274
|
-
|
|
275
|
-
yield chunk
|
|
274
|
+
yield from self.iter_chunks()
|
|
276
275
|
|
|
277
276
|
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
|
|
278
277
|
for chunk in self.iter_chunks():
|
|
@@ -28,12 +28,13 @@ from __future__ import annotations
|
|
|
28
28
|
|
|
29
29
|
import contextlib
|
|
30
30
|
import typing
|
|
31
|
+
from collections.abc import Generator
|
|
31
32
|
from types import TracebackType
|
|
32
33
|
|
|
33
|
-
if typing.TYPE_CHECKING:
|
|
34
|
-
import ssl
|
|
34
|
+
if typing.TYPE_CHECKING: # pragma: no cover
|
|
35
|
+
import ssl
|
|
35
36
|
|
|
36
|
-
import httpx2
|
|
37
|
+
import httpx2
|
|
37
38
|
|
|
38
39
|
from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
|
|
39
40
|
from .._exceptions import (
|
|
@@ -60,11 +61,7 @@ from .base import AsyncBaseTransport, BaseTransport
|
|
|
60
61
|
T = typing.TypeVar("T", bound="HTTPTransport")
|
|
61
62
|
A = typing.TypeVar("A", bound="AsyncHTTPTransport")
|
|
62
63
|
|
|
63
|
-
SOCKET_OPTION =
|
|
64
|
-
typing.Tuple[int, int, int],
|
|
65
|
-
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
|
66
|
-
typing.Tuple[int, int, None, int],
|
|
67
|
-
]
|
|
64
|
+
SOCKET_OPTION = tuple[int, int, int] | tuple[int, int, bytes | bytearray] | tuple[int, int, None, int]
|
|
68
65
|
|
|
69
66
|
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
|
|
70
67
|
|
|
@@ -93,7 +90,7 @@ def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx2.HTTPError]]
|
|
|
93
90
|
|
|
94
91
|
|
|
95
92
|
@contextlib.contextmanager
|
|
96
|
-
def map_httpcore_exceptions() ->
|
|
93
|
+
def map_httpcore_exceptions() -> Generator[None]:
|
|
97
94
|
global HTTPCORE_EXC_MAP
|
|
98
95
|
if len(HTTPCORE_EXC_MAP) == 0:
|
|
99
96
|
HTTPCORE_EXC_MAP = _load_httpcore_exceptions()
|
|
@@ -124,8 +121,7 @@ class ResponseStream(SyncByteStream):
|
|
|
124
121
|
|
|
125
122
|
def __iter__(self) -> typing.Iterator[bytes]:
|
|
126
123
|
with map_httpcore_exceptions():
|
|
127
|
-
|
|
128
|
-
yield part
|
|
124
|
+
yield from self._httpcore_stream
|
|
129
125
|
|
|
130
126
|
def close(self) -> None:
|
|
131
127
|
if hasattr(self._httpcore_stream, "close"):
|
|
@@ -33,8 +33,7 @@ class WSGIByteStream(SyncByteStream):
|
|
|
33
33
|
self._result = _skip_leading_empty_chunks(result)
|
|
34
34
|
|
|
35
35
|
def __iter__(self) -> typing.Iterator[bytes]:
|
|
36
|
-
|
|
37
|
-
yield part
|
|
36
|
+
yield from self._result
|
|
38
37
|
|
|
39
38
|
def close(self) -> None:
|
|
40
39
|
if self._close is not None:
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type definitions for type checking purposes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterable, AsyncIterator, Callable, Iterable, Iterator, Mapping, Sequence
|
|
6
|
+
from http.cookiejar import CookieJar
|
|
7
|
+
from typing import IO, TYPE_CHECKING, Any, Union
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ._auth import Auth # noqa: F401
|
|
11
|
+
from ._config import Proxy, Timeout # noqa: F401
|
|
12
|
+
from ._models import Cookies, Headers, Request # noqa: F401
|
|
13
|
+
from ._urls import URL, QueryParams # noqa: F401
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PrimitiveData = str | int | float | bool | None
|
|
17
|
+
|
|
18
|
+
URLTypes = Union["URL", str]
|
|
19
|
+
|
|
20
|
+
QueryParamTypes = Union[
|
|
21
|
+
"QueryParams",
|
|
22
|
+
Mapping[str, PrimitiveData | Sequence[PrimitiveData]],
|
|
23
|
+
list[tuple[str, PrimitiveData]],
|
|
24
|
+
tuple[tuple[str, PrimitiveData], ...],
|
|
25
|
+
str,
|
|
26
|
+
bytes,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
HeaderTypes = Union[
|
|
30
|
+
"Headers",
|
|
31
|
+
Mapping[str, str],
|
|
32
|
+
Mapping[bytes, bytes],
|
|
33
|
+
Sequence[tuple[str, str]],
|
|
34
|
+
Sequence[tuple[bytes, bytes]],
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
CookieTypes = Union["Cookies", CookieJar, dict[str, str], list[tuple[str, str]]]
|
|
38
|
+
|
|
39
|
+
TimeoutTypes = Union[float | None, tuple[float | None, float | None, float | None, float | None], "Timeout"]
|
|
40
|
+
ProxyTypes = Union["URL", str, "Proxy"]
|
|
41
|
+
CertTypes = str | tuple[str, str] | tuple[str, str, str]
|
|
42
|
+
|
|
43
|
+
AuthTypes = Union[tuple[str | bytes, str | bytes], Callable[["Request"], "Request"], "Auth"]
|
|
44
|
+
|
|
45
|
+
RequestContent = str | bytes | Iterable[bytes] | AsyncIterable[bytes]
|
|
46
|
+
ResponseContent = str | bytes | Iterable[bytes] | AsyncIterable[bytes]
|
|
47
|
+
ResponseExtensions = Mapping[str, Any]
|
|
48
|
+
|
|
49
|
+
RequestData = Mapping[str, Any]
|
|
50
|
+
|
|
51
|
+
FileContent = IO[bytes] | bytes | str
|
|
52
|
+
FileTypes = (
|
|
53
|
+
# # file (or bytes)
|
|
54
|
+
FileContent
|
|
55
|
+
# # (filename, file (or bytes))
|
|
56
|
+
| tuple[str | None, FileContent]
|
|
57
|
+
# # (filename, file (or bytes), content_type)
|
|
58
|
+
| tuple[str | None, FileContent, str | None]
|
|
59
|
+
| tuple[str | None, FileContent, str | None, Mapping[str, str]]
|
|
60
|
+
)
|
|
61
|
+
RequestFiles = Mapping[str, FileTypes] | Sequence[tuple[str, FileTypes]]
|
|
62
|
+
|
|
63
|
+
RequestExtensions = Mapping[str, Any]
|
|
64
|
+
|
|
65
|
+
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SyncByteStream:
|
|
69
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
70
|
+
raise NotImplementedError("The '__iter__' method must be implemented.") # pragma: no cover
|
|
71
|
+
yield b"" # pragma: no cover
|
|
72
|
+
|
|
73
|
+
def close(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Subclasses can override this method to release any network resources
|
|
76
|
+
after a request/response cycle is complete.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AsyncByteStream:
|
|
81
|
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
82
|
+
raise NotImplementedError("The '__aiter__' method must be implemented.") # pragma: no cover
|
|
83
|
+
yield b"" # pragma: no cover
|
|
84
|
+
|
|
85
|
+
async def aclose(self) -> None:
|
|
86
|
+
pass
|
|
@@ -480,7 +480,7 @@ def quote(string: str, safe: str) -> str:
|
|
|
480
480
|
need to be escaped. Unreserved characters are always treated as safe.
|
|
481
481
|
See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3
|
|
482
482
|
"""
|
|
483
|
-
parts = []
|
|
483
|
+
parts: list[str] = []
|
|
484
484
|
current_position = 0
|
|
485
485
|
for match in re.finditer(PERCENT_ENCODED_REGEX, string):
|
|
486
486
|
start_position, end_position = match.start(), match.end()
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import sys
|
|
3
4
|
import typing
|
|
4
5
|
from urllib.parse import parse_qs, unquote, urlencode
|
|
5
6
|
|
|
6
7
|
import idna
|
|
7
8
|
|
|
9
|
+
if sys.version_info >= (3, 13):
|
|
10
|
+
from warnings import deprecated # pragma: no cover
|
|
11
|
+
else:
|
|
12
|
+
from typing_extensions import deprecated # pragma: no cover
|
|
13
|
+
|
|
14
|
+
from ._exceptions import HTTPXDeprecationWarning
|
|
8
15
|
from ._types import QueryParamTypes
|
|
9
16
|
from ._urlparse import urlparse
|
|
10
17
|
from ._utils import primitive_value_to_str
|
|
@@ -184,8 +191,8 @@ class URL:
|
|
|
184
191
|
"""
|
|
185
192
|
host: str = self._uri_reference.host
|
|
186
193
|
|
|
187
|
-
if
|
|
188
|
-
host = idna.decode(host)
|
|
194
|
+
if "xn--" in host:
|
|
195
|
+
host = idna.decode(host, display=True)
|
|
189
196
|
|
|
190
197
|
return host
|
|
191
198
|
|
|
@@ -398,11 +405,10 @@ class URL:
|
|
|
398
405
|
return f"{self.__class__.__name__}({url!r})"
|
|
399
406
|
|
|
400
407
|
@property
|
|
408
|
+
@deprecated("URL.raw is deprecated.", category=HTTPXDeprecationWarning)
|
|
401
409
|
def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: no cover
|
|
402
410
|
import collections
|
|
403
|
-
import warnings
|
|
404
411
|
|
|
405
|
-
warnings.warn("URL.raw is deprecated.")
|
|
406
412
|
RawURL = collections.namedtuple("RawURL", ["raw_scheme", "raw_host", "port", "raw_path"])
|
|
407
413
|
return RawURL(
|
|
408
414
|
raw_scheme=self.raw_scheme,
|
|
@@ -86,10 +86,6 @@ def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr:
|
|
|
86
86
|
return value if isinstance(match_type_of, str) else value.encode()
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
def unquote(value: str) -> str:
|
|
90
|
-
return value[1:-1] if value[0] == value[-1] == '"' else value
|
|
91
|
-
|
|
92
|
-
|
|
93
89
|
def peek_filelike_length(stream: typing.Any) -> int | None:
|
|
94
90
|
"""
|
|
95
91
|
Given a file-like stream object, return its length in number of bytes
|
|
@@ -23,8 +23,9 @@ maintainers = [
|
|
|
23
23
|
{ name = "Pydantic Services Inc.", email = "engineering@pydantic.dev" }
|
|
24
24
|
]
|
|
25
25
|
classifiers = [
|
|
26
|
-
"Development Status ::
|
|
26
|
+
"Development Status :: 5 - Production/Stable",
|
|
27
27
|
"Environment :: Web Environment",
|
|
28
|
+
"Framework :: AnyIO",
|
|
28
29
|
"Framework :: AsyncIO",
|
|
29
30
|
"Framework :: Trio",
|
|
30
31
|
"Intended Audience :: Developers",
|
|
@@ -46,7 +47,8 @@ dependencies = [
|
|
|
46
47
|
"truststore>=0.10",
|
|
47
48
|
"httpcore2=={{ version }}",
|
|
48
49
|
"anyio",
|
|
49
|
-
"idna",
|
|
50
|
+
"idna>=3.18",
|
|
51
|
+
"typing_extensions>=4.5.0; python_version < '3.13'",
|
|
50
52
|
]
|
|
51
53
|
|
|
52
54
|
[project.optional-dependencies]
|
|
@@ -57,7 +59,7 @@ brotli = [
|
|
|
57
59
|
cli = [
|
|
58
60
|
"click==8.*",
|
|
59
61
|
"pygments==2.*",
|
|
60
|
-
"rich>=10,<
|
|
62
|
+
"rich>=10,<16",
|
|
61
63
|
]
|
|
62
64
|
http2 = [
|
|
63
65
|
"h2>=3,<5",
|
httpx2-2.3.0/httpx2/_types.py
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Type definitions for type checking purposes.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from http.cookiejar import CookieJar
|
|
6
|
-
from typing import (
|
|
7
|
-
IO,
|
|
8
|
-
TYPE_CHECKING,
|
|
9
|
-
Any,
|
|
10
|
-
AsyncIterable,
|
|
11
|
-
AsyncIterator,
|
|
12
|
-
Callable,
|
|
13
|
-
Dict,
|
|
14
|
-
Iterable,
|
|
15
|
-
Iterator,
|
|
16
|
-
List,
|
|
17
|
-
Mapping,
|
|
18
|
-
Optional,
|
|
19
|
-
Sequence,
|
|
20
|
-
Tuple,
|
|
21
|
-
Union,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
if TYPE_CHECKING:
|
|
25
|
-
from ._auth import Auth # noqa: F401
|
|
26
|
-
from ._config import Proxy, Timeout # noqa: F401
|
|
27
|
-
from ._models import Cookies, Headers, Request # noqa: F401
|
|
28
|
-
from ._urls import URL, QueryParams # noqa: F401
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
PrimitiveData = Optional[Union[str, int, float, bool]]
|
|
32
|
-
|
|
33
|
-
URLTypes = Union["URL", str]
|
|
34
|
-
|
|
35
|
-
QueryParamTypes = Union[
|
|
36
|
-
"QueryParams",
|
|
37
|
-
Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]],
|
|
38
|
-
List[Tuple[str, PrimitiveData]],
|
|
39
|
-
Tuple[Tuple[str, PrimitiveData], ...],
|
|
40
|
-
str,
|
|
41
|
-
bytes,
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
HeaderTypes = Union[
|
|
45
|
-
"Headers",
|
|
46
|
-
Mapping[str, str],
|
|
47
|
-
Mapping[bytes, bytes],
|
|
48
|
-
Sequence[Tuple[str, str]],
|
|
49
|
-
Sequence[Tuple[bytes, bytes]],
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
CookieTypes = Union["Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]]
|
|
53
|
-
|
|
54
|
-
TimeoutTypes = Union[
|
|
55
|
-
Optional[float],
|
|
56
|
-
Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
|
|
57
|
-
"Timeout",
|
|
58
|
-
]
|
|
59
|
-
ProxyTypes = Union["URL", str, "Proxy"]
|
|
60
|
-
CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]
|
|
61
|
-
|
|
62
|
-
AuthTypes = Union[
|
|
63
|
-
Tuple[Union[str, bytes], Union[str, bytes]],
|
|
64
|
-
Callable[["Request"], "Request"],
|
|
65
|
-
"Auth",
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
|
69
|
-
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
|
70
|
-
ResponseExtensions = Mapping[str, Any]
|
|
71
|
-
|
|
72
|
-
RequestData = Mapping[str, Any]
|
|
73
|
-
|
|
74
|
-
FileContent = Union[IO[bytes], bytes, str]
|
|
75
|
-
FileTypes = Union[
|
|
76
|
-
# file (or bytes)
|
|
77
|
-
FileContent,
|
|
78
|
-
# (filename, file (or bytes))
|
|
79
|
-
Tuple[Optional[str], FileContent],
|
|
80
|
-
# (filename, file (or bytes), content_type)
|
|
81
|
-
Tuple[Optional[str], FileContent, Optional[str]],
|
|
82
|
-
# (filename, file (or bytes), content_type, headers)
|
|
83
|
-
Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
|
|
84
|
-
]
|
|
85
|
-
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
|
|
86
|
-
|
|
87
|
-
RequestExtensions = Mapping[str, Any]
|
|
88
|
-
|
|
89
|
-
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class SyncByteStream:
|
|
93
|
-
def __iter__(self) -> Iterator[bytes]:
|
|
94
|
-
raise NotImplementedError("The '__iter__' method must be implemented.") # pragma: no cover
|
|
95
|
-
yield b"" # pragma: no cover
|
|
96
|
-
|
|
97
|
-
def close(self) -> None:
|
|
98
|
-
"""
|
|
99
|
-
Subclasses can override this method to release any network resources
|
|
100
|
-
after a request/response cycle is complete.
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class AsyncByteStream:
|
|
105
|
-
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
106
|
-
raise NotImplementedError("The '__aiter__' method must be implemented.") # pragma: no cover
|
|
107
|
-
yield b"" # pragma: no cover
|
|
108
|
-
|
|
109
|
-
async def aclose(self) -> None:
|
|
110
|
-
pass
|
|
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
|