anonymous-CAP commited on
Commit
76d6ddf
·
verified ·
1 Parent(s): a40a8f5

Upload folder using huggingface_hub (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/INSTALLER +1 -0
  3. venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/METADATA +625 -0
  4. venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/RECORD +68 -0
  5. venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/WHEEL +4 -0
  6. venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md +27 -0
  7. venv/lib/python3.12/site-packages/httpcore/_async/http2.py +592 -0
  8. venv/lib/python3.12/site-packages/httpcore/_async/http_proxy.py +367 -0
  9. venv/lib/python3.12/site-packages/httpcore/_async/interfaces.py +137 -0
  10. venv/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py +341 -0
  11. venv/lib/python3.12/site-packages/httpcore/_backends/__init__.py +0 -0
  12. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc +0 -0
  13. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc +0 -0
  14. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc +0 -0
  15. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc +0 -0
  16. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc +0 -0
  17. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc +0 -0
  18. venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc +0 -0
  19. venv/lib/python3.12/site-packages/httpcore/_backends/anyio.py +146 -0
  20. venv/lib/python3.12/site-packages/httpcore/_backends/auto.py +52 -0
  21. venv/lib/python3.12/site-packages/httpcore/_backends/base.py +101 -0
  22. venv/lib/python3.12/site-packages/httpcore/_backends/mock.py +143 -0
  23. venv/lib/python3.12/site-packages/httpcore/_backends/sync.py +241 -0
  24. venv/lib/python3.12/site-packages/httpcore/_backends/trio.py +159 -0
  25. venv/lib/python3.12/site-packages/httpcore/_exceptions.py +81 -0
  26. venv/lib/python3.12/site-packages/httpcore/_models.py +516 -0
  27. venv/lib/python3.12/site-packages/httpcore/_ssl.py +9 -0
  28. venv/lib/python3.12/site-packages/httpcore/_sync/__init__.py +39 -0
  29. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc +0 -0
  30. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc +0 -0
  31. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc +0 -0
  32. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc +0 -0
  33. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc +0 -0
  34. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc +0 -0
  35. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc +0 -0
  36. venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc +0 -0
  37. venv/lib/python3.12/site-packages/httpcore/_sync/connection.py +222 -0
  38. venv/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py +420 -0
  39. venv/lib/python3.12/site-packages/httpcore/_sync/http11.py +379 -0
  40. venv/lib/python3.12/site-packages/httpcore/_sync/http2.py +592 -0
  41. venv/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py +367 -0
  42. venv/lib/python3.12/site-packages/httpcore/_sync/interfaces.py +137 -0
  43. venv/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py +341 -0
  44. venv/lib/python3.12/site-packages/httpcore/_synchronization.py +318 -0
  45. venv/lib/python3.12/site-packages/httpcore/_trace.py +107 -0
  46. venv/lib/python3.12/site-packages/httpcore/_utils.py +37 -0
  47. venv/lib/python3.12/site-packages/httpcore/py.typed +0 -0
  48. venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER +1 -0
  49. venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA +203 -0
  50. venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD +54 -0
.gitattributes CHANGED
@@ -59,3 +59,6 @@ venv/lib/python3.12/site-packages/cuda/bindings/nvrtc.cpython-312-x86_64-linux-g
59
  venv/lib/python3.12/site-packages/cuda/bindings/nvvm.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
60
  venv/lib/python3.12/site-packages/cuda/bindings/runtime.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
61
  venv/lib/python3.12/site-packages/hf_xet/hf_xet.abi3.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
59
  venv/lib/python3.12/site-packages/cuda/bindings/nvvm.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
60
  venv/lib/python3.12/site-packages/cuda/bindings/runtime.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
61
  venv/lib/python3.12/site-packages/hf_xet/hf_xet.abi3.so filter=lfs diff=lfs merge=lfs -text
62
+ venv/lib/python3.12/site-packages/huggingface_hub/__pycache__/hf_api.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
63
+ venv/lib/python3.12/site-packages/huggingface_hub/inference/__pycache__/_client.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
64
+ venv/lib/python3.12/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/METADATA ADDED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: httpcore
3
+ Version: 1.0.9
4
+ Summary: A minimal low-level HTTP client.
5
+ Project-URL: Documentation, https://www.encode.io/httpcore
6
+ Project-URL: Homepage, https://www.encode.io/httpcore/
7
+ Project-URL: Source, https://github.com/encode/httpcore
8
+ Author-email: Tom Christie <tom@tomchristie.com>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE.md
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Framework :: Trio
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Requires-Python: >=3.8
27
+ Requires-Dist: certifi
28
+ Requires-Dist: h11>=0.16
29
+ Provides-Extra: asyncio
30
+ Requires-Dist: anyio<5.0,>=4.0; extra == 'asyncio'
31
+ Provides-Extra: http2
32
+ Requires-Dist: h2<5,>=3; extra == 'http2'
33
+ Provides-Extra: socks
34
+ Requires-Dist: socksio==1.*; extra == 'socks'
35
+ Provides-Extra: trio
36
+ Requires-Dist: trio<1.0,>=0.22.0; extra == 'trio'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # HTTP Core
40
+
41
+ [![Test Suite](https://github.com/encode/httpcore/workflows/Test%20Suite/badge.svg)](https://github.com/encode/httpcore/actions)
42
+ [![Package version](https://badge.fury.io/py/httpcore.svg)](https://pypi.org/project/httpcore/)
43
+
44
+ > *Do one thing, and do it well.*
45
+
46
+ The HTTP Core package provides a minimal low-level HTTP client, which does
47
+ one thing only. Sending HTTP requests.
48
+
49
+ It does not provide any high level model abstractions over the API,
50
+ does not handle redirects, multipart uploads, building authentication headers,
51
+ transparent HTTP caching, URL parsing, session cookie handling,
52
+ content or charset decoding, handling JSON, environment based configuration
53
+ defaults, or any of that Jazz.
54
+
55
+ Some things HTTP Core does do:
56
+
57
+ * Sending HTTP requests.
58
+ * Thread-safe / task-safe connection pooling.
59
+ * HTTP(S) proxy & SOCKS proxy support.
60
+ * Supports HTTP/1.1 and HTTP/2.
61
+ * Provides both sync and async interfaces.
62
+ * Async backend support for `asyncio` and `trio`.
63
+
64
+ ## Requirements
65
+
66
+ Python 3.8+
67
+
68
+ ## Installation
69
+
70
+ For HTTP/1.1 only support, install with:
71
+
72
+ ```shell
73
+ $ pip install httpcore
74
+ ```
75
+
76
+ There are also a number of optional extras available...
77
+
78
+ ```shell
79
+ $ pip install httpcore['asyncio,trio,http2,socks']
80
+ ```
81
+
82
+ ## Sending requests
83
+
84
+ Send an HTTP request:
85
+
86
+ ```python
87
+ import httpcore
88
+
89
+ response = httpcore.request("GET", "https://www.example.com/")
90
+
91
+ print(response)
92
+ # <Response [200]>
93
+ print(response.status)
94
+ # 200
95
+ print(response.headers)
96
+ # [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...]
97
+ print(response.content)
98
+ # b'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>\n\n<meta charset="utf-8"/>\n ...'
99
+ ```
100
+
101
+ The top-level `httpcore.request()` function is provided for convenience. In practice whenever you're working with `httpcore` you'll want to use the connection pooling functionality that it provides.
102
+
103
+ ```python
104
+ import httpcore
105
+
106
+ http = httpcore.ConnectionPool()
107
+ response = http.request("GET", "https://www.example.com/")
108
+ ```
109
+
110
+ Once you're ready to get going, [head over to the documentation](https://www.encode.io/httpcore/).
111
+
112
+ ## Motivation
113
+
114
+ You *probably* don't want to be using HTTP Core directly. It might make sense if
115
+ you're writing something like a proxy service in Python, and you just want
116
+ something at the lowest possible level, but more typically you'll want to use
117
+ a higher level client library, such as `httpx`.
118
+
119
+ The motivation for `httpcore` is:
120
+
121
+ * To provide a reusable low-level client library, that other packages can then build on top of.
122
+ * To provide a *really clear interface split* between the networking code and client logic,
123
+ so that each is easier to understand and reason about in isolation.
124
+
125
+ ## Dependencies
126
+
127
+ The `httpcore` package has the following dependencies...
128
+
129
+ * `h11`
130
+ * `certifi`
131
+
132
+ And the following optional extras...
133
+
134
+ * `anyio` - Required by `pip install httpcore['asyncio']`.
135
+ * `trio` - Required by `pip install httpcore['trio']`.
136
+ * `h2` - Required by `pip install httpcore['http2']`.
137
+ * `socksio` - Required by `pip install httpcore['socks']`.
138
+
139
+ ## Versioning
140
+
141
+ We use [SEMVER for our versioning policy](https://semver.org/).
142
+
143
+ For changes between package versions please see our [project changelog](CHANGELOG.md).
144
+
145
+ We recommend pinning your requirements either the most current major version, or a more specific version range:
146
+
147
+ ```python
148
+ pip install 'httpcore==1.*'
149
+ ```
150
+ # Changelog
151
+
152
+ All notable changes to this project will be documented in this file.
153
+
154
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
155
+
156
+ ## Version 1.0.9 (April 24th, 2025)
157
+
158
+ - Resolve https://github.com/advisories/GHSA-vqfr-h8mv-ghfj with h11 dependency update. (#1008)
159
+
160
+ ## Version 1.0.8 (April 11th, 2025)
161
+
162
+ - Fix `AttributeError` when importing on Python 3.14. (#1005)
163
+
164
+ ## Version 1.0.7 (November 15th, 2024)
165
+
166
+ - Support `proxy=…` configuration on `ConnectionPool()`. (#974)
167
+
168
+ ## Version 1.0.6 (October 1st, 2024)
169
+
170
+ - Relax `trio` dependency pinning. (#956)
171
+ - Handle `trio` raising `NotImplementedError` on unsupported platforms. (#955)
172
+ - Handle mapping `ssl.SSLError` to `httpcore.ConnectError`. (#918)
173
+
174
+ ## 1.0.5 (March 27th, 2024)
175
+
176
+ - Handle `EndOfStream` exception for anyio backend. (#899)
177
+ - Allow trio `0.25.*` series in package dependancies. (#903)
178
+
179
+ ## 1.0.4 (February 21st, 2024)
180
+
181
+ - Add `target` request extension. (#888)
182
+ - Fix support for connection `Upgrade` and `CONNECT` when some data in the stream has been read. (#882)
183
+
184
+ ## 1.0.3 (February 13th, 2024)
185
+
186
+ - Fix support for async cancellations. (#880)
187
+ - Fix trace extension when used with socks proxy. (#849)
188
+ - Fix SSL context for connections using the "wss" scheme (#869)
189
+
190
+ ## 1.0.2 (November 10th, 2023)
191
+
192
+ - Fix `float("inf")` timeouts in `Event.wait` function. (#846)
193
+
194
+ ## 1.0.1 (November 3rd, 2023)
195
+
196
+ - Fix pool timeout to account for the total time spent retrying. (#823)
197
+ - Raise a neater RuntimeError when the correct async deps are not installed. (#826)
198
+ - Add support for synchronous TLS-in-TLS streams. (#840)
199
+
200
+ ## 1.0.0 (October 6th, 2023)
201
+
202
+ From version 1.0 our async support is now optional, as the package has minimal dependencies by default.
203
+
204
+ For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`.
205
+
206
+ The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/.
207
+
208
+ - Async support becomes fully optional. (#809)
209
+ - Add support for Python 3.12. (#807)
210
+
211
+ ## 0.18.0 (September 8th, 2023)
212
+
213
+ - Add support for HTTPS proxies. (#745, #786)
214
+ - Drop Python 3.7 support. (#727)
215
+ - Handle `sni_hostname` extension with SOCKS proxy. (#774)
216
+ - Handle HTTP/1.1 half-closed connections gracefully. (#641)
217
+ - Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#762)
218
+
219
+ ## 0.17.3 (July 5th, 2023)
220
+
221
+ - Support async cancellations, ensuring that the connection pool is left in a clean state when cancellations occur. (#726)
222
+ - The networking backend interface has [been added to the public API](https://www.encode.io/httpcore/network-backends). Some classes which were previously private implementation detail are now part of the top-level public API. (#699)
223
+ - Graceful handling of HTTP/2 GoAway frames, with requests being transparently retried on a new connection. (#730)
224
+ - Add exceptions when a synchronous `trace callback` is passed to an asynchronous request or an asynchronous `trace callback` is passed to a synchronous request. (#717)
225
+ - Drop Python 3.7 support. (#727)
226
+
227
+ ## 0.17.2 (May 23th, 2023)
228
+
229
+ - Add `socket_options` argument to `ConnectionPool` and `HTTProxy` classes. (#668)
230
+ - Improve logging with per-module logger names. (#690)
231
+ - Add `sni_hostname` request extension. (#696)
232
+ - Resolve race condition during import of `anyio` package. (#692)
233
+ - Enable TCP_NODELAY for all synchronous sockets. (#651)
234
+
235
+ ## 0.17.1 (May 17th, 2023)
236
+
237
+ - If 'retries' is set, then allow retries if an SSL handshake error occurs. (#669)
238
+ - Improve correctness of tracebacks on network exceptions, by raising properly chained exceptions. (#678)
239
+ - Prevent connection-hanging behaviour when HTTP/2 connections are closed by a server-sent 'GoAway' frame. (#679)
240
+ - Fix edge-case exception when removing requests from the connection pool. (#680)
241
+ - Fix pool timeout edge-case. (#688)
242
+
243
+ ## 0.17.0 (March 16th, 2023)
244
+
245
+ - Add DEBUG level logging. (#648)
246
+ - Respect HTTP/2 max concurrent streams when settings updates are sent by server. (#652)
247
+ - Increase the allowable HTTP header size to 100kB. (#647)
248
+ - Add `retries` option to SOCKS proxy classes. (#643)
249
+
250
+ ## 0.16.3 (December 20th, 2022)
251
+
252
+ - Allow `ws` and `wss` schemes. Allows us to properly support websocket upgrade connections. (#625)
253
+ - Forwarding HTTP proxies use a connection-per-remote-host. Required by some proxy implementations. (#637)
254
+ - Don't raise `RuntimeError` when closing a connection pool with active connections. Removes some error cases when cancellations are used. (#631)
255
+ - Lazy import `anyio`, so that it's no longer a hard dependancy, and isn't imported if unused. (#639)
256
+
257
+ ## 0.16.2 (November 25th, 2022)
258
+
259
+ - Revert 'Fix async cancellation behaviour', which introduced race conditions. (#627)
260
+ - Raise `RuntimeError` if attempting to us UNIX domain sockets on Windows. (#619)
261
+
262
+ ## 0.16.1 (November 17th, 2022)
263
+
264
+ - Fix HTTP/1.1 interim informational responses, such as "100 Continue". (#605)
265
+
266
+ ## 0.16.0 (October 11th, 2022)
267
+
268
+ - Support HTTP/1.1 informational responses. (#581)
269
+ - Fix async cancellation behaviour. (#580)
270
+ - Support `h11` 0.14. (#579)
271
+
272
+ ## 0.15.0 (May 17th, 2022)
273
+
274
+ - Drop Python 3.6 support (#535)
275
+ - Ensure HTTP proxy CONNECT requests include `timeout` configuration. (#506)
276
+ - Switch to explicit `typing.Optional` for type hints. (#513)
277
+ - For `trio` map OSError exceptions to `ConnectError`. (#543)
278
+
279
+ ## 0.14.7 (February 4th, 2022)
280
+
281
+ - Requests which raise a PoolTimeout need to be removed from the pool queue. (#502)
282
+ - Fix AttributeError that happened when Socks5Connection were terminated. (#501)
283
+
284
+ ## 0.14.6 (February 1st, 2022)
285
+
286
+ - Fix SOCKS support for `http://` URLs. (#492)
287
+ - Resolve race condition around exceptions during streaming a response. (#491)
288
+
289
+ ## 0.14.5 (January 18th, 2022)
290
+
291
+ - SOCKS proxy support. (#478)
292
+ - Add proxy_auth argument to HTTPProxy. (#481)
293
+ - Improve error message on 'RemoteProtocolError' exception when server disconnects without sending a response. (#479)
294
+
295
+ ## 0.14.4 (January 5th, 2022)
296
+
297
+ - Support HTTP/2 on HTTPS tunnelling proxies. (#468)
298
+ - Fix proxy headers missing on HTTP forwarding. (#456)
299
+ - Only instantiate SSL context if required. (#457)
300
+ - More robust HTTP/2 handling. (#253, #439, #440, #441)
301
+
302
+ ## 0.14.3 (November 17th, 2021)
303
+
304
+ - Fix race condition when removing closed connections from the pool. (#437)
305
+
306
+ ## 0.14.2 (November 16th, 2021)
307
+
308
+ - Failed connections no longer remain in the pool. (Pull #433)
309
+
310
+ ## 0.14.1 (November 12th, 2021)
311
+
312
+ - `max_connections` becomes optional. (Pull #429)
313
+ - `certifi` is now included in the install dependancies. (Pull #428)
314
+ - `h2` is now strictly optional. (Pull #428)
315
+
316
+ ## 0.14.0 (November 11th, 2021)
317
+
318
+ The 0.14 release is a complete reworking of `httpcore`, comprehensively addressing some underlying issues in the connection pooling, as well as substantially redesigning the API to be more user friendly.
319
+
320
+ Some of the lower-level API design also makes the components more easily testable in isolation, and the package now has 100% test coverage.
321
+
322
+ See [discussion #419](https://github.com/encode/httpcore/discussions/419) for a little more background.
323
+
324
+ There's some other neat bits in there too, such as the "trace" extension, which gives a hook into inspecting the internal events that occur during the request/response cycle. This extension is needed for the HTTPX cli, in order to...
325
+
326
+ * Log the point at which the connection is established, and the IP/port on which it is made.
327
+ * Determine if the outgoing request should log as HTTP/1.1 or HTTP/2, rather than having to assume it's HTTP/2 if the --http2 flag was passed. (Which may not actually be true.)
328
+ * Log SSL version info / certificate info.
329
+
330
+ Note that `curio` support is not currently available in 0.14.0. If you're using `httpcore` with `curio` please get in touch, so we can assess if we ought to prioritize it as a feature or not.
331
+
332
+ ## 0.13.7 (September 13th, 2021)
333
+
334
+ - Fix broken error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #403)
335
+
336
+ ## 0.13.6 (June 15th, 2021)
337
+
338
+ ### Fixed
339
+
340
+ - Close sockets when read or write timeouts occur. (Pull #365)
341
+
342
+ ## 0.13.5 (June 14th, 2021)
343
+
344
+ ### Fixed
345
+
346
+ - Resolved niggles with AnyIO EOF behaviours. (Pull #358, #362)
347
+
348
+ ## 0.13.4 (June 9th, 2021)
349
+
350
+ ### Added
351
+
352
+ - Improved error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #354)
353
+
354
+ ### Fixed
355
+
356
+ - Switched to `anyio` as the default backend implementation when running with `asyncio`. Resolves some awkward [TLS timeout issues](https://github.com/encode/httpx/discussions/1511).
357
+
358
+ ## 0.13.3 (May 6th, 2021)
359
+
360
+ ### Added
361
+
362
+ - Support HTTP/2 prior knowledge, using `httpcore.SyncConnectionPool(http1=False)`. (Pull #333)
363
+
364
+ ### Fixed
365
+
366
+ - Handle cases where environment does not provide `select.poll` support. (Pull #331)
367
+
368
+ ## 0.13.2 (April 29th, 2021)
369
+
370
+ ### Added
371
+
372
+ - Improve error message for specific case of `RemoteProtocolError` where server disconnects without sending a response. (Pull #313)
373
+
374
+ ## 0.13.1 (April 28th, 2021)
375
+
376
+ ### Fixed
377
+
378
+ - More resiliant testing for closed connections. (Pull #311)
379
+ - Don't raise exceptions on ungraceful connection closes. (Pull #310)
380
+
381
+ ## 0.13.0 (April 21st, 2021)
382
+
383
+ The 0.13 release updates the core API in order to match the HTTPX Transport API,
384
+ introduced in HTTPX 0.18 onwards.
385
+
386
+ An example of making requests with the new interface is:
387
+
388
+ ```python
389
+ with httpcore.SyncConnectionPool() as http:
390
+ status_code, headers, stream, extensions = http.handle_request(
391
+ method=b'GET',
392
+ url=(b'https', b'example.org', 443, b'/'),
393
+ headers=[(b'host', b'example.org'), (b'user-agent', b'httpcore')]
394
+ stream=httpcore.ByteStream(b''),
395
+ extensions={}
396
+ )
397
+ body = stream.read()
398
+ print(status_code, body)
399
+ ```
400
+
401
+ ### Changed
402
+
403
+ - The `.request()` method is now `handle_request()`. (Pull #296)
404
+ - The `.arequest()` method is now `.handle_async_request()`. (Pull #296)
405
+ - The `headers` argument is no longer optional. (Pull #296)
406
+ - The `stream` argument is no longer optional. (Pull #296)
407
+ - The `ext` argument is now named `extensions`, and is no longer optional. (Pull #296)
408
+ - The `"reason"` extension keyword is now named `"reason_phrase"`. (Pull #296)
409
+ - The `"reason_phrase"` and `"http_version"` extensions now use byte strings for their values. (Pull #296)
410
+ - The `httpcore.PlainByteStream()` class becomes `httpcore.ByteStream()`. (Pull #296)
411
+
412
+ ### Added
413
+
414
+ - Streams now support a `.read()` interface. (Pull #296)
415
+
416
+ ### Fixed
417
+
418
+ - Task cancellation no longer leaks connections from the connection pool. (Pull #305)
419
+
420
+ ## 0.12.3 (December 7th, 2020)
421
+
422
+ ### Fixed
423
+
424
+ - Abort SSL connections on close rather than waiting for remote EOF when using `asyncio`. (Pull #167)
425
+ - Fix exception raised in case of connect timeouts when using the `anyio` backend. (Pull #236)
426
+ - Fix `Host` header precedence for `:authority` in HTTP/2. (Pull #241, #243)
427
+ - Handle extra edge case when detecting for socket readability when using `asyncio`. (Pull #242, #244)
428
+ - Fix `asyncio` SSL warning when using proxy tunneling. (Pull #249)
429
+
430
+ ## 0.12.2 (November 20th, 2020)
431
+
432
+ ### Fixed
433
+
434
+ - Properly wrap connect errors on the asyncio backend. (Pull #235)
435
+ - Fix `ImportError` occurring on Python 3.9 when using the HTTP/1.1 sync client in a multithreaded context. (Pull #237)
436
+
437
+ ## 0.12.1 (November 7th, 2020)
438
+
439
+ ### Added
440
+
441
+ - Add connect retries. (Pull #221)
442
+
443
+ ### Fixed
444
+
445
+ - Tweak detection of dropped connections, resolving an issue with open files limits on Linux. (Pull #185)
446
+ - Avoid leaking connections when establishing an HTTP tunnel to a proxy has failed. (Pull #223)
447
+ - Properly wrap OS errors when using `trio`. (Pull #225)
448
+
449
+ ## 0.12.0 (October 6th, 2020)
450
+
451
+ ### Changed
452
+
453
+ - HTTP header casing is now preserved, rather than always sent in lowercase. (#216 and python-hyper/h11#104)
454
+
455
+ ### Added
456
+
457
+ - Add Python 3.9 to officially supported versions.
458
+
459
+ ### Fixed
460
+
461
+ - Gracefully handle a stdlib asyncio bug when a connection is closed while it is in a paused-for-reading state. (#201)
462
+
463
+ ## 0.11.1 (September 28nd, 2020)
464
+
465
+ ### Fixed
466
+
467
+ - Add await to async semaphore release() coroutine (#197)
468
+ - Drop incorrect curio classifier (#192)
469
+
470
+ ## 0.11.0 (September 22nd, 2020)
471
+
472
+ The Transport API with 0.11.0 has a couple of significant changes.
473
+
474
+ Firstly we've moved changed the request interface in order to allow extensions, which will later enable us to support features
475
+ such as trailing headers, HTTP/2 server push, and CONNECT/Upgrade connections.
476
+
477
+ The interface changes from:
478
+
479
+ ```python
480
+ def request(method, url, headers, stream, timeout):
481
+ return (http_version, status_code, reason, headers, stream)
482
+ ```
483
+
484
+ To instead including an optional dictionary of extensions on the request and response:
485
+
486
+ ```python
487
+ def request(method, url, headers, stream, ext):
488
+ return (status_code, headers, stream, ext)
489
+ ```
490
+
491
+ Having an open-ended extensions point will allow us to add later support for various optional features, that wouldn't otherwise be supported without these API changes.
492
+
493
+ In particular:
494
+
495
+ * Trailing headers support.
496
+ * HTTP/2 Server Push
497
+ * sendfile.
498
+ * Exposing raw connection on CONNECT, Upgrade, HTTP/2 bi-di streaming.
499
+ * Exposing debug information out of the API, including template name, template context.
500
+
501
+ Currently extensions are limited to:
502
+
503
+ * request: `timeout` - Optional. Timeout dictionary.
504
+ * response: `http_version` - Optional. Include the HTTP version used on the response.
505
+ * response: `reason` - Optional. Include the reason phrase used on the response. Only valid with HTTP/1.*.
506
+
507
+ See https://github.com/encode/httpx/issues/1274#issuecomment-694884553 for the history behind this.
508
+
509
+ Secondly, the async version of `request` is now namespaced as `arequest`.
510
+
511
+ This allows concrete transports to support both sync and async implementations on the same class.
512
+
513
+ ### Added
514
+
515
+ - Add curio support. (Pull #168)
516
+ - Add anyio support, with `backend="anyio"`. (Pull #169)
517
+
518
+ ### Changed
519
+
520
+ - Update the Transport API to use 'ext' for optional extensions. (Pull #190)
521
+ - Update the Transport API to use `.request` and `.arequest` so implementations can support both sync and async. (Pull #189)
522
+
523
+ ## 0.10.2 (August 20th, 2020)
524
+
525
+ ### Added
526
+
527
+ - Added Unix Domain Socket support. (Pull #139)
528
+
529
+ ### Fixed
530
+
531
+ - Always include the port on proxy CONNECT requests. (Pull #154)
532
+ - Fix `max_keepalive_connections` configuration. (Pull #153)
533
+ - Fixes behaviour in HTTP/1.1 where server disconnects can be used to signal the end of the response body. (Pull #164)
534
+
535
+ ## 0.10.1 (August 7th, 2020)
536
+
537
+ - Include `max_keepalive_connections` on `AsyncHTTPProxy`/`SyncHTTPProxy` classes.
538
+
539
+ ## 0.10.0 (August 7th, 2020)
540
+
541
+ The most notable change in the 0.10.0 release is that HTTP/2 support is now fully optional.
542
+
543
+ Use either `pip install httpcore` for HTTP/1.1 support only, or `pip install httpcore[http2]` for HTTP/1.1 and HTTP/2 support.
544
+
545
+ ### Added
546
+
547
+ - HTTP/2 support becomes optional. (Pull #121, #130)
548
+ - Add `local_address=...` support. (Pull #100, #134)
549
+ - Add `PlainByteStream`, `IteratorByteStream`, `AsyncIteratorByteStream`. The `AsyncByteSteam` and `SyncByteStream` classes are now pure interface classes. (#133)
550
+ - Add `LocalProtocolError`, `RemoteProtocolError` exceptions. (Pull #129)
551
+ - Add `UnsupportedProtocol` exception. (Pull #128)
552
+ - Add `.get_connection_info()` method. (Pull #102, #137)
553
+ - Add better TRACE logs. (Pull #101)
554
+
555
+ ### Changed
556
+
557
+ - `max_keepalive` is deprecated in favour of `max_keepalive_connections`. (Pull #140)
558
+
559
+ ### Fixed
560
+
561
+ - Improve handling of server disconnects. (Pull #112)
562
+
563
+ ## 0.9.1 (May 27th, 2020)
564
+
565
+ ### Fixed
566
+
567
+ - Proper host resolution for sync case, including IPv6 support. (Pull #97)
568
+ - Close outstanding connections when connection pool is closed. (Pull #98)
569
+
570
+ ## 0.9.0 (May 21th, 2020)
571
+
572
+ ### Changed
573
+
574
+ - URL port becomes an `Optional[int]` instead of `int`. (Pull #92)
575
+
576
+ ### Fixed
577
+
578
+ - Honor HTTP/2 max concurrent streams settings. (Pull #89, #90)
579
+ - Remove incorrect debug log. (Pull #83)
580
+
581
+ ## 0.8.4 (May 11th, 2020)
582
+
583
+ ### Added
584
+
585
+ - Logging via HTTPCORE_LOG_LEVEL and HTTPX_LOG_LEVEL environment variables
586
+ and TRACE level logging. (Pull #79)
587
+
588
+ ### Fixed
589
+
590
+ - Reuse of connections on HTTP/2 in close concurrency situations. (Pull #81)
591
+
592
+ ## 0.8.3 (May 6rd, 2020)
593
+
594
+ ### Fixed
595
+
596
+ - Include `Host` and `Accept` headers on proxy "CONNECT" requests.
597
+ - De-duplicate any headers also contained in proxy_headers.
598
+ - HTTP/2 flag not being passed down to proxy connections.
599
+
600
+ ## 0.8.2 (May 3rd, 2020)
601
+
602
+ ### Fixed
603
+
604
+ - Fix connections using proxy forwarding requests not being added to the
605
+ connection pool properly. (Pull #70)
606
+
607
+ ## 0.8.1 (April 30th, 2020)
608
+
609
+ ### Changed
610
+
611
+ - Allow inherintance of both `httpcore.AsyncByteStream`, `httpcore.SyncByteStream` without type conflicts.
612
+
613
+ ## 0.8.0 (April 30th, 2020)
614
+
615
+ ### Fixed
616
+
617
+ - Fixed tunnel proxy support.
618
+
619
+ ### Added
620
+
621
+ - New `TimeoutException` base class.
622
+
623
+ ## 0.7.0 (March 5th, 2020)
624
+
625
+ - First integration with HTTPX.
venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/RECORD ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ httpcore-1.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ httpcore-1.0.9.dist-info/METADATA,sha256=_i1P2mGZEol4d54M8n88BFxTGGP83Zh-rMdPOhjUHCE,21529
3
+ httpcore-1.0.9.dist-info/RECORD,,
4
+ httpcore-1.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ httpcore-1.0.9.dist-info/licenses/LICENSE.md,sha256=_ctZFUx0y6uhahEkL3dAvqnyPW_rVUeRfYxflKgDkqU,1518
6
+ httpcore/__init__.py,sha256=9kT_kqChCCJUTHww24ZmR_ezcdbpRYWksD-gYNzkZP8,3445
7
+ httpcore/__pycache__/__init__.cpython-312.pyc,,
8
+ httpcore/__pycache__/_api.cpython-312.pyc,,
9
+ httpcore/__pycache__/_exceptions.cpython-312.pyc,,
10
+ httpcore/__pycache__/_models.cpython-312.pyc,,
11
+ httpcore/__pycache__/_ssl.cpython-312.pyc,,
12
+ httpcore/__pycache__/_synchronization.cpython-312.pyc,,
13
+ httpcore/__pycache__/_trace.cpython-312.pyc,,
14
+ httpcore/__pycache__/_utils.cpython-312.pyc,,
15
+ httpcore/_api.py,sha256=unZmeDschBWCGCPCwkS3Wot9euK6bg_kKxLtGTxw214,3146
16
+ httpcore/_async/__init__.py,sha256=EWdl2v4thnAHzJpqjU4h2a8DUiGAvNiWrkii9pfhTf0,1221
17
+ httpcore/_async/__pycache__/__init__.cpython-312.pyc,,
18
+ httpcore/_async/__pycache__/connection.cpython-312.pyc,,
19
+ httpcore/_async/__pycache__/connection_pool.cpython-312.pyc,,
20
+ httpcore/_async/__pycache__/http11.cpython-312.pyc,,
21
+ httpcore/_async/__pycache__/http2.cpython-312.pyc,,
22
+ httpcore/_async/__pycache__/http_proxy.cpython-312.pyc,,
23
+ httpcore/_async/__pycache__/interfaces.cpython-312.pyc,,
24
+ httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc,,
25
+ httpcore/_async/connection.py,sha256=6OcPXqMEfc0BU38_-iHUNDd1vKSTc2UVT09XqNb_BOk,8449
26
+ httpcore/_async/connection_pool.py,sha256=DOIQ2s2ZCf9qfwxhzMprTPLqCL8OxGXiKF6qRHxvVyY,17307
27
+ httpcore/_async/http11.py,sha256=-qM9bV7PjSQF5vxs37-eUXOIFwbIjPcZbNliuX9TtBw,13880
28
+ httpcore/_async/http2.py,sha256=azX1fcmtXaIwjputFlZ4vd92J8xwjGOa9ax9QIv4394,23936
29
+ httpcore/_async/http_proxy.py,sha256=2zVkrlv-Ds-rWGaqaXlrhEJiAQFPo23BT3Gq_sWoBXU,14701
30
+ httpcore/_async/interfaces.py,sha256=jTiaWL83pgpGC9ziv90ZfwaKNMmHwmOalzaKiuTxATo,4455
31
+ httpcore/_async/socks_proxy.py,sha256=lLKgLlggPfhFlqi0ODeBkOWvt9CghBBUyqsnsU1tx6Q,13841
32
+ httpcore/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ httpcore/_backends/__pycache__/__init__.cpython-312.pyc,,
34
+ httpcore/_backends/__pycache__/anyio.cpython-312.pyc,,
35
+ httpcore/_backends/__pycache__/auto.cpython-312.pyc,,
36
+ httpcore/_backends/__pycache__/base.cpython-312.pyc,,
37
+ httpcore/_backends/__pycache__/mock.cpython-312.pyc,,
38
+ httpcore/_backends/__pycache__/sync.cpython-312.pyc,,
39
+ httpcore/_backends/__pycache__/trio.cpython-312.pyc,,
40
+ httpcore/_backends/anyio.py,sha256=x8PgEhXRC8bVqsdzk_YJx8Y6d9Tub06CuUSwnbmtqoY,5252
41
+ httpcore/_backends/auto.py,sha256=zO136PKZmsaTDK-HRk84eA-MUg8_2wJf4NvmK432Aio,1662
42
+ httpcore/_backends/base.py,sha256=aShgRdZnMmRhFWHetjumlM73f8Kz1YOAyCUP_4kHslA,3042
43
+ httpcore/_backends/mock.py,sha256=er9T436uSe7NLrfiLa4x6Nuqg5ivQ693CxWYCWsgbH4,4077
44
+ httpcore/_backends/sync.py,sha256=bhE4d9iK9Umxdsdsgm2EfKnXaBms2WggGYU-7jmUujU,7977
45
+ httpcore/_backends/trio.py,sha256=LHu4_Mr5MswQmmT3yE4oLgf9b_JJfeVS4BjDxeJc7Ro,5996
46
+ httpcore/_exceptions.py,sha256=looCKga3_YVYu3s-d3L9RMPRJyhsY7fiuuGxvkOD0c0,1184
47
+ httpcore/_models.py,sha256=IO2CcXcdpovRcLTdGFGB6RyBZdEm2h_TOmoCc4rEKho,17623
48
+ httpcore/_ssl.py,sha256=srqmSNU4iOUvWF-SrJvb8G_YEbHFELOXQOwdDIBTS9c,187
49
+ httpcore/_sync/__init__.py,sha256=JBDIgXt5la1LCJ1sLQeKhjKFpLnpNr8Svs6z2ni3fgg,1141
50
+ httpcore/_sync/__pycache__/__init__.cpython-312.pyc,,
51
+ httpcore/_sync/__pycache__/connection.cpython-312.pyc,,
52
+ httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc,,
53
+ httpcore/_sync/__pycache__/http11.cpython-312.pyc,,
54
+ httpcore/_sync/__pycache__/http2.cpython-312.pyc,,
55
+ httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc,,
56
+ httpcore/_sync/__pycache__/interfaces.cpython-312.pyc,,
57
+ httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc,,
58
+ httpcore/_sync/connection.py,sha256=9exGOb3PB-Mp2T1-sckSeL2t-tJ_9-NXomV8ihmWCgU,8238
59
+ httpcore/_sync/connection_pool.py,sha256=a-T8LTsUxc7r0Ww1atfHSDoWPjQ0fA8Ul7S3-F0Mj70,16955
60
+ httpcore/_sync/http11.py,sha256=IFobD1Md5JFlJGKWnh1_Q3epikUryI8qo09v8MiJIEA,13476
61
+ httpcore/_sync/http2.py,sha256=AxU4yhcq68Bn5vqdJYtiXKYUj7nvhYbxz3v4rT4xnvA,23400
62
+ httpcore/_sync/http_proxy.py,sha256=_al_6crKuEZu2wyvu493RZImJdBJnj5oGKNjLOJL2Zo,14463
63
+ httpcore/_sync/interfaces.py,sha256=snXON42vUDHO5JBJvo8D4VWk2Wat44z2OXXHDrjbl94,4344
64
+ httpcore/_sync/socks_proxy.py,sha256=zegZW9Snqj2_992DFJa8_CppOVBkVL4AgwduRkStakQ,13614
65
+ httpcore/_synchronization.py,sha256=zSi13mAColBnknjZBknUC6hKNDQT4C6ijnezZ-r0T2s,9434
66
+ httpcore/_trace.py,sha256=ck6ZoIzYTkdNAIfq5MGeKqBXDtqjOX-qfYwmZFbrGco,3952
67
+ httpcore/_utils.py,sha256=_RLgXYOAYC350ikALV59GZ68IJrdocRZxPs9PjmzdFY,1537
68
+ httpcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright © 2020, [Encode OSS Ltd](https://www.encode.io/).
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
venv/lib/python3.12/site-packages/httpcore/_async/http2.py ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import time
6
+ import types
7
+ import typing
8
+
9
+ import h2.config
10
+ import h2.connection
11
+ import h2.events
12
+ import h2.exceptions
13
+ import h2.settings
14
+
15
+ from .._backends.base import AsyncNetworkStream
16
+ from .._exceptions import (
17
+ ConnectionNotAvailable,
18
+ LocalProtocolError,
19
+ RemoteProtocolError,
20
+ )
21
+ from .._models import Origin, Request, Response
22
+ from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation
23
+ from .._trace import Trace
24
+ from .interfaces import AsyncConnectionInterface
25
+
26
+ logger = logging.getLogger("httpcore.http2")
27
+
28
+
29
+ def has_body_headers(request: Request) -> bool:
30
+ return any(
31
+ k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
32
+ for k, v in request.headers
33
+ )
34
+
35
+
36
+ class HTTPConnectionState(enum.IntEnum):
37
+ ACTIVE = 1
38
+ IDLE = 2
39
+ CLOSED = 3
40
+
41
+
42
+ class AsyncHTTP2Connection(AsyncConnectionInterface):
43
+ READ_NUM_BYTES = 64 * 1024
44
+ CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)
45
+
46
+ def __init__(
47
+ self,
48
+ origin: Origin,
49
+ stream: AsyncNetworkStream,
50
+ keepalive_expiry: float | None = None,
51
+ ):
52
+ self._origin = origin
53
+ self._network_stream = stream
54
+ self._keepalive_expiry: float | None = keepalive_expiry
55
+ self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
56
+ self._state = HTTPConnectionState.IDLE
57
+ self._expire_at: float | None = None
58
+ self._request_count = 0
59
+ self._init_lock = AsyncLock()
60
+ self._state_lock = AsyncLock()
61
+ self._read_lock = AsyncLock()
62
+ self._write_lock = AsyncLock()
63
+ self._sent_connection_init = False
64
+ self._used_all_stream_ids = False
65
+ self._connection_error = False
66
+
67
+ # Mapping from stream ID to response stream events.
68
+ self._events: dict[
69
+ int,
70
+ list[
71
+ h2.events.ResponseReceived
72
+ | h2.events.DataReceived
73
+ | h2.events.StreamEnded
74
+ | h2.events.StreamReset,
75
+ ],
76
+ ] = {}
77
+
78
+ # Connection terminated events are stored as state since
79
+ # we need to handle them for all streams.
80
+ self._connection_terminated: h2.events.ConnectionTerminated | None = None
81
+
82
+ self._read_exception: Exception | None = None
83
+ self._write_exception: Exception | None = None
84
+
85
+ async def handle_async_request(self, request: Request) -> Response:
86
+ if not self.can_handle_request(request.url.origin):
87
+ # This cannot occur in normal operation, since the connection pool
88
+ # will only send requests on connections that handle them.
89
+ # It's in place simply for resilience as a guard against incorrect
90
+ # usage, for anyone working directly with httpcore connections.
91
+ raise RuntimeError(
92
+ f"Attempted to send request to {request.url.origin} on connection "
93
+ f"to {self._origin}"
94
+ )
95
+
96
+ async with self._state_lock:
97
+ if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
98
+ self._request_count += 1
99
+ self._expire_at = None
100
+ self._state = HTTPConnectionState.ACTIVE
101
+ else:
102
+ raise ConnectionNotAvailable()
103
+
104
+ async with self._init_lock:
105
+ if not self._sent_connection_init:
106
+ try:
107
+ sci_kwargs = {"request": request}
108
+ async with Trace(
109
+ "send_connection_init", logger, request, sci_kwargs
110
+ ):
111
+ await self._send_connection_init(**sci_kwargs)
112
+ except BaseException as exc:
113
+ with AsyncShieldCancellation():
114
+ await self.aclose()
115
+ raise exc
116
+
117
+ self._sent_connection_init = True
118
+
119
+ # Initially start with just 1 until the remote server provides
120
+ # its max_concurrent_streams value
121
+ self._max_streams = 1
122
+
123
+ local_settings_max_streams = (
124
+ self._h2_state.local_settings.max_concurrent_streams
125
+ )
126
+ self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams)
127
+
128
+ for _ in range(local_settings_max_streams - self._max_streams):
129
+ await self._max_streams_semaphore.acquire()
130
+
131
+ await self._max_streams_semaphore.acquire()
132
+
133
+ try:
134
+ stream_id = self._h2_state.get_next_available_stream_id()
135
+ self._events[stream_id] = []
136
+ except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover
137
+ self._used_all_stream_ids = True
138
+ self._request_count -= 1
139
+ raise ConnectionNotAvailable()
140
+
141
+ try:
142
+ kwargs = {"request": request, "stream_id": stream_id}
143
+ async with Trace("send_request_headers", logger, request, kwargs):
144
+ await self._send_request_headers(request=request, stream_id=stream_id)
145
+ async with Trace("send_request_body", logger, request, kwargs):
146
+ await self._send_request_body(request=request, stream_id=stream_id)
147
+ async with Trace(
148
+ "receive_response_headers", logger, request, kwargs
149
+ ) as trace:
150
+ status, headers = await self._receive_response(
151
+ request=request, stream_id=stream_id
152
+ )
153
+ trace.return_value = (status, headers)
154
+
155
+ return Response(
156
+ status=status,
157
+ headers=headers,
158
+ content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
159
+ extensions={
160
+ "http_version": b"HTTP/2",
161
+ "network_stream": self._network_stream,
162
+ "stream_id": stream_id,
163
+ },
164
+ )
165
+ except BaseException as exc: # noqa: PIE786
166
+ with AsyncShieldCancellation():
167
+ kwargs = {"stream_id": stream_id}
168
+ async with Trace("response_closed", logger, request, kwargs):
169
+ await self._response_closed(stream_id=stream_id)
170
+
171
+ if isinstance(exc, h2.exceptions.ProtocolError):
172
+ # One case where h2 can raise a protocol error is when a
173
+ # closed frame has been seen by the state machine.
174
+ #
175
+ # This happens when one stream is reading, and encounters
176
+ # a GOAWAY event. Other flows of control may then raise
177
+ # a protocol error at any point they interact with the 'h2_state'.
178
+ #
179
+ # In this case we'll have stored the event, and should raise
180
+ # it as a RemoteProtocolError.
181
+ if self._connection_terminated: # pragma: nocover
182
+ raise RemoteProtocolError(self._connection_terminated)
183
+ # If h2 raises a protocol error in some other state then we
184
+ # must somehow have made a protocol violation.
185
+ raise LocalProtocolError(exc) # pragma: nocover
186
+
187
+ raise exc
188
+
189
+ async def _send_connection_init(self, request: Request) -> None:
190
+ """
191
+ The HTTP/2 connection requires some initial setup before we can start
192
+ using individual request/response streams on it.
193
+ """
194
+ # Need to set these manually here instead of manipulating via
195
+ # __setitem__() otherwise the H2Connection will emit SettingsUpdate
196
+ # frames in addition to sending the undesired defaults.
197
+ self._h2_state.local_settings = h2.settings.Settings(
198
+ client=True,
199
+ initial_values={
200
+ # Disable PUSH_PROMISE frames from the server since we don't do anything
201
+ # with them for now. Maybe when we support caching?
202
+ h2.settings.SettingCodes.ENABLE_PUSH: 0,
203
+ # These two are taken from h2 for safe defaults
204
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
205
+ h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
206
+ },
207
+ )
208
+
209
+ # Some websites (*cough* Yahoo *cough*) balk at this setting being
210
+ # present in the initial handshake since it's not defined in the original
211
+ # RFC despite the RFC mandating ignoring settings you don't know about.
212
+ del self._h2_state.local_settings[
213
+ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
214
+ ]
215
+
216
+ self._h2_state.initiate_connection()
217
+ self._h2_state.increment_flow_control_window(2**24)
218
+ await self._write_outgoing_data(request)
219
+
220
+ # Sending the request...
221
+
222
+ async def _send_request_headers(self, request: Request, stream_id: int) -> None:
223
+ """
224
+ Send the request headers to a given stream ID.
225
+ """
226
+ end_stream = not has_body_headers(request)
227
+
228
+ # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
229
+ # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
230
+ # HTTP/1.1 style headers, and map them appropriately if we end up on
231
+ # an HTTP/2 connection.
232
+ authority = [v for k, v in request.headers if k.lower() == b"host"][0]
233
+
234
+ headers = [
235
+ (b":method", request.method),
236
+ (b":authority", authority),
237
+ (b":scheme", request.url.scheme),
238
+ (b":path", request.url.target),
239
+ ] + [
240
+ (k.lower(), v)
241
+ for k, v in request.headers
242
+ if k.lower()
243
+ not in (
244
+ b"host",
245
+ b"transfer-encoding",
246
+ )
247
+ ]
248
+
249
+ self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
250
+ self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
251
+ await self._write_outgoing_data(request)
252
+
253
+ async def _send_request_body(self, request: Request, stream_id: int) -> None:
254
+ """
255
+ Iterate over the request body sending it to a given stream ID.
256
+ """
257
+ if not has_body_headers(request):
258
+ return
259
+
260
+ assert isinstance(request.stream, typing.AsyncIterable)
261
+ async for data in request.stream:
262
+ await self._send_stream_data(request, stream_id, data)
263
+ await self._send_end_stream(request, stream_id)
264
+
265
+ async def _send_stream_data(
266
+ self, request: Request, stream_id: int, data: bytes
267
+ ) -> None:
268
+ """
269
+ Send a single chunk of data in one or more data frames.
270
+ """
271
+ while data:
272
+ max_flow = await self._wait_for_outgoing_flow(request, stream_id)
273
+ chunk_size = min(len(data), max_flow)
274
+ chunk, data = data[:chunk_size], data[chunk_size:]
275
+ self._h2_state.send_data(stream_id, chunk)
276
+ await self._write_outgoing_data(request)
277
+
278
+ async def _send_end_stream(self, request: Request, stream_id: int) -> None:
279
+ """
280
+ Send an empty data frame on on a given stream ID with the END_STREAM flag set.
281
+ """
282
+ self._h2_state.end_stream(stream_id)
283
+ await self._write_outgoing_data(request)
284
+
285
+ # Receiving the response...
286
+
287
+ async def _receive_response(
288
+ self, request: Request, stream_id: int
289
+ ) -> tuple[int, list[tuple[bytes, bytes]]]:
290
+ """
291
+ Return the response status code and headers for a given stream ID.
292
+ """
293
+ while True:
294
+ event = await self._receive_stream_event(request, stream_id)
295
+ if isinstance(event, h2.events.ResponseReceived):
296
+ break
297
+
298
+ status_code = 200
299
+ headers = []
300
+ assert event.headers is not None
301
+ for k, v in event.headers:
302
+ if k == b":status":
303
+ status_code = int(v.decode("ascii", errors="ignore"))
304
+ elif not k.startswith(b":"):
305
+ headers.append((k, v))
306
+
307
+ return (status_code, headers)
308
+
309
+ async def _receive_response_body(
310
+ self, request: Request, stream_id: int
311
+ ) -> typing.AsyncIterator[bytes]:
312
+ """
313
+ Iterator that returns the bytes of the response body for a given stream ID.
314
+ """
315
+ while True:
316
+ event = await self._receive_stream_event(request, stream_id)
317
+ if isinstance(event, h2.events.DataReceived):
318
+ assert event.flow_controlled_length is not None
319
+ assert event.data is not None
320
+ amount = event.flow_controlled_length
321
+ self._h2_state.acknowledge_received_data(amount, stream_id)
322
+ await self._write_outgoing_data(request)
323
+ yield event.data
324
+ elif isinstance(event, h2.events.StreamEnded):
325
+ break
326
+
327
+ async def _receive_stream_event(
328
+ self, request: Request, stream_id: int
329
+ ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
330
+ """
331
+ Return the next available event for a given stream ID.
332
+
333
+ Will read more data from the network if required.
334
+ """
335
+ while not self._events.get(stream_id):
336
+ await self._receive_events(request, stream_id)
337
+ event = self._events[stream_id].pop(0)
338
+ if isinstance(event, h2.events.StreamReset):
339
+ raise RemoteProtocolError(event)
340
+ return event
341
+
342
+ async def _receive_events(
343
+ self, request: Request, stream_id: int | None = None
344
+ ) -> None:
345
+ """
346
+ Read some data from the network until we see one or more events
347
+ for a given stream ID.
348
+ """
349
+ async with self._read_lock:
350
+ if self._connection_terminated is not None:
351
+ last_stream_id = self._connection_terminated.last_stream_id
352
+ if stream_id and last_stream_id and stream_id > last_stream_id:
353
+ self._request_count -= 1
354
+ raise ConnectionNotAvailable()
355
+ raise RemoteProtocolError(self._connection_terminated)
356
+
357
+ # This conditional is a bit icky. We don't want to block reading if we've
358
+ # actually got an event to return for a given stream. We need to do that
359
+ # check *within* the atomic read lock. Though it also need to be optional,
360
+ # because when we call it from `_wait_for_outgoing_flow` we *do* want to
361
+ # block until we've available flow control, event when we have events
362
+ # pending for the stream ID we're attempting to send on.
363
+ if stream_id is None or not self._events.get(stream_id):
364
+ events = await self._read_incoming_data(request)
365
+ for event in events:
366
+ if isinstance(event, h2.events.RemoteSettingsChanged):
367
+ async with Trace(
368
+ "receive_remote_settings", logger, request
369
+ ) as trace:
370
+ await self._receive_remote_settings_change(event)
371
+ trace.return_value = event
372
+
373
+ elif isinstance(
374
+ event,
375
+ (
376
+ h2.events.ResponseReceived,
377
+ h2.events.DataReceived,
378
+ h2.events.StreamEnded,
379
+ h2.events.StreamReset,
380
+ ),
381
+ ):
382
+ if event.stream_id in self._events:
383
+ self._events[event.stream_id].append(event)
384
+
385
+ elif isinstance(event, h2.events.ConnectionTerminated):
386
+ self._connection_terminated = event
387
+
388
+ await self._write_outgoing_data(request)
389
+
390
+ async def _receive_remote_settings_change(
391
+ self, event: h2.events.RemoteSettingsChanged
392
+ ) -> None:
393
+ max_concurrent_streams = event.changed_settings.get(
394
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
395
+ )
396
+ if max_concurrent_streams:
397
+ new_max_streams = min(
398
+ max_concurrent_streams.new_value,
399
+ self._h2_state.local_settings.max_concurrent_streams,
400
+ )
401
+ if new_max_streams and new_max_streams != self._max_streams:
402
+ while new_max_streams > self._max_streams:
403
+ await self._max_streams_semaphore.release()
404
+ self._max_streams += 1
405
+ while new_max_streams < self._max_streams:
406
+ await self._max_streams_semaphore.acquire()
407
+ self._max_streams -= 1
408
+
409
+ async def _response_closed(self, stream_id: int) -> None:
410
+ await self._max_streams_semaphore.release()
411
+ del self._events[stream_id]
412
+ async with self._state_lock:
413
+ if self._connection_terminated and not self._events:
414
+ await self.aclose()
415
+
416
+ elif self._state == HTTPConnectionState.ACTIVE and not self._events:
417
+ self._state = HTTPConnectionState.IDLE
418
+ if self._keepalive_expiry is not None:
419
+ now = time.monotonic()
420
+ self._expire_at = now + self._keepalive_expiry
421
+ if self._used_all_stream_ids: # pragma: nocover
422
+ await self.aclose()
423
+
424
+ async def aclose(self) -> None:
425
+ # Note that this method unilaterally closes the connection, and does
426
+ # not have any kind of locking in place around it.
427
+ self._h2_state.close_connection()
428
+ self._state = HTTPConnectionState.CLOSED
429
+ await self._network_stream.aclose()
430
+
431
+ # Wrappers around network read/write operations...
432
+
433
+ async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
434
+ timeouts = request.extensions.get("timeout", {})
435
+ timeout = timeouts.get("read", None)
436
+
437
+ if self._read_exception is not None:
438
+ raise self._read_exception # pragma: nocover
439
+
440
+ try:
441
+ data = await self._network_stream.read(self.READ_NUM_BYTES, timeout)
442
+ if data == b"":
443
+ raise RemoteProtocolError("Server disconnected")
444
+ except Exception as exc:
445
+ # If we get a network error we should:
446
+ #
447
+ # 1. Save the exception and just raise it immediately on any future reads.
448
+ # (For example, this means that a single read timeout or disconnect will
449
+ # immediately close all pending streams. Without requiring multiple
450
+ # sequential timeouts.)
451
+ # 2. Mark the connection as errored, so that we don't accept any other
452
+ # incoming requests.
453
+ self._read_exception = exc
454
+ self._connection_error = True
455
+ raise exc
456
+
457
+ events: list[h2.events.Event] = self._h2_state.receive_data(data)
458
+
459
+ return events
460
+
461
+ async def _write_outgoing_data(self, request: Request) -> None:
462
+ timeouts = request.extensions.get("timeout", {})
463
+ timeout = timeouts.get("write", None)
464
+
465
+ async with self._write_lock:
466
+ data_to_send = self._h2_state.data_to_send()
467
+
468
+ if self._write_exception is not None:
469
+ raise self._write_exception # pragma: nocover
470
+
471
+ try:
472
+ await self._network_stream.write(data_to_send, timeout)
473
+ except Exception as exc: # pragma: nocover
474
+ # If we get a network error we should:
475
+ #
476
+ # 1. Save the exception and just raise it immediately on any future write.
477
+ # (For example, this means that a single write timeout or disconnect will
478
+ # immediately close all pending streams. Without requiring multiple
479
+ # sequential timeouts.)
480
+ # 2. Mark the connection as errored, so that we don't accept any other
481
+ # incoming requests.
482
+ self._write_exception = exc
483
+ self._connection_error = True
484
+ raise exc
485
+
486
+ # Flow control...
487
+
488
+ async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
489
+ """
490
+ Returns the maximum allowable outgoing flow for a given stream.
491
+
492
+ If the allowable flow is zero, then waits on the network until
493
+ WindowUpdated frames have increased the flow rate.
494
+ https://tools.ietf.org/html/rfc7540#section-6.9
495
+ """
496
+ local_flow: int = self._h2_state.local_flow_control_window(stream_id)
497
+ max_frame_size: int = self._h2_state.max_outbound_frame_size
498
+ flow = min(local_flow, max_frame_size)
499
+ while flow == 0:
500
+ await self._receive_events(request)
501
+ local_flow = self._h2_state.local_flow_control_window(stream_id)
502
+ max_frame_size = self._h2_state.max_outbound_frame_size
503
+ flow = min(local_flow, max_frame_size)
504
+ return flow
505
+
506
+ # Interface for connection pooling...
507
+
508
+ def can_handle_request(self, origin: Origin) -> bool:
509
+ return origin == self._origin
510
+
511
+ def is_available(self) -> bool:
512
+ return (
513
+ self._state != HTTPConnectionState.CLOSED
514
+ and not self._connection_error
515
+ and not self._used_all_stream_ids
516
+ and not (
517
+ self._h2_state.state_machine.state
518
+ == h2.connection.ConnectionState.CLOSED
519
+ )
520
+ )
521
+
522
+ def has_expired(self) -> bool:
523
+ now = time.monotonic()
524
+ return self._expire_at is not None and now > self._expire_at
525
+
526
+ def is_idle(self) -> bool:
527
+ return self._state == HTTPConnectionState.IDLE
528
+
529
+ def is_closed(self) -> bool:
530
+ return self._state == HTTPConnectionState.CLOSED
531
+
532
+ def info(self) -> str:
533
+ origin = str(self._origin)
534
+ return (
535
+ f"{origin!r}, HTTP/2, {self._state.name}, "
536
+ f"Request Count: {self._request_count}"
537
+ )
538
+
539
+ def __repr__(self) -> str:
540
+ class_name = self.__class__.__name__
541
+ origin = str(self._origin)
542
+ return (
543
+ f"<{class_name} [{origin!r}, {self._state.name}, "
544
+ f"Request Count: {self._request_count}]>"
545
+ )
546
+
547
+ # These context managers are not used in the standard flow, but are
548
+ # useful for testing or working with connection instances directly.
549
+
550
+ async def __aenter__(self) -> AsyncHTTP2Connection:
551
+ return self
552
+
553
+ async def __aexit__(
554
+ self,
555
+ exc_type: type[BaseException] | None = None,
556
+ exc_value: BaseException | None = None,
557
+ traceback: types.TracebackType | None = None,
558
+ ) -> None:
559
+ await self.aclose()
560
+
561
+
562
+ class HTTP2ConnectionByteStream:
563
+ def __init__(
564
+ self, connection: AsyncHTTP2Connection, request: Request, stream_id: int
565
+ ) -> None:
566
+ self._connection = connection
567
+ self._request = request
568
+ self._stream_id = stream_id
569
+ self._closed = False
570
+
571
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
572
+ kwargs = {"request": self._request, "stream_id": self._stream_id}
573
+ try:
574
+ async with Trace("receive_response_body", logger, self._request, kwargs):
575
+ async for chunk in self._connection._receive_response_body(
576
+ request=self._request, stream_id=self._stream_id
577
+ ):
578
+ yield chunk
579
+ except BaseException as exc:
580
+ # If we get an exception while streaming the response,
581
+ # we want to close the response (and possibly the connection)
582
+ # before raising that exception.
583
+ with AsyncShieldCancellation():
584
+ await self.aclose()
585
+ raise exc
586
+
587
+ async def aclose(self) -> None:
588
+ if not self._closed:
589
+ self._closed = True
590
+ kwargs = {"stream_id": self._stream_id}
591
+ async with Trace("response_closed", logger, self._request, kwargs):
592
+ await self._connection._response_closed(stream_id=self._stream_id)
venv/lib/python3.12/site-packages/httpcore/_async/http_proxy.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import logging
5
+ import ssl
6
+ import typing
7
+
8
+ from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
9
+ from .._exceptions import ProxyError
10
+ from .._models import (
11
+ URL,
12
+ Origin,
13
+ Request,
14
+ Response,
15
+ enforce_bytes,
16
+ enforce_headers,
17
+ enforce_url,
18
+ )
19
+ from .._ssl import default_ssl_context
20
+ from .._synchronization import AsyncLock
21
+ from .._trace import Trace
22
+ from .connection import AsyncHTTPConnection
23
+ from .connection_pool import AsyncConnectionPool
24
+ from .http11 import AsyncHTTP11Connection
25
+ from .interfaces import AsyncConnectionInterface
26
+
27
+ ByteOrStr = typing.Union[bytes, str]
28
+ HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
29
+ HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
30
+
31
+
32
+ logger = logging.getLogger("httpcore.proxy")
33
+
34
+
35
+ def merge_headers(
36
+ default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
37
+ override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
38
+ ) -> list[tuple[bytes, bytes]]:
39
+ """
40
+ Append default_headers and override_headers, de-duplicating if a key exists
41
+ in both cases.
42
+ """
43
+ default_headers = [] if default_headers is None else list(default_headers)
44
+ override_headers = [] if override_headers is None else list(override_headers)
45
+ has_override = set(key.lower() for key, value in override_headers)
46
+ default_headers = [
47
+ (key, value)
48
+ for key, value in default_headers
49
+ if key.lower() not in has_override
50
+ ]
51
+ return default_headers + override_headers
52
+
53
+
54
+ class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover
55
+ """
56
+ A connection pool that sends requests via an HTTP proxy.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ proxy_url: URL | bytes | str,
62
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
63
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
64
+ ssl_context: ssl.SSLContext | None = None,
65
+ proxy_ssl_context: ssl.SSLContext | None = None,
66
+ max_connections: int | None = 10,
67
+ max_keepalive_connections: int | None = None,
68
+ keepalive_expiry: float | None = None,
69
+ http1: bool = True,
70
+ http2: bool = False,
71
+ retries: int = 0,
72
+ local_address: str | None = None,
73
+ uds: str | None = None,
74
+ network_backend: AsyncNetworkBackend | None = None,
75
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
76
+ ) -> None:
77
+ """
78
+ A connection pool for making HTTP requests.
79
+
80
+ Parameters:
81
+ proxy_url: The URL to use when connecting to the proxy server.
82
+ For example `"http://127.0.0.1:8080/"`.
83
+ proxy_auth: Any proxy authentication as a two-tuple of
84
+ (username, password). May be either bytes or ascii-only str.
85
+ proxy_headers: Any HTTP headers to use for the proxy requests.
86
+ For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.
87
+ ssl_context: An SSL context to use for verifying connections.
88
+ If not specified, the default `httpcore.default_ssl_context()`
89
+ will be used.
90
+ proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
91
+ max_connections: The maximum number of concurrent HTTP connections that
92
+ the pool should allow. Any attempt to send a request on a pool that
93
+ would exceed this amount will block until a connection is available.
94
+ max_keepalive_connections: The maximum number of idle HTTP connections
95
+ that will be maintained in the pool.
96
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
97
+ may be maintained for before being expired from the pool.
98
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
99
+ by the connection pool. Defaults to True.
100
+ http2: A boolean indicating if HTTP/2 requests should be supported by
101
+ the connection pool. Defaults to False.
102
+ retries: The maximum number of retries when trying to establish
103
+ a connection.
104
+ local_address: Local address to connect from. Can also be used to
105
+ connect using a particular address family. Using
106
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
107
+ (IPv4), while using `local_address="::"` will connect using an
108
+ `AF_INET6` address (IPv6).
109
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
110
+ network_backend: A backend instance to use for handling network I/O.
111
+ """
112
+ super().__init__(
113
+ ssl_context=ssl_context,
114
+ max_connections=max_connections,
115
+ max_keepalive_connections=max_keepalive_connections,
116
+ keepalive_expiry=keepalive_expiry,
117
+ http1=http1,
118
+ http2=http2,
119
+ network_backend=network_backend,
120
+ retries=retries,
121
+ local_address=local_address,
122
+ uds=uds,
123
+ socket_options=socket_options,
124
+ )
125
+
126
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
127
+ if (
128
+ self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
129
+ ): # pragma: no cover
130
+ raise RuntimeError(
131
+ "The `proxy_ssl_context` argument is not allowed for the http scheme"
132
+ )
133
+
134
+ self._ssl_context = ssl_context
135
+ self._proxy_ssl_context = proxy_ssl_context
136
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
137
+ if proxy_auth is not None:
138
+ username = enforce_bytes(proxy_auth[0], name="proxy_auth")
139
+ password = enforce_bytes(proxy_auth[1], name="proxy_auth")
140
+ userpass = username + b":" + password
141
+ authorization = b"Basic " + base64.b64encode(userpass)
142
+ self._proxy_headers = [
143
+ (b"Proxy-Authorization", authorization)
144
+ ] + self._proxy_headers
145
+
146
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
147
+ if origin.scheme == b"http":
148
+ return AsyncForwardHTTPConnection(
149
+ proxy_origin=self._proxy_url.origin,
150
+ proxy_headers=self._proxy_headers,
151
+ remote_origin=origin,
152
+ keepalive_expiry=self._keepalive_expiry,
153
+ network_backend=self._network_backend,
154
+ proxy_ssl_context=self._proxy_ssl_context,
155
+ )
156
+ return AsyncTunnelHTTPConnection(
157
+ proxy_origin=self._proxy_url.origin,
158
+ proxy_headers=self._proxy_headers,
159
+ remote_origin=origin,
160
+ ssl_context=self._ssl_context,
161
+ proxy_ssl_context=self._proxy_ssl_context,
162
+ keepalive_expiry=self._keepalive_expiry,
163
+ http1=self._http1,
164
+ http2=self._http2,
165
+ network_backend=self._network_backend,
166
+ )
167
+
168
+
169
+ class AsyncForwardHTTPConnection(AsyncConnectionInterface):
170
+ def __init__(
171
+ self,
172
+ proxy_origin: Origin,
173
+ remote_origin: Origin,
174
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
175
+ keepalive_expiry: float | None = None,
176
+ network_backend: AsyncNetworkBackend | None = None,
177
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
178
+ proxy_ssl_context: ssl.SSLContext | None = None,
179
+ ) -> None:
180
+ self._connection = AsyncHTTPConnection(
181
+ origin=proxy_origin,
182
+ keepalive_expiry=keepalive_expiry,
183
+ network_backend=network_backend,
184
+ socket_options=socket_options,
185
+ ssl_context=proxy_ssl_context,
186
+ )
187
+ self._proxy_origin = proxy_origin
188
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
189
+ self._remote_origin = remote_origin
190
+
191
+ async def handle_async_request(self, request: Request) -> Response:
192
+ headers = merge_headers(self._proxy_headers, request.headers)
193
+ url = URL(
194
+ scheme=self._proxy_origin.scheme,
195
+ host=self._proxy_origin.host,
196
+ port=self._proxy_origin.port,
197
+ target=bytes(request.url),
198
+ )
199
+ proxy_request = Request(
200
+ method=request.method,
201
+ url=url,
202
+ headers=headers,
203
+ content=request.stream,
204
+ extensions=request.extensions,
205
+ )
206
+ return await self._connection.handle_async_request(proxy_request)
207
+
208
+ def can_handle_request(self, origin: Origin) -> bool:
209
+ return origin == self._remote_origin
210
+
211
+ async def aclose(self) -> None:
212
+ await self._connection.aclose()
213
+
214
+ def info(self) -> str:
215
+ return self._connection.info()
216
+
217
+ def is_available(self) -> bool:
218
+ return self._connection.is_available()
219
+
220
+ def has_expired(self) -> bool:
221
+ return self._connection.has_expired()
222
+
223
+ def is_idle(self) -> bool:
224
+ return self._connection.is_idle()
225
+
226
+ def is_closed(self) -> bool:
227
+ return self._connection.is_closed()
228
+
229
+ def __repr__(self) -> str:
230
+ return f"<{self.__class__.__name__} [{self.info()}]>"
231
+
232
+
233
+ class AsyncTunnelHTTPConnection(AsyncConnectionInterface):
234
+ def __init__(
235
+ self,
236
+ proxy_origin: Origin,
237
+ remote_origin: Origin,
238
+ ssl_context: ssl.SSLContext | None = None,
239
+ proxy_ssl_context: ssl.SSLContext | None = None,
240
+ proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
241
+ keepalive_expiry: float | None = None,
242
+ http1: bool = True,
243
+ http2: bool = False,
244
+ network_backend: AsyncNetworkBackend | None = None,
245
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
246
+ ) -> None:
247
+ self._connection: AsyncConnectionInterface = AsyncHTTPConnection(
248
+ origin=proxy_origin,
249
+ keepalive_expiry=keepalive_expiry,
250
+ network_backend=network_backend,
251
+ socket_options=socket_options,
252
+ ssl_context=proxy_ssl_context,
253
+ )
254
+ self._proxy_origin = proxy_origin
255
+ self._remote_origin = remote_origin
256
+ self._ssl_context = ssl_context
257
+ self._proxy_ssl_context = proxy_ssl_context
258
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
259
+ self._keepalive_expiry = keepalive_expiry
260
+ self._http1 = http1
261
+ self._http2 = http2
262
+ self._connect_lock = AsyncLock()
263
+ self._connected = False
264
+
265
+ async def handle_async_request(self, request: Request) -> Response:
266
+ timeouts = request.extensions.get("timeout", {})
267
+ timeout = timeouts.get("connect", None)
268
+
269
+ async with self._connect_lock:
270
+ if not self._connected:
271
+ target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)
272
+
273
+ connect_url = URL(
274
+ scheme=self._proxy_origin.scheme,
275
+ host=self._proxy_origin.host,
276
+ port=self._proxy_origin.port,
277
+ target=target,
278
+ )
279
+ connect_headers = merge_headers(
280
+ [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
281
+ )
282
+ connect_request = Request(
283
+ method=b"CONNECT",
284
+ url=connect_url,
285
+ headers=connect_headers,
286
+ extensions=request.extensions,
287
+ )
288
+ connect_response = await self._connection.handle_async_request(
289
+ connect_request
290
+ )
291
+
292
+ if connect_response.status < 200 or connect_response.status > 299:
293
+ reason_bytes = connect_response.extensions.get("reason_phrase", b"")
294
+ reason_str = reason_bytes.decode("ascii", errors="ignore")
295
+ msg = "%d %s" % (connect_response.status, reason_str)
296
+ await self._connection.aclose()
297
+ raise ProxyError(msg)
298
+
299
+ stream = connect_response.extensions["network_stream"]
300
+
301
+ # Upgrade the stream to SSL
302
+ ssl_context = (
303
+ default_ssl_context()
304
+ if self._ssl_context is None
305
+ else self._ssl_context
306
+ )
307
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
308
+ ssl_context.set_alpn_protocols(alpn_protocols)
309
+
310
+ kwargs = {
311
+ "ssl_context": ssl_context,
312
+ "server_hostname": self._remote_origin.host.decode("ascii"),
313
+ "timeout": timeout,
314
+ }
315
+ async with Trace("start_tls", logger, request, kwargs) as trace:
316
+ stream = await stream.start_tls(**kwargs)
317
+ trace.return_value = stream
318
+
319
+ # Determine if we should be using HTTP/1.1 or HTTP/2
320
+ ssl_object = stream.get_extra_info("ssl_object")
321
+ http2_negotiated = (
322
+ ssl_object is not None
323
+ and ssl_object.selected_alpn_protocol() == "h2"
324
+ )
325
+
326
+ # Create the HTTP/1.1 or HTTP/2 connection
327
+ if http2_negotiated or (self._http2 and not self._http1):
328
+ from .http2 import AsyncHTTP2Connection
329
+
330
+ self._connection = AsyncHTTP2Connection(
331
+ origin=self._remote_origin,
332
+ stream=stream,
333
+ keepalive_expiry=self._keepalive_expiry,
334
+ )
335
+ else:
336
+ self._connection = AsyncHTTP11Connection(
337
+ origin=self._remote_origin,
338
+ stream=stream,
339
+ keepalive_expiry=self._keepalive_expiry,
340
+ )
341
+
342
+ self._connected = True
343
+ return await self._connection.handle_async_request(request)
344
+
345
+ def can_handle_request(self, origin: Origin) -> bool:
346
+ return origin == self._remote_origin
347
+
348
+ async def aclose(self) -> None:
349
+ await self._connection.aclose()
350
+
351
+ def info(self) -> str:
352
+ return self._connection.info()
353
+
354
+ def is_available(self) -> bool:
355
+ return self._connection.is_available()
356
+
357
+ def has_expired(self) -> bool:
358
+ return self._connection.has_expired()
359
+
360
+ def is_idle(self) -> bool:
361
+ return self._connection.is_idle()
362
+
363
+ def is_closed(self) -> bool:
364
+ return self._connection.is_closed()
365
+
366
+ def __repr__(self) -> str:
367
+ return f"<{self.__class__.__name__} [{self.info()}]>"
venv/lib/python3.12/site-packages/httpcore/_async/interfaces.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import typing
5
+
6
+ from .._models import (
7
+ URL,
8
+ Extensions,
9
+ HeaderTypes,
10
+ Origin,
11
+ Request,
12
+ Response,
13
+ enforce_bytes,
14
+ enforce_headers,
15
+ enforce_url,
16
+ include_request_headers,
17
+ )
18
+
19
+
20
+ class AsyncRequestInterface:
21
+ async def request(
22
+ self,
23
+ method: bytes | str,
24
+ url: URL | bytes | str,
25
+ *,
26
+ headers: HeaderTypes = None,
27
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
28
+ extensions: Extensions | None = None,
29
+ ) -> Response:
30
+ # Strict type checking on our parameters.
31
+ method = enforce_bytes(method, name="method")
32
+ url = enforce_url(url, name="url")
33
+ headers = enforce_headers(headers, name="headers")
34
+
35
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
36
+ headers = include_request_headers(headers, url=url, content=content)
37
+
38
+ request = Request(
39
+ method=method,
40
+ url=url,
41
+ headers=headers,
42
+ content=content,
43
+ extensions=extensions,
44
+ )
45
+ response = await self.handle_async_request(request)
46
+ try:
47
+ await response.aread()
48
+ finally:
49
+ await response.aclose()
50
+ return response
51
+
52
+ @contextlib.asynccontextmanager
53
+ async def stream(
54
+ self,
55
+ method: bytes | str,
56
+ url: URL | bytes | str,
57
+ *,
58
+ headers: HeaderTypes = None,
59
+ content: bytes | typing.AsyncIterator[bytes] | None = None,
60
+ extensions: Extensions | None = None,
61
+ ) -> typing.AsyncIterator[Response]:
62
+ # Strict type checking on our parameters.
63
+ method = enforce_bytes(method, name="method")
64
+ url = enforce_url(url, name="url")
65
+ headers = enforce_headers(headers, name="headers")
66
+
67
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
68
+ headers = include_request_headers(headers, url=url, content=content)
69
+
70
+ request = Request(
71
+ method=method,
72
+ url=url,
73
+ headers=headers,
74
+ content=content,
75
+ extensions=extensions,
76
+ )
77
+ response = await self.handle_async_request(request)
78
+ try:
79
+ yield response
80
+ finally:
81
+ await response.aclose()
82
+
83
+ async def handle_async_request(self, request: Request) -> Response:
84
+ raise NotImplementedError() # pragma: nocover
85
+
86
+
87
+ class AsyncConnectionInterface(AsyncRequestInterface):
88
+ async def aclose(self) -> None:
89
+ raise NotImplementedError() # pragma: nocover
90
+
91
+ def info(self) -> str:
92
+ raise NotImplementedError() # pragma: nocover
93
+
94
+ def can_handle_request(self, origin: Origin) -> bool:
95
+ raise NotImplementedError() # pragma: nocover
96
+
97
+ def is_available(self) -> bool:
98
+ """
99
+ Return `True` if the connection is currently able to accept an
100
+ outgoing request.
101
+
102
+ An HTTP/1.1 connection will only be available if it is currently idle.
103
+
104
+ An HTTP/2 connection will be available so long as the stream ID space is
105
+ not yet exhausted, and the connection is not in an error state.
106
+
107
+ While the connection is being established we may not yet know if it is going
108
+ to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
109
+ treated as being available, but might ultimately raise `NewConnectionRequired`
110
+ required exceptions if multiple requests are attempted over a connection
111
+ that ends up being established as HTTP/1.1.
112
+ """
113
+ raise NotImplementedError() # pragma: nocover
114
+
115
+ def has_expired(self) -> bool:
116
+ """
117
+ Return `True` if the connection is in a state where it should be closed.
118
+
119
+ This either means that the connection is idle and it has passed the
120
+ expiry time on its keep-alive, or that server has sent an EOF.
121
+ """
122
+ raise NotImplementedError() # pragma: nocover
123
+
124
+ def is_idle(self) -> bool:
125
+ """
126
+ Return `True` if the connection is currently idle.
127
+ """
128
+ raise NotImplementedError() # pragma: nocover
129
+
130
+ def is_closed(self) -> bool:
131
+ """
132
+ Return `True` if the connection has been closed.
133
+
134
+ Used when a response is closed to determine if the connection may be
135
+ returned to the connection pool or not.
136
+ """
137
+ raise NotImplementedError() # pragma: nocover
venv/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import ssl
5
+
6
+ import socksio
7
+
8
+ from .._backends.auto import AutoBackend
9
+ from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream
10
+ from .._exceptions import ConnectionNotAvailable, ProxyError
11
+ from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
12
+ from .._ssl import default_ssl_context
13
+ from .._synchronization import AsyncLock
14
+ from .._trace import Trace
15
+ from .connection_pool import AsyncConnectionPool
16
+ from .http11 import AsyncHTTP11Connection
17
+ from .interfaces import AsyncConnectionInterface
18
+
19
+ logger = logging.getLogger("httpcore.socks")
20
+
21
+
22
+ AUTH_METHODS = {
23
+ b"\x00": "NO AUTHENTICATION REQUIRED",
24
+ b"\x01": "GSSAPI",
25
+ b"\x02": "USERNAME/PASSWORD",
26
+ b"\xff": "NO ACCEPTABLE METHODS",
27
+ }
28
+
29
+ REPLY_CODES = {
30
+ b"\x00": "Succeeded",
31
+ b"\x01": "General SOCKS server failure",
32
+ b"\x02": "Connection not allowed by ruleset",
33
+ b"\x03": "Network unreachable",
34
+ b"\x04": "Host unreachable",
35
+ b"\x05": "Connection refused",
36
+ b"\x06": "TTL expired",
37
+ b"\x07": "Command not supported",
38
+ b"\x08": "Address type not supported",
39
+ }
40
+
41
+
42
+ async def _init_socks5_connection(
43
+ stream: AsyncNetworkStream,
44
+ *,
45
+ host: bytes,
46
+ port: int,
47
+ auth: tuple[bytes, bytes] | None = None,
48
+ ) -> None:
49
+ conn = socksio.socks5.SOCKS5Connection()
50
+
51
+ # Auth method request
52
+ auth_method = (
53
+ socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
54
+ if auth is None
55
+ else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
56
+ )
57
+ conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
58
+ outgoing_bytes = conn.data_to_send()
59
+ await stream.write(outgoing_bytes)
60
+
61
+ # Auth method response
62
+ incoming_bytes = await stream.read(max_bytes=4096)
63
+ response = conn.receive_data(incoming_bytes)
64
+ assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
65
+ if response.method != auth_method:
66
+ requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
67
+ responded = AUTH_METHODS.get(response.method, "UNKNOWN")
68
+ raise ProxyError(
69
+ f"Requested {requested} from proxy server, but got {responded}."
70
+ )
71
+
72
+ if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
73
+ # Username/password request
74
+ assert auth is not None
75
+ username, password = auth
76
+ conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
77
+ outgoing_bytes = conn.data_to_send()
78
+ await stream.write(outgoing_bytes)
79
+
80
+ # Username/password response
81
+ incoming_bytes = await stream.read(max_bytes=4096)
82
+ response = conn.receive_data(incoming_bytes)
83
+ assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
84
+ if not response.success:
85
+ raise ProxyError("Invalid username/password")
86
+
87
+ # Connect request
88
+ conn.send(
89
+ socksio.socks5.SOCKS5CommandRequest.from_address(
90
+ socksio.socks5.SOCKS5Command.CONNECT, (host, port)
91
+ )
92
+ )
93
+ outgoing_bytes = conn.data_to_send()
94
+ await stream.write(outgoing_bytes)
95
+
96
+ # Connect response
97
+ incoming_bytes = await stream.read(max_bytes=4096)
98
+ response = conn.receive_data(incoming_bytes)
99
+ assert isinstance(response, socksio.socks5.SOCKS5Reply)
100
+ if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
101
+ reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
102
+ raise ProxyError(f"Proxy Server could not connect: {reply_code}.")
103
+
104
+
105
+ class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover
106
+ """
107
+ A connection pool that sends requests via an HTTP proxy.
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ proxy_url: URL | bytes | str,
113
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
114
+ ssl_context: ssl.SSLContext | None = None,
115
+ max_connections: int | None = 10,
116
+ max_keepalive_connections: int | None = None,
117
+ keepalive_expiry: float | None = None,
118
+ http1: bool = True,
119
+ http2: bool = False,
120
+ retries: int = 0,
121
+ network_backend: AsyncNetworkBackend | None = None,
122
+ ) -> None:
123
+ """
124
+ A connection pool for making HTTP requests.
125
+
126
+ Parameters:
127
+ proxy_url: The URL to use when connecting to the proxy server.
128
+ For example `"http://127.0.0.1:8080/"`.
129
+ ssl_context: An SSL context to use for verifying connections.
130
+ If not specified, the default `httpcore.default_ssl_context()`
131
+ will be used.
132
+ max_connections: The maximum number of concurrent HTTP connections that
133
+ the pool should allow. Any attempt to send a request on a pool that
134
+ would exceed this amount will block until a connection is available.
135
+ max_keepalive_connections: The maximum number of idle HTTP connections
136
+ that will be maintained in the pool.
137
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
138
+ may be maintained for before being expired from the pool.
139
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
140
+ by the connection pool. Defaults to True.
141
+ http2: A boolean indicating if HTTP/2 requests should be supported by
142
+ the connection pool. Defaults to False.
143
+ retries: The maximum number of retries when trying to establish
144
+ a connection.
145
+ local_address: Local address to connect from. Can also be used to
146
+ connect using a particular address family. Using
147
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
148
+ (IPv4), while using `local_address="::"` will connect using an
149
+ `AF_INET6` address (IPv6).
150
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
151
+ network_backend: A backend instance to use for handling network I/O.
152
+ """
153
+ super().__init__(
154
+ ssl_context=ssl_context,
155
+ max_connections=max_connections,
156
+ max_keepalive_connections=max_keepalive_connections,
157
+ keepalive_expiry=keepalive_expiry,
158
+ http1=http1,
159
+ http2=http2,
160
+ network_backend=network_backend,
161
+ retries=retries,
162
+ )
163
+ self._ssl_context = ssl_context
164
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
165
+ if proxy_auth is not None:
166
+ username, password = proxy_auth
167
+ username_bytes = enforce_bytes(username, name="proxy_auth")
168
+ password_bytes = enforce_bytes(password, name="proxy_auth")
169
+ self._proxy_auth: tuple[bytes, bytes] | None = (
170
+ username_bytes,
171
+ password_bytes,
172
+ )
173
+ else:
174
+ self._proxy_auth = None
175
+
176
+ def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
177
+ return AsyncSocks5Connection(
178
+ proxy_origin=self._proxy_url.origin,
179
+ remote_origin=origin,
180
+ proxy_auth=self._proxy_auth,
181
+ ssl_context=self._ssl_context,
182
+ keepalive_expiry=self._keepalive_expiry,
183
+ http1=self._http1,
184
+ http2=self._http2,
185
+ network_backend=self._network_backend,
186
+ )
187
+
188
+
189
+ class AsyncSocks5Connection(AsyncConnectionInterface):
190
+ def __init__(
191
+ self,
192
+ proxy_origin: Origin,
193
+ remote_origin: Origin,
194
+ proxy_auth: tuple[bytes, bytes] | None = None,
195
+ ssl_context: ssl.SSLContext | None = None,
196
+ keepalive_expiry: float | None = None,
197
+ http1: bool = True,
198
+ http2: bool = False,
199
+ network_backend: AsyncNetworkBackend | None = None,
200
+ ) -> None:
201
+ self._proxy_origin = proxy_origin
202
+ self._remote_origin = remote_origin
203
+ self._proxy_auth = proxy_auth
204
+ self._ssl_context = ssl_context
205
+ self._keepalive_expiry = keepalive_expiry
206
+ self._http1 = http1
207
+ self._http2 = http2
208
+
209
+ self._network_backend: AsyncNetworkBackend = (
210
+ AutoBackend() if network_backend is None else network_backend
211
+ )
212
+ self._connect_lock = AsyncLock()
213
+ self._connection: AsyncConnectionInterface | None = None
214
+ self._connect_failed = False
215
+
216
+ async def handle_async_request(self, request: Request) -> Response:
217
+ timeouts = request.extensions.get("timeout", {})
218
+ sni_hostname = request.extensions.get("sni_hostname", None)
219
+ timeout = timeouts.get("connect", None)
220
+
221
+ async with self._connect_lock:
222
+ if self._connection is None:
223
+ try:
224
+ # Connect to the proxy
225
+ kwargs = {
226
+ "host": self._proxy_origin.host.decode("ascii"),
227
+ "port": self._proxy_origin.port,
228
+ "timeout": timeout,
229
+ }
230
+ async with Trace("connect_tcp", logger, request, kwargs) as trace:
231
+ stream = await self._network_backend.connect_tcp(**kwargs)
232
+ trace.return_value = stream
233
+
234
+ # Connect to the remote host using socks5
235
+ kwargs = {
236
+ "stream": stream,
237
+ "host": self._remote_origin.host.decode("ascii"),
238
+ "port": self._remote_origin.port,
239
+ "auth": self._proxy_auth,
240
+ }
241
+ async with Trace(
242
+ "setup_socks5_connection", logger, request, kwargs
243
+ ) as trace:
244
+ await _init_socks5_connection(**kwargs)
245
+ trace.return_value = stream
246
+
247
+ # Upgrade the stream to SSL
248
+ if self._remote_origin.scheme == b"https":
249
+ ssl_context = (
250
+ default_ssl_context()
251
+ if self._ssl_context is None
252
+ else self._ssl_context
253
+ )
254
+ alpn_protocols = (
255
+ ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
256
+ )
257
+ ssl_context.set_alpn_protocols(alpn_protocols)
258
+
259
+ kwargs = {
260
+ "ssl_context": ssl_context,
261
+ "server_hostname": sni_hostname
262
+ or self._remote_origin.host.decode("ascii"),
263
+ "timeout": timeout,
264
+ }
265
+ async with Trace("start_tls", logger, request, kwargs) as trace:
266
+ stream = await stream.start_tls(**kwargs)
267
+ trace.return_value = stream
268
+
269
+ # Determine if we should be using HTTP/1.1 or HTTP/2
270
+ ssl_object = stream.get_extra_info("ssl_object")
271
+ http2_negotiated = (
272
+ ssl_object is not None
273
+ and ssl_object.selected_alpn_protocol() == "h2"
274
+ )
275
+
276
+ # Create the HTTP/1.1 or HTTP/2 connection
277
+ if http2_negotiated or (
278
+ self._http2 and not self._http1
279
+ ): # pragma: nocover
280
+ from .http2 import AsyncHTTP2Connection
281
+
282
+ self._connection = AsyncHTTP2Connection(
283
+ origin=self._remote_origin,
284
+ stream=stream,
285
+ keepalive_expiry=self._keepalive_expiry,
286
+ )
287
+ else:
288
+ self._connection = AsyncHTTP11Connection(
289
+ origin=self._remote_origin,
290
+ stream=stream,
291
+ keepalive_expiry=self._keepalive_expiry,
292
+ )
293
+ except Exception as exc:
294
+ self._connect_failed = True
295
+ raise exc
296
+ elif not self._connection.is_available(): # pragma: nocover
297
+ raise ConnectionNotAvailable()
298
+
299
+ return await self._connection.handle_async_request(request)
300
+
301
+ def can_handle_request(self, origin: Origin) -> bool:
302
+ return origin == self._remote_origin
303
+
304
+ async def aclose(self) -> None:
305
+ if self._connection is not None:
306
+ await self._connection.aclose()
307
+
308
+ def is_available(self) -> bool:
309
+ if self._connection is None: # pragma: nocover
310
+ # If HTTP/2 support is enabled, and the resulting connection could
311
+ # end up as HTTP/2 then we should indicate the connection as being
312
+ # available to service multiple requests.
313
+ return (
314
+ self._http2
315
+ and (self._remote_origin.scheme == b"https" or not self._http1)
316
+ and not self._connect_failed
317
+ )
318
+ return self._connection.is_available()
319
+
320
+ def has_expired(self) -> bool:
321
+ if self._connection is None: # pragma: nocover
322
+ return self._connect_failed
323
+ return self._connection.has_expired()
324
+
325
+ def is_idle(self) -> bool:
326
+ if self._connection is None: # pragma: nocover
327
+ return self._connect_failed
328
+ return self._connection.is_idle()
329
+
330
+ def is_closed(self) -> bool:
331
+ if self._connection is None: # pragma: nocover
332
+ return self._connect_failed
333
+ return self._connection.is_closed()
334
+
335
+ def info(self) -> str:
336
+ if self._connection is None: # pragma: nocover
337
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
338
+ return self._connection.info()
339
+
340
+ def __repr__(self) -> str:
341
+ return f"<{self.__class__.__name__} [{self.info()}]>"
venv/lib/python3.12/site-packages/httpcore/_backends/__init__.py ADDED
File without changes
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (180 Bytes). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc ADDED
Binary file (8.61 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc ADDED
Binary file (2.67 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc ADDED
Binary file (4.87 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc ADDED
Binary file (7.14 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc ADDED
Binary file (11.4 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc ADDED
Binary file (8.95 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_backends/anyio.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import typing
5
+
6
+ import anyio
7
+
8
+ from .._exceptions import (
9
+ ConnectError,
10
+ ConnectTimeout,
11
+ ReadError,
12
+ ReadTimeout,
13
+ WriteError,
14
+ WriteTimeout,
15
+ map_exceptions,
16
+ )
17
+ from .._utils import is_socket_readable
18
+ from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
19
+
20
+
21
+ class AnyIOStream(AsyncNetworkStream):
22
+ def __init__(self, stream: anyio.abc.ByteStream) -> None:
23
+ self._stream = stream
24
+
25
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
26
+ exc_map = {
27
+ TimeoutError: ReadTimeout,
28
+ anyio.BrokenResourceError: ReadError,
29
+ anyio.ClosedResourceError: ReadError,
30
+ anyio.EndOfStream: ReadError,
31
+ }
32
+ with map_exceptions(exc_map):
33
+ with anyio.fail_after(timeout):
34
+ try:
35
+ return await self._stream.receive(max_bytes=max_bytes)
36
+ except anyio.EndOfStream: # pragma: nocover
37
+ return b""
38
+
39
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
40
+ if not buffer:
41
+ return
42
+
43
+ exc_map = {
44
+ TimeoutError: WriteTimeout,
45
+ anyio.BrokenResourceError: WriteError,
46
+ anyio.ClosedResourceError: WriteError,
47
+ }
48
+ with map_exceptions(exc_map):
49
+ with anyio.fail_after(timeout):
50
+ await self._stream.send(item=buffer)
51
+
52
+ async def aclose(self) -> None:
53
+ await self._stream.aclose()
54
+
55
+ async def start_tls(
56
+ self,
57
+ ssl_context: ssl.SSLContext,
58
+ server_hostname: str | None = None,
59
+ timeout: float | None = None,
60
+ ) -> AsyncNetworkStream:
61
+ exc_map = {
62
+ TimeoutError: ConnectTimeout,
63
+ anyio.BrokenResourceError: ConnectError,
64
+ anyio.EndOfStream: ConnectError,
65
+ ssl.SSLError: ConnectError,
66
+ }
67
+ with map_exceptions(exc_map):
68
+ try:
69
+ with anyio.fail_after(timeout):
70
+ ssl_stream = await anyio.streams.tls.TLSStream.wrap(
71
+ self._stream,
72
+ ssl_context=ssl_context,
73
+ hostname=server_hostname,
74
+ standard_compatible=False,
75
+ server_side=False,
76
+ )
77
+ except Exception as exc: # pragma: nocover
78
+ await self.aclose()
79
+ raise exc
80
+ return AnyIOStream(ssl_stream)
81
+
82
+ def get_extra_info(self, info: str) -> typing.Any:
83
+ if info == "ssl_object":
84
+ return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None)
85
+ if info == "client_addr":
86
+ return self._stream.extra(anyio.abc.SocketAttribute.local_address, None)
87
+ if info == "server_addr":
88
+ return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None)
89
+ if info == "socket":
90
+ return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
91
+ if info == "is_readable":
92
+ sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None)
93
+ return is_socket_readable(sock)
94
+ return None
95
+
96
+
97
+ class AnyIOBackend(AsyncNetworkBackend):
98
+ async def connect_tcp(
99
+ self,
100
+ host: str,
101
+ port: int,
102
+ timeout: float | None = None,
103
+ local_address: str | None = None,
104
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
105
+ ) -> AsyncNetworkStream: # pragma: nocover
106
+ if socket_options is None:
107
+ socket_options = []
108
+ exc_map = {
109
+ TimeoutError: ConnectTimeout,
110
+ OSError: ConnectError,
111
+ anyio.BrokenResourceError: ConnectError,
112
+ }
113
+ with map_exceptions(exc_map):
114
+ with anyio.fail_after(timeout):
115
+ stream: anyio.abc.ByteStream = await anyio.connect_tcp(
116
+ remote_host=host,
117
+ remote_port=port,
118
+ local_host=local_address,
119
+ )
120
+ # By default TCP sockets opened in `asyncio` include TCP_NODELAY.
121
+ for option in socket_options:
122
+ stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
123
+ return AnyIOStream(stream)
124
+
125
+ async def connect_unix_socket(
126
+ self,
127
+ path: str,
128
+ timeout: float | None = None,
129
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
130
+ ) -> AsyncNetworkStream: # pragma: nocover
131
+ if socket_options is None:
132
+ socket_options = []
133
+ exc_map = {
134
+ TimeoutError: ConnectTimeout,
135
+ OSError: ConnectError,
136
+ anyio.BrokenResourceError: ConnectError,
137
+ }
138
+ with map_exceptions(exc_map):
139
+ with anyio.fail_after(timeout):
140
+ stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
141
+ for option in socket_options:
142
+ stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
143
+ return AnyIOStream(stream)
144
+
145
+ async def sleep(self, seconds: float) -> None:
146
+ await anyio.sleep(seconds) # pragma: nocover
venv/lib/python3.12/site-packages/httpcore/_backends/auto.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from .._synchronization import current_async_library
6
+ from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
7
+
8
+
9
+ class AutoBackend(AsyncNetworkBackend):
10
+ async def _init_backend(self) -> None:
11
+ if not (hasattr(self, "_backend")):
12
+ backend = current_async_library()
13
+ if backend == "trio":
14
+ from .trio import TrioBackend
15
+
16
+ self._backend: AsyncNetworkBackend = TrioBackend()
17
+ else:
18
+ from .anyio import AnyIOBackend
19
+
20
+ self._backend = AnyIOBackend()
21
+
22
+ async def connect_tcp(
23
+ self,
24
+ host: str,
25
+ port: int,
26
+ timeout: float | None = None,
27
+ local_address: str | None = None,
28
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
29
+ ) -> AsyncNetworkStream:
30
+ await self._init_backend()
31
+ return await self._backend.connect_tcp(
32
+ host,
33
+ port,
34
+ timeout=timeout,
35
+ local_address=local_address,
36
+ socket_options=socket_options,
37
+ )
38
+
39
+ async def connect_unix_socket(
40
+ self,
41
+ path: str,
42
+ timeout: float | None = None,
43
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
44
+ ) -> AsyncNetworkStream: # pragma: nocover
45
+ await self._init_backend()
46
+ return await self._backend.connect_unix_socket(
47
+ path, timeout=timeout, socket_options=socket_options
48
+ )
49
+
50
+ async def sleep(self, seconds: float) -> None: # pragma: nocover
51
+ await self._init_backend()
52
+ return await self._backend.sleep(seconds)
venv/lib/python3.12/site-packages/httpcore/_backends/base.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import time
5
+ import typing
6
+
7
+ SOCKET_OPTION = typing.Union[
8
+ typing.Tuple[int, int, int],
9
+ typing.Tuple[int, int, typing.Union[bytes, bytearray]],
10
+ typing.Tuple[int, int, None, int],
11
+ ]
12
+
13
+
14
+ class NetworkStream:
15
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
16
+ raise NotImplementedError() # pragma: nocover
17
+
18
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
19
+ raise NotImplementedError() # pragma: nocover
20
+
21
+ def close(self) -> None:
22
+ raise NotImplementedError() # pragma: nocover
23
+
24
+ def start_tls(
25
+ self,
26
+ ssl_context: ssl.SSLContext,
27
+ server_hostname: str | None = None,
28
+ timeout: float | None = None,
29
+ ) -> NetworkStream:
30
+ raise NotImplementedError() # pragma: nocover
31
+
32
+ def get_extra_info(self, info: str) -> typing.Any:
33
+ return None # pragma: nocover
34
+
35
+
36
+ class NetworkBackend:
37
+ def connect_tcp(
38
+ self,
39
+ host: str,
40
+ port: int,
41
+ timeout: float | None = None,
42
+ local_address: str | None = None,
43
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
44
+ ) -> NetworkStream:
45
+ raise NotImplementedError() # pragma: nocover
46
+
47
+ def connect_unix_socket(
48
+ self,
49
+ path: str,
50
+ timeout: float | None = None,
51
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
52
+ ) -> NetworkStream:
53
+ raise NotImplementedError() # pragma: nocover
54
+
55
+ def sleep(self, seconds: float) -> None:
56
+ time.sleep(seconds) # pragma: nocover
57
+
58
+
59
+ class AsyncNetworkStream:
60
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
61
+ raise NotImplementedError() # pragma: nocover
62
+
63
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
64
+ raise NotImplementedError() # pragma: nocover
65
+
66
+ async def aclose(self) -> None:
67
+ raise NotImplementedError() # pragma: nocover
68
+
69
+ async def start_tls(
70
+ self,
71
+ ssl_context: ssl.SSLContext,
72
+ server_hostname: str | None = None,
73
+ timeout: float | None = None,
74
+ ) -> AsyncNetworkStream:
75
+ raise NotImplementedError() # pragma: nocover
76
+
77
+ def get_extra_info(self, info: str) -> typing.Any:
78
+ return None # pragma: nocover
79
+
80
+
81
+ class AsyncNetworkBackend:
82
+ async def connect_tcp(
83
+ self,
84
+ host: str,
85
+ port: int,
86
+ timeout: float | None = None,
87
+ local_address: str | None = None,
88
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
89
+ ) -> AsyncNetworkStream:
90
+ raise NotImplementedError() # pragma: nocover
91
+
92
+ async def connect_unix_socket(
93
+ self,
94
+ path: str,
95
+ timeout: float | None = None,
96
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
97
+ ) -> AsyncNetworkStream:
98
+ raise NotImplementedError() # pragma: nocover
99
+
100
+ async def sleep(self, seconds: float) -> None:
101
+ raise NotImplementedError() # pragma: nocover
venv/lib/python3.12/site-packages/httpcore/_backends/mock.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import typing
5
+
6
+ from .._exceptions import ReadError
7
+ from .base import (
8
+ SOCKET_OPTION,
9
+ AsyncNetworkBackend,
10
+ AsyncNetworkStream,
11
+ NetworkBackend,
12
+ NetworkStream,
13
+ )
14
+
15
+
16
+ class MockSSLObject:
17
+ def __init__(self, http2: bool):
18
+ self._http2 = http2
19
+
20
+ def selected_alpn_protocol(self) -> str:
21
+ return "h2" if self._http2 else "http/1.1"
22
+
23
+
24
+ class MockStream(NetworkStream):
25
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
26
+ self._buffer = buffer
27
+ self._http2 = http2
28
+ self._closed = False
29
+
30
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
31
+ if self._closed:
32
+ raise ReadError("Connection closed")
33
+ if not self._buffer:
34
+ return b""
35
+ return self._buffer.pop(0)
36
+
37
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
38
+ pass
39
+
40
+ def close(self) -> None:
41
+ self._closed = True
42
+
43
+ def start_tls(
44
+ self,
45
+ ssl_context: ssl.SSLContext,
46
+ server_hostname: str | None = None,
47
+ timeout: float | None = None,
48
+ ) -> NetworkStream:
49
+ return self
50
+
51
+ def get_extra_info(self, info: str) -> typing.Any:
52
+ return MockSSLObject(http2=self._http2) if info == "ssl_object" else None
53
+
54
+ def __repr__(self) -> str:
55
+ return "<httpcore.MockStream>"
56
+
57
+
58
+ class MockBackend(NetworkBackend):
59
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
60
+ self._buffer = buffer
61
+ self._http2 = http2
62
+
63
+ def connect_tcp(
64
+ self,
65
+ host: str,
66
+ port: int,
67
+ timeout: float | None = None,
68
+ local_address: str | None = None,
69
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
70
+ ) -> NetworkStream:
71
+ return MockStream(list(self._buffer), http2=self._http2)
72
+
73
+ def connect_unix_socket(
74
+ self,
75
+ path: str,
76
+ timeout: float | None = None,
77
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
78
+ ) -> NetworkStream:
79
+ return MockStream(list(self._buffer), http2=self._http2)
80
+
81
+ def sleep(self, seconds: float) -> None:
82
+ pass
83
+
84
+
85
+ class AsyncMockStream(AsyncNetworkStream):
86
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
87
+ self._buffer = buffer
88
+ self._http2 = http2
89
+ self._closed = False
90
+
91
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
92
+ if self._closed:
93
+ raise ReadError("Connection closed")
94
+ if not self._buffer:
95
+ return b""
96
+ return self._buffer.pop(0)
97
+
98
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
99
+ pass
100
+
101
+ async def aclose(self) -> None:
102
+ self._closed = True
103
+
104
+ async def start_tls(
105
+ self,
106
+ ssl_context: ssl.SSLContext,
107
+ server_hostname: str | None = None,
108
+ timeout: float | None = None,
109
+ ) -> AsyncNetworkStream:
110
+ return self
111
+
112
+ def get_extra_info(self, info: str) -> typing.Any:
113
+ return MockSSLObject(http2=self._http2) if info == "ssl_object" else None
114
+
115
+ def __repr__(self) -> str:
116
+ return "<httpcore.AsyncMockStream>"
117
+
118
+
119
+ class AsyncMockBackend(AsyncNetworkBackend):
120
+ def __init__(self, buffer: list[bytes], http2: bool = False) -> None:
121
+ self._buffer = buffer
122
+ self._http2 = http2
123
+
124
+ async def connect_tcp(
125
+ self,
126
+ host: str,
127
+ port: int,
128
+ timeout: float | None = None,
129
+ local_address: str | None = None,
130
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
131
+ ) -> AsyncNetworkStream:
132
+ return AsyncMockStream(list(self._buffer), http2=self._http2)
133
+
134
+ async def connect_unix_socket(
135
+ self,
136
+ path: str,
137
+ timeout: float | None = None,
138
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
139
+ ) -> AsyncNetworkStream:
140
+ return AsyncMockStream(list(self._buffer), http2=self._http2)
141
+
142
+ async def sleep(self, seconds: float) -> None:
143
+ pass
venv/lib/python3.12/site-packages/httpcore/_backends/sync.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import socket
5
+ import ssl
6
+ import sys
7
+ import typing
8
+
9
+ from .._exceptions import (
10
+ ConnectError,
11
+ ConnectTimeout,
12
+ ExceptionMapping,
13
+ ReadError,
14
+ ReadTimeout,
15
+ WriteError,
16
+ WriteTimeout,
17
+ map_exceptions,
18
+ )
19
+ from .._utils import is_socket_readable
20
+ from .base import SOCKET_OPTION, NetworkBackend, NetworkStream
21
+
22
+
23
+ class TLSinTLSStream(NetworkStream): # pragma: no cover
24
+ """
25
+ Because the standard `SSLContext.wrap_socket` method does
26
+ not work for `SSLSocket` objects, we need this class
27
+ to implement TLS stream using an underlying `SSLObject`
28
+ instance in order to support TLS on top of TLS.
29
+ """
30
+
31
+ # Defined in RFC 8449
32
+ TLS_RECORD_SIZE = 16384
33
+
34
+ def __init__(
35
+ self,
36
+ sock: socket.socket,
37
+ ssl_context: ssl.SSLContext,
38
+ server_hostname: str | None = None,
39
+ timeout: float | None = None,
40
+ ):
41
+ self._sock = sock
42
+ self._incoming = ssl.MemoryBIO()
43
+ self._outgoing = ssl.MemoryBIO()
44
+
45
+ self.ssl_obj = ssl_context.wrap_bio(
46
+ incoming=self._incoming,
47
+ outgoing=self._outgoing,
48
+ server_hostname=server_hostname,
49
+ )
50
+
51
+ self._sock.settimeout(timeout)
52
+ self._perform_io(self.ssl_obj.do_handshake)
53
+
54
+ def _perform_io(
55
+ self,
56
+ func: typing.Callable[..., typing.Any],
57
+ ) -> typing.Any:
58
+ ret = None
59
+
60
+ while True:
61
+ errno = None
62
+ try:
63
+ ret = func()
64
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e:
65
+ errno = e.errno
66
+
67
+ self._sock.sendall(self._outgoing.read())
68
+
69
+ if errno == ssl.SSL_ERROR_WANT_READ:
70
+ buf = self._sock.recv(self.TLS_RECORD_SIZE)
71
+
72
+ if buf:
73
+ self._incoming.write(buf)
74
+ else:
75
+ self._incoming.write_eof()
76
+ if errno is None:
77
+ return ret
78
+
79
+ 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):
82
+ self._sock.settimeout(timeout)
83
+ return typing.cast(
84
+ bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes))
85
+ )
86
+
87
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
88
+ exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
89
+ with map_exceptions(exc_map):
90
+ self._sock.settimeout(timeout)
91
+ while buffer:
92
+ nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer))
93
+ buffer = buffer[nsent:]
94
+
95
+ def close(self) -> None:
96
+ self._sock.close()
97
+
98
+ def start_tls(
99
+ self,
100
+ ssl_context: ssl.SSLContext,
101
+ server_hostname: str | None = None,
102
+ timeout: float | None = None,
103
+ ) -> NetworkStream:
104
+ raise NotImplementedError()
105
+
106
+ def get_extra_info(self, info: str) -> typing.Any:
107
+ if info == "ssl_object":
108
+ return self.ssl_obj
109
+ if info == "client_addr":
110
+ return self._sock.getsockname()
111
+ if info == "server_addr":
112
+ return self._sock.getpeername()
113
+ if info == "socket":
114
+ return self._sock
115
+ if info == "is_readable":
116
+ return is_socket_readable(self._sock)
117
+ return None
118
+
119
+
120
+ class SyncStream(NetworkStream):
121
+ def __init__(self, sock: socket.socket) -> None:
122
+ self._sock = sock
123
+
124
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
125
+ exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError}
126
+ with map_exceptions(exc_map):
127
+ self._sock.settimeout(timeout)
128
+ return self._sock.recv(max_bytes)
129
+
130
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
131
+ if not buffer:
132
+ return
133
+
134
+ exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError}
135
+ with map_exceptions(exc_map):
136
+ while buffer:
137
+ self._sock.settimeout(timeout)
138
+ n = self._sock.send(buffer)
139
+ buffer = buffer[n:]
140
+
141
+ def close(self) -> None:
142
+ self._sock.close()
143
+
144
+ def start_tls(
145
+ self,
146
+ ssl_context: ssl.SSLContext,
147
+ server_hostname: str | None = None,
148
+ timeout: float | None = None,
149
+ ) -> NetworkStream:
150
+ exc_map: ExceptionMapping = {
151
+ socket.timeout: ConnectTimeout,
152
+ OSError: ConnectError,
153
+ }
154
+ with map_exceptions(exc_map):
155
+ try:
156
+ if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover
157
+ # If the underlying socket has already been upgraded
158
+ # to the TLS layer (i.e. is an instance of SSLSocket),
159
+ # we need some additional smarts to support TLS-in-TLS.
160
+ return TLSinTLSStream(
161
+ self._sock, ssl_context, server_hostname, timeout
162
+ )
163
+ else:
164
+ self._sock.settimeout(timeout)
165
+ sock = ssl_context.wrap_socket(
166
+ self._sock, server_hostname=server_hostname
167
+ )
168
+ except Exception as exc: # pragma: nocover
169
+ self.close()
170
+ raise exc
171
+ return SyncStream(sock)
172
+
173
+ def get_extra_info(self, info: str) -> typing.Any:
174
+ if info == "ssl_object" and isinstance(self._sock, ssl.SSLSocket):
175
+ return self._sock._sslobj # type: ignore
176
+ if info == "client_addr":
177
+ return self._sock.getsockname()
178
+ if info == "server_addr":
179
+ return self._sock.getpeername()
180
+ if info == "socket":
181
+ return self._sock
182
+ if info == "is_readable":
183
+ return is_socket_readable(self._sock)
184
+ return None
185
+
186
+
187
+ class SyncBackend(NetworkBackend):
188
+ def connect_tcp(
189
+ self,
190
+ host: str,
191
+ port: int,
192
+ timeout: float | None = None,
193
+ local_address: str | None = None,
194
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
195
+ ) -> NetworkStream:
196
+ # Note that we automatically include `TCP_NODELAY`
197
+ # in addition to any other custom socket options.
198
+ if socket_options is None:
199
+ socket_options = [] # pragma: no cover
200
+ address = (host, port)
201
+ source_address = None if local_address is None else (local_address, 0)
202
+ exc_map: ExceptionMapping = {
203
+ socket.timeout: ConnectTimeout,
204
+ OSError: ConnectError,
205
+ }
206
+
207
+ with map_exceptions(exc_map):
208
+ sock = socket.create_connection(
209
+ address,
210
+ timeout,
211
+ source_address=source_address,
212
+ )
213
+ for option in socket_options:
214
+ sock.setsockopt(*option) # pragma: no cover
215
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
216
+ return SyncStream(sock)
217
+
218
+ def connect_unix_socket(
219
+ self,
220
+ path: str,
221
+ timeout: float | None = None,
222
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
223
+ ) -> NetworkStream: # pragma: nocover
224
+ if sys.platform == "win32":
225
+ raise RuntimeError(
226
+ "Attempted to connect to a UNIX socket on a Windows system."
227
+ )
228
+ if socket_options is None:
229
+ socket_options = []
230
+
231
+ exc_map: ExceptionMapping = {
232
+ socket.timeout: ConnectTimeout,
233
+ OSError: ConnectError,
234
+ }
235
+ with map_exceptions(exc_map):
236
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
237
+ for option in socket_options:
238
+ sock.setsockopt(*option)
239
+ sock.settimeout(timeout)
240
+ sock.connect(path)
241
+ return SyncStream(sock)
venv/lib/python3.12/site-packages/httpcore/_backends/trio.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import typing
5
+
6
+ import trio
7
+
8
+ from .._exceptions import (
9
+ ConnectError,
10
+ ConnectTimeout,
11
+ ExceptionMapping,
12
+ ReadError,
13
+ ReadTimeout,
14
+ WriteError,
15
+ WriteTimeout,
16
+ map_exceptions,
17
+ )
18
+ from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
19
+
20
+
21
+ class TrioStream(AsyncNetworkStream):
22
+ def __init__(self, stream: trio.abc.Stream) -> None:
23
+ self._stream = stream
24
+
25
+ async def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
26
+ timeout_or_inf = float("inf") if timeout is None else timeout
27
+ exc_map: ExceptionMapping = {
28
+ trio.TooSlowError: ReadTimeout,
29
+ trio.BrokenResourceError: ReadError,
30
+ trio.ClosedResourceError: ReadError,
31
+ }
32
+ with map_exceptions(exc_map):
33
+ with trio.fail_after(timeout_or_inf):
34
+ data: bytes = await self._stream.receive_some(max_bytes=max_bytes)
35
+ return data
36
+
37
+ async def write(self, buffer: bytes, timeout: float | None = None) -> None:
38
+ if not buffer:
39
+ return
40
+
41
+ timeout_or_inf = float("inf") if timeout is None else timeout
42
+ exc_map: ExceptionMapping = {
43
+ trio.TooSlowError: WriteTimeout,
44
+ trio.BrokenResourceError: WriteError,
45
+ trio.ClosedResourceError: WriteError,
46
+ }
47
+ with map_exceptions(exc_map):
48
+ with trio.fail_after(timeout_or_inf):
49
+ await self._stream.send_all(data=buffer)
50
+
51
+ async def aclose(self) -> None:
52
+ await self._stream.aclose()
53
+
54
+ async def start_tls(
55
+ self,
56
+ ssl_context: ssl.SSLContext,
57
+ server_hostname: str | None = None,
58
+ timeout: float | None = None,
59
+ ) -> AsyncNetworkStream:
60
+ timeout_or_inf = float("inf") if timeout is None else timeout
61
+ exc_map: ExceptionMapping = {
62
+ trio.TooSlowError: ConnectTimeout,
63
+ trio.BrokenResourceError: ConnectError,
64
+ }
65
+ ssl_stream = trio.SSLStream(
66
+ self._stream,
67
+ ssl_context=ssl_context,
68
+ server_hostname=server_hostname,
69
+ https_compatible=True,
70
+ server_side=False,
71
+ )
72
+ with map_exceptions(exc_map):
73
+ try:
74
+ with trio.fail_after(timeout_or_inf):
75
+ await ssl_stream.do_handshake()
76
+ except Exception as exc: # pragma: nocover
77
+ await self.aclose()
78
+ raise exc
79
+ return TrioStream(ssl_stream)
80
+
81
+ def get_extra_info(self, info: str) -> typing.Any:
82
+ if info == "ssl_object" and isinstance(self._stream, trio.SSLStream):
83
+ # Type checkers cannot see `_ssl_object` attribute because trio._ssl.SSLStream uses __getattr__/__setattr__.
84
+ # Tracked at https://github.com/python-trio/trio/issues/542
85
+ return self._stream._ssl_object # type: ignore[attr-defined]
86
+ if info == "client_addr":
87
+ return self._get_socket_stream().socket.getsockname()
88
+ if info == "server_addr":
89
+ return self._get_socket_stream().socket.getpeername()
90
+ if info == "socket":
91
+ stream = self._stream
92
+ while isinstance(stream, trio.SSLStream):
93
+ stream = stream.transport_stream
94
+ assert isinstance(stream, trio.SocketStream)
95
+ return stream.socket
96
+ if info == "is_readable":
97
+ socket = self.get_extra_info("socket")
98
+ return socket.is_readable()
99
+ return None
100
+
101
+ def _get_socket_stream(self) -> trio.SocketStream:
102
+ stream = self._stream
103
+ while isinstance(stream, trio.SSLStream):
104
+ stream = stream.transport_stream
105
+ assert isinstance(stream, trio.SocketStream)
106
+ return stream
107
+
108
+
109
+ class TrioBackend(AsyncNetworkBackend):
110
+ async def connect_tcp(
111
+ self,
112
+ host: str,
113
+ port: int,
114
+ timeout: float | None = None,
115
+ local_address: str | None = None,
116
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
117
+ ) -> AsyncNetworkStream:
118
+ # By default for TCP sockets, trio enables TCP_NODELAY.
119
+ # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream
120
+ if socket_options is None:
121
+ socket_options = [] # pragma: no cover
122
+ timeout_or_inf = float("inf") if timeout is None else timeout
123
+ exc_map: ExceptionMapping = {
124
+ trio.TooSlowError: ConnectTimeout,
125
+ trio.BrokenResourceError: ConnectError,
126
+ OSError: ConnectError,
127
+ }
128
+ with map_exceptions(exc_map):
129
+ with trio.fail_after(timeout_or_inf):
130
+ stream: trio.abc.Stream = await trio.open_tcp_stream(
131
+ host=host, port=port, local_address=local_address
132
+ )
133
+ for option in socket_options:
134
+ stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
135
+ return TrioStream(stream)
136
+
137
+ async def connect_unix_socket(
138
+ self,
139
+ path: str,
140
+ timeout: float | None = None,
141
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
142
+ ) -> AsyncNetworkStream: # pragma: nocover
143
+ if socket_options is None:
144
+ socket_options = []
145
+ timeout_or_inf = float("inf") if timeout is None else timeout
146
+ exc_map: ExceptionMapping = {
147
+ trio.TooSlowError: ConnectTimeout,
148
+ trio.BrokenResourceError: ConnectError,
149
+ OSError: ConnectError,
150
+ }
151
+ with map_exceptions(exc_map):
152
+ with trio.fail_after(timeout_or_inf):
153
+ stream: trio.abc.Stream = await trio.open_unix_socket(path)
154
+ for option in socket_options:
155
+ stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
156
+ return TrioStream(stream)
157
+
158
+ async def sleep(self, seconds: float) -> None:
159
+ await trio.sleep(seconds) # pragma: nocover
venv/lib/python3.12/site-packages/httpcore/_exceptions.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import contextlib
2
+ import typing
3
+
4
+ ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]]
5
+
6
+
7
+ @contextlib.contextmanager
8
+ def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]:
9
+ try:
10
+ yield
11
+ except Exception as exc: # noqa: PIE786
12
+ for from_exc, to_exc in map.items():
13
+ if isinstance(exc, from_exc):
14
+ raise to_exc(exc) from exc
15
+ raise # pragma: nocover
16
+
17
+
18
+ class ConnectionNotAvailable(Exception):
19
+ pass
20
+
21
+
22
+ class ProxyError(Exception):
23
+ pass
24
+
25
+
26
+ class UnsupportedProtocol(Exception):
27
+ pass
28
+
29
+
30
+ class ProtocolError(Exception):
31
+ pass
32
+
33
+
34
+ class RemoteProtocolError(ProtocolError):
35
+ pass
36
+
37
+
38
+ class LocalProtocolError(ProtocolError):
39
+ pass
40
+
41
+
42
+ # Timeout errors
43
+
44
+
45
+ class TimeoutException(Exception):
46
+ pass
47
+
48
+
49
+ class PoolTimeout(TimeoutException):
50
+ pass
51
+
52
+
53
+ class ConnectTimeout(TimeoutException):
54
+ pass
55
+
56
+
57
+ class ReadTimeout(TimeoutException):
58
+ pass
59
+
60
+
61
+ class WriteTimeout(TimeoutException):
62
+ pass
63
+
64
+
65
+ # Network errors
66
+
67
+
68
+ class NetworkError(Exception):
69
+ pass
70
+
71
+
72
+ class ConnectError(NetworkError):
73
+ pass
74
+
75
+
76
+ class ReadError(NetworkError):
77
+ pass
78
+
79
+
80
+ class WriteError(NetworkError):
81
+ pass
venv/lib/python3.12/site-packages/httpcore/_models.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import ssl
5
+ import typing
6
+ import urllib.parse
7
+
8
+ # Functions for typechecking...
9
+
10
+
11
+ ByteOrStr = typing.Union[bytes, str]
12
+ HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
13
+ HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
14
+ HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None]
15
+
16
+ Extensions = typing.MutableMapping[str, typing.Any]
17
+
18
+
19
+ def enforce_bytes(value: bytes | str, *, name: str) -> bytes:
20
+ """
21
+ Any arguments that are ultimately represented as bytes can be specified
22
+ either as bytes or as strings.
23
+
24
+ However we enforce that any string arguments must only contain characters in
25
+ the plain ASCII range. chr(0)...chr(127). If you need to use characters
26
+ outside that range then be precise, and use a byte-wise argument.
27
+ """
28
+ if isinstance(value, str):
29
+ try:
30
+ return value.encode("ascii")
31
+ except UnicodeEncodeError:
32
+ raise TypeError(f"{name} strings may not include unicode characters.")
33
+ elif isinstance(value, bytes):
34
+ return value
35
+
36
+ seen_type = type(value).__name__
37
+ raise TypeError(f"{name} must be bytes or str, but got {seen_type}.")
38
+
39
+
40
+ def enforce_url(value: URL | bytes | str, *, name: str) -> URL:
41
+ """
42
+ Type check for URL parameters.
43
+ """
44
+ if isinstance(value, (bytes, str)):
45
+ return URL(value)
46
+ elif isinstance(value, URL):
47
+ return value
48
+
49
+ seen_type = type(value).__name__
50
+ raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.")
51
+
52
+
53
+ def enforce_headers(
54
+ value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str
55
+ ) -> list[tuple[bytes, bytes]]:
56
+ """
57
+ Convienence function that ensure all items in request or response headers
58
+ are either bytes or strings in the plain ASCII range.
59
+ """
60
+ if value is None:
61
+ return []
62
+ elif isinstance(value, typing.Mapping):
63
+ return [
64
+ (
65
+ enforce_bytes(k, name="header name"),
66
+ enforce_bytes(v, name="header value"),
67
+ )
68
+ for k, v in value.items()
69
+ ]
70
+ elif isinstance(value, typing.Sequence):
71
+ return [
72
+ (
73
+ enforce_bytes(k, name="header name"),
74
+ enforce_bytes(v, name="header value"),
75
+ )
76
+ for k, v in value
77
+ ]
78
+
79
+ seen_type = type(value).__name__
80
+ raise TypeError(
81
+ f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}."
82
+ )
83
+
84
+
85
+ def enforce_stream(
86
+ value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None,
87
+ *,
88
+ name: str,
89
+ ) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]:
90
+ if value is None:
91
+ return ByteStream(b"")
92
+ elif isinstance(value, bytes):
93
+ return ByteStream(value)
94
+ return value
95
+
96
+
97
+ # * https://tools.ietf.org/html/rfc3986#section-3.2.3
98
+ # * https://url.spec.whatwg.org/#url-miscellaneous
99
+ # * https://url.spec.whatwg.org/#scheme-state
100
+ DEFAULT_PORTS = {
101
+ b"ftp": 21,
102
+ b"http": 80,
103
+ b"https": 443,
104
+ b"ws": 80,
105
+ b"wss": 443,
106
+ }
107
+
108
+
109
+ def include_request_headers(
110
+ headers: list[tuple[bytes, bytes]],
111
+ *,
112
+ url: "URL",
113
+ content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes],
114
+ ) -> list[tuple[bytes, bytes]]:
115
+ headers_set = set(k.lower() for k, v in headers)
116
+
117
+ if b"host" not in headers_set:
118
+ default_port = DEFAULT_PORTS.get(url.scheme)
119
+ if url.port is None or url.port == default_port:
120
+ header_value = url.host
121
+ else:
122
+ header_value = b"%b:%d" % (url.host, url.port)
123
+ headers = [(b"Host", header_value)] + headers
124
+
125
+ if (
126
+ content is not None
127
+ and b"content-length" not in headers_set
128
+ and b"transfer-encoding" not in headers_set
129
+ ):
130
+ if isinstance(content, bytes):
131
+ content_length = str(len(content)).encode("ascii")
132
+ headers += [(b"Content-Length", content_length)]
133
+ else:
134
+ headers += [(b"Transfer-Encoding", b"chunked")] # pragma: nocover
135
+
136
+ return headers
137
+
138
+
139
+ # Interfaces for byte streams...
140
+
141
+
142
+ class ByteStream:
143
+ """
144
+ A container for non-streaming content, and that supports both sync and async
145
+ stream iteration.
146
+ """
147
+
148
+ def __init__(self, content: bytes) -> None:
149
+ self._content = content
150
+
151
+ def __iter__(self) -> typing.Iterator[bytes]:
152
+ yield self._content
153
+
154
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
155
+ yield self._content
156
+
157
+ def __repr__(self) -> str:
158
+ return f"<{self.__class__.__name__} [{len(self._content)} bytes]>"
159
+
160
+
161
+ class Origin:
162
+ def __init__(self, scheme: bytes, host: bytes, port: int) -> None:
163
+ self.scheme = scheme
164
+ self.host = host
165
+ self.port = port
166
+
167
+ def __eq__(self, other: typing.Any) -> bool:
168
+ return (
169
+ isinstance(other, Origin)
170
+ and self.scheme == other.scheme
171
+ and self.host == other.host
172
+ and self.port == other.port
173
+ )
174
+
175
+ def __str__(self) -> str:
176
+ scheme = self.scheme.decode("ascii")
177
+ host = self.host.decode("ascii")
178
+ port = str(self.port)
179
+ return f"{scheme}://{host}:{port}"
180
+
181
+
182
+ class URL:
183
+ """
184
+ Represents the URL against which an HTTP request may be made.
185
+
186
+ The URL may either be specified as a plain string, for convienence:
187
+
188
+ ```python
189
+ url = httpcore.URL("https://www.example.com/")
190
+ ```
191
+
192
+ Or be constructed with explicitily pre-parsed components:
193
+
194
+ ```python
195
+ url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')
196
+ ```
197
+
198
+ Using this second more explicit style allows integrations that are using
199
+ `httpcore` to pass through URLs that have already been parsed in order to use
200
+ libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures
201
+ that URL parsing is treated identically at both the networking level and at any
202
+ higher layers of abstraction.
203
+
204
+ The four components are important here, as they allow the URL to be precisely
205
+ specified in a pre-parsed format. They also allow certain types of request to
206
+ be created that could not otherwise be expressed.
207
+
208
+ For example, an HTTP request to `http://www.example.com/` forwarded via a proxy
209
+ at `http://localhost:8080`...
210
+
211
+ ```python
212
+ # Constructs an HTTP request with a complete URL as the target:
213
+ # GET https://www.example.com/ HTTP/1.1
214
+ url = httpcore.URL(
215
+ scheme=b'http',
216
+ host=b'localhost',
217
+ port=8080,
218
+ target=b'https://www.example.com/'
219
+ )
220
+ request = httpcore.Request(
221
+ method="GET",
222
+ url=url
223
+ )
224
+ ```
225
+
226
+ Another example is constructing an `OPTIONS *` request...
227
+
228
+ ```python
229
+ # Constructs an 'OPTIONS *' HTTP request:
230
+ # OPTIONS * HTTP/1.1
231
+ url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')
232
+ request = httpcore.Request(method="OPTIONS", url=url)
233
+ ```
234
+
235
+ This kind of request is not possible to formulate with a URL string,
236
+ because the `/` delimiter is always used to demark the target from the
237
+ host/port portion of the URL.
238
+
239
+ For convenience, string-like arguments may be specified either as strings or
240
+ as bytes. However, once a request is being issue over-the-wire, the URL
241
+ components are always ultimately required to be a bytewise representation.
242
+
243
+ In order to avoid any ambiguity over character encodings, when strings are used
244
+ as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`.
245
+ If you require a bytewise representation that is outside this range you must
246
+ handle the character encoding directly, and pass a bytes instance.
247
+ """
248
+
249
+ def __init__(
250
+ self,
251
+ url: bytes | str = "",
252
+ *,
253
+ scheme: bytes | str = b"",
254
+ host: bytes | str = b"",
255
+ port: int | None = None,
256
+ target: bytes | str = b"",
257
+ ) -> None:
258
+ """
259
+ Parameters:
260
+ url: The complete URL as a string or bytes.
261
+ scheme: The URL scheme as a string or bytes.
262
+ Typically either `"http"` or `"https"`.
263
+ host: The URL host as a string or bytes. Such as `"www.example.com"`.
264
+ port: The port to connect to. Either an integer or `None`.
265
+ target: The target of the HTTP request. Such as `"/items?search=red"`.
266
+ """
267
+ if url:
268
+ parsed = urllib.parse.urlparse(enforce_bytes(url, name="url"))
269
+ self.scheme = parsed.scheme
270
+ self.host = parsed.hostname or b""
271
+ self.port = parsed.port
272
+ self.target = (parsed.path or b"/") + (
273
+ b"?" + parsed.query if parsed.query else b""
274
+ )
275
+ else:
276
+ self.scheme = enforce_bytes(scheme, name="scheme")
277
+ self.host = enforce_bytes(host, name="host")
278
+ self.port = port
279
+ self.target = enforce_bytes(target, name="target")
280
+
281
+ @property
282
+ def origin(self) -> Origin:
283
+ default_port = {
284
+ b"http": 80,
285
+ b"https": 443,
286
+ b"ws": 80,
287
+ b"wss": 443,
288
+ b"socks5": 1080,
289
+ b"socks5h": 1080,
290
+ }[self.scheme]
291
+ return Origin(
292
+ scheme=self.scheme, host=self.host, port=self.port or default_port
293
+ )
294
+
295
+ def __eq__(self, other: typing.Any) -> bool:
296
+ return (
297
+ isinstance(other, URL)
298
+ and other.scheme == self.scheme
299
+ and other.host == self.host
300
+ and other.port == self.port
301
+ and other.target == self.target
302
+ )
303
+
304
+ def __bytes__(self) -> bytes:
305
+ if self.port is None:
306
+ return b"%b://%b%b" % (self.scheme, self.host, self.target)
307
+ return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target)
308
+
309
+ def __repr__(self) -> str:
310
+ return (
311
+ f"{self.__class__.__name__}(scheme={self.scheme!r}, "
312
+ f"host={self.host!r}, port={self.port!r}, target={self.target!r})"
313
+ )
314
+
315
+
316
+ class Request:
317
+ """
318
+ An HTTP request.
319
+ """
320
+
321
+ def __init__(
322
+ self,
323
+ method: bytes | str,
324
+ url: URL | bytes | str,
325
+ *,
326
+ headers: HeaderTypes = None,
327
+ content: bytes
328
+ | typing.Iterable[bytes]
329
+ | typing.AsyncIterable[bytes]
330
+ | None = None,
331
+ extensions: Extensions | None = None,
332
+ ) -> None:
333
+ """
334
+ Parameters:
335
+ method: The HTTP request method, either as a string or bytes.
336
+ For example: `GET`.
337
+ url: The request URL, either as a `URL` instance, or as a string or bytes.
338
+ For example: `"https://www.example.com".`
339
+ headers: The HTTP request headers.
340
+ content: The content of the request body.
341
+ extensions: A dictionary of optional extra information included on
342
+ the request. Possible keys include `"timeout"`, and `"trace"`.
343
+ """
344
+ self.method: bytes = enforce_bytes(method, name="method")
345
+ self.url: URL = enforce_url(url, name="url")
346
+ self.headers: list[tuple[bytes, bytes]] = enforce_headers(
347
+ headers, name="headers"
348
+ )
349
+ self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
350
+ enforce_stream(content, name="content")
351
+ )
352
+ self.extensions = {} if extensions is None else extensions
353
+
354
+ if "target" in self.extensions:
355
+ self.url = URL(
356
+ scheme=self.url.scheme,
357
+ host=self.url.host,
358
+ port=self.url.port,
359
+ target=self.extensions["target"],
360
+ )
361
+
362
+ def __repr__(self) -> str:
363
+ return f"<{self.__class__.__name__} [{self.method!r}]>"
364
+
365
+
366
+ class Response:
367
+ """
368
+ An HTTP response.
369
+ """
370
+
371
+ def __init__(
372
+ self,
373
+ status: int,
374
+ *,
375
+ headers: HeaderTypes = None,
376
+ content: bytes
377
+ | typing.Iterable[bytes]
378
+ | typing.AsyncIterable[bytes]
379
+ | None = None,
380
+ extensions: Extensions | None = None,
381
+ ) -> None:
382
+ """
383
+ Parameters:
384
+ status: The HTTP status code of the response. For example `200`.
385
+ headers: The HTTP response headers.
386
+ content: The content of the response body.
387
+ extensions: A dictionary of optional extra information included on
388
+ the responseself.Possible keys include `"http_version"`,
389
+ `"reason_phrase"`, and `"network_stream"`.
390
+ """
391
+ self.status: int = status
392
+ self.headers: list[tuple[bytes, bytes]] = enforce_headers(
393
+ headers, name="headers"
394
+ )
395
+ self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (
396
+ enforce_stream(content, name="content")
397
+ )
398
+ self.extensions = {} if extensions is None else extensions
399
+
400
+ self._stream_consumed = False
401
+
402
+ @property
403
+ def content(self) -> bytes:
404
+ if not hasattr(self, "_content"):
405
+ if isinstance(self.stream, typing.Iterable):
406
+ raise RuntimeError(
407
+ "Attempted to access 'response.content' on a streaming response. "
408
+ "Call 'response.read()' first."
409
+ )
410
+ else:
411
+ raise RuntimeError(
412
+ "Attempted to access 'response.content' on a streaming response. "
413
+ "Call 'await response.aread()' first."
414
+ )
415
+ return self._content
416
+
417
+ def __repr__(self) -> str:
418
+ return f"<{self.__class__.__name__} [{self.status}]>"
419
+
420
+ # Sync interface...
421
+
422
+ def read(self) -> bytes:
423
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
424
+ raise RuntimeError(
425
+ "Attempted to read an asynchronous response using 'response.read()'. "
426
+ "You should use 'await response.aread()' instead."
427
+ )
428
+ if not hasattr(self, "_content"):
429
+ self._content = b"".join([part for part in self.iter_stream()])
430
+ return self._content
431
+
432
+ def iter_stream(self) -> typing.Iterator[bytes]:
433
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
434
+ raise RuntimeError(
435
+ "Attempted to stream an asynchronous response using 'for ... in "
436
+ "response.iter_stream()'. "
437
+ "You should use 'async for ... in response.aiter_stream()' instead."
438
+ )
439
+ if self._stream_consumed:
440
+ raise RuntimeError(
441
+ "Attempted to call 'for ... in response.iter_stream()' more than once."
442
+ )
443
+ self._stream_consumed = True
444
+ for chunk in self.stream:
445
+ yield chunk
446
+
447
+ def close(self) -> None:
448
+ if not isinstance(self.stream, typing.Iterable): # pragma: nocover
449
+ raise RuntimeError(
450
+ "Attempted to close an asynchronous response using 'response.close()'. "
451
+ "You should use 'await response.aclose()' instead."
452
+ )
453
+ if hasattr(self.stream, "close"):
454
+ self.stream.close()
455
+
456
+ # Async interface...
457
+
458
+ async def aread(self) -> bytes:
459
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
460
+ raise RuntimeError(
461
+ "Attempted to read an synchronous response using "
462
+ "'await response.aread()'. "
463
+ "You should use 'response.read()' instead."
464
+ )
465
+ if not hasattr(self, "_content"):
466
+ self._content = b"".join([part async for part in self.aiter_stream()])
467
+ return self._content
468
+
469
+ async def aiter_stream(self) -> typing.AsyncIterator[bytes]:
470
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
471
+ raise RuntimeError(
472
+ "Attempted to stream an synchronous response using 'async for ... in "
473
+ "response.aiter_stream()'. "
474
+ "You should use 'for ... in response.iter_stream()' instead."
475
+ )
476
+ if self._stream_consumed:
477
+ raise RuntimeError(
478
+ "Attempted to call 'async for ... in response.aiter_stream()' "
479
+ "more than once."
480
+ )
481
+ self._stream_consumed = True
482
+ async for chunk in self.stream:
483
+ yield chunk
484
+
485
+ async def aclose(self) -> None:
486
+ if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover
487
+ raise RuntimeError(
488
+ "Attempted to close a synchronous response using "
489
+ "'await response.aclose()'. "
490
+ "You should use 'response.close()' instead."
491
+ )
492
+ if hasattr(self.stream, "aclose"):
493
+ await self.stream.aclose()
494
+
495
+
496
+ class Proxy:
497
+ def __init__(
498
+ self,
499
+ url: URL | bytes | str,
500
+ auth: tuple[bytes | str, bytes | str] | None = None,
501
+ headers: HeadersAsMapping | HeadersAsSequence | None = None,
502
+ ssl_context: ssl.SSLContext | None = None,
503
+ ):
504
+ self.url = enforce_url(url, name="url")
505
+ self.headers = enforce_headers(headers, name="headers")
506
+ self.ssl_context = ssl_context
507
+
508
+ if auth is not None:
509
+ username = enforce_bytes(auth[0], name="auth")
510
+ password = enforce_bytes(auth[1], name="auth")
511
+ userpass = username + b":" + password
512
+ authorization = b"Basic " + base64.b64encode(userpass)
513
+ self.auth: tuple[bytes, bytes] | None = (username, password)
514
+ self.headers = [(b"Proxy-Authorization", authorization)] + self.headers
515
+ else:
516
+ self.auth = None
venv/lib/python3.12/site-packages/httpcore/_ssl.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import ssl
2
+
3
+ import certifi
4
+
5
+
6
+ def default_ssl_context() -> ssl.SSLContext:
7
+ context = ssl.create_default_context()
8
+ context.load_verify_locations(certifi.where())
9
+ return context
venv/lib/python3.12/site-packages/httpcore/_sync/__init__.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .connection import HTTPConnection
2
+ from .connection_pool import ConnectionPool
3
+ from .http11 import HTTP11Connection
4
+ from .http_proxy import HTTPProxy
5
+ from .interfaces import ConnectionInterface
6
+
7
+ try:
8
+ from .http2 import HTTP2Connection
9
+ except ImportError: # pragma: nocover
10
+
11
+ class HTTP2Connection: # type: ignore
12
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
13
+ raise RuntimeError(
14
+ "Attempted to use http2 support, but the `h2` package is not "
15
+ "installed. Use 'pip install httpcore[http2]'."
16
+ )
17
+
18
+
19
+ try:
20
+ from .socks_proxy import SOCKSProxy
21
+ except ImportError: # pragma: nocover
22
+
23
+ class SOCKSProxy: # type: ignore
24
+ def __init__(self, *args, **kwargs) -> None: # type: ignore
25
+ raise RuntimeError(
26
+ "Attempted to use SOCKS support, but the `socksio` package is not "
27
+ "installed. Use 'pip install httpcore[socks]'."
28
+ )
29
+
30
+
31
+ __all__ = [
32
+ "HTTPConnection",
33
+ "ConnectionPool",
34
+ "HTTPProxy",
35
+ "HTTP11Connection",
36
+ "HTTP2Connection",
37
+ "ConnectionInterface",
38
+ "SOCKSProxy",
39
+ ]
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.56 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc ADDED
Binary file (10.4 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc ADDED
Binary file (18.7 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc ADDED
Binary file (17.8 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc ADDED
Binary file (27.4 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc ADDED
Binary file (17.2 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc ADDED
Binary file (5.28 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc ADDED
Binary file (15.6 kB). View file
 
venv/lib/python3.12/site-packages/httpcore/_sync/connection.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ import logging
5
+ import ssl
6
+ import types
7
+ import typing
8
+
9
+ from .._backends.sync import SyncBackend
10
+ from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream
11
+ from .._exceptions import ConnectError, ConnectTimeout
12
+ from .._models import Origin, Request, Response
13
+ from .._ssl import default_ssl_context
14
+ from .._synchronization import Lock
15
+ from .._trace import Trace
16
+ from .http11 import HTTP11Connection
17
+ from .interfaces import ConnectionInterface
18
+
19
+ RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc.
20
+
21
+
22
+ logger = logging.getLogger("httpcore.connection")
23
+
24
+
25
+ def exponential_backoff(factor: float) -> typing.Iterator[float]:
26
+ """
27
+ Generate a geometric sequence that has a ratio of 2 and starts with 0.
28
+
29
+ For example:
30
+ - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
31
+ - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
32
+ """
33
+ yield 0
34
+ for n in itertools.count():
35
+ yield factor * 2**n
36
+
37
+
38
+ class HTTPConnection(ConnectionInterface):
39
+ def __init__(
40
+ self,
41
+ origin: Origin,
42
+ ssl_context: ssl.SSLContext | None = None,
43
+ keepalive_expiry: float | None = None,
44
+ http1: bool = True,
45
+ http2: bool = False,
46
+ retries: int = 0,
47
+ local_address: str | None = None,
48
+ uds: str | None = None,
49
+ network_backend: NetworkBackend | None = None,
50
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
51
+ ) -> None:
52
+ self._origin = origin
53
+ self._ssl_context = ssl_context
54
+ self._keepalive_expiry = keepalive_expiry
55
+ self._http1 = http1
56
+ self._http2 = http2
57
+ self._retries = retries
58
+ self._local_address = local_address
59
+ self._uds = uds
60
+
61
+ self._network_backend: NetworkBackend = (
62
+ SyncBackend() if network_backend is None else network_backend
63
+ )
64
+ self._connection: ConnectionInterface | None = None
65
+ self._connect_failed: bool = False
66
+ self._request_lock = Lock()
67
+ self._socket_options = socket_options
68
+
69
+ def handle_request(self, request: Request) -> Response:
70
+ if not self.can_handle_request(request.url.origin):
71
+ raise RuntimeError(
72
+ f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
73
+ )
74
+
75
+ try:
76
+ with self._request_lock:
77
+ if self._connection is None:
78
+ stream = self._connect(request)
79
+
80
+ ssl_object = stream.get_extra_info("ssl_object")
81
+ http2_negotiated = (
82
+ ssl_object is not None
83
+ and ssl_object.selected_alpn_protocol() == "h2"
84
+ )
85
+ if http2_negotiated or (self._http2 and not self._http1):
86
+ from .http2 import HTTP2Connection
87
+
88
+ self._connection = HTTP2Connection(
89
+ origin=self._origin,
90
+ stream=stream,
91
+ keepalive_expiry=self._keepalive_expiry,
92
+ )
93
+ else:
94
+ self._connection = HTTP11Connection(
95
+ origin=self._origin,
96
+ stream=stream,
97
+ keepalive_expiry=self._keepalive_expiry,
98
+ )
99
+ except BaseException as exc:
100
+ self._connect_failed = True
101
+ raise exc
102
+
103
+ return self._connection.handle_request(request)
104
+
105
+ def _connect(self, request: Request) -> NetworkStream:
106
+ timeouts = request.extensions.get("timeout", {})
107
+ sni_hostname = request.extensions.get("sni_hostname", None)
108
+ timeout = timeouts.get("connect", None)
109
+
110
+ retries_left = self._retries
111
+ delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)
112
+
113
+ while True:
114
+ try:
115
+ if self._uds is None:
116
+ kwargs = {
117
+ "host": self._origin.host.decode("ascii"),
118
+ "port": self._origin.port,
119
+ "local_address": self._local_address,
120
+ "timeout": timeout,
121
+ "socket_options": self._socket_options,
122
+ }
123
+ with Trace("connect_tcp", logger, request, kwargs) as trace:
124
+ stream = self._network_backend.connect_tcp(**kwargs)
125
+ trace.return_value = stream
126
+ else:
127
+ kwargs = {
128
+ "path": self._uds,
129
+ "timeout": timeout,
130
+ "socket_options": self._socket_options,
131
+ }
132
+ with Trace(
133
+ "connect_unix_socket", logger, request, kwargs
134
+ ) as trace:
135
+ stream = self._network_backend.connect_unix_socket(
136
+ **kwargs
137
+ )
138
+ trace.return_value = stream
139
+
140
+ if self._origin.scheme in (b"https", b"wss"):
141
+ ssl_context = (
142
+ default_ssl_context()
143
+ if self._ssl_context is None
144
+ else self._ssl_context
145
+ )
146
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
147
+ ssl_context.set_alpn_protocols(alpn_protocols)
148
+
149
+ kwargs = {
150
+ "ssl_context": ssl_context,
151
+ "server_hostname": sni_hostname
152
+ or self._origin.host.decode("ascii"),
153
+ "timeout": timeout,
154
+ }
155
+ with Trace("start_tls", logger, request, kwargs) as trace:
156
+ stream = stream.start_tls(**kwargs)
157
+ trace.return_value = stream
158
+ return stream
159
+ except (ConnectError, ConnectTimeout):
160
+ if retries_left <= 0:
161
+ raise
162
+ retries_left -= 1
163
+ delay = next(delays)
164
+ with Trace("retry", logger, request, kwargs) as trace:
165
+ self._network_backend.sleep(delay)
166
+
167
+ def can_handle_request(self, origin: Origin) -> bool:
168
+ return origin == self._origin
169
+
170
+ def close(self) -> None:
171
+ if self._connection is not None:
172
+ with Trace("close", logger, None, {}):
173
+ self._connection.close()
174
+
175
+ def is_available(self) -> bool:
176
+ if self._connection is None:
177
+ # If HTTP/2 support is enabled, and the resulting connection could
178
+ # end up as HTTP/2 then we should indicate the connection as being
179
+ # available to service multiple requests.
180
+ return (
181
+ self._http2
182
+ and (self._origin.scheme == b"https" or not self._http1)
183
+ and not self._connect_failed
184
+ )
185
+ return self._connection.is_available()
186
+
187
+ def has_expired(self) -> bool:
188
+ if self._connection is None:
189
+ return self._connect_failed
190
+ return self._connection.has_expired()
191
+
192
+ def is_idle(self) -> bool:
193
+ if self._connection is None:
194
+ return self._connect_failed
195
+ return self._connection.is_idle()
196
+
197
+ def is_closed(self) -> bool:
198
+ if self._connection is None:
199
+ return self._connect_failed
200
+ return self._connection.is_closed()
201
+
202
+ def info(self) -> str:
203
+ if self._connection is None:
204
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
205
+ return self._connection.info()
206
+
207
+ def __repr__(self) -> str:
208
+ return f"<{self.__class__.__name__} [{self.info()}]>"
209
+
210
+ # These context managers are not used in the standard flow, but are
211
+ # useful for testing or working with connection instances directly.
212
+
213
+ def __enter__(self) -> HTTPConnection:
214
+ return self
215
+
216
+ def __exit__(
217
+ self,
218
+ exc_type: type[BaseException] | None = None,
219
+ exc_value: BaseException | None = None,
220
+ traceback: types.TracebackType | None = None,
221
+ ) -> None:
222
+ self.close()
venv/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ssl
4
+ import sys
5
+ import types
6
+ import typing
7
+
8
+ from .._backends.sync import SyncBackend
9
+ from .._backends.base import SOCKET_OPTION, NetworkBackend
10
+ from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
11
+ from .._models import Origin, Proxy, Request, Response
12
+ from .._synchronization import Event, ShieldCancellation, ThreadLock
13
+ from .connection import HTTPConnection
14
+ from .interfaces import ConnectionInterface, RequestInterface
15
+
16
+
17
+ class PoolRequest:
18
+ def __init__(self, request: Request) -> None:
19
+ self.request = request
20
+ self.connection: ConnectionInterface | None = None
21
+ self._connection_acquired = Event()
22
+
23
+ def assign_to_connection(self, connection: ConnectionInterface | None) -> None:
24
+ self.connection = connection
25
+ self._connection_acquired.set()
26
+
27
+ def clear_connection(self) -> None:
28
+ self.connection = None
29
+ self._connection_acquired = Event()
30
+
31
+ def wait_for_connection(
32
+ self, timeout: float | None = None
33
+ ) -> ConnectionInterface:
34
+ if self.connection is None:
35
+ self._connection_acquired.wait(timeout=timeout)
36
+ assert self.connection is not None
37
+ return self.connection
38
+
39
+ def is_queued(self) -> bool:
40
+ return self.connection is None
41
+
42
+
43
+ class ConnectionPool(RequestInterface):
44
+ """
45
+ A connection pool for making HTTP requests.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ ssl_context: ssl.SSLContext | None = None,
51
+ proxy: Proxy | None = None,
52
+ max_connections: int | None = 10,
53
+ max_keepalive_connections: int | None = None,
54
+ keepalive_expiry: float | None = None,
55
+ http1: bool = True,
56
+ http2: bool = False,
57
+ retries: int = 0,
58
+ local_address: str | None = None,
59
+ uds: str | None = None,
60
+ network_backend: NetworkBackend | None = None,
61
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
62
+ ) -> None:
63
+ """
64
+ A connection pool for making HTTP requests.
65
+
66
+ Parameters:
67
+ ssl_context: An SSL context to use for verifying connections.
68
+ If not specified, the default `httpcore.default_ssl_context()`
69
+ will be used.
70
+ max_connections: The maximum number of concurrent HTTP connections that
71
+ the pool should allow. Any attempt to send a request on a pool that
72
+ would exceed this amount will block until a connection is available.
73
+ max_keepalive_connections: The maximum number of idle HTTP connections
74
+ that will be maintained in the pool.
75
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
76
+ may be maintained for before being expired from the pool.
77
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
78
+ by the connection pool. Defaults to True.
79
+ http2: A boolean indicating if HTTP/2 requests should be supported by
80
+ the connection pool. Defaults to False.
81
+ retries: The maximum number of retries when trying to establish a
82
+ connection.
83
+ local_address: Local address to connect from. Can also be used to connect
84
+ using a particular address family. Using `local_address="0.0.0.0"`
85
+ will connect using an `AF_INET` address (IPv4), while using
86
+ `local_address="::"` will connect using an `AF_INET6` address (IPv6).
87
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
88
+ network_backend: A backend instance to use for handling network I/O.
89
+ socket_options: Socket options that have to be included
90
+ in the TCP socket when the connection was established.
91
+ """
92
+ self._ssl_context = ssl_context
93
+ self._proxy = proxy
94
+ self._max_connections = (
95
+ sys.maxsize if max_connections is None else max_connections
96
+ )
97
+ self._max_keepalive_connections = (
98
+ sys.maxsize
99
+ if max_keepalive_connections is None
100
+ else max_keepalive_connections
101
+ )
102
+ self._max_keepalive_connections = min(
103
+ self._max_connections, self._max_keepalive_connections
104
+ )
105
+
106
+ self._keepalive_expiry = keepalive_expiry
107
+ self._http1 = http1
108
+ self._http2 = http2
109
+ self._retries = retries
110
+ self._local_address = local_address
111
+ self._uds = uds
112
+
113
+ self._network_backend = (
114
+ SyncBackend() if network_backend is None else network_backend
115
+ )
116
+ self._socket_options = socket_options
117
+
118
+ # The mutable state on a connection pool is the queue of incoming requests,
119
+ # and the set of connections that are servicing those requests.
120
+ self._connections: list[ConnectionInterface] = []
121
+ self._requests: list[PoolRequest] = []
122
+
123
+ # We only mutate the state of the connection pool within an 'optional_thread_lock'
124
+ # context. This holds a threading lock unless we're running in async mode,
125
+ # in which case it is a no-op.
126
+ self._optional_thread_lock = ThreadLock()
127
+
128
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
129
+ if self._proxy is not None:
130
+ if self._proxy.url.scheme in (b"socks5", b"socks5h"):
131
+ from .socks_proxy import Socks5Connection
132
+
133
+ return Socks5Connection(
134
+ proxy_origin=self._proxy.url.origin,
135
+ proxy_auth=self._proxy.auth,
136
+ remote_origin=origin,
137
+ ssl_context=self._ssl_context,
138
+ keepalive_expiry=self._keepalive_expiry,
139
+ http1=self._http1,
140
+ http2=self._http2,
141
+ network_backend=self._network_backend,
142
+ )
143
+ elif origin.scheme == b"http":
144
+ from .http_proxy import ForwardHTTPConnection
145
+
146
+ return ForwardHTTPConnection(
147
+ proxy_origin=self._proxy.url.origin,
148
+ proxy_headers=self._proxy.headers,
149
+ proxy_ssl_context=self._proxy.ssl_context,
150
+ remote_origin=origin,
151
+ keepalive_expiry=self._keepalive_expiry,
152
+ network_backend=self._network_backend,
153
+ )
154
+ from .http_proxy import TunnelHTTPConnection
155
+
156
+ return TunnelHTTPConnection(
157
+ proxy_origin=self._proxy.url.origin,
158
+ proxy_headers=self._proxy.headers,
159
+ proxy_ssl_context=self._proxy.ssl_context,
160
+ remote_origin=origin,
161
+ ssl_context=self._ssl_context,
162
+ keepalive_expiry=self._keepalive_expiry,
163
+ http1=self._http1,
164
+ http2=self._http2,
165
+ network_backend=self._network_backend,
166
+ )
167
+
168
+ return HTTPConnection(
169
+ origin=origin,
170
+ ssl_context=self._ssl_context,
171
+ keepalive_expiry=self._keepalive_expiry,
172
+ http1=self._http1,
173
+ http2=self._http2,
174
+ retries=self._retries,
175
+ local_address=self._local_address,
176
+ uds=self._uds,
177
+ network_backend=self._network_backend,
178
+ socket_options=self._socket_options,
179
+ )
180
+
181
+ @property
182
+ def connections(self) -> list[ConnectionInterface]:
183
+ """
184
+ Return a list of the connections currently in the pool.
185
+
186
+ For example:
187
+
188
+ ```python
189
+ >>> pool.connections
190
+ [
191
+ <HTTPConnection ['https://example.com:443', HTTP/1.1, ACTIVE, Request Count: 6]>,
192
+ <HTTPConnection ['https://example.com:443', HTTP/1.1, IDLE, Request Count: 9]> ,
193
+ <HTTPConnection ['http://example.com:80', HTTP/1.1, IDLE, Request Count: 1]>,
194
+ ]
195
+ ```
196
+ """
197
+ return list(self._connections)
198
+
199
+ def handle_request(self, request: Request) -> Response:
200
+ """
201
+ Send an HTTP request, and return an HTTP response.
202
+
203
+ This is the core implementation that is called into by `.request()` or `.stream()`.
204
+ """
205
+ scheme = request.url.scheme.decode()
206
+ if scheme == "":
207
+ raise UnsupportedProtocol(
208
+ "Request URL is missing an 'http://' or 'https://' protocol."
209
+ )
210
+ if scheme not in ("http", "https", "ws", "wss"):
211
+ raise UnsupportedProtocol(
212
+ f"Request URL has an unsupported protocol '{scheme}://'."
213
+ )
214
+
215
+ timeouts = request.extensions.get("timeout", {})
216
+ timeout = timeouts.get("pool", None)
217
+
218
+ with self._optional_thread_lock:
219
+ # Add the incoming request to our request queue.
220
+ pool_request = PoolRequest(request)
221
+ self._requests.append(pool_request)
222
+
223
+ try:
224
+ while True:
225
+ with self._optional_thread_lock:
226
+ # Assign incoming requests to available connections,
227
+ # closing or creating new connections as required.
228
+ closing = self._assign_requests_to_connections()
229
+ self._close_connections(closing)
230
+
231
+ # Wait until this request has an assigned connection.
232
+ connection = pool_request.wait_for_connection(timeout=timeout)
233
+
234
+ try:
235
+ # Send the request on the assigned connection.
236
+ response = connection.handle_request(
237
+ pool_request.request
238
+ )
239
+ except ConnectionNotAvailable:
240
+ # In some cases a connection may initially be available to
241
+ # handle a request, but then become unavailable.
242
+ #
243
+ # In this case we clear the connection and try again.
244
+ pool_request.clear_connection()
245
+ else:
246
+ break # pragma: nocover
247
+
248
+ except BaseException as exc:
249
+ with self._optional_thread_lock:
250
+ # For any exception or cancellation we remove the request from
251
+ # the queue, and then re-assign requests to connections.
252
+ self._requests.remove(pool_request)
253
+ closing = self._assign_requests_to_connections()
254
+
255
+ self._close_connections(closing)
256
+ raise exc from None
257
+
258
+ # Return the response. Note that in this case we still have to manage
259
+ # the point at which the response is closed.
260
+ assert isinstance(response.stream, typing.Iterable)
261
+ return Response(
262
+ status=response.status,
263
+ headers=response.headers,
264
+ content=PoolByteStream(
265
+ stream=response.stream, pool_request=pool_request, pool=self
266
+ ),
267
+ extensions=response.extensions,
268
+ )
269
+
270
+ def _assign_requests_to_connections(self) -> list[ConnectionInterface]:
271
+ """
272
+ Manage the state of the connection pool, assigning incoming
273
+ requests to connections as available.
274
+
275
+ Called whenever a new request is added or removed from the pool.
276
+
277
+ Any closing connections are returned, allowing the I/O for closing
278
+ those connections to be handled seperately.
279
+ """
280
+ closing_connections = []
281
+
282
+ # First we handle cleaning up any connections that are closed,
283
+ # have expired their keep-alive, or surplus idle connections.
284
+ for connection in list(self._connections):
285
+ if connection.is_closed():
286
+ # log: "removing closed connection"
287
+ self._connections.remove(connection)
288
+ elif connection.has_expired():
289
+ # log: "closing expired connection"
290
+ self._connections.remove(connection)
291
+ closing_connections.append(connection)
292
+ elif (
293
+ connection.is_idle()
294
+ and len([connection.is_idle() for connection in self._connections])
295
+ > self._max_keepalive_connections
296
+ ):
297
+ # log: "closing idle connection"
298
+ self._connections.remove(connection)
299
+ closing_connections.append(connection)
300
+
301
+ # Assign queued requests to connections.
302
+ queued_requests = [request for request in self._requests if request.is_queued()]
303
+ for pool_request in queued_requests:
304
+ origin = pool_request.request.url.origin
305
+ available_connections = [
306
+ connection
307
+ for connection in self._connections
308
+ if connection.can_handle_request(origin) and connection.is_available()
309
+ ]
310
+ idle_connections = [
311
+ connection for connection in self._connections if connection.is_idle()
312
+ ]
313
+
314
+ # There are three cases for how we may be able to handle the request:
315
+ #
316
+ # 1. There is an existing connection that can handle the request.
317
+ # 2. We can create a new connection to handle the request.
318
+ # 3. We can close an idle connection and then create a new connection
319
+ # to handle the request.
320
+ if available_connections:
321
+ # log: "reusing existing connection"
322
+ connection = available_connections[0]
323
+ pool_request.assign_to_connection(connection)
324
+ elif len(self._connections) < self._max_connections:
325
+ # log: "creating new connection"
326
+ connection = self.create_connection(origin)
327
+ self._connections.append(connection)
328
+ pool_request.assign_to_connection(connection)
329
+ elif idle_connections:
330
+ # log: "closing idle connection"
331
+ connection = idle_connections[0]
332
+ self._connections.remove(connection)
333
+ closing_connections.append(connection)
334
+ # log: "creating new connection"
335
+ connection = self.create_connection(origin)
336
+ self._connections.append(connection)
337
+ pool_request.assign_to_connection(connection)
338
+
339
+ return closing_connections
340
+
341
+ def _close_connections(self, closing: list[ConnectionInterface]) -> None:
342
+ # Close connections which have been removed from the pool.
343
+ with ShieldCancellation():
344
+ for connection in closing:
345
+ connection.close()
346
+
347
+ def close(self) -> None:
348
+ # Explicitly close the connection pool.
349
+ # Clears all existing requests and connections.
350
+ with self._optional_thread_lock:
351
+ closing_connections = list(self._connections)
352
+ self._connections = []
353
+ self._close_connections(closing_connections)
354
+
355
+ def __enter__(self) -> ConnectionPool:
356
+ return self
357
+
358
+ def __exit__(
359
+ self,
360
+ exc_type: type[BaseException] | None = None,
361
+ exc_value: BaseException | None = None,
362
+ traceback: types.TracebackType | None = None,
363
+ ) -> None:
364
+ self.close()
365
+
366
+ def __repr__(self) -> str:
367
+ class_name = self.__class__.__name__
368
+ with self._optional_thread_lock:
369
+ request_is_queued = [request.is_queued() for request in self._requests]
370
+ connection_is_idle = [
371
+ connection.is_idle() for connection in self._connections
372
+ ]
373
+
374
+ num_active_requests = request_is_queued.count(False)
375
+ num_queued_requests = request_is_queued.count(True)
376
+ num_active_connections = connection_is_idle.count(False)
377
+ num_idle_connections = connection_is_idle.count(True)
378
+
379
+ requests_info = (
380
+ f"Requests: {num_active_requests} active, {num_queued_requests} queued"
381
+ )
382
+ connection_info = (
383
+ f"Connections: {num_active_connections} active, {num_idle_connections} idle"
384
+ )
385
+
386
+ return f"<{class_name} [{requests_info} | {connection_info}]>"
387
+
388
+
389
+ class PoolByteStream:
390
+ def __init__(
391
+ self,
392
+ stream: typing.Iterable[bytes],
393
+ pool_request: PoolRequest,
394
+ pool: ConnectionPool,
395
+ ) -> None:
396
+ self._stream = stream
397
+ self._pool_request = pool_request
398
+ self._pool = pool
399
+ self._closed = False
400
+
401
+ def __iter__(self) -> typing.Iterator[bytes]:
402
+ try:
403
+ for part in self._stream:
404
+ yield part
405
+ except BaseException as exc:
406
+ self.close()
407
+ raise exc from None
408
+
409
+ def close(self) -> None:
410
+ if not self._closed:
411
+ self._closed = True
412
+ with ShieldCancellation():
413
+ if hasattr(self._stream, "close"):
414
+ self._stream.close()
415
+
416
+ with self._pool._optional_thread_lock:
417
+ self._pool._requests.remove(self._pool_request)
418
+ closing = self._pool._assign_requests_to_connections()
419
+
420
+ self._pool._close_connections(closing)
venv/lib/python3.12/site-packages/httpcore/_sync/http11.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import ssl
6
+ import time
7
+ import types
8
+ import typing
9
+
10
+ import h11
11
+
12
+ from .._backends.base import NetworkStream
13
+ from .._exceptions import (
14
+ ConnectionNotAvailable,
15
+ LocalProtocolError,
16
+ RemoteProtocolError,
17
+ WriteError,
18
+ map_exceptions,
19
+ )
20
+ from .._models import Origin, Request, Response
21
+ from .._synchronization import Lock, ShieldCancellation
22
+ from .._trace import Trace
23
+ from .interfaces import ConnectionInterface
24
+
25
+ logger = logging.getLogger("httpcore.http11")
26
+
27
+
28
+ # A subset of `h11.Event` types supported by `_send_event`
29
+ H11SendEvent = typing.Union[
30
+ h11.Request,
31
+ h11.Data,
32
+ h11.EndOfMessage,
33
+ ]
34
+
35
+
36
+ class HTTPConnectionState(enum.IntEnum):
37
+ NEW = 0
38
+ ACTIVE = 1
39
+ IDLE = 2
40
+ CLOSED = 3
41
+
42
+
43
+ class HTTP11Connection(ConnectionInterface):
44
+ READ_NUM_BYTES = 64 * 1024
45
+ MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024
46
+
47
+ def __init__(
48
+ self,
49
+ origin: Origin,
50
+ stream: NetworkStream,
51
+ keepalive_expiry: float | None = None,
52
+ ) -> None:
53
+ self._origin = origin
54
+ self._network_stream = stream
55
+ self._keepalive_expiry: float | None = keepalive_expiry
56
+ self._expire_at: float | None = None
57
+ self._state = HTTPConnectionState.NEW
58
+ self._state_lock = Lock()
59
+ self._request_count = 0
60
+ self._h11_state = h11.Connection(
61
+ our_role=h11.CLIENT,
62
+ max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE,
63
+ )
64
+
65
+ def handle_request(self, request: Request) -> Response:
66
+ if not self.can_handle_request(request.url.origin):
67
+ raise RuntimeError(
68
+ f"Attempted to send request to {request.url.origin} on connection "
69
+ f"to {self._origin}"
70
+ )
71
+
72
+ with self._state_lock:
73
+ if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE):
74
+ self._request_count += 1
75
+ self._state = HTTPConnectionState.ACTIVE
76
+ self._expire_at = None
77
+ else:
78
+ raise ConnectionNotAvailable()
79
+
80
+ try:
81
+ kwargs = {"request": request}
82
+ try:
83
+ with Trace(
84
+ "send_request_headers", logger, request, kwargs
85
+ ) as trace:
86
+ self._send_request_headers(**kwargs)
87
+ with Trace("send_request_body", logger, request, kwargs) as trace:
88
+ self._send_request_body(**kwargs)
89
+ except WriteError:
90
+ # If we get a write error while we're writing the request,
91
+ # then we supress this error and move on to attempting to
92
+ # read the response. Servers can sometimes close the request
93
+ # pre-emptively and then respond with a well formed HTTP
94
+ # error response.
95
+ pass
96
+
97
+ with Trace(
98
+ "receive_response_headers", logger, request, kwargs
99
+ ) as trace:
100
+ (
101
+ http_version,
102
+ status,
103
+ reason_phrase,
104
+ headers,
105
+ trailing_data,
106
+ ) = self._receive_response_headers(**kwargs)
107
+ trace.return_value = (
108
+ http_version,
109
+ status,
110
+ reason_phrase,
111
+ headers,
112
+ )
113
+
114
+ network_stream = self._network_stream
115
+
116
+ # CONNECT or Upgrade request
117
+ if (status == 101) or (
118
+ (request.method == b"CONNECT") and (200 <= status < 300)
119
+ ):
120
+ network_stream = HTTP11UpgradeStream(network_stream, trailing_data)
121
+
122
+ return Response(
123
+ status=status,
124
+ headers=headers,
125
+ content=HTTP11ConnectionByteStream(self, request),
126
+ extensions={
127
+ "http_version": http_version,
128
+ "reason_phrase": reason_phrase,
129
+ "network_stream": network_stream,
130
+ },
131
+ )
132
+ except BaseException as exc:
133
+ with ShieldCancellation():
134
+ with Trace("response_closed", logger, request) as trace:
135
+ self._response_closed()
136
+ raise exc
137
+
138
+ # Sending the request...
139
+
140
+ def _send_request_headers(self, request: Request) -> None:
141
+ timeouts = request.extensions.get("timeout", {})
142
+ timeout = timeouts.get("write", None)
143
+
144
+ with map_exceptions({h11.LocalProtocolError: LocalProtocolError}):
145
+ event = h11.Request(
146
+ method=request.method,
147
+ target=request.url.target,
148
+ headers=request.headers,
149
+ )
150
+ self._send_event(event, timeout=timeout)
151
+
152
+ def _send_request_body(self, request: Request) -> None:
153
+ timeouts = request.extensions.get("timeout", {})
154
+ timeout = timeouts.get("write", None)
155
+
156
+ assert isinstance(request.stream, typing.Iterable)
157
+ for chunk in request.stream:
158
+ event = h11.Data(data=chunk)
159
+ self._send_event(event, timeout=timeout)
160
+
161
+ self._send_event(h11.EndOfMessage(), timeout=timeout)
162
+
163
+ def _send_event(self, event: h11.Event, timeout: float | None = None) -> None:
164
+ bytes_to_send = self._h11_state.send(event)
165
+ if bytes_to_send is not None:
166
+ self._network_stream.write(bytes_to_send, timeout=timeout)
167
+
168
+ # Receiving the response...
169
+
170
+ def _receive_response_headers(
171
+ self, request: Request
172
+ ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]:
173
+ timeouts = request.extensions.get("timeout", {})
174
+ timeout = timeouts.get("read", None)
175
+
176
+ while True:
177
+ event = self._receive_event(timeout=timeout)
178
+ if isinstance(event, h11.Response):
179
+ break
180
+ if (
181
+ isinstance(event, h11.InformationalResponse)
182
+ and event.status_code == 101
183
+ ):
184
+ break
185
+
186
+ http_version = b"HTTP/" + event.http_version
187
+
188
+ # h11 version 0.11+ supports a `raw_items` interface to get the
189
+ # raw header casing, rather than the enforced lowercase headers.
190
+ headers = event.headers.raw_items()
191
+
192
+ trailing_data, _ = self._h11_state.trailing_data
193
+
194
+ return http_version, event.status_code, event.reason, headers, trailing_data
195
+
196
+ def _receive_response_body(
197
+ self, request: Request
198
+ ) -> typing.Iterator[bytes]:
199
+ timeouts = request.extensions.get("timeout", {})
200
+ timeout = timeouts.get("read", None)
201
+
202
+ while True:
203
+ event = self._receive_event(timeout=timeout)
204
+ if isinstance(event, h11.Data):
205
+ yield bytes(event.data)
206
+ elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)):
207
+ break
208
+
209
+ def _receive_event(
210
+ self, timeout: float | None = None
211
+ ) -> h11.Event | type[h11.PAUSED]:
212
+ while True:
213
+ with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}):
214
+ event = self._h11_state.next_event()
215
+
216
+ if event is h11.NEED_DATA:
217
+ data = self._network_stream.read(
218
+ self.READ_NUM_BYTES, timeout=timeout
219
+ )
220
+
221
+ # If we feed this case through h11 we'll raise an exception like:
222
+ #
223
+ # httpcore.RemoteProtocolError: can't handle event type
224
+ # ConnectionClosed when role=SERVER and state=SEND_RESPONSE
225
+ #
226
+ # Which is accurate, but not very informative from an end-user
227
+ # perspective. Instead we handle this case distinctly and treat
228
+ # it as a ConnectError.
229
+ if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:
230
+ msg = "Server disconnected without sending a response."
231
+ raise RemoteProtocolError(msg)
232
+
233
+ self._h11_state.receive_data(data)
234
+ else:
235
+ # mypy fails to narrow the type in the above if statement above
236
+ return event # type: ignore[return-value]
237
+
238
+ def _response_closed(self) -> None:
239
+ with self._state_lock:
240
+ if (
241
+ self._h11_state.our_state is h11.DONE
242
+ and self._h11_state.their_state is h11.DONE
243
+ ):
244
+ self._state = HTTPConnectionState.IDLE
245
+ self._h11_state.start_next_cycle()
246
+ if self._keepalive_expiry is not None:
247
+ now = time.monotonic()
248
+ self._expire_at = now + self._keepalive_expiry
249
+ else:
250
+ self.close()
251
+
252
+ # Once the connection is no longer required...
253
+
254
+ def close(self) -> None:
255
+ # Note that this method unilaterally closes the connection, and does
256
+ # not have any kind of locking in place around it.
257
+ self._state = HTTPConnectionState.CLOSED
258
+ self._network_stream.close()
259
+
260
+ # The ConnectionInterface methods provide information about the state of
261
+ # the connection, allowing for a connection pooling implementation to
262
+ # determine when to reuse and when to close the connection...
263
+
264
+ def can_handle_request(self, origin: Origin) -> bool:
265
+ return origin == self._origin
266
+
267
+ def is_available(self) -> bool:
268
+ # Note that HTTP/1.1 connections in the "NEW" state are not treated as
269
+ # being "available". The control flow which created the connection will
270
+ # be able to send an outgoing request, but the connection will not be
271
+ # acquired from the connection pool for any other request.
272
+ return self._state == HTTPConnectionState.IDLE
273
+
274
+ def has_expired(self) -> bool:
275
+ now = time.monotonic()
276
+ keepalive_expired = self._expire_at is not None and now > self._expire_at
277
+
278
+ # If the HTTP connection is idle but the socket is readable, then the
279
+ # only valid state is that the socket is about to return b"", indicating
280
+ # a server-initiated disconnect.
281
+ server_disconnected = (
282
+ self._state == HTTPConnectionState.IDLE
283
+ and self._network_stream.get_extra_info("is_readable")
284
+ )
285
+
286
+ return keepalive_expired or server_disconnected
287
+
288
+ def is_idle(self) -> bool:
289
+ return self._state == HTTPConnectionState.IDLE
290
+
291
+ def is_closed(self) -> bool:
292
+ return self._state == HTTPConnectionState.CLOSED
293
+
294
+ def info(self) -> str:
295
+ origin = str(self._origin)
296
+ return (
297
+ f"{origin!r}, HTTP/1.1, {self._state.name}, "
298
+ f"Request Count: {self._request_count}"
299
+ )
300
+
301
+ def __repr__(self) -> str:
302
+ class_name = self.__class__.__name__
303
+ origin = str(self._origin)
304
+ return (
305
+ f"<{class_name} [{origin!r}, {self._state.name}, "
306
+ f"Request Count: {self._request_count}]>"
307
+ )
308
+
309
+ # These context managers are not used in the standard flow, but are
310
+ # useful for testing or working with connection instances directly.
311
+
312
+ def __enter__(self) -> HTTP11Connection:
313
+ return self
314
+
315
+ def __exit__(
316
+ self,
317
+ exc_type: type[BaseException] | None = None,
318
+ exc_value: BaseException | None = None,
319
+ traceback: types.TracebackType | None = None,
320
+ ) -> None:
321
+ self.close()
322
+
323
+
324
+ class HTTP11ConnectionByteStream:
325
+ def __init__(self, connection: HTTP11Connection, request: Request) -> None:
326
+ self._connection = connection
327
+ self._request = request
328
+ self._closed = False
329
+
330
+ def __iter__(self) -> typing.Iterator[bytes]:
331
+ kwargs = {"request": self._request}
332
+ try:
333
+ with Trace("receive_response_body", logger, self._request, kwargs):
334
+ for chunk in self._connection._receive_response_body(**kwargs):
335
+ yield chunk
336
+ except BaseException as exc:
337
+ # If we get an exception while streaming the response,
338
+ # we want to close the response (and possibly the connection)
339
+ # before raising that exception.
340
+ with ShieldCancellation():
341
+ self.close()
342
+ raise exc
343
+
344
+ def close(self) -> None:
345
+ if not self._closed:
346
+ self._closed = True
347
+ with Trace("response_closed", logger, self._request):
348
+ self._connection._response_closed()
349
+
350
+
351
+ class HTTP11UpgradeStream(NetworkStream):
352
+ def __init__(self, stream: NetworkStream, leading_data: bytes) -> None:
353
+ self._stream = stream
354
+ self._leading_data = leading_data
355
+
356
+ def read(self, max_bytes: int, timeout: float | None = None) -> bytes:
357
+ if self._leading_data:
358
+ buffer = self._leading_data[:max_bytes]
359
+ self._leading_data = self._leading_data[max_bytes:]
360
+ return buffer
361
+ else:
362
+ return self._stream.read(max_bytes, timeout)
363
+
364
+ def write(self, buffer: bytes, timeout: float | None = None) -> None:
365
+ self._stream.write(buffer, timeout)
366
+
367
+ def close(self) -> None:
368
+ self._stream.close()
369
+
370
+ def start_tls(
371
+ self,
372
+ ssl_context: ssl.SSLContext,
373
+ server_hostname: str | None = None,
374
+ timeout: float | None = None,
375
+ ) -> NetworkStream:
376
+ return self._stream.start_tls(ssl_context, server_hostname, timeout)
377
+
378
+ def get_extra_info(self, info: str) -> typing.Any:
379
+ return self._stream.get_extra_info(info)
venv/lib/python3.12/site-packages/httpcore/_sync/http2.py ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import time
6
+ import types
7
+ import typing
8
+
9
+ import h2.config
10
+ import h2.connection
11
+ import h2.events
12
+ import h2.exceptions
13
+ import h2.settings
14
+
15
+ from .._backends.base import NetworkStream
16
+ from .._exceptions import (
17
+ ConnectionNotAvailable,
18
+ LocalProtocolError,
19
+ RemoteProtocolError,
20
+ )
21
+ from .._models import Origin, Request, Response
22
+ from .._synchronization import Lock, Semaphore, ShieldCancellation
23
+ from .._trace import Trace
24
+ from .interfaces import ConnectionInterface
25
+
26
+ logger = logging.getLogger("httpcore.http2")
27
+
28
+
29
+ def has_body_headers(request: Request) -> bool:
30
+ return any(
31
+ k.lower() == b"content-length" or k.lower() == b"transfer-encoding"
32
+ for k, v in request.headers
33
+ )
34
+
35
+
36
+ class HTTPConnectionState(enum.IntEnum):
37
+ ACTIVE = 1
38
+ IDLE = 2
39
+ CLOSED = 3
40
+
41
+
42
+ class HTTP2Connection(ConnectionInterface):
43
+ READ_NUM_BYTES = 64 * 1024
44
+ CONFIG = h2.config.H2Configuration(validate_inbound_headers=False)
45
+
46
+ def __init__(
47
+ self,
48
+ origin: Origin,
49
+ stream: NetworkStream,
50
+ keepalive_expiry: float | None = None,
51
+ ):
52
+ self._origin = origin
53
+ self._network_stream = stream
54
+ self._keepalive_expiry: float | None = keepalive_expiry
55
+ self._h2_state = h2.connection.H2Connection(config=self.CONFIG)
56
+ self._state = HTTPConnectionState.IDLE
57
+ self._expire_at: float | None = None
58
+ self._request_count = 0
59
+ self._init_lock = Lock()
60
+ self._state_lock = Lock()
61
+ self._read_lock = Lock()
62
+ self._write_lock = Lock()
63
+ self._sent_connection_init = False
64
+ self._used_all_stream_ids = False
65
+ self._connection_error = False
66
+
67
+ # Mapping from stream ID to response stream events.
68
+ self._events: dict[
69
+ int,
70
+ list[
71
+ h2.events.ResponseReceived
72
+ | h2.events.DataReceived
73
+ | h2.events.StreamEnded
74
+ | h2.events.StreamReset,
75
+ ],
76
+ ] = {}
77
+
78
+ # Connection terminated events are stored as state since
79
+ # we need to handle them for all streams.
80
+ self._connection_terminated: h2.events.ConnectionTerminated | None = None
81
+
82
+ self._read_exception: Exception | None = None
83
+ self._write_exception: Exception | None = None
84
+
85
+ def handle_request(self, request: Request) -> Response:
86
+ if not self.can_handle_request(request.url.origin):
87
+ # This cannot occur in normal operation, since the connection pool
88
+ # will only send requests on connections that handle them.
89
+ # It's in place simply for resilience as a guard against incorrect
90
+ # usage, for anyone working directly with httpcore connections.
91
+ raise RuntimeError(
92
+ f"Attempted to send request to {request.url.origin} on connection "
93
+ f"to {self._origin}"
94
+ )
95
+
96
+ with self._state_lock:
97
+ if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE):
98
+ self._request_count += 1
99
+ self._expire_at = None
100
+ self._state = HTTPConnectionState.ACTIVE
101
+ else:
102
+ raise ConnectionNotAvailable()
103
+
104
+ with self._init_lock:
105
+ if not self._sent_connection_init:
106
+ try:
107
+ sci_kwargs = {"request": request}
108
+ with Trace(
109
+ "send_connection_init", logger, request, sci_kwargs
110
+ ):
111
+ self._send_connection_init(**sci_kwargs)
112
+ except BaseException as exc:
113
+ with ShieldCancellation():
114
+ self.close()
115
+ raise exc
116
+
117
+ self._sent_connection_init = True
118
+
119
+ # Initially start with just 1 until the remote server provides
120
+ # its max_concurrent_streams value
121
+ self._max_streams = 1
122
+
123
+ local_settings_max_streams = (
124
+ self._h2_state.local_settings.max_concurrent_streams
125
+ )
126
+ self._max_streams_semaphore = Semaphore(local_settings_max_streams)
127
+
128
+ for _ in range(local_settings_max_streams - self._max_streams):
129
+ self._max_streams_semaphore.acquire()
130
+
131
+ self._max_streams_semaphore.acquire()
132
+
133
+ try:
134
+ stream_id = self._h2_state.get_next_available_stream_id()
135
+ self._events[stream_id] = []
136
+ except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover
137
+ self._used_all_stream_ids = True
138
+ self._request_count -= 1
139
+ raise ConnectionNotAvailable()
140
+
141
+ try:
142
+ kwargs = {"request": request, "stream_id": stream_id}
143
+ with Trace("send_request_headers", logger, request, kwargs):
144
+ self._send_request_headers(request=request, stream_id=stream_id)
145
+ with Trace("send_request_body", logger, request, kwargs):
146
+ self._send_request_body(request=request, stream_id=stream_id)
147
+ with Trace(
148
+ "receive_response_headers", logger, request, kwargs
149
+ ) as trace:
150
+ status, headers = self._receive_response(
151
+ request=request, stream_id=stream_id
152
+ )
153
+ trace.return_value = (status, headers)
154
+
155
+ return Response(
156
+ status=status,
157
+ headers=headers,
158
+ content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id),
159
+ extensions={
160
+ "http_version": b"HTTP/2",
161
+ "network_stream": self._network_stream,
162
+ "stream_id": stream_id,
163
+ },
164
+ )
165
+ except BaseException as exc: # noqa: PIE786
166
+ with ShieldCancellation():
167
+ kwargs = {"stream_id": stream_id}
168
+ with Trace("response_closed", logger, request, kwargs):
169
+ self._response_closed(stream_id=stream_id)
170
+
171
+ if isinstance(exc, h2.exceptions.ProtocolError):
172
+ # One case where h2 can raise a protocol error is when a
173
+ # closed frame has been seen by the state machine.
174
+ #
175
+ # This happens when one stream is reading, and encounters
176
+ # a GOAWAY event. Other flows of control may then raise
177
+ # a protocol error at any point they interact with the 'h2_state'.
178
+ #
179
+ # In this case we'll have stored the event, and should raise
180
+ # it as a RemoteProtocolError.
181
+ if self._connection_terminated: # pragma: nocover
182
+ raise RemoteProtocolError(self._connection_terminated)
183
+ # If h2 raises a protocol error in some other state then we
184
+ # must somehow have made a protocol violation.
185
+ raise LocalProtocolError(exc) # pragma: nocover
186
+
187
+ raise exc
188
+
189
+ def _send_connection_init(self, request: Request) -> None:
190
+ """
191
+ The HTTP/2 connection requires some initial setup before we can start
192
+ using individual request/response streams on it.
193
+ """
194
+ # Need to set these manually here instead of manipulating via
195
+ # __setitem__() otherwise the H2Connection will emit SettingsUpdate
196
+ # frames in addition to sending the undesired defaults.
197
+ self._h2_state.local_settings = h2.settings.Settings(
198
+ client=True,
199
+ initial_values={
200
+ # Disable PUSH_PROMISE frames from the server since we don't do anything
201
+ # with them for now. Maybe when we support caching?
202
+ h2.settings.SettingCodes.ENABLE_PUSH: 0,
203
+ # These two are taken from h2 for safe defaults
204
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
205
+ h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536,
206
+ },
207
+ )
208
+
209
+ # Some websites (*cough* Yahoo *cough*) balk at this setting being
210
+ # present in the initial handshake since it's not defined in the original
211
+ # RFC despite the RFC mandating ignoring settings you don't know about.
212
+ del self._h2_state.local_settings[
213
+ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL
214
+ ]
215
+
216
+ self._h2_state.initiate_connection()
217
+ self._h2_state.increment_flow_control_window(2**24)
218
+ self._write_outgoing_data(request)
219
+
220
+ # Sending the request...
221
+
222
+ def _send_request_headers(self, request: Request, stream_id: int) -> None:
223
+ """
224
+ Send the request headers to a given stream ID.
225
+ """
226
+ end_stream = not has_body_headers(request)
227
+
228
+ # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'.
229
+ # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require
230
+ # HTTP/1.1 style headers, and map them appropriately if we end up on
231
+ # an HTTP/2 connection.
232
+ authority = [v for k, v in request.headers if k.lower() == b"host"][0]
233
+
234
+ headers = [
235
+ (b":method", request.method),
236
+ (b":authority", authority),
237
+ (b":scheme", request.url.scheme),
238
+ (b":path", request.url.target),
239
+ ] + [
240
+ (k.lower(), v)
241
+ for k, v in request.headers
242
+ if k.lower()
243
+ not in (
244
+ b"host",
245
+ b"transfer-encoding",
246
+ )
247
+ ]
248
+
249
+ self._h2_state.send_headers(stream_id, headers, end_stream=end_stream)
250
+ self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id)
251
+ self._write_outgoing_data(request)
252
+
253
+ def _send_request_body(self, request: Request, stream_id: int) -> None:
254
+ """
255
+ Iterate over the request body sending it to a given stream ID.
256
+ """
257
+ if not has_body_headers(request):
258
+ return
259
+
260
+ assert isinstance(request.stream, typing.Iterable)
261
+ for data in request.stream:
262
+ self._send_stream_data(request, stream_id, data)
263
+ self._send_end_stream(request, stream_id)
264
+
265
+ def _send_stream_data(
266
+ self, request: Request, stream_id: int, data: bytes
267
+ ) -> None:
268
+ """
269
+ Send a single chunk of data in one or more data frames.
270
+ """
271
+ while data:
272
+ max_flow = self._wait_for_outgoing_flow(request, stream_id)
273
+ chunk_size = min(len(data), max_flow)
274
+ chunk, data = data[:chunk_size], data[chunk_size:]
275
+ self._h2_state.send_data(stream_id, chunk)
276
+ self._write_outgoing_data(request)
277
+
278
+ def _send_end_stream(self, request: Request, stream_id: int) -> None:
279
+ """
280
+ Send an empty data frame on on a given stream ID with the END_STREAM flag set.
281
+ """
282
+ self._h2_state.end_stream(stream_id)
283
+ self._write_outgoing_data(request)
284
+
285
+ # Receiving the response...
286
+
287
+ def _receive_response(
288
+ self, request: Request, stream_id: int
289
+ ) -> tuple[int, list[tuple[bytes, bytes]]]:
290
+ """
291
+ Return the response status code and headers for a given stream ID.
292
+ """
293
+ while True:
294
+ event = self._receive_stream_event(request, stream_id)
295
+ if isinstance(event, h2.events.ResponseReceived):
296
+ break
297
+
298
+ status_code = 200
299
+ headers = []
300
+ assert event.headers is not None
301
+ for k, v in event.headers:
302
+ if k == b":status":
303
+ status_code = int(v.decode("ascii", errors="ignore"))
304
+ elif not k.startswith(b":"):
305
+ headers.append((k, v))
306
+
307
+ return (status_code, headers)
308
+
309
+ def _receive_response_body(
310
+ self, request: Request, stream_id: int
311
+ ) -> typing.Iterator[bytes]:
312
+ """
313
+ Iterator that returns the bytes of the response body for a given stream ID.
314
+ """
315
+ while True:
316
+ event = self._receive_stream_event(request, stream_id)
317
+ if isinstance(event, h2.events.DataReceived):
318
+ assert event.flow_controlled_length is not None
319
+ assert event.data is not None
320
+ amount = event.flow_controlled_length
321
+ self._h2_state.acknowledge_received_data(amount, stream_id)
322
+ self._write_outgoing_data(request)
323
+ yield event.data
324
+ elif isinstance(event, h2.events.StreamEnded):
325
+ break
326
+
327
+ def _receive_stream_event(
328
+ self, request: Request, stream_id: int
329
+ ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded:
330
+ """
331
+ Return the next available event for a given stream ID.
332
+
333
+ Will read more data from the network if required.
334
+ """
335
+ while not self._events.get(stream_id):
336
+ self._receive_events(request, stream_id)
337
+ event = self._events[stream_id].pop(0)
338
+ if isinstance(event, h2.events.StreamReset):
339
+ raise RemoteProtocolError(event)
340
+ return event
341
+
342
+ def _receive_events(
343
+ self, request: Request, stream_id: int | None = None
344
+ ) -> None:
345
+ """
346
+ Read some data from the network until we see one or more events
347
+ for a given stream ID.
348
+ """
349
+ with self._read_lock:
350
+ if self._connection_terminated is not None:
351
+ last_stream_id = self._connection_terminated.last_stream_id
352
+ if stream_id and last_stream_id and stream_id > last_stream_id:
353
+ self._request_count -= 1
354
+ raise ConnectionNotAvailable()
355
+ raise RemoteProtocolError(self._connection_terminated)
356
+
357
+ # This conditional is a bit icky. We don't want to block reading if we've
358
+ # actually got an event to return for a given stream. We need to do that
359
+ # check *within* the atomic read lock. Though it also need to be optional,
360
+ # because when we call it from `_wait_for_outgoing_flow` we *do* want to
361
+ # block until we've available flow control, event when we have events
362
+ # pending for the stream ID we're attempting to send on.
363
+ if stream_id is None or not self._events.get(stream_id):
364
+ events = self._read_incoming_data(request)
365
+ for event in events:
366
+ if isinstance(event, h2.events.RemoteSettingsChanged):
367
+ with Trace(
368
+ "receive_remote_settings", logger, request
369
+ ) as trace:
370
+ self._receive_remote_settings_change(event)
371
+ trace.return_value = event
372
+
373
+ elif isinstance(
374
+ event,
375
+ (
376
+ h2.events.ResponseReceived,
377
+ h2.events.DataReceived,
378
+ h2.events.StreamEnded,
379
+ h2.events.StreamReset,
380
+ ),
381
+ ):
382
+ if event.stream_id in self._events:
383
+ self._events[event.stream_id].append(event)
384
+
385
+ elif isinstance(event, h2.events.ConnectionTerminated):
386
+ self._connection_terminated = event
387
+
388
+ self._write_outgoing_data(request)
389
+
390
+ def _receive_remote_settings_change(
391
+ self, event: h2.events.RemoteSettingsChanged
392
+ ) -> None:
393
+ max_concurrent_streams = event.changed_settings.get(
394
+ h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS
395
+ )
396
+ if max_concurrent_streams:
397
+ new_max_streams = min(
398
+ max_concurrent_streams.new_value,
399
+ self._h2_state.local_settings.max_concurrent_streams,
400
+ )
401
+ if new_max_streams and new_max_streams != self._max_streams:
402
+ while new_max_streams > self._max_streams:
403
+ self._max_streams_semaphore.release()
404
+ self._max_streams += 1
405
+ while new_max_streams < self._max_streams:
406
+ self._max_streams_semaphore.acquire()
407
+ self._max_streams -= 1
408
+
409
+ def _response_closed(self, stream_id: int) -> None:
410
+ self._max_streams_semaphore.release()
411
+ del self._events[stream_id]
412
+ with self._state_lock:
413
+ if self._connection_terminated and not self._events:
414
+ self.close()
415
+
416
+ elif self._state == HTTPConnectionState.ACTIVE and not self._events:
417
+ self._state = HTTPConnectionState.IDLE
418
+ if self._keepalive_expiry is not None:
419
+ now = time.monotonic()
420
+ self._expire_at = now + self._keepalive_expiry
421
+ if self._used_all_stream_ids: # pragma: nocover
422
+ self.close()
423
+
424
+ def close(self) -> None:
425
+ # Note that this method unilaterally closes the connection, and does
426
+ # not have any kind of locking in place around it.
427
+ self._h2_state.close_connection()
428
+ self._state = HTTPConnectionState.CLOSED
429
+ self._network_stream.close()
430
+
431
+ # Wrappers around network read/write operations...
432
+
433
+ def _read_incoming_data(self, request: Request) -> list[h2.events.Event]:
434
+ timeouts = request.extensions.get("timeout", {})
435
+ timeout = timeouts.get("read", None)
436
+
437
+ if self._read_exception is not None:
438
+ raise self._read_exception # pragma: nocover
439
+
440
+ try:
441
+ data = self._network_stream.read(self.READ_NUM_BYTES, timeout)
442
+ if data == b"":
443
+ raise RemoteProtocolError("Server disconnected")
444
+ except Exception as exc:
445
+ # If we get a network error we should:
446
+ #
447
+ # 1. Save the exception and just raise it immediately on any future reads.
448
+ # (For example, this means that a single read timeout or disconnect will
449
+ # immediately close all pending streams. Without requiring multiple
450
+ # sequential timeouts.)
451
+ # 2. Mark the connection as errored, so that we don't accept any other
452
+ # incoming requests.
453
+ self._read_exception = exc
454
+ self._connection_error = True
455
+ raise exc
456
+
457
+ events: list[h2.events.Event] = self._h2_state.receive_data(data)
458
+
459
+ return events
460
+
461
+ def _write_outgoing_data(self, request: Request) -> None:
462
+ timeouts = request.extensions.get("timeout", {})
463
+ timeout = timeouts.get("write", None)
464
+
465
+ with self._write_lock:
466
+ data_to_send = self._h2_state.data_to_send()
467
+
468
+ if self._write_exception is not None:
469
+ raise self._write_exception # pragma: nocover
470
+
471
+ try:
472
+ self._network_stream.write(data_to_send, timeout)
473
+ except Exception as exc: # pragma: nocover
474
+ # If we get a network error we should:
475
+ #
476
+ # 1. Save the exception and just raise it immediately on any future write.
477
+ # (For example, this means that a single write timeout or disconnect will
478
+ # immediately close all pending streams. Without requiring multiple
479
+ # sequential timeouts.)
480
+ # 2. Mark the connection as errored, so that we don't accept any other
481
+ # incoming requests.
482
+ self._write_exception = exc
483
+ self._connection_error = True
484
+ raise exc
485
+
486
+ # Flow control...
487
+
488
+ def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int:
489
+ """
490
+ Returns the maximum allowable outgoing flow for a given stream.
491
+
492
+ If the allowable flow is zero, then waits on the network until
493
+ WindowUpdated frames have increased the flow rate.
494
+ https://tools.ietf.org/html/rfc7540#section-6.9
495
+ """
496
+ local_flow: int = self._h2_state.local_flow_control_window(stream_id)
497
+ max_frame_size: int = self._h2_state.max_outbound_frame_size
498
+ flow = min(local_flow, max_frame_size)
499
+ while flow == 0:
500
+ self._receive_events(request)
501
+ local_flow = self._h2_state.local_flow_control_window(stream_id)
502
+ max_frame_size = self._h2_state.max_outbound_frame_size
503
+ flow = min(local_flow, max_frame_size)
504
+ return flow
505
+
506
+ # Interface for connection pooling...
507
+
508
+ def can_handle_request(self, origin: Origin) -> bool:
509
+ return origin == self._origin
510
+
511
+ def is_available(self) -> bool:
512
+ return (
513
+ self._state != HTTPConnectionState.CLOSED
514
+ and not self._connection_error
515
+ and not self._used_all_stream_ids
516
+ and not (
517
+ self._h2_state.state_machine.state
518
+ == h2.connection.ConnectionState.CLOSED
519
+ )
520
+ )
521
+
522
+ def has_expired(self) -> bool:
523
+ now = time.monotonic()
524
+ return self._expire_at is not None and now > self._expire_at
525
+
526
+ def is_idle(self) -> bool:
527
+ return self._state == HTTPConnectionState.IDLE
528
+
529
+ def is_closed(self) -> bool:
530
+ return self._state == HTTPConnectionState.CLOSED
531
+
532
+ def info(self) -> str:
533
+ origin = str(self._origin)
534
+ return (
535
+ f"{origin!r}, HTTP/2, {self._state.name}, "
536
+ f"Request Count: {self._request_count}"
537
+ )
538
+
539
+ def __repr__(self) -> str:
540
+ class_name = self.__class__.__name__
541
+ origin = str(self._origin)
542
+ return (
543
+ f"<{class_name} [{origin!r}, {self._state.name}, "
544
+ f"Request Count: {self._request_count}]>"
545
+ )
546
+
547
+ # These context managers are not used in the standard flow, but are
548
+ # useful for testing or working with connection instances directly.
549
+
550
+ def __enter__(self) -> HTTP2Connection:
551
+ return self
552
+
553
+ def __exit__(
554
+ self,
555
+ exc_type: type[BaseException] | None = None,
556
+ exc_value: BaseException | None = None,
557
+ traceback: types.TracebackType | None = None,
558
+ ) -> None:
559
+ self.close()
560
+
561
+
562
+ class HTTP2ConnectionByteStream:
563
+ def __init__(
564
+ self, connection: HTTP2Connection, request: Request, stream_id: int
565
+ ) -> None:
566
+ self._connection = connection
567
+ self._request = request
568
+ self._stream_id = stream_id
569
+ self._closed = False
570
+
571
+ def __iter__(self) -> typing.Iterator[bytes]:
572
+ kwargs = {"request": self._request, "stream_id": self._stream_id}
573
+ try:
574
+ with Trace("receive_response_body", logger, self._request, kwargs):
575
+ for chunk in self._connection._receive_response_body(
576
+ request=self._request, stream_id=self._stream_id
577
+ ):
578
+ yield chunk
579
+ except BaseException as exc:
580
+ # If we get an exception while streaming the response,
581
+ # we want to close the response (and possibly the connection)
582
+ # before raising that exception.
583
+ with ShieldCancellation():
584
+ self.close()
585
+ raise exc
586
+
587
+ def close(self) -> None:
588
+ if not self._closed:
589
+ self._closed = True
590
+ kwargs = {"stream_id": self._stream_id}
591
+ with Trace("response_closed", logger, self._request, kwargs):
592
+ self._connection._response_closed(stream_id=self._stream_id)
venv/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import logging
5
+ import ssl
6
+ import typing
7
+
8
+ from .._backends.base import SOCKET_OPTION, NetworkBackend
9
+ from .._exceptions import ProxyError
10
+ from .._models import (
11
+ URL,
12
+ Origin,
13
+ Request,
14
+ Response,
15
+ enforce_bytes,
16
+ enforce_headers,
17
+ enforce_url,
18
+ )
19
+ from .._ssl import default_ssl_context
20
+ from .._synchronization import Lock
21
+ from .._trace import Trace
22
+ from .connection import HTTPConnection
23
+ from .connection_pool import ConnectionPool
24
+ from .http11 import HTTP11Connection
25
+ from .interfaces import ConnectionInterface
26
+
27
+ ByteOrStr = typing.Union[bytes, str]
28
+ HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]
29
+ HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]
30
+
31
+
32
+ logger = logging.getLogger("httpcore.proxy")
33
+
34
+
35
+ def merge_headers(
36
+ default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
37
+ override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
38
+ ) -> list[tuple[bytes, bytes]]:
39
+ """
40
+ Append default_headers and override_headers, de-duplicating if a key exists
41
+ in both cases.
42
+ """
43
+ default_headers = [] if default_headers is None else list(default_headers)
44
+ override_headers = [] if override_headers is None else list(override_headers)
45
+ has_override = set(key.lower() for key, value in override_headers)
46
+ default_headers = [
47
+ (key, value)
48
+ for key, value in default_headers
49
+ if key.lower() not in has_override
50
+ ]
51
+ return default_headers + override_headers
52
+
53
+
54
+ class HTTPProxy(ConnectionPool): # pragma: nocover
55
+ """
56
+ A connection pool that sends requests via an HTTP proxy.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ proxy_url: URL | bytes | str,
62
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
63
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
64
+ ssl_context: ssl.SSLContext | None = None,
65
+ proxy_ssl_context: ssl.SSLContext | None = None,
66
+ max_connections: int | None = 10,
67
+ max_keepalive_connections: int | None = None,
68
+ keepalive_expiry: float | None = None,
69
+ http1: bool = True,
70
+ http2: bool = False,
71
+ retries: int = 0,
72
+ local_address: str | None = None,
73
+ uds: str | None = None,
74
+ network_backend: NetworkBackend | None = None,
75
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
76
+ ) -> None:
77
+ """
78
+ A connection pool for making HTTP requests.
79
+
80
+ Parameters:
81
+ proxy_url: The URL to use when connecting to the proxy server.
82
+ For example `"http://127.0.0.1:8080/"`.
83
+ proxy_auth: Any proxy authentication as a two-tuple of
84
+ (username, password). May be either bytes or ascii-only str.
85
+ proxy_headers: Any HTTP headers to use for the proxy requests.
86
+ For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.
87
+ ssl_context: An SSL context to use for verifying connections.
88
+ If not specified, the default `httpcore.default_ssl_context()`
89
+ will be used.
90
+ proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.
91
+ max_connections: The maximum number of concurrent HTTP connections that
92
+ the pool should allow. Any attempt to send a request on a pool that
93
+ would exceed this amount will block until a connection is available.
94
+ max_keepalive_connections: The maximum number of idle HTTP connections
95
+ that will be maintained in the pool.
96
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
97
+ may be maintained for before being expired from the pool.
98
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
99
+ by the connection pool. Defaults to True.
100
+ http2: A boolean indicating if HTTP/2 requests should be supported by
101
+ the connection pool. Defaults to False.
102
+ retries: The maximum number of retries when trying to establish
103
+ a connection.
104
+ local_address: Local address to connect from. Can also be used to
105
+ connect using a particular address family. Using
106
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
107
+ (IPv4), while using `local_address="::"` will connect using an
108
+ `AF_INET6` address (IPv6).
109
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
110
+ network_backend: A backend instance to use for handling network I/O.
111
+ """
112
+ super().__init__(
113
+ ssl_context=ssl_context,
114
+ max_connections=max_connections,
115
+ max_keepalive_connections=max_keepalive_connections,
116
+ keepalive_expiry=keepalive_expiry,
117
+ http1=http1,
118
+ http2=http2,
119
+ network_backend=network_backend,
120
+ retries=retries,
121
+ local_address=local_address,
122
+ uds=uds,
123
+ socket_options=socket_options,
124
+ )
125
+
126
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
127
+ if (
128
+ self._proxy_url.scheme == b"http" and proxy_ssl_context is not None
129
+ ): # pragma: no cover
130
+ raise RuntimeError(
131
+ "The `proxy_ssl_context` argument is not allowed for the http scheme"
132
+ )
133
+
134
+ self._ssl_context = ssl_context
135
+ self._proxy_ssl_context = proxy_ssl_context
136
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
137
+ if proxy_auth is not None:
138
+ username = enforce_bytes(proxy_auth[0], name="proxy_auth")
139
+ password = enforce_bytes(proxy_auth[1], name="proxy_auth")
140
+ userpass = username + b":" + password
141
+ authorization = b"Basic " + base64.b64encode(userpass)
142
+ self._proxy_headers = [
143
+ (b"Proxy-Authorization", authorization)
144
+ ] + self._proxy_headers
145
+
146
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
147
+ if origin.scheme == b"http":
148
+ return ForwardHTTPConnection(
149
+ proxy_origin=self._proxy_url.origin,
150
+ proxy_headers=self._proxy_headers,
151
+ remote_origin=origin,
152
+ keepalive_expiry=self._keepalive_expiry,
153
+ network_backend=self._network_backend,
154
+ proxy_ssl_context=self._proxy_ssl_context,
155
+ )
156
+ return TunnelHTTPConnection(
157
+ proxy_origin=self._proxy_url.origin,
158
+ proxy_headers=self._proxy_headers,
159
+ remote_origin=origin,
160
+ ssl_context=self._ssl_context,
161
+ proxy_ssl_context=self._proxy_ssl_context,
162
+ keepalive_expiry=self._keepalive_expiry,
163
+ http1=self._http1,
164
+ http2=self._http2,
165
+ network_backend=self._network_backend,
166
+ )
167
+
168
+
169
+ class ForwardHTTPConnection(ConnectionInterface):
170
+ def __init__(
171
+ self,
172
+ proxy_origin: Origin,
173
+ remote_origin: Origin,
174
+ proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,
175
+ keepalive_expiry: float | None = None,
176
+ network_backend: NetworkBackend | None = None,
177
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
178
+ proxy_ssl_context: ssl.SSLContext | None = None,
179
+ ) -> None:
180
+ self._connection = HTTPConnection(
181
+ origin=proxy_origin,
182
+ keepalive_expiry=keepalive_expiry,
183
+ network_backend=network_backend,
184
+ socket_options=socket_options,
185
+ ssl_context=proxy_ssl_context,
186
+ )
187
+ self._proxy_origin = proxy_origin
188
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
189
+ self._remote_origin = remote_origin
190
+
191
+ def handle_request(self, request: Request) -> Response:
192
+ headers = merge_headers(self._proxy_headers, request.headers)
193
+ url = URL(
194
+ scheme=self._proxy_origin.scheme,
195
+ host=self._proxy_origin.host,
196
+ port=self._proxy_origin.port,
197
+ target=bytes(request.url),
198
+ )
199
+ proxy_request = Request(
200
+ method=request.method,
201
+ url=url,
202
+ headers=headers,
203
+ content=request.stream,
204
+ extensions=request.extensions,
205
+ )
206
+ return self._connection.handle_request(proxy_request)
207
+
208
+ def can_handle_request(self, origin: Origin) -> bool:
209
+ return origin == self._remote_origin
210
+
211
+ def close(self) -> None:
212
+ self._connection.close()
213
+
214
+ def info(self) -> str:
215
+ return self._connection.info()
216
+
217
+ def is_available(self) -> bool:
218
+ return self._connection.is_available()
219
+
220
+ def has_expired(self) -> bool:
221
+ return self._connection.has_expired()
222
+
223
+ def is_idle(self) -> bool:
224
+ return self._connection.is_idle()
225
+
226
+ def is_closed(self) -> bool:
227
+ return self._connection.is_closed()
228
+
229
+ def __repr__(self) -> str:
230
+ return f"<{self.__class__.__name__} [{self.info()}]>"
231
+
232
+
233
+ class TunnelHTTPConnection(ConnectionInterface):
234
+ def __init__(
235
+ self,
236
+ proxy_origin: Origin,
237
+ remote_origin: Origin,
238
+ ssl_context: ssl.SSLContext | None = None,
239
+ proxy_ssl_context: ssl.SSLContext | None = None,
240
+ proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,
241
+ keepalive_expiry: float | None = None,
242
+ http1: bool = True,
243
+ http2: bool = False,
244
+ network_backend: NetworkBackend | None = None,
245
+ socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
246
+ ) -> None:
247
+ self._connection: ConnectionInterface = HTTPConnection(
248
+ origin=proxy_origin,
249
+ keepalive_expiry=keepalive_expiry,
250
+ network_backend=network_backend,
251
+ socket_options=socket_options,
252
+ ssl_context=proxy_ssl_context,
253
+ )
254
+ self._proxy_origin = proxy_origin
255
+ self._remote_origin = remote_origin
256
+ self._ssl_context = ssl_context
257
+ self._proxy_ssl_context = proxy_ssl_context
258
+ self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")
259
+ self._keepalive_expiry = keepalive_expiry
260
+ self._http1 = http1
261
+ self._http2 = http2
262
+ self._connect_lock = Lock()
263
+ self._connected = False
264
+
265
+ def handle_request(self, request: Request) -> Response:
266
+ timeouts = request.extensions.get("timeout", {})
267
+ timeout = timeouts.get("connect", None)
268
+
269
+ with self._connect_lock:
270
+ if not self._connected:
271
+ target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)
272
+
273
+ connect_url = URL(
274
+ scheme=self._proxy_origin.scheme,
275
+ host=self._proxy_origin.host,
276
+ port=self._proxy_origin.port,
277
+ target=target,
278
+ )
279
+ connect_headers = merge_headers(
280
+ [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers
281
+ )
282
+ connect_request = Request(
283
+ method=b"CONNECT",
284
+ url=connect_url,
285
+ headers=connect_headers,
286
+ extensions=request.extensions,
287
+ )
288
+ connect_response = self._connection.handle_request(
289
+ connect_request
290
+ )
291
+
292
+ if connect_response.status < 200 or connect_response.status > 299:
293
+ reason_bytes = connect_response.extensions.get("reason_phrase", b"")
294
+ reason_str = reason_bytes.decode("ascii", errors="ignore")
295
+ msg = "%d %s" % (connect_response.status, reason_str)
296
+ self._connection.close()
297
+ raise ProxyError(msg)
298
+
299
+ stream = connect_response.extensions["network_stream"]
300
+
301
+ # Upgrade the stream to SSL
302
+ ssl_context = (
303
+ default_ssl_context()
304
+ if self._ssl_context is None
305
+ else self._ssl_context
306
+ )
307
+ alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
308
+ ssl_context.set_alpn_protocols(alpn_protocols)
309
+
310
+ kwargs = {
311
+ "ssl_context": ssl_context,
312
+ "server_hostname": self._remote_origin.host.decode("ascii"),
313
+ "timeout": timeout,
314
+ }
315
+ with Trace("start_tls", logger, request, kwargs) as trace:
316
+ stream = stream.start_tls(**kwargs)
317
+ trace.return_value = stream
318
+
319
+ # Determine if we should be using HTTP/1.1 or HTTP/2
320
+ ssl_object = stream.get_extra_info("ssl_object")
321
+ http2_negotiated = (
322
+ ssl_object is not None
323
+ and ssl_object.selected_alpn_protocol() == "h2"
324
+ )
325
+
326
+ # Create the HTTP/1.1 or HTTP/2 connection
327
+ if http2_negotiated or (self._http2 and not self._http1):
328
+ from .http2 import HTTP2Connection
329
+
330
+ self._connection = HTTP2Connection(
331
+ origin=self._remote_origin,
332
+ stream=stream,
333
+ keepalive_expiry=self._keepalive_expiry,
334
+ )
335
+ else:
336
+ self._connection = HTTP11Connection(
337
+ origin=self._remote_origin,
338
+ stream=stream,
339
+ keepalive_expiry=self._keepalive_expiry,
340
+ )
341
+
342
+ self._connected = True
343
+ return self._connection.handle_request(request)
344
+
345
+ def can_handle_request(self, origin: Origin) -> bool:
346
+ return origin == self._remote_origin
347
+
348
+ def close(self) -> None:
349
+ self._connection.close()
350
+
351
+ def info(self) -> str:
352
+ return self._connection.info()
353
+
354
+ def is_available(self) -> bool:
355
+ return self._connection.is_available()
356
+
357
+ def has_expired(self) -> bool:
358
+ return self._connection.has_expired()
359
+
360
+ def is_idle(self) -> bool:
361
+ return self._connection.is_idle()
362
+
363
+ def is_closed(self) -> bool:
364
+ return self._connection.is_closed()
365
+
366
+ def __repr__(self) -> str:
367
+ return f"<{self.__class__.__name__} [{self.info()}]>"
venv/lib/python3.12/site-packages/httpcore/_sync/interfaces.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import typing
5
+
6
+ from .._models import (
7
+ URL,
8
+ Extensions,
9
+ HeaderTypes,
10
+ Origin,
11
+ Request,
12
+ Response,
13
+ enforce_bytes,
14
+ enforce_headers,
15
+ enforce_url,
16
+ include_request_headers,
17
+ )
18
+
19
+
20
+ class RequestInterface:
21
+ def request(
22
+ self,
23
+ method: bytes | str,
24
+ url: URL | bytes | str,
25
+ *,
26
+ headers: HeaderTypes = None,
27
+ content: bytes | typing.Iterator[bytes] | None = None,
28
+ extensions: Extensions | None = None,
29
+ ) -> Response:
30
+ # Strict type checking on our parameters.
31
+ method = enforce_bytes(method, name="method")
32
+ url = enforce_url(url, name="url")
33
+ headers = enforce_headers(headers, name="headers")
34
+
35
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
36
+ headers = include_request_headers(headers, url=url, content=content)
37
+
38
+ request = Request(
39
+ method=method,
40
+ url=url,
41
+ headers=headers,
42
+ content=content,
43
+ extensions=extensions,
44
+ )
45
+ response = self.handle_request(request)
46
+ try:
47
+ response.read()
48
+ finally:
49
+ response.close()
50
+ return response
51
+
52
+ @contextlib.contextmanager
53
+ def stream(
54
+ self,
55
+ method: bytes | str,
56
+ url: URL | bytes | str,
57
+ *,
58
+ headers: HeaderTypes = None,
59
+ content: bytes | typing.Iterator[bytes] | None = None,
60
+ extensions: Extensions | None = None,
61
+ ) -> typing.Iterator[Response]:
62
+ # Strict type checking on our parameters.
63
+ method = enforce_bytes(method, name="method")
64
+ url = enforce_url(url, name="url")
65
+ headers = enforce_headers(headers, name="headers")
66
+
67
+ # Include Host header, and optionally Content-Length or Transfer-Encoding.
68
+ headers = include_request_headers(headers, url=url, content=content)
69
+
70
+ request = Request(
71
+ method=method,
72
+ url=url,
73
+ headers=headers,
74
+ content=content,
75
+ extensions=extensions,
76
+ )
77
+ response = self.handle_request(request)
78
+ try:
79
+ yield response
80
+ finally:
81
+ response.close()
82
+
83
+ def handle_request(self, request: Request) -> Response:
84
+ raise NotImplementedError() # pragma: nocover
85
+
86
+
87
+ class ConnectionInterface(RequestInterface):
88
+ def close(self) -> None:
89
+ raise NotImplementedError() # pragma: nocover
90
+
91
+ def info(self) -> str:
92
+ raise NotImplementedError() # pragma: nocover
93
+
94
+ def can_handle_request(self, origin: Origin) -> bool:
95
+ raise NotImplementedError() # pragma: nocover
96
+
97
+ def is_available(self) -> bool:
98
+ """
99
+ Return `True` if the connection is currently able to accept an
100
+ outgoing request.
101
+
102
+ An HTTP/1.1 connection will only be available if it is currently idle.
103
+
104
+ An HTTP/2 connection will be available so long as the stream ID space is
105
+ not yet exhausted, and the connection is not in an error state.
106
+
107
+ While the connection is being established we may not yet know if it is going
108
+ to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
109
+ treated as being available, but might ultimately raise `NewConnectionRequired`
110
+ required exceptions if multiple requests are attempted over a connection
111
+ that ends up being established as HTTP/1.1.
112
+ """
113
+ raise NotImplementedError() # pragma: nocover
114
+
115
+ def has_expired(self) -> bool:
116
+ """
117
+ Return `True` if the connection is in a state where it should be closed.
118
+
119
+ This either means that the connection is idle and it has passed the
120
+ expiry time on its keep-alive, or that server has sent an EOF.
121
+ """
122
+ raise NotImplementedError() # pragma: nocover
123
+
124
+ def is_idle(self) -> bool:
125
+ """
126
+ Return `True` if the connection is currently idle.
127
+ """
128
+ raise NotImplementedError() # pragma: nocover
129
+
130
+ def is_closed(self) -> bool:
131
+ """
132
+ Return `True` if the connection has been closed.
133
+
134
+ Used when a response is closed to determine if the connection may be
135
+ returned to the connection pool or not.
136
+ """
137
+ raise NotImplementedError() # pragma: nocover
venv/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import ssl
5
+
6
+ import socksio
7
+
8
+ from .._backends.sync import SyncBackend
9
+ from .._backends.base import NetworkBackend, NetworkStream
10
+ from .._exceptions import ConnectionNotAvailable, ProxyError
11
+ from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
12
+ from .._ssl import default_ssl_context
13
+ from .._synchronization import Lock
14
+ from .._trace import Trace
15
+ from .connection_pool import ConnectionPool
16
+ from .http11 import HTTP11Connection
17
+ from .interfaces import ConnectionInterface
18
+
19
+ logger = logging.getLogger("httpcore.socks")
20
+
21
+
22
+ AUTH_METHODS = {
23
+ b"\x00": "NO AUTHENTICATION REQUIRED",
24
+ b"\x01": "GSSAPI",
25
+ b"\x02": "USERNAME/PASSWORD",
26
+ b"\xff": "NO ACCEPTABLE METHODS",
27
+ }
28
+
29
+ REPLY_CODES = {
30
+ b"\x00": "Succeeded",
31
+ b"\x01": "General SOCKS server failure",
32
+ b"\x02": "Connection not allowed by ruleset",
33
+ b"\x03": "Network unreachable",
34
+ b"\x04": "Host unreachable",
35
+ b"\x05": "Connection refused",
36
+ b"\x06": "TTL expired",
37
+ b"\x07": "Command not supported",
38
+ b"\x08": "Address type not supported",
39
+ }
40
+
41
+
42
+ def _init_socks5_connection(
43
+ stream: NetworkStream,
44
+ *,
45
+ host: bytes,
46
+ port: int,
47
+ auth: tuple[bytes, bytes] | None = None,
48
+ ) -> None:
49
+ conn = socksio.socks5.SOCKS5Connection()
50
+
51
+ # Auth method request
52
+ auth_method = (
53
+ socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED
54
+ if auth is None
55
+ else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD
56
+ )
57
+ conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method]))
58
+ outgoing_bytes = conn.data_to_send()
59
+ stream.write(outgoing_bytes)
60
+
61
+ # Auth method response
62
+ incoming_bytes = stream.read(max_bytes=4096)
63
+ response = conn.receive_data(incoming_bytes)
64
+ assert isinstance(response, socksio.socks5.SOCKS5AuthReply)
65
+ if response.method != auth_method:
66
+ requested = AUTH_METHODS.get(auth_method, "UNKNOWN")
67
+ responded = AUTH_METHODS.get(response.method, "UNKNOWN")
68
+ raise ProxyError(
69
+ f"Requested {requested} from proxy server, but got {responded}."
70
+ )
71
+
72
+ if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD:
73
+ # Username/password request
74
+ assert auth is not None
75
+ username, password = auth
76
+ conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password))
77
+ outgoing_bytes = conn.data_to_send()
78
+ stream.write(outgoing_bytes)
79
+
80
+ # Username/password response
81
+ incoming_bytes = stream.read(max_bytes=4096)
82
+ response = conn.receive_data(incoming_bytes)
83
+ assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply)
84
+ if not response.success:
85
+ raise ProxyError("Invalid username/password")
86
+
87
+ # Connect request
88
+ conn.send(
89
+ socksio.socks5.SOCKS5CommandRequest.from_address(
90
+ socksio.socks5.SOCKS5Command.CONNECT, (host, port)
91
+ )
92
+ )
93
+ outgoing_bytes = conn.data_to_send()
94
+ stream.write(outgoing_bytes)
95
+
96
+ # Connect response
97
+ incoming_bytes = stream.read(max_bytes=4096)
98
+ response = conn.receive_data(incoming_bytes)
99
+ assert isinstance(response, socksio.socks5.SOCKS5Reply)
100
+ if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED:
101
+ reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN")
102
+ raise ProxyError(f"Proxy Server could not connect: {reply_code}.")
103
+
104
+
105
+ class SOCKSProxy(ConnectionPool): # pragma: nocover
106
+ """
107
+ A connection pool that sends requests via an HTTP proxy.
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ proxy_url: URL | bytes | str,
113
+ proxy_auth: tuple[bytes | str, bytes | str] | None = None,
114
+ ssl_context: ssl.SSLContext | None = None,
115
+ max_connections: int | None = 10,
116
+ max_keepalive_connections: int | None = None,
117
+ keepalive_expiry: float | None = None,
118
+ http1: bool = True,
119
+ http2: bool = False,
120
+ retries: int = 0,
121
+ network_backend: NetworkBackend | None = None,
122
+ ) -> None:
123
+ """
124
+ A connection pool for making HTTP requests.
125
+
126
+ Parameters:
127
+ proxy_url: The URL to use when connecting to the proxy server.
128
+ For example `"http://127.0.0.1:8080/"`.
129
+ ssl_context: An SSL context to use for verifying connections.
130
+ If not specified, the default `httpcore.default_ssl_context()`
131
+ will be used.
132
+ max_connections: The maximum number of concurrent HTTP connections that
133
+ the pool should allow. Any attempt to send a request on a pool that
134
+ would exceed this amount will block until a connection is available.
135
+ max_keepalive_connections: The maximum number of idle HTTP connections
136
+ that will be maintained in the pool.
137
+ keepalive_expiry: The duration in seconds that an idle HTTP connection
138
+ may be maintained for before being expired from the pool.
139
+ http1: A boolean indicating if HTTP/1.1 requests should be supported
140
+ by the connection pool. Defaults to True.
141
+ http2: A boolean indicating if HTTP/2 requests should be supported by
142
+ the connection pool. Defaults to False.
143
+ retries: The maximum number of retries when trying to establish
144
+ a connection.
145
+ local_address: Local address to connect from. Can also be used to
146
+ connect using a particular address family. Using
147
+ `local_address="0.0.0.0"` will connect using an `AF_INET` address
148
+ (IPv4), while using `local_address="::"` will connect using an
149
+ `AF_INET6` address (IPv6).
150
+ uds: Path to a Unix Domain Socket to use instead of TCP sockets.
151
+ network_backend: A backend instance to use for handling network I/O.
152
+ """
153
+ super().__init__(
154
+ ssl_context=ssl_context,
155
+ max_connections=max_connections,
156
+ max_keepalive_connections=max_keepalive_connections,
157
+ keepalive_expiry=keepalive_expiry,
158
+ http1=http1,
159
+ http2=http2,
160
+ network_backend=network_backend,
161
+ retries=retries,
162
+ )
163
+ self._ssl_context = ssl_context
164
+ self._proxy_url = enforce_url(proxy_url, name="proxy_url")
165
+ if proxy_auth is not None:
166
+ username, password = proxy_auth
167
+ username_bytes = enforce_bytes(username, name="proxy_auth")
168
+ password_bytes = enforce_bytes(password, name="proxy_auth")
169
+ self._proxy_auth: tuple[bytes, bytes] | None = (
170
+ username_bytes,
171
+ password_bytes,
172
+ )
173
+ else:
174
+ self._proxy_auth = None
175
+
176
+ def create_connection(self, origin: Origin) -> ConnectionInterface:
177
+ return Socks5Connection(
178
+ proxy_origin=self._proxy_url.origin,
179
+ remote_origin=origin,
180
+ proxy_auth=self._proxy_auth,
181
+ ssl_context=self._ssl_context,
182
+ keepalive_expiry=self._keepalive_expiry,
183
+ http1=self._http1,
184
+ http2=self._http2,
185
+ network_backend=self._network_backend,
186
+ )
187
+
188
+
189
+ class Socks5Connection(ConnectionInterface):
190
+ def __init__(
191
+ self,
192
+ proxy_origin: Origin,
193
+ remote_origin: Origin,
194
+ proxy_auth: tuple[bytes, bytes] | None = None,
195
+ ssl_context: ssl.SSLContext | None = None,
196
+ keepalive_expiry: float | None = None,
197
+ http1: bool = True,
198
+ http2: bool = False,
199
+ network_backend: NetworkBackend | None = None,
200
+ ) -> None:
201
+ self._proxy_origin = proxy_origin
202
+ self._remote_origin = remote_origin
203
+ self._proxy_auth = proxy_auth
204
+ self._ssl_context = ssl_context
205
+ self._keepalive_expiry = keepalive_expiry
206
+ self._http1 = http1
207
+ self._http2 = http2
208
+
209
+ self._network_backend: NetworkBackend = (
210
+ SyncBackend() if network_backend is None else network_backend
211
+ )
212
+ self._connect_lock = Lock()
213
+ self._connection: ConnectionInterface | None = None
214
+ self._connect_failed = False
215
+
216
+ def handle_request(self, request: Request) -> Response:
217
+ timeouts = request.extensions.get("timeout", {})
218
+ sni_hostname = request.extensions.get("sni_hostname", None)
219
+ timeout = timeouts.get("connect", None)
220
+
221
+ with self._connect_lock:
222
+ if self._connection is None:
223
+ try:
224
+ # Connect to the proxy
225
+ kwargs = {
226
+ "host": self._proxy_origin.host.decode("ascii"),
227
+ "port": self._proxy_origin.port,
228
+ "timeout": timeout,
229
+ }
230
+ with Trace("connect_tcp", logger, request, kwargs) as trace:
231
+ stream = self._network_backend.connect_tcp(**kwargs)
232
+ trace.return_value = stream
233
+
234
+ # Connect to the remote host using socks5
235
+ kwargs = {
236
+ "stream": stream,
237
+ "host": self._remote_origin.host.decode("ascii"),
238
+ "port": self._remote_origin.port,
239
+ "auth": self._proxy_auth,
240
+ }
241
+ with Trace(
242
+ "setup_socks5_connection", logger, request, kwargs
243
+ ) as trace:
244
+ _init_socks5_connection(**kwargs)
245
+ trace.return_value = stream
246
+
247
+ # Upgrade the stream to SSL
248
+ if self._remote_origin.scheme == b"https":
249
+ ssl_context = (
250
+ default_ssl_context()
251
+ if self._ssl_context is None
252
+ else self._ssl_context
253
+ )
254
+ alpn_protocols = (
255
+ ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
256
+ )
257
+ ssl_context.set_alpn_protocols(alpn_protocols)
258
+
259
+ kwargs = {
260
+ "ssl_context": ssl_context,
261
+ "server_hostname": sni_hostname
262
+ or self._remote_origin.host.decode("ascii"),
263
+ "timeout": timeout,
264
+ }
265
+ with Trace("start_tls", logger, request, kwargs) as trace:
266
+ stream = stream.start_tls(**kwargs)
267
+ trace.return_value = stream
268
+
269
+ # Determine if we should be using HTTP/1.1 or HTTP/2
270
+ ssl_object = stream.get_extra_info("ssl_object")
271
+ http2_negotiated = (
272
+ ssl_object is not None
273
+ and ssl_object.selected_alpn_protocol() == "h2"
274
+ )
275
+
276
+ # Create the HTTP/1.1 or HTTP/2 connection
277
+ if http2_negotiated or (
278
+ self._http2 and not self._http1
279
+ ): # pragma: nocover
280
+ from .http2 import HTTP2Connection
281
+
282
+ self._connection = HTTP2Connection(
283
+ origin=self._remote_origin,
284
+ stream=stream,
285
+ keepalive_expiry=self._keepalive_expiry,
286
+ )
287
+ else:
288
+ self._connection = HTTP11Connection(
289
+ origin=self._remote_origin,
290
+ stream=stream,
291
+ keepalive_expiry=self._keepalive_expiry,
292
+ )
293
+ except Exception as exc:
294
+ self._connect_failed = True
295
+ raise exc
296
+ elif not self._connection.is_available(): # pragma: nocover
297
+ raise ConnectionNotAvailable()
298
+
299
+ return self._connection.handle_request(request)
300
+
301
+ def can_handle_request(self, origin: Origin) -> bool:
302
+ return origin == self._remote_origin
303
+
304
+ def close(self) -> None:
305
+ if self._connection is not None:
306
+ self._connection.close()
307
+
308
+ def is_available(self) -> bool:
309
+ if self._connection is None: # pragma: nocover
310
+ # If HTTP/2 support is enabled, and the resulting connection could
311
+ # end up as HTTP/2 then we should indicate the connection as being
312
+ # available to service multiple requests.
313
+ return (
314
+ self._http2
315
+ and (self._remote_origin.scheme == b"https" or not self._http1)
316
+ and not self._connect_failed
317
+ )
318
+ return self._connection.is_available()
319
+
320
+ def has_expired(self) -> bool:
321
+ if self._connection is None: # pragma: nocover
322
+ return self._connect_failed
323
+ return self._connection.has_expired()
324
+
325
+ def is_idle(self) -> bool:
326
+ if self._connection is None: # pragma: nocover
327
+ return self._connect_failed
328
+ return self._connection.is_idle()
329
+
330
+ def is_closed(self) -> bool:
331
+ if self._connection is None: # pragma: nocover
332
+ return self._connect_failed
333
+ return self._connection.is_closed()
334
+
335
+ def info(self) -> str:
336
+ if self._connection is None: # pragma: nocover
337
+ return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
338
+ return self._connection.info()
339
+
340
+ def __repr__(self) -> str:
341
+ return f"<{self.__class__.__name__} [{self.info()}]>"
venv/lib/python3.12/site-packages/httpcore/_synchronization.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ import types
5
+
6
+ from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions
7
+
8
+ # Our async synchronization primatives use either 'anyio' or 'trio' depending
9
+ # on if they're running under asyncio or trio.
10
+
11
+ try:
12
+ import trio
13
+ except (ImportError, NotImplementedError): # pragma: nocover
14
+ trio = None # type: ignore
15
+
16
+ try:
17
+ import anyio
18
+ except ImportError: # pragma: nocover
19
+ anyio = None # type: ignore
20
+
21
+
22
+ def current_async_library() -> str:
23
+ # Determine if we're running under trio or asyncio.
24
+ # See https://sniffio.readthedocs.io/en/latest/
25
+ try:
26
+ import sniffio
27
+ except ImportError: # pragma: nocover
28
+ environment = "asyncio"
29
+ else:
30
+ environment = sniffio.current_async_library()
31
+
32
+ if environment not in ("asyncio", "trio"): # pragma: nocover
33
+ raise RuntimeError("Running under an unsupported async environment.")
34
+
35
+ if environment == "asyncio" and anyio is None: # pragma: nocover
36
+ raise RuntimeError(
37
+ "Running with asyncio requires installation of 'httpcore[asyncio]'."
38
+ )
39
+
40
+ if environment == "trio" and trio is None: # pragma: nocover
41
+ raise RuntimeError(
42
+ "Running with trio requires installation of 'httpcore[trio]'."
43
+ )
44
+
45
+ return environment
46
+
47
+
48
+ class AsyncLock:
49
+ """
50
+ This is a standard lock.
51
+
52
+ In the sync case `Lock` provides thread locking.
53
+ In the async case `AsyncLock` provides async locking.
54
+ """
55
+
56
+ def __init__(self) -> None:
57
+ self._backend = ""
58
+
59
+ def setup(self) -> None:
60
+ """
61
+ Detect if we're running under 'asyncio' or 'trio' and create
62
+ a lock with the correct implementation.
63
+ """
64
+ self._backend = current_async_library()
65
+ if self._backend == "trio":
66
+ self._trio_lock = trio.Lock()
67
+ elif self._backend == "asyncio":
68
+ self._anyio_lock = anyio.Lock()
69
+
70
+ async def __aenter__(self) -> AsyncLock:
71
+ if not self._backend:
72
+ self.setup()
73
+
74
+ if self._backend == "trio":
75
+ await self._trio_lock.acquire()
76
+ elif self._backend == "asyncio":
77
+ await self._anyio_lock.acquire()
78
+
79
+ return self
80
+
81
+ async def __aexit__(
82
+ self,
83
+ exc_type: type[BaseException] | None = None,
84
+ exc_value: BaseException | None = None,
85
+ traceback: types.TracebackType | None = None,
86
+ ) -> None:
87
+ if self._backend == "trio":
88
+ self._trio_lock.release()
89
+ elif self._backend == "asyncio":
90
+ self._anyio_lock.release()
91
+
92
+
93
+ class AsyncThreadLock:
94
+ """
95
+ This is a threading-only lock for no-I/O contexts.
96
+
97
+ In the sync case `ThreadLock` provides thread locking.
98
+ In the async case `AsyncThreadLock` is a no-op.
99
+ """
100
+
101
+ def __enter__(self) -> AsyncThreadLock:
102
+ return self
103
+
104
+ def __exit__(
105
+ self,
106
+ exc_type: type[BaseException] | None = None,
107
+ exc_value: BaseException | None = None,
108
+ traceback: types.TracebackType | None = None,
109
+ ) -> None:
110
+ pass
111
+
112
+
113
+ class AsyncEvent:
114
+ def __init__(self) -> None:
115
+ self._backend = ""
116
+
117
+ def setup(self) -> None:
118
+ """
119
+ Detect if we're running under 'asyncio' or 'trio' and create
120
+ a lock with the correct implementation.
121
+ """
122
+ self._backend = current_async_library()
123
+ if self._backend == "trio":
124
+ self._trio_event = trio.Event()
125
+ elif self._backend == "asyncio":
126
+ self._anyio_event = anyio.Event()
127
+
128
+ def set(self) -> None:
129
+ if not self._backend:
130
+ self.setup()
131
+
132
+ if self._backend == "trio":
133
+ self._trio_event.set()
134
+ elif self._backend == "asyncio":
135
+ self._anyio_event.set()
136
+
137
+ async def wait(self, timeout: float | None = None) -> None:
138
+ if not self._backend:
139
+ self.setup()
140
+
141
+ if self._backend == "trio":
142
+ trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout}
143
+ timeout_or_inf = float("inf") if timeout is None else timeout
144
+ with map_exceptions(trio_exc_map):
145
+ with trio.fail_after(timeout_or_inf):
146
+ await self._trio_event.wait()
147
+ elif self._backend == "asyncio":
148
+ anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout}
149
+ with map_exceptions(anyio_exc_map):
150
+ with anyio.fail_after(timeout):
151
+ await self._anyio_event.wait()
152
+
153
+
154
+ class AsyncSemaphore:
155
+ def __init__(self, bound: int) -> None:
156
+ self._bound = bound
157
+ self._backend = ""
158
+
159
+ def setup(self) -> None:
160
+ """
161
+ Detect if we're running under 'asyncio' or 'trio' and create
162
+ a semaphore with the correct implementation.
163
+ """
164
+ self._backend = current_async_library()
165
+ if self._backend == "trio":
166
+ self._trio_semaphore = trio.Semaphore(
167
+ initial_value=self._bound, max_value=self._bound
168
+ )
169
+ elif self._backend == "asyncio":
170
+ self._anyio_semaphore = anyio.Semaphore(
171
+ initial_value=self._bound, max_value=self._bound
172
+ )
173
+
174
+ async def acquire(self) -> None:
175
+ if not self._backend:
176
+ self.setup()
177
+
178
+ if self._backend == "trio":
179
+ await self._trio_semaphore.acquire()
180
+ elif self._backend == "asyncio":
181
+ await self._anyio_semaphore.acquire()
182
+
183
+ async def release(self) -> None:
184
+ if self._backend == "trio":
185
+ self._trio_semaphore.release()
186
+ elif self._backend == "asyncio":
187
+ self._anyio_semaphore.release()
188
+
189
+
190
+ class AsyncShieldCancellation:
191
+ # For certain portions of our codebase where we're dealing with
192
+ # closing connections during exception handling we want to shield
193
+ # the operation from being cancelled.
194
+ #
195
+ # with AsyncShieldCancellation():
196
+ # ... # clean-up operations, shielded from cancellation.
197
+
198
+ def __init__(self) -> None:
199
+ """
200
+ Detect if we're running under 'asyncio' or 'trio' and create
201
+ a shielded scope with the correct implementation.
202
+ """
203
+ self._backend = current_async_library()
204
+
205
+ if self._backend == "trio":
206
+ self._trio_shield = trio.CancelScope(shield=True)
207
+ elif self._backend == "asyncio":
208
+ self._anyio_shield = anyio.CancelScope(shield=True)
209
+
210
+ def __enter__(self) -> AsyncShieldCancellation:
211
+ if self._backend == "trio":
212
+ self._trio_shield.__enter__()
213
+ elif self._backend == "asyncio":
214
+ self._anyio_shield.__enter__()
215
+ return self
216
+
217
+ def __exit__(
218
+ self,
219
+ exc_type: type[BaseException] | None = None,
220
+ exc_value: BaseException | None = None,
221
+ traceback: types.TracebackType | None = None,
222
+ ) -> None:
223
+ if self._backend == "trio":
224
+ self._trio_shield.__exit__(exc_type, exc_value, traceback)
225
+ elif self._backend == "asyncio":
226
+ self._anyio_shield.__exit__(exc_type, exc_value, traceback)
227
+
228
+
229
+ # Our thread-based synchronization primitives...
230
+
231
+
232
+ class Lock:
233
+ """
234
+ This is a standard lock.
235
+
236
+ In the sync case `Lock` provides thread locking.
237
+ In the async case `AsyncLock` provides async locking.
238
+ """
239
+
240
+ def __init__(self) -> None:
241
+ self._lock = threading.Lock()
242
+
243
+ def __enter__(self) -> Lock:
244
+ self._lock.acquire()
245
+ return self
246
+
247
+ def __exit__(
248
+ self,
249
+ exc_type: type[BaseException] | None = None,
250
+ exc_value: BaseException | None = None,
251
+ traceback: types.TracebackType | None = None,
252
+ ) -> None:
253
+ self._lock.release()
254
+
255
+
256
+ class ThreadLock:
257
+ """
258
+ This is a threading-only lock for no-I/O contexts.
259
+
260
+ In the sync case `ThreadLock` provides thread locking.
261
+ In the async case `AsyncThreadLock` is a no-op.
262
+ """
263
+
264
+ def __init__(self) -> None:
265
+ self._lock = threading.Lock()
266
+
267
+ def __enter__(self) -> ThreadLock:
268
+ self._lock.acquire()
269
+ return self
270
+
271
+ def __exit__(
272
+ self,
273
+ exc_type: type[BaseException] | None = None,
274
+ exc_value: BaseException | None = None,
275
+ traceback: types.TracebackType | None = None,
276
+ ) -> None:
277
+ self._lock.release()
278
+
279
+
280
+ class Event:
281
+ def __init__(self) -> None:
282
+ self._event = threading.Event()
283
+
284
+ def set(self) -> None:
285
+ self._event.set()
286
+
287
+ def wait(self, timeout: float | None = None) -> None:
288
+ if timeout == float("inf"): # pragma: no cover
289
+ timeout = None
290
+ if not self._event.wait(timeout=timeout):
291
+ raise PoolTimeout() # pragma: nocover
292
+
293
+
294
+ class Semaphore:
295
+ def __init__(self, bound: int) -> None:
296
+ self._semaphore = threading.Semaphore(value=bound)
297
+
298
+ def acquire(self) -> None:
299
+ self._semaphore.acquire()
300
+
301
+ def release(self) -> None:
302
+ self._semaphore.release()
303
+
304
+
305
+ class ShieldCancellation:
306
+ # Thread-synchronous codebases don't support cancellation semantics.
307
+ # We have this class because we need to mirror the async and sync
308
+ # cases within our package, but it's just a no-op.
309
+ def __enter__(self) -> ShieldCancellation:
310
+ return self
311
+
312
+ def __exit__(
313
+ self,
314
+ exc_type: type[BaseException] | None = None,
315
+ exc_value: BaseException | None = None,
316
+ traceback: types.TracebackType | None = None,
317
+ ) -> None:
318
+ pass
venv/lib/python3.12/site-packages/httpcore/_trace.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import logging
5
+ import types
6
+ import typing
7
+
8
+ from ._models import Request
9
+
10
+
11
+ class Trace:
12
+ def __init__(
13
+ self,
14
+ name: str,
15
+ logger: logging.Logger,
16
+ request: Request | None = None,
17
+ kwargs: dict[str, typing.Any] | None = None,
18
+ ) -> None:
19
+ self.name = name
20
+ self.logger = logger
21
+ self.trace_extension = (
22
+ None if request is None else request.extensions.get("trace")
23
+ )
24
+ self.debug = self.logger.isEnabledFor(logging.DEBUG)
25
+ self.kwargs = kwargs or {}
26
+ self.return_value: typing.Any = None
27
+ self.should_trace = self.debug or self.trace_extension is not None
28
+ self.prefix = self.logger.name.split(".")[-1]
29
+
30
+ def trace(self, name: str, info: dict[str, typing.Any]) -> None:
31
+ if self.trace_extension is not None:
32
+ prefix_and_name = f"{self.prefix}.{name}"
33
+ ret = self.trace_extension(prefix_and_name, info)
34
+ if inspect.iscoroutine(ret): # pragma: no cover
35
+ raise TypeError(
36
+ "If you are using a synchronous interface, "
37
+ "the callback of the `trace` extension should "
38
+ "be a normal function instead of an asynchronous function."
39
+ )
40
+
41
+ if self.debug:
42
+ if not info or "return_value" in info and info["return_value"] is None:
43
+ message = name
44
+ else:
45
+ args = " ".join([f"{key}={value!r}" for key, value in info.items()])
46
+ message = f"{name} {args}"
47
+ self.logger.debug(message)
48
+
49
+ def __enter__(self) -> Trace:
50
+ if self.should_trace:
51
+ info = self.kwargs
52
+ self.trace(f"{self.name}.started", info)
53
+ return self
54
+
55
+ def __exit__(
56
+ self,
57
+ exc_type: type[BaseException] | None = None,
58
+ exc_value: BaseException | None = None,
59
+ traceback: types.TracebackType | None = None,
60
+ ) -> None:
61
+ if self.should_trace:
62
+ if exc_value is None:
63
+ info = {"return_value": self.return_value}
64
+ self.trace(f"{self.name}.complete", info)
65
+ else:
66
+ info = {"exception": exc_value}
67
+ self.trace(f"{self.name}.failed", info)
68
+
69
+ async def atrace(self, name: str, info: dict[str, typing.Any]) -> None:
70
+ if self.trace_extension is not None:
71
+ prefix_and_name = f"{self.prefix}.{name}"
72
+ coro = self.trace_extension(prefix_and_name, info)
73
+ if not inspect.iscoroutine(coro): # pragma: no cover
74
+ raise TypeError(
75
+ "If you're using an asynchronous interface, "
76
+ "the callback of the `trace` extension should "
77
+ "be an asynchronous function rather than a normal function."
78
+ )
79
+ await coro
80
+
81
+ if self.debug:
82
+ if not info or "return_value" in info and info["return_value"] is None:
83
+ message = name
84
+ else:
85
+ args = " ".join([f"{key}={value!r}" for key, value in info.items()])
86
+ message = f"{name} {args}"
87
+ self.logger.debug(message)
88
+
89
+ async def __aenter__(self) -> Trace:
90
+ if self.should_trace:
91
+ info = self.kwargs
92
+ await self.atrace(f"{self.name}.started", info)
93
+ return self
94
+
95
+ async def __aexit__(
96
+ self,
97
+ exc_type: type[BaseException] | None = None,
98
+ exc_value: BaseException | None = None,
99
+ traceback: types.TracebackType | None = None,
100
+ ) -> None:
101
+ if self.should_trace:
102
+ if exc_value is None:
103
+ info = {"return_value": self.return_value}
104
+ await self.atrace(f"{self.name}.complete", info)
105
+ else:
106
+ info = {"exception": exc_value}
107
+ await self.atrace(f"{self.name}.failed", info)
venv/lib/python3.12/site-packages/httpcore/_utils.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import select
4
+ import socket
5
+ import sys
6
+
7
+
8
+ def is_socket_readable(sock: socket.socket | None) -> bool:
9
+ """
10
+ Return whether a socket, as identifed by its file descriptor, is readable.
11
+ "A socket is readable" means that the read buffer isn't empty, i.e. that calling
12
+ .recv() on it would immediately return some data.
13
+ """
14
+ # NOTE: we want check for readability without actually attempting to read, because
15
+ # we don't want to block forever if it's not readable.
16
+
17
+ # In the case that the socket no longer exists, or cannot return a file
18
+ # descriptor, we treat it as being readable, as if it the next read operation
19
+ # on it is ready to return the terminating `b""`.
20
+ sock_fd = None if sock is None else sock.fileno()
21
+ if sock_fd is None or sock_fd < 0: # pragma: nocover
22
+ return True
23
+
24
+ # The implementation below was stolen from:
25
+ # https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471-L478
26
+ # See also: https://github.com/encode/httpcore/pull/193#issuecomment-703129316
27
+
28
+ # Use select.select on Windows, and when poll is unavailable and select.poll
29
+ # everywhere else. (E.g. When eventlet is in use. See #327)
30
+ if (
31
+ sys.platform == "win32" or getattr(select, "poll", None) is None
32
+ ): # pragma: nocover
33
+ rready, _, _ = select.select([sock_fd], [], [], 0)
34
+ return bool(rready)
35
+ p = select.poll()
36
+ p.register(sock_fd, select.POLLIN)
37
+ return bool(p.poll(0))
venv/lib/python3.12/site-packages/httpcore/py.typed ADDED
File without changes
venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.3
2
+ Name: httpx
3
+ Version: 0.28.1
4
+ Summary: The next generation HTTP client.
5
+ Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md
6
+ Project-URL: Documentation, https://www.python-httpx.org
7
+ Project-URL: Homepage, https://github.com/encode/httpx
8
+ Project-URL: Source, https://github.com/encode/httpx
9
+ Author-email: Tom Christie <tom@tomchristie.com>
10
+ License: BSD-3-Clause
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Framework :: Trio
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: BSD License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Requires-Python: >=3.8
27
+ Requires-Dist: anyio
28
+ Requires-Dist: certifi
29
+ Requires-Dist: httpcore==1.*
30
+ Requires-Dist: idna
31
+ Provides-Extra: brotli
32
+ Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'
33
+ Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'
34
+ Provides-Extra: cli
35
+ Requires-Dist: click==8.*; extra == 'cli'
36
+ Requires-Dist: pygments==2.*; extra == 'cli'
37
+ Requires-Dist: rich<14,>=10; extra == 'cli'
38
+ Provides-Extra: http2
39
+ Requires-Dist: h2<5,>=3; extra == 'http2'
40
+ Provides-Extra: socks
41
+ Requires-Dist: socksio==1.*; extra == 'socks'
42
+ Provides-Extra: zstd
43
+ Requires-Dist: zstandard>=0.18.0; extra == 'zstd'
44
+ Description-Content-Type: text/markdown
45
+
46
+ <p align="center">
47
+ <a href="https://www.python-httpx.org/"><img width="350" height="208" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/butterfly.png" alt='HTTPX'></a>
48
+ </p>
49
+
50
+ <p align="center"><strong>HTTPX</strong> <em>- A next-generation HTTP client for Python.</em></p>
51
+
52
+ <p align="center">
53
+ <a href="https://github.com/encode/httpx/actions">
54
+ <img src="https://github.com/encode/httpx/workflows/Test%20Suite/badge.svg" alt="Test Suite">
55
+ </a>
56
+ <a href="https://pypi.org/project/httpx/">
57
+ <img src="https://badge.fury.io/py/httpx.svg" alt="Package version">
58
+ </a>
59
+ </p>
60
+
61
+ HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.
62
+
63
+ ---
64
+
65
+ Install HTTPX using pip:
66
+
67
+ ```shell
68
+ $ pip install httpx
69
+ ```
70
+
71
+ Now, let's get started:
72
+
73
+ ```pycon
74
+ >>> import httpx
75
+ >>> r = httpx.get('https://www.example.org/')
76
+ >>> r
77
+ <Response [200 OK]>
78
+ >>> r.status_code
79
+ 200
80
+ >>> r.headers['content-type']
81
+ 'text/html; charset=UTF-8'
82
+ >>> r.text
83
+ '<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
84
+ ```
85
+
86
+ Or, using the command-line client.
87
+
88
+ ```shell
89
+ $ pip install 'httpx[cli]' # The command line client is an optional dependency.
90
+ ```
91
+
92
+ Which now allows us to use HTTPX directly from the command-line...
93
+
94
+ <p align="center">
95
+ <img width="700" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/httpx-help.png" alt='httpx --help'>
96
+ </p>
97
+
98
+ Sending a request...
99
+
100
+ <p align="center">
101
+ <img width="700" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/httpx-request.png" alt='httpx http://httpbin.org/json'>
102
+ </p>
103
+
104
+ ## Features
105
+
106
+ HTTPX builds on the well-established usability of `requests`, and gives you:
107
+
108
+ * A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/).
109
+ * An integrated command-line client.
110
+ * HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/).
111
+ * Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/).
112
+ * Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/transports/#wsgi-transport) or [ASGI applications](https://www.python-httpx.org/advanced/transports/#asgi-transport).
113
+ * Strict timeouts everywhere.
114
+ * Fully type annotated.
115
+ * 100% test coverage.
116
+
117
+ Plus all the standard features of `requests`...
118
+
119
+ * International Domains and URLs
120
+ * Keep-Alive & Connection Pooling
121
+ * Sessions with Cookie Persistence
122
+ * Browser-style SSL Verification
123
+ * Basic/Digest Authentication
124
+ * Elegant Key/Value Cookies
125
+ * Automatic Decompression
126
+ * Automatic Content Decoding
127
+ * Unicode Response Bodies
128
+ * Multipart File Uploads
129
+ * HTTP(S) Proxy Support
130
+ * Connection Timeouts
131
+ * Streaming Downloads
132
+ * .netrc Support
133
+ * Chunked Requests
134
+
135
+ ## Installation
136
+
137
+ Install with pip:
138
+
139
+ ```shell
140
+ $ pip install httpx
141
+ ```
142
+
143
+ Or, to include the optional HTTP/2 support, use:
144
+
145
+ ```shell
146
+ $ pip install httpx[http2]
147
+ ```
148
+
149
+ HTTPX requires Python 3.8+.
150
+
151
+ ## Documentation
152
+
153
+ Project documentation is available at [https://www.python-httpx.org/](https://www.python-httpx.org/).
154
+
155
+ For a run-through of all the basics, head over to the [QuickStart](https://www.python-httpx.org/quickstart/).
156
+
157
+ For more advanced topics, see the [Advanced Usage](https://www.python-httpx.org/advanced/) section, the [async support](https://www.python-httpx.org/async/) section, or the [HTTP/2](https://www.python-httpx.org/http2/) section.
158
+
159
+ The [Developer Interface](https://www.python-httpx.org/api/) provides a comprehensive API reference.
160
+
161
+ To find out about tools that integrate with HTTPX, see [Third Party Packages](https://www.python-httpx.org/third_party_packages/).
162
+
163
+ ## Contribute
164
+
165
+ If you want to contribute with HTTPX check out the [Contributing Guide](https://www.python-httpx.org/contributing/) to learn how to start.
166
+
167
+ ## Dependencies
168
+
169
+ The HTTPX project relies on these excellent libraries:
170
+
171
+ * `httpcore` - The underlying transport implementation for `httpx`.
172
+ * `h11` - HTTP/1.1 support.
173
+ * `certifi` - SSL certificates.
174
+ * `idna` - Internationalized domain name support.
175
+ * `sniffio` - Async library autodetection.
176
+
177
+ As well as these optional installs:
178
+
179
+ * `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)*
180
+ * `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)*
181
+ * `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*
182
+ * `click` - Command line client support. *(Optional, with `httpx[cli]`)*
183
+ * `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)*
184
+ * `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
185
+
186
+ A huge amount of credit is due to `requests` for the API layout that
187
+ much of this work follows, as well as to `urllib3` for plenty of design
188
+ inspiration around the lower-level networking details.
189
+
190
+ ---
191
+
192
+ <p align="center"><i>HTTPX is <a href="https://github.com/encode/httpx/blob/master/LICENSE.md">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>&mdash; 🦋 &mdash;</p>
193
+
194
+ ## Release Information
195
+
196
+ ### Fixed
197
+
198
+ * Reintroduced supposedly-private `URLTypes` shortcut. (#2673)
199
+
200
+
201
+ ---
202
+
203
+ [Full changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ../../../bin/httpx,sha256=MnZC8HUjBL-ubtCy37H8jXXP7LM_acAi9t3VeBqtRgM,223
2
+ httpx-0.28.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
+ httpx-0.28.1.dist-info/METADATA,sha256=_rubD48-gNV8gZnDBPNcQzboWB0dGNeYPJJ2a4J5OyU,7052
4
+ httpx-0.28.1.dist-info/RECORD,,
5
+ httpx-0.28.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
6
+ httpx-0.28.1.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37
7
+ httpx-0.28.1.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508
8
+ httpx/__init__.py,sha256=CsaZe6yZj0rHg6322AWKWHGTMVr9txgEfD5P3_Rrz60,2171
9
+ httpx/__pycache__/__init__.cpython-312.pyc,,
10
+ httpx/__pycache__/__version__.cpython-312.pyc,,
11
+ httpx/__pycache__/_api.cpython-312.pyc,,
12
+ httpx/__pycache__/_auth.cpython-312.pyc,,
13
+ httpx/__pycache__/_client.cpython-312.pyc,,
14
+ httpx/__pycache__/_config.cpython-312.pyc,,
15
+ httpx/__pycache__/_content.cpython-312.pyc,,
16
+ httpx/__pycache__/_decoders.cpython-312.pyc,,
17
+ httpx/__pycache__/_exceptions.cpython-312.pyc,,
18
+ httpx/__pycache__/_main.cpython-312.pyc,,
19
+ httpx/__pycache__/_models.cpython-312.pyc,,
20
+ httpx/__pycache__/_multipart.cpython-312.pyc,,
21
+ httpx/__pycache__/_status_codes.cpython-312.pyc,,
22
+ httpx/__pycache__/_types.cpython-312.pyc,,
23
+ httpx/__pycache__/_urlparse.cpython-312.pyc,,
24
+ httpx/__pycache__/_urls.cpython-312.pyc,,
25
+ httpx/__pycache__/_utils.cpython-312.pyc,,
26
+ httpx/__version__.py,sha256=LoUyYeOXTieGzuP_64UL0wxdtxjuu_QbOvE7NOg-IqU,108
27
+ httpx/_api.py,sha256=r_Zgs4jIpcPJLqK5dbbSayqo_iVMKFaxZCd-oOHxLEs,11743
28
+ httpx/_auth.py,sha256=Yr3QwaUSK17rGYx-7j-FdicFIzz4Y9FFV-1F4-7RXX4,11891
29
+ httpx/_client.py,sha256=xD-UG67-WMkeltAAOeGGj-cZ2RRTAm19sWRxlFY7_40,65714
30
+ httpx/_config.py,sha256=pPp2U-wicfcKsF-KYRE1LYdt3e6ERGeIoXZ8Gjo3LWc,8547
31
+ httpx/_content.py,sha256=LGGzrJTR3OvN4Mb1GVVNLXkXJH-6oKlwAttO9p5w_yg,8161
32
+ httpx/_decoders.py,sha256=p0dX8I0NEHexs3UGp4SsZutiMhsXrrWl6-GnqVb0iKM,12041
33
+ httpx/_exceptions.py,sha256=bxW7fxzgVMAdNTbwT0Vnq04gJDW1_gI_GFiQPuMyjL0,8527
34
+ httpx/_main.py,sha256=Cg9GMabiTT_swaDfUgIRitSwxLRMSwUDOm7LdSGqlA4,15626
35
+ httpx/_models.py,sha256=4__Guyv1gLxuZChwim8kfQNiIOcJ9acreFOSurvZfms,44700
36
+ httpx/_multipart.py,sha256=KOHEZZl6oohg9mPaKyyu345qq1rJLg35TUG3YAzXB3Y,9843
37
+ httpx/_status_codes.py,sha256=DYn-2ufBgMeXy5s8x3_TB7wjAuAAMewTakPrm5rXEsc,5639
38
+ httpx/_transports/__init__.py,sha256=GbUoBSAOp7z-l-9j5YhMhR3DMIcn6FVLhj072O3Nnno,275
39
+ httpx/_transports/__pycache__/__init__.cpython-312.pyc,,
40
+ httpx/_transports/__pycache__/asgi.cpython-312.pyc,,
41
+ httpx/_transports/__pycache__/base.cpython-312.pyc,,
42
+ httpx/_transports/__pycache__/default.cpython-312.pyc,,
43
+ httpx/_transports/__pycache__/mock.cpython-312.pyc,,
44
+ httpx/_transports/__pycache__/wsgi.cpython-312.pyc,,
45
+ httpx/_transports/asgi.py,sha256=HRfiDYMPt4wQH2gFgHZg4c-i3sblo6bL5GTqcET-xz8,5501
46
+ httpx/_transports/base.py,sha256=kZS_VMbViYfF570pogUCJ1bulz-ybfL51Pqs9yktebU,2523
47
+ httpx/_transports/default.py,sha256=AzeaRUyVwCccTyyNJexDf0n1dFfzzydpdIQgvw7PLnk,13983
48
+ httpx/_transports/mock.py,sha256=PTo0d567RITXxGrki6kN7_67wwAxfwiMDcuXJiZCjEo,1232
49
+ httpx/_transports/wsgi.py,sha256=NcPX3Xap_EwCFZWO_OaSyQNuInCYx1QMNbO8GAei6jY,4825
50
+ httpx/_types.py,sha256=Jyh41GQq7AOev8IOWKDAg7zCbvHAfufmW5g_PiTtErY,2965
51
+ httpx/_urlparse.py,sha256=ZAmH47ONfkxrrj-PPYhGeiHjb6AjKCS-ANWIN4OL_KY,18546
52
+ httpx/_urls.py,sha256=dX99VR1DSOHpgo9Aq7PzYO4FKdxqKjwyNp8grf8dHN0,21550
53
+ httpx/_utils.py,sha256=_TVeqAKvxJkKHdz7dFeb4s0LZqQXgeFkXSgfiHBK_1o,8285
54
+ httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0