Publish GraphShieldMistral interactive explorer (part 2)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/INSTALLER +1 -0
- .venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/METADATA +625 -0
- .venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/RECORD +68 -0
- .venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/WHEEL +4 -0
- .venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md +27 -0
- .venv/lib/python3.14/site-packages/httpcore/_sync/interfaces.py +137 -0
- .venv/lib/python3.14/site-packages/httpcore/_sync/socks_proxy.py +341 -0
- .venv/lib/python3.14/site-packages/httpcore/_synchronization.py +318 -0
- .venv/lib/python3.14/site-packages/httpcore/_trace.py +107 -0
- .venv/lib/python3.14/site-packages/httpcore/_utils.py +37 -0
- .venv/lib/python3.14/site-packages/httpcore/py.typed +0 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/INSTALLER +1 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/METADATA +203 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/RECORD +54 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/WHEEL +4 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/entry_points.txt +2 -0
- .venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md +12 -0
- .venv/lib/python3.14/site-packages/httpx/__init__.py +105 -0
- .venv/lib/python3.14/site-packages/httpx/__version__.py +3 -0
- .venv/lib/python3.14/site-packages/httpx/_api.py +438 -0
- .venv/lib/python3.14/site-packages/httpx/_auth.py +348 -0
- .venv/lib/python3.14/site-packages/httpx/_client.py +2019 -0
- .venv/lib/python3.14/site-packages/httpx/_config.py +248 -0
- .venv/lib/python3.14/site-packages/httpx/_content.py +240 -0
- .venv/lib/python3.14/site-packages/httpx/_decoders.py +393 -0
- .venv/lib/python3.14/site-packages/httpx/_exceptions.py +379 -0
- .venv/lib/python3.14/site-packages/httpx/_main.py +506 -0
- .venv/lib/python3.14/site-packages/httpx/_models.py +1277 -0
- .venv/lib/python3.14/site-packages/httpx/_multipart.py +300 -0
- .venv/lib/python3.14/site-packages/httpx/_status_codes.py +162 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/__init__.py +15 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/asgi.py +187 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/base.py +86 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/default.py +406 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/mock.py +43 -0
- .venv/lib/python3.14/site-packages/httpx/_transports/wsgi.py +149 -0
- .venv/lib/python3.14/site-packages/httpx/_types.py +114 -0
- .venv/lib/python3.14/site-packages/httpx/_urlparse.py +527 -0
- .venv/lib/python3.14/site-packages/httpx/_urls.py +641 -0
- .venv/lib/python3.14/site-packages/httpx/_utils.py +242 -0
- .venv/lib/python3.14/site-packages/httpx/py.typed +0 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/INSTALLER +1 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/METADATA +373 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/RECORD +378 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/REQUESTED +0 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/WHEEL +5 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/entry_points.txt +7 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/licenses/LICENSE +201 -0
- .venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/top_level.txt +1 -0
- .venv/lib/python3.14/site-packages/huggingface_hub/__init__.py +1815 -0
.venv/lib/python3.14/site-packages/httpcore-1.0.9.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.14/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 |
+
[](https://github.com/encode/httpcore/actions)
|
| 42 |
+
[](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.14/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-314.pyc,,
|
| 8 |
+
httpcore/__pycache__/_api.cpython-314.pyc,,
|
| 9 |
+
httpcore/__pycache__/_exceptions.cpython-314.pyc,,
|
| 10 |
+
httpcore/__pycache__/_models.cpython-314.pyc,,
|
| 11 |
+
httpcore/__pycache__/_ssl.cpython-314.pyc,,
|
| 12 |
+
httpcore/__pycache__/_synchronization.cpython-314.pyc,,
|
| 13 |
+
httpcore/__pycache__/_trace.cpython-314.pyc,,
|
| 14 |
+
httpcore/__pycache__/_utils.cpython-314.pyc,,
|
| 15 |
+
httpcore/_api.py,sha256=unZmeDschBWCGCPCwkS3Wot9euK6bg_kKxLtGTxw214,3146
|
| 16 |
+
httpcore/_async/__init__.py,sha256=EWdl2v4thnAHzJpqjU4h2a8DUiGAvNiWrkii9pfhTf0,1221
|
| 17 |
+
httpcore/_async/__pycache__/__init__.cpython-314.pyc,,
|
| 18 |
+
httpcore/_async/__pycache__/connection.cpython-314.pyc,,
|
| 19 |
+
httpcore/_async/__pycache__/connection_pool.cpython-314.pyc,,
|
| 20 |
+
httpcore/_async/__pycache__/http11.cpython-314.pyc,,
|
| 21 |
+
httpcore/_async/__pycache__/http2.cpython-314.pyc,,
|
| 22 |
+
httpcore/_async/__pycache__/http_proxy.cpython-314.pyc,,
|
| 23 |
+
httpcore/_async/__pycache__/interfaces.cpython-314.pyc,,
|
| 24 |
+
httpcore/_async/__pycache__/socks_proxy.cpython-314.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-314.pyc,,
|
| 34 |
+
httpcore/_backends/__pycache__/anyio.cpython-314.pyc,,
|
| 35 |
+
httpcore/_backends/__pycache__/auto.cpython-314.pyc,,
|
| 36 |
+
httpcore/_backends/__pycache__/base.cpython-314.pyc,,
|
| 37 |
+
httpcore/_backends/__pycache__/mock.cpython-314.pyc,,
|
| 38 |
+
httpcore/_backends/__pycache__/sync.cpython-314.pyc,,
|
| 39 |
+
httpcore/_backends/__pycache__/trio.cpython-314.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-314.pyc,,
|
| 51 |
+
httpcore/_sync/__pycache__/connection.cpython-314.pyc,,
|
| 52 |
+
httpcore/_sync/__pycache__/connection_pool.cpython-314.pyc,,
|
| 53 |
+
httpcore/_sync/__pycache__/http11.cpython-314.pyc,,
|
| 54 |
+
httpcore/_sync/__pycache__/http2.cpython-314.pyc,,
|
| 55 |
+
httpcore/_sync/__pycache__/http_proxy.cpython-314.pyc,,
|
| 56 |
+
httpcore/_sync/__pycache__/interfaces.cpython-314.pyc,,
|
| 57 |
+
httpcore/_sync/__pycache__/socks_proxy.cpython-314.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.14/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.14/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.14/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.14/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.14/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.14/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.14/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.14/site-packages/httpcore/py.typed
ADDED
|
File without changes
|
.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.14/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/>— 🦋 —</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.14/site-packages/httpx-0.28.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
../../../bin/httpx,sha256=0i6yJyqOD-dNyJreSZBpg7NarW1DJ0uZAIHMk17DWqk,212
|
| 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-314.pyc,,
|
| 10 |
+
httpx/__pycache__/__version__.cpython-314.pyc,,
|
| 11 |
+
httpx/__pycache__/_api.cpython-314.pyc,,
|
| 12 |
+
httpx/__pycache__/_auth.cpython-314.pyc,,
|
| 13 |
+
httpx/__pycache__/_client.cpython-314.pyc,,
|
| 14 |
+
httpx/__pycache__/_config.cpython-314.pyc,,
|
| 15 |
+
httpx/__pycache__/_content.cpython-314.pyc,,
|
| 16 |
+
httpx/__pycache__/_decoders.cpython-314.pyc,,
|
| 17 |
+
httpx/__pycache__/_exceptions.cpython-314.pyc,,
|
| 18 |
+
httpx/__pycache__/_main.cpython-314.pyc,,
|
| 19 |
+
httpx/__pycache__/_models.cpython-314.pyc,,
|
| 20 |
+
httpx/__pycache__/_multipart.cpython-314.pyc,,
|
| 21 |
+
httpx/__pycache__/_status_codes.cpython-314.pyc,,
|
| 22 |
+
httpx/__pycache__/_types.cpython-314.pyc,,
|
| 23 |
+
httpx/__pycache__/_urlparse.cpython-314.pyc,,
|
| 24 |
+
httpx/__pycache__/_urls.cpython-314.pyc,,
|
| 25 |
+
httpx/__pycache__/_utils.cpython-314.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-314.pyc,,
|
| 40 |
+
httpx/_transports/__pycache__/asgi.cpython-314.pyc,,
|
| 41 |
+
httpx/_transports/__pycache__/base.cpython-314.pyc,,
|
| 42 |
+
httpx/_transports/__pycache__/default.cpython-314.pyc,,
|
| 43 |
+
httpx/_transports/__pycache__/mock.cpython-314.pyc,,
|
| 44 |
+
httpx/_transports/__pycache__/wsgi.cpython-314.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
|
.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.26.3
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
httpx = httpx:main
|
.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/).
|
| 2 |
+
All rights reserved.
|
| 3 |
+
|
| 4 |
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
| 5 |
+
|
| 6 |
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
| 7 |
+
|
| 8 |
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
| 9 |
+
|
| 10 |
+
* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
| 11 |
+
|
| 12 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
.venv/lib/python3.14/site-packages/httpx/__init__.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .__version__ import __description__, __title__, __version__
|
| 2 |
+
from ._api import *
|
| 3 |
+
from ._auth import *
|
| 4 |
+
from ._client import *
|
| 5 |
+
from ._config import *
|
| 6 |
+
from ._content import *
|
| 7 |
+
from ._exceptions import *
|
| 8 |
+
from ._models import *
|
| 9 |
+
from ._status_codes import *
|
| 10 |
+
from ._transports import *
|
| 11 |
+
from ._types import *
|
| 12 |
+
from ._urls import *
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from ._main import main
|
| 16 |
+
except ImportError: # pragma: no cover
|
| 17 |
+
|
| 18 |
+
def main() -> None: # type: ignore
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
print(
|
| 22 |
+
"The httpx command line client could not run because the required "
|
| 23 |
+
"dependencies were not installed.\nMake sure you've installed "
|
| 24 |
+
"everything with: pip install 'httpx[cli]'"
|
| 25 |
+
)
|
| 26 |
+
sys.exit(1)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
__all__ = [
|
| 30 |
+
"__description__",
|
| 31 |
+
"__title__",
|
| 32 |
+
"__version__",
|
| 33 |
+
"ASGITransport",
|
| 34 |
+
"AsyncBaseTransport",
|
| 35 |
+
"AsyncByteStream",
|
| 36 |
+
"AsyncClient",
|
| 37 |
+
"AsyncHTTPTransport",
|
| 38 |
+
"Auth",
|
| 39 |
+
"BaseTransport",
|
| 40 |
+
"BasicAuth",
|
| 41 |
+
"ByteStream",
|
| 42 |
+
"Client",
|
| 43 |
+
"CloseError",
|
| 44 |
+
"codes",
|
| 45 |
+
"ConnectError",
|
| 46 |
+
"ConnectTimeout",
|
| 47 |
+
"CookieConflict",
|
| 48 |
+
"Cookies",
|
| 49 |
+
"create_ssl_context",
|
| 50 |
+
"DecodingError",
|
| 51 |
+
"delete",
|
| 52 |
+
"DigestAuth",
|
| 53 |
+
"get",
|
| 54 |
+
"head",
|
| 55 |
+
"Headers",
|
| 56 |
+
"HTTPError",
|
| 57 |
+
"HTTPStatusError",
|
| 58 |
+
"HTTPTransport",
|
| 59 |
+
"InvalidURL",
|
| 60 |
+
"Limits",
|
| 61 |
+
"LocalProtocolError",
|
| 62 |
+
"main",
|
| 63 |
+
"MockTransport",
|
| 64 |
+
"NetRCAuth",
|
| 65 |
+
"NetworkError",
|
| 66 |
+
"options",
|
| 67 |
+
"patch",
|
| 68 |
+
"PoolTimeout",
|
| 69 |
+
"post",
|
| 70 |
+
"ProtocolError",
|
| 71 |
+
"Proxy",
|
| 72 |
+
"ProxyError",
|
| 73 |
+
"put",
|
| 74 |
+
"QueryParams",
|
| 75 |
+
"ReadError",
|
| 76 |
+
"ReadTimeout",
|
| 77 |
+
"RemoteProtocolError",
|
| 78 |
+
"request",
|
| 79 |
+
"Request",
|
| 80 |
+
"RequestError",
|
| 81 |
+
"RequestNotRead",
|
| 82 |
+
"Response",
|
| 83 |
+
"ResponseNotRead",
|
| 84 |
+
"stream",
|
| 85 |
+
"StreamClosed",
|
| 86 |
+
"StreamConsumed",
|
| 87 |
+
"StreamError",
|
| 88 |
+
"SyncByteStream",
|
| 89 |
+
"Timeout",
|
| 90 |
+
"TimeoutException",
|
| 91 |
+
"TooManyRedirects",
|
| 92 |
+
"TransportError",
|
| 93 |
+
"UnsupportedProtocol",
|
| 94 |
+
"URL",
|
| 95 |
+
"USE_CLIENT_DEFAULT",
|
| 96 |
+
"WriteError",
|
| 97 |
+
"WriteTimeout",
|
| 98 |
+
"WSGITransport",
|
| 99 |
+
]
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
__locals = locals()
|
| 103 |
+
for __name in __all__:
|
| 104 |
+
if not __name.startswith("__"):
|
| 105 |
+
setattr(__locals[__name], "__module__", "httpx") # noqa
|
.venv/lib/python3.14/site-packages/httpx/__version__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__title__ = "httpx"
|
| 2 |
+
__description__ = "A next generation HTTP client, for Python 3."
|
| 3 |
+
__version__ = "0.28.1"
|
.venv/lib/python3.14/site-packages/httpx/_api.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import typing
|
| 4 |
+
from contextlib import contextmanager
|
| 5 |
+
|
| 6 |
+
from ._client import Client
|
| 7 |
+
from ._config import DEFAULT_TIMEOUT_CONFIG
|
| 8 |
+
from ._models import Response
|
| 9 |
+
from ._types import (
|
| 10 |
+
AuthTypes,
|
| 11 |
+
CookieTypes,
|
| 12 |
+
HeaderTypes,
|
| 13 |
+
ProxyTypes,
|
| 14 |
+
QueryParamTypes,
|
| 15 |
+
RequestContent,
|
| 16 |
+
RequestData,
|
| 17 |
+
RequestFiles,
|
| 18 |
+
TimeoutTypes,
|
| 19 |
+
)
|
| 20 |
+
from ._urls import URL
|
| 21 |
+
|
| 22 |
+
if typing.TYPE_CHECKING:
|
| 23 |
+
import ssl # pragma: no cover
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
__all__ = [
|
| 27 |
+
"delete",
|
| 28 |
+
"get",
|
| 29 |
+
"head",
|
| 30 |
+
"options",
|
| 31 |
+
"patch",
|
| 32 |
+
"post",
|
| 33 |
+
"put",
|
| 34 |
+
"request",
|
| 35 |
+
"stream",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def request(
|
| 40 |
+
method: str,
|
| 41 |
+
url: URL | str,
|
| 42 |
+
*,
|
| 43 |
+
params: QueryParamTypes | None = None,
|
| 44 |
+
content: RequestContent | None = None,
|
| 45 |
+
data: RequestData | None = None,
|
| 46 |
+
files: RequestFiles | None = None,
|
| 47 |
+
json: typing.Any | None = None,
|
| 48 |
+
headers: HeaderTypes | None = None,
|
| 49 |
+
cookies: CookieTypes | None = None,
|
| 50 |
+
auth: AuthTypes | None = None,
|
| 51 |
+
proxy: ProxyTypes | None = None,
|
| 52 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 53 |
+
follow_redirects: bool = False,
|
| 54 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 55 |
+
trust_env: bool = True,
|
| 56 |
+
) -> Response:
|
| 57 |
+
"""
|
| 58 |
+
Sends an HTTP request.
|
| 59 |
+
|
| 60 |
+
**Parameters:**
|
| 61 |
+
|
| 62 |
+
* **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`,
|
| 63 |
+
`HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`.
|
| 64 |
+
* **url** - URL for the new `Request` object.
|
| 65 |
+
* **params** - *(optional)* Query parameters to include in the URL, as a
|
| 66 |
+
string, dictionary, or sequence of two-tuples.
|
| 67 |
+
* **content** - *(optional)* Binary content to include in the body of the
|
| 68 |
+
request, as bytes or a byte iterator.
|
| 69 |
+
* **data** - *(optional)* Form data to include in the body of the request,
|
| 70 |
+
as a dictionary.
|
| 71 |
+
* **files** - *(optional)* A dictionary of upload files to include in the
|
| 72 |
+
body of the request.
|
| 73 |
+
* **json** - *(optional)* A JSON serializable object to include in the body
|
| 74 |
+
of the request.
|
| 75 |
+
* **headers** - *(optional)* Dictionary of HTTP headers to include in the
|
| 76 |
+
request.
|
| 77 |
+
* **cookies** - *(optional)* Dictionary of Cookie items to include in the
|
| 78 |
+
request.
|
| 79 |
+
* **auth** - *(optional)* An authentication class to use when sending the
|
| 80 |
+
request.
|
| 81 |
+
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
| 82 |
+
* **timeout** - *(optional)* The timeout configuration to use when sending
|
| 83 |
+
the request.
|
| 84 |
+
* **follow_redirects** - *(optional)* Enables or disables HTTP redirects.
|
| 85 |
+
* **verify** - *(optional)* Either `True` to use an SSL context with the
|
| 86 |
+
default CA bundle, `False` to disable verification, or an instance of
|
| 87 |
+
`ssl.SSLContext` to use a custom context.
|
| 88 |
+
* **trust_env** - *(optional)* Enables or disables usage of environment
|
| 89 |
+
variables for configuration.
|
| 90 |
+
|
| 91 |
+
**Returns:** `Response`
|
| 92 |
+
|
| 93 |
+
Usage:
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
>>> import httpx
|
| 97 |
+
>>> response = httpx.request('GET', 'https://httpbin.org/get')
|
| 98 |
+
>>> response
|
| 99 |
+
<Response [200 OK]>
|
| 100 |
+
```
|
| 101 |
+
"""
|
| 102 |
+
with Client(
|
| 103 |
+
cookies=cookies,
|
| 104 |
+
proxy=proxy,
|
| 105 |
+
verify=verify,
|
| 106 |
+
timeout=timeout,
|
| 107 |
+
trust_env=trust_env,
|
| 108 |
+
) as client:
|
| 109 |
+
return client.request(
|
| 110 |
+
method=method,
|
| 111 |
+
url=url,
|
| 112 |
+
content=content,
|
| 113 |
+
data=data,
|
| 114 |
+
files=files,
|
| 115 |
+
json=json,
|
| 116 |
+
params=params,
|
| 117 |
+
headers=headers,
|
| 118 |
+
auth=auth,
|
| 119 |
+
follow_redirects=follow_redirects,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@contextmanager
|
| 124 |
+
def stream(
|
| 125 |
+
method: str,
|
| 126 |
+
url: URL | str,
|
| 127 |
+
*,
|
| 128 |
+
params: QueryParamTypes | None = None,
|
| 129 |
+
content: RequestContent | None = None,
|
| 130 |
+
data: RequestData | None = None,
|
| 131 |
+
files: RequestFiles | None = None,
|
| 132 |
+
json: typing.Any | None = None,
|
| 133 |
+
headers: HeaderTypes | None = None,
|
| 134 |
+
cookies: CookieTypes | None = None,
|
| 135 |
+
auth: AuthTypes | None = None,
|
| 136 |
+
proxy: ProxyTypes | None = None,
|
| 137 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 138 |
+
follow_redirects: bool = False,
|
| 139 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 140 |
+
trust_env: bool = True,
|
| 141 |
+
) -> typing.Iterator[Response]:
|
| 142 |
+
"""
|
| 143 |
+
Alternative to `httpx.request()` that streams the response body
|
| 144 |
+
instead of loading it into memory at once.
|
| 145 |
+
|
| 146 |
+
**Parameters**: See `httpx.request`.
|
| 147 |
+
|
| 148 |
+
See also: [Streaming Responses][0]
|
| 149 |
+
|
| 150 |
+
[0]: /quickstart#streaming-responses
|
| 151 |
+
"""
|
| 152 |
+
with Client(
|
| 153 |
+
cookies=cookies,
|
| 154 |
+
proxy=proxy,
|
| 155 |
+
verify=verify,
|
| 156 |
+
timeout=timeout,
|
| 157 |
+
trust_env=trust_env,
|
| 158 |
+
) as client:
|
| 159 |
+
with client.stream(
|
| 160 |
+
method=method,
|
| 161 |
+
url=url,
|
| 162 |
+
content=content,
|
| 163 |
+
data=data,
|
| 164 |
+
files=files,
|
| 165 |
+
json=json,
|
| 166 |
+
params=params,
|
| 167 |
+
headers=headers,
|
| 168 |
+
auth=auth,
|
| 169 |
+
follow_redirects=follow_redirects,
|
| 170 |
+
) as response:
|
| 171 |
+
yield response
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def get(
|
| 175 |
+
url: URL | str,
|
| 176 |
+
*,
|
| 177 |
+
params: QueryParamTypes | None = None,
|
| 178 |
+
headers: HeaderTypes | None = None,
|
| 179 |
+
cookies: CookieTypes | None = None,
|
| 180 |
+
auth: AuthTypes | None = None,
|
| 181 |
+
proxy: ProxyTypes | None = None,
|
| 182 |
+
follow_redirects: bool = False,
|
| 183 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 184 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 185 |
+
trust_env: bool = True,
|
| 186 |
+
) -> Response:
|
| 187 |
+
"""
|
| 188 |
+
Sends a `GET` request.
|
| 189 |
+
|
| 190 |
+
**Parameters**: See `httpx.request`.
|
| 191 |
+
|
| 192 |
+
Note that the `data`, `files`, `json` and `content` parameters are not available
|
| 193 |
+
on this function, as `GET` requests should not include a request body.
|
| 194 |
+
"""
|
| 195 |
+
return request(
|
| 196 |
+
"GET",
|
| 197 |
+
url,
|
| 198 |
+
params=params,
|
| 199 |
+
headers=headers,
|
| 200 |
+
cookies=cookies,
|
| 201 |
+
auth=auth,
|
| 202 |
+
proxy=proxy,
|
| 203 |
+
follow_redirects=follow_redirects,
|
| 204 |
+
verify=verify,
|
| 205 |
+
timeout=timeout,
|
| 206 |
+
trust_env=trust_env,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def options(
|
| 211 |
+
url: URL | str,
|
| 212 |
+
*,
|
| 213 |
+
params: QueryParamTypes | None = None,
|
| 214 |
+
headers: HeaderTypes | None = None,
|
| 215 |
+
cookies: CookieTypes | None = None,
|
| 216 |
+
auth: AuthTypes | None = None,
|
| 217 |
+
proxy: ProxyTypes | None = None,
|
| 218 |
+
follow_redirects: bool = False,
|
| 219 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 220 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 221 |
+
trust_env: bool = True,
|
| 222 |
+
) -> Response:
|
| 223 |
+
"""
|
| 224 |
+
Sends an `OPTIONS` request.
|
| 225 |
+
|
| 226 |
+
**Parameters**: See `httpx.request`.
|
| 227 |
+
|
| 228 |
+
Note that the `data`, `files`, `json` and `content` parameters are not available
|
| 229 |
+
on this function, as `OPTIONS` requests should not include a request body.
|
| 230 |
+
"""
|
| 231 |
+
return request(
|
| 232 |
+
"OPTIONS",
|
| 233 |
+
url,
|
| 234 |
+
params=params,
|
| 235 |
+
headers=headers,
|
| 236 |
+
cookies=cookies,
|
| 237 |
+
auth=auth,
|
| 238 |
+
proxy=proxy,
|
| 239 |
+
follow_redirects=follow_redirects,
|
| 240 |
+
verify=verify,
|
| 241 |
+
timeout=timeout,
|
| 242 |
+
trust_env=trust_env,
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def head(
|
| 247 |
+
url: URL | str,
|
| 248 |
+
*,
|
| 249 |
+
params: QueryParamTypes | None = None,
|
| 250 |
+
headers: HeaderTypes | None = None,
|
| 251 |
+
cookies: CookieTypes | None = None,
|
| 252 |
+
auth: AuthTypes | None = None,
|
| 253 |
+
proxy: ProxyTypes | None = None,
|
| 254 |
+
follow_redirects: bool = False,
|
| 255 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 256 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 257 |
+
trust_env: bool = True,
|
| 258 |
+
) -> Response:
|
| 259 |
+
"""
|
| 260 |
+
Sends a `HEAD` request.
|
| 261 |
+
|
| 262 |
+
**Parameters**: See `httpx.request`.
|
| 263 |
+
|
| 264 |
+
Note that the `data`, `files`, `json` and `content` parameters are not available
|
| 265 |
+
on this function, as `HEAD` requests should not include a request body.
|
| 266 |
+
"""
|
| 267 |
+
return request(
|
| 268 |
+
"HEAD",
|
| 269 |
+
url,
|
| 270 |
+
params=params,
|
| 271 |
+
headers=headers,
|
| 272 |
+
cookies=cookies,
|
| 273 |
+
auth=auth,
|
| 274 |
+
proxy=proxy,
|
| 275 |
+
follow_redirects=follow_redirects,
|
| 276 |
+
verify=verify,
|
| 277 |
+
timeout=timeout,
|
| 278 |
+
trust_env=trust_env,
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def post(
|
| 283 |
+
url: URL | str,
|
| 284 |
+
*,
|
| 285 |
+
content: RequestContent | None = None,
|
| 286 |
+
data: RequestData | None = None,
|
| 287 |
+
files: RequestFiles | None = None,
|
| 288 |
+
json: typing.Any | None = None,
|
| 289 |
+
params: QueryParamTypes | None = None,
|
| 290 |
+
headers: HeaderTypes | None = None,
|
| 291 |
+
cookies: CookieTypes | None = None,
|
| 292 |
+
auth: AuthTypes | None = None,
|
| 293 |
+
proxy: ProxyTypes | None = None,
|
| 294 |
+
follow_redirects: bool = False,
|
| 295 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 296 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 297 |
+
trust_env: bool = True,
|
| 298 |
+
) -> Response:
|
| 299 |
+
"""
|
| 300 |
+
Sends a `POST` request.
|
| 301 |
+
|
| 302 |
+
**Parameters**: See `httpx.request`.
|
| 303 |
+
"""
|
| 304 |
+
return request(
|
| 305 |
+
"POST",
|
| 306 |
+
url,
|
| 307 |
+
content=content,
|
| 308 |
+
data=data,
|
| 309 |
+
files=files,
|
| 310 |
+
json=json,
|
| 311 |
+
params=params,
|
| 312 |
+
headers=headers,
|
| 313 |
+
cookies=cookies,
|
| 314 |
+
auth=auth,
|
| 315 |
+
proxy=proxy,
|
| 316 |
+
follow_redirects=follow_redirects,
|
| 317 |
+
verify=verify,
|
| 318 |
+
timeout=timeout,
|
| 319 |
+
trust_env=trust_env,
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def put(
|
| 324 |
+
url: URL | str,
|
| 325 |
+
*,
|
| 326 |
+
content: RequestContent | None = None,
|
| 327 |
+
data: RequestData | None = None,
|
| 328 |
+
files: RequestFiles | None = None,
|
| 329 |
+
json: typing.Any | None = None,
|
| 330 |
+
params: QueryParamTypes | None = None,
|
| 331 |
+
headers: HeaderTypes | None = None,
|
| 332 |
+
cookies: CookieTypes | None = None,
|
| 333 |
+
auth: AuthTypes | None = None,
|
| 334 |
+
proxy: ProxyTypes | None = None,
|
| 335 |
+
follow_redirects: bool = False,
|
| 336 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 337 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 338 |
+
trust_env: bool = True,
|
| 339 |
+
) -> Response:
|
| 340 |
+
"""
|
| 341 |
+
Sends a `PUT` request.
|
| 342 |
+
|
| 343 |
+
**Parameters**: See `httpx.request`.
|
| 344 |
+
"""
|
| 345 |
+
return request(
|
| 346 |
+
"PUT",
|
| 347 |
+
url,
|
| 348 |
+
content=content,
|
| 349 |
+
data=data,
|
| 350 |
+
files=files,
|
| 351 |
+
json=json,
|
| 352 |
+
params=params,
|
| 353 |
+
headers=headers,
|
| 354 |
+
cookies=cookies,
|
| 355 |
+
auth=auth,
|
| 356 |
+
proxy=proxy,
|
| 357 |
+
follow_redirects=follow_redirects,
|
| 358 |
+
verify=verify,
|
| 359 |
+
timeout=timeout,
|
| 360 |
+
trust_env=trust_env,
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def patch(
|
| 365 |
+
url: URL | str,
|
| 366 |
+
*,
|
| 367 |
+
content: RequestContent | None = None,
|
| 368 |
+
data: RequestData | None = None,
|
| 369 |
+
files: RequestFiles | None = None,
|
| 370 |
+
json: typing.Any | None = None,
|
| 371 |
+
params: QueryParamTypes | None = None,
|
| 372 |
+
headers: HeaderTypes | None = None,
|
| 373 |
+
cookies: CookieTypes | None = None,
|
| 374 |
+
auth: AuthTypes | None = None,
|
| 375 |
+
proxy: ProxyTypes | None = None,
|
| 376 |
+
follow_redirects: bool = False,
|
| 377 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 378 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 379 |
+
trust_env: bool = True,
|
| 380 |
+
) -> Response:
|
| 381 |
+
"""
|
| 382 |
+
Sends a `PATCH` request.
|
| 383 |
+
|
| 384 |
+
**Parameters**: See `httpx.request`.
|
| 385 |
+
"""
|
| 386 |
+
return request(
|
| 387 |
+
"PATCH",
|
| 388 |
+
url,
|
| 389 |
+
content=content,
|
| 390 |
+
data=data,
|
| 391 |
+
files=files,
|
| 392 |
+
json=json,
|
| 393 |
+
params=params,
|
| 394 |
+
headers=headers,
|
| 395 |
+
cookies=cookies,
|
| 396 |
+
auth=auth,
|
| 397 |
+
proxy=proxy,
|
| 398 |
+
follow_redirects=follow_redirects,
|
| 399 |
+
verify=verify,
|
| 400 |
+
timeout=timeout,
|
| 401 |
+
trust_env=trust_env,
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def delete(
|
| 406 |
+
url: URL | str,
|
| 407 |
+
*,
|
| 408 |
+
params: QueryParamTypes | None = None,
|
| 409 |
+
headers: HeaderTypes | None = None,
|
| 410 |
+
cookies: CookieTypes | None = None,
|
| 411 |
+
auth: AuthTypes | None = None,
|
| 412 |
+
proxy: ProxyTypes | None = None,
|
| 413 |
+
follow_redirects: bool = False,
|
| 414 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 415 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 416 |
+
trust_env: bool = True,
|
| 417 |
+
) -> Response:
|
| 418 |
+
"""
|
| 419 |
+
Sends a `DELETE` request.
|
| 420 |
+
|
| 421 |
+
**Parameters**: See `httpx.request`.
|
| 422 |
+
|
| 423 |
+
Note that the `data`, `files`, `json` and `content` parameters are not available
|
| 424 |
+
on this function, as `DELETE` requests should not include a request body.
|
| 425 |
+
"""
|
| 426 |
+
return request(
|
| 427 |
+
"DELETE",
|
| 428 |
+
url,
|
| 429 |
+
params=params,
|
| 430 |
+
headers=headers,
|
| 431 |
+
cookies=cookies,
|
| 432 |
+
auth=auth,
|
| 433 |
+
proxy=proxy,
|
| 434 |
+
follow_redirects=follow_redirects,
|
| 435 |
+
verify=verify,
|
| 436 |
+
timeout=timeout,
|
| 437 |
+
trust_env=trust_env,
|
| 438 |
+
)
|
.venv/lib/python3.14/site-packages/httpx/_auth.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import time
|
| 7 |
+
import typing
|
| 8 |
+
from base64 import b64encode
|
| 9 |
+
from urllib.request import parse_http_list
|
| 10 |
+
|
| 11 |
+
from ._exceptions import ProtocolError
|
| 12 |
+
from ._models import Cookies, Request, Response
|
| 13 |
+
from ._utils import to_bytes, to_str, unquote
|
| 14 |
+
|
| 15 |
+
if typing.TYPE_CHECKING: # pragma: no cover
|
| 16 |
+
from hashlib import _Hash
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Auth:
|
| 23 |
+
"""
|
| 24 |
+
Base class for all authentication schemes.
|
| 25 |
+
|
| 26 |
+
To implement a custom authentication scheme, subclass `Auth` and override
|
| 27 |
+
the `.auth_flow()` method.
|
| 28 |
+
|
| 29 |
+
If the authentication scheme does I/O such as disk access or network calls, or uses
|
| 30 |
+
synchronization primitives such as locks, you should override `.sync_auth_flow()`
|
| 31 |
+
and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized
|
| 32 |
+
implementations that will be used by `Client` and `AsyncClient` respectively.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
requires_request_body = False
|
| 36 |
+
requires_response_body = False
|
| 37 |
+
|
| 38 |
+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
| 39 |
+
"""
|
| 40 |
+
Execute the authentication flow.
|
| 41 |
+
|
| 42 |
+
To dispatch a request, `yield` it:
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
yield request
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
The client will `.send()` the response back into the flow generator. You can
|
| 49 |
+
access it like so:
|
| 50 |
+
|
| 51 |
+
```
|
| 52 |
+
response = yield request
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
A `return` (or reaching the end of the generator) will result in the
|
| 56 |
+
client returning the last response obtained from the server.
|
| 57 |
+
|
| 58 |
+
You can dispatch as many requests as is necessary.
|
| 59 |
+
"""
|
| 60 |
+
yield request
|
| 61 |
+
|
| 62 |
+
def sync_auth_flow(
|
| 63 |
+
self, request: Request
|
| 64 |
+
) -> typing.Generator[Request, Response, None]:
|
| 65 |
+
"""
|
| 66 |
+
Execute the authentication flow synchronously.
|
| 67 |
+
|
| 68 |
+
By default, this defers to `.auth_flow()`. You should override this method
|
| 69 |
+
when the authentication scheme does I/O and/or uses concurrency primitives.
|
| 70 |
+
"""
|
| 71 |
+
if self.requires_request_body:
|
| 72 |
+
request.read()
|
| 73 |
+
|
| 74 |
+
flow = self.auth_flow(request)
|
| 75 |
+
request = next(flow)
|
| 76 |
+
|
| 77 |
+
while True:
|
| 78 |
+
response = yield request
|
| 79 |
+
if self.requires_response_body:
|
| 80 |
+
response.read()
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
request = flow.send(response)
|
| 84 |
+
except StopIteration:
|
| 85 |
+
break
|
| 86 |
+
|
| 87 |
+
async def async_auth_flow(
|
| 88 |
+
self, request: Request
|
| 89 |
+
) -> typing.AsyncGenerator[Request, Response]:
|
| 90 |
+
"""
|
| 91 |
+
Execute the authentication flow asynchronously.
|
| 92 |
+
|
| 93 |
+
By default, this defers to `.auth_flow()`. You should override this method
|
| 94 |
+
when the authentication scheme does I/O and/or uses concurrency primitives.
|
| 95 |
+
"""
|
| 96 |
+
if self.requires_request_body:
|
| 97 |
+
await request.aread()
|
| 98 |
+
|
| 99 |
+
flow = self.auth_flow(request)
|
| 100 |
+
request = next(flow)
|
| 101 |
+
|
| 102 |
+
while True:
|
| 103 |
+
response = yield request
|
| 104 |
+
if self.requires_response_body:
|
| 105 |
+
await response.aread()
|
| 106 |
+
|
| 107 |
+
try:
|
| 108 |
+
request = flow.send(response)
|
| 109 |
+
except StopIteration:
|
| 110 |
+
break
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class FunctionAuth(Auth):
|
| 114 |
+
"""
|
| 115 |
+
Allows the 'auth' argument to be passed as a simple callable function,
|
| 116 |
+
that takes the request, and returns a new, modified request.
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
def __init__(self, func: typing.Callable[[Request], Request]) -> None:
|
| 120 |
+
self._func = func
|
| 121 |
+
|
| 122 |
+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
| 123 |
+
yield self._func(request)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class BasicAuth(Auth):
|
| 127 |
+
"""
|
| 128 |
+
Allows the 'auth' argument to be passed as a (username, password) pair,
|
| 129 |
+
and uses HTTP Basic authentication.
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
def __init__(self, username: str | bytes, password: str | bytes) -> None:
|
| 133 |
+
self._auth_header = self._build_auth_header(username, password)
|
| 134 |
+
|
| 135 |
+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
| 136 |
+
request.headers["Authorization"] = self._auth_header
|
| 137 |
+
yield request
|
| 138 |
+
|
| 139 |
+
def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
|
| 140 |
+
userpass = b":".join((to_bytes(username), to_bytes(password)))
|
| 141 |
+
token = b64encode(userpass).decode()
|
| 142 |
+
return f"Basic {token}"
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class NetRCAuth(Auth):
|
| 146 |
+
"""
|
| 147 |
+
Use a 'netrc' file to lookup basic auth credentials based on the url host.
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
def __init__(self, file: str | None = None) -> None:
|
| 151 |
+
# Lazily import 'netrc'.
|
| 152 |
+
# There's no need for us to load this module unless 'NetRCAuth' is being used.
|
| 153 |
+
import netrc
|
| 154 |
+
|
| 155 |
+
self._netrc_info = netrc.netrc(file)
|
| 156 |
+
|
| 157 |
+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
| 158 |
+
auth_info = self._netrc_info.authenticators(request.url.host)
|
| 159 |
+
if auth_info is None or not auth_info[2]:
|
| 160 |
+
# The netrc file did not have authentication credentials for this host.
|
| 161 |
+
yield request
|
| 162 |
+
else:
|
| 163 |
+
# Build a basic auth header with credentials from the netrc file.
|
| 164 |
+
request.headers["Authorization"] = self._build_auth_header(
|
| 165 |
+
username=auth_info[0], password=auth_info[2]
|
| 166 |
+
)
|
| 167 |
+
yield request
|
| 168 |
+
|
| 169 |
+
def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:
|
| 170 |
+
userpass = b":".join((to_bytes(username), to_bytes(password)))
|
| 171 |
+
token = b64encode(userpass).decode()
|
| 172 |
+
return f"Basic {token}"
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class DigestAuth(Auth):
|
| 176 |
+
_ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = {
|
| 177 |
+
"MD5": hashlib.md5,
|
| 178 |
+
"MD5-SESS": hashlib.md5,
|
| 179 |
+
"SHA": hashlib.sha1,
|
| 180 |
+
"SHA-SESS": hashlib.sha1,
|
| 181 |
+
"SHA-256": hashlib.sha256,
|
| 182 |
+
"SHA-256-SESS": hashlib.sha256,
|
| 183 |
+
"SHA-512": hashlib.sha512,
|
| 184 |
+
"SHA-512-SESS": hashlib.sha512,
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
def __init__(self, username: str | bytes, password: str | bytes) -> None:
|
| 188 |
+
self._username = to_bytes(username)
|
| 189 |
+
self._password = to_bytes(password)
|
| 190 |
+
self._last_challenge: _DigestAuthChallenge | None = None
|
| 191 |
+
self._nonce_count = 1
|
| 192 |
+
|
| 193 |
+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
|
| 194 |
+
if self._last_challenge:
|
| 195 |
+
request.headers["Authorization"] = self._build_auth_header(
|
| 196 |
+
request, self._last_challenge
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
response = yield request
|
| 200 |
+
|
| 201 |
+
if response.status_code != 401 or "www-authenticate" not in response.headers:
|
| 202 |
+
# If the response is not a 401 then we don't
|
| 203 |
+
# need to build an authenticated request.
|
| 204 |
+
return
|
| 205 |
+
|
| 206 |
+
for auth_header in response.headers.get_list("www-authenticate"):
|
| 207 |
+
if auth_header.lower().startswith("digest "):
|
| 208 |
+
break
|
| 209 |
+
else:
|
| 210 |
+
# If the response does not include a 'WWW-Authenticate: Digest ...'
|
| 211 |
+
# header, then we don't need to build an authenticated request.
|
| 212 |
+
return
|
| 213 |
+
|
| 214 |
+
self._last_challenge = self._parse_challenge(request, response, auth_header)
|
| 215 |
+
self._nonce_count = 1
|
| 216 |
+
|
| 217 |
+
request.headers["Authorization"] = self._build_auth_header(
|
| 218 |
+
request, self._last_challenge
|
| 219 |
+
)
|
| 220 |
+
if response.cookies:
|
| 221 |
+
Cookies(response.cookies).set_cookie_header(request=request)
|
| 222 |
+
yield request
|
| 223 |
+
|
| 224 |
+
def _parse_challenge(
|
| 225 |
+
self, request: Request, response: Response, auth_header: str
|
| 226 |
+
) -> _DigestAuthChallenge:
|
| 227 |
+
"""
|
| 228 |
+
Returns a challenge from a Digest WWW-Authenticate header.
|
| 229 |
+
These take the form of:
|
| 230 |
+
`Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"`
|
| 231 |
+
"""
|
| 232 |
+
scheme, _, fields = auth_header.partition(" ")
|
| 233 |
+
|
| 234 |
+
# This method should only ever have been called with a Digest auth header.
|
| 235 |
+
assert scheme.lower() == "digest"
|
| 236 |
+
|
| 237 |
+
header_dict: dict[str, str] = {}
|
| 238 |
+
for field in parse_http_list(fields):
|
| 239 |
+
key, value = field.strip().split("=", 1)
|
| 240 |
+
header_dict[key] = unquote(value)
|
| 241 |
+
|
| 242 |
+
try:
|
| 243 |
+
realm = header_dict["realm"].encode()
|
| 244 |
+
nonce = header_dict["nonce"].encode()
|
| 245 |
+
algorithm = header_dict.get("algorithm", "MD5")
|
| 246 |
+
opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None
|
| 247 |
+
qop = header_dict["qop"].encode() if "qop" in header_dict else None
|
| 248 |
+
return _DigestAuthChallenge(
|
| 249 |
+
realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop
|
| 250 |
+
)
|
| 251 |
+
except KeyError as exc:
|
| 252 |
+
message = "Malformed Digest WWW-Authenticate header"
|
| 253 |
+
raise ProtocolError(message, request=request) from exc
|
| 254 |
+
|
| 255 |
+
def _build_auth_header(
|
| 256 |
+
self, request: Request, challenge: _DigestAuthChallenge
|
| 257 |
+
) -> str:
|
| 258 |
+
hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()]
|
| 259 |
+
|
| 260 |
+
def digest(data: bytes) -> bytes:
|
| 261 |
+
return hash_func(data).hexdigest().encode()
|
| 262 |
+
|
| 263 |
+
A1 = b":".join((self._username, challenge.realm, self._password))
|
| 264 |
+
|
| 265 |
+
path = request.url.raw_path
|
| 266 |
+
A2 = b":".join((request.method.encode(), path))
|
| 267 |
+
# TODO: implement auth-int
|
| 268 |
+
HA2 = digest(A2)
|
| 269 |
+
|
| 270 |
+
nc_value = b"%08x" % self._nonce_count
|
| 271 |
+
cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce)
|
| 272 |
+
self._nonce_count += 1
|
| 273 |
+
|
| 274 |
+
HA1 = digest(A1)
|
| 275 |
+
if challenge.algorithm.lower().endswith("-sess"):
|
| 276 |
+
HA1 = digest(b":".join((HA1, challenge.nonce, cnonce)))
|
| 277 |
+
|
| 278 |
+
qop = self._resolve_qop(challenge.qop, request=request)
|
| 279 |
+
if qop is None:
|
| 280 |
+
# Following RFC 2069
|
| 281 |
+
digest_data = [HA1, challenge.nonce, HA2]
|
| 282 |
+
else:
|
| 283 |
+
# Following RFC 2617/7616
|
| 284 |
+
digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2]
|
| 285 |
+
|
| 286 |
+
format_args = {
|
| 287 |
+
"username": self._username,
|
| 288 |
+
"realm": challenge.realm,
|
| 289 |
+
"nonce": challenge.nonce,
|
| 290 |
+
"uri": path,
|
| 291 |
+
"response": digest(b":".join(digest_data)),
|
| 292 |
+
"algorithm": challenge.algorithm.encode(),
|
| 293 |
+
}
|
| 294 |
+
if challenge.opaque:
|
| 295 |
+
format_args["opaque"] = challenge.opaque
|
| 296 |
+
if qop:
|
| 297 |
+
format_args["qop"] = b"auth"
|
| 298 |
+
format_args["nc"] = nc_value
|
| 299 |
+
format_args["cnonce"] = cnonce
|
| 300 |
+
|
| 301 |
+
return "Digest " + self._get_header_value(format_args)
|
| 302 |
+
|
| 303 |
+
def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes:
|
| 304 |
+
s = str(nonce_count).encode()
|
| 305 |
+
s += nonce
|
| 306 |
+
s += time.ctime().encode()
|
| 307 |
+
s += os.urandom(8)
|
| 308 |
+
|
| 309 |
+
return hashlib.sha1(s).hexdigest()[:16].encode()
|
| 310 |
+
|
| 311 |
+
def _get_header_value(self, header_fields: dict[str, bytes]) -> str:
|
| 312 |
+
NON_QUOTED_FIELDS = ("algorithm", "qop", "nc")
|
| 313 |
+
QUOTED_TEMPLATE = '{}="{}"'
|
| 314 |
+
NON_QUOTED_TEMPLATE = "{}={}"
|
| 315 |
+
|
| 316 |
+
header_value = ""
|
| 317 |
+
for i, (field, value) in enumerate(header_fields.items()):
|
| 318 |
+
if i > 0:
|
| 319 |
+
header_value += ", "
|
| 320 |
+
template = (
|
| 321 |
+
QUOTED_TEMPLATE
|
| 322 |
+
if field not in NON_QUOTED_FIELDS
|
| 323 |
+
else NON_QUOTED_TEMPLATE
|
| 324 |
+
)
|
| 325 |
+
header_value += template.format(field, to_str(value))
|
| 326 |
+
|
| 327 |
+
return header_value
|
| 328 |
+
|
| 329 |
+
def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None:
|
| 330 |
+
if qop is None:
|
| 331 |
+
return None
|
| 332 |
+
qops = re.split(b", ?", qop)
|
| 333 |
+
if b"auth" in qops:
|
| 334 |
+
return b"auth"
|
| 335 |
+
|
| 336 |
+
if qops == [b"auth-int"]:
|
| 337 |
+
raise NotImplementedError("Digest auth-int support is not yet implemented")
|
| 338 |
+
|
| 339 |
+
message = f'Unexpected qop value "{qop!r}" in digest auth'
|
| 340 |
+
raise ProtocolError(message, request=request)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
class _DigestAuthChallenge(typing.NamedTuple):
|
| 344 |
+
realm: bytes
|
| 345 |
+
nonce: bytes
|
| 346 |
+
algorithm: str
|
| 347 |
+
opaque: bytes | None
|
| 348 |
+
qop: bytes | None
|
.venv/lib/python3.14/site-packages/httpx/_client.py
ADDED
|
@@ -0,0 +1,2019 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import datetime
|
| 4 |
+
import enum
|
| 5 |
+
import logging
|
| 6 |
+
import time
|
| 7 |
+
import typing
|
| 8 |
+
import warnings
|
| 9 |
+
from contextlib import asynccontextmanager, contextmanager
|
| 10 |
+
from types import TracebackType
|
| 11 |
+
|
| 12 |
+
from .__version__ import __version__
|
| 13 |
+
from ._auth import Auth, BasicAuth, FunctionAuth
|
| 14 |
+
from ._config import (
|
| 15 |
+
DEFAULT_LIMITS,
|
| 16 |
+
DEFAULT_MAX_REDIRECTS,
|
| 17 |
+
DEFAULT_TIMEOUT_CONFIG,
|
| 18 |
+
Limits,
|
| 19 |
+
Proxy,
|
| 20 |
+
Timeout,
|
| 21 |
+
)
|
| 22 |
+
from ._decoders import SUPPORTED_DECODERS
|
| 23 |
+
from ._exceptions import (
|
| 24 |
+
InvalidURL,
|
| 25 |
+
RemoteProtocolError,
|
| 26 |
+
TooManyRedirects,
|
| 27 |
+
request_context,
|
| 28 |
+
)
|
| 29 |
+
from ._models import Cookies, Headers, Request, Response
|
| 30 |
+
from ._status_codes import codes
|
| 31 |
+
from ._transports.base import AsyncBaseTransport, BaseTransport
|
| 32 |
+
from ._transports.default import AsyncHTTPTransport, HTTPTransport
|
| 33 |
+
from ._types import (
|
| 34 |
+
AsyncByteStream,
|
| 35 |
+
AuthTypes,
|
| 36 |
+
CertTypes,
|
| 37 |
+
CookieTypes,
|
| 38 |
+
HeaderTypes,
|
| 39 |
+
ProxyTypes,
|
| 40 |
+
QueryParamTypes,
|
| 41 |
+
RequestContent,
|
| 42 |
+
RequestData,
|
| 43 |
+
RequestExtensions,
|
| 44 |
+
RequestFiles,
|
| 45 |
+
SyncByteStream,
|
| 46 |
+
TimeoutTypes,
|
| 47 |
+
)
|
| 48 |
+
from ._urls import URL, QueryParams
|
| 49 |
+
from ._utils import URLPattern, get_environment_proxies
|
| 50 |
+
|
| 51 |
+
if typing.TYPE_CHECKING:
|
| 52 |
+
import ssl # pragma: no cover
|
| 53 |
+
|
| 54 |
+
__all__ = ["USE_CLIENT_DEFAULT", "AsyncClient", "Client"]
|
| 55 |
+
|
| 56 |
+
# The type annotation for @classmethod and context managers here follows PEP 484
|
| 57 |
+
# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods
|
| 58 |
+
T = typing.TypeVar("T", bound="Client")
|
| 59 |
+
U = typing.TypeVar("U", bound="AsyncClient")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _is_https_redirect(url: URL, location: URL) -> bool:
|
| 63 |
+
"""
|
| 64 |
+
Return 'True' if 'location' is a HTTPS upgrade of 'url'
|
| 65 |
+
"""
|
| 66 |
+
if url.host != location.host:
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
return (
|
| 70 |
+
url.scheme == "http"
|
| 71 |
+
and _port_or_default(url) == 80
|
| 72 |
+
and location.scheme == "https"
|
| 73 |
+
and _port_or_default(location) == 443
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _port_or_default(url: URL) -> int | None:
|
| 78 |
+
if url.port is not None:
|
| 79 |
+
return url.port
|
| 80 |
+
return {"http": 80, "https": 443}.get(url.scheme)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _same_origin(url: URL, other: URL) -> bool:
|
| 84 |
+
"""
|
| 85 |
+
Return 'True' if the given URLs share the same origin.
|
| 86 |
+
"""
|
| 87 |
+
return (
|
| 88 |
+
url.scheme == other.scheme
|
| 89 |
+
and url.host == other.host
|
| 90 |
+
and _port_or_default(url) == _port_or_default(other)
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class UseClientDefault:
|
| 95 |
+
"""
|
| 96 |
+
For some parameters such as `auth=...` and `timeout=...` we need to be able
|
| 97 |
+
to indicate the default "unset" state, in a way that is distinctly different
|
| 98 |
+
to using `None`.
|
| 99 |
+
|
| 100 |
+
The default "unset" state indicates that whatever default is set on the
|
| 101 |
+
client should be used. This is different to setting `None`, which
|
| 102 |
+
explicitly disables the parameter, possibly overriding a client default.
|
| 103 |
+
|
| 104 |
+
For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature.
|
| 105 |
+
Omitting the `timeout` parameter will send a request using whatever default
|
| 106 |
+
timeout has been configured on the client. Including `timeout=None` will
|
| 107 |
+
ensure no timeout is used.
|
| 108 |
+
|
| 109 |
+
Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant,
|
| 110 |
+
but it is used internally when a parameter is not included.
|
| 111 |
+
"""
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
USE_CLIENT_DEFAULT = UseClientDefault()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
logger = logging.getLogger("httpx")
|
| 118 |
+
|
| 119 |
+
USER_AGENT = f"python-httpx/{__version__}"
|
| 120 |
+
ACCEPT_ENCODING = ", ".join(
|
| 121 |
+
[key for key in SUPPORTED_DECODERS.keys() if key != "identity"]
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class ClientState(enum.Enum):
|
| 126 |
+
# UNOPENED:
|
| 127 |
+
# The client has been instantiated, but has not been used to send a request,
|
| 128 |
+
# or been opened by entering the context of a `with` block.
|
| 129 |
+
UNOPENED = 1
|
| 130 |
+
# OPENED:
|
| 131 |
+
# The client has either sent a request, or is within a `with` block.
|
| 132 |
+
OPENED = 2
|
| 133 |
+
# CLOSED:
|
| 134 |
+
# The client has either exited the `with` block, or `close()` has
|
| 135 |
+
# been called explicitly.
|
| 136 |
+
CLOSED = 3
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class BoundSyncStream(SyncByteStream):
|
| 140 |
+
"""
|
| 141 |
+
A byte stream that is bound to a given response instance, and that
|
| 142 |
+
ensures the `response.elapsed` is set once the response is closed.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
def __init__(
|
| 146 |
+
self, stream: SyncByteStream, response: Response, start: float
|
| 147 |
+
) -> None:
|
| 148 |
+
self._stream = stream
|
| 149 |
+
self._response = response
|
| 150 |
+
self._start = start
|
| 151 |
+
|
| 152 |
+
def __iter__(self) -> typing.Iterator[bytes]:
|
| 153 |
+
for chunk in self._stream:
|
| 154 |
+
yield chunk
|
| 155 |
+
|
| 156 |
+
def close(self) -> None:
|
| 157 |
+
elapsed = time.perf_counter() - self._start
|
| 158 |
+
self._response.elapsed = datetime.timedelta(seconds=elapsed)
|
| 159 |
+
self._stream.close()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class BoundAsyncStream(AsyncByteStream):
|
| 163 |
+
"""
|
| 164 |
+
An async byte stream that is bound to a given response instance, and that
|
| 165 |
+
ensures the `response.elapsed` is set once the response is closed.
|
| 166 |
+
"""
|
| 167 |
+
|
| 168 |
+
def __init__(
|
| 169 |
+
self, stream: AsyncByteStream, response: Response, start: float
|
| 170 |
+
) -> None:
|
| 171 |
+
self._stream = stream
|
| 172 |
+
self._response = response
|
| 173 |
+
self._start = start
|
| 174 |
+
|
| 175 |
+
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
|
| 176 |
+
async for chunk in self._stream:
|
| 177 |
+
yield chunk
|
| 178 |
+
|
| 179 |
+
async def aclose(self) -> None:
|
| 180 |
+
elapsed = time.perf_counter() - self._start
|
| 181 |
+
self._response.elapsed = datetime.timedelta(seconds=elapsed)
|
| 182 |
+
await self._stream.aclose()
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
EventHook = typing.Callable[..., typing.Any]
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class BaseClient:
|
| 189 |
+
def __init__(
|
| 190 |
+
self,
|
| 191 |
+
*,
|
| 192 |
+
auth: AuthTypes | None = None,
|
| 193 |
+
params: QueryParamTypes | None = None,
|
| 194 |
+
headers: HeaderTypes | None = None,
|
| 195 |
+
cookies: CookieTypes | None = None,
|
| 196 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 197 |
+
follow_redirects: bool = False,
|
| 198 |
+
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
| 199 |
+
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
| 200 |
+
base_url: URL | str = "",
|
| 201 |
+
trust_env: bool = True,
|
| 202 |
+
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
| 203 |
+
) -> None:
|
| 204 |
+
event_hooks = {} if event_hooks is None else event_hooks
|
| 205 |
+
|
| 206 |
+
self._base_url = self._enforce_trailing_slash(URL(base_url))
|
| 207 |
+
|
| 208 |
+
self._auth = self._build_auth(auth)
|
| 209 |
+
self._params = QueryParams(params)
|
| 210 |
+
self.headers = Headers(headers)
|
| 211 |
+
self._cookies = Cookies(cookies)
|
| 212 |
+
self._timeout = Timeout(timeout)
|
| 213 |
+
self.follow_redirects = follow_redirects
|
| 214 |
+
self.max_redirects = max_redirects
|
| 215 |
+
self._event_hooks = {
|
| 216 |
+
"request": list(event_hooks.get("request", [])),
|
| 217 |
+
"response": list(event_hooks.get("response", [])),
|
| 218 |
+
}
|
| 219 |
+
self._trust_env = trust_env
|
| 220 |
+
self._default_encoding = default_encoding
|
| 221 |
+
self._state = ClientState.UNOPENED
|
| 222 |
+
|
| 223 |
+
@property
|
| 224 |
+
def is_closed(self) -> bool:
|
| 225 |
+
"""
|
| 226 |
+
Check if the client being closed
|
| 227 |
+
"""
|
| 228 |
+
return self._state == ClientState.CLOSED
|
| 229 |
+
|
| 230 |
+
@property
|
| 231 |
+
def trust_env(self) -> bool:
|
| 232 |
+
return self._trust_env
|
| 233 |
+
|
| 234 |
+
def _enforce_trailing_slash(self, url: URL) -> URL:
|
| 235 |
+
if url.raw_path.endswith(b"/"):
|
| 236 |
+
return url
|
| 237 |
+
return url.copy_with(raw_path=url.raw_path + b"/")
|
| 238 |
+
|
| 239 |
+
def _get_proxy_map(
|
| 240 |
+
self, proxy: ProxyTypes | None, allow_env_proxies: bool
|
| 241 |
+
) -> dict[str, Proxy | None]:
|
| 242 |
+
if proxy is None:
|
| 243 |
+
if allow_env_proxies:
|
| 244 |
+
return {
|
| 245 |
+
key: None if url is None else Proxy(url=url)
|
| 246 |
+
for key, url in get_environment_proxies().items()
|
| 247 |
+
}
|
| 248 |
+
return {}
|
| 249 |
+
else:
|
| 250 |
+
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
| 251 |
+
return {"all://": proxy}
|
| 252 |
+
|
| 253 |
+
@property
|
| 254 |
+
def timeout(self) -> Timeout:
|
| 255 |
+
return self._timeout
|
| 256 |
+
|
| 257 |
+
@timeout.setter
|
| 258 |
+
def timeout(self, timeout: TimeoutTypes) -> None:
|
| 259 |
+
self._timeout = Timeout(timeout)
|
| 260 |
+
|
| 261 |
+
@property
|
| 262 |
+
def event_hooks(self) -> dict[str, list[EventHook]]:
|
| 263 |
+
return self._event_hooks
|
| 264 |
+
|
| 265 |
+
@event_hooks.setter
|
| 266 |
+
def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None:
|
| 267 |
+
self._event_hooks = {
|
| 268 |
+
"request": list(event_hooks.get("request", [])),
|
| 269 |
+
"response": list(event_hooks.get("response", [])),
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
@property
|
| 273 |
+
def auth(self) -> Auth | None:
|
| 274 |
+
"""
|
| 275 |
+
Authentication class used when none is passed at the request-level.
|
| 276 |
+
|
| 277 |
+
See also [Authentication][0].
|
| 278 |
+
|
| 279 |
+
[0]: /quickstart/#authentication
|
| 280 |
+
"""
|
| 281 |
+
return self._auth
|
| 282 |
+
|
| 283 |
+
@auth.setter
|
| 284 |
+
def auth(self, auth: AuthTypes) -> None:
|
| 285 |
+
self._auth = self._build_auth(auth)
|
| 286 |
+
|
| 287 |
+
@property
|
| 288 |
+
def base_url(self) -> URL:
|
| 289 |
+
"""
|
| 290 |
+
Base URL to use when sending requests with relative URLs.
|
| 291 |
+
"""
|
| 292 |
+
return self._base_url
|
| 293 |
+
|
| 294 |
+
@base_url.setter
|
| 295 |
+
def base_url(self, url: URL | str) -> None:
|
| 296 |
+
self._base_url = self._enforce_trailing_slash(URL(url))
|
| 297 |
+
|
| 298 |
+
@property
|
| 299 |
+
def headers(self) -> Headers:
|
| 300 |
+
"""
|
| 301 |
+
HTTP headers to include when sending requests.
|
| 302 |
+
"""
|
| 303 |
+
return self._headers
|
| 304 |
+
|
| 305 |
+
@headers.setter
|
| 306 |
+
def headers(self, headers: HeaderTypes) -> None:
|
| 307 |
+
client_headers = Headers(
|
| 308 |
+
{
|
| 309 |
+
b"Accept": b"*/*",
|
| 310 |
+
b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"),
|
| 311 |
+
b"Connection": b"keep-alive",
|
| 312 |
+
b"User-Agent": USER_AGENT.encode("ascii"),
|
| 313 |
+
}
|
| 314 |
+
)
|
| 315 |
+
client_headers.update(headers)
|
| 316 |
+
self._headers = client_headers
|
| 317 |
+
|
| 318 |
+
@property
|
| 319 |
+
def cookies(self) -> Cookies:
|
| 320 |
+
"""
|
| 321 |
+
Cookie values to include when sending requests.
|
| 322 |
+
"""
|
| 323 |
+
return self._cookies
|
| 324 |
+
|
| 325 |
+
@cookies.setter
|
| 326 |
+
def cookies(self, cookies: CookieTypes) -> None:
|
| 327 |
+
self._cookies = Cookies(cookies)
|
| 328 |
+
|
| 329 |
+
@property
|
| 330 |
+
def params(self) -> QueryParams:
|
| 331 |
+
"""
|
| 332 |
+
Query parameters to include in the URL when sending requests.
|
| 333 |
+
"""
|
| 334 |
+
return self._params
|
| 335 |
+
|
| 336 |
+
@params.setter
|
| 337 |
+
def params(self, params: QueryParamTypes) -> None:
|
| 338 |
+
self._params = QueryParams(params)
|
| 339 |
+
|
| 340 |
+
def build_request(
|
| 341 |
+
self,
|
| 342 |
+
method: str,
|
| 343 |
+
url: URL | str,
|
| 344 |
+
*,
|
| 345 |
+
content: RequestContent | None = None,
|
| 346 |
+
data: RequestData | None = None,
|
| 347 |
+
files: RequestFiles | None = None,
|
| 348 |
+
json: typing.Any | None = None,
|
| 349 |
+
params: QueryParamTypes | None = None,
|
| 350 |
+
headers: HeaderTypes | None = None,
|
| 351 |
+
cookies: CookieTypes | None = None,
|
| 352 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 353 |
+
extensions: RequestExtensions | None = None,
|
| 354 |
+
) -> Request:
|
| 355 |
+
"""
|
| 356 |
+
Build and return a request instance.
|
| 357 |
+
|
| 358 |
+
* The `params`, `headers` and `cookies` arguments
|
| 359 |
+
are merged with any values set on the client.
|
| 360 |
+
* The `url` argument is merged with any `base_url` set on the client.
|
| 361 |
+
|
| 362 |
+
See also: [Request instances][0]
|
| 363 |
+
|
| 364 |
+
[0]: /advanced/clients/#request-instances
|
| 365 |
+
"""
|
| 366 |
+
url = self._merge_url(url)
|
| 367 |
+
headers = self._merge_headers(headers)
|
| 368 |
+
cookies = self._merge_cookies(cookies)
|
| 369 |
+
params = self._merge_queryparams(params)
|
| 370 |
+
extensions = {} if extensions is None else extensions
|
| 371 |
+
if "timeout" not in extensions:
|
| 372 |
+
timeout = (
|
| 373 |
+
self.timeout
|
| 374 |
+
if isinstance(timeout, UseClientDefault)
|
| 375 |
+
else Timeout(timeout)
|
| 376 |
+
)
|
| 377 |
+
extensions = dict(**extensions, timeout=timeout.as_dict())
|
| 378 |
+
return Request(
|
| 379 |
+
method,
|
| 380 |
+
url,
|
| 381 |
+
content=content,
|
| 382 |
+
data=data,
|
| 383 |
+
files=files,
|
| 384 |
+
json=json,
|
| 385 |
+
params=params,
|
| 386 |
+
headers=headers,
|
| 387 |
+
cookies=cookies,
|
| 388 |
+
extensions=extensions,
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
def _merge_url(self, url: URL | str) -> URL:
|
| 392 |
+
"""
|
| 393 |
+
Merge a URL argument together with any 'base_url' on the client,
|
| 394 |
+
to create the URL used for the outgoing request.
|
| 395 |
+
"""
|
| 396 |
+
merge_url = URL(url)
|
| 397 |
+
if merge_url.is_relative_url:
|
| 398 |
+
# To merge URLs we always append to the base URL. To get this
|
| 399 |
+
# behaviour correct we always ensure the base URL ends in a '/'
|
| 400 |
+
# separator, and strip any leading '/' from the merge URL.
|
| 401 |
+
#
|
| 402 |
+
# So, eg...
|
| 403 |
+
#
|
| 404 |
+
# >>> client = Client(base_url="https://www.example.com/subpath")
|
| 405 |
+
# >>> client.base_url
|
| 406 |
+
# URL('https://www.example.com/subpath/')
|
| 407 |
+
# >>> client.build_request("GET", "/path").url
|
| 408 |
+
# URL('https://www.example.com/subpath/path')
|
| 409 |
+
merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
|
| 410 |
+
return self.base_url.copy_with(raw_path=merge_raw_path)
|
| 411 |
+
return merge_url
|
| 412 |
+
|
| 413 |
+
def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None:
|
| 414 |
+
"""
|
| 415 |
+
Merge a cookies argument together with any cookies on the client,
|
| 416 |
+
to create the cookies used for the outgoing request.
|
| 417 |
+
"""
|
| 418 |
+
if cookies or self.cookies:
|
| 419 |
+
merged_cookies = Cookies(self.cookies)
|
| 420 |
+
merged_cookies.update(cookies)
|
| 421 |
+
return merged_cookies
|
| 422 |
+
return cookies
|
| 423 |
+
|
| 424 |
+
def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None:
|
| 425 |
+
"""
|
| 426 |
+
Merge a headers argument together with any headers on the client,
|
| 427 |
+
to create the headers used for the outgoing request.
|
| 428 |
+
"""
|
| 429 |
+
merged_headers = Headers(self.headers)
|
| 430 |
+
merged_headers.update(headers)
|
| 431 |
+
return merged_headers
|
| 432 |
+
|
| 433 |
+
def _merge_queryparams(
|
| 434 |
+
self, params: QueryParamTypes | None = None
|
| 435 |
+
) -> QueryParamTypes | None:
|
| 436 |
+
"""
|
| 437 |
+
Merge a queryparams argument together with any queryparams on the client,
|
| 438 |
+
to create the queryparams used for the outgoing request.
|
| 439 |
+
"""
|
| 440 |
+
if params or self.params:
|
| 441 |
+
merged_queryparams = QueryParams(self.params)
|
| 442 |
+
return merged_queryparams.merge(params)
|
| 443 |
+
return params
|
| 444 |
+
|
| 445 |
+
def _build_auth(self, auth: AuthTypes | None) -> Auth | None:
|
| 446 |
+
if auth is None:
|
| 447 |
+
return None
|
| 448 |
+
elif isinstance(auth, tuple):
|
| 449 |
+
return BasicAuth(username=auth[0], password=auth[1])
|
| 450 |
+
elif isinstance(auth, Auth):
|
| 451 |
+
return auth
|
| 452 |
+
elif callable(auth):
|
| 453 |
+
return FunctionAuth(func=auth)
|
| 454 |
+
else:
|
| 455 |
+
raise TypeError(f'Invalid "auth" argument: {auth!r}')
|
| 456 |
+
|
| 457 |
+
def _build_request_auth(
|
| 458 |
+
self,
|
| 459 |
+
request: Request,
|
| 460 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 461 |
+
) -> Auth:
|
| 462 |
+
auth = (
|
| 463 |
+
self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
if auth is not None:
|
| 467 |
+
return auth
|
| 468 |
+
|
| 469 |
+
username, password = request.url.username, request.url.password
|
| 470 |
+
if username or password:
|
| 471 |
+
return BasicAuth(username=username, password=password)
|
| 472 |
+
|
| 473 |
+
return Auth()
|
| 474 |
+
|
| 475 |
+
def _build_redirect_request(self, request: Request, response: Response) -> Request:
|
| 476 |
+
"""
|
| 477 |
+
Given a request and a redirect response, return a new request that
|
| 478 |
+
should be used to effect the redirect.
|
| 479 |
+
"""
|
| 480 |
+
method = self._redirect_method(request, response)
|
| 481 |
+
url = self._redirect_url(request, response)
|
| 482 |
+
headers = self._redirect_headers(request, url, method)
|
| 483 |
+
stream = self._redirect_stream(request, method)
|
| 484 |
+
cookies = Cookies(self.cookies)
|
| 485 |
+
return Request(
|
| 486 |
+
method=method,
|
| 487 |
+
url=url,
|
| 488 |
+
headers=headers,
|
| 489 |
+
cookies=cookies,
|
| 490 |
+
stream=stream,
|
| 491 |
+
extensions=request.extensions,
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
def _redirect_method(self, request: Request, response: Response) -> str:
|
| 495 |
+
"""
|
| 496 |
+
When being redirected we may want to change the method of the request
|
| 497 |
+
based on certain specs or browser behavior.
|
| 498 |
+
"""
|
| 499 |
+
method = request.method
|
| 500 |
+
|
| 501 |
+
# https://tools.ietf.org/html/rfc7231#section-6.4.4
|
| 502 |
+
if response.status_code == codes.SEE_OTHER and method != "HEAD":
|
| 503 |
+
method = "GET"
|
| 504 |
+
|
| 505 |
+
# Do what the browsers do, despite standards...
|
| 506 |
+
# Turn 302s into GETs.
|
| 507 |
+
if response.status_code == codes.FOUND and method != "HEAD":
|
| 508 |
+
method = "GET"
|
| 509 |
+
|
| 510 |
+
# If a POST is responded to with a 301, turn it into a GET.
|
| 511 |
+
# This bizarre behaviour is explained in 'requests' issue 1704.
|
| 512 |
+
if response.status_code == codes.MOVED_PERMANENTLY and method == "POST":
|
| 513 |
+
method = "GET"
|
| 514 |
+
|
| 515 |
+
return method
|
| 516 |
+
|
| 517 |
+
def _redirect_url(self, request: Request, response: Response) -> URL:
|
| 518 |
+
"""
|
| 519 |
+
Return the URL for the redirect to follow.
|
| 520 |
+
"""
|
| 521 |
+
location = response.headers["Location"]
|
| 522 |
+
|
| 523 |
+
try:
|
| 524 |
+
url = URL(location)
|
| 525 |
+
except InvalidURL as exc:
|
| 526 |
+
raise RemoteProtocolError(
|
| 527 |
+
f"Invalid URL in location header: {exc}.", request=request
|
| 528 |
+
) from None
|
| 529 |
+
|
| 530 |
+
# Handle malformed 'Location' headers that are "absolute" form, have no host.
|
| 531 |
+
# See: https://github.com/encode/httpx/issues/771
|
| 532 |
+
if url.scheme and not url.host:
|
| 533 |
+
url = url.copy_with(host=request.url.host)
|
| 534 |
+
|
| 535 |
+
# Facilitate relative 'Location' headers, as allowed by RFC 7231.
|
| 536 |
+
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
|
| 537 |
+
if url.is_relative_url:
|
| 538 |
+
url = request.url.join(url)
|
| 539 |
+
|
| 540 |
+
# Attach previous fragment if needed (RFC 7231 7.1.2)
|
| 541 |
+
if request.url.fragment and not url.fragment:
|
| 542 |
+
url = url.copy_with(fragment=request.url.fragment)
|
| 543 |
+
|
| 544 |
+
return url
|
| 545 |
+
|
| 546 |
+
def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers:
|
| 547 |
+
"""
|
| 548 |
+
Return the headers that should be used for the redirect request.
|
| 549 |
+
"""
|
| 550 |
+
headers = Headers(request.headers)
|
| 551 |
+
|
| 552 |
+
if not _same_origin(url, request.url):
|
| 553 |
+
if not _is_https_redirect(request.url, url):
|
| 554 |
+
# Strip Authorization headers when responses are redirected
|
| 555 |
+
# away from the origin. (Except for direct HTTP to HTTPS redirects.)
|
| 556 |
+
headers.pop("Authorization", None)
|
| 557 |
+
|
| 558 |
+
# Update the Host header.
|
| 559 |
+
headers["Host"] = url.netloc.decode("ascii")
|
| 560 |
+
|
| 561 |
+
if method != request.method and method == "GET":
|
| 562 |
+
# If we've switch to a 'GET' request, then strip any headers which
|
| 563 |
+
# are only relevant to the request body.
|
| 564 |
+
headers.pop("Content-Length", None)
|
| 565 |
+
headers.pop("Transfer-Encoding", None)
|
| 566 |
+
|
| 567 |
+
# We should use the client cookie store to determine any cookie header,
|
| 568 |
+
# rather than whatever was on the original outgoing request.
|
| 569 |
+
headers.pop("Cookie", None)
|
| 570 |
+
|
| 571 |
+
return headers
|
| 572 |
+
|
| 573 |
+
def _redirect_stream(
|
| 574 |
+
self, request: Request, method: str
|
| 575 |
+
) -> SyncByteStream | AsyncByteStream | None:
|
| 576 |
+
"""
|
| 577 |
+
Return the body that should be used for the redirect request.
|
| 578 |
+
"""
|
| 579 |
+
if method != request.method and method == "GET":
|
| 580 |
+
return None
|
| 581 |
+
|
| 582 |
+
return request.stream
|
| 583 |
+
|
| 584 |
+
def _set_timeout(self, request: Request) -> None:
|
| 585 |
+
if "timeout" not in request.extensions:
|
| 586 |
+
timeout = (
|
| 587 |
+
self.timeout
|
| 588 |
+
if isinstance(self.timeout, UseClientDefault)
|
| 589 |
+
else Timeout(self.timeout)
|
| 590 |
+
)
|
| 591 |
+
request.extensions = dict(**request.extensions, timeout=timeout.as_dict())
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
class Client(BaseClient):
|
| 595 |
+
"""
|
| 596 |
+
An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.
|
| 597 |
+
|
| 598 |
+
It can be shared between threads.
|
| 599 |
+
|
| 600 |
+
Usage:
|
| 601 |
+
|
| 602 |
+
```python
|
| 603 |
+
>>> client = httpx.Client()
|
| 604 |
+
>>> response = client.get('https://example.org')
|
| 605 |
+
```
|
| 606 |
+
|
| 607 |
+
**Parameters:**
|
| 608 |
+
|
| 609 |
+
* **auth** - *(optional)* An authentication class to use when sending
|
| 610 |
+
requests.
|
| 611 |
+
* **params** - *(optional)* Query parameters to include in request URLs, as
|
| 612 |
+
a string, dictionary, or sequence of two-tuples.
|
| 613 |
+
* **headers** - *(optional)* Dictionary of HTTP headers to include when
|
| 614 |
+
sending requests.
|
| 615 |
+
* **cookies** - *(optional)* Dictionary of Cookie items to include when
|
| 616 |
+
sending requests.
|
| 617 |
+
* **verify** - *(optional)* Either `True` to use an SSL context with the
|
| 618 |
+
default CA bundle, `False` to disable verification, or an instance of
|
| 619 |
+
`ssl.SSLContext` to use a custom context.
|
| 620 |
+
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
|
| 621 |
+
enabled. Defaults to `False`.
|
| 622 |
+
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
| 623 |
+
* **timeout** - *(optional)* The timeout configuration to use when sending
|
| 624 |
+
requests.
|
| 625 |
+
* **limits** - *(optional)* The limits configuration to use.
|
| 626 |
+
* **max_redirects** - *(optional)* The maximum number of redirect responses
|
| 627 |
+
that should be followed.
|
| 628 |
+
* **base_url** - *(optional)* A URL to use as the base when building
|
| 629 |
+
request URLs.
|
| 630 |
+
* **transport** - *(optional)* A transport class to use for sending requests
|
| 631 |
+
over the network.
|
| 632 |
+
* **trust_env** - *(optional)* Enables or disables usage of environment
|
| 633 |
+
variables for configuration.
|
| 634 |
+
* **default_encoding** - *(optional)* The default encoding to use for decoding
|
| 635 |
+
response text, if no charset information is included in a response Content-Type
|
| 636 |
+
header. Set to a callable for automatic character set detection. Default: "utf-8".
|
| 637 |
+
"""
|
| 638 |
+
|
| 639 |
+
def __init__(
|
| 640 |
+
self,
|
| 641 |
+
*,
|
| 642 |
+
auth: AuthTypes | None = None,
|
| 643 |
+
params: QueryParamTypes | None = None,
|
| 644 |
+
headers: HeaderTypes | None = None,
|
| 645 |
+
cookies: CookieTypes | None = None,
|
| 646 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 647 |
+
cert: CertTypes | None = None,
|
| 648 |
+
trust_env: bool = True,
|
| 649 |
+
http1: bool = True,
|
| 650 |
+
http2: bool = False,
|
| 651 |
+
proxy: ProxyTypes | None = None,
|
| 652 |
+
mounts: None | (typing.Mapping[str, BaseTransport | None]) = None,
|
| 653 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 654 |
+
follow_redirects: bool = False,
|
| 655 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 656 |
+
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
| 657 |
+
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
| 658 |
+
base_url: URL | str = "",
|
| 659 |
+
transport: BaseTransport | None = None,
|
| 660 |
+
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
| 661 |
+
) -> None:
|
| 662 |
+
super().__init__(
|
| 663 |
+
auth=auth,
|
| 664 |
+
params=params,
|
| 665 |
+
headers=headers,
|
| 666 |
+
cookies=cookies,
|
| 667 |
+
timeout=timeout,
|
| 668 |
+
follow_redirects=follow_redirects,
|
| 669 |
+
max_redirects=max_redirects,
|
| 670 |
+
event_hooks=event_hooks,
|
| 671 |
+
base_url=base_url,
|
| 672 |
+
trust_env=trust_env,
|
| 673 |
+
default_encoding=default_encoding,
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
if http2:
|
| 677 |
+
try:
|
| 678 |
+
import h2 # noqa
|
| 679 |
+
except ImportError: # pragma: no cover
|
| 680 |
+
raise ImportError(
|
| 681 |
+
"Using http2=True, but the 'h2' package is not installed. "
|
| 682 |
+
"Make sure to install httpx using `pip install httpx[http2]`."
|
| 683 |
+
) from None
|
| 684 |
+
|
| 685 |
+
allow_env_proxies = trust_env and transport is None
|
| 686 |
+
proxy_map = self._get_proxy_map(proxy, allow_env_proxies)
|
| 687 |
+
|
| 688 |
+
self._transport = self._init_transport(
|
| 689 |
+
verify=verify,
|
| 690 |
+
cert=cert,
|
| 691 |
+
trust_env=trust_env,
|
| 692 |
+
http1=http1,
|
| 693 |
+
http2=http2,
|
| 694 |
+
limits=limits,
|
| 695 |
+
transport=transport,
|
| 696 |
+
)
|
| 697 |
+
self._mounts: dict[URLPattern, BaseTransport | None] = {
|
| 698 |
+
URLPattern(key): None
|
| 699 |
+
if proxy is None
|
| 700 |
+
else self._init_proxy_transport(
|
| 701 |
+
proxy,
|
| 702 |
+
verify=verify,
|
| 703 |
+
cert=cert,
|
| 704 |
+
trust_env=trust_env,
|
| 705 |
+
http1=http1,
|
| 706 |
+
http2=http2,
|
| 707 |
+
limits=limits,
|
| 708 |
+
)
|
| 709 |
+
for key, proxy in proxy_map.items()
|
| 710 |
+
}
|
| 711 |
+
if mounts is not None:
|
| 712 |
+
self._mounts.update(
|
| 713 |
+
{URLPattern(key): transport for key, transport in mounts.items()}
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
self._mounts = dict(sorted(self._mounts.items()))
|
| 717 |
+
|
| 718 |
+
def _init_transport(
|
| 719 |
+
self,
|
| 720 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 721 |
+
cert: CertTypes | None = None,
|
| 722 |
+
trust_env: bool = True,
|
| 723 |
+
http1: bool = True,
|
| 724 |
+
http2: bool = False,
|
| 725 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 726 |
+
transport: BaseTransport | None = None,
|
| 727 |
+
) -> BaseTransport:
|
| 728 |
+
if transport is not None:
|
| 729 |
+
return transport
|
| 730 |
+
|
| 731 |
+
return HTTPTransport(
|
| 732 |
+
verify=verify,
|
| 733 |
+
cert=cert,
|
| 734 |
+
trust_env=trust_env,
|
| 735 |
+
http1=http1,
|
| 736 |
+
http2=http2,
|
| 737 |
+
limits=limits,
|
| 738 |
+
)
|
| 739 |
+
|
| 740 |
+
def _init_proxy_transport(
|
| 741 |
+
self,
|
| 742 |
+
proxy: Proxy,
|
| 743 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 744 |
+
cert: CertTypes | None = None,
|
| 745 |
+
trust_env: bool = True,
|
| 746 |
+
http1: bool = True,
|
| 747 |
+
http2: bool = False,
|
| 748 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 749 |
+
) -> BaseTransport:
|
| 750 |
+
return HTTPTransport(
|
| 751 |
+
verify=verify,
|
| 752 |
+
cert=cert,
|
| 753 |
+
trust_env=trust_env,
|
| 754 |
+
http1=http1,
|
| 755 |
+
http2=http2,
|
| 756 |
+
limits=limits,
|
| 757 |
+
proxy=proxy,
|
| 758 |
+
)
|
| 759 |
+
|
| 760 |
+
def _transport_for_url(self, url: URL) -> BaseTransport:
|
| 761 |
+
"""
|
| 762 |
+
Returns the transport instance that should be used for a given URL.
|
| 763 |
+
This will either be the standard connection pool, or a proxy.
|
| 764 |
+
"""
|
| 765 |
+
for pattern, transport in self._mounts.items():
|
| 766 |
+
if pattern.matches(url):
|
| 767 |
+
return self._transport if transport is None else transport
|
| 768 |
+
|
| 769 |
+
return self._transport
|
| 770 |
+
|
| 771 |
+
def request(
|
| 772 |
+
self,
|
| 773 |
+
method: str,
|
| 774 |
+
url: URL | str,
|
| 775 |
+
*,
|
| 776 |
+
content: RequestContent | None = None,
|
| 777 |
+
data: RequestData | None = None,
|
| 778 |
+
files: RequestFiles | None = None,
|
| 779 |
+
json: typing.Any | None = None,
|
| 780 |
+
params: QueryParamTypes | None = None,
|
| 781 |
+
headers: HeaderTypes | None = None,
|
| 782 |
+
cookies: CookieTypes | None = None,
|
| 783 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 784 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 785 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 786 |
+
extensions: RequestExtensions | None = None,
|
| 787 |
+
) -> Response:
|
| 788 |
+
"""
|
| 789 |
+
Build and send a request.
|
| 790 |
+
|
| 791 |
+
Equivalent to:
|
| 792 |
+
|
| 793 |
+
```python
|
| 794 |
+
request = client.build_request(...)
|
| 795 |
+
response = client.send(request, ...)
|
| 796 |
+
```
|
| 797 |
+
|
| 798 |
+
See `Client.build_request()`, `Client.send()` and
|
| 799 |
+
[Merging of configuration][0] for how the various parameters
|
| 800 |
+
are merged with client-level configuration.
|
| 801 |
+
|
| 802 |
+
[0]: /advanced/clients/#merging-of-configuration
|
| 803 |
+
"""
|
| 804 |
+
if cookies is not None:
|
| 805 |
+
message = (
|
| 806 |
+
"Setting per-request cookies=<...> is being deprecated, because "
|
| 807 |
+
"the expected behaviour on cookie persistence is ambiguous. Set "
|
| 808 |
+
"cookies directly on the client instance instead."
|
| 809 |
+
)
|
| 810 |
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
| 811 |
+
|
| 812 |
+
request = self.build_request(
|
| 813 |
+
method=method,
|
| 814 |
+
url=url,
|
| 815 |
+
content=content,
|
| 816 |
+
data=data,
|
| 817 |
+
files=files,
|
| 818 |
+
json=json,
|
| 819 |
+
params=params,
|
| 820 |
+
headers=headers,
|
| 821 |
+
cookies=cookies,
|
| 822 |
+
timeout=timeout,
|
| 823 |
+
extensions=extensions,
|
| 824 |
+
)
|
| 825 |
+
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
| 826 |
+
|
| 827 |
+
@contextmanager
|
| 828 |
+
def stream(
|
| 829 |
+
self,
|
| 830 |
+
method: str,
|
| 831 |
+
url: URL | str,
|
| 832 |
+
*,
|
| 833 |
+
content: RequestContent | None = None,
|
| 834 |
+
data: RequestData | None = None,
|
| 835 |
+
files: RequestFiles | None = None,
|
| 836 |
+
json: typing.Any | None = None,
|
| 837 |
+
params: QueryParamTypes | None = None,
|
| 838 |
+
headers: HeaderTypes | None = None,
|
| 839 |
+
cookies: CookieTypes | None = None,
|
| 840 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 841 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 842 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 843 |
+
extensions: RequestExtensions | None = None,
|
| 844 |
+
) -> typing.Iterator[Response]:
|
| 845 |
+
"""
|
| 846 |
+
Alternative to `httpx.request()` that streams the response body
|
| 847 |
+
instead of loading it into memory at once.
|
| 848 |
+
|
| 849 |
+
**Parameters**: See `httpx.request`.
|
| 850 |
+
|
| 851 |
+
See also: [Streaming Responses][0]
|
| 852 |
+
|
| 853 |
+
[0]: /quickstart#streaming-responses
|
| 854 |
+
"""
|
| 855 |
+
request = self.build_request(
|
| 856 |
+
method=method,
|
| 857 |
+
url=url,
|
| 858 |
+
content=content,
|
| 859 |
+
data=data,
|
| 860 |
+
files=files,
|
| 861 |
+
json=json,
|
| 862 |
+
params=params,
|
| 863 |
+
headers=headers,
|
| 864 |
+
cookies=cookies,
|
| 865 |
+
timeout=timeout,
|
| 866 |
+
extensions=extensions,
|
| 867 |
+
)
|
| 868 |
+
response = self.send(
|
| 869 |
+
request=request,
|
| 870 |
+
auth=auth,
|
| 871 |
+
follow_redirects=follow_redirects,
|
| 872 |
+
stream=True,
|
| 873 |
+
)
|
| 874 |
+
try:
|
| 875 |
+
yield response
|
| 876 |
+
finally:
|
| 877 |
+
response.close()
|
| 878 |
+
|
| 879 |
+
def send(
|
| 880 |
+
self,
|
| 881 |
+
request: Request,
|
| 882 |
+
*,
|
| 883 |
+
stream: bool = False,
|
| 884 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 885 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 886 |
+
) -> Response:
|
| 887 |
+
"""
|
| 888 |
+
Send a request.
|
| 889 |
+
|
| 890 |
+
The request is sent as-is, unmodified.
|
| 891 |
+
|
| 892 |
+
Typically you'll want to build one with `Client.build_request()`
|
| 893 |
+
so that any client-level configuration is merged into the request,
|
| 894 |
+
but passing an explicit `httpx.Request()` is supported as well.
|
| 895 |
+
|
| 896 |
+
See also: [Request instances][0]
|
| 897 |
+
|
| 898 |
+
[0]: /advanced/clients/#request-instances
|
| 899 |
+
"""
|
| 900 |
+
if self._state == ClientState.CLOSED:
|
| 901 |
+
raise RuntimeError("Cannot send a request, as the client has been closed.")
|
| 902 |
+
|
| 903 |
+
self._state = ClientState.OPENED
|
| 904 |
+
follow_redirects = (
|
| 905 |
+
self.follow_redirects
|
| 906 |
+
if isinstance(follow_redirects, UseClientDefault)
|
| 907 |
+
else follow_redirects
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
self._set_timeout(request)
|
| 911 |
+
|
| 912 |
+
auth = self._build_request_auth(request, auth)
|
| 913 |
+
|
| 914 |
+
response = self._send_handling_auth(
|
| 915 |
+
request,
|
| 916 |
+
auth=auth,
|
| 917 |
+
follow_redirects=follow_redirects,
|
| 918 |
+
history=[],
|
| 919 |
+
)
|
| 920 |
+
try:
|
| 921 |
+
if not stream:
|
| 922 |
+
response.read()
|
| 923 |
+
|
| 924 |
+
return response
|
| 925 |
+
|
| 926 |
+
except BaseException as exc:
|
| 927 |
+
response.close()
|
| 928 |
+
raise exc
|
| 929 |
+
|
| 930 |
+
def _send_handling_auth(
|
| 931 |
+
self,
|
| 932 |
+
request: Request,
|
| 933 |
+
auth: Auth,
|
| 934 |
+
follow_redirects: bool,
|
| 935 |
+
history: list[Response],
|
| 936 |
+
) -> Response:
|
| 937 |
+
auth_flow = auth.sync_auth_flow(request)
|
| 938 |
+
try:
|
| 939 |
+
request = next(auth_flow)
|
| 940 |
+
|
| 941 |
+
while True:
|
| 942 |
+
response = self._send_handling_redirects(
|
| 943 |
+
request,
|
| 944 |
+
follow_redirects=follow_redirects,
|
| 945 |
+
history=history,
|
| 946 |
+
)
|
| 947 |
+
try:
|
| 948 |
+
try:
|
| 949 |
+
next_request = auth_flow.send(response)
|
| 950 |
+
except StopIteration:
|
| 951 |
+
return response
|
| 952 |
+
|
| 953 |
+
response.history = list(history)
|
| 954 |
+
response.read()
|
| 955 |
+
request = next_request
|
| 956 |
+
history.append(response)
|
| 957 |
+
|
| 958 |
+
except BaseException as exc:
|
| 959 |
+
response.close()
|
| 960 |
+
raise exc
|
| 961 |
+
finally:
|
| 962 |
+
auth_flow.close()
|
| 963 |
+
|
| 964 |
+
def _send_handling_redirects(
|
| 965 |
+
self,
|
| 966 |
+
request: Request,
|
| 967 |
+
follow_redirects: bool,
|
| 968 |
+
history: list[Response],
|
| 969 |
+
) -> Response:
|
| 970 |
+
while True:
|
| 971 |
+
if len(history) > self.max_redirects:
|
| 972 |
+
raise TooManyRedirects(
|
| 973 |
+
"Exceeded maximum allowed redirects.", request=request
|
| 974 |
+
)
|
| 975 |
+
|
| 976 |
+
for hook in self._event_hooks["request"]:
|
| 977 |
+
hook(request)
|
| 978 |
+
|
| 979 |
+
response = self._send_single_request(request)
|
| 980 |
+
try:
|
| 981 |
+
for hook in self._event_hooks["response"]:
|
| 982 |
+
hook(response)
|
| 983 |
+
response.history = list(history)
|
| 984 |
+
|
| 985 |
+
if not response.has_redirect_location:
|
| 986 |
+
return response
|
| 987 |
+
|
| 988 |
+
request = self._build_redirect_request(request, response)
|
| 989 |
+
history = history + [response]
|
| 990 |
+
|
| 991 |
+
if follow_redirects:
|
| 992 |
+
response.read()
|
| 993 |
+
else:
|
| 994 |
+
response.next_request = request
|
| 995 |
+
return response
|
| 996 |
+
|
| 997 |
+
except BaseException as exc:
|
| 998 |
+
response.close()
|
| 999 |
+
raise exc
|
| 1000 |
+
|
| 1001 |
+
def _send_single_request(self, request: Request) -> Response:
|
| 1002 |
+
"""
|
| 1003 |
+
Sends a single request, without handling any redirections.
|
| 1004 |
+
"""
|
| 1005 |
+
transport = self._transport_for_url(request.url)
|
| 1006 |
+
start = time.perf_counter()
|
| 1007 |
+
|
| 1008 |
+
if not isinstance(request.stream, SyncByteStream):
|
| 1009 |
+
raise RuntimeError(
|
| 1010 |
+
"Attempted to send an async request with a sync Client instance."
|
| 1011 |
+
)
|
| 1012 |
+
|
| 1013 |
+
with request_context(request=request):
|
| 1014 |
+
response = transport.handle_request(request)
|
| 1015 |
+
|
| 1016 |
+
assert isinstance(response.stream, SyncByteStream)
|
| 1017 |
+
|
| 1018 |
+
response.request = request
|
| 1019 |
+
response.stream = BoundSyncStream(
|
| 1020 |
+
response.stream, response=response, start=start
|
| 1021 |
+
)
|
| 1022 |
+
self.cookies.extract_cookies(response)
|
| 1023 |
+
response.default_encoding = self._default_encoding
|
| 1024 |
+
|
| 1025 |
+
logger.info(
|
| 1026 |
+
'HTTP Request: %s %s "%s %d %s"',
|
| 1027 |
+
request.method,
|
| 1028 |
+
request.url,
|
| 1029 |
+
response.http_version,
|
| 1030 |
+
response.status_code,
|
| 1031 |
+
response.reason_phrase,
|
| 1032 |
+
)
|
| 1033 |
+
|
| 1034 |
+
return response
|
| 1035 |
+
|
| 1036 |
+
def get(
|
| 1037 |
+
self,
|
| 1038 |
+
url: URL | str,
|
| 1039 |
+
*,
|
| 1040 |
+
params: QueryParamTypes | None = None,
|
| 1041 |
+
headers: HeaderTypes | None = None,
|
| 1042 |
+
cookies: CookieTypes | None = None,
|
| 1043 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 1044 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1045 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1046 |
+
extensions: RequestExtensions | None = None,
|
| 1047 |
+
) -> Response:
|
| 1048 |
+
"""
|
| 1049 |
+
Send a `GET` request.
|
| 1050 |
+
|
| 1051 |
+
**Parameters**: See `httpx.request`.
|
| 1052 |
+
"""
|
| 1053 |
+
return self.request(
|
| 1054 |
+
"GET",
|
| 1055 |
+
url,
|
| 1056 |
+
params=params,
|
| 1057 |
+
headers=headers,
|
| 1058 |
+
cookies=cookies,
|
| 1059 |
+
auth=auth,
|
| 1060 |
+
follow_redirects=follow_redirects,
|
| 1061 |
+
timeout=timeout,
|
| 1062 |
+
extensions=extensions,
|
| 1063 |
+
)
|
| 1064 |
+
|
| 1065 |
+
def options(
|
| 1066 |
+
self,
|
| 1067 |
+
url: URL | str,
|
| 1068 |
+
*,
|
| 1069 |
+
params: QueryParamTypes | None = None,
|
| 1070 |
+
headers: HeaderTypes | None = None,
|
| 1071 |
+
cookies: CookieTypes | None = None,
|
| 1072 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1073 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1074 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1075 |
+
extensions: RequestExtensions | None = None,
|
| 1076 |
+
) -> Response:
|
| 1077 |
+
"""
|
| 1078 |
+
Send an `OPTIONS` request.
|
| 1079 |
+
|
| 1080 |
+
**Parameters**: See `httpx.request`.
|
| 1081 |
+
"""
|
| 1082 |
+
return self.request(
|
| 1083 |
+
"OPTIONS",
|
| 1084 |
+
url,
|
| 1085 |
+
params=params,
|
| 1086 |
+
headers=headers,
|
| 1087 |
+
cookies=cookies,
|
| 1088 |
+
auth=auth,
|
| 1089 |
+
follow_redirects=follow_redirects,
|
| 1090 |
+
timeout=timeout,
|
| 1091 |
+
extensions=extensions,
|
| 1092 |
+
)
|
| 1093 |
+
|
| 1094 |
+
def head(
|
| 1095 |
+
self,
|
| 1096 |
+
url: URL | str,
|
| 1097 |
+
*,
|
| 1098 |
+
params: QueryParamTypes | None = None,
|
| 1099 |
+
headers: HeaderTypes | None = None,
|
| 1100 |
+
cookies: CookieTypes | None = None,
|
| 1101 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1102 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1103 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1104 |
+
extensions: RequestExtensions | None = None,
|
| 1105 |
+
) -> Response:
|
| 1106 |
+
"""
|
| 1107 |
+
Send a `HEAD` request.
|
| 1108 |
+
|
| 1109 |
+
**Parameters**: See `httpx.request`.
|
| 1110 |
+
"""
|
| 1111 |
+
return self.request(
|
| 1112 |
+
"HEAD",
|
| 1113 |
+
url,
|
| 1114 |
+
params=params,
|
| 1115 |
+
headers=headers,
|
| 1116 |
+
cookies=cookies,
|
| 1117 |
+
auth=auth,
|
| 1118 |
+
follow_redirects=follow_redirects,
|
| 1119 |
+
timeout=timeout,
|
| 1120 |
+
extensions=extensions,
|
| 1121 |
+
)
|
| 1122 |
+
|
| 1123 |
+
def post(
|
| 1124 |
+
self,
|
| 1125 |
+
url: URL | str,
|
| 1126 |
+
*,
|
| 1127 |
+
content: RequestContent | None = None,
|
| 1128 |
+
data: RequestData | None = None,
|
| 1129 |
+
files: RequestFiles | None = None,
|
| 1130 |
+
json: typing.Any | None = None,
|
| 1131 |
+
params: QueryParamTypes | None = None,
|
| 1132 |
+
headers: HeaderTypes | None = None,
|
| 1133 |
+
cookies: CookieTypes | None = None,
|
| 1134 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1135 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1136 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1137 |
+
extensions: RequestExtensions | None = None,
|
| 1138 |
+
) -> Response:
|
| 1139 |
+
"""
|
| 1140 |
+
Send a `POST` request.
|
| 1141 |
+
|
| 1142 |
+
**Parameters**: See `httpx.request`.
|
| 1143 |
+
"""
|
| 1144 |
+
return self.request(
|
| 1145 |
+
"POST",
|
| 1146 |
+
url,
|
| 1147 |
+
content=content,
|
| 1148 |
+
data=data,
|
| 1149 |
+
files=files,
|
| 1150 |
+
json=json,
|
| 1151 |
+
params=params,
|
| 1152 |
+
headers=headers,
|
| 1153 |
+
cookies=cookies,
|
| 1154 |
+
auth=auth,
|
| 1155 |
+
follow_redirects=follow_redirects,
|
| 1156 |
+
timeout=timeout,
|
| 1157 |
+
extensions=extensions,
|
| 1158 |
+
)
|
| 1159 |
+
|
| 1160 |
+
def put(
|
| 1161 |
+
self,
|
| 1162 |
+
url: URL | str,
|
| 1163 |
+
*,
|
| 1164 |
+
content: RequestContent | None = None,
|
| 1165 |
+
data: RequestData | None = None,
|
| 1166 |
+
files: RequestFiles | None = None,
|
| 1167 |
+
json: typing.Any | None = None,
|
| 1168 |
+
params: QueryParamTypes | None = None,
|
| 1169 |
+
headers: HeaderTypes | None = None,
|
| 1170 |
+
cookies: CookieTypes | None = None,
|
| 1171 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1172 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1173 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1174 |
+
extensions: RequestExtensions | None = None,
|
| 1175 |
+
) -> Response:
|
| 1176 |
+
"""
|
| 1177 |
+
Send a `PUT` request.
|
| 1178 |
+
|
| 1179 |
+
**Parameters**: See `httpx.request`.
|
| 1180 |
+
"""
|
| 1181 |
+
return self.request(
|
| 1182 |
+
"PUT",
|
| 1183 |
+
url,
|
| 1184 |
+
content=content,
|
| 1185 |
+
data=data,
|
| 1186 |
+
files=files,
|
| 1187 |
+
json=json,
|
| 1188 |
+
params=params,
|
| 1189 |
+
headers=headers,
|
| 1190 |
+
cookies=cookies,
|
| 1191 |
+
auth=auth,
|
| 1192 |
+
follow_redirects=follow_redirects,
|
| 1193 |
+
timeout=timeout,
|
| 1194 |
+
extensions=extensions,
|
| 1195 |
+
)
|
| 1196 |
+
|
| 1197 |
+
def patch(
|
| 1198 |
+
self,
|
| 1199 |
+
url: URL | str,
|
| 1200 |
+
*,
|
| 1201 |
+
content: RequestContent | None = None,
|
| 1202 |
+
data: RequestData | None = None,
|
| 1203 |
+
files: RequestFiles | None = None,
|
| 1204 |
+
json: typing.Any | None = None,
|
| 1205 |
+
params: QueryParamTypes | None = None,
|
| 1206 |
+
headers: HeaderTypes | None = None,
|
| 1207 |
+
cookies: CookieTypes | None = None,
|
| 1208 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1209 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1210 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1211 |
+
extensions: RequestExtensions | None = None,
|
| 1212 |
+
) -> Response:
|
| 1213 |
+
"""
|
| 1214 |
+
Send a `PATCH` request.
|
| 1215 |
+
|
| 1216 |
+
**Parameters**: See `httpx.request`.
|
| 1217 |
+
"""
|
| 1218 |
+
return self.request(
|
| 1219 |
+
"PATCH",
|
| 1220 |
+
url,
|
| 1221 |
+
content=content,
|
| 1222 |
+
data=data,
|
| 1223 |
+
files=files,
|
| 1224 |
+
json=json,
|
| 1225 |
+
params=params,
|
| 1226 |
+
headers=headers,
|
| 1227 |
+
cookies=cookies,
|
| 1228 |
+
auth=auth,
|
| 1229 |
+
follow_redirects=follow_redirects,
|
| 1230 |
+
timeout=timeout,
|
| 1231 |
+
extensions=extensions,
|
| 1232 |
+
)
|
| 1233 |
+
|
| 1234 |
+
def delete(
|
| 1235 |
+
self,
|
| 1236 |
+
url: URL | str,
|
| 1237 |
+
*,
|
| 1238 |
+
params: QueryParamTypes | None = None,
|
| 1239 |
+
headers: HeaderTypes | None = None,
|
| 1240 |
+
cookies: CookieTypes | None = None,
|
| 1241 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1242 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1243 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1244 |
+
extensions: RequestExtensions | None = None,
|
| 1245 |
+
) -> Response:
|
| 1246 |
+
"""
|
| 1247 |
+
Send a `DELETE` request.
|
| 1248 |
+
|
| 1249 |
+
**Parameters**: See `httpx.request`.
|
| 1250 |
+
"""
|
| 1251 |
+
return self.request(
|
| 1252 |
+
"DELETE",
|
| 1253 |
+
url,
|
| 1254 |
+
params=params,
|
| 1255 |
+
headers=headers,
|
| 1256 |
+
cookies=cookies,
|
| 1257 |
+
auth=auth,
|
| 1258 |
+
follow_redirects=follow_redirects,
|
| 1259 |
+
timeout=timeout,
|
| 1260 |
+
extensions=extensions,
|
| 1261 |
+
)
|
| 1262 |
+
|
| 1263 |
+
def close(self) -> None:
|
| 1264 |
+
"""
|
| 1265 |
+
Close transport and proxies.
|
| 1266 |
+
"""
|
| 1267 |
+
if self._state != ClientState.CLOSED:
|
| 1268 |
+
self._state = ClientState.CLOSED
|
| 1269 |
+
|
| 1270 |
+
self._transport.close()
|
| 1271 |
+
for transport in self._mounts.values():
|
| 1272 |
+
if transport is not None:
|
| 1273 |
+
transport.close()
|
| 1274 |
+
|
| 1275 |
+
def __enter__(self: T) -> T:
|
| 1276 |
+
if self._state != ClientState.UNOPENED:
|
| 1277 |
+
msg = {
|
| 1278 |
+
ClientState.OPENED: "Cannot open a client instance more than once.",
|
| 1279 |
+
ClientState.CLOSED: (
|
| 1280 |
+
"Cannot reopen a client instance, once it has been closed."
|
| 1281 |
+
),
|
| 1282 |
+
}[self._state]
|
| 1283 |
+
raise RuntimeError(msg)
|
| 1284 |
+
|
| 1285 |
+
self._state = ClientState.OPENED
|
| 1286 |
+
|
| 1287 |
+
self._transport.__enter__()
|
| 1288 |
+
for transport in self._mounts.values():
|
| 1289 |
+
if transport is not None:
|
| 1290 |
+
transport.__enter__()
|
| 1291 |
+
return self
|
| 1292 |
+
|
| 1293 |
+
def __exit__(
|
| 1294 |
+
self,
|
| 1295 |
+
exc_type: type[BaseException] | None = None,
|
| 1296 |
+
exc_value: BaseException | None = None,
|
| 1297 |
+
traceback: TracebackType | None = None,
|
| 1298 |
+
) -> None:
|
| 1299 |
+
self._state = ClientState.CLOSED
|
| 1300 |
+
|
| 1301 |
+
self._transport.__exit__(exc_type, exc_value, traceback)
|
| 1302 |
+
for transport in self._mounts.values():
|
| 1303 |
+
if transport is not None:
|
| 1304 |
+
transport.__exit__(exc_type, exc_value, traceback)
|
| 1305 |
+
|
| 1306 |
+
|
| 1307 |
+
class AsyncClient(BaseClient):
|
| 1308 |
+
"""
|
| 1309 |
+
An asynchronous HTTP client, with connection pooling, HTTP/2, redirects,
|
| 1310 |
+
cookie persistence, etc.
|
| 1311 |
+
|
| 1312 |
+
It can be shared between tasks.
|
| 1313 |
+
|
| 1314 |
+
Usage:
|
| 1315 |
+
|
| 1316 |
+
```python
|
| 1317 |
+
>>> async with httpx.AsyncClient() as client:
|
| 1318 |
+
>>> response = await client.get('https://example.org')
|
| 1319 |
+
```
|
| 1320 |
+
|
| 1321 |
+
**Parameters:**
|
| 1322 |
+
|
| 1323 |
+
* **auth** - *(optional)* An authentication class to use when sending
|
| 1324 |
+
requests.
|
| 1325 |
+
* **params** - *(optional)* Query parameters to include in request URLs, as
|
| 1326 |
+
a string, dictionary, or sequence of two-tuples.
|
| 1327 |
+
* **headers** - *(optional)* Dictionary of HTTP headers to include when
|
| 1328 |
+
sending requests.
|
| 1329 |
+
* **cookies** - *(optional)* Dictionary of Cookie items to include when
|
| 1330 |
+
sending requests.
|
| 1331 |
+
* **verify** - *(optional)* Either `True` to use an SSL context with the
|
| 1332 |
+
default CA bundle, `False` to disable verification, or an instance of
|
| 1333 |
+
`ssl.SSLContext` to use a custom context.
|
| 1334 |
+
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
|
| 1335 |
+
enabled. Defaults to `False`.
|
| 1336 |
+
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
| 1337 |
+
* **timeout** - *(optional)* The timeout configuration to use when sending
|
| 1338 |
+
requests.
|
| 1339 |
+
* **limits** - *(optional)* The limits configuration to use.
|
| 1340 |
+
* **max_redirects** - *(optional)* The maximum number of redirect responses
|
| 1341 |
+
that should be followed.
|
| 1342 |
+
* **base_url** - *(optional)* A URL to use as the base when building
|
| 1343 |
+
request URLs.
|
| 1344 |
+
* **transport** - *(optional)* A transport class to use for sending requests
|
| 1345 |
+
over the network.
|
| 1346 |
+
* **trust_env** - *(optional)* Enables or disables usage of environment
|
| 1347 |
+
variables for configuration.
|
| 1348 |
+
* **default_encoding** - *(optional)* The default encoding to use for decoding
|
| 1349 |
+
response text, if no charset information is included in a response Content-Type
|
| 1350 |
+
header. Set to a callable for automatic character set detection. Default: "utf-8".
|
| 1351 |
+
"""
|
| 1352 |
+
|
| 1353 |
+
def __init__(
|
| 1354 |
+
self,
|
| 1355 |
+
*,
|
| 1356 |
+
auth: AuthTypes | None = None,
|
| 1357 |
+
params: QueryParamTypes | None = None,
|
| 1358 |
+
headers: HeaderTypes | None = None,
|
| 1359 |
+
cookies: CookieTypes | None = None,
|
| 1360 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 1361 |
+
cert: CertTypes | None = None,
|
| 1362 |
+
http1: bool = True,
|
| 1363 |
+
http2: bool = False,
|
| 1364 |
+
proxy: ProxyTypes | None = None,
|
| 1365 |
+
mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None,
|
| 1366 |
+
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
| 1367 |
+
follow_redirects: bool = False,
|
| 1368 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 1369 |
+
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
| 1370 |
+
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
| 1371 |
+
base_url: URL | str = "",
|
| 1372 |
+
transport: AsyncBaseTransport | None = None,
|
| 1373 |
+
trust_env: bool = True,
|
| 1374 |
+
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
| 1375 |
+
) -> None:
|
| 1376 |
+
super().__init__(
|
| 1377 |
+
auth=auth,
|
| 1378 |
+
params=params,
|
| 1379 |
+
headers=headers,
|
| 1380 |
+
cookies=cookies,
|
| 1381 |
+
timeout=timeout,
|
| 1382 |
+
follow_redirects=follow_redirects,
|
| 1383 |
+
max_redirects=max_redirects,
|
| 1384 |
+
event_hooks=event_hooks,
|
| 1385 |
+
base_url=base_url,
|
| 1386 |
+
trust_env=trust_env,
|
| 1387 |
+
default_encoding=default_encoding,
|
| 1388 |
+
)
|
| 1389 |
+
|
| 1390 |
+
if http2:
|
| 1391 |
+
try:
|
| 1392 |
+
import h2 # noqa
|
| 1393 |
+
except ImportError: # pragma: no cover
|
| 1394 |
+
raise ImportError(
|
| 1395 |
+
"Using http2=True, but the 'h2' package is not installed. "
|
| 1396 |
+
"Make sure to install httpx using `pip install httpx[http2]`."
|
| 1397 |
+
) from None
|
| 1398 |
+
|
| 1399 |
+
allow_env_proxies = trust_env and transport is None
|
| 1400 |
+
proxy_map = self._get_proxy_map(proxy, allow_env_proxies)
|
| 1401 |
+
|
| 1402 |
+
self._transport = self._init_transport(
|
| 1403 |
+
verify=verify,
|
| 1404 |
+
cert=cert,
|
| 1405 |
+
trust_env=trust_env,
|
| 1406 |
+
http1=http1,
|
| 1407 |
+
http2=http2,
|
| 1408 |
+
limits=limits,
|
| 1409 |
+
transport=transport,
|
| 1410 |
+
)
|
| 1411 |
+
|
| 1412 |
+
self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {
|
| 1413 |
+
URLPattern(key): None
|
| 1414 |
+
if proxy is None
|
| 1415 |
+
else self._init_proxy_transport(
|
| 1416 |
+
proxy,
|
| 1417 |
+
verify=verify,
|
| 1418 |
+
cert=cert,
|
| 1419 |
+
trust_env=trust_env,
|
| 1420 |
+
http1=http1,
|
| 1421 |
+
http2=http2,
|
| 1422 |
+
limits=limits,
|
| 1423 |
+
)
|
| 1424 |
+
for key, proxy in proxy_map.items()
|
| 1425 |
+
}
|
| 1426 |
+
if mounts is not None:
|
| 1427 |
+
self._mounts.update(
|
| 1428 |
+
{URLPattern(key): transport for key, transport in mounts.items()}
|
| 1429 |
+
)
|
| 1430 |
+
self._mounts = dict(sorted(self._mounts.items()))
|
| 1431 |
+
|
| 1432 |
+
def _init_transport(
|
| 1433 |
+
self,
|
| 1434 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 1435 |
+
cert: CertTypes | None = None,
|
| 1436 |
+
trust_env: bool = True,
|
| 1437 |
+
http1: bool = True,
|
| 1438 |
+
http2: bool = False,
|
| 1439 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 1440 |
+
transport: AsyncBaseTransport | None = None,
|
| 1441 |
+
) -> AsyncBaseTransport:
|
| 1442 |
+
if transport is not None:
|
| 1443 |
+
return transport
|
| 1444 |
+
|
| 1445 |
+
return AsyncHTTPTransport(
|
| 1446 |
+
verify=verify,
|
| 1447 |
+
cert=cert,
|
| 1448 |
+
trust_env=trust_env,
|
| 1449 |
+
http1=http1,
|
| 1450 |
+
http2=http2,
|
| 1451 |
+
limits=limits,
|
| 1452 |
+
)
|
| 1453 |
+
|
| 1454 |
+
def _init_proxy_transport(
|
| 1455 |
+
self,
|
| 1456 |
+
proxy: Proxy,
|
| 1457 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 1458 |
+
cert: CertTypes | None = None,
|
| 1459 |
+
trust_env: bool = True,
|
| 1460 |
+
http1: bool = True,
|
| 1461 |
+
http2: bool = False,
|
| 1462 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 1463 |
+
) -> AsyncBaseTransport:
|
| 1464 |
+
return AsyncHTTPTransport(
|
| 1465 |
+
verify=verify,
|
| 1466 |
+
cert=cert,
|
| 1467 |
+
trust_env=trust_env,
|
| 1468 |
+
http1=http1,
|
| 1469 |
+
http2=http2,
|
| 1470 |
+
limits=limits,
|
| 1471 |
+
proxy=proxy,
|
| 1472 |
+
)
|
| 1473 |
+
|
| 1474 |
+
def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
|
| 1475 |
+
"""
|
| 1476 |
+
Returns the transport instance that should be used for a given URL.
|
| 1477 |
+
This will either be the standard connection pool, or a proxy.
|
| 1478 |
+
"""
|
| 1479 |
+
for pattern, transport in self._mounts.items():
|
| 1480 |
+
if pattern.matches(url):
|
| 1481 |
+
return self._transport if transport is None else transport
|
| 1482 |
+
|
| 1483 |
+
return self._transport
|
| 1484 |
+
|
| 1485 |
+
async def request(
|
| 1486 |
+
self,
|
| 1487 |
+
method: str,
|
| 1488 |
+
url: URL | str,
|
| 1489 |
+
*,
|
| 1490 |
+
content: RequestContent | None = None,
|
| 1491 |
+
data: RequestData | None = None,
|
| 1492 |
+
files: RequestFiles | None = None,
|
| 1493 |
+
json: typing.Any | None = None,
|
| 1494 |
+
params: QueryParamTypes | None = None,
|
| 1495 |
+
headers: HeaderTypes | None = None,
|
| 1496 |
+
cookies: CookieTypes | None = None,
|
| 1497 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 1498 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1499 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1500 |
+
extensions: RequestExtensions | None = None,
|
| 1501 |
+
) -> Response:
|
| 1502 |
+
"""
|
| 1503 |
+
Build and send a request.
|
| 1504 |
+
|
| 1505 |
+
Equivalent to:
|
| 1506 |
+
|
| 1507 |
+
```python
|
| 1508 |
+
request = client.build_request(...)
|
| 1509 |
+
response = await client.send(request, ...)
|
| 1510 |
+
```
|
| 1511 |
+
|
| 1512 |
+
See `AsyncClient.build_request()`, `AsyncClient.send()`
|
| 1513 |
+
and [Merging of configuration][0] for how the various parameters
|
| 1514 |
+
are merged with client-level configuration.
|
| 1515 |
+
|
| 1516 |
+
[0]: /advanced/clients/#merging-of-configuration
|
| 1517 |
+
"""
|
| 1518 |
+
|
| 1519 |
+
if cookies is not None: # pragma: no cover
|
| 1520 |
+
message = (
|
| 1521 |
+
"Setting per-request cookies=<...> is being deprecated, because "
|
| 1522 |
+
"the expected behaviour on cookie persistence is ambiguous. Set "
|
| 1523 |
+
"cookies directly on the client instance instead."
|
| 1524 |
+
)
|
| 1525 |
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
| 1526 |
+
|
| 1527 |
+
request = self.build_request(
|
| 1528 |
+
method=method,
|
| 1529 |
+
url=url,
|
| 1530 |
+
content=content,
|
| 1531 |
+
data=data,
|
| 1532 |
+
files=files,
|
| 1533 |
+
json=json,
|
| 1534 |
+
params=params,
|
| 1535 |
+
headers=headers,
|
| 1536 |
+
cookies=cookies,
|
| 1537 |
+
timeout=timeout,
|
| 1538 |
+
extensions=extensions,
|
| 1539 |
+
)
|
| 1540 |
+
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
|
| 1541 |
+
|
| 1542 |
+
@asynccontextmanager
|
| 1543 |
+
async def stream(
|
| 1544 |
+
self,
|
| 1545 |
+
method: str,
|
| 1546 |
+
url: URL | str,
|
| 1547 |
+
*,
|
| 1548 |
+
content: RequestContent | None = None,
|
| 1549 |
+
data: RequestData | None = None,
|
| 1550 |
+
files: RequestFiles | None = None,
|
| 1551 |
+
json: typing.Any | None = None,
|
| 1552 |
+
params: QueryParamTypes | None = None,
|
| 1553 |
+
headers: HeaderTypes | None = None,
|
| 1554 |
+
cookies: CookieTypes | None = None,
|
| 1555 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 1556 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1557 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1558 |
+
extensions: RequestExtensions | None = None,
|
| 1559 |
+
) -> typing.AsyncIterator[Response]:
|
| 1560 |
+
"""
|
| 1561 |
+
Alternative to `httpx.request()` that streams the response body
|
| 1562 |
+
instead of loading it into memory at once.
|
| 1563 |
+
|
| 1564 |
+
**Parameters**: See `httpx.request`.
|
| 1565 |
+
|
| 1566 |
+
See also: [Streaming Responses][0]
|
| 1567 |
+
|
| 1568 |
+
[0]: /quickstart#streaming-responses
|
| 1569 |
+
"""
|
| 1570 |
+
request = self.build_request(
|
| 1571 |
+
method=method,
|
| 1572 |
+
url=url,
|
| 1573 |
+
content=content,
|
| 1574 |
+
data=data,
|
| 1575 |
+
files=files,
|
| 1576 |
+
json=json,
|
| 1577 |
+
params=params,
|
| 1578 |
+
headers=headers,
|
| 1579 |
+
cookies=cookies,
|
| 1580 |
+
timeout=timeout,
|
| 1581 |
+
extensions=extensions,
|
| 1582 |
+
)
|
| 1583 |
+
response = await self.send(
|
| 1584 |
+
request=request,
|
| 1585 |
+
auth=auth,
|
| 1586 |
+
follow_redirects=follow_redirects,
|
| 1587 |
+
stream=True,
|
| 1588 |
+
)
|
| 1589 |
+
try:
|
| 1590 |
+
yield response
|
| 1591 |
+
finally:
|
| 1592 |
+
await response.aclose()
|
| 1593 |
+
|
| 1594 |
+
async def send(
|
| 1595 |
+
self,
|
| 1596 |
+
request: Request,
|
| 1597 |
+
*,
|
| 1598 |
+
stream: bool = False,
|
| 1599 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 1600 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1601 |
+
) -> Response:
|
| 1602 |
+
"""
|
| 1603 |
+
Send a request.
|
| 1604 |
+
|
| 1605 |
+
The request is sent as-is, unmodified.
|
| 1606 |
+
|
| 1607 |
+
Typically you'll want to build one with `AsyncClient.build_request()`
|
| 1608 |
+
so that any client-level configuration is merged into the request,
|
| 1609 |
+
but passing an explicit `httpx.Request()` is supported as well.
|
| 1610 |
+
|
| 1611 |
+
See also: [Request instances][0]
|
| 1612 |
+
|
| 1613 |
+
[0]: /advanced/clients/#request-instances
|
| 1614 |
+
"""
|
| 1615 |
+
if self._state == ClientState.CLOSED:
|
| 1616 |
+
raise RuntimeError("Cannot send a request, as the client has been closed.")
|
| 1617 |
+
|
| 1618 |
+
self._state = ClientState.OPENED
|
| 1619 |
+
follow_redirects = (
|
| 1620 |
+
self.follow_redirects
|
| 1621 |
+
if isinstance(follow_redirects, UseClientDefault)
|
| 1622 |
+
else follow_redirects
|
| 1623 |
+
)
|
| 1624 |
+
|
| 1625 |
+
self._set_timeout(request)
|
| 1626 |
+
|
| 1627 |
+
auth = self._build_request_auth(request, auth)
|
| 1628 |
+
|
| 1629 |
+
response = await self._send_handling_auth(
|
| 1630 |
+
request,
|
| 1631 |
+
auth=auth,
|
| 1632 |
+
follow_redirects=follow_redirects,
|
| 1633 |
+
history=[],
|
| 1634 |
+
)
|
| 1635 |
+
try:
|
| 1636 |
+
if not stream:
|
| 1637 |
+
await response.aread()
|
| 1638 |
+
|
| 1639 |
+
return response
|
| 1640 |
+
|
| 1641 |
+
except BaseException as exc:
|
| 1642 |
+
await response.aclose()
|
| 1643 |
+
raise exc
|
| 1644 |
+
|
| 1645 |
+
async def _send_handling_auth(
|
| 1646 |
+
self,
|
| 1647 |
+
request: Request,
|
| 1648 |
+
auth: Auth,
|
| 1649 |
+
follow_redirects: bool,
|
| 1650 |
+
history: list[Response],
|
| 1651 |
+
) -> Response:
|
| 1652 |
+
auth_flow = auth.async_auth_flow(request)
|
| 1653 |
+
try:
|
| 1654 |
+
request = await auth_flow.__anext__()
|
| 1655 |
+
|
| 1656 |
+
while True:
|
| 1657 |
+
response = await self._send_handling_redirects(
|
| 1658 |
+
request,
|
| 1659 |
+
follow_redirects=follow_redirects,
|
| 1660 |
+
history=history,
|
| 1661 |
+
)
|
| 1662 |
+
try:
|
| 1663 |
+
try:
|
| 1664 |
+
next_request = await auth_flow.asend(response)
|
| 1665 |
+
except StopAsyncIteration:
|
| 1666 |
+
return response
|
| 1667 |
+
|
| 1668 |
+
response.history = list(history)
|
| 1669 |
+
await response.aread()
|
| 1670 |
+
request = next_request
|
| 1671 |
+
history.append(response)
|
| 1672 |
+
|
| 1673 |
+
except BaseException as exc:
|
| 1674 |
+
await response.aclose()
|
| 1675 |
+
raise exc
|
| 1676 |
+
finally:
|
| 1677 |
+
await auth_flow.aclose()
|
| 1678 |
+
|
| 1679 |
+
async def _send_handling_redirects(
|
| 1680 |
+
self,
|
| 1681 |
+
request: Request,
|
| 1682 |
+
follow_redirects: bool,
|
| 1683 |
+
history: list[Response],
|
| 1684 |
+
) -> Response:
|
| 1685 |
+
while True:
|
| 1686 |
+
if len(history) > self.max_redirects:
|
| 1687 |
+
raise TooManyRedirects(
|
| 1688 |
+
"Exceeded maximum allowed redirects.", request=request
|
| 1689 |
+
)
|
| 1690 |
+
|
| 1691 |
+
for hook in self._event_hooks["request"]:
|
| 1692 |
+
await hook(request)
|
| 1693 |
+
|
| 1694 |
+
response = await self._send_single_request(request)
|
| 1695 |
+
try:
|
| 1696 |
+
for hook in self._event_hooks["response"]:
|
| 1697 |
+
await hook(response)
|
| 1698 |
+
|
| 1699 |
+
response.history = list(history)
|
| 1700 |
+
|
| 1701 |
+
if not response.has_redirect_location:
|
| 1702 |
+
return response
|
| 1703 |
+
|
| 1704 |
+
request = self._build_redirect_request(request, response)
|
| 1705 |
+
history = history + [response]
|
| 1706 |
+
|
| 1707 |
+
if follow_redirects:
|
| 1708 |
+
await response.aread()
|
| 1709 |
+
else:
|
| 1710 |
+
response.next_request = request
|
| 1711 |
+
return response
|
| 1712 |
+
|
| 1713 |
+
except BaseException as exc:
|
| 1714 |
+
await response.aclose()
|
| 1715 |
+
raise exc
|
| 1716 |
+
|
| 1717 |
+
async def _send_single_request(self, request: Request) -> Response:
|
| 1718 |
+
"""
|
| 1719 |
+
Sends a single request, without handling any redirections.
|
| 1720 |
+
"""
|
| 1721 |
+
transport = self._transport_for_url(request.url)
|
| 1722 |
+
start = time.perf_counter()
|
| 1723 |
+
|
| 1724 |
+
if not isinstance(request.stream, AsyncByteStream):
|
| 1725 |
+
raise RuntimeError(
|
| 1726 |
+
"Attempted to send an sync request with an AsyncClient instance."
|
| 1727 |
+
)
|
| 1728 |
+
|
| 1729 |
+
with request_context(request=request):
|
| 1730 |
+
response = await transport.handle_async_request(request)
|
| 1731 |
+
|
| 1732 |
+
assert isinstance(response.stream, AsyncByteStream)
|
| 1733 |
+
response.request = request
|
| 1734 |
+
response.stream = BoundAsyncStream(
|
| 1735 |
+
response.stream, response=response, start=start
|
| 1736 |
+
)
|
| 1737 |
+
self.cookies.extract_cookies(response)
|
| 1738 |
+
response.default_encoding = self._default_encoding
|
| 1739 |
+
|
| 1740 |
+
logger.info(
|
| 1741 |
+
'HTTP Request: %s %s "%s %d %s"',
|
| 1742 |
+
request.method,
|
| 1743 |
+
request.url,
|
| 1744 |
+
response.http_version,
|
| 1745 |
+
response.status_code,
|
| 1746 |
+
response.reason_phrase,
|
| 1747 |
+
)
|
| 1748 |
+
|
| 1749 |
+
return response
|
| 1750 |
+
|
| 1751 |
+
async def get(
|
| 1752 |
+
self,
|
| 1753 |
+
url: URL | str,
|
| 1754 |
+
*,
|
| 1755 |
+
params: QueryParamTypes | None = None,
|
| 1756 |
+
headers: HeaderTypes | None = None,
|
| 1757 |
+
cookies: CookieTypes | None = None,
|
| 1758 |
+
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
|
| 1759 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1760 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1761 |
+
extensions: RequestExtensions | None = None,
|
| 1762 |
+
) -> Response:
|
| 1763 |
+
"""
|
| 1764 |
+
Send a `GET` request.
|
| 1765 |
+
|
| 1766 |
+
**Parameters**: See `httpx.request`.
|
| 1767 |
+
"""
|
| 1768 |
+
return await self.request(
|
| 1769 |
+
"GET",
|
| 1770 |
+
url,
|
| 1771 |
+
params=params,
|
| 1772 |
+
headers=headers,
|
| 1773 |
+
cookies=cookies,
|
| 1774 |
+
auth=auth,
|
| 1775 |
+
follow_redirects=follow_redirects,
|
| 1776 |
+
timeout=timeout,
|
| 1777 |
+
extensions=extensions,
|
| 1778 |
+
)
|
| 1779 |
+
|
| 1780 |
+
async def options(
|
| 1781 |
+
self,
|
| 1782 |
+
url: URL | str,
|
| 1783 |
+
*,
|
| 1784 |
+
params: QueryParamTypes | None = None,
|
| 1785 |
+
headers: HeaderTypes | None = None,
|
| 1786 |
+
cookies: CookieTypes | None = None,
|
| 1787 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1788 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1789 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1790 |
+
extensions: RequestExtensions | None = None,
|
| 1791 |
+
) -> Response:
|
| 1792 |
+
"""
|
| 1793 |
+
Send an `OPTIONS` request.
|
| 1794 |
+
|
| 1795 |
+
**Parameters**: See `httpx.request`.
|
| 1796 |
+
"""
|
| 1797 |
+
return await self.request(
|
| 1798 |
+
"OPTIONS",
|
| 1799 |
+
url,
|
| 1800 |
+
params=params,
|
| 1801 |
+
headers=headers,
|
| 1802 |
+
cookies=cookies,
|
| 1803 |
+
auth=auth,
|
| 1804 |
+
follow_redirects=follow_redirects,
|
| 1805 |
+
timeout=timeout,
|
| 1806 |
+
extensions=extensions,
|
| 1807 |
+
)
|
| 1808 |
+
|
| 1809 |
+
async def head(
|
| 1810 |
+
self,
|
| 1811 |
+
url: URL | str,
|
| 1812 |
+
*,
|
| 1813 |
+
params: QueryParamTypes | None = None,
|
| 1814 |
+
headers: HeaderTypes | None = None,
|
| 1815 |
+
cookies: CookieTypes | None = None,
|
| 1816 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1817 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1818 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1819 |
+
extensions: RequestExtensions | None = None,
|
| 1820 |
+
) -> Response:
|
| 1821 |
+
"""
|
| 1822 |
+
Send a `HEAD` request.
|
| 1823 |
+
|
| 1824 |
+
**Parameters**: See `httpx.request`.
|
| 1825 |
+
"""
|
| 1826 |
+
return await self.request(
|
| 1827 |
+
"HEAD",
|
| 1828 |
+
url,
|
| 1829 |
+
params=params,
|
| 1830 |
+
headers=headers,
|
| 1831 |
+
cookies=cookies,
|
| 1832 |
+
auth=auth,
|
| 1833 |
+
follow_redirects=follow_redirects,
|
| 1834 |
+
timeout=timeout,
|
| 1835 |
+
extensions=extensions,
|
| 1836 |
+
)
|
| 1837 |
+
|
| 1838 |
+
async def post(
|
| 1839 |
+
self,
|
| 1840 |
+
url: URL | str,
|
| 1841 |
+
*,
|
| 1842 |
+
content: RequestContent | None = None,
|
| 1843 |
+
data: RequestData | None = None,
|
| 1844 |
+
files: RequestFiles | None = None,
|
| 1845 |
+
json: typing.Any | None = None,
|
| 1846 |
+
params: QueryParamTypes | None = None,
|
| 1847 |
+
headers: HeaderTypes | None = None,
|
| 1848 |
+
cookies: CookieTypes | None = None,
|
| 1849 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1850 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1851 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1852 |
+
extensions: RequestExtensions | None = None,
|
| 1853 |
+
) -> Response:
|
| 1854 |
+
"""
|
| 1855 |
+
Send a `POST` request.
|
| 1856 |
+
|
| 1857 |
+
**Parameters**: See `httpx.request`.
|
| 1858 |
+
"""
|
| 1859 |
+
return await self.request(
|
| 1860 |
+
"POST",
|
| 1861 |
+
url,
|
| 1862 |
+
content=content,
|
| 1863 |
+
data=data,
|
| 1864 |
+
files=files,
|
| 1865 |
+
json=json,
|
| 1866 |
+
params=params,
|
| 1867 |
+
headers=headers,
|
| 1868 |
+
cookies=cookies,
|
| 1869 |
+
auth=auth,
|
| 1870 |
+
follow_redirects=follow_redirects,
|
| 1871 |
+
timeout=timeout,
|
| 1872 |
+
extensions=extensions,
|
| 1873 |
+
)
|
| 1874 |
+
|
| 1875 |
+
async def put(
|
| 1876 |
+
self,
|
| 1877 |
+
url: URL | str,
|
| 1878 |
+
*,
|
| 1879 |
+
content: RequestContent | None = None,
|
| 1880 |
+
data: RequestData | None = None,
|
| 1881 |
+
files: RequestFiles | None = None,
|
| 1882 |
+
json: typing.Any | None = None,
|
| 1883 |
+
params: QueryParamTypes | None = None,
|
| 1884 |
+
headers: HeaderTypes | None = None,
|
| 1885 |
+
cookies: CookieTypes | None = None,
|
| 1886 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1887 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1888 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1889 |
+
extensions: RequestExtensions | None = None,
|
| 1890 |
+
) -> Response:
|
| 1891 |
+
"""
|
| 1892 |
+
Send a `PUT` request.
|
| 1893 |
+
|
| 1894 |
+
**Parameters**: See `httpx.request`.
|
| 1895 |
+
"""
|
| 1896 |
+
return await self.request(
|
| 1897 |
+
"PUT",
|
| 1898 |
+
url,
|
| 1899 |
+
content=content,
|
| 1900 |
+
data=data,
|
| 1901 |
+
files=files,
|
| 1902 |
+
json=json,
|
| 1903 |
+
params=params,
|
| 1904 |
+
headers=headers,
|
| 1905 |
+
cookies=cookies,
|
| 1906 |
+
auth=auth,
|
| 1907 |
+
follow_redirects=follow_redirects,
|
| 1908 |
+
timeout=timeout,
|
| 1909 |
+
extensions=extensions,
|
| 1910 |
+
)
|
| 1911 |
+
|
| 1912 |
+
async def patch(
|
| 1913 |
+
self,
|
| 1914 |
+
url: URL | str,
|
| 1915 |
+
*,
|
| 1916 |
+
content: RequestContent | None = None,
|
| 1917 |
+
data: RequestData | None = None,
|
| 1918 |
+
files: RequestFiles | None = None,
|
| 1919 |
+
json: typing.Any | None = None,
|
| 1920 |
+
params: QueryParamTypes | None = None,
|
| 1921 |
+
headers: HeaderTypes | None = None,
|
| 1922 |
+
cookies: CookieTypes | None = None,
|
| 1923 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1924 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1925 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1926 |
+
extensions: RequestExtensions | None = None,
|
| 1927 |
+
) -> Response:
|
| 1928 |
+
"""
|
| 1929 |
+
Send a `PATCH` request.
|
| 1930 |
+
|
| 1931 |
+
**Parameters**: See `httpx.request`.
|
| 1932 |
+
"""
|
| 1933 |
+
return await self.request(
|
| 1934 |
+
"PATCH",
|
| 1935 |
+
url,
|
| 1936 |
+
content=content,
|
| 1937 |
+
data=data,
|
| 1938 |
+
files=files,
|
| 1939 |
+
json=json,
|
| 1940 |
+
params=params,
|
| 1941 |
+
headers=headers,
|
| 1942 |
+
cookies=cookies,
|
| 1943 |
+
auth=auth,
|
| 1944 |
+
follow_redirects=follow_redirects,
|
| 1945 |
+
timeout=timeout,
|
| 1946 |
+
extensions=extensions,
|
| 1947 |
+
)
|
| 1948 |
+
|
| 1949 |
+
async def delete(
|
| 1950 |
+
self,
|
| 1951 |
+
url: URL | str,
|
| 1952 |
+
*,
|
| 1953 |
+
params: QueryParamTypes | None = None,
|
| 1954 |
+
headers: HeaderTypes | None = None,
|
| 1955 |
+
cookies: CookieTypes | None = None,
|
| 1956 |
+
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1957 |
+
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1958 |
+
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
|
| 1959 |
+
extensions: RequestExtensions | None = None,
|
| 1960 |
+
) -> Response:
|
| 1961 |
+
"""
|
| 1962 |
+
Send a `DELETE` request.
|
| 1963 |
+
|
| 1964 |
+
**Parameters**: See `httpx.request`.
|
| 1965 |
+
"""
|
| 1966 |
+
return await self.request(
|
| 1967 |
+
"DELETE",
|
| 1968 |
+
url,
|
| 1969 |
+
params=params,
|
| 1970 |
+
headers=headers,
|
| 1971 |
+
cookies=cookies,
|
| 1972 |
+
auth=auth,
|
| 1973 |
+
follow_redirects=follow_redirects,
|
| 1974 |
+
timeout=timeout,
|
| 1975 |
+
extensions=extensions,
|
| 1976 |
+
)
|
| 1977 |
+
|
| 1978 |
+
async def aclose(self) -> None:
|
| 1979 |
+
"""
|
| 1980 |
+
Close transport and proxies.
|
| 1981 |
+
"""
|
| 1982 |
+
if self._state != ClientState.CLOSED:
|
| 1983 |
+
self._state = ClientState.CLOSED
|
| 1984 |
+
|
| 1985 |
+
await self._transport.aclose()
|
| 1986 |
+
for proxy in self._mounts.values():
|
| 1987 |
+
if proxy is not None:
|
| 1988 |
+
await proxy.aclose()
|
| 1989 |
+
|
| 1990 |
+
async def __aenter__(self: U) -> U:
|
| 1991 |
+
if self._state != ClientState.UNOPENED:
|
| 1992 |
+
msg = {
|
| 1993 |
+
ClientState.OPENED: "Cannot open a client instance more than once.",
|
| 1994 |
+
ClientState.CLOSED: (
|
| 1995 |
+
"Cannot reopen a client instance, once it has been closed."
|
| 1996 |
+
),
|
| 1997 |
+
}[self._state]
|
| 1998 |
+
raise RuntimeError(msg)
|
| 1999 |
+
|
| 2000 |
+
self._state = ClientState.OPENED
|
| 2001 |
+
|
| 2002 |
+
await self._transport.__aenter__()
|
| 2003 |
+
for proxy in self._mounts.values():
|
| 2004 |
+
if proxy is not None:
|
| 2005 |
+
await proxy.__aenter__()
|
| 2006 |
+
return self
|
| 2007 |
+
|
| 2008 |
+
async def __aexit__(
|
| 2009 |
+
self,
|
| 2010 |
+
exc_type: type[BaseException] | None = None,
|
| 2011 |
+
exc_value: BaseException | None = None,
|
| 2012 |
+
traceback: TracebackType | None = None,
|
| 2013 |
+
) -> None:
|
| 2014 |
+
self._state = ClientState.CLOSED
|
| 2015 |
+
|
| 2016 |
+
await self._transport.__aexit__(exc_type, exc_value, traceback)
|
| 2017 |
+
for proxy in self._mounts.values():
|
| 2018 |
+
if proxy is not None:
|
| 2019 |
+
await proxy.__aexit__(exc_type, exc_value, traceback)
|
.venv/lib/python3.14/site-packages/httpx/_config.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import typing
|
| 5 |
+
|
| 6 |
+
from ._models import Headers
|
| 7 |
+
from ._types import CertTypes, HeaderTypes, TimeoutTypes
|
| 8 |
+
from ._urls import URL
|
| 9 |
+
|
| 10 |
+
if typing.TYPE_CHECKING:
|
| 11 |
+
import ssl # pragma: no cover
|
| 12 |
+
|
| 13 |
+
__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class UnsetType:
|
| 17 |
+
pass # pragma: no cover
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
UNSET = UnsetType()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def create_ssl_context(
|
| 24 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 25 |
+
cert: CertTypes | None = None,
|
| 26 |
+
trust_env: bool = True,
|
| 27 |
+
) -> ssl.SSLContext:
|
| 28 |
+
import ssl
|
| 29 |
+
import warnings
|
| 30 |
+
|
| 31 |
+
import certifi
|
| 32 |
+
|
| 33 |
+
if verify is True:
|
| 34 |
+
if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover
|
| 35 |
+
ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"])
|
| 36 |
+
elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover
|
| 37 |
+
ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"])
|
| 38 |
+
else:
|
| 39 |
+
# Default case...
|
| 40 |
+
ctx = ssl.create_default_context(cafile=certifi.where())
|
| 41 |
+
elif verify is False:
|
| 42 |
+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
| 43 |
+
ctx.check_hostname = False
|
| 44 |
+
ctx.verify_mode = ssl.CERT_NONE
|
| 45 |
+
elif isinstance(verify, str): # pragma: nocover
|
| 46 |
+
message = (
|
| 47 |
+
"`verify=<str>` is deprecated. "
|
| 48 |
+
"Use `verify=ssl.create_default_context(cafile=...)` "
|
| 49 |
+
"or `verify=ssl.create_default_context(capath=...)` instead."
|
| 50 |
+
)
|
| 51 |
+
warnings.warn(message, DeprecationWarning)
|
| 52 |
+
if os.path.isdir(verify):
|
| 53 |
+
return ssl.create_default_context(capath=verify)
|
| 54 |
+
return ssl.create_default_context(cafile=verify)
|
| 55 |
+
else:
|
| 56 |
+
ctx = verify
|
| 57 |
+
|
| 58 |
+
if cert: # pragma: nocover
|
| 59 |
+
message = (
|
| 60 |
+
"`cert=...` is deprecated. Use `verify=<ssl_context>` instead,"
|
| 61 |
+
"with `.load_cert_chain()` to configure the certificate chain."
|
| 62 |
+
)
|
| 63 |
+
warnings.warn(message, DeprecationWarning)
|
| 64 |
+
if isinstance(cert, str):
|
| 65 |
+
ctx.load_cert_chain(cert)
|
| 66 |
+
else:
|
| 67 |
+
ctx.load_cert_chain(*cert)
|
| 68 |
+
|
| 69 |
+
return ctx
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class Timeout:
|
| 73 |
+
"""
|
| 74 |
+
Timeout configuration.
|
| 75 |
+
|
| 76 |
+
**Usage**:
|
| 77 |
+
|
| 78 |
+
Timeout(None) # No timeouts.
|
| 79 |
+
Timeout(5.0) # 5s timeout on all operations.
|
| 80 |
+
Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts.
|
| 81 |
+
Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere.
|
| 82 |
+
Timeout(5.0, pool=None) # No timeout on acquiring connection from pool.
|
| 83 |
+
# 5s timeout elsewhere.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(
|
| 87 |
+
self,
|
| 88 |
+
timeout: TimeoutTypes | UnsetType = UNSET,
|
| 89 |
+
*,
|
| 90 |
+
connect: None | float | UnsetType = UNSET,
|
| 91 |
+
read: None | float | UnsetType = UNSET,
|
| 92 |
+
write: None | float | UnsetType = UNSET,
|
| 93 |
+
pool: None | float | UnsetType = UNSET,
|
| 94 |
+
) -> None:
|
| 95 |
+
if isinstance(timeout, Timeout):
|
| 96 |
+
# Passed as a single explicit Timeout.
|
| 97 |
+
assert connect is UNSET
|
| 98 |
+
assert read is UNSET
|
| 99 |
+
assert write is UNSET
|
| 100 |
+
assert pool is UNSET
|
| 101 |
+
self.connect = timeout.connect # type: typing.Optional[float]
|
| 102 |
+
self.read = timeout.read # type: typing.Optional[float]
|
| 103 |
+
self.write = timeout.write # type: typing.Optional[float]
|
| 104 |
+
self.pool = timeout.pool # type: typing.Optional[float]
|
| 105 |
+
elif isinstance(timeout, tuple):
|
| 106 |
+
# Passed as a tuple.
|
| 107 |
+
self.connect = timeout[0]
|
| 108 |
+
self.read = timeout[1]
|
| 109 |
+
self.write = None if len(timeout) < 3 else timeout[2]
|
| 110 |
+
self.pool = None if len(timeout) < 4 else timeout[3]
|
| 111 |
+
elif not (
|
| 112 |
+
isinstance(connect, UnsetType)
|
| 113 |
+
or isinstance(read, UnsetType)
|
| 114 |
+
or isinstance(write, UnsetType)
|
| 115 |
+
or isinstance(pool, UnsetType)
|
| 116 |
+
):
|
| 117 |
+
self.connect = connect
|
| 118 |
+
self.read = read
|
| 119 |
+
self.write = write
|
| 120 |
+
self.pool = pool
|
| 121 |
+
else:
|
| 122 |
+
if isinstance(timeout, UnsetType):
|
| 123 |
+
raise ValueError(
|
| 124 |
+
"httpx.Timeout must either include a default, or set all "
|
| 125 |
+
"four parameters explicitly."
|
| 126 |
+
)
|
| 127 |
+
self.connect = timeout if isinstance(connect, UnsetType) else connect
|
| 128 |
+
self.read = timeout if isinstance(read, UnsetType) else read
|
| 129 |
+
self.write = timeout if isinstance(write, UnsetType) else write
|
| 130 |
+
self.pool = timeout if isinstance(pool, UnsetType) else pool
|
| 131 |
+
|
| 132 |
+
def as_dict(self) -> dict[str, float | None]:
|
| 133 |
+
return {
|
| 134 |
+
"connect": self.connect,
|
| 135 |
+
"read": self.read,
|
| 136 |
+
"write": self.write,
|
| 137 |
+
"pool": self.pool,
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 141 |
+
return (
|
| 142 |
+
isinstance(other, self.__class__)
|
| 143 |
+
and self.connect == other.connect
|
| 144 |
+
and self.read == other.read
|
| 145 |
+
and self.write == other.write
|
| 146 |
+
and self.pool == other.pool
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
def __repr__(self) -> str:
|
| 150 |
+
class_name = self.__class__.__name__
|
| 151 |
+
if len({self.connect, self.read, self.write, self.pool}) == 1:
|
| 152 |
+
return f"{class_name}(timeout={self.connect})"
|
| 153 |
+
return (
|
| 154 |
+
f"{class_name}(connect={self.connect}, "
|
| 155 |
+
f"read={self.read}, write={self.write}, pool={self.pool})"
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class Limits:
|
| 160 |
+
"""
|
| 161 |
+
Configuration for limits to various client behaviors.
|
| 162 |
+
|
| 163 |
+
**Parameters:**
|
| 164 |
+
|
| 165 |
+
* **max_connections** - The maximum number of concurrent connections that may be
|
| 166 |
+
established.
|
| 167 |
+
* **max_keepalive_connections** - Allow the connection pool to maintain
|
| 168 |
+
keep-alive connections below this point. Should be less than or equal
|
| 169 |
+
to `max_connections`.
|
| 170 |
+
* **keepalive_expiry** - Time limit on idle keep-alive connections in seconds.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
def __init__(
|
| 174 |
+
self,
|
| 175 |
+
*,
|
| 176 |
+
max_connections: int | None = None,
|
| 177 |
+
max_keepalive_connections: int | None = None,
|
| 178 |
+
keepalive_expiry: float | None = 5.0,
|
| 179 |
+
) -> None:
|
| 180 |
+
self.max_connections = max_connections
|
| 181 |
+
self.max_keepalive_connections = max_keepalive_connections
|
| 182 |
+
self.keepalive_expiry = keepalive_expiry
|
| 183 |
+
|
| 184 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 185 |
+
return (
|
| 186 |
+
isinstance(other, self.__class__)
|
| 187 |
+
and self.max_connections == other.max_connections
|
| 188 |
+
and self.max_keepalive_connections == other.max_keepalive_connections
|
| 189 |
+
and self.keepalive_expiry == other.keepalive_expiry
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
def __repr__(self) -> str:
|
| 193 |
+
class_name = self.__class__.__name__
|
| 194 |
+
return (
|
| 195 |
+
f"{class_name}(max_connections={self.max_connections}, "
|
| 196 |
+
f"max_keepalive_connections={self.max_keepalive_connections}, "
|
| 197 |
+
f"keepalive_expiry={self.keepalive_expiry})"
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class Proxy:
|
| 202 |
+
def __init__(
|
| 203 |
+
self,
|
| 204 |
+
url: URL | str,
|
| 205 |
+
*,
|
| 206 |
+
ssl_context: ssl.SSLContext | None = None,
|
| 207 |
+
auth: tuple[str, str] | None = None,
|
| 208 |
+
headers: HeaderTypes | None = None,
|
| 209 |
+
) -> None:
|
| 210 |
+
url = URL(url)
|
| 211 |
+
headers = Headers(headers)
|
| 212 |
+
|
| 213 |
+
if url.scheme not in ("http", "https", "socks5", "socks5h"):
|
| 214 |
+
raise ValueError(f"Unknown scheme for proxy URL {url!r}")
|
| 215 |
+
|
| 216 |
+
if url.username or url.password:
|
| 217 |
+
# Remove any auth credentials from the URL.
|
| 218 |
+
auth = (url.username, url.password)
|
| 219 |
+
url = url.copy_with(username=None, password=None)
|
| 220 |
+
|
| 221 |
+
self.url = url
|
| 222 |
+
self.auth = auth
|
| 223 |
+
self.headers = headers
|
| 224 |
+
self.ssl_context = ssl_context
|
| 225 |
+
|
| 226 |
+
@property
|
| 227 |
+
def raw_auth(self) -> tuple[bytes, bytes] | None:
|
| 228 |
+
# The proxy authentication as raw bytes.
|
| 229 |
+
return (
|
| 230 |
+
None
|
| 231 |
+
if self.auth is None
|
| 232 |
+
else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8"))
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
def __repr__(self) -> str:
|
| 236 |
+
# The authentication is represented with the password component masked.
|
| 237 |
+
auth = (self.auth[0], "********") if self.auth else None
|
| 238 |
+
|
| 239 |
+
# Build a nice concise representation.
|
| 240 |
+
url_str = f"{str(self.url)!r}"
|
| 241 |
+
auth_str = f", auth={auth!r}" if auth else ""
|
| 242 |
+
headers_str = f", headers={dict(self.headers)!r}" if self.headers else ""
|
| 243 |
+
return f"Proxy({url_str}{auth_str}{headers_str})"
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
|
| 247 |
+
DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
|
| 248 |
+
DEFAULT_MAX_REDIRECTS = 20
|
.venv/lib/python3.14/site-packages/httpx/_content.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
import warnings
|
| 5 |
+
from json import dumps as json_dumps
|
| 6 |
+
from typing import (
|
| 7 |
+
Any,
|
| 8 |
+
AsyncIterable,
|
| 9 |
+
AsyncIterator,
|
| 10 |
+
Iterable,
|
| 11 |
+
Iterator,
|
| 12 |
+
Mapping,
|
| 13 |
+
)
|
| 14 |
+
from urllib.parse import urlencode
|
| 15 |
+
|
| 16 |
+
from ._exceptions import StreamClosed, StreamConsumed
|
| 17 |
+
from ._multipart import MultipartStream
|
| 18 |
+
from ._types import (
|
| 19 |
+
AsyncByteStream,
|
| 20 |
+
RequestContent,
|
| 21 |
+
RequestData,
|
| 22 |
+
RequestFiles,
|
| 23 |
+
ResponseContent,
|
| 24 |
+
SyncByteStream,
|
| 25 |
+
)
|
| 26 |
+
from ._utils import peek_filelike_length, primitive_value_to_str
|
| 27 |
+
|
| 28 |
+
__all__ = ["ByteStream"]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class ByteStream(AsyncByteStream, SyncByteStream):
|
| 32 |
+
def __init__(self, stream: bytes) -> None:
|
| 33 |
+
self._stream = stream
|
| 34 |
+
|
| 35 |
+
def __iter__(self) -> Iterator[bytes]:
|
| 36 |
+
yield self._stream
|
| 37 |
+
|
| 38 |
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
| 39 |
+
yield self._stream
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class IteratorByteStream(SyncByteStream):
|
| 43 |
+
CHUNK_SIZE = 65_536
|
| 44 |
+
|
| 45 |
+
def __init__(self, stream: Iterable[bytes]) -> None:
|
| 46 |
+
self._stream = stream
|
| 47 |
+
self._is_stream_consumed = False
|
| 48 |
+
self._is_generator = inspect.isgenerator(stream)
|
| 49 |
+
|
| 50 |
+
def __iter__(self) -> Iterator[bytes]:
|
| 51 |
+
if self._is_stream_consumed and self._is_generator:
|
| 52 |
+
raise StreamConsumed()
|
| 53 |
+
|
| 54 |
+
self._is_stream_consumed = True
|
| 55 |
+
if hasattr(self._stream, "read"):
|
| 56 |
+
# File-like interfaces should use 'read' directly.
|
| 57 |
+
chunk = self._stream.read(self.CHUNK_SIZE)
|
| 58 |
+
while chunk:
|
| 59 |
+
yield chunk
|
| 60 |
+
chunk = self._stream.read(self.CHUNK_SIZE)
|
| 61 |
+
else:
|
| 62 |
+
# Otherwise iterate.
|
| 63 |
+
for part in self._stream:
|
| 64 |
+
yield part
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class AsyncIteratorByteStream(AsyncByteStream):
|
| 68 |
+
CHUNK_SIZE = 65_536
|
| 69 |
+
|
| 70 |
+
def __init__(self, stream: AsyncIterable[bytes]) -> None:
|
| 71 |
+
self._stream = stream
|
| 72 |
+
self._is_stream_consumed = False
|
| 73 |
+
self._is_generator = inspect.isasyncgen(stream)
|
| 74 |
+
|
| 75 |
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
| 76 |
+
if self._is_stream_consumed and self._is_generator:
|
| 77 |
+
raise StreamConsumed()
|
| 78 |
+
|
| 79 |
+
self._is_stream_consumed = True
|
| 80 |
+
if hasattr(self._stream, "aread"):
|
| 81 |
+
# File-like interfaces should use 'aread' directly.
|
| 82 |
+
chunk = await self._stream.aread(self.CHUNK_SIZE)
|
| 83 |
+
while chunk:
|
| 84 |
+
yield chunk
|
| 85 |
+
chunk = await self._stream.aread(self.CHUNK_SIZE)
|
| 86 |
+
else:
|
| 87 |
+
# Otherwise iterate.
|
| 88 |
+
async for part in self._stream:
|
| 89 |
+
yield part
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class UnattachedStream(AsyncByteStream, SyncByteStream):
|
| 93 |
+
"""
|
| 94 |
+
If a request or response is serialized using pickle, then it is no longer
|
| 95 |
+
attached to a stream for I/O purposes. Any stream operations should result
|
| 96 |
+
in `httpx.StreamClosed`.
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
def __iter__(self) -> Iterator[bytes]:
|
| 100 |
+
raise StreamClosed()
|
| 101 |
+
|
| 102 |
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
| 103 |
+
raise StreamClosed()
|
| 104 |
+
yield b"" # pragma: no cover
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def encode_content(
|
| 108 |
+
content: str | bytes | Iterable[bytes] | AsyncIterable[bytes],
|
| 109 |
+
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
|
| 110 |
+
if isinstance(content, (bytes, str)):
|
| 111 |
+
body = content.encode("utf-8") if isinstance(content, str) else content
|
| 112 |
+
content_length = len(body)
|
| 113 |
+
headers = {"Content-Length": str(content_length)} if body else {}
|
| 114 |
+
return headers, ByteStream(body)
|
| 115 |
+
|
| 116 |
+
elif isinstance(content, Iterable) and not isinstance(content, dict):
|
| 117 |
+
# `not isinstance(content, dict)` is a bit oddly specific, but it
|
| 118 |
+
# catches a case that's easy for users to make in error, and would
|
| 119 |
+
# otherwise pass through here, like any other bytes-iterable,
|
| 120 |
+
# because `dict` happens to be iterable. See issue #2491.
|
| 121 |
+
content_length_or_none = peek_filelike_length(content)
|
| 122 |
+
|
| 123 |
+
if content_length_or_none is None:
|
| 124 |
+
headers = {"Transfer-Encoding": "chunked"}
|
| 125 |
+
else:
|
| 126 |
+
headers = {"Content-Length": str(content_length_or_none)}
|
| 127 |
+
return headers, IteratorByteStream(content) # type: ignore
|
| 128 |
+
|
| 129 |
+
elif isinstance(content, AsyncIterable):
|
| 130 |
+
headers = {"Transfer-Encoding": "chunked"}
|
| 131 |
+
return headers, AsyncIteratorByteStream(content)
|
| 132 |
+
|
| 133 |
+
raise TypeError(f"Unexpected type for 'content', {type(content)!r}")
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def encode_urlencoded_data(
|
| 137 |
+
data: RequestData,
|
| 138 |
+
) -> tuple[dict[str, str], ByteStream]:
|
| 139 |
+
plain_data = []
|
| 140 |
+
for key, value in data.items():
|
| 141 |
+
if isinstance(value, (list, tuple)):
|
| 142 |
+
plain_data.extend([(key, primitive_value_to_str(item)) for item in value])
|
| 143 |
+
else:
|
| 144 |
+
plain_data.append((key, primitive_value_to_str(value)))
|
| 145 |
+
body = urlencode(plain_data, doseq=True).encode("utf-8")
|
| 146 |
+
content_length = str(len(body))
|
| 147 |
+
content_type = "application/x-www-form-urlencoded"
|
| 148 |
+
headers = {"Content-Length": content_length, "Content-Type": content_type}
|
| 149 |
+
return headers, ByteStream(body)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def encode_multipart_data(
|
| 153 |
+
data: RequestData, files: RequestFiles, boundary: bytes | None
|
| 154 |
+
) -> tuple[dict[str, str], MultipartStream]:
|
| 155 |
+
multipart = MultipartStream(data=data, files=files, boundary=boundary)
|
| 156 |
+
headers = multipart.get_headers()
|
| 157 |
+
return headers, multipart
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def encode_text(text: str) -> tuple[dict[str, str], ByteStream]:
|
| 161 |
+
body = text.encode("utf-8")
|
| 162 |
+
content_length = str(len(body))
|
| 163 |
+
content_type = "text/plain; charset=utf-8"
|
| 164 |
+
headers = {"Content-Length": content_length, "Content-Type": content_type}
|
| 165 |
+
return headers, ByteStream(body)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def encode_html(html: str) -> tuple[dict[str, str], ByteStream]:
|
| 169 |
+
body = html.encode("utf-8")
|
| 170 |
+
content_length = str(len(body))
|
| 171 |
+
content_type = "text/html; charset=utf-8"
|
| 172 |
+
headers = {"Content-Length": content_length, "Content-Type": content_type}
|
| 173 |
+
return headers, ByteStream(body)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]:
|
| 177 |
+
body = json_dumps(
|
| 178 |
+
json, ensure_ascii=False, separators=(",", ":"), allow_nan=False
|
| 179 |
+
).encode("utf-8")
|
| 180 |
+
content_length = str(len(body))
|
| 181 |
+
content_type = "application/json"
|
| 182 |
+
headers = {"Content-Length": content_length, "Content-Type": content_type}
|
| 183 |
+
return headers, ByteStream(body)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def encode_request(
|
| 187 |
+
content: RequestContent | None = None,
|
| 188 |
+
data: RequestData | None = None,
|
| 189 |
+
files: RequestFiles | None = None,
|
| 190 |
+
json: Any | None = None,
|
| 191 |
+
boundary: bytes | None = None,
|
| 192 |
+
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
|
| 193 |
+
"""
|
| 194 |
+
Handles encoding the given `content`, `data`, `files`, and `json`,
|
| 195 |
+
returning a two-tuple of (<headers>, <stream>).
|
| 196 |
+
"""
|
| 197 |
+
if data is not None and not isinstance(data, Mapping):
|
| 198 |
+
# We prefer to separate `content=<bytes|str|byte iterator|bytes aiterator>`
|
| 199 |
+
# for raw request content, and `data=<form data>` for url encoded or
|
| 200 |
+
# multipart form content.
|
| 201 |
+
#
|
| 202 |
+
# However for compat with requests, we *do* still support
|
| 203 |
+
# `data=<bytes...>` usages. We deal with that case here, treating it
|
| 204 |
+
# as if `content=<...>` had been supplied instead.
|
| 205 |
+
message = "Use 'content=<...>' to upload raw bytes/text content."
|
| 206 |
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
| 207 |
+
return encode_content(data)
|
| 208 |
+
|
| 209 |
+
if content is not None:
|
| 210 |
+
return encode_content(content)
|
| 211 |
+
elif files:
|
| 212 |
+
return encode_multipart_data(data or {}, files, boundary)
|
| 213 |
+
elif data:
|
| 214 |
+
return encode_urlencoded_data(data)
|
| 215 |
+
elif json is not None:
|
| 216 |
+
return encode_json(json)
|
| 217 |
+
|
| 218 |
+
return {}, ByteStream(b"")
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def encode_response(
|
| 222 |
+
content: ResponseContent | None = None,
|
| 223 |
+
text: str | None = None,
|
| 224 |
+
html: str | None = None,
|
| 225 |
+
json: Any | None = None,
|
| 226 |
+
) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:
|
| 227 |
+
"""
|
| 228 |
+
Handles encoding the given `content`, returning a two-tuple of
|
| 229 |
+
(<headers>, <stream>).
|
| 230 |
+
"""
|
| 231 |
+
if content is not None:
|
| 232 |
+
return encode_content(content)
|
| 233 |
+
elif text is not None:
|
| 234 |
+
return encode_text(text)
|
| 235 |
+
elif html is not None:
|
| 236 |
+
return encode_html(html)
|
| 237 |
+
elif json is not None:
|
| 238 |
+
return encode_json(json)
|
| 239 |
+
|
| 240 |
+
return {}, ByteStream(b"")
|
.venv/lib/python3.14/site-packages/httpx/_decoders.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Handlers for Content-Encoding.
|
| 3 |
+
|
| 4 |
+
See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import codecs
|
| 10 |
+
import io
|
| 11 |
+
import typing
|
| 12 |
+
import zlib
|
| 13 |
+
|
| 14 |
+
from ._exceptions import DecodingError
|
| 15 |
+
|
| 16 |
+
# Brotli support is optional
|
| 17 |
+
try:
|
| 18 |
+
# The C bindings in `brotli` are recommended for CPython.
|
| 19 |
+
import brotli
|
| 20 |
+
except ImportError: # pragma: no cover
|
| 21 |
+
try:
|
| 22 |
+
# The CFFI bindings in `brotlicffi` are recommended for PyPy
|
| 23 |
+
# and other environments.
|
| 24 |
+
import brotlicffi as brotli
|
| 25 |
+
except ImportError:
|
| 26 |
+
brotli = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Zstandard support is optional
|
| 30 |
+
try:
|
| 31 |
+
import zstandard
|
| 32 |
+
except ImportError: # pragma: no cover
|
| 33 |
+
zstandard = None # type: ignore
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class ContentDecoder:
|
| 37 |
+
def decode(self, data: bytes) -> bytes:
|
| 38 |
+
raise NotImplementedError() # pragma: no cover
|
| 39 |
+
|
| 40 |
+
def flush(self) -> bytes:
|
| 41 |
+
raise NotImplementedError() # pragma: no cover
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class IdentityDecoder(ContentDecoder):
|
| 45 |
+
"""
|
| 46 |
+
Handle unencoded data.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def decode(self, data: bytes) -> bytes:
|
| 50 |
+
return data
|
| 51 |
+
|
| 52 |
+
def flush(self) -> bytes:
|
| 53 |
+
return b""
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class DeflateDecoder(ContentDecoder):
|
| 57 |
+
"""
|
| 58 |
+
Handle 'deflate' decoding.
|
| 59 |
+
|
| 60 |
+
See: https://stackoverflow.com/questions/1838699
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
def __init__(self) -> None:
|
| 64 |
+
self.first_attempt = True
|
| 65 |
+
self.decompressor = zlib.decompressobj()
|
| 66 |
+
|
| 67 |
+
def decode(self, data: bytes) -> bytes:
|
| 68 |
+
was_first_attempt = self.first_attempt
|
| 69 |
+
self.first_attempt = False
|
| 70 |
+
try:
|
| 71 |
+
return self.decompressor.decompress(data)
|
| 72 |
+
except zlib.error as exc:
|
| 73 |
+
if was_first_attempt:
|
| 74 |
+
self.decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
|
| 75 |
+
return self.decode(data)
|
| 76 |
+
raise DecodingError(str(exc)) from exc
|
| 77 |
+
|
| 78 |
+
def flush(self) -> bytes:
|
| 79 |
+
try:
|
| 80 |
+
return self.decompressor.flush()
|
| 81 |
+
except zlib.error as exc: # pragma: no cover
|
| 82 |
+
raise DecodingError(str(exc)) from exc
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class GZipDecoder(ContentDecoder):
|
| 86 |
+
"""
|
| 87 |
+
Handle 'gzip' decoding.
|
| 88 |
+
|
| 89 |
+
See: https://stackoverflow.com/questions/1838699
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(self) -> None:
|
| 93 |
+
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 16)
|
| 94 |
+
|
| 95 |
+
def decode(self, data: bytes) -> bytes:
|
| 96 |
+
try:
|
| 97 |
+
return self.decompressor.decompress(data)
|
| 98 |
+
except zlib.error as exc:
|
| 99 |
+
raise DecodingError(str(exc)) from exc
|
| 100 |
+
|
| 101 |
+
def flush(self) -> bytes:
|
| 102 |
+
try:
|
| 103 |
+
return self.decompressor.flush()
|
| 104 |
+
except zlib.error as exc: # pragma: no cover
|
| 105 |
+
raise DecodingError(str(exc)) from exc
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class BrotliDecoder(ContentDecoder):
|
| 109 |
+
"""
|
| 110 |
+
Handle 'brotli' decoding.
|
| 111 |
+
|
| 112 |
+
Requires `pip install brotlipy`. See: https://brotlipy.readthedocs.io/
|
| 113 |
+
or `pip install brotli`. See https://github.com/google/brotli
|
| 114 |
+
Supports both 'brotlipy' and 'Brotli' packages since they share an import
|
| 115 |
+
name. The top branches are for 'brotlipy' and bottom branches for 'Brotli'
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
def __init__(self) -> None:
|
| 119 |
+
if brotli is None: # pragma: no cover
|
| 120 |
+
raise ImportError(
|
| 121 |
+
"Using 'BrotliDecoder', but neither of the 'brotlicffi' or 'brotli' "
|
| 122 |
+
"packages have been installed. "
|
| 123 |
+
"Make sure to install httpx using `pip install httpx[brotli]`."
|
| 124 |
+
) from None
|
| 125 |
+
|
| 126 |
+
self.decompressor = brotli.Decompressor()
|
| 127 |
+
self.seen_data = False
|
| 128 |
+
self._decompress: typing.Callable[[bytes], bytes]
|
| 129 |
+
if hasattr(self.decompressor, "decompress"):
|
| 130 |
+
# The 'brotlicffi' package.
|
| 131 |
+
self._decompress = self.decompressor.decompress # pragma: no cover
|
| 132 |
+
else:
|
| 133 |
+
# The 'brotli' package.
|
| 134 |
+
self._decompress = self.decompressor.process # pragma: no cover
|
| 135 |
+
|
| 136 |
+
def decode(self, data: bytes) -> bytes:
|
| 137 |
+
if not data:
|
| 138 |
+
return b""
|
| 139 |
+
self.seen_data = True
|
| 140 |
+
try:
|
| 141 |
+
return self._decompress(data)
|
| 142 |
+
except brotli.error as exc:
|
| 143 |
+
raise DecodingError(str(exc)) from exc
|
| 144 |
+
|
| 145 |
+
def flush(self) -> bytes:
|
| 146 |
+
if not self.seen_data:
|
| 147 |
+
return b""
|
| 148 |
+
try:
|
| 149 |
+
if hasattr(self.decompressor, "finish"):
|
| 150 |
+
# Only available in the 'brotlicffi' package.
|
| 151 |
+
|
| 152 |
+
# As the decompressor decompresses eagerly, this
|
| 153 |
+
# will never actually emit any data. However, it will potentially throw
|
| 154 |
+
# errors if a truncated or damaged data stream has been used.
|
| 155 |
+
self.decompressor.finish() # pragma: no cover
|
| 156 |
+
return b""
|
| 157 |
+
except brotli.error as exc: # pragma: no cover
|
| 158 |
+
raise DecodingError(str(exc)) from exc
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class ZStandardDecoder(ContentDecoder):
|
| 162 |
+
"""
|
| 163 |
+
Handle 'zstd' RFC 8878 decoding.
|
| 164 |
+
|
| 165 |
+
Requires `pip install zstandard`.
|
| 166 |
+
Can be installed as a dependency of httpx using `pip install httpx[zstd]`.
|
| 167 |
+
"""
|
| 168 |
+
|
| 169 |
+
# inspired by the ZstdDecoder implementation in urllib3
|
| 170 |
+
def __init__(self) -> None:
|
| 171 |
+
if zstandard is None: # pragma: no cover
|
| 172 |
+
raise ImportError(
|
| 173 |
+
"Using 'ZStandardDecoder', ..."
|
| 174 |
+
"Make sure to install httpx using `pip install httpx[zstd]`."
|
| 175 |
+
) from None
|
| 176 |
+
|
| 177 |
+
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
|
| 178 |
+
self.seen_data = False
|
| 179 |
+
|
| 180 |
+
def decode(self, data: bytes) -> bytes:
|
| 181 |
+
assert zstandard is not None
|
| 182 |
+
self.seen_data = True
|
| 183 |
+
output = io.BytesIO()
|
| 184 |
+
try:
|
| 185 |
+
output.write(self.decompressor.decompress(data))
|
| 186 |
+
while self.decompressor.eof and self.decompressor.unused_data:
|
| 187 |
+
unused_data = self.decompressor.unused_data
|
| 188 |
+
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
|
| 189 |
+
output.write(self.decompressor.decompress(unused_data))
|
| 190 |
+
except zstandard.ZstdError as exc:
|
| 191 |
+
raise DecodingError(str(exc)) from exc
|
| 192 |
+
return output.getvalue()
|
| 193 |
+
|
| 194 |
+
def flush(self) -> bytes:
|
| 195 |
+
if not self.seen_data:
|
| 196 |
+
return b""
|
| 197 |
+
ret = self.decompressor.flush() # note: this is a no-op
|
| 198 |
+
if not self.decompressor.eof:
|
| 199 |
+
raise DecodingError("Zstandard data is incomplete") # pragma: no cover
|
| 200 |
+
return bytes(ret)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
class MultiDecoder(ContentDecoder):
|
| 204 |
+
"""
|
| 205 |
+
Handle the case where multiple encodings have been applied.
|
| 206 |
+
"""
|
| 207 |
+
|
| 208 |
+
def __init__(self, children: typing.Sequence[ContentDecoder]) -> None:
|
| 209 |
+
"""
|
| 210 |
+
'children' should be a sequence of decoders in the order in which
|
| 211 |
+
each was applied.
|
| 212 |
+
"""
|
| 213 |
+
# Note that we reverse the order for decoding.
|
| 214 |
+
self.children = list(reversed(children))
|
| 215 |
+
|
| 216 |
+
def decode(self, data: bytes) -> bytes:
|
| 217 |
+
for child in self.children:
|
| 218 |
+
data = child.decode(data)
|
| 219 |
+
return data
|
| 220 |
+
|
| 221 |
+
def flush(self) -> bytes:
|
| 222 |
+
data = b""
|
| 223 |
+
for child in self.children:
|
| 224 |
+
data = child.decode(data) + child.flush()
|
| 225 |
+
return data
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class ByteChunker:
|
| 229 |
+
"""
|
| 230 |
+
Handles returning byte content in fixed-size chunks.
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
def __init__(self, chunk_size: int | None = None) -> None:
|
| 234 |
+
self._buffer = io.BytesIO()
|
| 235 |
+
self._chunk_size = chunk_size
|
| 236 |
+
|
| 237 |
+
def decode(self, content: bytes) -> list[bytes]:
|
| 238 |
+
if self._chunk_size is None:
|
| 239 |
+
return [content] if content else []
|
| 240 |
+
|
| 241 |
+
self._buffer.write(content)
|
| 242 |
+
if self._buffer.tell() >= self._chunk_size:
|
| 243 |
+
value = self._buffer.getvalue()
|
| 244 |
+
chunks = [
|
| 245 |
+
value[i : i + self._chunk_size]
|
| 246 |
+
for i in range(0, len(value), self._chunk_size)
|
| 247 |
+
]
|
| 248 |
+
if len(chunks[-1]) == self._chunk_size:
|
| 249 |
+
self._buffer.seek(0)
|
| 250 |
+
self._buffer.truncate()
|
| 251 |
+
return chunks
|
| 252 |
+
else:
|
| 253 |
+
self._buffer.seek(0)
|
| 254 |
+
self._buffer.write(chunks[-1])
|
| 255 |
+
self._buffer.truncate()
|
| 256 |
+
return chunks[:-1]
|
| 257 |
+
else:
|
| 258 |
+
return []
|
| 259 |
+
|
| 260 |
+
def flush(self) -> list[bytes]:
|
| 261 |
+
value = self._buffer.getvalue()
|
| 262 |
+
self._buffer.seek(0)
|
| 263 |
+
self._buffer.truncate()
|
| 264 |
+
return [value] if value else []
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
class TextChunker:
|
| 268 |
+
"""
|
| 269 |
+
Handles returning text content in fixed-size chunks.
|
| 270 |
+
"""
|
| 271 |
+
|
| 272 |
+
def __init__(self, chunk_size: int | None = None) -> None:
|
| 273 |
+
self._buffer = io.StringIO()
|
| 274 |
+
self._chunk_size = chunk_size
|
| 275 |
+
|
| 276 |
+
def decode(self, content: str) -> list[str]:
|
| 277 |
+
if self._chunk_size is None:
|
| 278 |
+
return [content] if content else []
|
| 279 |
+
|
| 280 |
+
self._buffer.write(content)
|
| 281 |
+
if self._buffer.tell() >= self._chunk_size:
|
| 282 |
+
value = self._buffer.getvalue()
|
| 283 |
+
chunks = [
|
| 284 |
+
value[i : i + self._chunk_size]
|
| 285 |
+
for i in range(0, len(value), self._chunk_size)
|
| 286 |
+
]
|
| 287 |
+
if len(chunks[-1]) == self._chunk_size:
|
| 288 |
+
self._buffer.seek(0)
|
| 289 |
+
self._buffer.truncate()
|
| 290 |
+
return chunks
|
| 291 |
+
else:
|
| 292 |
+
self._buffer.seek(0)
|
| 293 |
+
self._buffer.write(chunks[-1])
|
| 294 |
+
self._buffer.truncate()
|
| 295 |
+
return chunks[:-1]
|
| 296 |
+
else:
|
| 297 |
+
return []
|
| 298 |
+
|
| 299 |
+
def flush(self) -> list[str]:
|
| 300 |
+
value = self._buffer.getvalue()
|
| 301 |
+
self._buffer.seek(0)
|
| 302 |
+
self._buffer.truncate()
|
| 303 |
+
return [value] if value else []
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
class TextDecoder:
|
| 307 |
+
"""
|
| 308 |
+
Handles incrementally decoding bytes into text
|
| 309 |
+
"""
|
| 310 |
+
|
| 311 |
+
def __init__(self, encoding: str = "utf-8") -> None:
|
| 312 |
+
self.decoder = codecs.getincrementaldecoder(encoding)(errors="replace")
|
| 313 |
+
|
| 314 |
+
def decode(self, data: bytes) -> str:
|
| 315 |
+
return self.decoder.decode(data)
|
| 316 |
+
|
| 317 |
+
def flush(self) -> str:
|
| 318 |
+
return self.decoder.decode(b"", True)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
class LineDecoder:
|
| 322 |
+
"""
|
| 323 |
+
Handles incrementally reading lines from text.
|
| 324 |
+
|
| 325 |
+
Has the same behaviour as the stdllib splitlines,
|
| 326 |
+
but handling the input iteratively.
|
| 327 |
+
"""
|
| 328 |
+
|
| 329 |
+
def __init__(self) -> None:
|
| 330 |
+
self.buffer: list[str] = []
|
| 331 |
+
self.trailing_cr: bool = False
|
| 332 |
+
|
| 333 |
+
def decode(self, text: str) -> list[str]:
|
| 334 |
+
# See https://docs.python.org/3/library/stdtypes.html#str.splitlines
|
| 335 |
+
NEWLINE_CHARS = "\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029"
|
| 336 |
+
|
| 337 |
+
# We always push a trailing `\r` into the next decode iteration.
|
| 338 |
+
if self.trailing_cr:
|
| 339 |
+
text = "\r" + text
|
| 340 |
+
self.trailing_cr = False
|
| 341 |
+
if text.endswith("\r"):
|
| 342 |
+
self.trailing_cr = True
|
| 343 |
+
text = text[:-1]
|
| 344 |
+
|
| 345 |
+
if not text:
|
| 346 |
+
# NOTE: the edge case input of empty text doesn't occur in practice,
|
| 347 |
+
# because other httpx internals filter out this value
|
| 348 |
+
return [] # pragma: no cover
|
| 349 |
+
|
| 350 |
+
trailing_newline = text[-1] in NEWLINE_CHARS
|
| 351 |
+
lines = text.splitlines()
|
| 352 |
+
|
| 353 |
+
if len(lines) == 1 and not trailing_newline:
|
| 354 |
+
# No new lines, buffer the input and continue.
|
| 355 |
+
self.buffer.append(lines[0])
|
| 356 |
+
return []
|
| 357 |
+
|
| 358 |
+
if self.buffer:
|
| 359 |
+
# Include any existing buffer in the first portion of the
|
| 360 |
+
# splitlines result.
|
| 361 |
+
lines = ["".join(self.buffer) + lines[0]] + lines[1:]
|
| 362 |
+
self.buffer = []
|
| 363 |
+
|
| 364 |
+
if not trailing_newline:
|
| 365 |
+
# If the last segment of splitlines is not newline terminated,
|
| 366 |
+
# then drop it from our output and start a new buffer.
|
| 367 |
+
self.buffer = [lines.pop()]
|
| 368 |
+
|
| 369 |
+
return lines
|
| 370 |
+
|
| 371 |
+
def flush(self) -> list[str]:
|
| 372 |
+
if not self.buffer and not self.trailing_cr:
|
| 373 |
+
return []
|
| 374 |
+
|
| 375 |
+
lines = ["".join(self.buffer)]
|
| 376 |
+
self.buffer = []
|
| 377 |
+
self.trailing_cr = False
|
| 378 |
+
return lines
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
SUPPORTED_DECODERS = {
|
| 382 |
+
"identity": IdentityDecoder,
|
| 383 |
+
"gzip": GZipDecoder,
|
| 384 |
+
"deflate": DeflateDecoder,
|
| 385 |
+
"br": BrotliDecoder,
|
| 386 |
+
"zstd": ZStandardDecoder,
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
if brotli is None:
|
| 391 |
+
SUPPORTED_DECODERS.pop("br") # pragma: no cover
|
| 392 |
+
if zstandard is None:
|
| 393 |
+
SUPPORTED_DECODERS.pop("zstd") # pragma: no cover
|
.venv/lib/python3.14/site-packages/httpx/_exceptions.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Our exception hierarchy:
|
| 3 |
+
|
| 4 |
+
* HTTPError
|
| 5 |
+
x RequestError
|
| 6 |
+
+ TransportError
|
| 7 |
+
- TimeoutException
|
| 8 |
+
· ConnectTimeout
|
| 9 |
+
· ReadTimeout
|
| 10 |
+
· WriteTimeout
|
| 11 |
+
· PoolTimeout
|
| 12 |
+
- NetworkError
|
| 13 |
+
· ConnectError
|
| 14 |
+
· ReadError
|
| 15 |
+
· WriteError
|
| 16 |
+
· CloseError
|
| 17 |
+
- ProtocolError
|
| 18 |
+
· LocalProtocolError
|
| 19 |
+
· RemoteProtocolError
|
| 20 |
+
- ProxyError
|
| 21 |
+
- UnsupportedProtocol
|
| 22 |
+
+ DecodingError
|
| 23 |
+
+ TooManyRedirects
|
| 24 |
+
x HTTPStatusError
|
| 25 |
+
* InvalidURL
|
| 26 |
+
* CookieConflict
|
| 27 |
+
* StreamError
|
| 28 |
+
x StreamConsumed
|
| 29 |
+
x StreamClosed
|
| 30 |
+
x ResponseNotRead
|
| 31 |
+
x RequestNotRead
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
from __future__ import annotations
|
| 35 |
+
|
| 36 |
+
import contextlib
|
| 37 |
+
import typing
|
| 38 |
+
|
| 39 |
+
if typing.TYPE_CHECKING:
|
| 40 |
+
from ._models import Request, Response # pragma: no cover
|
| 41 |
+
|
| 42 |
+
__all__ = [
|
| 43 |
+
"CloseError",
|
| 44 |
+
"ConnectError",
|
| 45 |
+
"ConnectTimeout",
|
| 46 |
+
"CookieConflict",
|
| 47 |
+
"DecodingError",
|
| 48 |
+
"HTTPError",
|
| 49 |
+
"HTTPStatusError",
|
| 50 |
+
"InvalidURL",
|
| 51 |
+
"LocalProtocolError",
|
| 52 |
+
"NetworkError",
|
| 53 |
+
"PoolTimeout",
|
| 54 |
+
"ProtocolError",
|
| 55 |
+
"ProxyError",
|
| 56 |
+
"ReadError",
|
| 57 |
+
"ReadTimeout",
|
| 58 |
+
"RemoteProtocolError",
|
| 59 |
+
"RequestError",
|
| 60 |
+
"RequestNotRead",
|
| 61 |
+
"ResponseNotRead",
|
| 62 |
+
"StreamClosed",
|
| 63 |
+
"StreamConsumed",
|
| 64 |
+
"StreamError",
|
| 65 |
+
"TimeoutException",
|
| 66 |
+
"TooManyRedirects",
|
| 67 |
+
"TransportError",
|
| 68 |
+
"UnsupportedProtocol",
|
| 69 |
+
"WriteError",
|
| 70 |
+
"WriteTimeout",
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class HTTPError(Exception):
|
| 75 |
+
"""
|
| 76 |
+
Base class for `RequestError` and `HTTPStatusError`.
|
| 77 |
+
|
| 78 |
+
Useful for `try...except` blocks when issuing a request,
|
| 79 |
+
and then calling `.raise_for_status()`.
|
| 80 |
+
|
| 81 |
+
For example:
|
| 82 |
+
|
| 83 |
+
```
|
| 84 |
+
try:
|
| 85 |
+
response = httpx.get("https://www.example.com")
|
| 86 |
+
response.raise_for_status()
|
| 87 |
+
except httpx.HTTPError as exc:
|
| 88 |
+
print(f"HTTP Exception for {exc.request.url} - {exc}")
|
| 89 |
+
```
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(self, message: str) -> None:
|
| 93 |
+
super().__init__(message)
|
| 94 |
+
self._request: Request | None = None
|
| 95 |
+
|
| 96 |
+
@property
|
| 97 |
+
def request(self) -> Request:
|
| 98 |
+
if self._request is None:
|
| 99 |
+
raise RuntimeError("The .request property has not been set.")
|
| 100 |
+
return self._request
|
| 101 |
+
|
| 102 |
+
@request.setter
|
| 103 |
+
def request(self, request: Request) -> None:
|
| 104 |
+
self._request = request
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class RequestError(HTTPError):
|
| 108 |
+
"""
|
| 109 |
+
Base class for all exceptions that may occur when issuing a `.request()`.
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def __init__(self, message: str, *, request: Request | None = None) -> None:
|
| 113 |
+
super().__init__(message)
|
| 114 |
+
# At the point an exception is raised we won't typically have a request
|
| 115 |
+
# instance to associate it with.
|
| 116 |
+
#
|
| 117 |
+
# The 'request_context' context manager is used within the Client and
|
| 118 |
+
# Response methods in order to ensure that any raised exceptions
|
| 119 |
+
# have a `.request` property set on them.
|
| 120 |
+
self._request = request
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class TransportError(RequestError):
|
| 124 |
+
"""
|
| 125 |
+
Base class for all exceptions that occur at the level of the Transport API.
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# Timeout exceptions...
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class TimeoutException(TransportError):
|
| 133 |
+
"""
|
| 134 |
+
The base class for timeout errors.
|
| 135 |
+
|
| 136 |
+
An operation has timed out.
|
| 137 |
+
"""
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
class ConnectTimeout(TimeoutException):
|
| 141 |
+
"""
|
| 142 |
+
Timed out while connecting to the host.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class ReadTimeout(TimeoutException):
|
| 147 |
+
"""
|
| 148 |
+
Timed out while receiving data from the host.
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class WriteTimeout(TimeoutException):
|
| 153 |
+
"""
|
| 154 |
+
Timed out while sending data to the host.
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class PoolTimeout(TimeoutException):
|
| 159 |
+
"""
|
| 160 |
+
Timed out waiting to acquire a connection from the pool.
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# Core networking exceptions...
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class NetworkError(TransportError):
|
| 168 |
+
"""
|
| 169 |
+
The base class for network-related errors.
|
| 170 |
+
|
| 171 |
+
An error occurred while interacting with the network.
|
| 172 |
+
"""
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
class ReadError(NetworkError):
|
| 176 |
+
"""
|
| 177 |
+
Failed to receive data from the network.
|
| 178 |
+
"""
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class WriteError(NetworkError):
|
| 182 |
+
"""
|
| 183 |
+
Failed to send data through the network.
|
| 184 |
+
"""
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class ConnectError(NetworkError):
|
| 188 |
+
"""
|
| 189 |
+
Failed to establish a connection.
|
| 190 |
+
"""
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class CloseError(NetworkError):
|
| 194 |
+
"""
|
| 195 |
+
Failed to close a connection.
|
| 196 |
+
"""
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# Other transport exceptions...
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
class ProxyError(TransportError):
|
| 203 |
+
"""
|
| 204 |
+
An error occurred while establishing a proxy connection.
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class UnsupportedProtocol(TransportError):
|
| 209 |
+
"""
|
| 210 |
+
Attempted to make a request to an unsupported protocol.
|
| 211 |
+
|
| 212 |
+
For example issuing a request to `ftp://www.example.com`.
|
| 213 |
+
"""
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class ProtocolError(TransportError):
|
| 217 |
+
"""
|
| 218 |
+
The protocol was violated.
|
| 219 |
+
"""
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
class LocalProtocolError(ProtocolError):
|
| 223 |
+
"""
|
| 224 |
+
A protocol was violated by the client.
|
| 225 |
+
|
| 226 |
+
For example if the user instantiated a `Request` instance explicitly,
|
| 227 |
+
failed to include the mandatory `Host:` header, and then issued it directly
|
| 228 |
+
using `client.send()`.
|
| 229 |
+
"""
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
class RemoteProtocolError(ProtocolError):
|
| 233 |
+
"""
|
| 234 |
+
The protocol was violated by the server.
|
| 235 |
+
|
| 236 |
+
For example, returning malformed HTTP.
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
# Other request exceptions...
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
class DecodingError(RequestError):
|
| 244 |
+
"""
|
| 245 |
+
Decoding of the response failed, due to a malformed encoding.
|
| 246 |
+
"""
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
class TooManyRedirects(RequestError):
|
| 250 |
+
"""
|
| 251 |
+
Too many redirects.
|
| 252 |
+
"""
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
# Client errors
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
class HTTPStatusError(HTTPError):
|
| 259 |
+
"""
|
| 260 |
+
The response had an error HTTP status of 4xx or 5xx.
|
| 261 |
+
|
| 262 |
+
May be raised when calling `response.raise_for_status()`
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
def __init__(self, message: str, *, request: Request, response: Response) -> None:
|
| 266 |
+
super().__init__(message)
|
| 267 |
+
self.request = request
|
| 268 |
+
self.response = response
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
class InvalidURL(Exception):
|
| 272 |
+
"""
|
| 273 |
+
URL is improperly formed or cannot be parsed.
|
| 274 |
+
"""
|
| 275 |
+
|
| 276 |
+
def __init__(self, message: str) -> None:
|
| 277 |
+
super().__init__(message)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
class CookieConflict(Exception):
|
| 281 |
+
"""
|
| 282 |
+
Attempted to lookup a cookie by name, but multiple cookies existed.
|
| 283 |
+
|
| 284 |
+
Can occur when calling `response.cookies.get(...)`.
|
| 285 |
+
"""
|
| 286 |
+
|
| 287 |
+
def __init__(self, message: str) -> None:
|
| 288 |
+
super().__init__(message)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
# Stream exceptions...
|
| 292 |
+
|
| 293 |
+
# These may occur as the result of a programming error, by accessing
|
| 294 |
+
# the request/response stream in an invalid manner.
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
class StreamError(RuntimeError):
|
| 298 |
+
"""
|
| 299 |
+
The base class for stream exceptions.
|
| 300 |
+
|
| 301 |
+
The developer made an error in accessing the request stream in
|
| 302 |
+
an invalid way.
|
| 303 |
+
"""
|
| 304 |
+
|
| 305 |
+
def __init__(self, message: str) -> None:
|
| 306 |
+
super().__init__(message)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class StreamConsumed(StreamError):
|
| 310 |
+
"""
|
| 311 |
+
Attempted to read or stream content, but the content has already
|
| 312 |
+
been streamed.
|
| 313 |
+
"""
|
| 314 |
+
|
| 315 |
+
def __init__(self) -> None:
|
| 316 |
+
message = (
|
| 317 |
+
"Attempted to read or stream some content, but the content has "
|
| 318 |
+
"already been streamed. For requests, this could be due to passing "
|
| 319 |
+
"a generator as request content, and then receiving a redirect "
|
| 320 |
+
"response or a secondary request as part of an authentication flow."
|
| 321 |
+
"For responses, this could be due to attempting to stream the response "
|
| 322 |
+
"content more than once."
|
| 323 |
+
)
|
| 324 |
+
super().__init__(message)
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
class StreamClosed(StreamError):
|
| 328 |
+
"""
|
| 329 |
+
Attempted to read or stream response content, but the request has been
|
| 330 |
+
closed.
|
| 331 |
+
"""
|
| 332 |
+
|
| 333 |
+
def __init__(self) -> None:
|
| 334 |
+
message = (
|
| 335 |
+
"Attempted to read or stream content, but the stream has " "been closed."
|
| 336 |
+
)
|
| 337 |
+
super().__init__(message)
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
class ResponseNotRead(StreamError):
|
| 341 |
+
"""
|
| 342 |
+
Attempted to access streaming response content, without having called `read()`.
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
def __init__(self) -> None:
|
| 346 |
+
message = (
|
| 347 |
+
"Attempted to access streaming response content,"
|
| 348 |
+
" without having called `read()`."
|
| 349 |
+
)
|
| 350 |
+
super().__init__(message)
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
class RequestNotRead(StreamError):
|
| 354 |
+
"""
|
| 355 |
+
Attempted to access streaming request content, without having called `read()`.
|
| 356 |
+
"""
|
| 357 |
+
|
| 358 |
+
def __init__(self) -> None:
|
| 359 |
+
message = (
|
| 360 |
+
"Attempted to access streaming request content,"
|
| 361 |
+
" without having called `read()`."
|
| 362 |
+
)
|
| 363 |
+
super().__init__(message)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
@contextlib.contextmanager
|
| 367 |
+
def request_context(
|
| 368 |
+
request: Request | None = None,
|
| 369 |
+
) -> typing.Iterator[None]:
|
| 370 |
+
"""
|
| 371 |
+
A context manager that can be used to attach the given request context
|
| 372 |
+
to any `RequestError` exceptions that are raised within the block.
|
| 373 |
+
"""
|
| 374 |
+
try:
|
| 375 |
+
yield
|
| 376 |
+
except RequestError as exc:
|
| 377 |
+
if request is not None:
|
| 378 |
+
exc.request = request
|
| 379 |
+
raise exc
|
.venv/lib/python3.14/site-packages/httpx/_main.py
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import functools
|
| 4 |
+
import json
|
| 5 |
+
import sys
|
| 6 |
+
import typing
|
| 7 |
+
|
| 8 |
+
import click
|
| 9 |
+
import pygments.lexers
|
| 10 |
+
import pygments.util
|
| 11 |
+
import rich.console
|
| 12 |
+
import rich.markup
|
| 13 |
+
import rich.progress
|
| 14 |
+
import rich.syntax
|
| 15 |
+
import rich.table
|
| 16 |
+
|
| 17 |
+
from ._client import Client
|
| 18 |
+
from ._exceptions import RequestError
|
| 19 |
+
from ._models import Response
|
| 20 |
+
from ._status_codes import codes
|
| 21 |
+
|
| 22 |
+
if typing.TYPE_CHECKING:
|
| 23 |
+
import httpcore # pragma: no cover
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def print_help() -> None:
|
| 27 |
+
console = rich.console.Console()
|
| 28 |
+
|
| 29 |
+
console.print("[bold]HTTPX :butterfly:", justify="center")
|
| 30 |
+
console.print()
|
| 31 |
+
console.print("A next generation HTTP client.", justify="center")
|
| 32 |
+
console.print()
|
| 33 |
+
console.print(
|
| 34 |
+
"Usage: [bold]httpx[/bold] [cyan]<URL> [OPTIONS][/cyan] ", justify="left"
|
| 35 |
+
)
|
| 36 |
+
console.print()
|
| 37 |
+
|
| 38 |
+
table = rich.table.Table.grid(padding=1, pad_edge=True)
|
| 39 |
+
table.add_column("Parameter", no_wrap=True, justify="left", style="bold")
|
| 40 |
+
table.add_column("Description")
|
| 41 |
+
table.add_row(
|
| 42 |
+
"-m, --method [cyan]METHOD",
|
| 43 |
+
"Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\n"
|
| 44 |
+
"[Default: GET, or POST if a request body is included]",
|
| 45 |
+
)
|
| 46 |
+
table.add_row(
|
| 47 |
+
"-p, --params [cyan]<NAME VALUE> ...",
|
| 48 |
+
"Query parameters to include in the request URL.",
|
| 49 |
+
)
|
| 50 |
+
table.add_row(
|
| 51 |
+
"-c, --content [cyan]TEXT", "Byte content to include in the request body."
|
| 52 |
+
)
|
| 53 |
+
table.add_row(
|
| 54 |
+
"-d, --data [cyan]<NAME VALUE> ...", "Form data to include in the request body."
|
| 55 |
+
)
|
| 56 |
+
table.add_row(
|
| 57 |
+
"-f, --files [cyan]<NAME FILENAME> ...",
|
| 58 |
+
"Form files to include in the request body.",
|
| 59 |
+
)
|
| 60 |
+
table.add_row("-j, --json [cyan]TEXT", "JSON data to include in the request body.")
|
| 61 |
+
table.add_row(
|
| 62 |
+
"-h, --headers [cyan]<NAME VALUE> ...",
|
| 63 |
+
"Include additional HTTP headers in the request.",
|
| 64 |
+
)
|
| 65 |
+
table.add_row(
|
| 66 |
+
"--cookies [cyan]<NAME VALUE> ...", "Cookies to include in the request."
|
| 67 |
+
)
|
| 68 |
+
table.add_row(
|
| 69 |
+
"--auth [cyan]<USER PASS>",
|
| 70 |
+
"Username and password to include in the request. Specify '-' for the password"
|
| 71 |
+
" to use a password prompt. Note that using --verbose/-v will expose"
|
| 72 |
+
" the Authorization header, including the password encoding"
|
| 73 |
+
" in a trivially reversible format.",
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
table.add_row(
|
| 77 |
+
"--proxy [cyan]URL",
|
| 78 |
+
"Send the request via a proxy. Should be the URL giving the proxy address.",
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
table.add_row(
|
| 82 |
+
"--timeout [cyan]FLOAT",
|
| 83 |
+
"Timeout value to use for network operations, such as establishing the"
|
| 84 |
+
" connection, reading some data, etc... [Default: 5.0]",
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
table.add_row("--follow-redirects", "Automatically follow redirects.")
|
| 88 |
+
table.add_row("--no-verify", "Disable SSL verification.")
|
| 89 |
+
table.add_row(
|
| 90 |
+
"--http2", "Send the request using HTTP/2, if the remote server supports it."
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
table.add_row(
|
| 94 |
+
"--download [cyan]FILE",
|
| 95 |
+
"Save the response content as a file, rather than displaying it.",
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
table.add_row("-v, --verbose", "Verbose output. Show request as well as response.")
|
| 99 |
+
table.add_row("--help", "Show this message and exit.")
|
| 100 |
+
console.print(table)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_lexer_for_response(response: Response) -> str:
|
| 104 |
+
content_type = response.headers.get("Content-Type")
|
| 105 |
+
if content_type is not None:
|
| 106 |
+
mime_type, _, _ = content_type.partition(";")
|
| 107 |
+
try:
|
| 108 |
+
return typing.cast(
|
| 109 |
+
str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name
|
| 110 |
+
)
|
| 111 |
+
except pygments.util.ClassNotFound: # pragma: no cover
|
| 112 |
+
pass
|
| 113 |
+
return "" # pragma: no cover
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def format_request_headers(request: httpcore.Request, http2: bool = False) -> str:
|
| 117 |
+
version = "HTTP/2" if http2 else "HTTP/1.1"
|
| 118 |
+
headers = [
|
| 119 |
+
(name.lower() if http2 else name, value) for name, value in request.headers
|
| 120 |
+
]
|
| 121 |
+
method = request.method.decode("ascii")
|
| 122 |
+
target = request.url.target.decode("ascii")
|
| 123 |
+
lines = [f"{method} {target} {version}"] + [
|
| 124 |
+
f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
|
| 125 |
+
]
|
| 126 |
+
return "\n".join(lines)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def format_response_headers(
|
| 130 |
+
http_version: bytes,
|
| 131 |
+
status: int,
|
| 132 |
+
reason_phrase: bytes | None,
|
| 133 |
+
headers: list[tuple[bytes, bytes]],
|
| 134 |
+
) -> str:
|
| 135 |
+
version = http_version.decode("ascii")
|
| 136 |
+
reason = (
|
| 137 |
+
codes.get_reason_phrase(status)
|
| 138 |
+
if reason_phrase is None
|
| 139 |
+
else reason_phrase.decode("ascii")
|
| 140 |
+
)
|
| 141 |
+
lines = [f"{version} {status} {reason}"] + [
|
| 142 |
+
f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
|
| 143 |
+
]
|
| 144 |
+
return "\n".join(lines)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def print_request_headers(request: httpcore.Request, http2: bool = False) -> None:
|
| 148 |
+
console = rich.console.Console()
|
| 149 |
+
http_text = format_request_headers(request, http2=http2)
|
| 150 |
+
syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
|
| 151 |
+
console.print(syntax)
|
| 152 |
+
syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
|
| 153 |
+
console.print(syntax)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def print_response_headers(
|
| 157 |
+
http_version: bytes,
|
| 158 |
+
status: int,
|
| 159 |
+
reason_phrase: bytes | None,
|
| 160 |
+
headers: list[tuple[bytes, bytes]],
|
| 161 |
+
) -> None:
|
| 162 |
+
console = rich.console.Console()
|
| 163 |
+
http_text = format_response_headers(http_version, status, reason_phrase, headers)
|
| 164 |
+
syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
|
| 165 |
+
console.print(syntax)
|
| 166 |
+
syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
|
| 167 |
+
console.print(syntax)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def print_response(response: Response) -> None:
|
| 171 |
+
console = rich.console.Console()
|
| 172 |
+
lexer_name = get_lexer_for_response(response)
|
| 173 |
+
if lexer_name:
|
| 174 |
+
if lexer_name.lower() == "json":
|
| 175 |
+
try:
|
| 176 |
+
data = response.json()
|
| 177 |
+
text = json.dumps(data, indent=4)
|
| 178 |
+
except ValueError: # pragma: no cover
|
| 179 |
+
text = response.text
|
| 180 |
+
else:
|
| 181 |
+
text = response.text
|
| 182 |
+
|
| 183 |
+
syntax = rich.syntax.Syntax(text, lexer_name, theme="ansi_dark", word_wrap=True)
|
| 184 |
+
console.print(syntax)
|
| 185 |
+
else:
|
| 186 |
+
console.print(f"<{len(response.content)} bytes of binary data>")
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...]
|
| 190 |
+
_PCTRTTT = typing.Tuple[_PCTRTT, ...]
|
| 191 |
+
_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]]
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover
|
| 195 |
+
lines = []
|
| 196 |
+
for key, value in cert.items():
|
| 197 |
+
if isinstance(value, (list, tuple)):
|
| 198 |
+
lines.append(f"* {key}:")
|
| 199 |
+
for item in value:
|
| 200 |
+
if key in ("subject", "issuer"):
|
| 201 |
+
for sub_item in item:
|
| 202 |
+
lines.append(f"* {sub_item[0]}: {sub_item[1]!r}")
|
| 203 |
+
elif isinstance(item, tuple) and len(item) == 2:
|
| 204 |
+
lines.append(f"* {item[0]}: {item[1]!r}")
|
| 205 |
+
else:
|
| 206 |
+
lines.append(f"* {item!r}")
|
| 207 |
+
else:
|
| 208 |
+
lines.append(f"* {key}: {value!r}")
|
| 209 |
+
return "\n".join(lines)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def trace(
|
| 213 |
+
name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False
|
| 214 |
+
) -> None:
|
| 215 |
+
console = rich.console.Console()
|
| 216 |
+
if name == "connection.connect_tcp.started" and verbose:
|
| 217 |
+
host = info["host"]
|
| 218 |
+
console.print(f"* Connecting to {host!r}")
|
| 219 |
+
elif name == "connection.connect_tcp.complete" and verbose:
|
| 220 |
+
stream = info["return_value"]
|
| 221 |
+
server_addr = stream.get_extra_info("server_addr")
|
| 222 |
+
console.print(f"* Connected to {server_addr[0]!r} on port {server_addr[1]}")
|
| 223 |
+
elif name == "connection.start_tls.complete" and verbose: # pragma: no cover
|
| 224 |
+
stream = info["return_value"]
|
| 225 |
+
ssl_object = stream.get_extra_info("ssl_object")
|
| 226 |
+
version = ssl_object.version()
|
| 227 |
+
cipher = ssl_object.cipher()
|
| 228 |
+
server_cert = ssl_object.getpeercert()
|
| 229 |
+
alpn = ssl_object.selected_alpn_protocol()
|
| 230 |
+
console.print(f"* SSL established using {version!r} / {cipher[0]!r}")
|
| 231 |
+
console.print(f"* Selected ALPN protocol: {alpn!r}")
|
| 232 |
+
if server_cert:
|
| 233 |
+
console.print("* Server certificate:")
|
| 234 |
+
console.print(format_certificate(server_cert))
|
| 235 |
+
elif name == "http11.send_request_headers.started" and verbose:
|
| 236 |
+
request = info["request"]
|
| 237 |
+
print_request_headers(request, http2=False)
|
| 238 |
+
elif name == "http2.send_request_headers.started" and verbose: # pragma: no cover
|
| 239 |
+
request = info["request"]
|
| 240 |
+
print_request_headers(request, http2=True)
|
| 241 |
+
elif name == "http11.receive_response_headers.complete":
|
| 242 |
+
http_version, status, reason_phrase, headers = info["return_value"]
|
| 243 |
+
print_response_headers(http_version, status, reason_phrase, headers)
|
| 244 |
+
elif name == "http2.receive_response_headers.complete": # pragma: no cover
|
| 245 |
+
status, headers = info["return_value"]
|
| 246 |
+
http_version = b"HTTP/2"
|
| 247 |
+
reason_phrase = None
|
| 248 |
+
print_response_headers(http_version, status, reason_phrase, headers)
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def download_response(response: Response, download: typing.BinaryIO) -> None:
|
| 252 |
+
console = rich.console.Console()
|
| 253 |
+
console.print()
|
| 254 |
+
content_length = response.headers.get("Content-Length")
|
| 255 |
+
with rich.progress.Progress(
|
| 256 |
+
"[progress.description]{task.description}",
|
| 257 |
+
"[progress.percentage]{task.percentage:>3.0f}%",
|
| 258 |
+
rich.progress.BarColumn(bar_width=None),
|
| 259 |
+
rich.progress.DownloadColumn(),
|
| 260 |
+
rich.progress.TransferSpeedColumn(),
|
| 261 |
+
) as progress:
|
| 262 |
+
description = f"Downloading [bold]{rich.markup.escape(download.name)}"
|
| 263 |
+
download_task = progress.add_task(
|
| 264 |
+
description,
|
| 265 |
+
total=int(content_length or 0),
|
| 266 |
+
start=content_length is not None,
|
| 267 |
+
)
|
| 268 |
+
for chunk in response.iter_bytes():
|
| 269 |
+
download.write(chunk)
|
| 270 |
+
progress.update(download_task, completed=response.num_bytes_downloaded)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def validate_json(
|
| 274 |
+
ctx: click.Context,
|
| 275 |
+
param: click.Option | click.Parameter,
|
| 276 |
+
value: typing.Any,
|
| 277 |
+
) -> typing.Any:
|
| 278 |
+
if value is None:
|
| 279 |
+
return None
|
| 280 |
+
|
| 281 |
+
try:
|
| 282 |
+
return json.loads(value)
|
| 283 |
+
except json.JSONDecodeError: # pragma: no cover
|
| 284 |
+
raise click.BadParameter("Not valid JSON")
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def validate_auth(
|
| 288 |
+
ctx: click.Context,
|
| 289 |
+
param: click.Option | click.Parameter,
|
| 290 |
+
value: typing.Any,
|
| 291 |
+
) -> typing.Any:
|
| 292 |
+
if value == (None, None):
|
| 293 |
+
return None
|
| 294 |
+
|
| 295 |
+
username, password = value
|
| 296 |
+
if password == "-": # pragma: no cover
|
| 297 |
+
password = click.prompt("Password", hide_input=True)
|
| 298 |
+
return (username, password)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def handle_help(
|
| 302 |
+
ctx: click.Context,
|
| 303 |
+
param: click.Option | click.Parameter,
|
| 304 |
+
value: typing.Any,
|
| 305 |
+
) -> None:
|
| 306 |
+
if not value or ctx.resilient_parsing:
|
| 307 |
+
return
|
| 308 |
+
|
| 309 |
+
print_help()
|
| 310 |
+
ctx.exit()
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
@click.command(add_help_option=False)
|
| 314 |
+
@click.argument("url", type=str)
|
| 315 |
+
@click.option(
|
| 316 |
+
"--method",
|
| 317 |
+
"-m",
|
| 318 |
+
"method",
|
| 319 |
+
type=str,
|
| 320 |
+
help=(
|
| 321 |
+
"Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. "
|
| 322 |
+
"[Default: GET, or POST if a request body is included]"
|
| 323 |
+
),
|
| 324 |
+
)
|
| 325 |
+
@click.option(
|
| 326 |
+
"--params",
|
| 327 |
+
"-p",
|
| 328 |
+
"params",
|
| 329 |
+
type=(str, str),
|
| 330 |
+
multiple=True,
|
| 331 |
+
help="Query parameters to include in the request URL.",
|
| 332 |
+
)
|
| 333 |
+
@click.option(
|
| 334 |
+
"--content",
|
| 335 |
+
"-c",
|
| 336 |
+
"content",
|
| 337 |
+
type=str,
|
| 338 |
+
help="Byte content to include in the request body.",
|
| 339 |
+
)
|
| 340 |
+
@click.option(
|
| 341 |
+
"--data",
|
| 342 |
+
"-d",
|
| 343 |
+
"data",
|
| 344 |
+
type=(str, str),
|
| 345 |
+
multiple=True,
|
| 346 |
+
help="Form data to include in the request body.",
|
| 347 |
+
)
|
| 348 |
+
@click.option(
|
| 349 |
+
"--files",
|
| 350 |
+
"-f",
|
| 351 |
+
"files",
|
| 352 |
+
type=(str, click.File(mode="rb")),
|
| 353 |
+
multiple=True,
|
| 354 |
+
help="Form files to include in the request body.",
|
| 355 |
+
)
|
| 356 |
+
@click.option(
|
| 357 |
+
"--json",
|
| 358 |
+
"-j",
|
| 359 |
+
"json",
|
| 360 |
+
type=str,
|
| 361 |
+
callback=validate_json,
|
| 362 |
+
help="JSON data to include in the request body.",
|
| 363 |
+
)
|
| 364 |
+
@click.option(
|
| 365 |
+
"--headers",
|
| 366 |
+
"-h",
|
| 367 |
+
"headers",
|
| 368 |
+
type=(str, str),
|
| 369 |
+
multiple=True,
|
| 370 |
+
help="Include additional HTTP headers in the request.",
|
| 371 |
+
)
|
| 372 |
+
@click.option(
|
| 373 |
+
"--cookies",
|
| 374 |
+
"cookies",
|
| 375 |
+
type=(str, str),
|
| 376 |
+
multiple=True,
|
| 377 |
+
help="Cookies to include in the request.",
|
| 378 |
+
)
|
| 379 |
+
@click.option(
|
| 380 |
+
"--auth",
|
| 381 |
+
"auth",
|
| 382 |
+
type=(str, str),
|
| 383 |
+
default=(None, None),
|
| 384 |
+
callback=validate_auth,
|
| 385 |
+
help=(
|
| 386 |
+
"Username and password to include in the request. "
|
| 387 |
+
"Specify '-' for the password to use a password prompt. "
|
| 388 |
+
"Note that using --verbose/-v will expose the Authorization header, "
|
| 389 |
+
"including the password encoding in a trivially reversible format."
|
| 390 |
+
),
|
| 391 |
+
)
|
| 392 |
+
@click.option(
|
| 393 |
+
"--proxy",
|
| 394 |
+
"proxy",
|
| 395 |
+
type=str,
|
| 396 |
+
default=None,
|
| 397 |
+
help="Send the request via a proxy. Should be the URL giving the proxy address.",
|
| 398 |
+
)
|
| 399 |
+
@click.option(
|
| 400 |
+
"--timeout",
|
| 401 |
+
"timeout",
|
| 402 |
+
type=float,
|
| 403 |
+
default=5.0,
|
| 404 |
+
help=(
|
| 405 |
+
"Timeout value to use for network operations, such as establishing the "
|
| 406 |
+
"connection, reading some data, etc... [Default: 5.0]"
|
| 407 |
+
),
|
| 408 |
+
)
|
| 409 |
+
@click.option(
|
| 410 |
+
"--follow-redirects",
|
| 411 |
+
"follow_redirects",
|
| 412 |
+
is_flag=True,
|
| 413 |
+
default=False,
|
| 414 |
+
help="Automatically follow redirects.",
|
| 415 |
+
)
|
| 416 |
+
@click.option(
|
| 417 |
+
"--no-verify",
|
| 418 |
+
"verify",
|
| 419 |
+
is_flag=True,
|
| 420 |
+
default=True,
|
| 421 |
+
help="Disable SSL verification.",
|
| 422 |
+
)
|
| 423 |
+
@click.option(
|
| 424 |
+
"--http2",
|
| 425 |
+
"http2",
|
| 426 |
+
type=bool,
|
| 427 |
+
is_flag=True,
|
| 428 |
+
default=False,
|
| 429 |
+
help="Send the request using HTTP/2, if the remote server supports it.",
|
| 430 |
+
)
|
| 431 |
+
@click.option(
|
| 432 |
+
"--download",
|
| 433 |
+
type=click.File("wb"),
|
| 434 |
+
help="Save the response content as a file, rather than displaying it.",
|
| 435 |
+
)
|
| 436 |
+
@click.option(
|
| 437 |
+
"--verbose",
|
| 438 |
+
"-v",
|
| 439 |
+
type=bool,
|
| 440 |
+
is_flag=True,
|
| 441 |
+
default=False,
|
| 442 |
+
help="Verbose. Show request as well as response.",
|
| 443 |
+
)
|
| 444 |
+
@click.option(
|
| 445 |
+
"--help",
|
| 446 |
+
is_flag=True,
|
| 447 |
+
is_eager=True,
|
| 448 |
+
expose_value=False,
|
| 449 |
+
callback=handle_help,
|
| 450 |
+
help="Show this message and exit.",
|
| 451 |
+
)
|
| 452 |
+
def main(
|
| 453 |
+
url: str,
|
| 454 |
+
method: str,
|
| 455 |
+
params: list[tuple[str, str]],
|
| 456 |
+
content: str,
|
| 457 |
+
data: list[tuple[str, str]],
|
| 458 |
+
files: list[tuple[str, click.File]],
|
| 459 |
+
json: str,
|
| 460 |
+
headers: list[tuple[str, str]],
|
| 461 |
+
cookies: list[tuple[str, str]],
|
| 462 |
+
auth: tuple[str, str] | None,
|
| 463 |
+
proxy: str,
|
| 464 |
+
timeout: float,
|
| 465 |
+
follow_redirects: bool,
|
| 466 |
+
verify: bool,
|
| 467 |
+
http2: bool,
|
| 468 |
+
download: typing.BinaryIO | None,
|
| 469 |
+
verbose: bool,
|
| 470 |
+
) -> None:
|
| 471 |
+
"""
|
| 472 |
+
An HTTP command line client.
|
| 473 |
+
Sends a request and displays the response.
|
| 474 |
+
"""
|
| 475 |
+
if not method:
|
| 476 |
+
method = "POST" if content or data or files or json else "GET"
|
| 477 |
+
|
| 478 |
+
try:
|
| 479 |
+
with Client(proxy=proxy, timeout=timeout, http2=http2, verify=verify) as client:
|
| 480 |
+
with client.stream(
|
| 481 |
+
method,
|
| 482 |
+
url,
|
| 483 |
+
params=list(params),
|
| 484 |
+
content=content,
|
| 485 |
+
data=dict(data),
|
| 486 |
+
files=files, # type: ignore
|
| 487 |
+
json=json,
|
| 488 |
+
headers=headers,
|
| 489 |
+
cookies=dict(cookies),
|
| 490 |
+
auth=auth,
|
| 491 |
+
follow_redirects=follow_redirects,
|
| 492 |
+
extensions={"trace": functools.partial(trace, verbose=verbose)},
|
| 493 |
+
) as response:
|
| 494 |
+
if download is not None:
|
| 495 |
+
download_response(response, download)
|
| 496 |
+
else:
|
| 497 |
+
response.read()
|
| 498 |
+
if response.content:
|
| 499 |
+
print_response(response)
|
| 500 |
+
|
| 501 |
+
except RequestError as exc:
|
| 502 |
+
console = rich.console.Console()
|
| 503 |
+
console.print(f"[red]{type(exc).__name__}[/red]: {exc}")
|
| 504 |
+
sys.exit(1)
|
| 505 |
+
|
| 506 |
+
sys.exit(0 if response.is_success else 1)
|
.venv/lib/python3.14/site-packages/httpx/_models.py
ADDED
|
@@ -0,0 +1,1277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import codecs
|
| 4 |
+
import datetime
|
| 5 |
+
import email.message
|
| 6 |
+
import json as jsonlib
|
| 7 |
+
import re
|
| 8 |
+
import typing
|
| 9 |
+
import urllib.request
|
| 10 |
+
from collections.abc import Mapping
|
| 11 |
+
from http.cookiejar import Cookie, CookieJar
|
| 12 |
+
|
| 13 |
+
from ._content import ByteStream, UnattachedStream, encode_request, encode_response
|
| 14 |
+
from ._decoders import (
|
| 15 |
+
SUPPORTED_DECODERS,
|
| 16 |
+
ByteChunker,
|
| 17 |
+
ContentDecoder,
|
| 18 |
+
IdentityDecoder,
|
| 19 |
+
LineDecoder,
|
| 20 |
+
MultiDecoder,
|
| 21 |
+
TextChunker,
|
| 22 |
+
TextDecoder,
|
| 23 |
+
)
|
| 24 |
+
from ._exceptions import (
|
| 25 |
+
CookieConflict,
|
| 26 |
+
HTTPStatusError,
|
| 27 |
+
RequestNotRead,
|
| 28 |
+
ResponseNotRead,
|
| 29 |
+
StreamClosed,
|
| 30 |
+
StreamConsumed,
|
| 31 |
+
request_context,
|
| 32 |
+
)
|
| 33 |
+
from ._multipart import get_multipart_boundary_from_content_type
|
| 34 |
+
from ._status_codes import codes
|
| 35 |
+
from ._types import (
|
| 36 |
+
AsyncByteStream,
|
| 37 |
+
CookieTypes,
|
| 38 |
+
HeaderTypes,
|
| 39 |
+
QueryParamTypes,
|
| 40 |
+
RequestContent,
|
| 41 |
+
RequestData,
|
| 42 |
+
RequestExtensions,
|
| 43 |
+
RequestFiles,
|
| 44 |
+
ResponseContent,
|
| 45 |
+
ResponseExtensions,
|
| 46 |
+
SyncByteStream,
|
| 47 |
+
)
|
| 48 |
+
from ._urls import URL
|
| 49 |
+
from ._utils import to_bytes_or_str, to_str
|
| 50 |
+
|
| 51 |
+
__all__ = ["Cookies", "Headers", "Request", "Response"]
|
| 52 |
+
|
| 53 |
+
SENSITIVE_HEADERS = {"authorization", "proxy-authorization"}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _is_known_encoding(encoding: str) -> bool:
|
| 57 |
+
"""
|
| 58 |
+
Return `True` if `encoding` is a known codec.
|
| 59 |
+
"""
|
| 60 |
+
try:
|
| 61 |
+
codecs.lookup(encoding)
|
| 62 |
+
except LookupError:
|
| 63 |
+
return False
|
| 64 |
+
return True
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes:
|
| 68 |
+
"""
|
| 69 |
+
Coerce str/bytes into a strictly byte-wise HTTP header key.
|
| 70 |
+
"""
|
| 71 |
+
return key if isinstance(key, bytes) else key.encode(encoding or "ascii")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes:
|
| 75 |
+
"""
|
| 76 |
+
Coerce str/bytes into a strictly byte-wise HTTP header value.
|
| 77 |
+
"""
|
| 78 |
+
if isinstance(value, bytes):
|
| 79 |
+
return value
|
| 80 |
+
if not isinstance(value, str):
|
| 81 |
+
raise TypeError(f"Header value must be str or bytes, not {type(value)}")
|
| 82 |
+
return value.encode(encoding or "ascii")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _parse_content_type_charset(content_type: str) -> str | None:
|
| 86 |
+
# We used to use `cgi.parse_header()` here, but `cgi` became a dead battery.
|
| 87 |
+
# See: https://peps.python.org/pep-0594/#cgi
|
| 88 |
+
msg = email.message.Message()
|
| 89 |
+
msg["content-type"] = content_type
|
| 90 |
+
return msg.get_content_charset(failobj=None)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _parse_header_links(value: str) -> list[dict[str, str]]:
|
| 94 |
+
"""
|
| 95 |
+
Returns a list of parsed link headers, for more info see:
|
| 96 |
+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
|
| 97 |
+
The generic syntax of those is:
|
| 98 |
+
Link: < uri-reference >; param1=value1; param2="value2"
|
| 99 |
+
So for instance:
|
| 100 |
+
Link; '<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;'
|
| 101 |
+
would return
|
| 102 |
+
[
|
| 103 |
+
{"url": "http:/.../front.jpeg", "type": "image/jpeg"},
|
| 104 |
+
{"url": "http://.../back.jpeg"},
|
| 105 |
+
]
|
| 106 |
+
:param value: HTTP Link entity-header field
|
| 107 |
+
:return: list of parsed link headers
|
| 108 |
+
"""
|
| 109 |
+
links: list[dict[str, str]] = []
|
| 110 |
+
replace_chars = " '\""
|
| 111 |
+
value = value.strip(replace_chars)
|
| 112 |
+
if not value:
|
| 113 |
+
return links
|
| 114 |
+
for val in re.split(", *<", value):
|
| 115 |
+
try:
|
| 116 |
+
url, params = val.split(";", 1)
|
| 117 |
+
except ValueError:
|
| 118 |
+
url, params = val, ""
|
| 119 |
+
link = {"url": url.strip("<> '\"")}
|
| 120 |
+
for param in params.split(";"):
|
| 121 |
+
try:
|
| 122 |
+
key, value = param.split("=")
|
| 123 |
+
except ValueError:
|
| 124 |
+
break
|
| 125 |
+
link[key.strip(replace_chars)] = value.strip(replace_chars)
|
| 126 |
+
links.append(link)
|
| 127 |
+
return links
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def _obfuscate_sensitive_headers(
|
| 131 |
+
items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]],
|
| 132 |
+
) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]:
|
| 133 |
+
for k, v in items:
|
| 134 |
+
if to_str(k.lower()) in SENSITIVE_HEADERS:
|
| 135 |
+
v = to_bytes_or_str("[secure]", match_type_of=v)
|
| 136 |
+
yield k, v
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class Headers(typing.MutableMapping[str, str]):
|
| 140 |
+
"""
|
| 141 |
+
HTTP headers, as a case-insensitive multi-dict.
|
| 142 |
+
"""
|
| 143 |
+
|
| 144 |
+
def __init__(
|
| 145 |
+
self,
|
| 146 |
+
headers: HeaderTypes | None = None,
|
| 147 |
+
encoding: str | None = None,
|
| 148 |
+
) -> None:
|
| 149 |
+
self._list = [] # type: typing.List[typing.Tuple[bytes, bytes, bytes]]
|
| 150 |
+
|
| 151 |
+
if isinstance(headers, Headers):
|
| 152 |
+
self._list = list(headers._list)
|
| 153 |
+
elif isinstance(headers, Mapping):
|
| 154 |
+
for k, v in headers.items():
|
| 155 |
+
bytes_key = _normalize_header_key(k, encoding)
|
| 156 |
+
bytes_value = _normalize_header_value(v, encoding)
|
| 157 |
+
self._list.append((bytes_key, bytes_key.lower(), bytes_value))
|
| 158 |
+
elif headers is not None:
|
| 159 |
+
for k, v in headers:
|
| 160 |
+
bytes_key = _normalize_header_key(k, encoding)
|
| 161 |
+
bytes_value = _normalize_header_value(v, encoding)
|
| 162 |
+
self._list.append((bytes_key, bytes_key.lower(), bytes_value))
|
| 163 |
+
|
| 164 |
+
self._encoding = encoding
|
| 165 |
+
|
| 166 |
+
@property
|
| 167 |
+
def encoding(self) -> str:
|
| 168 |
+
"""
|
| 169 |
+
Header encoding is mandated as ascii, but we allow fallbacks to utf-8
|
| 170 |
+
or iso-8859-1.
|
| 171 |
+
"""
|
| 172 |
+
if self._encoding is None:
|
| 173 |
+
for encoding in ["ascii", "utf-8"]:
|
| 174 |
+
for key, value in self.raw:
|
| 175 |
+
try:
|
| 176 |
+
key.decode(encoding)
|
| 177 |
+
value.decode(encoding)
|
| 178 |
+
except UnicodeDecodeError:
|
| 179 |
+
break
|
| 180 |
+
else:
|
| 181 |
+
# The else block runs if 'break' did not occur, meaning
|
| 182 |
+
# all values fitted the encoding.
|
| 183 |
+
self._encoding = encoding
|
| 184 |
+
break
|
| 185 |
+
else:
|
| 186 |
+
# The ISO-8859-1 encoding covers all 256 code points in a byte,
|
| 187 |
+
# so will never raise decode errors.
|
| 188 |
+
self._encoding = "iso-8859-1"
|
| 189 |
+
return self._encoding
|
| 190 |
+
|
| 191 |
+
@encoding.setter
|
| 192 |
+
def encoding(self, value: str) -> None:
|
| 193 |
+
self._encoding = value
|
| 194 |
+
|
| 195 |
+
@property
|
| 196 |
+
def raw(self) -> list[tuple[bytes, bytes]]:
|
| 197 |
+
"""
|
| 198 |
+
Returns a list of the raw header items, as byte pairs.
|
| 199 |
+
"""
|
| 200 |
+
return [(raw_key, value) for raw_key, _, value in self._list]
|
| 201 |
+
|
| 202 |
+
def keys(self) -> typing.KeysView[str]:
|
| 203 |
+
return {key.decode(self.encoding): None for _, key, value in self._list}.keys()
|
| 204 |
+
|
| 205 |
+
def values(self) -> typing.ValuesView[str]:
|
| 206 |
+
values_dict: dict[str, str] = {}
|
| 207 |
+
for _, key, value in self._list:
|
| 208 |
+
str_key = key.decode(self.encoding)
|
| 209 |
+
str_value = value.decode(self.encoding)
|
| 210 |
+
if str_key in values_dict:
|
| 211 |
+
values_dict[str_key] += f", {str_value}"
|
| 212 |
+
else:
|
| 213 |
+
values_dict[str_key] = str_value
|
| 214 |
+
return values_dict.values()
|
| 215 |
+
|
| 216 |
+
def items(self) -> typing.ItemsView[str, str]:
|
| 217 |
+
"""
|
| 218 |
+
Return `(key, value)` items of headers. Concatenate headers
|
| 219 |
+
into a single comma separated value when a key occurs multiple times.
|
| 220 |
+
"""
|
| 221 |
+
values_dict: dict[str, str] = {}
|
| 222 |
+
for _, key, value in self._list:
|
| 223 |
+
str_key = key.decode(self.encoding)
|
| 224 |
+
str_value = value.decode(self.encoding)
|
| 225 |
+
if str_key in values_dict:
|
| 226 |
+
values_dict[str_key] += f", {str_value}"
|
| 227 |
+
else:
|
| 228 |
+
values_dict[str_key] = str_value
|
| 229 |
+
return values_dict.items()
|
| 230 |
+
|
| 231 |
+
def multi_items(self) -> list[tuple[str, str]]:
|
| 232 |
+
"""
|
| 233 |
+
Return a list of `(key, value)` pairs of headers. Allow multiple
|
| 234 |
+
occurrences of the same key without concatenating into a single
|
| 235 |
+
comma separated value.
|
| 236 |
+
"""
|
| 237 |
+
return [
|
| 238 |
+
(key.decode(self.encoding), value.decode(self.encoding))
|
| 239 |
+
for _, key, value in self._list
|
| 240 |
+
]
|
| 241 |
+
|
| 242 |
+
def get(self, key: str, default: typing.Any = None) -> typing.Any:
|
| 243 |
+
"""
|
| 244 |
+
Return a header value. If multiple occurrences of the header occur
|
| 245 |
+
then concatenate them together with commas.
|
| 246 |
+
"""
|
| 247 |
+
try:
|
| 248 |
+
return self[key]
|
| 249 |
+
except KeyError:
|
| 250 |
+
return default
|
| 251 |
+
|
| 252 |
+
def get_list(self, key: str, split_commas: bool = False) -> list[str]:
|
| 253 |
+
"""
|
| 254 |
+
Return a list of all header values for a given key.
|
| 255 |
+
If `split_commas=True` is passed, then any comma separated header
|
| 256 |
+
values are split into multiple return strings.
|
| 257 |
+
"""
|
| 258 |
+
get_header_key = key.lower().encode(self.encoding)
|
| 259 |
+
|
| 260 |
+
values = [
|
| 261 |
+
item_value.decode(self.encoding)
|
| 262 |
+
for _, item_key, item_value in self._list
|
| 263 |
+
if item_key.lower() == get_header_key
|
| 264 |
+
]
|
| 265 |
+
|
| 266 |
+
if not split_commas:
|
| 267 |
+
return values
|
| 268 |
+
|
| 269 |
+
split_values = []
|
| 270 |
+
for value in values:
|
| 271 |
+
split_values.extend([item.strip() for item in value.split(",")])
|
| 272 |
+
return split_values
|
| 273 |
+
|
| 274 |
+
def update(self, headers: HeaderTypes | None = None) -> None: # type: ignore
|
| 275 |
+
headers = Headers(headers)
|
| 276 |
+
for key in headers.keys():
|
| 277 |
+
if key in self:
|
| 278 |
+
self.pop(key)
|
| 279 |
+
self._list.extend(headers._list)
|
| 280 |
+
|
| 281 |
+
def copy(self) -> Headers:
|
| 282 |
+
return Headers(self, encoding=self.encoding)
|
| 283 |
+
|
| 284 |
+
def __getitem__(self, key: str) -> str:
|
| 285 |
+
"""
|
| 286 |
+
Return a single header value.
|
| 287 |
+
|
| 288 |
+
If there are multiple headers with the same key, then we concatenate
|
| 289 |
+
them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2
|
| 290 |
+
"""
|
| 291 |
+
normalized_key = key.lower().encode(self.encoding)
|
| 292 |
+
|
| 293 |
+
items = [
|
| 294 |
+
header_value.decode(self.encoding)
|
| 295 |
+
for _, header_key, header_value in self._list
|
| 296 |
+
if header_key == normalized_key
|
| 297 |
+
]
|
| 298 |
+
|
| 299 |
+
if items:
|
| 300 |
+
return ", ".join(items)
|
| 301 |
+
|
| 302 |
+
raise KeyError(key)
|
| 303 |
+
|
| 304 |
+
def __setitem__(self, key: str, value: str) -> None:
|
| 305 |
+
"""
|
| 306 |
+
Set the header `key` to `value`, removing any duplicate entries.
|
| 307 |
+
Retains insertion order.
|
| 308 |
+
"""
|
| 309 |
+
set_key = key.encode(self._encoding or "utf-8")
|
| 310 |
+
set_value = value.encode(self._encoding or "utf-8")
|
| 311 |
+
lookup_key = set_key.lower()
|
| 312 |
+
|
| 313 |
+
found_indexes = [
|
| 314 |
+
idx
|
| 315 |
+
for idx, (_, item_key, _) in enumerate(self._list)
|
| 316 |
+
if item_key == lookup_key
|
| 317 |
+
]
|
| 318 |
+
|
| 319 |
+
for idx in reversed(found_indexes[1:]):
|
| 320 |
+
del self._list[idx]
|
| 321 |
+
|
| 322 |
+
if found_indexes:
|
| 323 |
+
idx = found_indexes[0]
|
| 324 |
+
self._list[idx] = (set_key, lookup_key, set_value)
|
| 325 |
+
else:
|
| 326 |
+
self._list.append((set_key, lookup_key, set_value))
|
| 327 |
+
|
| 328 |
+
def __delitem__(self, key: str) -> None:
|
| 329 |
+
"""
|
| 330 |
+
Remove the header `key`.
|
| 331 |
+
"""
|
| 332 |
+
del_key = key.lower().encode(self.encoding)
|
| 333 |
+
|
| 334 |
+
pop_indexes = [
|
| 335 |
+
idx
|
| 336 |
+
for idx, (_, item_key, _) in enumerate(self._list)
|
| 337 |
+
if item_key.lower() == del_key
|
| 338 |
+
]
|
| 339 |
+
|
| 340 |
+
if not pop_indexes:
|
| 341 |
+
raise KeyError(key)
|
| 342 |
+
|
| 343 |
+
for idx in reversed(pop_indexes):
|
| 344 |
+
del self._list[idx]
|
| 345 |
+
|
| 346 |
+
def __contains__(self, key: typing.Any) -> bool:
|
| 347 |
+
header_key = key.lower().encode(self.encoding)
|
| 348 |
+
return header_key in [key for _, key, _ in self._list]
|
| 349 |
+
|
| 350 |
+
def __iter__(self) -> typing.Iterator[typing.Any]:
|
| 351 |
+
return iter(self.keys())
|
| 352 |
+
|
| 353 |
+
def __len__(self) -> int:
|
| 354 |
+
return len(self._list)
|
| 355 |
+
|
| 356 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 357 |
+
try:
|
| 358 |
+
other_headers = Headers(other)
|
| 359 |
+
except ValueError:
|
| 360 |
+
return False
|
| 361 |
+
|
| 362 |
+
self_list = [(key, value) for _, key, value in self._list]
|
| 363 |
+
other_list = [(key, value) for _, key, value in other_headers._list]
|
| 364 |
+
return sorted(self_list) == sorted(other_list)
|
| 365 |
+
|
| 366 |
+
def __repr__(self) -> str:
|
| 367 |
+
class_name = self.__class__.__name__
|
| 368 |
+
|
| 369 |
+
encoding_str = ""
|
| 370 |
+
if self.encoding != "ascii":
|
| 371 |
+
encoding_str = f", encoding={self.encoding!r}"
|
| 372 |
+
|
| 373 |
+
as_list = list(_obfuscate_sensitive_headers(self.multi_items()))
|
| 374 |
+
as_dict = dict(as_list)
|
| 375 |
+
|
| 376 |
+
no_duplicate_keys = len(as_dict) == len(as_list)
|
| 377 |
+
if no_duplicate_keys:
|
| 378 |
+
return f"{class_name}({as_dict!r}{encoding_str})"
|
| 379 |
+
return f"{class_name}({as_list!r}{encoding_str})"
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
class Request:
|
| 383 |
+
def __init__(
|
| 384 |
+
self,
|
| 385 |
+
method: str,
|
| 386 |
+
url: URL | str,
|
| 387 |
+
*,
|
| 388 |
+
params: QueryParamTypes | None = None,
|
| 389 |
+
headers: HeaderTypes | None = None,
|
| 390 |
+
cookies: CookieTypes | None = None,
|
| 391 |
+
content: RequestContent | None = None,
|
| 392 |
+
data: RequestData | None = None,
|
| 393 |
+
files: RequestFiles | None = None,
|
| 394 |
+
json: typing.Any | None = None,
|
| 395 |
+
stream: SyncByteStream | AsyncByteStream | None = None,
|
| 396 |
+
extensions: RequestExtensions | None = None,
|
| 397 |
+
) -> None:
|
| 398 |
+
self.method = method.upper()
|
| 399 |
+
self.url = URL(url) if params is None else URL(url, params=params)
|
| 400 |
+
self.headers = Headers(headers)
|
| 401 |
+
self.extensions = {} if extensions is None else dict(extensions)
|
| 402 |
+
|
| 403 |
+
if cookies:
|
| 404 |
+
Cookies(cookies).set_cookie_header(self)
|
| 405 |
+
|
| 406 |
+
if stream is None:
|
| 407 |
+
content_type: str | None = self.headers.get("content-type")
|
| 408 |
+
headers, stream = encode_request(
|
| 409 |
+
content=content,
|
| 410 |
+
data=data,
|
| 411 |
+
files=files,
|
| 412 |
+
json=json,
|
| 413 |
+
boundary=get_multipart_boundary_from_content_type(
|
| 414 |
+
content_type=content_type.encode(self.headers.encoding)
|
| 415 |
+
if content_type
|
| 416 |
+
else None
|
| 417 |
+
),
|
| 418 |
+
)
|
| 419 |
+
self._prepare(headers)
|
| 420 |
+
self.stream = stream
|
| 421 |
+
# Load the request body, except for streaming content.
|
| 422 |
+
if isinstance(stream, ByteStream):
|
| 423 |
+
self.read()
|
| 424 |
+
else:
|
| 425 |
+
# There's an important distinction between `Request(content=...)`,
|
| 426 |
+
# and `Request(stream=...)`.
|
| 427 |
+
#
|
| 428 |
+
# Using `content=...` implies automatically populated `Host` and content
|
| 429 |
+
# headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`.
|
| 430 |
+
#
|
| 431 |
+
# Using `stream=...` will not automatically include *any*
|
| 432 |
+
# auto-populated headers.
|
| 433 |
+
#
|
| 434 |
+
# As an end-user you don't really need `stream=...`. It's only
|
| 435 |
+
# useful when:
|
| 436 |
+
#
|
| 437 |
+
# * Preserving the request stream when copying requests, eg for redirects.
|
| 438 |
+
# * Creating request instances on the *server-side* of the transport API.
|
| 439 |
+
self.stream = stream
|
| 440 |
+
|
| 441 |
+
def _prepare(self, default_headers: dict[str, str]) -> None:
|
| 442 |
+
for key, value in default_headers.items():
|
| 443 |
+
# Ignore Transfer-Encoding if the Content-Length has been set explicitly.
|
| 444 |
+
if key.lower() == "transfer-encoding" and "Content-Length" in self.headers:
|
| 445 |
+
continue
|
| 446 |
+
self.headers.setdefault(key, value)
|
| 447 |
+
|
| 448 |
+
auto_headers: list[tuple[bytes, bytes]] = []
|
| 449 |
+
|
| 450 |
+
has_host = "Host" in self.headers
|
| 451 |
+
has_content_length = (
|
| 452 |
+
"Content-Length" in self.headers or "Transfer-Encoding" in self.headers
|
| 453 |
+
)
|
| 454 |
+
|
| 455 |
+
if not has_host and self.url.host:
|
| 456 |
+
auto_headers.append((b"Host", self.url.netloc))
|
| 457 |
+
if not has_content_length and self.method in ("POST", "PUT", "PATCH"):
|
| 458 |
+
auto_headers.append((b"Content-Length", b"0"))
|
| 459 |
+
|
| 460 |
+
self.headers = Headers(auto_headers + self.headers.raw)
|
| 461 |
+
|
| 462 |
+
@property
|
| 463 |
+
def content(self) -> bytes:
|
| 464 |
+
if not hasattr(self, "_content"):
|
| 465 |
+
raise RequestNotRead()
|
| 466 |
+
return self._content
|
| 467 |
+
|
| 468 |
+
def read(self) -> bytes:
|
| 469 |
+
"""
|
| 470 |
+
Read and return the request content.
|
| 471 |
+
"""
|
| 472 |
+
if not hasattr(self, "_content"):
|
| 473 |
+
assert isinstance(self.stream, typing.Iterable)
|
| 474 |
+
self._content = b"".join(self.stream)
|
| 475 |
+
if not isinstance(self.stream, ByteStream):
|
| 476 |
+
# If a streaming request has been read entirely into memory, then
|
| 477 |
+
# we can replace the stream with a raw bytes implementation,
|
| 478 |
+
# to ensure that any non-replayable streams can still be used.
|
| 479 |
+
self.stream = ByteStream(self._content)
|
| 480 |
+
return self._content
|
| 481 |
+
|
| 482 |
+
async def aread(self) -> bytes:
|
| 483 |
+
"""
|
| 484 |
+
Read and return the request content.
|
| 485 |
+
"""
|
| 486 |
+
if not hasattr(self, "_content"):
|
| 487 |
+
assert isinstance(self.stream, typing.AsyncIterable)
|
| 488 |
+
self._content = b"".join([part async for part in self.stream])
|
| 489 |
+
if not isinstance(self.stream, ByteStream):
|
| 490 |
+
# If a streaming request has been read entirely into memory, then
|
| 491 |
+
# we can replace the stream with a raw bytes implementation,
|
| 492 |
+
# to ensure that any non-replayable streams can still be used.
|
| 493 |
+
self.stream = ByteStream(self._content)
|
| 494 |
+
return self._content
|
| 495 |
+
|
| 496 |
+
def __repr__(self) -> str:
|
| 497 |
+
class_name = self.__class__.__name__
|
| 498 |
+
url = str(self.url)
|
| 499 |
+
return f"<{class_name}({self.method!r}, {url!r})>"
|
| 500 |
+
|
| 501 |
+
def __getstate__(self) -> dict[str, typing.Any]:
|
| 502 |
+
return {
|
| 503 |
+
name: value
|
| 504 |
+
for name, value in self.__dict__.items()
|
| 505 |
+
if name not in ["extensions", "stream"]
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
def __setstate__(self, state: dict[str, typing.Any]) -> None:
|
| 509 |
+
for name, value in state.items():
|
| 510 |
+
setattr(self, name, value)
|
| 511 |
+
self.extensions = {}
|
| 512 |
+
self.stream = UnattachedStream()
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
class Response:
|
| 516 |
+
def __init__(
|
| 517 |
+
self,
|
| 518 |
+
status_code: int,
|
| 519 |
+
*,
|
| 520 |
+
headers: HeaderTypes | None = None,
|
| 521 |
+
content: ResponseContent | None = None,
|
| 522 |
+
text: str | None = None,
|
| 523 |
+
html: str | None = None,
|
| 524 |
+
json: typing.Any = None,
|
| 525 |
+
stream: SyncByteStream | AsyncByteStream | None = None,
|
| 526 |
+
request: Request | None = None,
|
| 527 |
+
extensions: ResponseExtensions | None = None,
|
| 528 |
+
history: list[Response] | None = None,
|
| 529 |
+
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
| 530 |
+
) -> None:
|
| 531 |
+
self.status_code = status_code
|
| 532 |
+
self.headers = Headers(headers)
|
| 533 |
+
|
| 534 |
+
self._request: Request | None = request
|
| 535 |
+
|
| 536 |
+
# When follow_redirects=False and a redirect is received,
|
| 537 |
+
# the client will set `response.next_request`.
|
| 538 |
+
self.next_request: Request | None = None
|
| 539 |
+
|
| 540 |
+
self.extensions = {} if extensions is None else dict(extensions)
|
| 541 |
+
self.history = [] if history is None else list(history)
|
| 542 |
+
|
| 543 |
+
self.is_closed = False
|
| 544 |
+
self.is_stream_consumed = False
|
| 545 |
+
|
| 546 |
+
self.default_encoding = default_encoding
|
| 547 |
+
|
| 548 |
+
if stream is None:
|
| 549 |
+
headers, stream = encode_response(content, text, html, json)
|
| 550 |
+
self._prepare(headers)
|
| 551 |
+
self.stream = stream
|
| 552 |
+
if isinstance(stream, ByteStream):
|
| 553 |
+
# Load the response body, except for streaming content.
|
| 554 |
+
self.read()
|
| 555 |
+
else:
|
| 556 |
+
# There's an important distinction between `Response(content=...)`,
|
| 557 |
+
# and `Response(stream=...)`.
|
| 558 |
+
#
|
| 559 |
+
# Using `content=...` implies automatically populated content headers,
|
| 560 |
+
# of either `Content-Length: ...` or `Transfer-Encoding: chunked`.
|
| 561 |
+
#
|
| 562 |
+
# Using `stream=...` will not automatically include any content headers.
|
| 563 |
+
#
|
| 564 |
+
# As an end-user you don't really need `stream=...`. It's only
|
| 565 |
+
# useful when creating response instances having received a stream
|
| 566 |
+
# from the transport API.
|
| 567 |
+
self.stream = stream
|
| 568 |
+
|
| 569 |
+
self._num_bytes_downloaded = 0
|
| 570 |
+
|
| 571 |
+
def _prepare(self, default_headers: dict[str, str]) -> None:
|
| 572 |
+
for key, value in default_headers.items():
|
| 573 |
+
# Ignore Transfer-Encoding if the Content-Length has been set explicitly.
|
| 574 |
+
if key.lower() == "transfer-encoding" and "content-length" in self.headers:
|
| 575 |
+
continue
|
| 576 |
+
self.headers.setdefault(key, value)
|
| 577 |
+
|
| 578 |
+
@property
|
| 579 |
+
def elapsed(self) -> datetime.timedelta:
|
| 580 |
+
"""
|
| 581 |
+
Returns the time taken for the complete request/response
|
| 582 |
+
cycle to complete.
|
| 583 |
+
"""
|
| 584 |
+
if not hasattr(self, "_elapsed"):
|
| 585 |
+
raise RuntimeError(
|
| 586 |
+
"'.elapsed' may only be accessed after the response "
|
| 587 |
+
"has been read or closed."
|
| 588 |
+
)
|
| 589 |
+
return self._elapsed
|
| 590 |
+
|
| 591 |
+
@elapsed.setter
|
| 592 |
+
def elapsed(self, elapsed: datetime.timedelta) -> None:
|
| 593 |
+
self._elapsed = elapsed
|
| 594 |
+
|
| 595 |
+
@property
|
| 596 |
+
def request(self) -> Request:
|
| 597 |
+
"""
|
| 598 |
+
Returns the request instance associated to the current response.
|
| 599 |
+
"""
|
| 600 |
+
if self._request is None:
|
| 601 |
+
raise RuntimeError(
|
| 602 |
+
"The request instance has not been set on this response."
|
| 603 |
+
)
|
| 604 |
+
return self._request
|
| 605 |
+
|
| 606 |
+
@request.setter
|
| 607 |
+
def request(self, value: Request) -> None:
|
| 608 |
+
self._request = value
|
| 609 |
+
|
| 610 |
+
@property
|
| 611 |
+
def http_version(self) -> str:
|
| 612 |
+
try:
|
| 613 |
+
http_version: bytes = self.extensions["http_version"]
|
| 614 |
+
except KeyError:
|
| 615 |
+
return "HTTP/1.1"
|
| 616 |
+
else:
|
| 617 |
+
return http_version.decode("ascii", errors="ignore")
|
| 618 |
+
|
| 619 |
+
@property
|
| 620 |
+
def reason_phrase(self) -> str:
|
| 621 |
+
try:
|
| 622 |
+
reason_phrase: bytes = self.extensions["reason_phrase"]
|
| 623 |
+
except KeyError:
|
| 624 |
+
return codes.get_reason_phrase(self.status_code)
|
| 625 |
+
else:
|
| 626 |
+
return reason_phrase.decode("ascii", errors="ignore")
|
| 627 |
+
|
| 628 |
+
@property
|
| 629 |
+
def url(self) -> URL:
|
| 630 |
+
"""
|
| 631 |
+
Returns the URL for which the request was made.
|
| 632 |
+
"""
|
| 633 |
+
return self.request.url
|
| 634 |
+
|
| 635 |
+
@property
|
| 636 |
+
def content(self) -> bytes:
|
| 637 |
+
if not hasattr(self, "_content"):
|
| 638 |
+
raise ResponseNotRead()
|
| 639 |
+
return self._content
|
| 640 |
+
|
| 641 |
+
@property
|
| 642 |
+
def text(self) -> str:
|
| 643 |
+
if not hasattr(self, "_text"):
|
| 644 |
+
content = self.content
|
| 645 |
+
if not content:
|
| 646 |
+
self._text = ""
|
| 647 |
+
else:
|
| 648 |
+
decoder = TextDecoder(encoding=self.encoding or "utf-8")
|
| 649 |
+
self._text = "".join([decoder.decode(self.content), decoder.flush()])
|
| 650 |
+
return self._text
|
| 651 |
+
|
| 652 |
+
@property
|
| 653 |
+
def encoding(self) -> str | None:
|
| 654 |
+
"""
|
| 655 |
+
Return an encoding to use for decoding the byte content into text.
|
| 656 |
+
The priority for determining this is given by...
|
| 657 |
+
|
| 658 |
+
* `.encoding = <>` has been set explicitly.
|
| 659 |
+
* The encoding as specified by the charset parameter in the Content-Type header.
|
| 660 |
+
* The encoding as determined by `default_encoding`, which may either be
|
| 661 |
+
a string like "utf-8" indicating the encoding to use, or may be a callable
|
| 662 |
+
which enables charset autodetection.
|
| 663 |
+
"""
|
| 664 |
+
if not hasattr(self, "_encoding"):
|
| 665 |
+
encoding = self.charset_encoding
|
| 666 |
+
if encoding is None or not _is_known_encoding(encoding):
|
| 667 |
+
if isinstance(self.default_encoding, str):
|
| 668 |
+
encoding = self.default_encoding
|
| 669 |
+
elif hasattr(self, "_content"):
|
| 670 |
+
encoding = self.default_encoding(self._content)
|
| 671 |
+
self._encoding = encoding or "utf-8"
|
| 672 |
+
return self._encoding
|
| 673 |
+
|
| 674 |
+
@encoding.setter
|
| 675 |
+
def encoding(self, value: str) -> None:
|
| 676 |
+
"""
|
| 677 |
+
Set the encoding to use for decoding the byte content into text.
|
| 678 |
+
|
| 679 |
+
If the `text` attribute has been accessed, attempting to set the
|
| 680 |
+
encoding will throw a ValueError.
|
| 681 |
+
"""
|
| 682 |
+
if hasattr(self, "_text"):
|
| 683 |
+
raise ValueError(
|
| 684 |
+
"Setting encoding after `text` has been accessed is not allowed."
|
| 685 |
+
)
|
| 686 |
+
self._encoding = value
|
| 687 |
+
|
| 688 |
+
@property
|
| 689 |
+
def charset_encoding(self) -> str | None:
|
| 690 |
+
"""
|
| 691 |
+
Return the encoding, as specified by the Content-Type header.
|
| 692 |
+
"""
|
| 693 |
+
content_type = self.headers.get("Content-Type")
|
| 694 |
+
if content_type is None:
|
| 695 |
+
return None
|
| 696 |
+
|
| 697 |
+
return _parse_content_type_charset(content_type)
|
| 698 |
+
|
| 699 |
+
def _get_content_decoder(self) -> ContentDecoder:
|
| 700 |
+
"""
|
| 701 |
+
Returns a decoder instance which can be used to decode the raw byte
|
| 702 |
+
content, depending on the Content-Encoding used in the response.
|
| 703 |
+
"""
|
| 704 |
+
if not hasattr(self, "_decoder"):
|
| 705 |
+
decoders: list[ContentDecoder] = []
|
| 706 |
+
values = self.headers.get_list("content-encoding", split_commas=True)
|
| 707 |
+
for value in values:
|
| 708 |
+
value = value.strip().lower()
|
| 709 |
+
try:
|
| 710 |
+
decoder_cls = SUPPORTED_DECODERS[value]
|
| 711 |
+
decoders.append(decoder_cls())
|
| 712 |
+
except KeyError:
|
| 713 |
+
continue
|
| 714 |
+
|
| 715 |
+
if len(decoders) == 1:
|
| 716 |
+
self._decoder = decoders[0]
|
| 717 |
+
elif len(decoders) > 1:
|
| 718 |
+
self._decoder = MultiDecoder(children=decoders)
|
| 719 |
+
else:
|
| 720 |
+
self._decoder = IdentityDecoder()
|
| 721 |
+
|
| 722 |
+
return self._decoder
|
| 723 |
+
|
| 724 |
+
@property
|
| 725 |
+
def is_informational(self) -> bool:
|
| 726 |
+
"""
|
| 727 |
+
A property which is `True` for 1xx status codes, `False` otherwise.
|
| 728 |
+
"""
|
| 729 |
+
return codes.is_informational(self.status_code)
|
| 730 |
+
|
| 731 |
+
@property
|
| 732 |
+
def is_success(self) -> bool:
|
| 733 |
+
"""
|
| 734 |
+
A property which is `True` for 2xx status codes, `False` otherwise.
|
| 735 |
+
"""
|
| 736 |
+
return codes.is_success(self.status_code)
|
| 737 |
+
|
| 738 |
+
@property
|
| 739 |
+
def is_redirect(self) -> bool:
|
| 740 |
+
"""
|
| 741 |
+
A property which is `True` for 3xx status codes, `False` otherwise.
|
| 742 |
+
|
| 743 |
+
Note that not all responses with a 3xx status code indicate a URL redirect.
|
| 744 |
+
|
| 745 |
+
Use `response.has_redirect_location` to determine responses with a properly
|
| 746 |
+
formed URL redirection.
|
| 747 |
+
"""
|
| 748 |
+
return codes.is_redirect(self.status_code)
|
| 749 |
+
|
| 750 |
+
@property
|
| 751 |
+
def is_client_error(self) -> bool:
|
| 752 |
+
"""
|
| 753 |
+
A property which is `True` for 4xx status codes, `False` otherwise.
|
| 754 |
+
"""
|
| 755 |
+
return codes.is_client_error(self.status_code)
|
| 756 |
+
|
| 757 |
+
@property
|
| 758 |
+
def is_server_error(self) -> bool:
|
| 759 |
+
"""
|
| 760 |
+
A property which is `True` for 5xx status codes, `False` otherwise.
|
| 761 |
+
"""
|
| 762 |
+
return codes.is_server_error(self.status_code)
|
| 763 |
+
|
| 764 |
+
@property
|
| 765 |
+
def is_error(self) -> bool:
|
| 766 |
+
"""
|
| 767 |
+
A property which is `True` for 4xx and 5xx status codes, `False` otherwise.
|
| 768 |
+
"""
|
| 769 |
+
return codes.is_error(self.status_code)
|
| 770 |
+
|
| 771 |
+
@property
|
| 772 |
+
def has_redirect_location(self) -> bool:
|
| 773 |
+
"""
|
| 774 |
+
Returns True for 3xx responses with a properly formed URL redirection,
|
| 775 |
+
`False` otherwise.
|
| 776 |
+
"""
|
| 777 |
+
return (
|
| 778 |
+
self.status_code
|
| 779 |
+
in (
|
| 780 |
+
# 301 (Cacheable redirect. Method may change to GET.)
|
| 781 |
+
codes.MOVED_PERMANENTLY,
|
| 782 |
+
# 302 (Uncacheable redirect. Method may change to GET.)
|
| 783 |
+
codes.FOUND,
|
| 784 |
+
# 303 (Client should make a GET or HEAD request.)
|
| 785 |
+
codes.SEE_OTHER,
|
| 786 |
+
# 307 (Equiv. 302, but retain method)
|
| 787 |
+
codes.TEMPORARY_REDIRECT,
|
| 788 |
+
# 308 (Equiv. 301, but retain method)
|
| 789 |
+
codes.PERMANENT_REDIRECT,
|
| 790 |
+
)
|
| 791 |
+
and "Location" in self.headers
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
def raise_for_status(self) -> Response:
|
| 795 |
+
"""
|
| 796 |
+
Raise the `HTTPStatusError` if one occurred.
|
| 797 |
+
"""
|
| 798 |
+
request = self._request
|
| 799 |
+
if request is None:
|
| 800 |
+
raise RuntimeError(
|
| 801 |
+
"Cannot call `raise_for_status` as the request "
|
| 802 |
+
"instance has not been set on this response."
|
| 803 |
+
)
|
| 804 |
+
|
| 805 |
+
if self.is_success:
|
| 806 |
+
return self
|
| 807 |
+
|
| 808 |
+
if self.has_redirect_location:
|
| 809 |
+
message = (
|
| 810 |
+
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
|
| 811 |
+
"Redirect location: '{0.headers[location]}'\n"
|
| 812 |
+
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
|
| 813 |
+
)
|
| 814 |
+
else:
|
| 815 |
+
message = (
|
| 816 |
+
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
|
| 817 |
+
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
|
| 818 |
+
)
|
| 819 |
+
|
| 820 |
+
status_class = self.status_code // 100
|
| 821 |
+
error_types = {
|
| 822 |
+
1: "Informational response",
|
| 823 |
+
3: "Redirect response",
|
| 824 |
+
4: "Client error",
|
| 825 |
+
5: "Server error",
|
| 826 |
+
}
|
| 827 |
+
error_type = error_types.get(status_class, "Invalid status code")
|
| 828 |
+
message = message.format(self, error_type=error_type)
|
| 829 |
+
raise HTTPStatusError(message, request=request, response=self)
|
| 830 |
+
|
| 831 |
+
def json(self, **kwargs: typing.Any) -> typing.Any:
|
| 832 |
+
return jsonlib.loads(self.content, **kwargs)
|
| 833 |
+
|
| 834 |
+
@property
|
| 835 |
+
def cookies(self) -> Cookies:
|
| 836 |
+
if not hasattr(self, "_cookies"):
|
| 837 |
+
self._cookies = Cookies()
|
| 838 |
+
self._cookies.extract_cookies(self)
|
| 839 |
+
return self._cookies
|
| 840 |
+
|
| 841 |
+
@property
|
| 842 |
+
def links(self) -> dict[str | None, dict[str, str]]:
|
| 843 |
+
"""
|
| 844 |
+
Returns the parsed header links of the response, if any
|
| 845 |
+
"""
|
| 846 |
+
header = self.headers.get("link")
|
| 847 |
+
if header is None:
|
| 848 |
+
return {}
|
| 849 |
+
|
| 850 |
+
return {
|
| 851 |
+
(link.get("rel") or link.get("url")): link
|
| 852 |
+
for link in _parse_header_links(header)
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
@property
|
| 856 |
+
def num_bytes_downloaded(self) -> int:
|
| 857 |
+
return self._num_bytes_downloaded
|
| 858 |
+
|
| 859 |
+
def __repr__(self) -> str:
|
| 860 |
+
return f"<Response [{self.status_code} {self.reason_phrase}]>"
|
| 861 |
+
|
| 862 |
+
def __getstate__(self) -> dict[str, typing.Any]:
|
| 863 |
+
return {
|
| 864 |
+
name: value
|
| 865 |
+
for name, value in self.__dict__.items()
|
| 866 |
+
if name not in ["extensions", "stream", "is_closed", "_decoder"]
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
def __setstate__(self, state: dict[str, typing.Any]) -> None:
|
| 870 |
+
for name, value in state.items():
|
| 871 |
+
setattr(self, name, value)
|
| 872 |
+
self.is_closed = True
|
| 873 |
+
self.extensions = {}
|
| 874 |
+
self.stream = UnattachedStream()
|
| 875 |
+
|
| 876 |
+
def read(self) -> bytes:
|
| 877 |
+
"""
|
| 878 |
+
Read and return the response content.
|
| 879 |
+
"""
|
| 880 |
+
if not hasattr(self, "_content"):
|
| 881 |
+
self._content = b"".join(self.iter_bytes())
|
| 882 |
+
return self._content
|
| 883 |
+
|
| 884 |
+
def iter_bytes(self, chunk_size: int | None = None) -> typing.Iterator[bytes]:
|
| 885 |
+
"""
|
| 886 |
+
A byte-iterator over the decoded response content.
|
| 887 |
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
| 888 |
+
"""
|
| 889 |
+
if hasattr(self, "_content"):
|
| 890 |
+
chunk_size = len(self._content) if chunk_size is None else chunk_size
|
| 891 |
+
for i in range(0, len(self._content), max(chunk_size, 1)):
|
| 892 |
+
yield self._content[i : i + chunk_size]
|
| 893 |
+
else:
|
| 894 |
+
decoder = self._get_content_decoder()
|
| 895 |
+
chunker = ByteChunker(chunk_size=chunk_size)
|
| 896 |
+
with request_context(request=self._request):
|
| 897 |
+
for raw_bytes in self.iter_raw():
|
| 898 |
+
decoded = decoder.decode(raw_bytes)
|
| 899 |
+
for chunk in chunker.decode(decoded):
|
| 900 |
+
yield chunk
|
| 901 |
+
decoded = decoder.flush()
|
| 902 |
+
for chunk in chunker.decode(decoded):
|
| 903 |
+
yield chunk # pragma: no cover
|
| 904 |
+
for chunk in chunker.flush():
|
| 905 |
+
yield chunk
|
| 906 |
+
|
| 907 |
+
def iter_text(self, chunk_size: int | None = None) -> typing.Iterator[str]:
|
| 908 |
+
"""
|
| 909 |
+
A str-iterator over the decoded response content
|
| 910 |
+
that handles both gzip, deflate, etc but also detects the content's
|
| 911 |
+
string encoding.
|
| 912 |
+
"""
|
| 913 |
+
decoder = TextDecoder(encoding=self.encoding or "utf-8")
|
| 914 |
+
chunker = TextChunker(chunk_size=chunk_size)
|
| 915 |
+
with request_context(request=self._request):
|
| 916 |
+
for byte_content in self.iter_bytes():
|
| 917 |
+
text_content = decoder.decode(byte_content)
|
| 918 |
+
for chunk in chunker.decode(text_content):
|
| 919 |
+
yield chunk
|
| 920 |
+
text_content = decoder.flush()
|
| 921 |
+
for chunk in chunker.decode(text_content):
|
| 922 |
+
yield chunk # pragma: no cover
|
| 923 |
+
for chunk in chunker.flush():
|
| 924 |
+
yield chunk
|
| 925 |
+
|
| 926 |
+
def iter_lines(self) -> typing.Iterator[str]:
|
| 927 |
+
decoder = LineDecoder()
|
| 928 |
+
with request_context(request=self._request):
|
| 929 |
+
for text in self.iter_text():
|
| 930 |
+
for line in decoder.decode(text):
|
| 931 |
+
yield line
|
| 932 |
+
for line in decoder.flush():
|
| 933 |
+
yield line
|
| 934 |
+
|
| 935 |
+
def iter_raw(self, chunk_size: int | None = None) -> typing.Iterator[bytes]:
|
| 936 |
+
"""
|
| 937 |
+
A byte-iterator over the raw response content.
|
| 938 |
+
"""
|
| 939 |
+
if self.is_stream_consumed:
|
| 940 |
+
raise StreamConsumed()
|
| 941 |
+
if self.is_closed:
|
| 942 |
+
raise StreamClosed()
|
| 943 |
+
if not isinstance(self.stream, SyncByteStream):
|
| 944 |
+
raise RuntimeError("Attempted to call a sync iterator on an async stream.")
|
| 945 |
+
|
| 946 |
+
self.is_stream_consumed = True
|
| 947 |
+
self._num_bytes_downloaded = 0
|
| 948 |
+
chunker = ByteChunker(chunk_size=chunk_size)
|
| 949 |
+
|
| 950 |
+
with request_context(request=self._request):
|
| 951 |
+
for raw_stream_bytes in self.stream:
|
| 952 |
+
self._num_bytes_downloaded += len(raw_stream_bytes)
|
| 953 |
+
for chunk in chunker.decode(raw_stream_bytes):
|
| 954 |
+
yield chunk
|
| 955 |
+
|
| 956 |
+
for chunk in chunker.flush():
|
| 957 |
+
yield chunk
|
| 958 |
+
|
| 959 |
+
self.close()
|
| 960 |
+
|
| 961 |
+
def close(self) -> None:
|
| 962 |
+
"""
|
| 963 |
+
Close the response and release the connection.
|
| 964 |
+
Automatically called if the response body is read to completion.
|
| 965 |
+
"""
|
| 966 |
+
if not isinstance(self.stream, SyncByteStream):
|
| 967 |
+
raise RuntimeError("Attempted to call an sync close on an async stream.")
|
| 968 |
+
|
| 969 |
+
if not self.is_closed:
|
| 970 |
+
self.is_closed = True
|
| 971 |
+
with request_context(request=self._request):
|
| 972 |
+
self.stream.close()
|
| 973 |
+
|
| 974 |
+
async def aread(self) -> bytes:
|
| 975 |
+
"""
|
| 976 |
+
Read and return the response content.
|
| 977 |
+
"""
|
| 978 |
+
if not hasattr(self, "_content"):
|
| 979 |
+
self._content = b"".join([part async for part in self.aiter_bytes()])
|
| 980 |
+
return self._content
|
| 981 |
+
|
| 982 |
+
async def aiter_bytes(
|
| 983 |
+
self, chunk_size: int | None = None
|
| 984 |
+
) -> typing.AsyncIterator[bytes]:
|
| 985 |
+
"""
|
| 986 |
+
A byte-iterator over the decoded response content.
|
| 987 |
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
| 988 |
+
"""
|
| 989 |
+
if hasattr(self, "_content"):
|
| 990 |
+
chunk_size = len(self._content) if chunk_size is None else chunk_size
|
| 991 |
+
for i in range(0, len(self._content), max(chunk_size, 1)):
|
| 992 |
+
yield self._content[i : i + chunk_size]
|
| 993 |
+
else:
|
| 994 |
+
decoder = self._get_content_decoder()
|
| 995 |
+
chunker = ByteChunker(chunk_size=chunk_size)
|
| 996 |
+
with request_context(request=self._request):
|
| 997 |
+
async for raw_bytes in self.aiter_raw():
|
| 998 |
+
decoded = decoder.decode(raw_bytes)
|
| 999 |
+
for chunk in chunker.decode(decoded):
|
| 1000 |
+
yield chunk
|
| 1001 |
+
decoded = decoder.flush()
|
| 1002 |
+
for chunk in chunker.decode(decoded):
|
| 1003 |
+
yield chunk # pragma: no cover
|
| 1004 |
+
for chunk in chunker.flush():
|
| 1005 |
+
yield chunk
|
| 1006 |
+
|
| 1007 |
+
async def aiter_text(
|
| 1008 |
+
self, chunk_size: int | None = None
|
| 1009 |
+
) -> typing.AsyncIterator[str]:
|
| 1010 |
+
"""
|
| 1011 |
+
A str-iterator over the decoded response content
|
| 1012 |
+
that handles both gzip, deflate, etc but also detects the content's
|
| 1013 |
+
string encoding.
|
| 1014 |
+
"""
|
| 1015 |
+
decoder = TextDecoder(encoding=self.encoding or "utf-8")
|
| 1016 |
+
chunker = TextChunker(chunk_size=chunk_size)
|
| 1017 |
+
with request_context(request=self._request):
|
| 1018 |
+
async for byte_content in self.aiter_bytes():
|
| 1019 |
+
text_content = decoder.decode(byte_content)
|
| 1020 |
+
for chunk in chunker.decode(text_content):
|
| 1021 |
+
yield chunk
|
| 1022 |
+
text_content = decoder.flush()
|
| 1023 |
+
for chunk in chunker.decode(text_content):
|
| 1024 |
+
yield chunk # pragma: no cover
|
| 1025 |
+
for chunk in chunker.flush():
|
| 1026 |
+
yield chunk
|
| 1027 |
+
|
| 1028 |
+
async def aiter_lines(self) -> typing.AsyncIterator[str]:
|
| 1029 |
+
decoder = LineDecoder()
|
| 1030 |
+
with request_context(request=self._request):
|
| 1031 |
+
async for text in self.aiter_text():
|
| 1032 |
+
for line in decoder.decode(text):
|
| 1033 |
+
yield line
|
| 1034 |
+
for line in decoder.flush():
|
| 1035 |
+
yield line
|
| 1036 |
+
|
| 1037 |
+
async def aiter_raw(
|
| 1038 |
+
self, chunk_size: int | None = None
|
| 1039 |
+
) -> typing.AsyncIterator[bytes]:
|
| 1040 |
+
"""
|
| 1041 |
+
A byte-iterator over the raw response content.
|
| 1042 |
+
"""
|
| 1043 |
+
if self.is_stream_consumed:
|
| 1044 |
+
raise StreamConsumed()
|
| 1045 |
+
if self.is_closed:
|
| 1046 |
+
raise StreamClosed()
|
| 1047 |
+
if not isinstance(self.stream, AsyncByteStream):
|
| 1048 |
+
raise RuntimeError("Attempted to call an async iterator on an sync stream.")
|
| 1049 |
+
|
| 1050 |
+
self.is_stream_consumed = True
|
| 1051 |
+
self._num_bytes_downloaded = 0
|
| 1052 |
+
chunker = ByteChunker(chunk_size=chunk_size)
|
| 1053 |
+
|
| 1054 |
+
with request_context(request=self._request):
|
| 1055 |
+
async for raw_stream_bytes in self.stream:
|
| 1056 |
+
self._num_bytes_downloaded += len(raw_stream_bytes)
|
| 1057 |
+
for chunk in chunker.decode(raw_stream_bytes):
|
| 1058 |
+
yield chunk
|
| 1059 |
+
|
| 1060 |
+
for chunk in chunker.flush():
|
| 1061 |
+
yield chunk
|
| 1062 |
+
|
| 1063 |
+
await self.aclose()
|
| 1064 |
+
|
| 1065 |
+
async def aclose(self) -> None:
|
| 1066 |
+
"""
|
| 1067 |
+
Close the response and release the connection.
|
| 1068 |
+
Automatically called if the response body is read to completion.
|
| 1069 |
+
"""
|
| 1070 |
+
if not isinstance(self.stream, AsyncByteStream):
|
| 1071 |
+
raise RuntimeError("Attempted to call an async close on an sync stream.")
|
| 1072 |
+
|
| 1073 |
+
if not self.is_closed:
|
| 1074 |
+
self.is_closed = True
|
| 1075 |
+
with request_context(request=self._request):
|
| 1076 |
+
await self.stream.aclose()
|
| 1077 |
+
|
| 1078 |
+
|
| 1079 |
+
class Cookies(typing.MutableMapping[str, str]):
|
| 1080 |
+
"""
|
| 1081 |
+
HTTP Cookies, as a mutable mapping.
|
| 1082 |
+
"""
|
| 1083 |
+
|
| 1084 |
+
def __init__(self, cookies: CookieTypes | None = None) -> None:
|
| 1085 |
+
if cookies is None or isinstance(cookies, dict):
|
| 1086 |
+
self.jar = CookieJar()
|
| 1087 |
+
if isinstance(cookies, dict):
|
| 1088 |
+
for key, value in cookies.items():
|
| 1089 |
+
self.set(key, value)
|
| 1090 |
+
elif isinstance(cookies, list):
|
| 1091 |
+
self.jar = CookieJar()
|
| 1092 |
+
for key, value in cookies:
|
| 1093 |
+
self.set(key, value)
|
| 1094 |
+
elif isinstance(cookies, Cookies):
|
| 1095 |
+
self.jar = CookieJar()
|
| 1096 |
+
for cookie in cookies.jar:
|
| 1097 |
+
self.jar.set_cookie(cookie)
|
| 1098 |
+
else:
|
| 1099 |
+
self.jar = cookies
|
| 1100 |
+
|
| 1101 |
+
def extract_cookies(self, response: Response) -> None:
|
| 1102 |
+
"""
|
| 1103 |
+
Loads any cookies based on the response `Set-Cookie` headers.
|
| 1104 |
+
"""
|
| 1105 |
+
urllib_response = self._CookieCompatResponse(response)
|
| 1106 |
+
urllib_request = self._CookieCompatRequest(response.request)
|
| 1107 |
+
|
| 1108 |
+
self.jar.extract_cookies(urllib_response, urllib_request) # type: ignore
|
| 1109 |
+
|
| 1110 |
+
def set_cookie_header(self, request: Request) -> None:
|
| 1111 |
+
"""
|
| 1112 |
+
Sets an appropriate 'Cookie:' HTTP header on the `Request`.
|
| 1113 |
+
"""
|
| 1114 |
+
urllib_request = self._CookieCompatRequest(request)
|
| 1115 |
+
self.jar.add_cookie_header(urllib_request)
|
| 1116 |
+
|
| 1117 |
+
def set(self, name: str, value: str, domain: str = "", path: str = "/") -> None:
|
| 1118 |
+
"""
|
| 1119 |
+
Set a cookie value by name. May optionally include domain and path.
|
| 1120 |
+
"""
|
| 1121 |
+
kwargs = {
|
| 1122 |
+
"version": 0,
|
| 1123 |
+
"name": name,
|
| 1124 |
+
"value": value,
|
| 1125 |
+
"port": None,
|
| 1126 |
+
"port_specified": False,
|
| 1127 |
+
"domain": domain,
|
| 1128 |
+
"domain_specified": bool(domain),
|
| 1129 |
+
"domain_initial_dot": domain.startswith("."),
|
| 1130 |
+
"path": path,
|
| 1131 |
+
"path_specified": bool(path),
|
| 1132 |
+
"secure": False,
|
| 1133 |
+
"expires": None,
|
| 1134 |
+
"discard": True,
|
| 1135 |
+
"comment": None,
|
| 1136 |
+
"comment_url": None,
|
| 1137 |
+
"rest": {"HttpOnly": None},
|
| 1138 |
+
"rfc2109": False,
|
| 1139 |
+
}
|
| 1140 |
+
cookie = Cookie(**kwargs) # type: ignore
|
| 1141 |
+
self.jar.set_cookie(cookie)
|
| 1142 |
+
|
| 1143 |
+
def get( # type: ignore
|
| 1144 |
+
self,
|
| 1145 |
+
name: str,
|
| 1146 |
+
default: str | None = None,
|
| 1147 |
+
domain: str | None = None,
|
| 1148 |
+
path: str | None = None,
|
| 1149 |
+
) -> str | None:
|
| 1150 |
+
"""
|
| 1151 |
+
Get a cookie by name. May optionally include domain and path
|
| 1152 |
+
in order to specify exactly which cookie to retrieve.
|
| 1153 |
+
"""
|
| 1154 |
+
value = None
|
| 1155 |
+
for cookie in self.jar:
|
| 1156 |
+
if cookie.name == name:
|
| 1157 |
+
if domain is None or cookie.domain == domain:
|
| 1158 |
+
if path is None or cookie.path == path:
|
| 1159 |
+
if value is not None:
|
| 1160 |
+
message = f"Multiple cookies exist with name={name}"
|
| 1161 |
+
raise CookieConflict(message)
|
| 1162 |
+
value = cookie.value
|
| 1163 |
+
|
| 1164 |
+
if value is None:
|
| 1165 |
+
return default
|
| 1166 |
+
return value
|
| 1167 |
+
|
| 1168 |
+
def delete(
|
| 1169 |
+
self,
|
| 1170 |
+
name: str,
|
| 1171 |
+
domain: str | None = None,
|
| 1172 |
+
path: str | None = None,
|
| 1173 |
+
) -> None:
|
| 1174 |
+
"""
|
| 1175 |
+
Delete a cookie by name. May optionally include domain and path
|
| 1176 |
+
in order to specify exactly which cookie to delete.
|
| 1177 |
+
"""
|
| 1178 |
+
if domain is not None and path is not None:
|
| 1179 |
+
return self.jar.clear(domain, path, name)
|
| 1180 |
+
|
| 1181 |
+
remove = [
|
| 1182 |
+
cookie
|
| 1183 |
+
for cookie in self.jar
|
| 1184 |
+
if cookie.name == name
|
| 1185 |
+
and (domain is None or cookie.domain == domain)
|
| 1186 |
+
and (path is None or cookie.path == path)
|
| 1187 |
+
]
|
| 1188 |
+
|
| 1189 |
+
for cookie in remove:
|
| 1190 |
+
self.jar.clear(cookie.domain, cookie.path, cookie.name)
|
| 1191 |
+
|
| 1192 |
+
def clear(self, domain: str | None = None, path: str | None = None) -> None:
|
| 1193 |
+
"""
|
| 1194 |
+
Delete all cookies. Optionally include a domain and path in
|
| 1195 |
+
order to only delete a subset of all the cookies.
|
| 1196 |
+
"""
|
| 1197 |
+
args = []
|
| 1198 |
+
if domain is not None:
|
| 1199 |
+
args.append(domain)
|
| 1200 |
+
if path is not None:
|
| 1201 |
+
assert domain is not None
|
| 1202 |
+
args.append(path)
|
| 1203 |
+
self.jar.clear(*args)
|
| 1204 |
+
|
| 1205 |
+
def update(self, cookies: CookieTypes | None = None) -> None: # type: ignore
|
| 1206 |
+
cookies = Cookies(cookies)
|
| 1207 |
+
for cookie in cookies.jar:
|
| 1208 |
+
self.jar.set_cookie(cookie)
|
| 1209 |
+
|
| 1210 |
+
def __setitem__(self, name: str, value: str) -> None:
|
| 1211 |
+
return self.set(name, value)
|
| 1212 |
+
|
| 1213 |
+
def __getitem__(self, name: str) -> str:
|
| 1214 |
+
value = self.get(name)
|
| 1215 |
+
if value is None:
|
| 1216 |
+
raise KeyError(name)
|
| 1217 |
+
return value
|
| 1218 |
+
|
| 1219 |
+
def __delitem__(self, name: str) -> None:
|
| 1220 |
+
return self.delete(name)
|
| 1221 |
+
|
| 1222 |
+
def __len__(self) -> int:
|
| 1223 |
+
return len(self.jar)
|
| 1224 |
+
|
| 1225 |
+
def __iter__(self) -> typing.Iterator[str]:
|
| 1226 |
+
return (cookie.name for cookie in self.jar)
|
| 1227 |
+
|
| 1228 |
+
def __bool__(self) -> bool:
|
| 1229 |
+
for _ in self.jar:
|
| 1230 |
+
return True
|
| 1231 |
+
return False
|
| 1232 |
+
|
| 1233 |
+
def __repr__(self) -> str:
|
| 1234 |
+
cookies_repr = ", ".join(
|
| 1235 |
+
[
|
| 1236 |
+
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />"
|
| 1237 |
+
for cookie in self.jar
|
| 1238 |
+
]
|
| 1239 |
+
)
|
| 1240 |
+
|
| 1241 |
+
return f"<Cookies[{cookies_repr}]>"
|
| 1242 |
+
|
| 1243 |
+
class _CookieCompatRequest(urllib.request.Request):
|
| 1244 |
+
"""
|
| 1245 |
+
Wraps a `Request` instance up in a compatibility interface suitable
|
| 1246 |
+
for use with `CookieJar` operations.
|
| 1247 |
+
"""
|
| 1248 |
+
|
| 1249 |
+
def __init__(self, request: Request) -> None:
|
| 1250 |
+
super().__init__(
|
| 1251 |
+
url=str(request.url),
|
| 1252 |
+
headers=dict(request.headers),
|
| 1253 |
+
method=request.method,
|
| 1254 |
+
)
|
| 1255 |
+
self.request = request
|
| 1256 |
+
|
| 1257 |
+
def add_unredirected_header(self, key: str, value: str) -> None:
|
| 1258 |
+
super().add_unredirected_header(key, value)
|
| 1259 |
+
self.request.headers[key] = value
|
| 1260 |
+
|
| 1261 |
+
class _CookieCompatResponse:
|
| 1262 |
+
"""
|
| 1263 |
+
Wraps a `Request` instance up in a compatibility interface suitable
|
| 1264 |
+
for use with `CookieJar` operations.
|
| 1265 |
+
"""
|
| 1266 |
+
|
| 1267 |
+
def __init__(self, response: Response) -> None:
|
| 1268 |
+
self.response = response
|
| 1269 |
+
|
| 1270 |
+
def info(self) -> email.message.Message:
|
| 1271 |
+
info = email.message.Message()
|
| 1272 |
+
for key, value in self.response.headers.multi_items():
|
| 1273 |
+
# Note that setting `info[key]` here is an "append" operation,
|
| 1274 |
+
# not a "replace" operation.
|
| 1275 |
+
# https://docs.python.org/3/library/email.compat32-message.html#email.message.Message.__setitem__
|
| 1276 |
+
info[key] = value
|
| 1277 |
+
return info
|
.venv/lib/python3.14/site-packages/httpx/_multipart.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import io
|
| 4 |
+
import mimetypes
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import typing
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
from ._types import (
|
| 11 |
+
AsyncByteStream,
|
| 12 |
+
FileContent,
|
| 13 |
+
FileTypes,
|
| 14 |
+
RequestData,
|
| 15 |
+
RequestFiles,
|
| 16 |
+
SyncByteStream,
|
| 17 |
+
)
|
| 18 |
+
from ._utils import (
|
| 19 |
+
peek_filelike_length,
|
| 20 |
+
primitive_value_to_str,
|
| 21 |
+
to_bytes,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"}
|
| 25 |
+
_HTML5_FORM_ENCODING_REPLACEMENTS.update(
|
| 26 |
+
{chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B}
|
| 27 |
+
)
|
| 28 |
+
_HTML5_FORM_ENCODING_RE = re.compile(
|
| 29 |
+
r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _format_form_param(name: str, value: str) -> bytes:
|
| 34 |
+
"""
|
| 35 |
+
Encode a name/value pair within a multipart form.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def replacer(match: typing.Match[str]) -> str:
|
| 39 |
+
return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)]
|
| 40 |
+
|
| 41 |
+
value = _HTML5_FORM_ENCODING_RE.sub(replacer, value)
|
| 42 |
+
return f'{name}="{value}"'.encode()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _guess_content_type(filename: str | None) -> str | None:
|
| 46 |
+
"""
|
| 47 |
+
Guesses the mimetype based on a filename. Defaults to `application/octet-stream`.
|
| 48 |
+
|
| 49 |
+
Returns `None` if `filename` is `None` or empty.
|
| 50 |
+
"""
|
| 51 |
+
if filename:
|
| 52 |
+
return mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
| 53 |
+
return None
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def get_multipart_boundary_from_content_type(
|
| 57 |
+
content_type: bytes | None,
|
| 58 |
+
) -> bytes | None:
|
| 59 |
+
if not content_type or not content_type.startswith(b"multipart/form-data"):
|
| 60 |
+
return None
|
| 61 |
+
# parse boundary according to
|
| 62 |
+
# https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
|
| 63 |
+
if b";" in content_type:
|
| 64 |
+
for section in content_type.split(b";"):
|
| 65 |
+
if section.strip().lower().startswith(b"boundary="):
|
| 66 |
+
return section.strip()[len(b"boundary=") :].strip(b'"')
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class DataField:
|
| 71 |
+
"""
|
| 72 |
+
A single form field item, within a multipart form field.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __init__(self, name: str, value: str | bytes | int | float | None) -> None:
|
| 76 |
+
if not isinstance(name, str):
|
| 77 |
+
raise TypeError(
|
| 78 |
+
f"Invalid type for name. Expected str, got {type(name)}: {name!r}"
|
| 79 |
+
)
|
| 80 |
+
if value is not None and not isinstance(value, (str, bytes, int, float)):
|
| 81 |
+
raise TypeError(
|
| 82 |
+
"Invalid type for value. Expected primitive type,"
|
| 83 |
+
f" got {type(value)}: {value!r}"
|
| 84 |
+
)
|
| 85 |
+
self.name = name
|
| 86 |
+
self.value: str | bytes = (
|
| 87 |
+
value if isinstance(value, bytes) else primitive_value_to_str(value)
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
def render_headers(self) -> bytes:
|
| 91 |
+
if not hasattr(self, "_headers"):
|
| 92 |
+
name = _format_form_param("name", self.name)
|
| 93 |
+
self._headers = b"".join(
|
| 94 |
+
[b"Content-Disposition: form-data; ", name, b"\r\n\r\n"]
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
return self._headers
|
| 98 |
+
|
| 99 |
+
def render_data(self) -> bytes:
|
| 100 |
+
if not hasattr(self, "_data"):
|
| 101 |
+
self._data = to_bytes(self.value)
|
| 102 |
+
|
| 103 |
+
return self._data
|
| 104 |
+
|
| 105 |
+
def get_length(self) -> int:
|
| 106 |
+
headers = self.render_headers()
|
| 107 |
+
data = self.render_data()
|
| 108 |
+
return len(headers) + len(data)
|
| 109 |
+
|
| 110 |
+
def render(self) -> typing.Iterator[bytes]:
|
| 111 |
+
yield self.render_headers()
|
| 112 |
+
yield self.render_data()
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class FileField:
|
| 116 |
+
"""
|
| 117 |
+
A single file field item, within a multipart form field.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
CHUNK_SIZE = 64 * 1024
|
| 121 |
+
|
| 122 |
+
def __init__(self, name: str, value: FileTypes) -> None:
|
| 123 |
+
self.name = name
|
| 124 |
+
|
| 125 |
+
fileobj: FileContent
|
| 126 |
+
|
| 127 |
+
headers: dict[str, str] = {}
|
| 128 |
+
content_type: str | None = None
|
| 129 |
+
|
| 130 |
+
# This large tuple based API largely mirror's requests' API
|
| 131 |
+
# It would be good to think of better APIs for this that we could
|
| 132 |
+
# include in httpx 2.0 since variable length tuples(especially of 4 elements)
|
| 133 |
+
# are quite unwieldly
|
| 134 |
+
if isinstance(value, tuple):
|
| 135 |
+
if len(value) == 2:
|
| 136 |
+
# neither the 3rd parameter (content_type) nor the 4th (headers)
|
| 137 |
+
# was included
|
| 138 |
+
filename, fileobj = value
|
| 139 |
+
elif len(value) == 3:
|
| 140 |
+
filename, fileobj, content_type = value
|
| 141 |
+
else:
|
| 142 |
+
# all 4 parameters included
|
| 143 |
+
filename, fileobj, content_type, headers = value # type: ignore
|
| 144 |
+
else:
|
| 145 |
+
filename = Path(str(getattr(value, "name", "upload"))).name
|
| 146 |
+
fileobj = value
|
| 147 |
+
|
| 148 |
+
if content_type is None:
|
| 149 |
+
content_type = _guess_content_type(filename)
|
| 150 |
+
|
| 151 |
+
has_content_type_header = any("content-type" in key.lower() for key in headers)
|
| 152 |
+
if content_type is not None and not has_content_type_header:
|
| 153 |
+
# note that unlike requests, we ignore the content_type provided in the 3rd
|
| 154 |
+
# tuple element if it is also included in the headers requests does
|
| 155 |
+
# the opposite (it overwrites the headerwith the 3rd tuple element)
|
| 156 |
+
headers["Content-Type"] = content_type
|
| 157 |
+
|
| 158 |
+
if isinstance(fileobj, io.StringIO):
|
| 159 |
+
raise TypeError(
|
| 160 |
+
"Multipart file uploads require 'io.BytesIO', not 'io.StringIO'."
|
| 161 |
+
)
|
| 162 |
+
if isinstance(fileobj, io.TextIOBase):
|
| 163 |
+
raise TypeError(
|
| 164 |
+
"Multipart file uploads must be opened in binary mode, not text mode."
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
self.filename = filename
|
| 168 |
+
self.file = fileobj
|
| 169 |
+
self.headers = headers
|
| 170 |
+
|
| 171 |
+
def get_length(self) -> int | None:
|
| 172 |
+
headers = self.render_headers()
|
| 173 |
+
|
| 174 |
+
if isinstance(self.file, (str, bytes)):
|
| 175 |
+
return len(headers) + len(to_bytes(self.file))
|
| 176 |
+
|
| 177 |
+
file_length = peek_filelike_length(self.file)
|
| 178 |
+
|
| 179 |
+
# If we can't determine the filesize without reading it into memory,
|
| 180 |
+
# then return `None` here, to indicate an unknown file length.
|
| 181 |
+
if file_length is None:
|
| 182 |
+
return None
|
| 183 |
+
|
| 184 |
+
return len(headers) + file_length
|
| 185 |
+
|
| 186 |
+
def render_headers(self) -> bytes:
|
| 187 |
+
if not hasattr(self, "_headers"):
|
| 188 |
+
parts = [
|
| 189 |
+
b"Content-Disposition: form-data; ",
|
| 190 |
+
_format_form_param("name", self.name),
|
| 191 |
+
]
|
| 192 |
+
if self.filename:
|
| 193 |
+
filename = _format_form_param("filename", self.filename)
|
| 194 |
+
parts.extend([b"; ", filename])
|
| 195 |
+
for header_name, header_value in self.headers.items():
|
| 196 |
+
key, val = f"\r\n{header_name}: ".encode(), header_value.encode()
|
| 197 |
+
parts.extend([key, val])
|
| 198 |
+
parts.append(b"\r\n\r\n")
|
| 199 |
+
self._headers = b"".join(parts)
|
| 200 |
+
|
| 201 |
+
return self._headers
|
| 202 |
+
|
| 203 |
+
def render_data(self) -> typing.Iterator[bytes]:
|
| 204 |
+
if isinstance(self.file, (str, bytes)):
|
| 205 |
+
yield to_bytes(self.file)
|
| 206 |
+
return
|
| 207 |
+
|
| 208 |
+
if hasattr(self.file, "seek"):
|
| 209 |
+
try:
|
| 210 |
+
self.file.seek(0)
|
| 211 |
+
except io.UnsupportedOperation:
|
| 212 |
+
pass
|
| 213 |
+
|
| 214 |
+
chunk = self.file.read(self.CHUNK_SIZE)
|
| 215 |
+
while chunk:
|
| 216 |
+
yield to_bytes(chunk)
|
| 217 |
+
chunk = self.file.read(self.CHUNK_SIZE)
|
| 218 |
+
|
| 219 |
+
def render(self) -> typing.Iterator[bytes]:
|
| 220 |
+
yield self.render_headers()
|
| 221 |
+
yield from self.render_data()
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class MultipartStream(SyncByteStream, AsyncByteStream):
|
| 225 |
+
"""
|
| 226 |
+
Request content as streaming multipart encoded form data.
|
| 227 |
+
"""
|
| 228 |
+
|
| 229 |
+
def __init__(
|
| 230 |
+
self,
|
| 231 |
+
data: RequestData,
|
| 232 |
+
files: RequestFiles,
|
| 233 |
+
boundary: bytes | None = None,
|
| 234 |
+
) -> None:
|
| 235 |
+
if boundary is None:
|
| 236 |
+
boundary = os.urandom(16).hex().encode("ascii")
|
| 237 |
+
|
| 238 |
+
self.boundary = boundary
|
| 239 |
+
self.content_type = "multipart/form-data; boundary=%s" % boundary.decode(
|
| 240 |
+
"ascii"
|
| 241 |
+
)
|
| 242 |
+
self.fields = list(self._iter_fields(data, files))
|
| 243 |
+
|
| 244 |
+
def _iter_fields(
|
| 245 |
+
self, data: RequestData, files: RequestFiles
|
| 246 |
+
) -> typing.Iterator[FileField | DataField]:
|
| 247 |
+
for name, value in data.items():
|
| 248 |
+
if isinstance(value, (tuple, list)):
|
| 249 |
+
for item in value:
|
| 250 |
+
yield DataField(name=name, value=item)
|
| 251 |
+
else:
|
| 252 |
+
yield DataField(name=name, value=value)
|
| 253 |
+
|
| 254 |
+
file_items = files.items() if isinstance(files, typing.Mapping) else files
|
| 255 |
+
for name, value in file_items:
|
| 256 |
+
yield FileField(name=name, value=value)
|
| 257 |
+
|
| 258 |
+
def iter_chunks(self) -> typing.Iterator[bytes]:
|
| 259 |
+
for field in self.fields:
|
| 260 |
+
yield b"--%s\r\n" % self.boundary
|
| 261 |
+
yield from field.render()
|
| 262 |
+
yield b"\r\n"
|
| 263 |
+
yield b"--%s--\r\n" % self.boundary
|
| 264 |
+
|
| 265 |
+
def get_content_length(self) -> int | None:
|
| 266 |
+
"""
|
| 267 |
+
Return the length of the multipart encoded content, or `None` if
|
| 268 |
+
any of the files have a length that cannot be determined upfront.
|
| 269 |
+
"""
|
| 270 |
+
boundary_length = len(self.boundary)
|
| 271 |
+
length = 0
|
| 272 |
+
|
| 273 |
+
for field in self.fields:
|
| 274 |
+
field_length = field.get_length()
|
| 275 |
+
if field_length is None:
|
| 276 |
+
return None
|
| 277 |
+
|
| 278 |
+
length += 2 + boundary_length + 2 # b"--{boundary}\r\n"
|
| 279 |
+
length += field_length
|
| 280 |
+
length += 2 # b"\r\n"
|
| 281 |
+
|
| 282 |
+
length += 2 + boundary_length + 4 # b"--{boundary}--\r\n"
|
| 283 |
+
return length
|
| 284 |
+
|
| 285 |
+
# Content stream interface.
|
| 286 |
+
|
| 287 |
+
def get_headers(self) -> dict[str, str]:
|
| 288 |
+
content_length = self.get_content_length()
|
| 289 |
+
content_type = self.content_type
|
| 290 |
+
if content_length is None:
|
| 291 |
+
return {"Transfer-Encoding": "chunked", "Content-Type": content_type}
|
| 292 |
+
return {"Content-Length": str(content_length), "Content-Type": content_type}
|
| 293 |
+
|
| 294 |
+
def __iter__(self) -> typing.Iterator[bytes]:
|
| 295 |
+
for chunk in self.iter_chunks():
|
| 296 |
+
yield chunk
|
| 297 |
+
|
| 298 |
+
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
|
| 299 |
+
for chunk in self.iter_chunks():
|
| 300 |
+
yield chunk
|
.venv/lib/python3.14/site-packages/httpx/_status_codes.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from enum import IntEnum
|
| 4 |
+
|
| 5 |
+
__all__ = ["codes"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class codes(IntEnum):
|
| 9 |
+
"""HTTP status codes and reason phrases
|
| 10 |
+
|
| 11 |
+
Status codes from the following RFCs are all observed:
|
| 12 |
+
|
| 13 |
+
* RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616
|
| 14 |
+
* RFC 6585: Additional HTTP Status Codes
|
| 15 |
+
* RFC 3229: Delta encoding in HTTP
|
| 16 |
+
* RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518
|
| 17 |
+
* RFC 5842: Binding Extensions to WebDAV
|
| 18 |
+
* RFC 7238: Permanent Redirect
|
| 19 |
+
* RFC 2295: Transparent Content Negotiation in HTTP
|
| 20 |
+
* RFC 2774: An HTTP Extension Framework
|
| 21 |
+
* RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)
|
| 22 |
+
* RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)
|
| 23 |
+
* RFC 7725: An HTTP Status Code to Report Legal Obstacles
|
| 24 |
+
* RFC 8297: An HTTP Status Code for Indicating Hints
|
| 25 |
+
* RFC 8470: Using Early Data in HTTP
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
def __new__(cls, value: int, phrase: str = "") -> codes:
|
| 29 |
+
obj = int.__new__(cls, value)
|
| 30 |
+
obj._value_ = value
|
| 31 |
+
|
| 32 |
+
obj.phrase = phrase # type: ignore[attr-defined]
|
| 33 |
+
return obj
|
| 34 |
+
|
| 35 |
+
def __str__(self) -> str:
|
| 36 |
+
return str(self.value)
|
| 37 |
+
|
| 38 |
+
@classmethod
|
| 39 |
+
def get_reason_phrase(cls, value: int) -> str:
|
| 40 |
+
try:
|
| 41 |
+
return codes(value).phrase # type: ignore
|
| 42 |
+
except ValueError:
|
| 43 |
+
return ""
|
| 44 |
+
|
| 45 |
+
@classmethod
|
| 46 |
+
def is_informational(cls, value: int) -> bool:
|
| 47 |
+
"""
|
| 48 |
+
Returns `True` for 1xx status codes, `False` otherwise.
|
| 49 |
+
"""
|
| 50 |
+
return 100 <= value <= 199
|
| 51 |
+
|
| 52 |
+
@classmethod
|
| 53 |
+
def is_success(cls, value: int) -> bool:
|
| 54 |
+
"""
|
| 55 |
+
Returns `True` for 2xx status codes, `False` otherwise.
|
| 56 |
+
"""
|
| 57 |
+
return 200 <= value <= 299
|
| 58 |
+
|
| 59 |
+
@classmethod
|
| 60 |
+
def is_redirect(cls, value: int) -> bool:
|
| 61 |
+
"""
|
| 62 |
+
Returns `True` for 3xx status codes, `False` otherwise.
|
| 63 |
+
"""
|
| 64 |
+
return 300 <= value <= 399
|
| 65 |
+
|
| 66 |
+
@classmethod
|
| 67 |
+
def is_client_error(cls, value: int) -> bool:
|
| 68 |
+
"""
|
| 69 |
+
Returns `True` for 4xx status codes, `False` otherwise.
|
| 70 |
+
"""
|
| 71 |
+
return 400 <= value <= 499
|
| 72 |
+
|
| 73 |
+
@classmethod
|
| 74 |
+
def is_server_error(cls, value: int) -> bool:
|
| 75 |
+
"""
|
| 76 |
+
Returns `True` for 5xx status codes, `False` otherwise.
|
| 77 |
+
"""
|
| 78 |
+
return 500 <= value <= 599
|
| 79 |
+
|
| 80 |
+
@classmethod
|
| 81 |
+
def is_error(cls, value: int) -> bool:
|
| 82 |
+
"""
|
| 83 |
+
Returns `True` for 4xx or 5xx status codes, `False` otherwise.
|
| 84 |
+
"""
|
| 85 |
+
return 400 <= value <= 599
|
| 86 |
+
|
| 87 |
+
# informational
|
| 88 |
+
CONTINUE = 100, "Continue"
|
| 89 |
+
SWITCHING_PROTOCOLS = 101, "Switching Protocols"
|
| 90 |
+
PROCESSING = 102, "Processing"
|
| 91 |
+
EARLY_HINTS = 103, "Early Hints"
|
| 92 |
+
|
| 93 |
+
# success
|
| 94 |
+
OK = 200, "OK"
|
| 95 |
+
CREATED = 201, "Created"
|
| 96 |
+
ACCEPTED = 202, "Accepted"
|
| 97 |
+
NON_AUTHORITATIVE_INFORMATION = 203, "Non-Authoritative Information"
|
| 98 |
+
NO_CONTENT = 204, "No Content"
|
| 99 |
+
RESET_CONTENT = 205, "Reset Content"
|
| 100 |
+
PARTIAL_CONTENT = 206, "Partial Content"
|
| 101 |
+
MULTI_STATUS = 207, "Multi-Status"
|
| 102 |
+
ALREADY_REPORTED = 208, "Already Reported"
|
| 103 |
+
IM_USED = 226, "IM Used"
|
| 104 |
+
|
| 105 |
+
# redirection
|
| 106 |
+
MULTIPLE_CHOICES = 300, "Multiple Choices"
|
| 107 |
+
MOVED_PERMANENTLY = 301, "Moved Permanently"
|
| 108 |
+
FOUND = 302, "Found"
|
| 109 |
+
SEE_OTHER = 303, "See Other"
|
| 110 |
+
NOT_MODIFIED = 304, "Not Modified"
|
| 111 |
+
USE_PROXY = 305, "Use Proxy"
|
| 112 |
+
TEMPORARY_REDIRECT = 307, "Temporary Redirect"
|
| 113 |
+
PERMANENT_REDIRECT = 308, "Permanent Redirect"
|
| 114 |
+
|
| 115 |
+
# client error
|
| 116 |
+
BAD_REQUEST = 400, "Bad Request"
|
| 117 |
+
UNAUTHORIZED = 401, "Unauthorized"
|
| 118 |
+
PAYMENT_REQUIRED = 402, "Payment Required"
|
| 119 |
+
FORBIDDEN = 403, "Forbidden"
|
| 120 |
+
NOT_FOUND = 404, "Not Found"
|
| 121 |
+
METHOD_NOT_ALLOWED = 405, "Method Not Allowed"
|
| 122 |
+
NOT_ACCEPTABLE = 406, "Not Acceptable"
|
| 123 |
+
PROXY_AUTHENTICATION_REQUIRED = 407, "Proxy Authentication Required"
|
| 124 |
+
REQUEST_TIMEOUT = 408, "Request Timeout"
|
| 125 |
+
CONFLICT = 409, "Conflict"
|
| 126 |
+
GONE = 410, "Gone"
|
| 127 |
+
LENGTH_REQUIRED = 411, "Length Required"
|
| 128 |
+
PRECONDITION_FAILED = 412, "Precondition Failed"
|
| 129 |
+
REQUEST_ENTITY_TOO_LARGE = 413, "Request Entity Too Large"
|
| 130 |
+
REQUEST_URI_TOO_LONG = 414, "Request-URI Too Long"
|
| 131 |
+
UNSUPPORTED_MEDIA_TYPE = 415, "Unsupported Media Type"
|
| 132 |
+
REQUESTED_RANGE_NOT_SATISFIABLE = 416, "Requested Range Not Satisfiable"
|
| 133 |
+
EXPECTATION_FAILED = 417, "Expectation Failed"
|
| 134 |
+
IM_A_TEAPOT = 418, "I'm a teapot"
|
| 135 |
+
MISDIRECTED_REQUEST = 421, "Misdirected Request"
|
| 136 |
+
UNPROCESSABLE_ENTITY = 422, "Unprocessable Entity"
|
| 137 |
+
LOCKED = 423, "Locked"
|
| 138 |
+
FAILED_DEPENDENCY = 424, "Failed Dependency"
|
| 139 |
+
TOO_EARLY = 425, "Too Early"
|
| 140 |
+
UPGRADE_REQUIRED = 426, "Upgrade Required"
|
| 141 |
+
PRECONDITION_REQUIRED = 428, "Precondition Required"
|
| 142 |
+
TOO_MANY_REQUESTS = 429, "Too Many Requests"
|
| 143 |
+
REQUEST_HEADER_FIELDS_TOO_LARGE = 431, "Request Header Fields Too Large"
|
| 144 |
+
UNAVAILABLE_FOR_LEGAL_REASONS = 451, "Unavailable For Legal Reasons"
|
| 145 |
+
|
| 146 |
+
# server errors
|
| 147 |
+
INTERNAL_SERVER_ERROR = 500, "Internal Server Error"
|
| 148 |
+
NOT_IMPLEMENTED = 501, "Not Implemented"
|
| 149 |
+
BAD_GATEWAY = 502, "Bad Gateway"
|
| 150 |
+
SERVICE_UNAVAILABLE = 503, "Service Unavailable"
|
| 151 |
+
GATEWAY_TIMEOUT = 504, "Gateway Timeout"
|
| 152 |
+
HTTP_VERSION_NOT_SUPPORTED = 505, "HTTP Version Not Supported"
|
| 153 |
+
VARIANT_ALSO_NEGOTIATES = 506, "Variant Also Negotiates"
|
| 154 |
+
INSUFFICIENT_STORAGE = 507, "Insufficient Storage"
|
| 155 |
+
LOOP_DETECTED = 508, "Loop Detected"
|
| 156 |
+
NOT_EXTENDED = 510, "Not Extended"
|
| 157 |
+
NETWORK_AUTHENTICATION_REQUIRED = 511, "Network Authentication Required"
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# Include lower-case styles for `requests` compatibility.
|
| 161 |
+
for code in codes:
|
| 162 |
+
setattr(codes, code._name_.lower(), int(code))
|
.venv/lib/python3.14/site-packages/httpx/_transports/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .asgi import *
|
| 2 |
+
from .base import *
|
| 3 |
+
from .default import *
|
| 4 |
+
from .mock import *
|
| 5 |
+
from .wsgi import *
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"ASGITransport",
|
| 9 |
+
"AsyncBaseTransport",
|
| 10 |
+
"BaseTransport",
|
| 11 |
+
"AsyncHTTPTransport",
|
| 12 |
+
"HTTPTransport",
|
| 13 |
+
"MockTransport",
|
| 14 |
+
"WSGITransport",
|
| 15 |
+
]
|
.venv/lib/python3.14/site-packages/httpx/_transports/asgi.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import typing
|
| 4 |
+
|
| 5 |
+
from .._models import Request, Response
|
| 6 |
+
from .._types import AsyncByteStream
|
| 7 |
+
from .base import AsyncBaseTransport
|
| 8 |
+
|
| 9 |
+
if typing.TYPE_CHECKING: # pragma: no cover
|
| 10 |
+
import asyncio
|
| 11 |
+
|
| 12 |
+
import trio
|
| 13 |
+
|
| 14 |
+
Event = typing.Union[asyncio.Event, trio.Event]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
_Message = typing.MutableMapping[str, typing.Any]
|
| 18 |
+
_Receive = typing.Callable[[], typing.Awaitable[_Message]]
|
| 19 |
+
_Send = typing.Callable[
|
| 20 |
+
[typing.MutableMapping[str, typing.Any]], typing.Awaitable[None]
|
| 21 |
+
]
|
| 22 |
+
_ASGIApp = typing.Callable[
|
| 23 |
+
[typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None]
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
__all__ = ["ASGITransport"]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def is_running_trio() -> bool:
|
| 30 |
+
try:
|
| 31 |
+
# sniffio is a dependency of trio.
|
| 32 |
+
|
| 33 |
+
# See https://github.com/python-trio/trio/issues/2802
|
| 34 |
+
import sniffio
|
| 35 |
+
|
| 36 |
+
if sniffio.current_async_library() == "trio":
|
| 37 |
+
return True
|
| 38 |
+
except ImportError: # pragma: nocover
|
| 39 |
+
pass
|
| 40 |
+
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def create_event() -> Event:
|
| 45 |
+
if is_running_trio():
|
| 46 |
+
import trio
|
| 47 |
+
|
| 48 |
+
return trio.Event()
|
| 49 |
+
|
| 50 |
+
import asyncio
|
| 51 |
+
|
| 52 |
+
return asyncio.Event()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class ASGIResponseStream(AsyncByteStream):
|
| 56 |
+
def __init__(self, body: list[bytes]) -> None:
|
| 57 |
+
self._body = body
|
| 58 |
+
|
| 59 |
+
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
|
| 60 |
+
yield b"".join(self._body)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class ASGITransport(AsyncBaseTransport):
|
| 64 |
+
"""
|
| 65 |
+
A custom AsyncTransport that handles sending requests directly to an ASGI app.
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
transport = httpx.ASGITransport(
|
| 69 |
+
app=app,
|
| 70 |
+
root_path="/submount",
|
| 71 |
+
client=("1.2.3.4", 123)
|
| 72 |
+
)
|
| 73 |
+
client = httpx.AsyncClient(transport=transport)
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Arguments:
|
| 77 |
+
|
| 78 |
+
* `app` - The ASGI application.
|
| 79 |
+
* `raise_app_exceptions` - Boolean indicating if exceptions in the application
|
| 80 |
+
should be raised. Default to `True`. Can be set to `False` for use cases
|
| 81 |
+
such as testing the content of a client 500 response.
|
| 82 |
+
* `root_path` - The root path on which the ASGI application should be mounted.
|
| 83 |
+
* `client` - A two-tuple indicating the client IP and port of incoming requests.
|
| 84 |
+
```
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
def __init__(
|
| 88 |
+
self,
|
| 89 |
+
app: _ASGIApp,
|
| 90 |
+
raise_app_exceptions: bool = True,
|
| 91 |
+
root_path: str = "",
|
| 92 |
+
client: tuple[str, int] = ("127.0.0.1", 123),
|
| 93 |
+
) -> None:
|
| 94 |
+
self.app = app
|
| 95 |
+
self.raise_app_exceptions = raise_app_exceptions
|
| 96 |
+
self.root_path = root_path
|
| 97 |
+
self.client = client
|
| 98 |
+
|
| 99 |
+
async def handle_async_request(
|
| 100 |
+
self,
|
| 101 |
+
request: Request,
|
| 102 |
+
) -> Response:
|
| 103 |
+
assert isinstance(request.stream, AsyncByteStream)
|
| 104 |
+
|
| 105 |
+
# ASGI scope.
|
| 106 |
+
scope = {
|
| 107 |
+
"type": "http",
|
| 108 |
+
"asgi": {"version": "3.0"},
|
| 109 |
+
"http_version": "1.1",
|
| 110 |
+
"method": request.method,
|
| 111 |
+
"headers": [(k.lower(), v) for (k, v) in request.headers.raw],
|
| 112 |
+
"scheme": request.url.scheme,
|
| 113 |
+
"path": request.url.path,
|
| 114 |
+
"raw_path": request.url.raw_path.split(b"?")[0],
|
| 115 |
+
"query_string": request.url.query,
|
| 116 |
+
"server": (request.url.host, request.url.port),
|
| 117 |
+
"client": self.client,
|
| 118 |
+
"root_path": self.root_path,
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
# Request.
|
| 122 |
+
request_body_chunks = request.stream.__aiter__()
|
| 123 |
+
request_complete = False
|
| 124 |
+
|
| 125 |
+
# Response.
|
| 126 |
+
status_code = None
|
| 127 |
+
response_headers = None
|
| 128 |
+
body_parts = []
|
| 129 |
+
response_started = False
|
| 130 |
+
response_complete = create_event()
|
| 131 |
+
|
| 132 |
+
# ASGI callables.
|
| 133 |
+
|
| 134 |
+
async def receive() -> dict[str, typing.Any]:
|
| 135 |
+
nonlocal request_complete
|
| 136 |
+
|
| 137 |
+
if request_complete:
|
| 138 |
+
await response_complete.wait()
|
| 139 |
+
return {"type": "http.disconnect"}
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
body = await request_body_chunks.__anext__()
|
| 143 |
+
except StopAsyncIteration:
|
| 144 |
+
request_complete = True
|
| 145 |
+
return {"type": "http.request", "body": b"", "more_body": False}
|
| 146 |
+
return {"type": "http.request", "body": body, "more_body": True}
|
| 147 |
+
|
| 148 |
+
async def send(message: typing.MutableMapping[str, typing.Any]) -> None:
|
| 149 |
+
nonlocal status_code, response_headers, response_started
|
| 150 |
+
|
| 151 |
+
if message["type"] == "http.response.start":
|
| 152 |
+
assert not response_started
|
| 153 |
+
|
| 154 |
+
status_code = message["status"]
|
| 155 |
+
response_headers = message.get("headers", [])
|
| 156 |
+
response_started = True
|
| 157 |
+
|
| 158 |
+
elif message["type"] == "http.response.body":
|
| 159 |
+
assert not response_complete.is_set()
|
| 160 |
+
body = message.get("body", b"")
|
| 161 |
+
more_body = message.get("more_body", False)
|
| 162 |
+
|
| 163 |
+
if body and request.method != "HEAD":
|
| 164 |
+
body_parts.append(body)
|
| 165 |
+
|
| 166 |
+
if not more_body:
|
| 167 |
+
response_complete.set()
|
| 168 |
+
|
| 169 |
+
try:
|
| 170 |
+
await self.app(scope, receive, send)
|
| 171 |
+
except Exception: # noqa: PIE-786
|
| 172 |
+
if self.raise_app_exceptions:
|
| 173 |
+
raise
|
| 174 |
+
|
| 175 |
+
response_complete.set()
|
| 176 |
+
if status_code is None:
|
| 177 |
+
status_code = 500
|
| 178 |
+
if response_headers is None:
|
| 179 |
+
response_headers = {}
|
| 180 |
+
|
| 181 |
+
assert response_complete.is_set()
|
| 182 |
+
assert status_code is not None
|
| 183 |
+
assert response_headers is not None
|
| 184 |
+
|
| 185 |
+
stream = ASGIResponseStream(body_parts)
|
| 186 |
+
|
| 187 |
+
return Response(status_code, headers=response_headers, stream=stream)
|
.venv/lib/python3.14/site-packages/httpx/_transports/base.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import typing
|
| 4 |
+
from types import TracebackType
|
| 5 |
+
|
| 6 |
+
from .._models import Request, Response
|
| 7 |
+
|
| 8 |
+
T = typing.TypeVar("T", bound="BaseTransport")
|
| 9 |
+
A = typing.TypeVar("A", bound="AsyncBaseTransport")
|
| 10 |
+
|
| 11 |
+
__all__ = ["AsyncBaseTransport", "BaseTransport"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class BaseTransport:
|
| 15 |
+
def __enter__(self: T) -> T:
|
| 16 |
+
return self
|
| 17 |
+
|
| 18 |
+
def __exit__(
|
| 19 |
+
self,
|
| 20 |
+
exc_type: type[BaseException] | None = None,
|
| 21 |
+
exc_value: BaseException | None = None,
|
| 22 |
+
traceback: TracebackType | None = None,
|
| 23 |
+
) -> None:
|
| 24 |
+
self.close()
|
| 25 |
+
|
| 26 |
+
def handle_request(self, request: Request) -> Response:
|
| 27 |
+
"""
|
| 28 |
+
Send a single HTTP request and return a response.
|
| 29 |
+
|
| 30 |
+
Developers shouldn't typically ever need to call into this API directly,
|
| 31 |
+
since the Client class provides all the higher level user-facing API
|
| 32 |
+
niceties.
|
| 33 |
+
|
| 34 |
+
In order to properly release any network resources, the response
|
| 35 |
+
stream should *either* be consumed immediately, with a call to
|
| 36 |
+
`response.stream.read()`, or else the `handle_request` call should
|
| 37 |
+
be followed with a try/finally block to ensuring the stream is
|
| 38 |
+
always closed.
|
| 39 |
+
|
| 40 |
+
Example usage:
|
| 41 |
+
|
| 42 |
+
with httpx.HTTPTransport() as transport:
|
| 43 |
+
req = httpx.Request(
|
| 44 |
+
method=b"GET",
|
| 45 |
+
url=(b"https", b"www.example.com", 443, b"/"),
|
| 46 |
+
headers=[(b"Host", b"www.example.com")],
|
| 47 |
+
)
|
| 48 |
+
resp = transport.handle_request(req)
|
| 49 |
+
body = resp.stream.read()
|
| 50 |
+
print(resp.status_code, resp.headers, body)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
Takes a `Request` instance as the only argument.
|
| 54 |
+
|
| 55 |
+
Returns a `Response` instance.
|
| 56 |
+
"""
|
| 57 |
+
raise NotImplementedError(
|
| 58 |
+
"The 'handle_request' method must be implemented."
|
| 59 |
+
) # pragma: no cover
|
| 60 |
+
|
| 61 |
+
def close(self) -> None:
|
| 62 |
+
pass
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class AsyncBaseTransport:
|
| 66 |
+
async def __aenter__(self: A) -> A:
|
| 67 |
+
return self
|
| 68 |
+
|
| 69 |
+
async def __aexit__(
|
| 70 |
+
self,
|
| 71 |
+
exc_type: type[BaseException] | None = None,
|
| 72 |
+
exc_value: BaseException | None = None,
|
| 73 |
+
traceback: TracebackType | None = None,
|
| 74 |
+
) -> None:
|
| 75 |
+
await self.aclose()
|
| 76 |
+
|
| 77 |
+
async def handle_async_request(
|
| 78 |
+
self,
|
| 79 |
+
request: Request,
|
| 80 |
+
) -> Response:
|
| 81 |
+
raise NotImplementedError(
|
| 82 |
+
"The 'handle_async_request' method must be implemented."
|
| 83 |
+
) # pragma: no cover
|
| 84 |
+
|
| 85 |
+
async def aclose(self) -> None:
|
| 86 |
+
pass
|
.venv/lib/python3.14/site-packages/httpx/_transports/default.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Custom transports, with nicely configured defaults.
|
| 3 |
+
|
| 4 |
+
The following additional keyword arguments are currently supported by httpcore...
|
| 5 |
+
|
| 6 |
+
* uds: str
|
| 7 |
+
* local_address: str
|
| 8 |
+
* retries: int
|
| 9 |
+
|
| 10 |
+
Example usages...
|
| 11 |
+
|
| 12 |
+
# Disable HTTP/2 on a single specific domain.
|
| 13 |
+
mounts = {
|
| 14 |
+
"all://": httpx.HTTPTransport(http2=True),
|
| 15 |
+
"all://*example.org": httpx.HTTPTransport()
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
# Using advanced httpcore configuration, with connection retries.
|
| 19 |
+
transport = httpx.HTTPTransport(retries=1)
|
| 20 |
+
client = httpx.Client(transport=transport)
|
| 21 |
+
|
| 22 |
+
# Using advanced httpcore configuration, with unix domain sockets.
|
| 23 |
+
transport = httpx.HTTPTransport(uds="socket.uds")
|
| 24 |
+
client = httpx.Client(transport=transport)
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import contextlib
|
| 30 |
+
import typing
|
| 31 |
+
from types import TracebackType
|
| 32 |
+
|
| 33 |
+
if typing.TYPE_CHECKING:
|
| 34 |
+
import ssl # pragma: no cover
|
| 35 |
+
|
| 36 |
+
import httpx # pragma: no cover
|
| 37 |
+
|
| 38 |
+
from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
|
| 39 |
+
from .._exceptions import (
|
| 40 |
+
ConnectError,
|
| 41 |
+
ConnectTimeout,
|
| 42 |
+
LocalProtocolError,
|
| 43 |
+
NetworkError,
|
| 44 |
+
PoolTimeout,
|
| 45 |
+
ProtocolError,
|
| 46 |
+
ProxyError,
|
| 47 |
+
ReadError,
|
| 48 |
+
ReadTimeout,
|
| 49 |
+
RemoteProtocolError,
|
| 50 |
+
TimeoutException,
|
| 51 |
+
UnsupportedProtocol,
|
| 52 |
+
WriteError,
|
| 53 |
+
WriteTimeout,
|
| 54 |
+
)
|
| 55 |
+
from .._models import Request, Response
|
| 56 |
+
from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream
|
| 57 |
+
from .._urls import URL
|
| 58 |
+
from .base import AsyncBaseTransport, BaseTransport
|
| 59 |
+
|
| 60 |
+
T = typing.TypeVar("T", bound="HTTPTransport")
|
| 61 |
+
A = typing.TypeVar("A", bound="AsyncHTTPTransport")
|
| 62 |
+
|
| 63 |
+
SOCKET_OPTION = typing.Union[
|
| 64 |
+
typing.Tuple[int, int, int],
|
| 65 |
+
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
| 66 |
+
typing.Tuple[int, int, None, int],
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
|
| 70 |
+
|
| 71 |
+
HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:
|
| 75 |
+
import httpcore
|
| 76 |
+
|
| 77 |
+
return {
|
| 78 |
+
httpcore.TimeoutException: TimeoutException,
|
| 79 |
+
httpcore.ConnectTimeout: ConnectTimeout,
|
| 80 |
+
httpcore.ReadTimeout: ReadTimeout,
|
| 81 |
+
httpcore.WriteTimeout: WriteTimeout,
|
| 82 |
+
httpcore.PoolTimeout: PoolTimeout,
|
| 83 |
+
httpcore.NetworkError: NetworkError,
|
| 84 |
+
httpcore.ConnectError: ConnectError,
|
| 85 |
+
httpcore.ReadError: ReadError,
|
| 86 |
+
httpcore.WriteError: WriteError,
|
| 87 |
+
httpcore.ProxyError: ProxyError,
|
| 88 |
+
httpcore.UnsupportedProtocol: UnsupportedProtocol,
|
| 89 |
+
httpcore.ProtocolError: ProtocolError,
|
| 90 |
+
httpcore.LocalProtocolError: LocalProtocolError,
|
| 91 |
+
httpcore.RemoteProtocolError: RemoteProtocolError,
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@contextlib.contextmanager
|
| 96 |
+
def map_httpcore_exceptions() -> typing.Iterator[None]:
|
| 97 |
+
global HTTPCORE_EXC_MAP
|
| 98 |
+
if len(HTTPCORE_EXC_MAP) == 0:
|
| 99 |
+
HTTPCORE_EXC_MAP = _load_httpcore_exceptions()
|
| 100 |
+
try:
|
| 101 |
+
yield
|
| 102 |
+
except Exception as exc:
|
| 103 |
+
mapped_exc = None
|
| 104 |
+
|
| 105 |
+
for from_exc, to_exc in HTTPCORE_EXC_MAP.items():
|
| 106 |
+
if not isinstance(exc, from_exc):
|
| 107 |
+
continue
|
| 108 |
+
# We want to map to the most specific exception we can find.
|
| 109 |
+
# Eg if `exc` is an `httpcore.ReadTimeout`, we want to map to
|
| 110 |
+
# `httpx.ReadTimeout`, not just `httpx.TimeoutException`.
|
| 111 |
+
if mapped_exc is None or issubclass(to_exc, mapped_exc):
|
| 112 |
+
mapped_exc = to_exc
|
| 113 |
+
|
| 114 |
+
if mapped_exc is None: # pragma: no cover
|
| 115 |
+
raise
|
| 116 |
+
|
| 117 |
+
message = str(exc)
|
| 118 |
+
raise mapped_exc(message) from exc
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class ResponseStream(SyncByteStream):
|
| 122 |
+
def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None:
|
| 123 |
+
self._httpcore_stream = httpcore_stream
|
| 124 |
+
|
| 125 |
+
def __iter__(self) -> typing.Iterator[bytes]:
|
| 126 |
+
with map_httpcore_exceptions():
|
| 127 |
+
for part in self._httpcore_stream:
|
| 128 |
+
yield part
|
| 129 |
+
|
| 130 |
+
def close(self) -> None:
|
| 131 |
+
if hasattr(self._httpcore_stream, "close"):
|
| 132 |
+
self._httpcore_stream.close()
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class HTTPTransport(BaseTransport):
|
| 136 |
+
def __init__(
|
| 137 |
+
self,
|
| 138 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 139 |
+
cert: CertTypes | None = None,
|
| 140 |
+
trust_env: bool = True,
|
| 141 |
+
http1: bool = True,
|
| 142 |
+
http2: bool = False,
|
| 143 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 144 |
+
proxy: ProxyTypes | None = None,
|
| 145 |
+
uds: str | None = None,
|
| 146 |
+
local_address: str | None = None,
|
| 147 |
+
retries: int = 0,
|
| 148 |
+
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
| 149 |
+
) -> None:
|
| 150 |
+
import httpcore
|
| 151 |
+
|
| 152 |
+
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
| 153 |
+
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
| 154 |
+
|
| 155 |
+
if proxy is None:
|
| 156 |
+
self._pool = httpcore.ConnectionPool(
|
| 157 |
+
ssl_context=ssl_context,
|
| 158 |
+
max_connections=limits.max_connections,
|
| 159 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 160 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 161 |
+
http1=http1,
|
| 162 |
+
http2=http2,
|
| 163 |
+
uds=uds,
|
| 164 |
+
local_address=local_address,
|
| 165 |
+
retries=retries,
|
| 166 |
+
socket_options=socket_options,
|
| 167 |
+
)
|
| 168 |
+
elif proxy.url.scheme in ("http", "https"):
|
| 169 |
+
self._pool = httpcore.HTTPProxy(
|
| 170 |
+
proxy_url=httpcore.URL(
|
| 171 |
+
scheme=proxy.url.raw_scheme,
|
| 172 |
+
host=proxy.url.raw_host,
|
| 173 |
+
port=proxy.url.port,
|
| 174 |
+
target=proxy.url.raw_path,
|
| 175 |
+
),
|
| 176 |
+
proxy_auth=proxy.raw_auth,
|
| 177 |
+
proxy_headers=proxy.headers.raw,
|
| 178 |
+
ssl_context=ssl_context,
|
| 179 |
+
proxy_ssl_context=proxy.ssl_context,
|
| 180 |
+
max_connections=limits.max_connections,
|
| 181 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 182 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 183 |
+
http1=http1,
|
| 184 |
+
http2=http2,
|
| 185 |
+
socket_options=socket_options,
|
| 186 |
+
)
|
| 187 |
+
elif proxy.url.scheme in ("socks5", "socks5h"):
|
| 188 |
+
try:
|
| 189 |
+
import socksio # noqa
|
| 190 |
+
except ImportError: # pragma: no cover
|
| 191 |
+
raise ImportError(
|
| 192 |
+
"Using SOCKS proxy, but the 'socksio' package is not installed. "
|
| 193 |
+
"Make sure to install httpx using `pip install httpx[socks]`."
|
| 194 |
+
) from None
|
| 195 |
+
|
| 196 |
+
self._pool = httpcore.SOCKSProxy(
|
| 197 |
+
proxy_url=httpcore.URL(
|
| 198 |
+
scheme=proxy.url.raw_scheme,
|
| 199 |
+
host=proxy.url.raw_host,
|
| 200 |
+
port=proxy.url.port,
|
| 201 |
+
target=proxy.url.raw_path,
|
| 202 |
+
),
|
| 203 |
+
proxy_auth=proxy.raw_auth,
|
| 204 |
+
ssl_context=ssl_context,
|
| 205 |
+
max_connections=limits.max_connections,
|
| 206 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 207 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 208 |
+
http1=http1,
|
| 209 |
+
http2=http2,
|
| 210 |
+
)
|
| 211 |
+
else: # pragma: no cover
|
| 212 |
+
raise ValueError(
|
| 213 |
+
"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
|
| 214 |
+
f" but got {proxy.url.scheme!r}."
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
def __enter__(self: T) -> T: # Use generics for subclass support.
|
| 218 |
+
self._pool.__enter__()
|
| 219 |
+
return self
|
| 220 |
+
|
| 221 |
+
def __exit__(
|
| 222 |
+
self,
|
| 223 |
+
exc_type: type[BaseException] | None = None,
|
| 224 |
+
exc_value: BaseException | None = None,
|
| 225 |
+
traceback: TracebackType | None = None,
|
| 226 |
+
) -> None:
|
| 227 |
+
with map_httpcore_exceptions():
|
| 228 |
+
self._pool.__exit__(exc_type, exc_value, traceback)
|
| 229 |
+
|
| 230 |
+
def handle_request(
|
| 231 |
+
self,
|
| 232 |
+
request: Request,
|
| 233 |
+
) -> Response:
|
| 234 |
+
assert isinstance(request.stream, SyncByteStream)
|
| 235 |
+
import httpcore
|
| 236 |
+
|
| 237 |
+
req = httpcore.Request(
|
| 238 |
+
method=request.method,
|
| 239 |
+
url=httpcore.URL(
|
| 240 |
+
scheme=request.url.raw_scheme,
|
| 241 |
+
host=request.url.raw_host,
|
| 242 |
+
port=request.url.port,
|
| 243 |
+
target=request.url.raw_path,
|
| 244 |
+
),
|
| 245 |
+
headers=request.headers.raw,
|
| 246 |
+
content=request.stream,
|
| 247 |
+
extensions=request.extensions,
|
| 248 |
+
)
|
| 249 |
+
with map_httpcore_exceptions():
|
| 250 |
+
resp = self._pool.handle_request(req)
|
| 251 |
+
|
| 252 |
+
assert isinstance(resp.stream, typing.Iterable)
|
| 253 |
+
|
| 254 |
+
return Response(
|
| 255 |
+
status_code=resp.status,
|
| 256 |
+
headers=resp.headers,
|
| 257 |
+
stream=ResponseStream(resp.stream),
|
| 258 |
+
extensions=resp.extensions,
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
def close(self) -> None:
|
| 262 |
+
self._pool.close()
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class AsyncResponseStream(AsyncByteStream):
|
| 266 |
+
def __init__(self, httpcore_stream: typing.AsyncIterable[bytes]) -> None:
|
| 267 |
+
self._httpcore_stream = httpcore_stream
|
| 268 |
+
|
| 269 |
+
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
|
| 270 |
+
with map_httpcore_exceptions():
|
| 271 |
+
async for part in self._httpcore_stream:
|
| 272 |
+
yield part
|
| 273 |
+
|
| 274 |
+
async def aclose(self) -> None:
|
| 275 |
+
if hasattr(self._httpcore_stream, "aclose"):
|
| 276 |
+
await self._httpcore_stream.aclose()
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
class AsyncHTTPTransport(AsyncBaseTransport):
|
| 280 |
+
def __init__(
|
| 281 |
+
self,
|
| 282 |
+
verify: ssl.SSLContext | str | bool = True,
|
| 283 |
+
cert: CertTypes | None = None,
|
| 284 |
+
trust_env: bool = True,
|
| 285 |
+
http1: bool = True,
|
| 286 |
+
http2: bool = False,
|
| 287 |
+
limits: Limits = DEFAULT_LIMITS,
|
| 288 |
+
proxy: ProxyTypes | None = None,
|
| 289 |
+
uds: str | None = None,
|
| 290 |
+
local_address: str | None = None,
|
| 291 |
+
retries: int = 0,
|
| 292 |
+
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
| 293 |
+
) -> None:
|
| 294 |
+
import httpcore
|
| 295 |
+
|
| 296 |
+
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
| 297 |
+
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
| 298 |
+
|
| 299 |
+
if proxy is None:
|
| 300 |
+
self._pool = httpcore.AsyncConnectionPool(
|
| 301 |
+
ssl_context=ssl_context,
|
| 302 |
+
max_connections=limits.max_connections,
|
| 303 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 304 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 305 |
+
http1=http1,
|
| 306 |
+
http2=http2,
|
| 307 |
+
uds=uds,
|
| 308 |
+
local_address=local_address,
|
| 309 |
+
retries=retries,
|
| 310 |
+
socket_options=socket_options,
|
| 311 |
+
)
|
| 312 |
+
elif proxy.url.scheme in ("http", "https"):
|
| 313 |
+
self._pool = httpcore.AsyncHTTPProxy(
|
| 314 |
+
proxy_url=httpcore.URL(
|
| 315 |
+
scheme=proxy.url.raw_scheme,
|
| 316 |
+
host=proxy.url.raw_host,
|
| 317 |
+
port=proxy.url.port,
|
| 318 |
+
target=proxy.url.raw_path,
|
| 319 |
+
),
|
| 320 |
+
proxy_auth=proxy.raw_auth,
|
| 321 |
+
proxy_headers=proxy.headers.raw,
|
| 322 |
+
proxy_ssl_context=proxy.ssl_context,
|
| 323 |
+
ssl_context=ssl_context,
|
| 324 |
+
max_connections=limits.max_connections,
|
| 325 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 326 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 327 |
+
http1=http1,
|
| 328 |
+
http2=http2,
|
| 329 |
+
socket_options=socket_options,
|
| 330 |
+
)
|
| 331 |
+
elif proxy.url.scheme in ("socks5", "socks5h"):
|
| 332 |
+
try:
|
| 333 |
+
import socksio # noqa
|
| 334 |
+
except ImportError: # pragma: no cover
|
| 335 |
+
raise ImportError(
|
| 336 |
+
"Using SOCKS proxy, but the 'socksio' package is not installed. "
|
| 337 |
+
"Make sure to install httpx using `pip install httpx[socks]`."
|
| 338 |
+
) from None
|
| 339 |
+
|
| 340 |
+
self._pool = httpcore.AsyncSOCKSProxy(
|
| 341 |
+
proxy_url=httpcore.URL(
|
| 342 |
+
scheme=proxy.url.raw_scheme,
|
| 343 |
+
host=proxy.url.raw_host,
|
| 344 |
+
port=proxy.url.port,
|
| 345 |
+
target=proxy.url.raw_path,
|
| 346 |
+
),
|
| 347 |
+
proxy_auth=proxy.raw_auth,
|
| 348 |
+
ssl_context=ssl_context,
|
| 349 |
+
max_connections=limits.max_connections,
|
| 350 |
+
max_keepalive_connections=limits.max_keepalive_connections,
|
| 351 |
+
keepalive_expiry=limits.keepalive_expiry,
|
| 352 |
+
http1=http1,
|
| 353 |
+
http2=http2,
|
| 354 |
+
)
|
| 355 |
+
else: # pragma: no cover
|
| 356 |
+
raise ValueError(
|
| 357 |
+
"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
|
| 358 |
+
" but got {proxy.url.scheme!r}."
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
async def __aenter__(self: A) -> A: # Use generics for subclass support.
|
| 362 |
+
await self._pool.__aenter__()
|
| 363 |
+
return self
|
| 364 |
+
|
| 365 |
+
async def __aexit__(
|
| 366 |
+
self,
|
| 367 |
+
exc_type: type[BaseException] | None = None,
|
| 368 |
+
exc_value: BaseException | None = None,
|
| 369 |
+
traceback: TracebackType | None = None,
|
| 370 |
+
) -> None:
|
| 371 |
+
with map_httpcore_exceptions():
|
| 372 |
+
await self._pool.__aexit__(exc_type, exc_value, traceback)
|
| 373 |
+
|
| 374 |
+
async def handle_async_request(
|
| 375 |
+
self,
|
| 376 |
+
request: Request,
|
| 377 |
+
) -> Response:
|
| 378 |
+
assert isinstance(request.stream, AsyncByteStream)
|
| 379 |
+
import httpcore
|
| 380 |
+
|
| 381 |
+
req = httpcore.Request(
|
| 382 |
+
method=request.method,
|
| 383 |
+
url=httpcore.URL(
|
| 384 |
+
scheme=request.url.raw_scheme,
|
| 385 |
+
host=request.url.raw_host,
|
| 386 |
+
port=request.url.port,
|
| 387 |
+
target=request.url.raw_path,
|
| 388 |
+
),
|
| 389 |
+
headers=request.headers.raw,
|
| 390 |
+
content=request.stream,
|
| 391 |
+
extensions=request.extensions,
|
| 392 |
+
)
|
| 393 |
+
with map_httpcore_exceptions():
|
| 394 |
+
resp = await self._pool.handle_async_request(req)
|
| 395 |
+
|
| 396 |
+
assert isinstance(resp.stream, typing.AsyncIterable)
|
| 397 |
+
|
| 398 |
+
return Response(
|
| 399 |
+
status_code=resp.status,
|
| 400 |
+
headers=resp.headers,
|
| 401 |
+
stream=AsyncResponseStream(resp.stream),
|
| 402 |
+
extensions=resp.extensions,
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
async def aclose(self) -> None:
|
| 406 |
+
await self._pool.aclose()
|
.venv/lib/python3.14/site-packages/httpx/_transports/mock.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import typing
|
| 4 |
+
|
| 5 |
+
from .._models import Request, Response
|
| 6 |
+
from .base import AsyncBaseTransport, BaseTransport
|
| 7 |
+
|
| 8 |
+
SyncHandler = typing.Callable[[Request], Response]
|
| 9 |
+
AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__all__ = ["MockTransport"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class MockTransport(AsyncBaseTransport, BaseTransport):
|
| 16 |
+
def __init__(self, handler: SyncHandler | AsyncHandler) -> None:
|
| 17 |
+
self.handler = handler
|
| 18 |
+
|
| 19 |
+
def handle_request(
|
| 20 |
+
self,
|
| 21 |
+
request: Request,
|
| 22 |
+
) -> Response:
|
| 23 |
+
request.read()
|
| 24 |
+
response = self.handler(request)
|
| 25 |
+
if not isinstance(response, Response): # pragma: no cover
|
| 26 |
+
raise TypeError("Cannot use an async handler in a sync Client")
|
| 27 |
+
return response
|
| 28 |
+
|
| 29 |
+
async def handle_async_request(
|
| 30 |
+
self,
|
| 31 |
+
request: Request,
|
| 32 |
+
) -> Response:
|
| 33 |
+
await request.aread()
|
| 34 |
+
response = self.handler(request)
|
| 35 |
+
|
| 36 |
+
# Allow handler to *optionally* be an `async` function.
|
| 37 |
+
# If it is, then the `response` variable need to be awaited to actually
|
| 38 |
+
# return the result.
|
| 39 |
+
|
| 40 |
+
if not isinstance(response, Response):
|
| 41 |
+
response = await response
|
| 42 |
+
|
| 43 |
+
return response
|
.venv/lib/python3.14/site-packages/httpx/_transports/wsgi.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import io
|
| 4 |
+
import itertools
|
| 5 |
+
import sys
|
| 6 |
+
import typing
|
| 7 |
+
|
| 8 |
+
from .._models import Request, Response
|
| 9 |
+
from .._types import SyncByteStream
|
| 10 |
+
from .base import BaseTransport
|
| 11 |
+
|
| 12 |
+
if typing.TYPE_CHECKING:
|
| 13 |
+
from _typeshed import OptExcInfo # pragma: no cover
|
| 14 |
+
from _typeshed.wsgi import WSGIApplication # pragma: no cover
|
| 15 |
+
|
| 16 |
+
_T = typing.TypeVar("_T")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
__all__ = ["WSGITransport"]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _skip_leading_empty_chunks(body: typing.Iterable[_T]) -> typing.Iterable[_T]:
|
| 23 |
+
body = iter(body)
|
| 24 |
+
for chunk in body:
|
| 25 |
+
if chunk:
|
| 26 |
+
return itertools.chain([chunk], body)
|
| 27 |
+
return []
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class WSGIByteStream(SyncByteStream):
|
| 31 |
+
def __init__(self, result: typing.Iterable[bytes]) -> None:
|
| 32 |
+
self._close = getattr(result, "close", None)
|
| 33 |
+
self._result = _skip_leading_empty_chunks(result)
|
| 34 |
+
|
| 35 |
+
def __iter__(self) -> typing.Iterator[bytes]:
|
| 36 |
+
for part in self._result:
|
| 37 |
+
yield part
|
| 38 |
+
|
| 39 |
+
def close(self) -> None:
|
| 40 |
+
if self._close is not None:
|
| 41 |
+
self._close()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class WSGITransport(BaseTransport):
|
| 45 |
+
"""
|
| 46 |
+
A custom transport that handles sending requests directly to an WSGI app.
|
| 47 |
+
The simplest way to use this functionality is to use the `app` argument.
|
| 48 |
+
|
| 49 |
+
```
|
| 50 |
+
client = httpx.Client(app=app)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
Alternatively, you can setup the transport instance explicitly.
|
| 54 |
+
This allows you to include any additional configuration arguments specific
|
| 55 |
+
to the WSGITransport class:
|
| 56 |
+
|
| 57 |
+
```
|
| 58 |
+
transport = httpx.WSGITransport(
|
| 59 |
+
app=app,
|
| 60 |
+
script_name="/submount",
|
| 61 |
+
remote_addr="1.2.3.4"
|
| 62 |
+
)
|
| 63 |
+
client = httpx.Client(transport=transport)
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Arguments:
|
| 67 |
+
|
| 68 |
+
* `app` - The WSGI application.
|
| 69 |
+
* `raise_app_exceptions` - Boolean indicating if exceptions in the application
|
| 70 |
+
should be raised. Default to `True`. Can be set to `False` for use cases
|
| 71 |
+
such as testing the content of a client 500 response.
|
| 72 |
+
* `script_name` - The root path on which the WSGI application should be mounted.
|
| 73 |
+
* `remote_addr` - A string indicating the client IP of incoming requests.
|
| 74 |
+
```
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
def __init__(
|
| 78 |
+
self,
|
| 79 |
+
app: WSGIApplication,
|
| 80 |
+
raise_app_exceptions: bool = True,
|
| 81 |
+
script_name: str = "",
|
| 82 |
+
remote_addr: str = "127.0.0.1",
|
| 83 |
+
wsgi_errors: typing.TextIO | None = None,
|
| 84 |
+
) -> None:
|
| 85 |
+
self.app = app
|
| 86 |
+
self.raise_app_exceptions = raise_app_exceptions
|
| 87 |
+
self.script_name = script_name
|
| 88 |
+
self.remote_addr = remote_addr
|
| 89 |
+
self.wsgi_errors = wsgi_errors
|
| 90 |
+
|
| 91 |
+
def handle_request(self, request: Request) -> Response:
|
| 92 |
+
request.read()
|
| 93 |
+
wsgi_input = io.BytesIO(request.content)
|
| 94 |
+
|
| 95 |
+
port = request.url.port or {"http": 80, "https": 443}[request.url.scheme]
|
| 96 |
+
environ = {
|
| 97 |
+
"wsgi.version": (1, 0),
|
| 98 |
+
"wsgi.url_scheme": request.url.scheme,
|
| 99 |
+
"wsgi.input": wsgi_input,
|
| 100 |
+
"wsgi.errors": self.wsgi_errors or sys.stderr,
|
| 101 |
+
"wsgi.multithread": True,
|
| 102 |
+
"wsgi.multiprocess": False,
|
| 103 |
+
"wsgi.run_once": False,
|
| 104 |
+
"REQUEST_METHOD": request.method,
|
| 105 |
+
"SCRIPT_NAME": self.script_name,
|
| 106 |
+
"PATH_INFO": request.url.path,
|
| 107 |
+
"QUERY_STRING": request.url.query.decode("ascii"),
|
| 108 |
+
"SERVER_NAME": request.url.host,
|
| 109 |
+
"SERVER_PORT": str(port),
|
| 110 |
+
"SERVER_PROTOCOL": "HTTP/1.1",
|
| 111 |
+
"REMOTE_ADDR": self.remote_addr,
|
| 112 |
+
}
|
| 113 |
+
for header_key, header_value in request.headers.raw:
|
| 114 |
+
key = header_key.decode("ascii").upper().replace("-", "_")
|
| 115 |
+
if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
|
| 116 |
+
key = "HTTP_" + key
|
| 117 |
+
environ[key] = header_value.decode("ascii")
|
| 118 |
+
|
| 119 |
+
seen_status = None
|
| 120 |
+
seen_response_headers = None
|
| 121 |
+
seen_exc_info = None
|
| 122 |
+
|
| 123 |
+
def start_response(
|
| 124 |
+
status: str,
|
| 125 |
+
response_headers: list[tuple[str, str]],
|
| 126 |
+
exc_info: OptExcInfo | None = None,
|
| 127 |
+
) -> typing.Callable[[bytes], typing.Any]:
|
| 128 |
+
nonlocal seen_status, seen_response_headers, seen_exc_info
|
| 129 |
+
seen_status = status
|
| 130 |
+
seen_response_headers = response_headers
|
| 131 |
+
seen_exc_info = exc_info
|
| 132 |
+
return lambda _: None
|
| 133 |
+
|
| 134 |
+
result = self.app(environ, start_response)
|
| 135 |
+
|
| 136 |
+
stream = WSGIByteStream(result)
|
| 137 |
+
|
| 138 |
+
assert seen_status is not None
|
| 139 |
+
assert seen_response_headers is not None
|
| 140 |
+
if seen_exc_info and seen_exc_info[0] and self.raise_app_exceptions:
|
| 141 |
+
raise seen_exc_info[1]
|
| 142 |
+
|
| 143 |
+
status_code = int(seen_status.split()[0])
|
| 144 |
+
headers = [
|
| 145 |
+
(key.encode("ascii"), value.encode("ascii"))
|
| 146 |
+
for key, value in seen_response_headers
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
return Response(status_code, headers=headers, stream=stream)
|
.venv/lib/python3.14/site-packages/httpx/_types.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Type definitions for type checking purposes.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from http.cookiejar import CookieJar
|
| 6 |
+
from typing import (
|
| 7 |
+
IO,
|
| 8 |
+
TYPE_CHECKING,
|
| 9 |
+
Any,
|
| 10 |
+
AsyncIterable,
|
| 11 |
+
AsyncIterator,
|
| 12 |
+
Callable,
|
| 13 |
+
Dict,
|
| 14 |
+
Iterable,
|
| 15 |
+
Iterator,
|
| 16 |
+
List,
|
| 17 |
+
Mapping,
|
| 18 |
+
Optional,
|
| 19 |
+
Sequence,
|
| 20 |
+
Tuple,
|
| 21 |
+
Union,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 25 |
+
from ._auth import Auth # noqa: F401
|
| 26 |
+
from ._config import Proxy, Timeout # noqa: F401
|
| 27 |
+
from ._models import Cookies, Headers, Request # noqa: F401
|
| 28 |
+
from ._urls import URL, QueryParams # noqa: F401
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
PrimitiveData = Optional[Union[str, int, float, bool]]
|
| 32 |
+
|
| 33 |
+
URLTypes = Union["URL", str]
|
| 34 |
+
|
| 35 |
+
QueryParamTypes = Union[
|
| 36 |
+
"QueryParams",
|
| 37 |
+
Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]],
|
| 38 |
+
List[Tuple[str, PrimitiveData]],
|
| 39 |
+
Tuple[Tuple[str, PrimitiveData], ...],
|
| 40 |
+
str,
|
| 41 |
+
bytes,
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
HeaderTypes = Union[
|
| 45 |
+
"Headers",
|
| 46 |
+
Mapping[str, str],
|
| 47 |
+
Mapping[bytes, bytes],
|
| 48 |
+
Sequence[Tuple[str, str]],
|
| 49 |
+
Sequence[Tuple[bytes, bytes]],
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
CookieTypes = Union["Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]]
|
| 53 |
+
|
| 54 |
+
TimeoutTypes = Union[
|
| 55 |
+
Optional[float],
|
| 56 |
+
Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
|
| 57 |
+
"Timeout",
|
| 58 |
+
]
|
| 59 |
+
ProxyTypes = Union["URL", str, "Proxy"]
|
| 60 |
+
CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]
|
| 61 |
+
|
| 62 |
+
AuthTypes = Union[
|
| 63 |
+
Tuple[Union[str, bytes], Union[str, bytes]],
|
| 64 |
+
Callable[["Request"], "Request"],
|
| 65 |
+
"Auth",
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
| 69 |
+
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
| 70 |
+
ResponseExtensions = Mapping[str, Any]
|
| 71 |
+
|
| 72 |
+
RequestData = Mapping[str, Any]
|
| 73 |
+
|
| 74 |
+
FileContent = Union[IO[bytes], bytes, str]
|
| 75 |
+
FileTypes = Union[
|
| 76 |
+
# file (or bytes)
|
| 77 |
+
FileContent,
|
| 78 |
+
# (filename, file (or bytes))
|
| 79 |
+
Tuple[Optional[str], FileContent],
|
| 80 |
+
# (filename, file (or bytes), content_type)
|
| 81 |
+
Tuple[Optional[str], FileContent, Optional[str]],
|
| 82 |
+
# (filename, file (or bytes), content_type, headers)
|
| 83 |
+
Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
|
| 84 |
+
]
|
| 85 |
+
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
|
| 86 |
+
|
| 87 |
+
RequestExtensions = Mapping[str, Any]
|
| 88 |
+
|
| 89 |
+
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class SyncByteStream:
|
| 93 |
+
def __iter__(self) -> Iterator[bytes]:
|
| 94 |
+
raise NotImplementedError(
|
| 95 |
+
"The '__iter__' method must be implemented."
|
| 96 |
+
) # pragma: no cover
|
| 97 |
+
yield b"" # pragma: no cover
|
| 98 |
+
|
| 99 |
+
def close(self) -> None:
|
| 100 |
+
"""
|
| 101 |
+
Subclasses can override this method to release any network resources
|
| 102 |
+
after a request/response cycle is complete.
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class AsyncByteStream:
|
| 107 |
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
| 108 |
+
raise NotImplementedError(
|
| 109 |
+
"The '__aiter__' method must be implemented."
|
| 110 |
+
) # pragma: no cover
|
| 111 |
+
yield b"" # pragma: no cover
|
| 112 |
+
|
| 113 |
+
async def aclose(self) -> None:
|
| 114 |
+
pass
|
.venv/lib/python3.14/site-packages/httpx/_urlparse.py
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
An implementation of `urlparse` that provides URL validation and normalization
|
| 3 |
+
as described by RFC3986.
|
| 4 |
+
|
| 5 |
+
We rely on this implementation rather than the one in Python's stdlib, because:
|
| 6 |
+
|
| 7 |
+
* It provides more complete URL validation.
|
| 8 |
+
* It properly differentiates between an empty querystring and an absent querystring,
|
| 9 |
+
to distinguish URLs with a trailing '?'.
|
| 10 |
+
* It handles scheme, hostname, port, and path normalization.
|
| 11 |
+
* It supports IDNA hostnames, normalizing them to their encoded form.
|
| 12 |
+
* The API supports passing individual components, as well as the complete URL string.
|
| 13 |
+
|
| 14 |
+
Previously we relied on the excellent `rfc3986` package to handle URL parsing and
|
| 15 |
+
validation, but this module provides a simpler alternative, with less indirection
|
| 16 |
+
required.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import ipaddress
|
| 22 |
+
import re
|
| 23 |
+
import typing
|
| 24 |
+
|
| 25 |
+
import idna
|
| 26 |
+
|
| 27 |
+
from ._exceptions import InvalidURL
|
| 28 |
+
|
| 29 |
+
MAX_URL_LENGTH = 65536
|
| 30 |
+
|
| 31 |
+
# https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3
|
| 32 |
+
UNRESERVED_CHARACTERS = (
|
| 33 |
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
| 34 |
+
)
|
| 35 |
+
SUB_DELIMS = "!$&'()*+,;="
|
| 36 |
+
|
| 37 |
+
PERCENT_ENCODED_REGEX = re.compile("%[A-Fa-f0-9]{2}")
|
| 38 |
+
|
| 39 |
+
# https://url.spec.whatwg.org/#percent-encoded-bytes
|
| 40 |
+
|
| 41 |
+
# The fragment percent-encode set is the C0 control percent-encode set
|
| 42 |
+
# and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`).
|
| 43 |
+
FRAG_SAFE = "".join(
|
| 44 |
+
[chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)]
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# The query percent-encode set is the C0 control percent-encode set
|
| 48 |
+
# and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>).
|
| 49 |
+
QUERY_SAFE = "".join(
|
| 50 |
+
[chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)]
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# The path percent-encode set is the query percent-encode set
|
| 54 |
+
# and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}).
|
| 55 |
+
PATH_SAFE = "".join(
|
| 56 |
+
[
|
| 57 |
+
chr(i)
|
| 58 |
+
for i in range(0x20, 0x7F)
|
| 59 |
+
if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D)
|
| 60 |
+
]
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# The userinfo percent-encode set is the path percent-encode set
|
| 64 |
+
# and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@),
|
| 65 |
+
# U+005B ([) to U+005E (^), inclusive, and U+007C (|).
|
| 66 |
+
USERNAME_SAFE = "".join(
|
| 67 |
+
[
|
| 68 |
+
chr(i)
|
| 69 |
+
for i in range(0x20, 0x7F)
|
| 70 |
+
if i
|
| 71 |
+
not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
|
| 72 |
+
+ (0x3F, 0x60, 0x7B, 0x7D)
|
| 73 |
+
+ (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
|
| 74 |
+
]
|
| 75 |
+
)
|
| 76 |
+
PASSWORD_SAFE = "".join(
|
| 77 |
+
[
|
| 78 |
+
chr(i)
|
| 79 |
+
for i in range(0x20, 0x7F)
|
| 80 |
+
if i
|
| 81 |
+
not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
|
| 82 |
+
+ (0x3F, 0x60, 0x7B, 0x7D)
|
| 83 |
+
+ (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
|
| 84 |
+
]
|
| 85 |
+
)
|
| 86 |
+
# Note... The terminology 'userinfo' percent-encode set in the WHATWG document
|
| 87 |
+
# is used for the username and password quoting. For the joint userinfo component
|
| 88 |
+
# we remove U+003A (:) from the safe set.
|
| 89 |
+
USERINFO_SAFE = "".join(
|
| 90 |
+
[
|
| 91 |
+
chr(i)
|
| 92 |
+
for i in range(0x20, 0x7F)
|
| 93 |
+
if i
|
| 94 |
+
not in (0x20, 0x22, 0x23, 0x3C, 0x3E)
|
| 95 |
+
+ (0x3F, 0x60, 0x7B, 0x7D)
|
| 96 |
+
+ (0x2F, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)
|
| 97 |
+
]
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# {scheme}: (optional)
|
| 102 |
+
# //{authority} (optional)
|
| 103 |
+
# {path}
|
| 104 |
+
# ?{query} (optional)
|
| 105 |
+
# #{fragment} (optional)
|
| 106 |
+
URL_REGEX = re.compile(
|
| 107 |
+
(
|
| 108 |
+
r"(?:(?P<scheme>{scheme}):)?"
|
| 109 |
+
r"(?://(?P<authority>{authority}))?"
|
| 110 |
+
r"(?P<path>{path})"
|
| 111 |
+
r"(?:\?(?P<query>{query}))?"
|
| 112 |
+
r"(?:#(?P<fragment>{fragment}))?"
|
| 113 |
+
).format(
|
| 114 |
+
scheme="([a-zA-Z][a-zA-Z0-9+.-]*)?",
|
| 115 |
+
authority="[^/?#]*",
|
| 116 |
+
path="[^?#]*",
|
| 117 |
+
query="[^#]*",
|
| 118 |
+
fragment=".*",
|
| 119 |
+
)
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
# {userinfo}@ (optional)
|
| 123 |
+
# {host}
|
| 124 |
+
# :{port} (optional)
|
| 125 |
+
AUTHORITY_REGEX = re.compile(
|
| 126 |
+
(
|
| 127 |
+
r"(?:(?P<userinfo>{userinfo})@)?" r"(?P<host>{host})" r":?(?P<port>{port})?"
|
| 128 |
+
).format(
|
| 129 |
+
userinfo=".*", # Any character sequence.
|
| 130 |
+
host="(\\[.*\\]|[^:@]*)", # Either any character sequence excluding ':' or '@',
|
| 131 |
+
# or an IPv6 address enclosed within square brackets.
|
| 132 |
+
port=".*", # Any character sequence.
|
| 133 |
+
)
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
# If we call urlparse with an individual component, then we need to regex
|
| 138 |
+
# validate that component individually.
|
| 139 |
+
# Note that we're duplicating the same strings as above. Shock! Horror!!
|
| 140 |
+
COMPONENT_REGEX = {
|
| 141 |
+
"scheme": re.compile("([a-zA-Z][a-zA-Z0-9+.-]*)?"),
|
| 142 |
+
"authority": re.compile("[^/?#]*"),
|
| 143 |
+
"path": re.compile("[^?#]*"),
|
| 144 |
+
"query": re.compile("[^#]*"),
|
| 145 |
+
"fragment": re.compile(".*"),
|
| 146 |
+
"userinfo": re.compile("[^@]*"),
|
| 147 |
+
"host": re.compile("(\\[.*\\]|[^:]*)"),
|
| 148 |
+
"port": re.compile(".*"),
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
# We use these simple regexs as a first pass before handing off to
|
| 153 |
+
# the stdlib 'ipaddress' module for IP address validation.
|
| 154 |
+
IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$")
|
| 155 |
+
IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$")
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class ParseResult(typing.NamedTuple):
|
| 159 |
+
scheme: str
|
| 160 |
+
userinfo: str
|
| 161 |
+
host: str
|
| 162 |
+
port: int | None
|
| 163 |
+
path: str
|
| 164 |
+
query: str | None
|
| 165 |
+
fragment: str | None
|
| 166 |
+
|
| 167 |
+
@property
|
| 168 |
+
def authority(self) -> str:
|
| 169 |
+
return "".join(
|
| 170 |
+
[
|
| 171 |
+
f"{self.userinfo}@" if self.userinfo else "",
|
| 172 |
+
f"[{self.host}]" if ":" in self.host else self.host,
|
| 173 |
+
f":{self.port}" if self.port is not None else "",
|
| 174 |
+
]
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def netloc(self) -> str:
|
| 179 |
+
return "".join(
|
| 180 |
+
[
|
| 181 |
+
f"[{self.host}]" if ":" in self.host else self.host,
|
| 182 |
+
f":{self.port}" if self.port is not None else "",
|
| 183 |
+
]
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
def copy_with(self, **kwargs: str | None) -> ParseResult:
|
| 187 |
+
if not kwargs:
|
| 188 |
+
return self
|
| 189 |
+
|
| 190 |
+
defaults = {
|
| 191 |
+
"scheme": self.scheme,
|
| 192 |
+
"authority": self.authority,
|
| 193 |
+
"path": self.path,
|
| 194 |
+
"query": self.query,
|
| 195 |
+
"fragment": self.fragment,
|
| 196 |
+
}
|
| 197 |
+
defaults.update(kwargs)
|
| 198 |
+
return urlparse("", **defaults)
|
| 199 |
+
|
| 200 |
+
def __str__(self) -> str:
|
| 201 |
+
authority = self.authority
|
| 202 |
+
return "".join(
|
| 203 |
+
[
|
| 204 |
+
f"{self.scheme}:" if self.scheme else "",
|
| 205 |
+
f"//{authority}" if authority else "",
|
| 206 |
+
self.path,
|
| 207 |
+
f"?{self.query}" if self.query is not None else "",
|
| 208 |
+
f"#{self.fragment}" if self.fragment is not None else "",
|
| 209 |
+
]
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def urlparse(url: str = "", **kwargs: str | None) -> ParseResult:
|
| 214 |
+
# Initial basic checks on allowable URLs.
|
| 215 |
+
# ---------------------------------------
|
| 216 |
+
|
| 217 |
+
# Hard limit the maximum allowable URL length.
|
| 218 |
+
if len(url) > MAX_URL_LENGTH:
|
| 219 |
+
raise InvalidURL("URL too long")
|
| 220 |
+
|
| 221 |
+
# If a URL includes any ASCII control characters including \t, \r, \n,
|
| 222 |
+
# then treat it as invalid.
|
| 223 |
+
if any(char.isascii() and not char.isprintable() for char in url):
|
| 224 |
+
char = next(char for char in url if char.isascii() and not char.isprintable())
|
| 225 |
+
idx = url.find(char)
|
| 226 |
+
error = (
|
| 227 |
+
f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}."
|
| 228 |
+
)
|
| 229 |
+
raise InvalidURL(error)
|
| 230 |
+
|
| 231 |
+
# Some keyword arguments require special handling.
|
| 232 |
+
# ------------------------------------------------
|
| 233 |
+
|
| 234 |
+
# Coerce "port" to a string, if it is provided as an integer.
|
| 235 |
+
if "port" in kwargs:
|
| 236 |
+
port = kwargs["port"]
|
| 237 |
+
kwargs["port"] = str(port) if isinstance(port, int) else port
|
| 238 |
+
|
| 239 |
+
# Replace "netloc" with "host and "port".
|
| 240 |
+
if "netloc" in kwargs:
|
| 241 |
+
netloc = kwargs.pop("netloc") or ""
|
| 242 |
+
kwargs["host"], _, kwargs["port"] = netloc.partition(":")
|
| 243 |
+
|
| 244 |
+
# Replace "username" and/or "password" with "userinfo".
|
| 245 |
+
if "username" in kwargs or "password" in kwargs:
|
| 246 |
+
username = quote(kwargs.pop("username", "") or "", safe=USERNAME_SAFE)
|
| 247 |
+
password = quote(kwargs.pop("password", "") or "", safe=PASSWORD_SAFE)
|
| 248 |
+
kwargs["userinfo"] = f"{username}:{password}" if password else username
|
| 249 |
+
|
| 250 |
+
# Replace "raw_path" with "path" and "query".
|
| 251 |
+
if "raw_path" in kwargs:
|
| 252 |
+
raw_path = kwargs.pop("raw_path") or ""
|
| 253 |
+
kwargs["path"], seperator, kwargs["query"] = raw_path.partition("?")
|
| 254 |
+
if not seperator:
|
| 255 |
+
kwargs["query"] = None
|
| 256 |
+
|
| 257 |
+
# Ensure that IPv6 "host" addresses are always escaped with "[...]".
|
| 258 |
+
if "host" in kwargs:
|
| 259 |
+
host = kwargs.get("host") or ""
|
| 260 |
+
if ":" in host and not (host.startswith("[") and host.endswith("]")):
|
| 261 |
+
kwargs["host"] = f"[{host}]"
|
| 262 |
+
|
| 263 |
+
# If any keyword arguments are provided, ensure they are valid.
|
| 264 |
+
# -------------------------------------------------------------
|
| 265 |
+
|
| 266 |
+
for key, value in kwargs.items():
|
| 267 |
+
if value is not None:
|
| 268 |
+
if len(value) > MAX_URL_LENGTH:
|
| 269 |
+
raise InvalidURL(f"URL component '{key}' too long")
|
| 270 |
+
|
| 271 |
+
# If a component includes any ASCII control characters including \t, \r, \n,
|
| 272 |
+
# then treat it as invalid.
|
| 273 |
+
if any(char.isascii() and not char.isprintable() for char in value):
|
| 274 |
+
char = next(
|
| 275 |
+
char for char in value if char.isascii() and not char.isprintable()
|
| 276 |
+
)
|
| 277 |
+
idx = value.find(char)
|
| 278 |
+
error = (
|
| 279 |
+
f"Invalid non-printable ASCII character in URL {key} component, "
|
| 280 |
+
f"{char!r} at position {idx}."
|
| 281 |
+
)
|
| 282 |
+
raise InvalidURL(error)
|
| 283 |
+
|
| 284 |
+
# Ensure that keyword arguments match as a valid regex.
|
| 285 |
+
if not COMPONENT_REGEX[key].fullmatch(value):
|
| 286 |
+
raise InvalidURL(f"Invalid URL component '{key}'")
|
| 287 |
+
|
| 288 |
+
# The URL_REGEX will always match, but may have empty components.
|
| 289 |
+
url_match = URL_REGEX.match(url)
|
| 290 |
+
assert url_match is not None
|
| 291 |
+
url_dict = url_match.groupdict()
|
| 292 |
+
|
| 293 |
+
# * 'scheme', 'authority', and 'path' may be empty strings.
|
| 294 |
+
# * 'query' may be 'None', indicating no trailing "?" portion.
|
| 295 |
+
# Any string including the empty string, indicates a trailing "?".
|
| 296 |
+
# * 'fragment' may be 'None', indicating no trailing "#" portion.
|
| 297 |
+
# Any string including the empty string, indicates a trailing "#".
|
| 298 |
+
scheme = kwargs.get("scheme", url_dict["scheme"]) or ""
|
| 299 |
+
authority = kwargs.get("authority", url_dict["authority"]) or ""
|
| 300 |
+
path = kwargs.get("path", url_dict["path"]) or ""
|
| 301 |
+
query = kwargs.get("query", url_dict["query"])
|
| 302 |
+
frag = kwargs.get("fragment", url_dict["fragment"])
|
| 303 |
+
|
| 304 |
+
# The AUTHORITY_REGEX will always match, but may have empty components.
|
| 305 |
+
authority_match = AUTHORITY_REGEX.match(authority)
|
| 306 |
+
assert authority_match is not None
|
| 307 |
+
authority_dict = authority_match.groupdict()
|
| 308 |
+
|
| 309 |
+
# * 'userinfo' and 'host' may be empty strings.
|
| 310 |
+
# * 'port' may be 'None'.
|
| 311 |
+
userinfo = kwargs.get("userinfo", authority_dict["userinfo"]) or ""
|
| 312 |
+
host = kwargs.get("host", authority_dict["host"]) or ""
|
| 313 |
+
port = kwargs.get("port", authority_dict["port"])
|
| 314 |
+
|
| 315 |
+
# Normalize and validate each component.
|
| 316 |
+
# We end up with a parsed representation of the URL,
|
| 317 |
+
# with components that are plain ASCII bytestrings.
|
| 318 |
+
parsed_scheme: str = scheme.lower()
|
| 319 |
+
parsed_userinfo: str = quote(userinfo, safe=USERINFO_SAFE)
|
| 320 |
+
parsed_host: str = encode_host(host)
|
| 321 |
+
parsed_port: int | None = normalize_port(port, scheme)
|
| 322 |
+
|
| 323 |
+
has_scheme = parsed_scheme != ""
|
| 324 |
+
has_authority = (
|
| 325 |
+
parsed_userinfo != "" or parsed_host != "" or parsed_port is not None
|
| 326 |
+
)
|
| 327 |
+
validate_path(path, has_scheme=has_scheme, has_authority=has_authority)
|
| 328 |
+
if has_scheme or has_authority:
|
| 329 |
+
path = normalize_path(path)
|
| 330 |
+
|
| 331 |
+
parsed_path: str = quote(path, safe=PATH_SAFE)
|
| 332 |
+
parsed_query: str | None = None if query is None else quote(query, safe=QUERY_SAFE)
|
| 333 |
+
parsed_frag: str | None = None if frag is None else quote(frag, safe=FRAG_SAFE)
|
| 334 |
+
|
| 335 |
+
# The parsed ASCII bytestrings are our canonical form.
|
| 336 |
+
# All properties of the URL are derived from these.
|
| 337 |
+
return ParseResult(
|
| 338 |
+
parsed_scheme,
|
| 339 |
+
parsed_userinfo,
|
| 340 |
+
parsed_host,
|
| 341 |
+
parsed_port,
|
| 342 |
+
parsed_path,
|
| 343 |
+
parsed_query,
|
| 344 |
+
parsed_frag,
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def encode_host(host: str) -> str:
|
| 349 |
+
if not host:
|
| 350 |
+
return ""
|
| 351 |
+
|
| 352 |
+
elif IPv4_STYLE_HOSTNAME.match(host):
|
| 353 |
+
# Validate IPv4 hostnames like #.#.#.#
|
| 354 |
+
#
|
| 355 |
+
# From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
|
| 356 |
+
#
|
| 357 |
+
# IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
|
| 358 |
+
try:
|
| 359 |
+
ipaddress.IPv4Address(host)
|
| 360 |
+
except ipaddress.AddressValueError:
|
| 361 |
+
raise InvalidURL(f"Invalid IPv4 address: {host!r}")
|
| 362 |
+
return host
|
| 363 |
+
|
| 364 |
+
elif IPv6_STYLE_HOSTNAME.match(host):
|
| 365 |
+
# Validate IPv6 hostnames like [...]
|
| 366 |
+
#
|
| 367 |
+
# From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
|
| 368 |
+
#
|
| 369 |
+
# "A host identified by an Internet Protocol literal address, version 6
|
| 370 |
+
# [RFC3513] or later, is distinguished by enclosing the IP literal
|
| 371 |
+
# within square brackets ("[" and "]"). This is the only place where
|
| 372 |
+
# square bracket characters are allowed in the URI syntax."
|
| 373 |
+
try:
|
| 374 |
+
ipaddress.IPv6Address(host[1:-1])
|
| 375 |
+
except ipaddress.AddressValueError:
|
| 376 |
+
raise InvalidURL(f"Invalid IPv6 address: {host!r}")
|
| 377 |
+
return host[1:-1]
|
| 378 |
+
|
| 379 |
+
elif host.isascii():
|
| 380 |
+
# Regular ASCII hostnames
|
| 381 |
+
#
|
| 382 |
+
# From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2
|
| 383 |
+
#
|
| 384 |
+
# reg-name = *( unreserved / pct-encoded / sub-delims )
|
| 385 |
+
WHATWG_SAFE = '"`{}%|\\'
|
| 386 |
+
return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE)
|
| 387 |
+
|
| 388 |
+
# IDNA hostnames
|
| 389 |
+
try:
|
| 390 |
+
return idna.encode(host.lower()).decode("ascii")
|
| 391 |
+
except idna.IDNAError:
|
| 392 |
+
raise InvalidURL(f"Invalid IDNA hostname: {host!r}")
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def normalize_port(port: str | int | None, scheme: str) -> int | None:
|
| 396 |
+
# From https://tools.ietf.org/html/rfc3986#section-3.2.3
|
| 397 |
+
#
|
| 398 |
+
# "A scheme may define a default port. For example, the "http" scheme
|
| 399 |
+
# defines a default port of "80", corresponding to its reserved TCP
|
| 400 |
+
# port number. The type of port designated by the port number (e.g.,
|
| 401 |
+
# TCP, UDP, SCTP) is defined by the URI scheme. URI producers and
|
| 402 |
+
# normalizers should omit the port component and its ":" delimiter if
|
| 403 |
+
# port is empty or if its value would be the same as that of the
|
| 404 |
+
# scheme's default."
|
| 405 |
+
if port is None or port == "":
|
| 406 |
+
return None
|
| 407 |
+
|
| 408 |
+
try:
|
| 409 |
+
port_as_int = int(port)
|
| 410 |
+
except ValueError:
|
| 411 |
+
raise InvalidURL(f"Invalid port: {port!r}")
|
| 412 |
+
|
| 413 |
+
# See https://url.spec.whatwg.org/#url-miscellaneous
|
| 414 |
+
default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get(
|
| 415 |
+
scheme
|
| 416 |
+
)
|
| 417 |
+
if port_as_int == default_port:
|
| 418 |
+
return None
|
| 419 |
+
return port_as_int
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None:
|
| 423 |
+
"""
|
| 424 |
+
Path validation rules that depend on if the URL contains
|
| 425 |
+
a scheme or authority component.
|
| 426 |
+
|
| 427 |
+
See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3
|
| 428 |
+
"""
|
| 429 |
+
if has_authority:
|
| 430 |
+
# If a URI contains an authority component, then the path component
|
| 431 |
+
# must either be empty or begin with a slash ("/") character."
|
| 432 |
+
if path and not path.startswith("/"):
|
| 433 |
+
raise InvalidURL("For absolute URLs, path must be empty or begin with '/'")
|
| 434 |
+
|
| 435 |
+
if not has_scheme and not has_authority:
|
| 436 |
+
# If a URI does not contain an authority component, then the path cannot begin
|
| 437 |
+
# with two slash characters ("//").
|
| 438 |
+
if path.startswith("//"):
|
| 439 |
+
raise InvalidURL("Relative URLs cannot have a path starting with '//'")
|
| 440 |
+
|
| 441 |
+
# In addition, a URI reference (Section 4.1) may be a relative-path reference,
|
| 442 |
+
# in which case the first path segment cannot contain a colon (":") character.
|
| 443 |
+
if path.startswith(":"):
|
| 444 |
+
raise InvalidURL("Relative URLs cannot have a path starting with ':'")
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def normalize_path(path: str) -> str:
|
| 448 |
+
"""
|
| 449 |
+
Drop "." and ".." segments from a URL path.
|
| 450 |
+
|
| 451 |
+
For example:
|
| 452 |
+
|
| 453 |
+
normalize_path("/path/./to/somewhere/..") == "/path/to"
|
| 454 |
+
"""
|
| 455 |
+
# Fast return when no '.' characters in the path.
|
| 456 |
+
if "." not in path:
|
| 457 |
+
return path
|
| 458 |
+
|
| 459 |
+
components = path.split("/")
|
| 460 |
+
|
| 461 |
+
# Fast return when no '.' or '..' components in the path.
|
| 462 |
+
if "." not in components and ".." not in components:
|
| 463 |
+
return path
|
| 464 |
+
|
| 465 |
+
# https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
|
| 466 |
+
output: list[str] = []
|
| 467 |
+
for component in components:
|
| 468 |
+
if component == ".":
|
| 469 |
+
pass
|
| 470 |
+
elif component == "..":
|
| 471 |
+
if output and output != [""]:
|
| 472 |
+
output.pop()
|
| 473 |
+
else:
|
| 474 |
+
output.append(component)
|
| 475 |
+
return "/".join(output)
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def PERCENT(string: str) -> str:
|
| 479 |
+
return "".join([f"%{byte:02X}" for byte in string.encode("utf-8")])
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
def percent_encoded(string: str, safe: str) -> str:
|
| 483 |
+
"""
|
| 484 |
+
Use percent-encoding to quote a string.
|
| 485 |
+
"""
|
| 486 |
+
NON_ESCAPED_CHARS = UNRESERVED_CHARACTERS + safe
|
| 487 |
+
|
| 488 |
+
# Fast path for strings that don't need escaping.
|
| 489 |
+
if not string.rstrip(NON_ESCAPED_CHARS):
|
| 490 |
+
return string
|
| 491 |
+
|
| 492 |
+
return "".join(
|
| 493 |
+
[char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string]
|
| 494 |
+
)
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
def quote(string: str, safe: str) -> str:
|
| 498 |
+
"""
|
| 499 |
+
Use percent-encoding to quote a string, omitting existing '%xx' escape sequences.
|
| 500 |
+
|
| 501 |
+
See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1
|
| 502 |
+
|
| 503 |
+
* `string`: The string to be percent-escaped.
|
| 504 |
+
* `safe`: A string containing characters that may be treated as safe, and do not
|
| 505 |
+
need to be escaped. Unreserved characters are always treated as safe.
|
| 506 |
+
See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3
|
| 507 |
+
"""
|
| 508 |
+
parts = []
|
| 509 |
+
current_position = 0
|
| 510 |
+
for match in re.finditer(PERCENT_ENCODED_REGEX, string):
|
| 511 |
+
start_position, end_position = match.start(), match.end()
|
| 512 |
+
matched_text = match.group(0)
|
| 513 |
+
# Add any text up to the '%xx' escape sequence.
|
| 514 |
+
if start_position != current_position:
|
| 515 |
+
leading_text = string[current_position:start_position]
|
| 516 |
+
parts.append(percent_encoded(leading_text, safe=safe))
|
| 517 |
+
|
| 518 |
+
# Add the '%xx' escape sequence.
|
| 519 |
+
parts.append(matched_text)
|
| 520 |
+
current_position = end_position
|
| 521 |
+
|
| 522 |
+
# Add any text after the final '%xx' escape sequence.
|
| 523 |
+
if current_position != len(string):
|
| 524 |
+
trailing_text = string[current_position:]
|
| 525 |
+
parts.append(percent_encoded(trailing_text, safe=safe))
|
| 526 |
+
|
| 527 |
+
return "".join(parts)
|
.venv/lib/python3.14/site-packages/httpx/_urls.py
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import typing
|
| 4 |
+
from urllib.parse import parse_qs, unquote, urlencode
|
| 5 |
+
|
| 6 |
+
import idna
|
| 7 |
+
|
| 8 |
+
from ._types import QueryParamTypes
|
| 9 |
+
from ._urlparse import urlparse
|
| 10 |
+
from ._utils import primitive_value_to_str
|
| 11 |
+
|
| 12 |
+
__all__ = ["URL", "QueryParams"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class URL:
|
| 16 |
+
"""
|
| 17 |
+
url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink")
|
| 18 |
+
|
| 19 |
+
assert url.scheme == "https"
|
| 20 |
+
assert url.username == "jo@email.com"
|
| 21 |
+
assert url.password == "a secret"
|
| 22 |
+
assert url.userinfo == b"jo%40email.com:a%20secret"
|
| 23 |
+
assert url.host == "müller.de"
|
| 24 |
+
assert url.raw_host == b"xn--mller-kva.de"
|
| 25 |
+
assert url.port == 1234
|
| 26 |
+
assert url.netloc == b"xn--mller-kva.de:1234"
|
| 27 |
+
assert url.path == "/pa th"
|
| 28 |
+
assert url.query == b"?search=ab"
|
| 29 |
+
assert url.raw_path == b"/pa%20th?search=ab"
|
| 30 |
+
assert url.fragment == "anchorlink"
|
| 31 |
+
|
| 32 |
+
The components of a URL are broken down like this:
|
| 33 |
+
|
| 34 |
+
https://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink
|
| 35 |
+
[scheme] [ username ] [password] [ host ][port][ path ] [ query ] [fragment]
|
| 36 |
+
[ userinfo ] [ netloc ][ raw_path ]
|
| 37 |
+
|
| 38 |
+
Note that:
|
| 39 |
+
|
| 40 |
+
* `url.scheme` is normalized to always be lowercased.
|
| 41 |
+
|
| 42 |
+
* `url.host` is normalized to always be lowercased. Internationalized domain
|
| 43 |
+
names are represented in unicode, without IDNA encoding applied. For instance:
|
| 44 |
+
|
| 45 |
+
url = httpx.URL("http://中国.icom.museum")
|
| 46 |
+
assert url.host == "中国.icom.museum"
|
| 47 |
+
url = httpx.URL("http://xn--fiqs8s.icom.museum")
|
| 48 |
+
assert url.host == "中国.icom.museum"
|
| 49 |
+
|
| 50 |
+
* `url.raw_host` is normalized to always be lowercased, and is IDNA encoded.
|
| 51 |
+
|
| 52 |
+
url = httpx.URL("http://中国.icom.museum")
|
| 53 |
+
assert url.raw_host == b"xn--fiqs8s.icom.museum"
|
| 54 |
+
url = httpx.URL("http://xn--fiqs8s.icom.museum")
|
| 55 |
+
assert url.raw_host == b"xn--fiqs8s.icom.museum"
|
| 56 |
+
|
| 57 |
+
* `url.port` is either None or an integer. URLs that include the default port for
|
| 58 |
+
"http", "https", "ws", "wss", and "ftp" schemes have their port
|
| 59 |
+
normalized to `None`.
|
| 60 |
+
|
| 61 |
+
assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80")
|
| 62 |
+
assert httpx.URL("http://example.com").port is None
|
| 63 |
+
assert httpx.URL("http://example.com:80").port is None
|
| 64 |
+
|
| 65 |
+
* `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work
|
| 66 |
+
with `url.username` and `url.password` instead, which handle the URL escaping.
|
| 67 |
+
|
| 68 |
+
* `url.raw_path` is raw bytes of both the path and query, without URL escaping.
|
| 69 |
+
This portion is used as the target when constructing HTTP requests. Usually you'll
|
| 70 |
+
want to work with `url.path` instead.
|
| 71 |
+
|
| 72 |
+
* `url.query` is raw bytes, without URL escaping. A URL query string portion can
|
| 73 |
+
only be properly URL escaped when decoding the parameter names and values
|
| 74 |
+
themselves.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None:
|
| 78 |
+
if kwargs:
|
| 79 |
+
allowed = {
|
| 80 |
+
"scheme": str,
|
| 81 |
+
"username": str,
|
| 82 |
+
"password": str,
|
| 83 |
+
"userinfo": bytes,
|
| 84 |
+
"host": str,
|
| 85 |
+
"port": int,
|
| 86 |
+
"netloc": bytes,
|
| 87 |
+
"path": str,
|
| 88 |
+
"query": bytes,
|
| 89 |
+
"raw_path": bytes,
|
| 90 |
+
"fragment": str,
|
| 91 |
+
"params": object,
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
# Perform type checking for all supported keyword arguments.
|
| 95 |
+
for key, value in kwargs.items():
|
| 96 |
+
if key not in allowed:
|
| 97 |
+
message = f"{key!r} is an invalid keyword argument for URL()"
|
| 98 |
+
raise TypeError(message)
|
| 99 |
+
if value is not None and not isinstance(value, allowed[key]):
|
| 100 |
+
expected = allowed[key].__name__
|
| 101 |
+
seen = type(value).__name__
|
| 102 |
+
message = f"Argument {key!r} must be {expected} but got {seen}"
|
| 103 |
+
raise TypeError(message)
|
| 104 |
+
if isinstance(value, bytes):
|
| 105 |
+
kwargs[key] = value.decode("ascii")
|
| 106 |
+
|
| 107 |
+
if "params" in kwargs:
|
| 108 |
+
# Replace any "params" keyword with the raw "query" instead.
|
| 109 |
+
#
|
| 110 |
+
# Ensure that empty params use `kwargs["query"] = None` rather
|
| 111 |
+
# than `kwargs["query"] = ""`, so that generated URLs do not
|
| 112 |
+
# include an empty trailing "?".
|
| 113 |
+
params = kwargs.pop("params")
|
| 114 |
+
kwargs["query"] = None if not params else str(QueryParams(params))
|
| 115 |
+
|
| 116 |
+
if isinstance(url, str):
|
| 117 |
+
self._uri_reference = urlparse(url, **kwargs)
|
| 118 |
+
elif isinstance(url, URL):
|
| 119 |
+
self._uri_reference = url._uri_reference.copy_with(**kwargs)
|
| 120 |
+
else:
|
| 121 |
+
raise TypeError(
|
| 122 |
+
"Invalid type for url. Expected str or httpx.URL,"
|
| 123 |
+
f" got {type(url)}: {url!r}"
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
@property
|
| 127 |
+
def scheme(self) -> str:
|
| 128 |
+
"""
|
| 129 |
+
The URL scheme, such as "http", "https".
|
| 130 |
+
Always normalised to lowercase.
|
| 131 |
+
"""
|
| 132 |
+
return self._uri_reference.scheme
|
| 133 |
+
|
| 134 |
+
@property
|
| 135 |
+
def raw_scheme(self) -> bytes:
|
| 136 |
+
"""
|
| 137 |
+
The raw bytes representation of the URL scheme, such as b"http", b"https".
|
| 138 |
+
Always normalised to lowercase.
|
| 139 |
+
"""
|
| 140 |
+
return self._uri_reference.scheme.encode("ascii")
|
| 141 |
+
|
| 142 |
+
@property
|
| 143 |
+
def userinfo(self) -> bytes:
|
| 144 |
+
"""
|
| 145 |
+
The URL userinfo as a raw bytestring.
|
| 146 |
+
For example: b"jo%40email.com:a%20secret".
|
| 147 |
+
"""
|
| 148 |
+
return self._uri_reference.userinfo.encode("ascii")
|
| 149 |
+
|
| 150 |
+
@property
|
| 151 |
+
def username(self) -> str:
|
| 152 |
+
"""
|
| 153 |
+
The URL username as a string, with URL decoding applied.
|
| 154 |
+
For example: "jo@email.com"
|
| 155 |
+
"""
|
| 156 |
+
userinfo = self._uri_reference.userinfo
|
| 157 |
+
return unquote(userinfo.partition(":")[0])
|
| 158 |
+
|
| 159 |
+
@property
|
| 160 |
+
def password(self) -> str:
|
| 161 |
+
"""
|
| 162 |
+
The URL password as a string, with URL decoding applied.
|
| 163 |
+
For example: "a secret"
|
| 164 |
+
"""
|
| 165 |
+
userinfo = self._uri_reference.userinfo
|
| 166 |
+
return unquote(userinfo.partition(":")[2])
|
| 167 |
+
|
| 168 |
+
@property
|
| 169 |
+
def host(self) -> str:
|
| 170 |
+
"""
|
| 171 |
+
The URL host as a string.
|
| 172 |
+
Always normalized to lowercase, with IDNA hosts decoded into unicode.
|
| 173 |
+
|
| 174 |
+
Examples:
|
| 175 |
+
|
| 176 |
+
url = httpx.URL("http://www.EXAMPLE.org")
|
| 177 |
+
assert url.host == "www.example.org"
|
| 178 |
+
|
| 179 |
+
url = httpx.URL("http://中国.icom.museum")
|
| 180 |
+
assert url.host == "中国.icom.museum"
|
| 181 |
+
|
| 182 |
+
url = httpx.URL("http://xn--fiqs8s.icom.museum")
|
| 183 |
+
assert url.host == "中国.icom.museum"
|
| 184 |
+
|
| 185 |
+
url = httpx.URL("https://[::ffff:192.168.0.1]")
|
| 186 |
+
assert url.host == "::ffff:192.168.0.1"
|
| 187 |
+
"""
|
| 188 |
+
host: str = self._uri_reference.host
|
| 189 |
+
|
| 190 |
+
if host.startswith("xn--"):
|
| 191 |
+
host = idna.decode(host)
|
| 192 |
+
|
| 193 |
+
return host
|
| 194 |
+
|
| 195 |
+
@property
|
| 196 |
+
def raw_host(self) -> bytes:
|
| 197 |
+
"""
|
| 198 |
+
The raw bytes representation of the URL host.
|
| 199 |
+
Always normalized to lowercase, and IDNA encoded.
|
| 200 |
+
|
| 201 |
+
Examples:
|
| 202 |
+
|
| 203 |
+
url = httpx.URL("http://www.EXAMPLE.org")
|
| 204 |
+
assert url.raw_host == b"www.example.org"
|
| 205 |
+
|
| 206 |
+
url = httpx.URL("http://中国.icom.museum")
|
| 207 |
+
assert url.raw_host == b"xn--fiqs8s.icom.museum"
|
| 208 |
+
|
| 209 |
+
url = httpx.URL("http://xn--fiqs8s.icom.museum")
|
| 210 |
+
assert url.raw_host == b"xn--fiqs8s.icom.museum"
|
| 211 |
+
|
| 212 |
+
url = httpx.URL("https://[::ffff:192.168.0.1]")
|
| 213 |
+
assert url.raw_host == b"::ffff:192.168.0.1"
|
| 214 |
+
"""
|
| 215 |
+
return self._uri_reference.host.encode("ascii")
|
| 216 |
+
|
| 217 |
+
@property
|
| 218 |
+
def port(self) -> int | None:
|
| 219 |
+
"""
|
| 220 |
+
The URL port as an integer.
|
| 221 |
+
|
| 222 |
+
Note that the URL class performs port normalization as per the WHATWG spec.
|
| 223 |
+
Default ports for "http", "https", "ws", "wss", and "ftp" schemes are always
|
| 224 |
+
treated as `None`.
|
| 225 |
+
|
| 226 |
+
For example:
|
| 227 |
+
|
| 228 |
+
assert httpx.URL("http://www.example.com") == httpx.URL("http://www.example.com:80")
|
| 229 |
+
assert httpx.URL("http://www.example.com:80").port is None
|
| 230 |
+
"""
|
| 231 |
+
return self._uri_reference.port
|
| 232 |
+
|
| 233 |
+
@property
|
| 234 |
+
def netloc(self) -> bytes:
|
| 235 |
+
"""
|
| 236 |
+
Either `<host>` or `<host>:<port>` as bytes.
|
| 237 |
+
Always normalized to lowercase, and IDNA encoded.
|
| 238 |
+
|
| 239 |
+
This property may be used for generating the value of a request
|
| 240 |
+
"Host" header.
|
| 241 |
+
"""
|
| 242 |
+
return self._uri_reference.netloc.encode("ascii")
|
| 243 |
+
|
| 244 |
+
@property
|
| 245 |
+
def path(self) -> str:
|
| 246 |
+
"""
|
| 247 |
+
The URL path as a string. Excluding the query string, and URL decoded.
|
| 248 |
+
|
| 249 |
+
For example:
|
| 250 |
+
|
| 251 |
+
url = httpx.URL("https://example.com/pa%20th")
|
| 252 |
+
assert url.path == "/pa th"
|
| 253 |
+
"""
|
| 254 |
+
path = self._uri_reference.path or "/"
|
| 255 |
+
return unquote(path)
|
| 256 |
+
|
| 257 |
+
@property
|
| 258 |
+
def query(self) -> bytes:
|
| 259 |
+
"""
|
| 260 |
+
The URL query string, as raw bytes, excluding the leading b"?".
|
| 261 |
+
|
| 262 |
+
This is necessarily a bytewise interface, because we cannot
|
| 263 |
+
perform URL decoding of this representation until we've parsed
|
| 264 |
+
the keys and values into a QueryParams instance.
|
| 265 |
+
|
| 266 |
+
For example:
|
| 267 |
+
|
| 268 |
+
url = httpx.URL("https://example.com/?filter=some%20search%20terms")
|
| 269 |
+
assert url.query == b"filter=some%20search%20terms"
|
| 270 |
+
"""
|
| 271 |
+
query = self._uri_reference.query or ""
|
| 272 |
+
return query.encode("ascii")
|
| 273 |
+
|
| 274 |
+
@property
|
| 275 |
+
def params(self) -> QueryParams:
|
| 276 |
+
"""
|
| 277 |
+
The URL query parameters, neatly parsed and packaged into an immutable
|
| 278 |
+
multidict representation.
|
| 279 |
+
"""
|
| 280 |
+
return QueryParams(self._uri_reference.query)
|
| 281 |
+
|
| 282 |
+
@property
|
| 283 |
+
def raw_path(self) -> bytes:
|
| 284 |
+
"""
|
| 285 |
+
The complete URL path and query string as raw bytes.
|
| 286 |
+
Used as the target when constructing HTTP requests.
|
| 287 |
+
|
| 288 |
+
For example:
|
| 289 |
+
|
| 290 |
+
GET /users?search=some%20text HTTP/1.1
|
| 291 |
+
Host: www.example.org
|
| 292 |
+
Connection: close
|
| 293 |
+
"""
|
| 294 |
+
path = self._uri_reference.path or "/"
|
| 295 |
+
if self._uri_reference.query is not None:
|
| 296 |
+
path += "?" + self._uri_reference.query
|
| 297 |
+
return path.encode("ascii")
|
| 298 |
+
|
| 299 |
+
@property
|
| 300 |
+
def fragment(self) -> str:
|
| 301 |
+
"""
|
| 302 |
+
The URL fragments, as used in HTML anchors.
|
| 303 |
+
As a string, without the leading '#'.
|
| 304 |
+
"""
|
| 305 |
+
return unquote(self._uri_reference.fragment or "")
|
| 306 |
+
|
| 307 |
+
@property
|
| 308 |
+
def is_absolute_url(self) -> bool:
|
| 309 |
+
"""
|
| 310 |
+
Return `True` for absolute URLs such as 'http://example.com/path',
|
| 311 |
+
and `False` for relative URLs such as '/path'.
|
| 312 |
+
"""
|
| 313 |
+
# We don't use `.is_absolute` from `rfc3986` because it treats
|
| 314 |
+
# URLs with a fragment portion as not absolute.
|
| 315 |
+
# What we actually care about is if the URL provides
|
| 316 |
+
# a scheme and hostname to which connections should be made.
|
| 317 |
+
return bool(self._uri_reference.scheme and self._uri_reference.host)
|
| 318 |
+
|
| 319 |
+
@property
|
| 320 |
+
def is_relative_url(self) -> bool:
|
| 321 |
+
"""
|
| 322 |
+
Return `False` for absolute URLs such as 'http://example.com/path',
|
| 323 |
+
and `True` for relative URLs such as '/path'.
|
| 324 |
+
"""
|
| 325 |
+
return not self.is_absolute_url
|
| 326 |
+
|
| 327 |
+
def copy_with(self, **kwargs: typing.Any) -> URL:
|
| 328 |
+
"""
|
| 329 |
+
Copy this URL, returning a new URL with some components altered.
|
| 330 |
+
Accepts the same set of parameters as the components that are made
|
| 331 |
+
available via properties on the `URL` class.
|
| 332 |
+
|
| 333 |
+
For example:
|
| 334 |
+
|
| 335 |
+
url = httpx.URL("https://www.example.com").copy_with(
|
| 336 |
+
username="jo@gmail.com", password="a secret"
|
| 337 |
+
)
|
| 338 |
+
assert url == "https://jo%40email.com:a%20secret@www.example.com"
|
| 339 |
+
"""
|
| 340 |
+
return URL(self, **kwargs)
|
| 341 |
+
|
| 342 |
+
def copy_set_param(self, key: str, value: typing.Any = None) -> URL:
|
| 343 |
+
return self.copy_with(params=self.params.set(key, value))
|
| 344 |
+
|
| 345 |
+
def copy_add_param(self, key: str, value: typing.Any = None) -> URL:
|
| 346 |
+
return self.copy_with(params=self.params.add(key, value))
|
| 347 |
+
|
| 348 |
+
def copy_remove_param(self, key: str) -> URL:
|
| 349 |
+
return self.copy_with(params=self.params.remove(key))
|
| 350 |
+
|
| 351 |
+
def copy_merge_params(self, params: QueryParamTypes) -> URL:
|
| 352 |
+
return self.copy_with(params=self.params.merge(params))
|
| 353 |
+
|
| 354 |
+
def join(self, url: URL | str) -> URL:
|
| 355 |
+
"""
|
| 356 |
+
Return an absolute URL, using this URL as the base.
|
| 357 |
+
|
| 358 |
+
Eg.
|
| 359 |
+
|
| 360 |
+
url = httpx.URL("https://www.example.com/test")
|
| 361 |
+
url = url.join("/new/path")
|
| 362 |
+
assert url == "https://www.example.com/new/path"
|
| 363 |
+
"""
|
| 364 |
+
from urllib.parse import urljoin
|
| 365 |
+
|
| 366 |
+
return URL(urljoin(str(self), str(URL(url))))
|
| 367 |
+
|
| 368 |
+
def __hash__(self) -> int:
|
| 369 |
+
return hash(str(self))
|
| 370 |
+
|
| 371 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 372 |
+
return isinstance(other, (URL, str)) and str(self) == str(URL(other))
|
| 373 |
+
|
| 374 |
+
def __str__(self) -> str:
|
| 375 |
+
return str(self._uri_reference)
|
| 376 |
+
|
| 377 |
+
def __repr__(self) -> str:
|
| 378 |
+
scheme, userinfo, host, port, path, query, fragment = self._uri_reference
|
| 379 |
+
|
| 380 |
+
if ":" in userinfo:
|
| 381 |
+
# Mask any password component.
|
| 382 |
+
userinfo = f'{userinfo.split(":")[0]}:[secure]'
|
| 383 |
+
|
| 384 |
+
authority = "".join(
|
| 385 |
+
[
|
| 386 |
+
f"{userinfo}@" if userinfo else "",
|
| 387 |
+
f"[{host}]" if ":" in host else host,
|
| 388 |
+
f":{port}" if port is not None else "",
|
| 389 |
+
]
|
| 390 |
+
)
|
| 391 |
+
url = "".join(
|
| 392 |
+
[
|
| 393 |
+
f"{self.scheme}:" if scheme else "",
|
| 394 |
+
f"//{authority}" if authority else "",
|
| 395 |
+
path,
|
| 396 |
+
f"?{query}" if query is not None else "",
|
| 397 |
+
f"#{fragment}" if fragment is not None else "",
|
| 398 |
+
]
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
return f"{self.__class__.__name__}({url!r})"
|
| 402 |
+
|
| 403 |
+
@property
|
| 404 |
+
def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: nocover
|
| 405 |
+
import collections
|
| 406 |
+
import warnings
|
| 407 |
+
|
| 408 |
+
warnings.warn("URL.raw is deprecated.")
|
| 409 |
+
RawURL = collections.namedtuple(
|
| 410 |
+
"RawURL", ["raw_scheme", "raw_host", "port", "raw_path"]
|
| 411 |
+
)
|
| 412 |
+
return RawURL(
|
| 413 |
+
raw_scheme=self.raw_scheme,
|
| 414 |
+
raw_host=self.raw_host,
|
| 415 |
+
port=self.port,
|
| 416 |
+
raw_path=self.raw_path,
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
class QueryParams(typing.Mapping[str, str]):
|
| 421 |
+
"""
|
| 422 |
+
URL query parameters, as a multi-dict.
|
| 423 |
+
"""
|
| 424 |
+
|
| 425 |
+
def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None:
|
| 426 |
+
assert len(args) < 2, "Too many arguments."
|
| 427 |
+
assert not (args and kwargs), "Cannot mix named and unnamed arguments."
|
| 428 |
+
|
| 429 |
+
value = args[0] if args else kwargs
|
| 430 |
+
|
| 431 |
+
if value is None or isinstance(value, (str, bytes)):
|
| 432 |
+
value = value.decode("ascii") if isinstance(value, bytes) else value
|
| 433 |
+
self._dict = parse_qs(value, keep_blank_values=True)
|
| 434 |
+
elif isinstance(value, QueryParams):
|
| 435 |
+
self._dict = {k: list(v) for k, v in value._dict.items()}
|
| 436 |
+
else:
|
| 437 |
+
dict_value: dict[typing.Any, list[typing.Any]] = {}
|
| 438 |
+
if isinstance(value, (list, tuple)):
|
| 439 |
+
# Convert list inputs like:
|
| 440 |
+
# [("a", "123"), ("a", "456"), ("b", "789")]
|
| 441 |
+
# To a dict representation, like:
|
| 442 |
+
# {"a": ["123", "456"], "b": ["789"]}
|
| 443 |
+
for item in value:
|
| 444 |
+
dict_value.setdefault(item[0], []).append(item[1])
|
| 445 |
+
else:
|
| 446 |
+
# Convert dict inputs like:
|
| 447 |
+
# {"a": "123", "b": ["456", "789"]}
|
| 448 |
+
# To dict inputs where values are always lists, like:
|
| 449 |
+
# {"a": ["123"], "b": ["456", "789"]}
|
| 450 |
+
dict_value = {
|
| 451 |
+
k: list(v) if isinstance(v, (list, tuple)) else [v]
|
| 452 |
+
for k, v in value.items()
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
# Ensure that keys and values are neatly coerced to strings.
|
| 456 |
+
# We coerce values `True` and `False` to JSON-like "true" and "false"
|
| 457 |
+
# representations, and coerce `None` values to the empty string.
|
| 458 |
+
self._dict = {
|
| 459 |
+
str(k): [primitive_value_to_str(item) for item in v]
|
| 460 |
+
for k, v in dict_value.items()
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
def keys(self) -> typing.KeysView[str]:
|
| 464 |
+
"""
|
| 465 |
+
Return all the keys in the query params.
|
| 466 |
+
|
| 467 |
+
Usage:
|
| 468 |
+
|
| 469 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 470 |
+
assert list(q.keys()) == ["a", "b"]
|
| 471 |
+
"""
|
| 472 |
+
return self._dict.keys()
|
| 473 |
+
|
| 474 |
+
def values(self) -> typing.ValuesView[str]:
|
| 475 |
+
"""
|
| 476 |
+
Return all the values in the query params. If a key occurs more than once
|
| 477 |
+
only the first item for that key is returned.
|
| 478 |
+
|
| 479 |
+
Usage:
|
| 480 |
+
|
| 481 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 482 |
+
assert list(q.values()) == ["123", "789"]
|
| 483 |
+
"""
|
| 484 |
+
return {k: v[0] for k, v in self._dict.items()}.values()
|
| 485 |
+
|
| 486 |
+
def items(self) -> typing.ItemsView[str, str]:
|
| 487 |
+
"""
|
| 488 |
+
Return all items in the query params. If a key occurs more than once
|
| 489 |
+
only the first item for that key is returned.
|
| 490 |
+
|
| 491 |
+
Usage:
|
| 492 |
+
|
| 493 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 494 |
+
assert list(q.items()) == [("a", "123"), ("b", "789")]
|
| 495 |
+
"""
|
| 496 |
+
return {k: v[0] for k, v in self._dict.items()}.items()
|
| 497 |
+
|
| 498 |
+
def multi_items(self) -> list[tuple[str, str]]:
|
| 499 |
+
"""
|
| 500 |
+
Return all items in the query params. Allow duplicate keys to occur.
|
| 501 |
+
|
| 502 |
+
Usage:
|
| 503 |
+
|
| 504 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 505 |
+
assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")]
|
| 506 |
+
"""
|
| 507 |
+
multi_items: list[tuple[str, str]] = []
|
| 508 |
+
for k, v in self._dict.items():
|
| 509 |
+
multi_items.extend([(k, i) for i in v])
|
| 510 |
+
return multi_items
|
| 511 |
+
|
| 512 |
+
def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any:
|
| 513 |
+
"""
|
| 514 |
+
Get a value from the query param for a given key. If the key occurs
|
| 515 |
+
more than once, then only the first value is returned.
|
| 516 |
+
|
| 517 |
+
Usage:
|
| 518 |
+
|
| 519 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 520 |
+
assert q.get("a") == "123"
|
| 521 |
+
"""
|
| 522 |
+
if key in self._dict:
|
| 523 |
+
return self._dict[str(key)][0]
|
| 524 |
+
return default
|
| 525 |
+
|
| 526 |
+
def get_list(self, key: str) -> list[str]:
|
| 527 |
+
"""
|
| 528 |
+
Get all values from the query param for a given key.
|
| 529 |
+
|
| 530 |
+
Usage:
|
| 531 |
+
|
| 532 |
+
q = httpx.QueryParams("a=123&a=456&b=789")
|
| 533 |
+
assert q.get_list("a") == ["123", "456"]
|
| 534 |
+
"""
|
| 535 |
+
return list(self._dict.get(str(key), []))
|
| 536 |
+
|
| 537 |
+
def set(self, key: str, value: typing.Any = None) -> QueryParams:
|
| 538 |
+
"""
|
| 539 |
+
Return a new QueryParams instance, setting the value of a key.
|
| 540 |
+
|
| 541 |
+
Usage:
|
| 542 |
+
|
| 543 |
+
q = httpx.QueryParams("a=123")
|
| 544 |
+
q = q.set("a", "456")
|
| 545 |
+
assert q == httpx.QueryParams("a=456")
|
| 546 |
+
"""
|
| 547 |
+
q = QueryParams()
|
| 548 |
+
q._dict = dict(self._dict)
|
| 549 |
+
q._dict[str(key)] = [primitive_value_to_str(value)]
|
| 550 |
+
return q
|
| 551 |
+
|
| 552 |
+
def add(self, key: str, value: typing.Any = None) -> QueryParams:
|
| 553 |
+
"""
|
| 554 |
+
Return a new QueryParams instance, setting or appending the value of a key.
|
| 555 |
+
|
| 556 |
+
Usage:
|
| 557 |
+
|
| 558 |
+
q = httpx.QueryParams("a=123")
|
| 559 |
+
q = q.add("a", "456")
|
| 560 |
+
assert q == httpx.QueryParams("a=123&a=456")
|
| 561 |
+
"""
|
| 562 |
+
q = QueryParams()
|
| 563 |
+
q._dict = dict(self._dict)
|
| 564 |
+
q._dict[str(key)] = q.get_list(key) + [primitive_value_to_str(value)]
|
| 565 |
+
return q
|
| 566 |
+
|
| 567 |
+
def remove(self, key: str) -> QueryParams:
|
| 568 |
+
"""
|
| 569 |
+
Return a new QueryParams instance, removing the value of a key.
|
| 570 |
+
|
| 571 |
+
Usage:
|
| 572 |
+
|
| 573 |
+
q = httpx.QueryParams("a=123")
|
| 574 |
+
q = q.remove("a")
|
| 575 |
+
assert q == httpx.QueryParams("")
|
| 576 |
+
"""
|
| 577 |
+
q = QueryParams()
|
| 578 |
+
q._dict = dict(self._dict)
|
| 579 |
+
q._dict.pop(str(key), None)
|
| 580 |
+
return q
|
| 581 |
+
|
| 582 |
+
def merge(self, params: QueryParamTypes | None = None) -> QueryParams:
|
| 583 |
+
"""
|
| 584 |
+
Return a new QueryParams instance, updated with.
|
| 585 |
+
|
| 586 |
+
Usage:
|
| 587 |
+
|
| 588 |
+
q = httpx.QueryParams("a=123")
|
| 589 |
+
q = q.merge({"b": "456"})
|
| 590 |
+
assert q == httpx.QueryParams("a=123&b=456")
|
| 591 |
+
|
| 592 |
+
q = httpx.QueryParams("a=123")
|
| 593 |
+
q = q.merge({"a": "456", "b": "789"})
|
| 594 |
+
assert q == httpx.QueryParams("a=456&b=789")
|
| 595 |
+
"""
|
| 596 |
+
q = QueryParams(params)
|
| 597 |
+
q._dict = {**self._dict, **q._dict}
|
| 598 |
+
return q
|
| 599 |
+
|
| 600 |
+
def __getitem__(self, key: typing.Any) -> str:
|
| 601 |
+
return self._dict[key][0]
|
| 602 |
+
|
| 603 |
+
def __contains__(self, key: typing.Any) -> bool:
|
| 604 |
+
return key in self._dict
|
| 605 |
+
|
| 606 |
+
def __iter__(self) -> typing.Iterator[typing.Any]:
|
| 607 |
+
return iter(self.keys())
|
| 608 |
+
|
| 609 |
+
def __len__(self) -> int:
|
| 610 |
+
return len(self._dict)
|
| 611 |
+
|
| 612 |
+
def __bool__(self) -> bool:
|
| 613 |
+
return bool(self._dict)
|
| 614 |
+
|
| 615 |
+
def __hash__(self) -> int:
|
| 616 |
+
return hash(str(self))
|
| 617 |
+
|
| 618 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 619 |
+
if not isinstance(other, self.__class__):
|
| 620 |
+
return False
|
| 621 |
+
return sorted(self.multi_items()) == sorted(other.multi_items())
|
| 622 |
+
|
| 623 |
+
def __str__(self) -> str:
|
| 624 |
+
return urlencode(self.multi_items())
|
| 625 |
+
|
| 626 |
+
def __repr__(self) -> str:
|
| 627 |
+
class_name = self.__class__.__name__
|
| 628 |
+
query_string = str(self)
|
| 629 |
+
return f"{class_name}({query_string!r})"
|
| 630 |
+
|
| 631 |
+
def update(self, params: QueryParamTypes | None = None) -> None:
|
| 632 |
+
raise RuntimeError(
|
| 633 |
+
"QueryParams are immutable since 0.18.0. "
|
| 634 |
+
"Use `q = q.merge(...)` to create an updated copy."
|
| 635 |
+
)
|
| 636 |
+
|
| 637 |
+
def __setitem__(self, key: str, value: str) -> None:
|
| 638 |
+
raise RuntimeError(
|
| 639 |
+
"QueryParams are immutable since 0.18.0. "
|
| 640 |
+
"Use `q = q.set(key, value)` to create an updated copy."
|
| 641 |
+
)
|
.venv/lib/python3.14/site-packages/httpx/_utils.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import ipaddress
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import typing
|
| 7 |
+
from urllib.request import getproxies
|
| 8 |
+
|
| 9 |
+
from ._types import PrimitiveData
|
| 10 |
+
|
| 11 |
+
if typing.TYPE_CHECKING: # pragma: no cover
|
| 12 |
+
from ._urls import URL
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def primitive_value_to_str(value: PrimitiveData) -> str:
|
| 16 |
+
"""
|
| 17 |
+
Coerce a primitive data type into a string value.
|
| 18 |
+
|
| 19 |
+
Note that we prefer JSON-style 'true'/'false' for boolean values here.
|
| 20 |
+
"""
|
| 21 |
+
if value is True:
|
| 22 |
+
return "true"
|
| 23 |
+
elif value is False:
|
| 24 |
+
return "false"
|
| 25 |
+
elif value is None:
|
| 26 |
+
return ""
|
| 27 |
+
return str(value)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def get_environment_proxies() -> dict[str, str | None]:
|
| 31 |
+
"""Gets proxy information from the environment"""
|
| 32 |
+
|
| 33 |
+
# urllib.request.getproxies() falls back on System
|
| 34 |
+
# Registry and Config for proxies on Windows and macOS.
|
| 35 |
+
# We don't want to propagate non-HTTP proxies into
|
| 36 |
+
# our configuration such as 'TRAVIS_APT_PROXY'.
|
| 37 |
+
proxy_info = getproxies()
|
| 38 |
+
mounts: dict[str, str | None] = {}
|
| 39 |
+
|
| 40 |
+
for scheme in ("http", "https", "all"):
|
| 41 |
+
if proxy_info.get(scheme):
|
| 42 |
+
hostname = proxy_info[scheme]
|
| 43 |
+
mounts[f"{scheme}://"] = (
|
| 44 |
+
hostname if "://" in hostname else f"http://{hostname}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")]
|
| 48 |
+
for hostname in no_proxy_hosts:
|
| 49 |
+
# See https://curl.haxx.se/libcurl/c/CURLOPT_NOPROXY.html for details
|
| 50 |
+
# on how names in `NO_PROXY` are handled.
|
| 51 |
+
if hostname == "*":
|
| 52 |
+
# If NO_PROXY=* is used or if "*" occurs as any one of the comma
|
| 53 |
+
# separated hostnames, then we should just bypass any information
|
| 54 |
+
# from HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and always ignore
|
| 55 |
+
# proxies.
|
| 56 |
+
return {}
|
| 57 |
+
elif hostname:
|
| 58 |
+
# NO_PROXY=.google.com is marked as "all://*.google.com,
|
| 59 |
+
# which disables "www.google.com" but not "google.com"
|
| 60 |
+
# NO_PROXY=google.com is marked as "all://*google.com,
|
| 61 |
+
# which disables "www.google.com" and "google.com".
|
| 62 |
+
# (But not "wwwgoogle.com")
|
| 63 |
+
# NO_PROXY can include domains, IPv6, IPv4 addresses and "localhost"
|
| 64 |
+
# NO_PROXY=example.com,::1,localhost,192.168.0.0/16
|
| 65 |
+
if "://" in hostname:
|
| 66 |
+
mounts[hostname] = None
|
| 67 |
+
elif is_ipv4_hostname(hostname):
|
| 68 |
+
mounts[f"all://{hostname}"] = None
|
| 69 |
+
elif is_ipv6_hostname(hostname):
|
| 70 |
+
mounts[f"all://[{hostname}]"] = None
|
| 71 |
+
elif hostname.lower() == "localhost":
|
| 72 |
+
mounts[f"all://{hostname}"] = None
|
| 73 |
+
else:
|
| 74 |
+
mounts[f"all://*{hostname}"] = None
|
| 75 |
+
|
| 76 |
+
return mounts
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def to_bytes(value: str | bytes, encoding: str = "utf-8") -> bytes:
|
| 80 |
+
return value.encode(encoding) if isinstance(value, str) else value
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def to_str(value: str | bytes, encoding: str = "utf-8") -> str:
|
| 84 |
+
return value if isinstance(value, str) else value.decode(encoding)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr:
|
| 88 |
+
return value if isinstance(match_type_of, str) else value.encode()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def unquote(value: str) -> str:
|
| 92 |
+
return value[1:-1] if value[0] == value[-1] == '"' else value
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def peek_filelike_length(stream: typing.Any) -> int | None:
|
| 96 |
+
"""
|
| 97 |
+
Given a file-like stream object, return its length in number of bytes
|
| 98 |
+
without reading it into memory.
|
| 99 |
+
"""
|
| 100 |
+
try:
|
| 101 |
+
# Is it an actual file?
|
| 102 |
+
fd = stream.fileno()
|
| 103 |
+
# Yup, seems to be an actual file.
|
| 104 |
+
length = os.fstat(fd).st_size
|
| 105 |
+
except (AttributeError, OSError):
|
| 106 |
+
# No... Maybe it's something that supports random access, like `io.BytesIO`?
|
| 107 |
+
try:
|
| 108 |
+
# Assuming so, go to end of stream to figure out its length,
|
| 109 |
+
# then put it back in place.
|
| 110 |
+
offset = stream.tell()
|
| 111 |
+
length = stream.seek(0, os.SEEK_END)
|
| 112 |
+
stream.seek(offset)
|
| 113 |
+
except (AttributeError, OSError):
|
| 114 |
+
# Not even that? Sorry, we're doomed...
|
| 115 |
+
return None
|
| 116 |
+
|
| 117 |
+
return length
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class URLPattern:
|
| 121 |
+
"""
|
| 122 |
+
A utility class currently used for making lookups against proxy keys...
|
| 123 |
+
|
| 124 |
+
# Wildcard matching...
|
| 125 |
+
>>> pattern = URLPattern("all://")
|
| 126 |
+
>>> pattern.matches(httpx.URL("http://example.com"))
|
| 127 |
+
True
|
| 128 |
+
|
| 129 |
+
# Witch scheme matching...
|
| 130 |
+
>>> pattern = URLPattern("https://")
|
| 131 |
+
>>> pattern.matches(httpx.URL("https://example.com"))
|
| 132 |
+
True
|
| 133 |
+
>>> pattern.matches(httpx.URL("http://example.com"))
|
| 134 |
+
False
|
| 135 |
+
|
| 136 |
+
# With domain matching...
|
| 137 |
+
>>> pattern = URLPattern("https://example.com")
|
| 138 |
+
>>> pattern.matches(httpx.URL("https://example.com"))
|
| 139 |
+
True
|
| 140 |
+
>>> pattern.matches(httpx.URL("http://example.com"))
|
| 141 |
+
False
|
| 142 |
+
>>> pattern.matches(httpx.URL("https://other.com"))
|
| 143 |
+
False
|
| 144 |
+
|
| 145 |
+
# Wildcard scheme, with domain matching...
|
| 146 |
+
>>> pattern = URLPattern("all://example.com")
|
| 147 |
+
>>> pattern.matches(httpx.URL("https://example.com"))
|
| 148 |
+
True
|
| 149 |
+
>>> pattern.matches(httpx.URL("http://example.com"))
|
| 150 |
+
True
|
| 151 |
+
>>> pattern.matches(httpx.URL("https://other.com"))
|
| 152 |
+
False
|
| 153 |
+
|
| 154 |
+
# With port matching...
|
| 155 |
+
>>> pattern = URLPattern("https://example.com:1234")
|
| 156 |
+
>>> pattern.matches(httpx.URL("https://example.com:1234"))
|
| 157 |
+
True
|
| 158 |
+
>>> pattern.matches(httpx.URL("https://example.com"))
|
| 159 |
+
False
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
def __init__(self, pattern: str) -> None:
|
| 163 |
+
from ._urls import URL
|
| 164 |
+
|
| 165 |
+
if pattern and ":" not in pattern:
|
| 166 |
+
raise ValueError(
|
| 167 |
+
f"Proxy keys should use proper URL forms rather "
|
| 168 |
+
f"than plain scheme strings. "
|
| 169 |
+
f'Instead of "{pattern}", use "{pattern}://"'
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
url = URL(pattern)
|
| 173 |
+
self.pattern = pattern
|
| 174 |
+
self.scheme = "" if url.scheme == "all" else url.scheme
|
| 175 |
+
self.host = "" if url.host == "*" else url.host
|
| 176 |
+
self.port = url.port
|
| 177 |
+
if not url.host or url.host == "*":
|
| 178 |
+
self.host_regex: typing.Pattern[str] | None = None
|
| 179 |
+
elif url.host.startswith("*."):
|
| 180 |
+
# *.example.com should match "www.example.com", but not "example.com"
|
| 181 |
+
domain = re.escape(url.host[2:])
|
| 182 |
+
self.host_regex = re.compile(f"^.+\\.{domain}$")
|
| 183 |
+
elif url.host.startswith("*"):
|
| 184 |
+
# *example.com should match "www.example.com" and "example.com"
|
| 185 |
+
domain = re.escape(url.host[1:])
|
| 186 |
+
self.host_regex = re.compile(f"^(.+\\.)?{domain}$")
|
| 187 |
+
else:
|
| 188 |
+
# example.com should match "example.com" but not "www.example.com"
|
| 189 |
+
domain = re.escape(url.host)
|
| 190 |
+
self.host_regex = re.compile(f"^{domain}$")
|
| 191 |
+
|
| 192 |
+
def matches(self, other: URL) -> bool:
|
| 193 |
+
if self.scheme and self.scheme != other.scheme:
|
| 194 |
+
return False
|
| 195 |
+
if (
|
| 196 |
+
self.host
|
| 197 |
+
and self.host_regex is not None
|
| 198 |
+
and not self.host_regex.match(other.host)
|
| 199 |
+
):
|
| 200 |
+
return False
|
| 201 |
+
if self.port is not None and self.port != other.port:
|
| 202 |
+
return False
|
| 203 |
+
return True
|
| 204 |
+
|
| 205 |
+
@property
|
| 206 |
+
def priority(self) -> tuple[int, int, int]:
|
| 207 |
+
"""
|
| 208 |
+
The priority allows URLPattern instances to be sortable, so that
|
| 209 |
+
we can match from most specific to least specific.
|
| 210 |
+
"""
|
| 211 |
+
# URLs with a port should take priority over URLs without a port.
|
| 212 |
+
port_priority = 0 if self.port is not None else 1
|
| 213 |
+
# Longer hostnames should match first.
|
| 214 |
+
host_priority = -len(self.host)
|
| 215 |
+
# Longer schemes should match first.
|
| 216 |
+
scheme_priority = -len(self.scheme)
|
| 217 |
+
return (port_priority, host_priority, scheme_priority)
|
| 218 |
+
|
| 219 |
+
def __hash__(self) -> int:
|
| 220 |
+
return hash(self.pattern)
|
| 221 |
+
|
| 222 |
+
def __lt__(self, other: URLPattern) -> bool:
|
| 223 |
+
return self.priority < other.priority
|
| 224 |
+
|
| 225 |
+
def __eq__(self, other: typing.Any) -> bool:
|
| 226 |
+
return isinstance(other, URLPattern) and self.pattern == other.pattern
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def is_ipv4_hostname(hostname: str) -> bool:
|
| 230 |
+
try:
|
| 231 |
+
ipaddress.IPv4Address(hostname.split("/")[0])
|
| 232 |
+
except Exception:
|
| 233 |
+
return False
|
| 234 |
+
return True
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def is_ipv6_hostname(hostname: str) -> bool:
|
| 238 |
+
try:
|
| 239 |
+
ipaddress.IPv6Address(hostname.split("/")[0])
|
| 240 |
+
except Exception:
|
| 241 |
+
return False
|
| 242 |
+
return True
|
.venv/lib/python3.14/site-packages/httpx/py.typed
ADDED
|
File without changes
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: huggingface_hub
|
| 3 |
+
Version: 1.26.0
|
| 4 |
+
Summary: Client library to download and publish models, datasets and other repos on the huggingface.co hub
|
| 5 |
+
Home-page: https://github.com/huggingface/huggingface_hub
|
| 6 |
+
Author: Hugging Face, Inc.
|
| 7 |
+
Author-email: julien@huggingface.co
|
| 8 |
+
License: Apache-2.0
|
| 9 |
+
Keywords: model-hub machine-learning models natural-language-processing deep-learning pytorch pretrained-models
|
| 10 |
+
Classifier: Intended Audience :: Developers
|
| 11 |
+
Classifier: Intended Audience :: Education
|
| 12 |
+
Classifier: Intended Audience :: Science/Research
|
| 13 |
+
Classifier: License :: OSI Approved :: Apache Software License
|
| 14 |
+
Classifier: Operating System :: OS Independent
|
| 15 |
+
Classifier: Programming Language :: Python :: 3
|
| 16 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 22 |
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
| 23 |
+
Requires-Python: >=3.10.0
|
| 24 |
+
Description-Content-Type: text/markdown
|
| 25 |
+
License-File: LICENSE
|
| 26 |
+
Requires-Dist: click<9.0.0,>=8.4.2
|
| 27 |
+
Requires-Dist: filelock>=3.10.0
|
| 28 |
+
Requires-Dist: fsspec>=2023.5.0
|
| 29 |
+
Requires-Dist: hf-xet<2.0.0,>=1.5.1; platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "arm64" or platform_machine == "aarch64"
|
| 30 |
+
Requires-Dist: httpx<1,>=0.23.0
|
| 31 |
+
Requires-Dist: packaging>=20.9
|
| 32 |
+
Requires-Dist: pyyaml>=5.1
|
| 33 |
+
Requires-Dist: tqdm>=4.42.1
|
| 34 |
+
Requires-Dist: typing-extensions>=4.1.0
|
| 35 |
+
Provides-Extra: oauth
|
| 36 |
+
Requires-Dist: authlib>=1.3.2; extra == "oauth"
|
| 37 |
+
Requires-Dist: fastapi; extra == "oauth"
|
| 38 |
+
Requires-Dist: httpx; extra == "oauth"
|
| 39 |
+
Requires-Dist: itsdangerous; extra == "oauth"
|
| 40 |
+
Provides-Extra: torch
|
| 41 |
+
Requires-Dist: torch; extra == "torch"
|
| 42 |
+
Requires-Dist: safetensors[torch]; extra == "torch"
|
| 43 |
+
Provides-Extra: fastai
|
| 44 |
+
Requires-Dist: toml; extra == "fastai"
|
| 45 |
+
Requires-Dist: fastai>=2.4; extra == "fastai"
|
| 46 |
+
Requires-Dist: fastcore>=1.3.27; extra == "fastai"
|
| 47 |
+
Provides-Extra: hf-xet
|
| 48 |
+
Requires-Dist: hf-xet<2.0.0,>=1.5.1; extra == "hf-xet"
|
| 49 |
+
Provides-Extra: mcp
|
| 50 |
+
Requires-Dist: mcp>=1.8.0; extra == "mcp"
|
| 51 |
+
Provides-Extra: testing
|
| 52 |
+
Requires-Dist: authlib>=1.3.2; extra == "testing"
|
| 53 |
+
Requires-Dist: fastapi; extra == "testing"
|
| 54 |
+
Requires-Dist: httpx; extra == "testing"
|
| 55 |
+
Requires-Dist: itsdangerous; extra == "testing"
|
| 56 |
+
Requires-Dist: jedi; extra == "testing"
|
| 57 |
+
Requires-Dist: Jinja2; extra == "testing"
|
| 58 |
+
Requires-Dist: pytest>=8.4.2; extra == "testing"
|
| 59 |
+
Requires-Dist: pytest-cov; extra == "testing"
|
| 60 |
+
Requires-Dist: pytest-env; extra == "testing"
|
| 61 |
+
Requires-Dist: pytest-xdist; extra == "testing"
|
| 62 |
+
Requires-Dist: pytest-vcr; extra == "testing"
|
| 63 |
+
Requires-Dist: pytest-asyncio; extra == "testing"
|
| 64 |
+
Requires-Dist: pytest-rerunfailures>=16.2; extra == "testing"
|
| 65 |
+
Requires-Dist: pytest-mock; extra == "testing"
|
| 66 |
+
Requires-Dist: urllib3<2.0; extra == "testing"
|
| 67 |
+
Requires-Dist: soundfile; extra == "testing"
|
| 68 |
+
Requires-Dist: Pillow; extra == "testing"
|
| 69 |
+
Requires-Dist: numpy; extra == "testing"
|
| 70 |
+
Requires-Dist: duckdb; extra == "testing"
|
| 71 |
+
Requires-Dist: fastapi; extra == "testing"
|
| 72 |
+
Provides-Extra: gradio
|
| 73 |
+
Requires-Dist: gradio>=5.0.0; extra == "gradio"
|
| 74 |
+
Requires-Dist: requests; extra == "gradio"
|
| 75 |
+
Provides-Extra: typing
|
| 76 |
+
Requires-Dist: typing-extensions>=4.8.0; extra == "typing"
|
| 77 |
+
Requires-Dist: types-PyYAML; extra == "typing"
|
| 78 |
+
Requires-Dist: types-simplejson; extra == "typing"
|
| 79 |
+
Requires-Dist: types-toml; extra == "typing"
|
| 80 |
+
Requires-Dist: types-tqdm; extra == "typing"
|
| 81 |
+
Requires-Dist: types-urllib3; extra == "typing"
|
| 82 |
+
Provides-Extra: quality
|
| 83 |
+
Requires-Dist: ruff>=0.9.0; extra == "quality"
|
| 84 |
+
Requires-Dist: mypy==1.15.0; extra == "quality"
|
| 85 |
+
Requires-Dist: libcst>=1.4.0; extra == "quality"
|
| 86 |
+
Requires-Dist: ty; extra == "quality"
|
| 87 |
+
Provides-Extra: all
|
| 88 |
+
Requires-Dist: authlib>=1.3.2; extra == "all"
|
| 89 |
+
Requires-Dist: fastapi; extra == "all"
|
| 90 |
+
Requires-Dist: httpx; extra == "all"
|
| 91 |
+
Requires-Dist: itsdangerous; extra == "all"
|
| 92 |
+
Requires-Dist: jedi; extra == "all"
|
| 93 |
+
Requires-Dist: Jinja2; extra == "all"
|
| 94 |
+
Requires-Dist: pytest>=8.4.2; extra == "all"
|
| 95 |
+
Requires-Dist: pytest-cov; extra == "all"
|
| 96 |
+
Requires-Dist: pytest-env; extra == "all"
|
| 97 |
+
Requires-Dist: pytest-xdist; extra == "all"
|
| 98 |
+
Requires-Dist: pytest-vcr; extra == "all"
|
| 99 |
+
Requires-Dist: pytest-asyncio; extra == "all"
|
| 100 |
+
Requires-Dist: pytest-rerunfailures>=16.2; extra == "all"
|
| 101 |
+
Requires-Dist: pytest-mock; extra == "all"
|
| 102 |
+
Requires-Dist: urllib3<2.0; extra == "all"
|
| 103 |
+
Requires-Dist: soundfile; extra == "all"
|
| 104 |
+
Requires-Dist: Pillow; extra == "all"
|
| 105 |
+
Requires-Dist: numpy; extra == "all"
|
| 106 |
+
Requires-Dist: duckdb; extra == "all"
|
| 107 |
+
Requires-Dist: fastapi; extra == "all"
|
| 108 |
+
Requires-Dist: ruff>=0.9.0; extra == "all"
|
| 109 |
+
Requires-Dist: mypy==1.15.0; extra == "all"
|
| 110 |
+
Requires-Dist: libcst>=1.4.0; extra == "all"
|
| 111 |
+
Requires-Dist: ty; extra == "all"
|
| 112 |
+
Requires-Dist: typing-extensions>=4.8.0; extra == "all"
|
| 113 |
+
Requires-Dist: types-PyYAML; extra == "all"
|
| 114 |
+
Requires-Dist: types-simplejson; extra == "all"
|
| 115 |
+
Requires-Dist: types-toml; extra == "all"
|
| 116 |
+
Requires-Dist: types-tqdm; extra == "all"
|
| 117 |
+
Requires-Dist: types-urllib3; extra == "all"
|
| 118 |
+
Provides-Extra: dev
|
| 119 |
+
Requires-Dist: authlib>=1.3.2; extra == "dev"
|
| 120 |
+
Requires-Dist: fastapi; extra == "dev"
|
| 121 |
+
Requires-Dist: httpx; extra == "dev"
|
| 122 |
+
Requires-Dist: itsdangerous; extra == "dev"
|
| 123 |
+
Requires-Dist: jedi; extra == "dev"
|
| 124 |
+
Requires-Dist: Jinja2; extra == "dev"
|
| 125 |
+
Requires-Dist: pytest>=8.4.2; extra == "dev"
|
| 126 |
+
Requires-Dist: pytest-cov; extra == "dev"
|
| 127 |
+
Requires-Dist: pytest-env; extra == "dev"
|
| 128 |
+
Requires-Dist: pytest-xdist; extra == "dev"
|
| 129 |
+
Requires-Dist: pytest-vcr; extra == "dev"
|
| 130 |
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
| 131 |
+
Requires-Dist: pytest-rerunfailures>=16.2; extra == "dev"
|
| 132 |
+
Requires-Dist: pytest-mock; extra == "dev"
|
| 133 |
+
Requires-Dist: urllib3<2.0; extra == "dev"
|
| 134 |
+
Requires-Dist: soundfile; extra == "dev"
|
| 135 |
+
Requires-Dist: Pillow; extra == "dev"
|
| 136 |
+
Requires-Dist: numpy; extra == "dev"
|
| 137 |
+
Requires-Dist: duckdb; extra == "dev"
|
| 138 |
+
Requires-Dist: fastapi; extra == "dev"
|
| 139 |
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
| 140 |
+
Requires-Dist: mypy==1.15.0; extra == "dev"
|
| 141 |
+
Requires-Dist: libcst>=1.4.0; extra == "dev"
|
| 142 |
+
Requires-Dist: ty; extra == "dev"
|
| 143 |
+
Requires-Dist: typing-extensions>=4.8.0; extra == "dev"
|
| 144 |
+
Requires-Dist: types-PyYAML; extra == "dev"
|
| 145 |
+
Requires-Dist: types-simplejson; extra == "dev"
|
| 146 |
+
Requires-Dist: types-toml; extra == "dev"
|
| 147 |
+
Requires-Dist: types-tqdm; extra == "dev"
|
| 148 |
+
Requires-Dist: types-urllib3; extra == "dev"
|
| 149 |
+
Dynamic: author
|
| 150 |
+
Dynamic: author-email
|
| 151 |
+
Dynamic: classifier
|
| 152 |
+
Dynamic: description
|
| 153 |
+
Dynamic: description-content-type
|
| 154 |
+
Dynamic: home-page
|
| 155 |
+
Dynamic: keywords
|
| 156 |
+
Dynamic: license
|
| 157 |
+
Dynamic: license-file
|
| 158 |
+
Dynamic: provides-extra
|
| 159 |
+
Dynamic: requires-dist
|
| 160 |
+
Dynamic: requires-python
|
| 161 |
+
Dynamic: summary
|
| 162 |
+
|
| 163 |
+
<p align="center">
|
| 164 |
+
<picture>
|
| 165 |
+
<source media="(prefers-color-scheme: dark)" srcset="https://huggingface.co/datasets/huggingface/documentation-images/raw/main/huggingface_hub-dark.svg">
|
| 166 |
+
<source media="(prefers-color-scheme: light)" srcset="https://huggingface.co/datasets/huggingface/documentation-images/raw/main/huggingface_hub.svg">
|
| 167 |
+
<img alt="huggingface_hub library logo" src="https://huggingface.co/datasets/huggingface/documentation-images/raw/main/huggingface_hub.svg" width="352" height="59" style="max-width: 100%">
|
| 168 |
+
</picture>
|
| 169 |
+
<br/>
|
| 170 |
+
<br/>
|
| 171 |
+
</p>
|
| 172 |
+
|
| 173 |
+
<p align="center">
|
| 174 |
+
<i>The official CLI and Python client for the Hugging Face Hub.</i>
|
| 175 |
+
<br/>
|
| 176 |
+
<a href="#what-is-huggingface_hub">About</a>
|
| 177 |
+
·
|
| 178 |
+
<a href="https://huggingface.co/docs/huggingface_hub">Documentation</a>
|
| 179 |
+
·
|
| 180 |
+
<a href="https://huggingface.co/docs/huggingface_hub/en/installation">Install</a>
|
| 181 |
+
·
|
| 182 |
+
<a href="https://huggingface.co/docs/huggingface_hub/en/guides/cli">CLI Guide</a>
|
| 183 |
+
·
|
| 184 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md">Contributing</a>
|
| 185 |
+
</p>
|
| 186 |
+
|
| 187 |
+
<p align="center">
|
| 188 |
+
<a href="https://huggingface.co/docs/huggingface_hub/en/index"><img alt="Documentation" src="https://img.shields.io/website/http/huggingface.co/docs/huggingface_hub/index.svg?down_color=red&down_message=offline&up_message=online&label=doc"></a>
|
| 189 |
+
<a href="https://github.com/huggingface/huggingface_hub/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/huggingface/huggingface_hub.svg"></a>
|
| 190 |
+
<a href="https://github.com/huggingface/huggingface_hub"><img alt="PyPi version" src="https://img.shields.io/pypi/pyversions/huggingface_hub.svg"></a>
|
| 191 |
+
<a href="https://pypi.org/project/huggingface-hub"><img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/huggingface_hub"></a>
|
| 192 |
+
<a href="https://codecov.io/gh/huggingface/huggingface_hub"><img alt="Code coverage" src="https://codecov.io/gh/huggingface/huggingface_hub/branch/main/graph/badge.svg?token=RXP95LE2XL"></a>
|
| 193 |
+
</p>
|
| 194 |
+
|
| 195 |
+
<h4 align="center">
|
| 196 |
+
<p>
|
| 197 |
+
<b>English</b> |
|
| 198 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_de.md">Deutsch</a> |
|
| 199 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_fr.md">Français</a> |
|
| 200 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_hi.md">हिंदी</a> |
|
| 201 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_ko.md">한국어</a> |
|
| 202 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_cn.md">中文 (简体)</a> |
|
| 203 |
+
<a href="https://github.com/huggingface/huggingface_hub/blob/main/i18n/README_kn.md">ಕನ್ನಡ</a>
|
| 204 |
+
</p>
|
| 205 |
+
</h4>
|
| 206 |
+
|
| 207 |
+
## Quick start
|
| 208 |
+
|
| 209 |
+
Install the [`hf` CLI](https://huggingface.co/docs/huggingface_hub/en/guides/cli) with the standalone installer:
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
# On macOS and Linux.
|
| 213 |
+
curl -LsSf https://hf.co/cli/install.sh | bash
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
```powershell
|
| 217 |
+
# On Windows.
|
| 218 |
+
powershell -ExecutionPolicy ByPass -c "irm https://hf.co/cli/install.ps1 | iex"
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
Log in, then start working with the Hub:
|
| 222 |
+
|
| 223 |
+
```bash
|
| 224 |
+
# Log in (use --token $HF_TOKEN in non-interactive environments)
|
| 225 |
+
hf auth login
|
| 226 |
+
|
| 227 |
+
# Find models served by Inference Providers
|
| 228 |
+
hf models ls --warm
|
| 229 |
+
|
| 230 |
+
# Download a model
|
| 231 |
+
hf download Qwen/Qwen3-0.6B
|
| 232 |
+
|
| 233 |
+
# Upload files to your own repo
|
| 234 |
+
hf upload username/my-cool-model ./model.safetensors
|
| 235 |
+
|
| 236 |
+
# Sync a local folder to a storage bucket
|
| 237 |
+
hf buckets sync ./checkpoints hf://buckets/username/my-bucket
|
| 238 |
+
|
| 239 |
+
# Run a job on Hugging Face infrastructure
|
| 240 |
+
hf jobs run python:3.12 python -c "print('Hello from the cloud!')"
|
| 241 |
+
|
| 242 |
+
# Discover everything else
|
| 243 |
+
hf --help
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
The Hub uses tokens to authenticate applications (see [docs](https://huggingface.co/docs/hub/security-tokens)). Check out the [CLI guide](https://huggingface.co/docs/huggingface_hub/en/guides/cli) for a tour of the main features.
|
| 247 |
+
|
| 248 |
+
## What is `huggingface_hub`?
|
| 249 |
+
|
| 250 |
+
The `huggingface_hub` library allows you to interact with the [Hugging Face Hub](https://huggingface.co/), a platform democratizing open-source Machine Learning for creators and collaborators. Discover pre-trained models and datasets for your projects, play with the thousands of machine learning apps hosted on the Hub, or create and share your own models, datasets and demos with the community. Everything ships in one package with two interfaces: the [`hf` CLI](https://huggingface.co/docs/huggingface_hub/en/guides/cli) for your terminal and the `huggingface_hub` library for Python — both designed to work well for humans and AI agents. Use them to:
|
| 251 |
+
|
| 252 |
+
- [Download files](https://huggingface.co/docs/huggingface_hub/en/guides/download) from the Hub.
|
| 253 |
+
- [Upload files](https://huggingface.co/docs/huggingface_hub/en/guides/upload) to the Hub.
|
| 254 |
+
- [Manage your repositories](https://huggingface.co/docs/huggingface_hub/en/guides/repository).
|
| 255 |
+
- [Run Inference](https://huggingface.co/docs/huggingface_hub/en/guides/inference) on deployed models.
|
| 256 |
+
- [Run Jobs](https://huggingface.co/docs/huggingface_hub/en/guides/jobs) on Hugging Face infrastructure.
|
| 257 |
+
- [Search](https://huggingface.co/docs/huggingface_hub/en/guides/search) for models, datasets and Spaces.
|
| 258 |
+
- [Share Model Cards](https://huggingface.co/docs/huggingface_hub/en/guides/model-cards) to document your models.
|
| 259 |
+
- [Engage with the community](https://huggingface.co/docs/huggingface_hub/en/guides/community) through PRs and comments.
|
| 260 |
+
- Do all of the above from the terminal with the [`hf` CLI](https://huggingface.co/docs/huggingface_hub/en/guides/cli).
|
| 261 |
+
|
| 262 |
+
## Built for humans and AI agents
|
| 263 |
+
|
| 264 |
+
The `hf` CLI is designed for people and coding agents alike: the same commands adapt their output when run by an agent. If you use Claude Code, Codex, Cursor, or another coding agent, install the `hf` CLI Skill — a command reference generated from your installed CLI:
|
| 265 |
+
|
| 266 |
+
```bash
|
| 267 |
+
# for Codex, Cursor, OpenCode, Pi and other agents that load skills from `.agents/skills`
|
| 268 |
+
hf skills add
|
| 269 |
+
# includes the above + Claude Code
|
| 270 |
+
hf skills add --claude
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
Learn more in the [Hugging Face CLI for AI agents guide](https://huggingface.co/docs/hub/agents-cli) and the [announcement blog post](https://huggingface.co/blog/hf-cli-for-agents).
|
| 274 |
+
|
| 275 |
+
## Use the Python library
|
| 276 |
+
|
| 277 |
+
Install the `huggingface_hub` package with [pip](https://pypi.org/project/huggingface-hub/) (this also installs the `hf` CLI):
|
| 278 |
+
|
| 279 |
+
```bash
|
| 280 |
+
pip install huggingface_hub
|
| 281 |
+
```
|
| 282 |
+
|
| 283 |
+
We recommend using [`uv`](https://docs.astral.sh/uv/) for a fast and reliable install:
|
| 284 |
+
|
| 285 |
+
```bash
|
| 286 |
+
uv pip install huggingface_hub
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
In order to keep the package minimal by default, `huggingface_hub` comes with optional dependencies useful for some use cases. For example, if you want to use the MCP module, run:
|
| 290 |
+
|
| 291 |
+
```bash
|
| 292 |
+
pip install "huggingface_hub[mcp]"
|
| 293 |
+
```
|
| 294 |
+
|
| 295 |
+
To learn more about installation and optional dependencies, check out the [installation guide](https://huggingface.co/docs/huggingface_hub/en/installation).
|
| 296 |
+
|
| 297 |
+
### Download files
|
| 298 |
+
|
| 299 |
+
Download a single file
|
| 300 |
+
|
| 301 |
+
```py
|
| 302 |
+
from huggingface_hub import hf_hub_download
|
| 303 |
+
|
| 304 |
+
hf_hub_download(repo_id="zai-org/GLM-5.2", filename="config.json")
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
Or an entire repository
|
| 308 |
+
|
| 309 |
+
```py
|
| 310 |
+
from huggingface_hub import snapshot_download
|
| 311 |
+
|
| 312 |
+
snapshot_download("sentence-transformers/all-MiniLM-L6-v2")
|
| 313 |
+
```
|
| 314 |
+
|
| 315 |
+
Files will be downloaded in a local cache folder. More details in [this guide](https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache).
|
| 316 |
+
|
| 317 |
+
### Create a repository
|
| 318 |
+
|
| 319 |
+
```py
|
| 320 |
+
from huggingface_hub import create_repo
|
| 321 |
+
|
| 322 |
+
create_repo(repo_id="super-cool-model")
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
### Upload files
|
| 326 |
+
|
| 327 |
+
Upload a single file
|
| 328 |
+
|
| 329 |
+
```py
|
| 330 |
+
from huggingface_hub import upload_file
|
| 331 |
+
|
| 332 |
+
upload_file(
|
| 333 |
+
path_or_fileobj="/home/lysandre/dummy-test/README.md",
|
| 334 |
+
path_in_repo="README.md",
|
| 335 |
+
repo_id="lysandre/test-model",
|
| 336 |
+
)
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
Or an entire folder
|
| 340 |
+
|
| 341 |
+
```py
|
| 342 |
+
from huggingface_hub import upload_folder
|
| 343 |
+
|
| 344 |
+
upload_folder(
|
| 345 |
+
folder_path="/path/to/local/space",
|
| 346 |
+
repo_id="username/my-cool-space",
|
| 347 |
+
repo_type="space",
|
| 348 |
+
)
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
More details in the [upload guide](https://huggingface.co/docs/huggingface_hub/en/guides/upload).
|
| 352 |
+
|
| 353 |
+
## Integrating with the Hub.
|
| 354 |
+
|
| 355 |
+
We're partnering with cool open source ML libraries to provide free model hosting and versioning. You can find the existing integrations [here](https://huggingface.co/docs/hub/libraries).
|
| 356 |
+
|
| 357 |
+
The advantages are:
|
| 358 |
+
|
| 359 |
+
- Free model or dataset hosting for libraries and their users.
|
| 360 |
+
- Built-in file versioning, even with very large files, made possible by [Xet](https://huggingface.co/docs/hub/xet/index), the Hub's chunk-deduplicated storage backend.
|
| 361 |
+
- In-browser widgets to play with the uploaded models.
|
| 362 |
+
- Anyone can upload a new model for your library, they just need to add the corresponding tag for the model to be discoverable.
|
| 363 |
+
- Fast downloads! We use Cloudfront (a CDN) to geo-replicate downloads so they're blazing fast from anywhere on the globe.
|
| 364 |
+
- Usage stats and more features to come.
|
| 365 |
+
|
| 366 |
+
If you would like to integrate your library, feel free to open an issue to begin the discussion. We wrote a [step-by-step guide](https://huggingface.co/docs/hub/adding-a-library) with ❤️ showing how to do this integration.
|
| 367 |
+
|
| 368 |
+
## Contributions (feature requests, bugs, etc.) are super welcome 💙💚💛💜🧡❤️
|
| 369 |
+
|
| 370 |
+
Everyone is welcome to contribute, and we value everybody's contribution. Code is not the only way to help the community.
|
| 371 |
+
Answering questions, helping others, reaching out and improving the documentations are immensely valuable to the community.
|
| 372 |
+
We wrote a [contribution guide](https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md) to summarize
|
| 373 |
+
how to get started to contribute to this repository.
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
../../../bin/hf,sha256=JGwY5R4KD0jI6vjiDs3QN1gW7f2Gg1sICqZX3ZsJEDA,229
|
| 2 |
+
../../../bin/huggingface-cli,sha256=NBXfi2Mm2Aix-bhlYkTCTScQjEeDhMH9aLNe-ckOdhY,241
|
| 3 |
+
../../../bin/tiny-agents,sha256=enIFQEFKcRStd8yjZJDeXQhC77nfBUd15u7g4FCamxg,239
|
| 4 |
+
huggingface_hub-1.26.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 5 |
+
huggingface_hub-1.26.0.dist-info/METADATA,sha256=KcwO-q5qwA5Ebh_FICnvHDP8U6mPa8rdlnih7rnssYc,16314
|
| 6 |
+
huggingface_hub-1.26.0.dist-info/RECORD,,
|
| 7 |
+
huggingface_hub-1.26.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 8 |
+
huggingface_hub-1.26.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
| 9 |
+
huggingface_hub-1.26.0.dist-info/entry_points.txt,sha256=zP7F_bBSdircPQFysHQZ9F3Lcn5_dCSOEZxVlGCsG0w,212
|
| 10 |
+
huggingface_hub-1.26.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
| 11 |
+
huggingface_hub-1.26.0.dist-info/top_level.txt,sha256=8KzlQJAY4miUvjAssOAJodqKOw3harNzuiwGQ9qLSSk,16
|
| 12 |
+
huggingface_hub/__init__.py,sha256=MHnwtoK4eZWretvLQzTgRce55TAwjlkZld6FvUq1TLY,60623
|
| 13 |
+
huggingface_hub/__pycache__/__init__.cpython-314.pyc,,
|
| 14 |
+
huggingface_hub/__pycache__/_buckets.cpython-314.pyc,,
|
| 15 |
+
huggingface_hub/__pycache__/_commit_api.cpython-314.pyc,,
|
| 16 |
+
huggingface_hub/__pycache__/_commit_scheduler.cpython-314.pyc,,
|
| 17 |
+
huggingface_hub/__pycache__/_dataset_viewer.cpython-314.pyc,,
|
| 18 |
+
huggingface_hub/__pycache__/_eval_results.cpython-314.pyc,,
|
| 19 |
+
huggingface_hub/__pycache__/_inference_endpoints.cpython-314.pyc,,
|
| 20 |
+
huggingface_hub/__pycache__/_jobs_api.cpython-314.pyc,,
|
| 21 |
+
huggingface_hub/__pycache__/_local_folder.cpython-314.pyc,,
|
| 22 |
+
huggingface_hub/__pycache__/_login.cpython-314.pyc,,
|
| 23 |
+
huggingface_hub/__pycache__/_oauth.cpython-314.pyc,,
|
| 24 |
+
huggingface_hub/__pycache__/_oidc.cpython-314.pyc,,
|
| 25 |
+
huggingface_hub/__pycache__/_revision.cpython-314.pyc,,
|
| 26 |
+
huggingface_hub/__pycache__/_sandbox.cpython-314.pyc,,
|
| 27 |
+
huggingface_hub/__pycache__/_sandbox_cache.cpython-314.pyc,,
|
| 28 |
+
huggingface_hub/__pycache__/_snapshot_download.cpython-314.pyc,,
|
| 29 |
+
huggingface_hub/__pycache__/_space_api.cpython-314.pyc,,
|
| 30 |
+
huggingface_hub/__pycache__/_tensorboard_logger.cpython-314.pyc,,
|
| 31 |
+
huggingface_hub/__pycache__/_tree_cache.cpython-314.pyc,,
|
| 32 |
+
huggingface_hub/__pycache__/_upload_large_folder.cpython-314.pyc,,
|
| 33 |
+
huggingface_hub/__pycache__/_upload_pipeline.cpython-314.pyc,,
|
| 34 |
+
huggingface_hub/__pycache__/_webhooks_payload.cpython-314.pyc,,
|
| 35 |
+
huggingface_hub/__pycache__/_webhooks_server.cpython-314.pyc,,
|
| 36 |
+
huggingface_hub/__pycache__/community.cpython-314.pyc,,
|
| 37 |
+
huggingface_hub/__pycache__/constants.cpython-314.pyc,,
|
| 38 |
+
huggingface_hub/__pycache__/dataclasses.cpython-314.pyc,,
|
| 39 |
+
huggingface_hub/__pycache__/errors.cpython-314.pyc,,
|
| 40 |
+
huggingface_hub/__pycache__/fastai_utils.cpython-314.pyc,,
|
| 41 |
+
huggingface_hub/__pycache__/file_download.cpython-314.pyc,,
|
| 42 |
+
huggingface_hub/__pycache__/hf_api.cpython-314.pyc,,
|
| 43 |
+
huggingface_hub/__pycache__/hf_file_system.cpython-314.pyc,,
|
| 44 |
+
huggingface_hub/__pycache__/hub_mixin.cpython-314.pyc,,
|
| 45 |
+
huggingface_hub/__pycache__/lfs.cpython-314.pyc,,
|
| 46 |
+
huggingface_hub/__pycache__/repocard.cpython-314.pyc,,
|
| 47 |
+
huggingface_hub/__pycache__/repocard_data.cpython-314.pyc,,
|
| 48 |
+
huggingface_hub/_buckets.py,sha256=XyLbsLzS2fpQz0aAIjKzV-1YbQ4XYZ4-HXQTVq0TXoU,45311
|
| 49 |
+
huggingface_hub/_commit_api.py,sha256=lalqhJKl8_RWVHgAdnzZKG_fy4E7fbyHIev6nDLrj4w,45378
|
| 50 |
+
huggingface_hub/_commit_scheduler.py,sha256=VG3zjb7xb66NrFMq_pB9dWIsoqSPoF6CntovV7vUiWg,14684
|
| 51 |
+
huggingface_hub/_dataset_viewer.py,sha256=LE9k4s0WJ0JNdriJ8L6qV_-xfbUVC1VEh7CBd0jOjLI,5675
|
| 52 |
+
huggingface_hub/_eval_results.py,sha256=Og7p7bCl2HQG6ZWlSDyir-CRTXjNWPu3S3q65cTGffs,8126
|
| 53 |
+
huggingface_hub/_hot_reload/__init__.py,sha256=nQEuyVZLAS_463Dff1JH5MorTpkYsBVFCyPhYWY3UyI,606
|
| 54 |
+
huggingface_hub/_hot_reload/__pycache__/__init__.cpython-314.pyc,,
|
| 55 |
+
huggingface_hub/_hot_reload/__pycache__/client.cpython-314.pyc,,
|
| 56 |
+
huggingface_hub/_hot_reload/__pycache__/sse_client.cpython-314.pyc,,
|
| 57 |
+
huggingface_hub/_hot_reload/__pycache__/types.cpython-314.pyc,,
|
| 58 |
+
huggingface_hub/_hot_reload/client.py,sha256=Ax7qT4CUi5wrg3mDzqkAG8ZzDsCwdcIIS1FIXK0SqD8,4269
|
| 59 |
+
huggingface_hub/_hot_reload/sse_client.py,sha256=49kmsB2P9sxgbZ16ed6F4aLDs3rsHrNpbp6iKlvF90c,4948
|
| 60 |
+
huggingface_hub/_hot_reload/types.py,sha256=d3AOJCLUxT41Fhz4jUrFevm9bfpooe_3PQhpBdgNYSs,2725
|
| 61 |
+
huggingface_hub/_inference_endpoints.py,sha256=TNCEvHfu5M790bQPl4BccRzcii8WS5FlDYGrGqQzdW4,17724
|
| 62 |
+
huggingface_hub/_jobs_api.py,sha256=f7NhziUMH6jWpWClnLPFx4XIZ0xVGAdTGfBxIn2IVy0,23679
|
| 63 |
+
huggingface_hub/_local_folder.py,sha256=KsetQw7pW0ZwFI2RLQPqVq0ZnORogPkBwRYcbRqNd5o,20654
|
| 64 |
+
huggingface_hub/_login.py,sha256=wAMdhqMSSh3GBJDozodmk4IIsAvb9J6LaLY9KnXFoyY,21818
|
| 65 |
+
huggingface_hub/_oauth.py,sha256=S8evEd8JLJHz-JiSjGH4WiIi_A7oDox_xH7E9rN1Jic,18628
|
| 66 |
+
huggingface_hub/_oidc.py,sha256=3kcCLavWQ5FQOwizUPzLaBb1ft2FIJo0bSXL24S3r4U,7098
|
| 67 |
+
huggingface_hub/_revision.py,sha256=NCmIVCR-_HjmxeYiDntcsTU-bLSfJLq8PLvvG8UhZtI,1696
|
| 68 |
+
huggingface_hub/_sandbox.py,sha256=XEXZi5S3rYyw_o3td3rB0xOVdmhpAlnyUykBsAw9KP8,80148
|
| 69 |
+
huggingface_hub/_sandbox_cache.py,sha256=UYpvHOWZj7CRhacYs5Ot-5N1wFcAapiAyniYdoX-jX8,5742
|
| 70 |
+
huggingface_hub/_snapshot_download.py,sha256=fOte9xmpaok0LGbubJZhSRd7t0nn-p00mWMebCx56qc,31029
|
| 71 |
+
huggingface_hub/_space_api.py,sha256=MF3ezhgl0Vpt3RP1wuwZ7VI9_rSyIlIJ5SmDzIwQlcQ,13424
|
| 72 |
+
huggingface_hub/_tensorboard_logger.py,sha256=GA-LhoG4Z-0Zq7w5B9vTIDJbWUkAhiG2d8a-ktlwGDY,8377
|
| 73 |
+
huggingface_hub/_tree_cache.py,sha256=M7CHMKhwY1cuhe9fb3WT1X8jmd9BXa2Sx_HS4TIXFgI,5843
|
| 74 |
+
huggingface_hub/_upload_large_folder.py,sha256=BWA1zyOK9W_3YUerX9YWQa3wAYtHRe_ypgsO79JEkqo,31235
|
| 75 |
+
huggingface_hub/_upload_pipeline.py,sha256=6YaQNtubAEaPyRoRBjPQrmsdSnCExg2KzfBuhjNqI5I,28994
|
| 76 |
+
huggingface_hub/_webhooks_payload.py,sha256=B9zqljcm20DGeVxJKba570i6RP-cR9CEupMGBuNDHHA,3544
|
| 77 |
+
huggingface_hub/_webhooks_server.py,sha256=VXOk3FHkbIc28phxknJfaFy5B_EoKOKihoCKyOaWZQk,15767
|
| 78 |
+
huggingface_hub/cli/__init__.py,sha256=tWu3Is-m2tlJwXhM_vT2mPRgpnt9KhU9qvw5aRsnjU0,664
|
| 79 |
+
huggingface_hub/cli/__pycache__/__init__.cpython-314.pyc,,
|
| 80 |
+
huggingface_hub/cli/__pycache__/_city_game.cpython-314.pyc,,
|
| 81 |
+
huggingface_hub/cli/__pycache__/_cli_utils.cpython-314.pyc,,
|
| 82 |
+
huggingface_hub/cli/__pycache__/_completion.cpython-314.pyc,,
|
| 83 |
+
huggingface_hub/cli/__pycache__/_cp.cpython-314.pyc,,
|
| 84 |
+
huggingface_hub/cli/__pycache__/_errors.cpython-314.pyc,,
|
| 85 |
+
huggingface_hub/cli/__pycache__/_file_listing.cpython-314.pyc,,
|
| 86 |
+
huggingface_hub/cli/__pycache__/_framework.cpython-314.pyc,,
|
| 87 |
+
huggingface_hub/cli/__pycache__/_help_formatter.cpython-314.pyc,,
|
| 88 |
+
huggingface_hub/cli/__pycache__/_output.cpython-314.pyc,,
|
| 89 |
+
huggingface_hub/cli/__pycache__/_skills.cpython-314.pyc,,
|
| 90 |
+
huggingface_hub/cli/__pycache__/auth.cpython-314.pyc,,
|
| 91 |
+
huggingface_hub/cli/__pycache__/buckets.cpython-314.pyc,,
|
| 92 |
+
huggingface_hub/cli/__pycache__/cache.cpython-314.pyc,,
|
| 93 |
+
huggingface_hub/cli/__pycache__/collections.cpython-314.pyc,,
|
| 94 |
+
huggingface_hub/cli/__pycache__/datasets.cpython-314.pyc,,
|
| 95 |
+
huggingface_hub/cli/__pycache__/deprecated_cli.cpython-314.pyc,,
|
| 96 |
+
huggingface_hub/cli/__pycache__/discussions.cpython-314.pyc,,
|
| 97 |
+
huggingface_hub/cli/__pycache__/download.cpython-314.pyc,,
|
| 98 |
+
huggingface_hub/cli/__pycache__/extensions.cpython-314.pyc,,
|
| 99 |
+
huggingface_hub/cli/__pycache__/hf.cpython-314.pyc,,
|
| 100 |
+
huggingface_hub/cli/__pycache__/inference_endpoints.cpython-314.pyc,,
|
| 101 |
+
huggingface_hub/cli/__pycache__/jobs.cpython-314.pyc,,
|
| 102 |
+
huggingface_hub/cli/__pycache__/lfs.cpython-314.pyc,,
|
| 103 |
+
huggingface_hub/cli/__pycache__/models.cpython-314.pyc,,
|
| 104 |
+
huggingface_hub/cli/__pycache__/papers.cpython-314.pyc,,
|
| 105 |
+
huggingface_hub/cli/__pycache__/repo_files.cpython-314.pyc,,
|
| 106 |
+
huggingface_hub/cli/__pycache__/repos.cpython-314.pyc,,
|
| 107 |
+
huggingface_hub/cli/__pycache__/sandbox.cpython-314.pyc,,
|
| 108 |
+
huggingface_hub/cli/__pycache__/skills.cpython-314.pyc,,
|
| 109 |
+
huggingface_hub/cli/__pycache__/spaces.cpython-314.pyc,,
|
| 110 |
+
huggingface_hub/cli/__pycache__/system.cpython-314.pyc,,
|
| 111 |
+
huggingface_hub/cli/__pycache__/upload.cpython-314.pyc,,
|
| 112 |
+
huggingface_hub/cli/__pycache__/upload_large_folder.cpython-314.pyc,,
|
| 113 |
+
huggingface_hub/cli/__pycache__/webhooks.cpython-314.pyc,,
|
| 114 |
+
huggingface_hub/cli/_city_game.py,sha256=2qkC9dCtPwf4-aqrnbN-vApf0YqIcGGjn2v-etrTfqg,20585
|
| 115 |
+
huggingface_hub/cli/_cli_utils.py,sha256=Q5795puu89Kf4fEvQYPjyJmDnyqFEyEr5SW1xQXQk8I,41313
|
| 116 |
+
huggingface_hub/cli/_completion.py,sha256=BDP6Vob9TLFp6K3lr0XOY98FyD-rm22KrjwsO_YGNbE,3759
|
| 117 |
+
huggingface_hub/cli/_cp.py,sha256=Z-2Z_tBs1e4XBqRO5R9QnabU4qTi9HDlbA52jTBaCkY,10436
|
| 118 |
+
huggingface_hub/cli/_errors.py,sha256=X0rUTO-3eCK5v_MLGEza1BxcuXGccPY-h7ShzmuQc34,5907
|
| 119 |
+
huggingface_hub/cli/_file_listing.py,sha256=h_dTEphSqtvknzqJ_9cIK3Cjlz5M6cycYD3cqUW9Fn0,7986
|
| 120 |
+
huggingface_hub/cli/_framework.py,sha256=HqfAEYGWRSjKABAZi2zVNgryyU9QKW8u-tRXfR_4nQ8,27364
|
| 121 |
+
huggingface_hub/cli/_help_formatter.py,sha256=-vsa3HfHQFkKWms77EGyp9i22t44ETmVt2L-e8qP-KM,1288
|
| 122 |
+
huggingface_hub/cli/_output.py,sha256=OxFrvAcDU1TRDV0AbNh4EI1P6tqifSeVT-yqfF92U_0,14754
|
| 123 |
+
huggingface_hub/cli/_skills.py,sha256=b1dlasxALM5KsDG5R1azcETksQ6ssYYZN-kaz-RdHwg,12134
|
| 124 |
+
huggingface_hub/cli/auth.py,sha256=Ew79M60pTMCnS1ewKVK3fd8ZyfHrNjREOmNWuFXy37M,6056
|
| 125 |
+
huggingface_hub/cli/buckets.py,sha256=J8xMP88fQ6ri3f65Z8XEImI2GNA4KKWloLJJQCBezZw,21062
|
| 126 |
+
huggingface_hub/cli/cache.py,sha256=2T0oTYXsrtcJxhQENSpXnYYNUkagsvvdrf5gbCACBjA,28574
|
| 127 |
+
huggingface_hub/cli/collections.py,sha256=Ed41zJqrvK-AWkHdkeHWm_aJKR_ZeNIovCGvWG04itk,10120
|
| 128 |
+
huggingface_hub/cli/datasets.py,sha256=X1xMJTFhHH5NJtmjTS6NN1g4ffai6Abh_obDEJM5mvo,10497
|
| 129 |
+
huggingface_hub/cli/deprecated_cli.py,sha256=vAK_CR4NE9uzNJ0I59SyBnIln5nlypG4CPoohMoPMVg,1090
|
| 130 |
+
huggingface_hub/cli/discussions.py,sha256=obvvN4mtsFnfMs67YTZaMvHkVaM6A-FYr5n-wGkF_jE,14029
|
| 131 |
+
huggingface_hub/cli/download.py,sha256=o6fMKJoNQMxCm1RQUnX3blihJ17mantxkf0F68rZIxs,9669
|
| 132 |
+
huggingface_hub/cli/extensions.py,sha256=qNM1DmzcQXt8kPWQfISc8Pvyku8BxAs-BOVP6sKRBOM,25572
|
| 133 |
+
huggingface_hub/cli/hf.py,sha256=G2R-Mgo2AX09aRiAb-13eZbO441m9yDTwMCtSUf1iac,5208
|
| 134 |
+
huggingface_hub/cli/inference_endpoints.py,sha256=4pfvPm5PVQizStyXGRBnrev62KvhzSNEL65UEVtRcKw,17418
|
| 135 |
+
huggingface_hub/cli/jobs.py,sha256=EVGuxn2GZqkQolHib98PztJeJ64Opq-MjgIub2WTYb4,52840
|
| 136 |
+
huggingface_hub/cli/lfs.py,sha256=MAccazVPX0n-ySRaPocaQUHP7oBnVrvWZt0RhmNIavs,5936
|
| 137 |
+
huggingface_hub/cli/models.py,sha256=eG7H5yDjCVrdL7W_-wpcl70qXlv8bC-roSEu8AroPws,10148
|
| 138 |
+
huggingface_hub/cli/papers.py,sha256=HvT-8zj2fi14ua6awWpHphlbJ362FEJIwSCBJRQZ_cM,4982
|
| 139 |
+
huggingface_hub/cli/repo_files.py,sha256=sGbD5wND3miF71ThfOBJm4xzPRUpJBeLFdvyj229NAM,2443
|
| 140 |
+
huggingface_hub/cli/repos.py,sha256=QS3xYKvspLhCfO5VvpORrSiF6XhSVFclmBxsacVDP2c,20192
|
| 141 |
+
huggingface_hub/cli/sandbox.py,sha256=rTCZf6WhoDa7UiNhsTU_4Lh5uKqKYOs73xXGAAqJGdA,18379
|
| 142 |
+
huggingface_hub/cli/skills.py,sha256=JQkqD43bfr8IapIVp8rvQpd8NKA0dXSbGJD8g-u51w8,19473
|
| 143 |
+
huggingface_hub/cli/spaces.py,sha256=AU655GGrQmF1s1yO8yO6ml2Z8veE7zsh4UMuR5b13RI,40800
|
| 144 |
+
huggingface_hub/cli/system.py,sha256=NtICLnQ49BHFpm2Cl37IV_h21L6mufxVDfse8Lp38HA,1760
|
| 145 |
+
huggingface_hub/cli/upload.py,sha256=P6sq8Dj81a2wazUgZEqbliRleVAAmJrf8ajFDDzHrBE,11318
|
| 146 |
+
huggingface_hub/cli/upload_large_folder.py,sha256=A-2qxAdLrYd8IogfZS2nDhSR1cpA_aaxhXst638WkAk,4081
|
| 147 |
+
huggingface_hub/cli/webhooks.py,sha256=HdpPp01c6Ece_EuNNJr46rnRki3vF4j4o9-SqYRUHnM,8550
|
| 148 |
+
huggingface_hub/community.py,sha256=N5OE5Rbc2BMwpNXCFOPQAwn0otqQunsXdEnSE_0J2Xo,12325
|
| 149 |
+
huggingface_hub/constants.py,sha256=y4TqYE5UuXFrc-ivzpLiQKoKKw6xHY75SBEIgOuuD8g,13174
|
| 150 |
+
huggingface_hub/dataclasses.py,sha256=dkPHOKq1YVlto9TWMkeX8BnErmbZk6tVtqD1S_n6Cw8,25896
|
| 151 |
+
huggingface_hub/errors.py,sha256=MsfOFq6Sxo2bZ8PTPWbQb250v7aqZJO5-mmWSnX_kDk,18695
|
| 152 |
+
huggingface_hub/fastai_utils.py,sha256=jwKeVXH_VP0zxnOKVNQCVCkF-stH70l-bExbbrYkBKQ,16583
|
| 153 |
+
huggingface_hub/file_download.py,sha256=QT4ICMPp_CbLBVNn-qCYnyuwmA2mjjc546fYppt1m8Y,87740
|
| 154 |
+
huggingface_hub/hf_api.py,sha256=ByjtP6rHRHBeQIgYKc9AjysjA219t8O14tuXfVs-6YQ,672231
|
| 155 |
+
huggingface_hub/hf_file_system.py,sha256=39YZea452YnGVvHZosmL3_QtWaz7VS-4P2Mc-xI7K0E,60503
|
| 156 |
+
huggingface_hub/hub_mixin.py,sha256=ljKAtm3qQsY-ARtxY-PCFx0koFXLI37JytMrzpqfnQo,36912
|
| 157 |
+
huggingface_hub/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 158 |
+
huggingface_hub/inference/__pycache__/__init__.cpython-314.pyc,,
|
| 159 |
+
huggingface_hub/inference/__pycache__/_client.cpython-314.pyc,,
|
| 160 |
+
huggingface_hub/inference/__pycache__/_common.cpython-314.pyc,,
|
| 161 |
+
huggingface_hub/inference/_client.py,sha256=bN5cj4tBHPD6v603uq4CJjMB_mmmpdVcfXtl9XIOVqc,157679
|
| 162 |
+
huggingface_hub/inference/_common.py,sha256=X-fBP8s6xuNTAuQsGVIethE8dNQaa-mK7Ds54qVVDz8,14999
|
| 163 |
+
huggingface_hub/inference/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 164 |
+
huggingface_hub/inference/_generated/__pycache__/__init__.cpython-314.pyc,,
|
| 165 |
+
huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-314.pyc,,
|
| 166 |
+
huggingface_hub/inference/_generated/_async_client.py,sha256=imtfJD9m3qxeo_59gox3nnV8Tr8dfQF3dQF0Joy5K4g,160890
|
| 167 |
+
huggingface_hub/inference/_generated/types/__init__.py,sha256=e625ENOmTUM4uUL1eAlsZOK7CovNoGBDH-TxSNQhaC4,6869
|
| 168 |
+
huggingface_hub/inference/_generated/types/__pycache__/__init__.cpython-314.pyc,,
|
| 169 |
+
huggingface_hub/inference/_generated/types/__pycache__/audio_classification.cpython-314.pyc,,
|
| 170 |
+
huggingface_hub/inference/_generated/types/__pycache__/audio_to_audio.cpython-314.pyc,,
|
| 171 |
+
huggingface_hub/inference/_generated/types/__pycache__/automatic_speech_recognition.cpython-314.pyc,,
|
| 172 |
+
huggingface_hub/inference/_generated/types/__pycache__/base.cpython-314.pyc,,
|
| 173 |
+
huggingface_hub/inference/_generated/types/__pycache__/chat_completion.cpython-314.pyc,,
|
| 174 |
+
huggingface_hub/inference/_generated/types/__pycache__/depth_estimation.cpython-314.pyc,,
|
| 175 |
+
huggingface_hub/inference/_generated/types/__pycache__/document_question_answering.cpython-314.pyc,,
|
| 176 |
+
huggingface_hub/inference/_generated/types/__pycache__/feature_extraction.cpython-314.pyc,,
|
| 177 |
+
huggingface_hub/inference/_generated/types/__pycache__/fill_mask.cpython-314.pyc,,
|
| 178 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_classification.cpython-314.pyc,,
|
| 179 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_segmentation.cpython-314.pyc,,
|
| 180 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_text_to_image.cpython-314.pyc,,
|
| 181 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_text_to_video.cpython-314.pyc,,
|
| 182 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_to_image.cpython-314.pyc,,
|
| 183 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_to_text.cpython-314.pyc,,
|
| 184 |
+
huggingface_hub/inference/_generated/types/__pycache__/image_to_video.cpython-314.pyc,,
|
| 185 |
+
huggingface_hub/inference/_generated/types/__pycache__/object_detection.cpython-314.pyc,,
|
| 186 |
+
huggingface_hub/inference/_generated/types/__pycache__/question_answering.cpython-314.pyc,,
|
| 187 |
+
huggingface_hub/inference/_generated/types/__pycache__/sentence_similarity.cpython-314.pyc,,
|
| 188 |
+
huggingface_hub/inference/_generated/types/__pycache__/summarization.cpython-314.pyc,,
|
| 189 |
+
huggingface_hub/inference/_generated/types/__pycache__/table_question_answering.cpython-314.pyc,,
|
| 190 |
+
huggingface_hub/inference/_generated/types/__pycache__/text2text_generation.cpython-314.pyc,,
|
| 191 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_classification.cpython-314.pyc,,
|
| 192 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_generation.cpython-314.pyc,,
|
| 193 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_to_audio.cpython-314.pyc,,
|
| 194 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_to_image.cpython-314.pyc,,
|
| 195 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_to_speech.cpython-314.pyc,,
|
| 196 |
+
huggingface_hub/inference/_generated/types/__pycache__/text_to_video.cpython-314.pyc,,
|
| 197 |
+
huggingface_hub/inference/_generated/types/__pycache__/token_classification.cpython-314.pyc,,
|
| 198 |
+
huggingface_hub/inference/_generated/types/__pycache__/translation.cpython-314.pyc,,
|
| 199 |
+
huggingface_hub/inference/_generated/types/__pycache__/video_classification.cpython-314.pyc,,
|
| 200 |
+
huggingface_hub/inference/_generated/types/__pycache__/visual_question_answering.cpython-314.pyc,,
|
| 201 |
+
huggingface_hub/inference/_generated/types/__pycache__/zero_shot_classification.cpython-314.pyc,,
|
| 202 |
+
huggingface_hub/inference/_generated/types/__pycache__/zero_shot_image_classification.cpython-314.pyc,,
|
| 203 |
+
huggingface_hub/inference/_generated/types/__pycache__/zero_shot_object_detection.cpython-314.pyc,,
|
| 204 |
+
huggingface_hub/inference/_generated/types/audio_classification.py,sha256=DGWEvrRPX7xpXlbYY4LMojGcULdEZFTshhNR81rTNcQ,1567
|
| 205 |
+
huggingface_hub/inference/_generated/types/audio_to_audio.py,sha256=2Ep4WkePL7oJwcp5nRJqApwviumGHbft9HhXE9XLHj4,891
|
| 206 |
+
huggingface_hub/inference/_generated/types/automatic_speech_recognition.py,sha256=X0uCj9qwAcOmlKtV3vWH5cAgLGY0PuYIy2eSH7GE1qs,5439
|
| 207 |
+
huggingface_hub/inference/_generated/types/base.py,sha256=EPWp1qudjeEimEAyQ-ptdxpAACoRM8tW342-Ds7pP3I,6938
|
| 208 |
+
huggingface_hub/inference/_generated/types/chat_completion.py,sha256=3yc06ZI0NCZzsaeoxSMWyIHqGc8Vn_6aExncuXa1llE,11100
|
| 209 |
+
huggingface_hub/inference/_generated/types/depth_estimation.py,sha256=5Kn4xNGJg235Cofr1lzO59eRGCuLTZun8z5t6a2do8Q,910
|
| 210 |
+
huggingface_hub/inference/_generated/types/document_question_answering.py,sha256=aUZcrDGexjw8Fnm2rACm_3my77V15NnQHZxj-sa7bjo,3146
|
| 211 |
+
huggingface_hub/inference/_generated/types/feature_extraction.py,sha256=DiO47PqwhdNta_KPDBsXfCPIL6BWNhyCLymxORlqUA0,1509
|
| 212 |
+
huggingface_hub/inference/_generated/types/fill_mask.py,sha256=NhEskgoiMst7U_0cV_S-wryga4_2T5LgpbyQ23vHMp0,1680
|
| 213 |
+
huggingface_hub/inference/_generated/types/image_classification.py,sha256=xWr4OY65EcIIhd7MI610a7xlaAVHQeydi8Gc7tlaPTU,1579
|
| 214 |
+
huggingface_hub/inference/_generated/types/image_segmentation.py,sha256=lZKzbn_gkSi1sYGLb9ryeRr0aDAVrfYh0H5Iiv5JNsY,1935
|
| 215 |
+
huggingface_hub/inference/_generated/types/image_text_to_image.py,sha256=pRQgYKXJbIpEjRBxuIE6NRDtk_km9DSTQ2s2qxkYJRM,2585
|
| 216 |
+
huggingface_hub/inference/_generated/types/image_text_to_video.py,sha256=MVhP8D4eYjpT3hWYkkpuiME-hWY09o7C4rIn_wmnAmY,2450
|
| 217 |
+
huggingface_hub/inference/_generated/types/image_to_image.py,sha256=P9TeMFSnZI_pa6DyinzDPWL0PNm_INJwsqFUEzpwIcU,2262
|
| 218 |
+
huggingface_hub/inference/_generated/types/image_to_text.py,sha256=DCDS0Nqi-4AuATitvZm4EgrzYQuQEQ5lzBcaHuCmHnY,4740
|
| 219 |
+
huggingface_hub/inference/_generated/types/image_to_video.py,sha256=iMCjiFTUza9AW_MyJ1videhYgvMY9QSzQ59kNdTE9QY,2206
|
| 220 |
+
huggingface_hub/inference/_generated/types/object_detection.py,sha256=zNILlyZJh59rNNfqXM5x9uprDCPfEU-bvBtabeKSMzI,1965
|
| 221 |
+
huggingface_hub/inference/_generated/types/question_answering.py,sha256=lIRf8DjRiAsjzWUBEsGt354SB4bvejLokf-8luHw6_o,2845
|
| 222 |
+
huggingface_hub/inference/_generated/types/sentence_similarity.py,sha256=eNGiO4dR-Lft9yOLE0E97jfVyOT_ExDWZW5GeJGdatU,1027
|
| 223 |
+
huggingface_hub/inference/_generated/types/summarization.py,sha256=5Dpt6LRteT-hO9esa3Ty_-xSL5U-zTtVZzy3iKwZRng,1472
|
| 224 |
+
huggingface_hub/inference/_generated/types/table_question_answering.py,sha256=bwn616XJ-eLa5JKzn3m9rZZ3z63INGEUwRSOCWlEh-4,2269
|
| 225 |
+
huggingface_hub/inference/_generated/types/text2text_generation.py,sha256=fotSlPWtIlp_LG26AQvNRXxU-jCUfda5PWsxCkbFeKM,1591
|
| 226 |
+
huggingface_hub/inference/_generated/types/text_classification.py,sha256=uamBzz4YzWQ9mb6yuriykX8r47ZHf0K3eJpOLzB2qJw,1439
|
| 227 |
+
huggingface_hub/inference/_generated/types/text_generation.py,sha256=FRUljypvCSQ2RaiylhlHeLJfHNy8qZvuty6SCB_zcaA,5813
|
| 228 |
+
huggingface_hub/inference/_generated/types/text_to_audio.py,sha256=CiaTUSBdFtOs3gGTosqyQrjCwrN8mVJLRxDDWUtwZpQ,4677
|
| 229 |
+
huggingface_hub/inference/_generated/types/text_to_image.py,sha256=Ju25WIDHE-1n8jlZ_udG4rpAaPYpmghR7-cDgVfuf48,1869
|
| 230 |
+
huggingface_hub/inference/_generated/types/text_to_speech.py,sha256=WqDzWkhZsDcjapkoFrkyjF903SKs7aHhjypsYrn8oBw,4693
|
| 231 |
+
huggingface_hub/inference/_generated/types/text_to_video.py,sha256=KjcDbtChwrIxIdTH6vcesUaMcjL2xjPf2nWEnHazj4Y,1756
|
| 232 |
+
huggingface_hub/inference/_generated/types/token_classification.py,sha256=8KRBlqgFDszaUnPMvdRkZNQaZBtDfBhKZ6SggQldQMM,1894
|
| 233 |
+
huggingface_hub/inference/_generated/types/translation.py,sha256=-j2PC1-qfoxvGMaKesqf5jXelqj0BDlEpfvZ7dsIEJM,1742
|
| 234 |
+
huggingface_hub/inference/_generated/types/video_classification.py,sha256=vfaCqp8MuRMMtGvYCX0x4-AcwEd71kPHgSYJTNIOxLQ,1668
|
| 235 |
+
huggingface_hub/inference/_generated/types/visual_question_answering.py,sha256=sWIB-SuEpW21oU6O5qQF3tt5PavUFsFOa8Xg4upTtek,1654
|
| 236 |
+
huggingface_hub/inference/_generated/types/zero_shot_classification.py,sha256=gx-7WHad7xsNa89iSKSteKHjaHrKbIyzN_nSdJN3HCY,1697
|
| 237 |
+
huggingface_hub/inference/_generated/types/zero_shot_image_classification.py,sha256=JZTQvcbSGrOQlWcPuJvAMT8YziDpBRn1O2RQSw507uY,1449
|
| 238 |
+
huggingface_hub/inference/_generated/types/zero_shot_object_detection.py,sha256=sjdpVUN5zW9aYBymLVUs6i5HVk2qkUBO9ysEjHmsXVM,1605
|
| 239 |
+
huggingface_hub/inference/_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 240 |
+
huggingface_hub/inference/_mcp/__pycache__/__init__.cpython-314.pyc,,
|
| 241 |
+
huggingface_hub/inference/_mcp/__pycache__/_cli_hacks.cpython-314.pyc,,
|
| 242 |
+
huggingface_hub/inference/_mcp/__pycache__/agent.cpython-314.pyc,,
|
| 243 |
+
huggingface_hub/inference/_mcp/__pycache__/cli.cpython-314.pyc,,
|
| 244 |
+
huggingface_hub/inference/_mcp/__pycache__/constants.cpython-314.pyc,,
|
| 245 |
+
huggingface_hub/inference/_mcp/__pycache__/mcp_client.cpython-314.pyc,,
|
| 246 |
+
huggingface_hub/inference/_mcp/__pycache__/types.cpython-314.pyc,,
|
| 247 |
+
huggingface_hub/inference/_mcp/__pycache__/utils.cpython-314.pyc,,
|
| 248 |
+
huggingface_hub/inference/_mcp/_cli_hacks.py,sha256=kIA8FfRfOWS3SrZrMTbuVOjE6yxfKfMK75Co5egTWHA,3284
|
| 249 |
+
huggingface_hub/inference/_mcp/agent.py,sha256=ufIzMGHore5n252hV5GZPM0ouDXIl6tv5Jl_5gHXnbg,4250
|
| 250 |
+
huggingface_hub/inference/_mcp/cli.py,sha256=RKdVQ_VdEgf1AL_QBCvwrsUQ0Ux4R1yerrE46DrV5C0,9642
|
| 251 |
+
huggingface_hub/inference/_mcp/constants.py,sha256=0Ih6ivEvG-pfF8NwErFngzP4Rof4lQoz6ms_eKSAkUI,2463
|
| 252 |
+
huggingface_hub/inference/_mcp/mcp_client.py,sha256=dGp8PhN6aVw4bDnuSySFSiguHUiz-nzhgv89CVdO7pI,17243
|
| 253 |
+
huggingface_hub/inference/_mcp/types.py,sha256=yHNfPsM9MhD06oeKdkbmrBsW-3WhUeqA26fyfRfx_bk,929
|
| 254 |
+
huggingface_hub/inference/_mcp/utils.py,sha256=gxSB_rBjQ6VrkApKFsxk6-UzhijxkDVNuZrsjW5pL8k,4318
|
| 255 |
+
huggingface_hub/inference/_providers/__init__.py,sha256=TNWnSNPk0RmToBysTc5D1COm0vHEJoGom0Yh7oG2wC4,9838
|
| 256 |
+
huggingface_hub/inference/_providers/__pycache__/__init__.cpython-314.pyc,,
|
| 257 |
+
huggingface_hub/inference/_providers/__pycache__/_common.cpython-314.pyc,,
|
| 258 |
+
huggingface_hub/inference/_providers/__pycache__/cerebras.cpython-314.pyc,,
|
| 259 |
+
huggingface_hub/inference/_providers/__pycache__/cohere.cpython-314.pyc,,
|
| 260 |
+
huggingface_hub/inference/_providers/__pycache__/deepinfra.cpython-314.pyc,,
|
| 261 |
+
huggingface_hub/inference/_providers/__pycache__/fal_ai.cpython-314.pyc,,
|
| 262 |
+
huggingface_hub/inference/_providers/__pycache__/featherless_ai.cpython-314.pyc,,
|
| 263 |
+
huggingface_hub/inference/_providers/__pycache__/fireworks_ai.cpython-314.pyc,,
|
| 264 |
+
huggingface_hub/inference/_providers/__pycache__/groq.cpython-314.pyc,,
|
| 265 |
+
huggingface_hub/inference/_providers/__pycache__/hf_inference.cpython-314.pyc,,
|
| 266 |
+
huggingface_hub/inference/_providers/__pycache__/novita.cpython-314.pyc,,
|
| 267 |
+
huggingface_hub/inference/_providers/__pycache__/nscale.cpython-314.pyc,,
|
| 268 |
+
huggingface_hub/inference/_providers/__pycache__/openai.cpython-314.pyc,,
|
| 269 |
+
huggingface_hub/inference/_providers/__pycache__/ovhcloud.cpython-314.pyc,,
|
| 270 |
+
huggingface_hub/inference/_providers/__pycache__/publicai.cpython-314.pyc,,
|
| 271 |
+
huggingface_hub/inference/_providers/__pycache__/replicate.cpython-314.pyc,,
|
| 272 |
+
huggingface_hub/inference/_providers/__pycache__/scaleway.cpython-314.pyc,,
|
| 273 |
+
huggingface_hub/inference/_providers/__pycache__/together.cpython-314.pyc,,
|
| 274 |
+
huggingface_hub/inference/_providers/__pycache__/wavespeed.cpython-314.pyc,,
|
| 275 |
+
huggingface_hub/inference/_providers/__pycache__/zai_org.cpython-314.pyc,,
|
| 276 |
+
huggingface_hub/inference/_providers/_common.py,sha256=9CjlMAtb7W0-GdB1Z-VkV0GKZ0Zm68rIuicdGE-765M,13716
|
| 277 |
+
huggingface_hub/inference/_providers/cerebras.py,sha256=QOJ-1U-os7uE7p6eUnn_P_APq-yQhx28be7c3Tq2EuA,210
|
| 278 |
+
huggingface_hub/inference/_providers/cohere.py,sha256=P9kbIuvQ2rXI1yNmgbw5VKFFTE0huLq2k-BCyDkDico,1226
|
| 279 |
+
huggingface_hub/inference/_providers/deepinfra.py,sha256=4Behgf5V7FTv7CbN6vPEIYwv1z4gztdRRU-KVoOgxVE,4703
|
| 280 |
+
huggingface_hub/inference/_providers/fal_ai.py,sha256=Y2vo5Dl3e8EoehplwNshu8LbDT1e7V4ZyhAMqs0wXws,11705
|
| 281 |
+
huggingface_hub/inference/_providers/featherless_ai.py,sha256=C8OHdFpoFyA--pawLTekUmUSRq4sw_r0D-cSPekD4kE,1347
|
| 282 |
+
huggingface_hub/inference/_providers/fireworks_ai.py,sha256=go6XPum8u0-g768HJ_r0S4Gb445gZJxnuY_acktz-9c,1188
|
| 283 |
+
huggingface_hub/inference/_providers/groq.py,sha256=JTk2JV4ZOlaohho7zLAFQtk92kGVsPmLJ1hmzcwsqvQ,315
|
| 284 |
+
huggingface_hub/inference/_providers/hf_inference.py,sha256=d2vdCKSi5CtyQqYndHHvH7SY131-7YHUK3yE7653eIc,9467
|
| 285 |
+
huggingface_hub/inference/_providers/novita.py,sha256=ATEoSdPAPLMfx3JpBc0sOyLh4upJYP6xHBuo4YEDYvg,2470
|
| 286 |
+
huggingface_hub/inference/_providers/nscale.py,sha256=T1L2JLI9LqYT9_YEdW76YEtxfDNtdGkoFhH-p2LmSxg,1767
|
| 287 |
+
huggingface_hub/inference/_providers/openai.py,sha256=wwjaaQ55xLmDsDnHpZk52xbuLoGZfWzJkFsE8AdFVaI,1054
|
| 288 |
+
huggingface_hub/inference/_providers/ovhcloud.py,sha256=tdmymlkbddMJKV7NRZ-tH2wymbLPFDTqUSXpWJUXyDQ,314
|
| 289 |
+
huggingface_hub/inference/_providers/publicai.py,sha256=1I2W6rORloB5QHSvky4njZO2XKLTwA-kPdNoauoT5rg,210
|
| 290 |
+
huggingface_hub/inference/_providers/replicate.py,sha256=hte0ZB2RtGFwpAuLrFp2Gbgsn3EOlccCnJkWTDTW__A,6027
|
| 291 |
+
huggingface_hub/inference/_providers/scaleway.py,sha256=MfIc7ZND1sPr__rOmNHZVg0VpECQo0bVyksgIm_32xQ,1174
|
| 292 |
+
huggingface_hub/inference/_providers/together.py,sha256=lrymDZ86n5yoR_Zz7o3AzzSePyo0s7BOBU5digLxb5o,12220
|
| 293 |
+
huggingface_hub/inference/_providers/wavespeed.py,sha256=MGM7Y7r2nQiH_EH0t5UE1o1fT-o3sS7RaQlicS9WHsg,5028
|
| 294 |
+
huggingface_hub/inference/_providers/zai_org.py,sha256=gLJZOEmCPmUZvdM7VDn2nxm4ac3veHdoMgQNR63UeWE,4739
|
| 295 |
+
huggingface_hub/lfs.py,sha256=8ymYNHRXgvgUUM9w0EAnC-vkSOOq_1EFJEJNoevoeE8,16588
|
| 296 |
+
huggingface_hub/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 297 |
+
huggingface_hub/repocard.py,sha256=7u7hiKjNuPYOwa3XPOPUnYPpuv4buya4WM47kVexklY,35602
|
| 298 |
+
huggingface_hub/repocard_data.py,sha256=f834MUSXGqrVcC02668r3ACrPzKKOAg75y3aB_hXm1U,34308
|
| 299 |
+
huggingface_hub/serialization/__init__.py,sha256=jCiw_vVQYW52gwVfWiqgocf2Q19kGTQlRGVpf-4SLP8,963
|
| 300 |
+
huggingface_hub/serialization/__pycache__/__init__.cpython-314.pyc,,
|
| 301 |
+
huggingface_hub/serialization/__pycache__/_base.cpython-314.pyc,,
|
| 302 |
+
huggingface_hub/serialization/__pycache__/_dduf.cpython-314.pyc,,
|
| 303 |
+
huggingface_hub/serialization/__pycache__/_torch.cpython-314.pyc,,
|
| 304 |
+
huggingface_hub/serialization/_base.py,sha256=8vYeDIsqgyOO3I_uii44Bkb50LsMz3kkD4gwAPzhfWU,8436
|
| 305 |
+
huggingface_hub/serialization/_dduf.py,sha256=FmGRg5wkXI5sNCgZgCNJFmqb_nVrrQYOHf4Lqq0d64I,15385
|
| 306 |
+
huggingface_hub/serialization/_torch.py,sha256=BtaCP-G3oYRP6Pj9MhI-ygBsk3JbBPnVDW9huSOgtkk,46897
|
| 307 |
+
huggingface_hub/templates/datasetcard_template.md,sha256=W-EMqR6wndbrnZorkVv56URWPG49l7MATGeI015kTvs,5503
|
| 308 |
+
huggingface_hub/templates/modelcard_template.md,sha256=4AqArS3cqdtbit5Bo-DhjcnDFR-pza5hErLLTPM4Yuc,6870
|
| 309 |
+
huggingface_hub/utils/__init__.py,sha256=pdVhPWWZkv0QM7kAQw7Bf259RZdPtYBLQqQwLQN-VC4,4045
|
| 310 |
+
huggingface_hub/utils/__pycache__/__init__.cpython-314.pyc,,
|
| 311 |
+
huggingface_hub/utils/__pycache__/_auth.cpython-314.pyc,,
|
| 312 |
+
huggingface_hub/utils/__pycache__/_cache_assets.cpython-314.pyc,,
|
| 313 |
+
huggingface_hub/utils/__pycache__/_cache_manager.cpython-314.pyc,,
|
| 314 |
+
huggingface_hub/utils/__pycache__/_chunk_utils.cpython-314.pyc,,
|
| 315 |
+
huggingface_hub/utils/__pycache__/_datetime.cpython-314.pyc,,
|
| 316 |
+
huggingface_hub/utils/__pycache__/_deprecation.cpython-314.pyc,,
|
| 317 |
+
huggingface_hub/utils/__pycache__/_detect_agent.cpython-314.pyc,,
|
| 318 |
+
huggingface_hub/utils/__pycache__/_dotenv.cpython-314.pyc,,
|
| 319 |
+
huggingface_hub/utils/__pycache__/_experimental.cpython-314.pyc,,
|
| 320 |
+
huggingface_hub/utils/__pycache__/_fixes.cpython-314.pyc,,
|
| 321 |
+
huggingface_hub/utils/__pycache__/_git_credential.cpython-314.pyc,,
|
| 322 |
+
huggingface_hub/utils/__pycache__/_headers.cpython-314.pyc,,
|
| 323 |
+
huggingface_hub/utils/__pycache__/_hf_uris.cpython-314.pyc,,
|
| 324 |
+
huggingface_hub/utils/__pycache__/_http.cpython-314.pyc,,
|
| 325 |
+
huggingface_hub/utils/__pycache__/_lfs.cpython-314.pyc,,
|
| 326 |
+
huggingface_hub/utils/__pycache__/_oauth_device.cpython-314.pyc,,
|
| 327 |
+
huggingface_hub/utils/__pycache__/_pagination.cpython-314.pyc,,
|
| 328 |
+
huggingface_hub/utils/__pycache__/_parsing.cpython-314.pyc,,
|
| 329 |
+
huggingface_hub/utils/__pycache__/_paths.cpython-314.pyc,,
|
| 330 |
+
huggingface_hub/utils/__pycache__/_runtime.cpython-314.pyc,,
|
| 331 |
+
huggingface_hub/utils/__pycache__/_safetensors.cpython-314.pyc,,
|
| 332 |
+
huggingface_hub/utils/__pycache__/_subprocess.cpython-314.pyc,,
|
| 333 |
+
huggingface_hub/utils/__pycache__/_telemetry.cpython-314.pyc,,
|
| 334 |
+
huggingface_hub/utils/__pycache__/_terminal.cpython-314.pyc,,
|
| 335 |
+
huggingface_hub/utils/__pycache__/_typing.cpython-314.pyc,,
|
| 336 |
+
huggingface_hub/utils/__pycache__/_validators.cpython-314.pyc,,
|
| 337 |
+
huggingface_hub/utils/__pycache__/_verification.cpython-314.pyc,,
|
| 338 |
+
huggingface_hub/utils/__pycache__/_xet.cpython-314.pyc,,
|
| 339 |
+
huggingface_hub/utils/__pycache__/_xet_progress_reporting.cpython-314.pyc,,
|
| 340 |
+
huggingface_hub/utils/__pycache__/endpoint_helpers.cpython-314.pyc,,
|
| 341 |
+
huggingface_hub/utils/__pycache__/insecure_hashlib.cpython-314.pyc,,
|
| 342 |
+
huggingface_hub/utils/__pycache__/logging.cpython-314.pyc,,
|
| 343 |
+
huggingface_hub/utils/__pycache__/sha.cpython-314.pyc,,
|
| 344 |
+
huggingface_hub/utils/__pycache__/tqdm.cpython-314.pyc,,
|
| 345 |
+
huggingface_hub/utils/_auth.py,sha256=Ykuq3yi67jxHmFdyLSc43xvGuCAIR7-h-dajX8IxpGQ,19958
|
| 346 |
+
huggingface_hub/utils/_cache_assets.py,sha256=nnzHRtQAR50dQeIK6qKddsmjjTW9v9HZ7b9bq7PJqss,5691
|
| 347 |
+
huggingface_hub/utils/_cache_manager.py,sha256=2StxP-nHZ5W9_hi8N9wBFqRNLw5HwO0h9HC7NTAGaFE,34955
|
| 348 |
+
huggingface_hub/utils/_chunk_utils.py,sha256=pTjy8Z-KLU4W_6D3OUh3E8lCodWDCd6aJwjNDU0C5U8,2121
|
| 349 |
+
huggingface_hub/utils/_datetime.py,sha256=tbNyI0Dkh27oScPUtLIT_8apqIIkXZYbigjOn9S3aMw,2755
|
| 350 |
+
huggingface_hub/utils/_deprecation.py,sha256=n4kNHbGipquSObJ-gxodcfd6lqoe_8s-VIsTuo3Oruk,4865
|
| 351 |
+
huggingface_hub/utils/_detect_agent.py,sha256=Iw2KW9BUZWfIypn9CtiTmEibHwKK9xx5Ar48z1cMU-Q,7337
|
| 352 |
+
huggingface_hub/utils/_dotenv.py,sha256=NdlEM8OXtzwlwmJCmdc6oGubFObrpgr408y8gnSs7ls,2604
|
| 353 |
+
huggingface_hub/utils/_experimental.py,sha256=q9vUvc1JybVFRQ0GRREG-BLouZEJC_40MxcwbAlOud0,2464
|
| 354 |
+
huggingface_hub/utils/_fixes.py,sha256=jTK1VLmc0ZC9ROSXjKoL5F6kOZFByzirRWIdXxhBfWU,4124
|
| 355 |
+
huggingface_hub/utils/_git_credential.py,sha256=1BhjvIScCOAToDORLOKrR3Szs-m0E5AYHPmC0SD7Nrs,4548
|
| 356 |
+
huggingface_hub/utils/_headers.py,sha256=Q9bu9mvOpm-9rfv-YRlr_Ywy_aMZkmRcN3MbVEcJAJA,8234
|
| 357 |
+
huggingface_hub/utils/_hf_uris.py,sha256=RTHrVKLT5vVU-ca8roXHrKwbN3njowanvxHjx5B4sUg,27558
|
| 358 |
+
huggingface_hub/utils/_http.py,sha256=C_mC63TsaxpmzyYBPHCneXKFLACEytAkBwxRwMNxcB4,46014
|
| 359 |
+
huggingface_hub/utils/_lfs.py,sha256=xMU-ROgNAUpDkzbH6yRZsE-eVYUYTNMGrgVb_QdTv-k,3942
|
| 360 |
+
huggingface_hub/utils/_oauth_device.py,sha256=o7ICmI01yDt1oAFNMUJvN1ThSfaCDpC8AHYSQgcnFGw,7983
|
| 361 |
+
huggingface_hub/utils/_pagination.py,sha256=h-TJjFbX3FqkA1T0tGZ0EhxMvuRSCg-UfL_a-BMs54I,1943
|
| 362 |
+
huggingface_hub/utils/_parsing.py,sha256=DVWuO6s_XMiGfXyc0jyX5hi_yGgBHpF9KLoeQM-VVK0,3586
|
| 363 |
+
huggingface_hub/utils/_paths.py,sha256=kWpUMIK9rmKb2LHgHCkk2DnroDzucrwbj-urCJCgfGw,5962
|
| 364 |
+
huggingface_hub/utils/_runtime.py,sha256=GVJ_Dt6y_48TZWl0ui9yPdW8M7rqGblRGyUqKaXxkCo,13493
|
| 365 |
+
huggingface_hub/utils/_safetensors.py,sha256=qFE7OA-vjU8X0zBBtOeNRzLu147KF-PdFoaxkYfWB4M,4426
|
| 366 |
+
huggingface_hub/utils/_subprocess.py,sha256=tFVBBNot_HLVqQ79y873TGb12C4PUTMwub-GhzDTemE,4542
|
| 367 |
+
huggingface_hub/utils/_telemetry.py,sha256=Tpa3YmOLhK_MEnL2i2fRLQy8aqAEZWbj6MKC18Hs8BA,4824
|
| 368 |
+
huggingface_hub/utils/_terminal.py,sha256=whHgs0Co-1LatoCmJE_ojqE_cWP6b5dWPWpU-uSFWUU,8969
|
| 369 |
+
huggingface_hub/utils/_typing.py,sha256=1LeE785YedppXSR9a1fQDu5rhTxX5v9kBYBaHrP65rA,3542
|
| 370 |
+
huggingface_hub/utils/_validators.py,sha256=tuC1U4yxB-4XKIajZMnKZUSKvZ6qPpydY8LItfa0g40,8419
|
| 371 |
+
huggingface_hub/utils/_verification.py,sha256=ZSilnolSkYmHB3vmBlWuuVr53xz59P5uGnxC3H-Hjc0,5434
|
| 372 |
+
huggingface_hub/utils/_xet.py,sha256=lVCUmCxzmBFYRSkJxFLtfhe7_HJWdLzJdENL-ln5Grc,6556
|
| 373 |
+
huggingface_hub/utils/_xet_progress_reporting.py,sha256=MnNCxB9ieflArNNyz3R4kpkjOdt6mQxi8qp8xqzXQTM,14162
|
| 374 |
+
huggingface_hub/utils/endpoint_helpers.py,sha256=9VtIAlxQ5H_4y30sjCAgbu7XCqAtNLC7aRYxaNn0hLI,2366
|
| 375 |
+
huggingface_hub/utils/insecure_hashlib.py,sha256=z3dVUFvdBZ8kQI_8Vzvvlr3ims-EBiY-SYPdnzIKOkw,1008
|
| 376 |
+
huggingface_hub/utils/logging.py,sha256=WaXk5gRa8Ml_LUIH34QCr8suYj8i_8Wot4IDnYJyWyM,4870
|
| 377 |
+
huggingface_hub/utils/sha.py,sha256=h8wxheZpcv671RhtiIFcFmQTDSrogN3kwQx3ZaNEUHg,2121
|
| 378 |
+
huggingface_hub/utils/tqdm.py,sha256=xXjKGy2y2gMqpKeMUqXcQWc66buW39NwkTr3hlWH6MY,16729
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/REQUESTED
ADDED
|
File without changes
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (83.0.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
hf = huggingface_hub.cli.hf:main
|
| 3 |
+
huggingface-cli = huggingface_hub.cli.deprecated_cli:main
|
| 4 |
+
tiny-agents = huggingface_hub.inference._mcp.cli:app
|
| 5 |
+
|
| 6 |
+
[fsspec.specs]
|
| 7 |
+
hf = huggingface_hub.HfFileSystem
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
.venv/lib/python3.14/site-packages/huggingface_hub-1.26.0.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
huggingface_hub
|
.venv/lib/python3.14/site-packages/huggingface_hub/__init__.py
ADDED
|
@@ -0,0 +1,1815 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2020 The HuggingFace Team. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
# ***********
|
| 16 |
+
# `huggingface_hub` init has 2 modes:
|
| 17 |
+
# - Normal usage:
|
| 18 |
+
# If imported to use it, all modules and functions are lazy-loaded. This means
|
| 19 |
+
# they exist at top level in module but are imported only the first time they are
|
| 20 |
+
# used. This way, `from huggingface_hub import something` will import `something`
|
| 21 |
+
# quickly without the hassle of importing all the features from `huggingface_hub`.
|
| 22 |
+
# - Static check:
|
| 23 |
+
# If statically analyzed, all modules and functions are loaded normally. This way
|
| 24 |
+
# static typing check works properly as well as autocomplete in text editors and
|
| 25 |
+
# IDEs.
|
| 26 |
+
#
|
| 27 |
+
# The static model imports are done inside the `if TYPE_CHECKING:` statement at
|
| 28 |
+
# the bottom of this file. Since module/functions imports are duplicated, it is
|
| 29 |
+
# mandatory to make sure to add them twice when adding one. This is checked in the
|
| 30 |
+
# `make quality` command.
|
| 31 |
+
#
|
| 32 |
+
# To update the static imports, please run the following command and commit the changes.
|
| 33 |
+
# ```
|
| 34 |
+
# # Use script
|
| 35 |
+
# python utils/check_static_imports.py --update-file
|
| 36 |
+
#
|
| 37 |
+
# # Or run style on codebase
|
| 38 |
+
# make style
|
| 39 |
+
# ```
|
| 40 |
+
#
|
| 41 |
+
# ***********
|
| 42 |
+
# Lazy loader vendored from https://github.com/scientific-python/lazy_loader
|
| 43 |
+
import importlib
|
| 44 |
+
import os
|
| 45 |
+
import sys
|
| 46 |
+
from typing import TYPE_CHECKING
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
__version__ = "1.26.0"
|
| 50 |
+
|
| 51 |
+
# Alphabetical order of definitions is ensured in tests
|
| 52 |
+
# WARNING: any comment added in this dictionary definition will be lost when
|
| 53 |
+
# re-generating the file !
|
| 54 |
+
_SUBMOD_ATTRS = {
|
| 55 |
+
"_buckets": [
|
| 56 |
+
"BucketFile",
|
| 57 |
+
"BucketFileMetadata",
|
| 58 |
+
"BucketFolder",
|
| 59 |
+
"BucketInfo",
|
| 60 |
+
"BucketUrl",
|
| 61 |
+
"SyncOperation",
|
| 62 |
+
"SyncPlan",
|
| 63 |
+
],
|
| 64 |
+
"_commit_scheduler": [
|
| 65 |
+
"CommitScheduler",
|
| 66 |
+
],
|
| 67 |
+
"_eval_results": [
|
| 68 |
+
"EvalResultEntry",
|
| 69 |
+
"eval_result_entries_to_yaml",
|
| 70 |
+
"parse_eval_result_entries",
|
| 71 |
+
],
|
| 72 |
+
"_inference_endpoints": [
|
| 73 |
+
"InferenceEndpoint",
|
| 74 |
+
"InferenceEndpointError",
|
| 75 |
+
"InferenceEndpointStatus",
|
| 76 |
+
"InferenceEndpointTimeoutError",
|
| 77 |
+
"InferenceEndpointType",
|
| 78 |
+
],
|
| 79 |
+
"_jobs_api": [
|
| 80 |
+
"JobAccelerator",
|
| 81 |
+
"JobDurations",
|
| 82 |
+
"JobHardware",
|
| 83 |
+
"JobHardwareInfo",
|
| 84 |
+
"JobInfo",
|
| 85 |
+
"JobInitiator",
|
| 86 |
+
"JobOwner",
|
| 87 |
+
"JobStage",
|
| 88 |
+
"JobStatus",
|
| 89 |
+
],
|
| 90 |
+
"_login": [
|
| 91 |
+
"auth_list",
|
| 92 |
+
"auth_switch",
|
| 93 |
+
"interpreter_login",
|
| 94 |
+
"login",
|
| 95 |
+
"logout",
|
| 96 |
+
"notebook_login",
|
| 97 |
+
],
|
| 98 |
+
"_oauth": [
|
| 99 |
+
"OAuthInfo",
|
| 100 |
+
"OAuthOrgInfo",
|
| 101 |
+
"OAuthUserInfo",
|
| 102 |
+
"attach_huggingface_oauth",
|
| 103 |
+
"parse_huggingface_oauth",
|
| 104 |
+
],
|
| 105 |
+
"_revision": [
|
| 106 |
+
"ResolvedRevision",
|
| 107 |
+
],
|
| 108 |
+
"_sandbox": [
|
| 109 |
+
"Sandbox",
|
| 110 |
+
"SandboxCommandResult",
|
| 111 |
+
"SandboxPool",
|
| 112 |
+
"SandboxProcess",
|
| 113 |
+
],
|
| 114 |
+
"_snapshot_download": [
|
| 115 |
+
"get_cached_repo_tree",
|
| 116 |
+
"snapshot_download",
|
| 117 |
+
],
|
| 118 |
+
"_space_api": [
|
| 119 |
+
"SpaceHardware",
|
| 120 |
+
"SpaceRuntime",
|
| 121 |
+
"SpaceSecret",
|
| 122 |
+
"SpaceStage",
|
| 123 |
+
"SpaceStorage",
|
| 124 |
+
"SpaceVariable",
|
| 125 |
+
"Volume",
|
| 126 |
+
],
|
| 127 |
+
"_tensorboard_logger": [
|
| 128 |
+
"HFSummaryWriter",
|
| 129 |
+
],
|
| 130 |
+
"_webhooks_payload": [
|
| 131 |
+
"WebhookPayload",
|
| 132 |
+
"WebhookPayloadComment",
|
| 133 |
+
"WebhookPayloadDiscussion",
|
| 134 |
+
"WebhookPayloadDiscussionChanges",
|
| 135 |
+
"WebhookPayloadEvent",
|
| 136 |
+
"WebhookPayloadMovedTo",
|
| 137 |
+
"WebhookPayloadRepo",
|
| 138 |
+
"WebhookPayloadUrl",
|
| 139 |
+
"WebhookPayloadWebhook",
|
| 140 |
+
],
|
| 141 |
+
"_webhooks_server": [
|
| 142 |
+
"WebhooksServer",
|
| 143 |
+
"webhook_endpoint",
|
| 144 |
+
],
|
| 145 |
+
"cli._cli_utils": [
|
| 146 |
+
"check_cli_update",
|
| 147 |
+
"typer_factory",
|
| 148 |
+
],
|
| 149 |
+
"community": [
|
| 150 |
+
"Discussion",
|
| 151 |
+
"DiscussionComment",
|
| 152 |
+
"DiscussionCommit",
|
| 153 |
+
"DiscussionEvent",
|
| 154 |
+
"DiscussionStatusChange",
|
| 155 |
+
"DiscussionTitleChange",
|
| 156 |
+
"DiscussionWithDetails",
|
| 157 |
+
],
|
| 158 |
+
"constants": [
|
| 159 |
+
"CONFIG_NAME",
|
| 160 |
+
"FLAX_WEIGHTS_NAME",
|
| 161 |
+
"HUGGINGFACE_CO_URL_HOME",
|
| 162 |
+
"HUGGINGFACE_CO_URL_TEMPLATE",
|
| 163 |
+
"PYTORCH_WEIGHTS_NAME",
|
| 164 |
+
"REPO_TYPE_DATASET",
|
| 165 |
+
"REPO_TYPE_MODEL",
|
| 166 |
+
"REPO_TYPE_SPACE",
|
| 167 |
+
"TF2_WEIGHTS_NAME",
|
| 168 |
+
"TF_WEIGHTS_NAME",
|
| 169 |
+
"is_offline_mode",
|
| 170 |
+
],
|
| 171 |
+
"fastai_utils": [
|
| 172 |
+
"_save_pretrained_fastai",
|
| 173 |
+
"from_pretrained_fastai",
|
| 174 |
+
"push_to_hub_fastai",
|
| 175 |
+
],
|
| 176 |
+
"file_download": [
|
| 177 |
+
"DryRunFileInfo",
|
| 178 |
+
"HfFileMetadata",
|
| 179 |
+
"_CACHED_NO_EXIST",
|
| 180 |
+
"get_hf_file_metadata",
|
| 181 |
+
"hf_hub_download",
|
| 182 |
+
"hf_hub_url",
|
| 183 |
+
"try_to_load_from_cache",
|
| 184 |
+
],
|
| 185 |
+
"hf_api": [
|
| 186 |
+
"Collection",
|
| 187 |
+
"CollectionItem",
|
| 188 |
+
"CommitInfo",
|
| 189 |
+
"CommitOperation",
|
| 190 |
+
"CommitOperationAdd",
|
| 191 |
+
"CommitOperationCopy",
|
| 192 |
+
"CommitOperationDelete",
|
| 193 |
+
"DatasetInfo",
|
| 194 |
+
"DatasetLeaderboardEntry",
|
| 195 |
+
"GitCommitInfo",
|
| 196 |
+
"GitRefInfo",
|
| 197 |
+
"GitRefs",
|
| 198 |
+
"HfApi",
|
| 199 |
+
"KernelInfo",
|
| 200 |
+
"ModelInfo",
|
| 201 |
+
"Organization",
|
| 202 |
+
"RepoFile",
|
| 203 |
+
"RepoFolder",
|
| 204 |
+
"RepoStorageInfo",
|
| 205 |
+
"RepoUrl",
|
| 206 |
+
"SpaceInfo",
|
| 207 |
+
"SpaceSearchResult",
|
| 208 |
+
"SpaceTemplate",
|
| 209 |
+
"User",
|
| 210 |
+
"UserLikes",
|
| 211 |
+
"WebhookInfo",
|
| 212 |
+
"WebhookWatchedItem",
|
| 213 |
+
"accept_access_request",
|
| 214 |
+
"add_collection_item",
|
| 215 |
+
"add_space_secret",
|
| 216 |
+
"add_space_variable",
|
| 217 |
+
"auth_check",
|
| 218 |
+
"batch_bucket_files",
|
| 219 |
+
"bucket_info",
|
| 220 |
+
"cancel_access_request",
|
| 221 |
+
"cancel_job",
|
| 222 |
+
"change_discussion_status",
|
| 223 |
+
"comment_discussion",
|
| 224 |
+
"copy_files",
|
| 225 |
+
"create_branch",
|
| 226 |
+
"create_bucket",
|
| 227 |
+
"create_collection",
|
| 228 |
+
"create_commit",
|
| 229 |
+
"create_discussion",
|
| 230 |
+
"create_inference_endpoint",
|
| 231 |
+
"create_inference_endpoint_from_catalog",
|
| 232 |
+
"create_pull_request",
|
| 233 |
+
"create_repo",
|
| 234 |
+
"create_scheduled_job",
|
| 235 |
+
"create_scheduled_uv_job",
|
| 236 |
+
"create_tag",
|
| 237 |
+
"create_webhook",
|
| 238 |
+
"dataset_info",
|
| 239 |
+
"delete_branch",
|
| 240 |
+
"delete_bucket",
|
| 241 |
+
"delete_collection",
|
| 242 |
+
"delete_collection_item",
|
| 243 |
+
"delete_file",
|
| 244 |
+
"delete_folder",
|
| 245 |
+
"delete_inference_endpoint",
|
| 246 |
+
"delete_repo",
|
| 247 |
+
"delete_scheduled_job",
|
| 248 |
+
"delete_space_secret",
|
| 249 |
+
"delete_space_storage",
|
| 250 |
+
"delete_space_variable",
|
| 251 |
+
"delete_space_volumes",
|
| 252 |
+
"delete_tag",
|
| 253 |
+
"delete_webhook",
|
| 254 |
+
"disable_space_dev_mode",
|
| 255 |
+
"disable_webhook",
|
| 256 |
+
"download_bucket_files",
|
| 257 |
+
"duplicate_repo",
|
| 258 |
+
"duplicate_space",
|
| 259 |
+
"edit_discussion_comment",
|
| 260 |
+
"enable_space_dev_mode",
|
| 261 |
+
"enable_webhook",
|
| 262 |
+
"fetch_job_logs",
|
| 263 |
+
"fetch_job_metrics",
|
| 264 |
+
"fetch_space_logs",
|
| 265 |
+
"file_exists",
|
| 266 |
+
"get_bucket_file_metadata",
|
| 267 |
+
"get_bucket_paths_info",
|
| 268 |
+
"get_collection",
|
| 269 |
+
"get_dataset_leaderboard",
|
| 270 |
+
"get_dataset_tags",
|
| 271 |
+
"get_discussion_details",
|
| 272 |
+
"get_full_repo_name",
|
| 273 |
+
"get_inference_endpoint",
|
| 274 |
+
"get_local_safetensors_metadata",
|
| 275 |
+
"get_model_tags",
|
| 276 |
+
"get_organization_overview",
|
| 277 |
+
"get_paths_info",
|
| 278 |
+
"get_repo_discussions",
|
| 279 |
+
"get_safetensors_metadata",
|
| 280 |
+
"get_space_runtime",
|
| 281 |
+
"get_space_secrets",
|
| 282 |
+
"get_space_variables",
|
| 283 |
+
"get_user_overview",
|
| 284 |
+
"get_webhook",
|
| 285 |
+
"grant_access",
|
| 286 |
+
"inspect_job",
|
| 287 |
+
"inspect_scheduled_job",
|
| 288 |
+
"kernel_info",
|
| 289 |
+
"list_accepted_access_requests",
|
| 290 |
+
"list_bucket_tree",
|
| 291 |
+
"list_buckets",
|
| 292 |
+
"list_collections",
|
| 293 |
+
"list_daily_papers",
|
| 294 |
+
"list_dataset_parquet_files",
|
| 295 |
+
"list_datasets",
|
| 296 |
+
"list_inference_catalog",
|
| 297 |
+
"list_inference_endpoints",
|
| 298 |
+
"list_jobs",
|
| 299 |
+
"list_jobs_hardware",
|
| 300 |
+
"list_lfs_files",
|
| 301 |
+
"list_liked_repos",
|
| 302 |
+
"list_models",
|
| 303 |
+
"list_organization_followers",
|
| 304 |
+
"list_organization_members",
|
| 305 |
+
"list_papers",
|
| 306 |
+
"list_pending_access_requests",
|
| 307 |
+
"list_rejected_access_requests",
|
| 308 |
+
"list_repo_commits",
|
| 309 |
+
"list_repo_files",
|
| 310 |
+
"list_repo_likers",
|
| 311 |
+
"list_repo_refs",
|
| 312 |
+
"list_repo_tree",
|
| 313 |
+
"list_space_templates",
|
| 314 |
+
"list_spaces",
|
| 315 |
+
"list_spaces_hardware",
|
| 316 |
+
"list_user_followers",
|
| 317 |
+
"list_user_following",
|
| 318 |
+
"list_user_repos",
|
| 319 |
+
"list_webhooks",
|
| 320 |
+
"merge_pull_request",
|
| 321 |
+
"model_info",
|
| 322 |
+
"move_bucket",
|
| 323 |
+
"move_repo",
|
| 324 |
+
"paper_info",
|
| 325 |
+
"parse_local_safetensors_file_metadata",
|
| 326 |
+
"parse_safetensors_file_metadata",
|
| 327 |
+
"pause_inference_endpoint",
|
| 328 |
+
"pause_space",
|
| 329 |
+
"permanently_delete_lfs_files",
|
| 330 |
+
"preupload_lfs_files",
|
| 331 |
+
"read_paper",
|
| 332 |
+
"reject_access_request",
|
| 333 |
+
"rename_discussion",
|
| 334 |
+
"repo_exists",
|
| 335 |
+
"repo_info",
|
| 336 |
+
"repo_type_and_id_from_hf_id",
|
| 337 |
+
"request_space_hardware",
|
| 338 |
+
"request_space_storage",
|
| 339 |
+
"resolve_revision",
|
| 340 |
+
"restart_space",
|
| 341 |
+
"resume_inference_endpoint",
|
| 342 |
+
"resume_scheduled_job",
|
| 343 |
+
"revision_exists",
|
| 344 |
+
"run_as_future",
|
| 345 |
+
"run_job",
|
| 346 |
+
"run_uv_job",
|
| 347 |
+
"scale_to_zero_inference_endpoint",
|
| 348 |
+
"search_spaces",
|
| 349 |
+
"set_space_sleep_time",
|
| 350 |
+
"set_space_volumes",
|
| 351 |
+
"space_info",
|
| 352 |
+
"super_squash_history",
|
| 353 |
+
"suspend_scheduled_job",
|
| 354 |
+
"sync_bucket",
|
| 355 |
+
"sync_job_volume",
|
| 356 |
+
"trigger_scheduled_job",
|
| 357 |
+
"unlike",
|
| 358 |
+
"update_collection_item",
|
| 359 |
+
"update_collection_metadata",
|
| 360 |
+
"update_collection_resource_group",
|
| 361 |
+
"update_inference_endpoint",
|
| 362 |
+
"update_job_labels",
|
| 363 |
+
"update_repo_settings",
|
| 364 |
+
"update_scheduled_job_labels",
|
| 365 |
+
"update_webhook",
|
| 366 |
+
"upload_file",
|
| 367 |
+
"upload_folder",
|
| 368 |
+
"upload_large_folder",
|
| 369 |
+
"verify_repo_checksums",
|
| 370 |
+
"wait_for_job",
|
| 371 |
+
"wait_for_space",
|
| 372 |
+
"whoami",
|
| 373 |
+
],
|
| 374 |
+
"hf_file_system": [
|
| 375 |
+
"HfFileSystem",
|
| 376 |
+
"HfFileSystemFile",
|
| 377 |
+
"HfFileSystemResolvedPath",
|
| 378 |
+
"HfFileSystemStreamFile",
|
| 379 |
+
"hffs",
|
| 380 |
+
],
|
| 381 |
+
"hub_mixin": [
|
| 382 |
+
"ModelHubMixin",
|
| 383 |
+
"PyTorchModelHubMixin",
|
| 384 |
+
],
|
| 385 |
+
"inference._client": [
|
| 386 |
+
"InferenceClient",
|
| 387 |
+
"InferenceTimeoutError",
|
| 388 |
+
],
|
| 389 |
+
"inference._generated._async_client": [
|
| 390 |
+
"AsyncInferenceClient",
|
| 391 |
+
],
|
| 392 |
+
"inference._generated.types": [
|
| 393 |
+
"AudioClassificationInput",
|
| 394 |
+
"AudioClassificationOutputElement",
|
| 395 |
+
"AudioClassificationOutputTransform",
|
| 396 |
+
"AudioClassificationParameters",
|
| 397 |
+
"AudioToAudioInput",
|
| 398 |
+
"AudioToAudioOutputElement",
|
| 399 |
+
"AutomaticSpeechRecognitionEarlyStoppingEnum",
|
| 400 |
+
"AutomaticSpeechRecognitionGenerationParameters",
|
| 401 |
+
"AutomaticSpeechRecognitionInput",
|
| 402 |
+
"AutomaticSpeechRecognitionOutput",
|
| 403 |
+
"AutomaticSpeechRecognitionOutputChunk",
|
| 404 |
+
"AutomaticSpeechRecognitionParameters",
|
| 405 |
+
"ChatCompletionInput",
|
| 406 |
+
"ChatCompletionInputFunctionDefinition",
|
| 407 |
+
"ChatCompletionInputFunctionName",
|
| 408 |
+
"ChatCompletionInputGrammarType",
|
| 409 |
+
"ChatCompletionInputJSONSchema",
|
| 410 |
+
"ChatCompletionInputMessage",
|
| 411 |
+
"ChatCompletionInputMessageChunk",
|
| 412 |
+
"ChatCompletionInputMessageChunkType",
|
| 413 |
+
"ChatCompletionInputResponseFormatJSONObject",
|
| 414 |
+
"ChatCompletionInputResponseFormatJSONSchema",
|
| 415 |
+
"ChatCompletionInputResponseFormatText",
|
| 416 |
+
"ChatCompletionInputStreamOptions",
|
| 417 |
+
"ChatCompletionInputTool",
|
| 418 |
+
"ChatCompletionInputToolCall",
|
| 419 |
+
"ChatCompletionInputToolChoiceClass",
|
| 420 |
+
"ChatCompletionInputToolChoiceEnum",
|
| 421 |
+
"ChatCompletionInputURL",
|
| 422 |
+
"ChatCompletionOutput",
|
| 423 |
+
"ChatCompletionOutputComplete",
|
| 424 |
+
"ChatCompletionOutputFunctionDefinition",
|
| 425 |
+
"ChatCompletionOutputLogprob",
|
| 426 |
+
"ChatCompletionOutputLogprobs",
|
| 427 |
+
"ChatCompletionOutputMessage",
|
| 428 |
+
"ChatCompletionOutputToolCall",
|
| 429 |
+
"ChatCompletionOutputTopLogprob",
|
| 430 |
+
"ChatCompletionOutputUsage",
|
| 431 |
+
"ChatCompletionStreamOutput",
|
| 432 |
+
"ChatCompletionStreamOutputChoice",
|
| 433 |
+
"ChatCompletionStreamOutputDelta",
|
| 434 |
+
"ChatCompletionStreamOutputDeltaToolCall",
|
| 435 |
+
"ChatCompletionStreamOutputFunction",
|
| 436 |
+
"ChatCompletionStreamOutputLogprob",
|
| 437 |
+
"ChatCompletionStreamOutputLogprobs",
|
| 438 |
+
"ChatCompletionStreamOutputTopLogprob",
|
| 439 |
+
"ChatCompletionStreamOutputUsage",
|
| 440 |
+
"DepthEstimationInput",
|
| 441 |
+
"DepthEstimationOutput",
|
| 442 |
+
"DocumentQuestionAnsweringInput",
|
| 443 |
+
"DocumentQuestionAnsweringInputData",
|
| 444 |
+
"DocumentQuestionAnsweringOutputElement",
|
| 445 |
+
"DocumentQuestionAnsweringParameters",
|
| 446 |
+
"FeatureExtractionInput",
|
| 447 |
+
"FeatureExtractionInputTruncationDirection",
|
| 448 |
+
"FillMaskInput",
|
| 449 |
+
"FillMaskOutputElement",
|
| 450 |
+
"FillMaskParameters",
|
| 451 |
+
"ImageClassificationInput",
|
| 452 |
+
"ImageClassificationOutputElement",
|
| 453 |
+
"ImageClassificationOutputTransform",
|
| 454 |
+
"ImageClassificationParameters",
|
| 455 |
+
"ImageSegmentationInput",
|
| 456 |
+
"ImageSegmentationOutputElement",
|
| 457 |
+
"ImageSegmentationParameters",
|
| 458 |
+
"ImageSegmentationSubtask",
|
| 459 |
+
"ImageTextToImageInput",
|
| 460 |
+
"ImageTextToImageOutput",
|
| 461 |
+
"ImageTextToImageParameters",
|
| 462 |
+
"ImageTextToImageTargetSize",
|
| 463 |
+
"ImageTextToVideoInput",
|
| 464 |
+
"ImageTextToVideoOutput",
|
| 465 |
+
"ImageTextToVideoParameters",
|
| 466 |
+
"ImageTextToVideoTargetSize",
|
| 467 |
+
"ImageToImageInput",
|
| 468 |
+
"ImageToImageOutput",
|
| 469 |
+
"ImageToImageParameters",
|
| 470 |
+
"ImageToImageTargetSize",
|
| 471 |
+
"ImageToTextEarlyStoppingEnum",
|
| 472 |
+
"ImageToTextGenerationParameters",
|
| 473 |
+
"ImageToTextInput",
|
| 474 |
+
"ImageToTextOutput",
|
| 475 |
+
"ImageToTextParameters",
|
| 476 |
+
"ImageToVideoInput",
|
| 477 |
+
"ImageToVideoOutput",
|
| 478 |
+
"ImageToVideoParameters",
|
| 479 |
+
"ImageToVideoTargetSize",
|
| 480 |
+
"ObjectDetectionBoundingBox",
|
| 481 |
+
"ObjectDetectionInput",
|
| 482 |
+
"ObjectDetectionOutputElement",
|
| 483 |
+
"ObjectDetectionParameters",
|
| 484 |
+
"Padding",
|
| 485 |
+
"QuestionAnsweringInput",
|
| 486 |
+
"QuestionAnsweringInputData",
|
| 487 |
+
"QuestionAnsweringOutputElement",
|
| 488 |
+
"QuestionAnsweringParameters",
|
| 489 |
+
"SentenceSimilarityInput",
|
| 490 |
+
"SentenceSimilarityInputData",
|
| 491 |
+
"SummarizationInput",
|
| 492 |
+
"SummarizationOutput",
|
| 493 |
+
"SummarizationParameters",
|
| 494 |
+
"SummarizationTruncationStrategy",
|
| 495 |
+
"TableQuestionAnsweringInput",
|
| 496 |
+
"TableQuestionAnsweringInputData",
|
| 497 |
+
"TableQuestionAnsweringOutputElement",
|
| 498 |
+
"TableQuestionAnsweringParameters",
|
| 499 |
+
"Text2TextGenerationInput",
|
| 500 |
+
"Text2TextGenerationOutput",
|
| 501 |
+
"Text2TextGenerationParameters",
|
| 502 |
+
"Text2TextGenerationTruncationStrategy",
|
| 503 |
+
"TextClassificationInput",
|
| 504 |
+
"TextClassificationOutputElement",
|
| 505 |
+
"TextClassificationOutputTransform",
|
| 506 |
+
"TextClassificationParameters",
|
| 507 |
+
"TextGenerationInput",
|
| 508 |
+
"TextGenerationInputGenerateParameters",
|
| 509 |
+
"TextGenerationInputGrammarType",
|
| 510 |
+
"TextGenerationOutput",
|
| 511 |
+
"TextGenerationOutputBestOfSequence",
|
| 512 |
+
"TextGenerationOutputDetails",
|
| 513 |
+
"TextGenerationOutputFinishReason",
|
| 514 |
+
"TextGenerationOutputPrefillToken",
|
| 515 |
+
"TextGenerationOutputToken",
|
| 516 |
+
"TextGenerationStreamOutput",
|
| 517 |
+
"TextGenerationStreamOutputStreamDetails",
|
| 518 |
+
"TextGenerationStreamOutputToken",
|
| 519 |
+
"TextToAudioEarlyStoppingEnum",
|
| 520 |
+
"TextToAudioGenerationParameters",
|
| 521 |
+
"TextToAudioInput",
|
| 522 |
+
"TextToAudioOutput",
|
| 523 |
+
"TextToAudioParameters",
|
| 524 |
+
"TextToImageInput",
|
| 525 |
+
"TextToImageOutput",
|
| 526 |
+
"TextToImageParameters",
|
| 527 |
+
"TextToSpeechEarlyStoppingEnum",
|
| 528 |
+
"TextToSpeechGenerationParameters",
|
| 529 |
+
"TextToSpeechInput",
|
| 530 |
+
"TextToSpeechOutput",
|
| 531 |
+
"TextToSpeechParameters",
|
| 532 |
+
"TextToVideoInput",
|
| 533 |
+
"TextToVideoOutput",
|
| 534 |
+
"TextToVideoParameters",
|
| 535 |
+
"TokenClassificationAggregationStrategy",
|
| 536 |
+
"TokenClassificationInput",
|
| 537 |
+
"TokenClassificationOutputElement",
|
| 538 |
+
"TokenClassificationParameters",
|
| 539 |
+
"TranslationInput",
|
| 540 |
+
"TranslationOutput",
|
| 541 |
+
"TranslationParameters",
|
| 542 |
+
"TranslationTruncationStrategy",
|
| 543 |
+
"TypeEnum",
|
| 544 |
+
"VideoClassificationInput",
|
| 545 |
+
"VideoClassificationOutputElement",
|
| 546 |
+
"VideoClassificationOutputTransform",
|
| 547 |
+
"VideoClassificationParameters",
|
| 548 |
+
"VisualQuestionAnsweringInput",
|
| 549 |
+
"VisualQuestionAnsweringInputData",
|
| 550 |
+
"VisualQuestionAnsweringOutputElement",
|
| 551 |
+
"VisualQuestionAnsweringParameters",
|
| 552 |
+
"ZeroShotClassificationInput",
|
| 553 |
+
"ZeroShotClassificationOutputElement",
|
| 554 |
+
"ZeroShotClassificationParameters",
|
| 555 |
+
"ZeroShotImageClassificationInput",
|
| 556 |
+
"ZeroShotImageClassificationOutputElement",
|
| 557 |
+
"ZeroShotImageClassificationParameters",
|
| 558 |
+
"ZeroShotObjectDetectionBoundingBox",
|
| 559 |
+
"ZeroShotObjectDetectionInput",
|
| 560 |
+
"ZeroShotObjectDetectionOutputElement",
|
| 561 |
+
"ZeroShotObjectDetectionParameters",
|
| 562 |
+
],
|
| 563 |
+
"inference._mcp.agent": [
|
| 564 |
+
"Agent",
|
| 565 |
+
],
|
| 566 |
+
"inference._mcp.mcp_client": [
|
| 567 |
+
"MCPClient",
|
| 568 |
+
],
|
| 569 |
+
"repocard": [
|
| 570 |
+
"DatasetCard",
|
| 571 |
+
"ModelCard",
|
| 572 |
+
"RepoCard",
|
| 573 |
+
"SpaceCard",
|
| 574 |
+
"metadata_eval_result",
|
| 575 |
+
"metadata_load",
|
| 576 |
+
"metadata_save",
|
| 577 |
+
"metadata_update",
|
| 578 |
+
],
|
| 579 |
+
"repocard_data": [
|
| 580 |
+
"CardData",
|
| 581 |
+
"DatasetCardData",
|
| 582 |
+
"EvalResult",
|
| 583 |
+
"ModelCardData",
|
| 584 |
+
"SpaceCardData",
|
| 585 |
+
],
|
| 586 |
+
"serialization": [
|
| 587 |
+
"StateDictSplit",
|
| 588 |
+
"get_torch_storage_id",
|
| 589 |
+
"get_torch_storage_size",
|
| 590 |
+
"load_state_dict_from_file",
|
| 591 |
+
"load_torch_model",
|
| 592 |
+
"save_torch_model",
|
| 593 |
+
"save_torch_state_dict",
|
| 594 |
+
"split_state_dict_into_shards_factory",
|
| 595 |
+
"split_torch_state_dict_into_shards",
|
| 596 |
+
],
|
| 597 |
+
"serialization._dduf": [
|
| 598 |
+
"DDUFEntry",
|
| 599 |
+
"export_entries_as_dduf",
|
| 600 |
+
"export_folder_as_dduf",
|
| 601 |
+
"read_dduf_file",
|
| 602 |
+
],
|
| 603 |
+
"utils": [
|
| 604 |
+
"ASYNC_CLIENT_FACTORY_T",
|
| 605 |
+
"CLIENT_FACTORY_T",
|
| 606 |
+
"CacheNotFound",
|
| 607 |
+
"CachedFileInfo",
|
| 608 |
+
"CachedIncompleteFileInfo",
|
| 609 |
+
"CachedRepoInfo",
|
| 610 |
+
"CachedRevisionInfo",
|
| 611 |
+
"CorruptedCacheException",
|
| 612 |
+
"DeleteCacheStrategy",
|
| 613 |
+
"HFCacheInfo",
|
| 614 |
+
"HfUri",
|
| 615 |
+
"cached_assets_path",
|
| 616 |
+
"close_session",
|
| 617 |
+
"dump_environment_info",
|
| 618 |
+
"get_async_session",
|
| 619 |
+
"get_session",
|
| 620 |
+
"get_token",
|
| 621 |
+
"hf_raise_for_status",
|
| 622 |
+
"logging",
|
| 623 |
+
"parse_hf_uri",
|
| 624 |
+
"scan_cache_dir",
|
| 625 |
+
"set_async_client_factory",
|
| 626 |
+
"set_client_factory",
|
| 627 |
+
],
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
# WARNING: __all__ is generated automatically, Any manual edit will be lost when re-generating this file !
|
| 631 |
+
#
|
| 632 |
+
# To update the static imports, please run the following command and commit the changes.
|
| 633 |
+
# ```
|
| 634 |
+
# # Use script
|
| 635 |
+
# python utils/check_all_variable.py --update
|
| 636 |
+
#
|
| 637 |
+
# # Or run style on codebase
|
| 638 |
+
# make style
|
| 639 |
+
# ```
|
| 640 |
+
|
| 641 |
+
__all__ = [
|
| 642 |
+
"ASYNC_CLIENT_FACTORY_T",
|
| 643 |
+
"Agent",
|
| 644 |
+
"AsyncInferenceClient",
|
| 645 |
+
"AudioClassificationInput",
|
| 646 |
+
"AudioClassificationOutputElement",
|
| 647 |
+
"AudioClassificationOutputTransform",
|
| 648 |
+
"AudioClassificationParameters",
|
| 649 |
+
"AudioToAudioInput",
|
| 650 |
+
"AudioToAudioOutputElement",
|
| 651 |
+
"AutomaticSpeechRecognitionEarlyStoppingEnum",
|
| 652 |
+
"AutomaticSpeechRecognitionGenerationParameters",
|
| 653 |
+
"AutomaticSpeechRecognitionInput",
|
| 654 |
+
"AutomaticSpeechRecognitionOutput",
|
| 655 |
+
"AutomaticSpeechRecognitionOutputChunk",
|
| 656 |
+
"AutomaticSpeechRecognitionParameters",
|
| 657 |
+
"BucketFile",
|
| 658 |
+
"BucketFileMetadata",
|
| 659 |
+
"BucketFolder",
|
| 660 |
+
"BucketInfo",
|
| 661 |
+
"BucketUrl",
|
| 662 |
+
"CLIENT_FACTORY_T",
|
| 663 |
+
"CONFIG_NAME",
|
| 664 |
+
"CacheNotFound",
|
| 665 |
+
"CachedFileInfo",
|
| 666 |
+
"CachedIncompleteFileInfo",
|
| 667 |
+
"CachedRepoInfo",
|
| 668 |
+
"CachedRevisionInfo",
|
| 669 |
+
"CardData",
|
| 670 |
+
"ChatCompletionInput",
|
| 671 |
+
"ChatCompletionInputFunctionDefinition",
|
| 672 |
+
"ChatCompletionInputFunctionName",
|
| 673 |
+
"ChatCompletionInputGrammarType",
|
| 674 |
+
"ChatCompletionInputJSONSchema",
|
| 675 |
+
"ChatCompletionInputMessage",
|
| 676 |
+
"ChatCompletionInputMessageChunk",
|
| 677 |
+
"ChatCompletionInputMessageChunkType",
|
| 678 |
+
"ChatCompletionInputResponseFormatJSONObject",
|
| 679 |
+
"ChatCompletionInputResponseFormatJSONSchema",
|
| 680 |
+
"ChatCompletionInputResponseFormatText",
|
| 681 |
+
"ChatCompletionInputStreamOptions",
|
| 682 |
+
"ChatCompletionInputTool",
|
| 683 |
+
"ChatCompletionInputToolCall",
|
| 684 |
+
"ChatCompletionInputToolChoiceClass",
|
| 685 |
+
"ChatCompletionInputToolChoiceEnum",
|
| 686 |
+
"ChatCompletionInputURL",
|
| 687 |
+
"ChatCompletionOutput",
|
| 688 |
+
"ChatCompletionOutputComplete",
|
| 689 |
+
"ChatCompletionOutputFunctionDefinition",
|
| 690 |
+
"ChatCompletionOutputLogprob",
|
| 691 |
+
"ChatCompletionOutputLogprobs",
|
| 692 |
+
"ChatCompletionOutputMessage",
|
| 693 |
+
"ChatCompletionOutputToolCall",
|
| 694 |
+
"ChatCompletionOutputTopLogprob",
|
| 695 |
+
"ChatCompletionOutputUsage",
|
| 696 |
+
"ChatCompletionStreamOutput",
|
| 697 |
+
"ChatCompletionStreamOutputChoice",
|
| 698 |
+
"ChatCompletionStreamOutputDelta",
|
| 699 |
+
"ChatCompletionStreamOutputDeltaToolCall",
|
| 700 |
+
"ChatCompletionStreamOutputFunction",
|
| 701 |
+
"ChatCompletionStreamOutputLogprob",
|
| 702 |
+
"ChatCompletionStreamOutputLogprobs",
|
| 703 |
+
"ChatCompletionStreamOutputTopLogprob",
|
| 704 |
+
"ChatCompletionStreamOutputUsage",
|
| 705 |
+
"Collection",
|
| 706 |
+
"CollectionItem",
|
| 707 |
+
"CommitInfo",
|
| 708 |
+
"CommitOperation",
|
| 709 |
+
"CommitOperationAdd",
|
| 710 |
+
"CommitOperationCopy",
|
| 711 |
+
"CommitOperationDelete",
|
| 712 |
+
"CommitScheduler",
|
| 713 |
+
"CorruptedCacheException",
|
| 714 |
+
"DDUFEntry",
|
| 715 |
+
"DatasetCard",
|
| 716 |
+
"DatasetCardData",
|
| 717 |
+
"DatasetInfo",
|
| 718 |
+
"DatasetLeaderboardEntry",
|
| 719 |
+
"DeleteCacheStrategy",
|
| 720 |
+
"DepthEstimationInput",
|
| 721 |
+
"DepthEstimationOutput",
|
| 722 |
+
"Discussion",
|
| 723 |
+
"DiscussionComment",
|
| 724 |
+
"DiscussionCommit",
|
| 725 |
+
"DiscussionEvent",
|
| 726 |
+
"DiscussionStatusChange",
|
| 727 |
+
"DiscussionTitleChange",
|
| 728 |
+
"DiscussionWithDetails",
|
| 729 |
+
"DocumentQuestionAnsweringInput",
|
| 730 |
+
"DocumentQuestionAnsweringInputData",
|
| 731 |
+
"DocumentQuestionAnsweringOutputElement",
|
| 732 |
+
"DocumentQuestionAnsweringParameters",
|
| 733 |
+
"DryRunFileInfo",
|
| 734 |
+
"EvalResult",
|
| 735 |
+
"EvalResultEntry",
|
| 736 |
+
"FLAX_WEIGHTS_NAME",
|
| 737 |
+
"FeatureExtractionInput",
|
| 738 |
+
"FeatureExtractionInputTruncationDirection",
|
| 739 |
+
"FillMaskInput",
|
| 740 |
+
"FillMaskOutputElement",
|
| 741 |
+
"FillMaskParameters",
|
| 742 |
+
"GitCommitInfo",
|
| 743 |
+
"GitRefInfo",
|
| 744 |
+
"GitRefs",
|
| 745 |
+
"HFCacheInfo",
|
| 746 |
+
"HFSummaryWriter",
|
| 747 |
+
"HUGGINGFACE_CO_URL_HOME",
|
| 748 |
+
"HUGGINGFACE_CO_URL_TEMPLATE",
|
| 749 |
+
"HfApi",
|
| 750 |
+
"HfFileMetadata",
|
| 751 |
+
"HfFileSystem",
|
| 752 |
+
"HfFileSystemFile",
|
| 753 |
+
"HfFileSystemResolvedPath",
|
| 754 |
+
"HfFileSystemStreamFile",
|
| 755 |
+
"HfUri",
|
| 756 |
+
"ImageClassificationInput",
|
| 757 |
+
"ImageClassificationOutputElement",
|
| 758 |
+
"ImageClassificationOutputTransform",
|
| 759 |
+
"ImageClassificationParameters",
|
| 760 |
+
"ImageSegmentationInput",
|
| 761 |
+
"ImageSegmentationOutputElement",
|
| 762 |
+
"ImageSegmentationParameters",
|
| 763 |
+
"ImageSegmentationSubtask",
|
| 764 |
+
"ImageTextToImageInput",
|
| 765 |
+
"ImageTextToImageOutput",
|
| 766 |
+
"ImageTextToImageParameters",
|
| 767 |
+
"ImageTextToImageTargetSize",
|
| 768 |
+
"ImageTextToVideoInput",
|
| 769 |
+
"ImageTextToVideoOutput",
|
| 770 |
+
"ImageTextToVideoParameters",
|
| 771 |
+
"ImageTextToVideoTargetSize",
|
| 772 |
+
"ImageToImageInput",
|
| 773 |
+
"ImageToImageOutput",
|
| 774 |
+
"ImageToImageParameters",
|
| 775 |
+
"ImageToImageTargetSize",
|
| 776 |
+
"ImageToTextEarlyStoppingEnum",
|
| 777 |
+
"ImageToTextGenerationParameters",
|
| 778 |
+
"ImageToTextInput",
|
| 779 |
+
"ImageToTextOutput",
|
| 780 |
+
"ImageToTextParameters",
|
| 781 |
+
"ImageToVideoInput",
|
| 782 |
+
"ImageToVideoOutput",
|
| 783 |
+
"ImageToVideoParameters",
|
| 784 |
+
"ImageToVideoTargetSize",
|
| 785 |
+
"InferenceClient",
|
| 786 |
+
"InferenceEndpoint",
|
| 787 |
+
"InferenceEndpointError",
|
| 788 |
+
"InferenceEndpointStatus",
|
| 789 |
+
"InferenceEndpointTimeoutError",
|
| 790 |
+
"InferenceEndpointType",
|
| 791 |
+
"InferenceTimeoutError",
|
| 792 |
+
"JobAccelerator",
|
| 793 |
+
"JobDurations",
|
| 794 |
+
"JobHardware",
|
| 795 |
+
"JobHardwareInfo",
|
| 796 |
+
"JobInfo",
|
| 797 |
+
"JobInitiator",
|
| 798 |
+
"JobOwner",
|
| 799 |
+
"JobStage",
|
| 800 |
+
"JobStatus",
|
| 801 |
+
"KernelInfo",
|
| 802 |
+
"MCPClient",
|
| 803 |
+
"ModelCard",
|
| 804 |
+
"ModelCardData",
|
| 805 |
+
"ModelHubMixin",
|
| 806 |
+
"ModelInfo",
|
| 807 |
+
"OAuthInfo",
|
| 808 |
+
"OAuthOrgInfo",
|
| 809 |
+
"OAuthUserInfo",
|
| 810 |
+
"ObjectDetectionBoundingBox",
|
| 811 |
+
"ObjectDetectionInput",
|
| 812 |
+
"ObjectDetectionOutputElement",
|
| 813 |
+
"ObjectDetectionParameters",
|
| 814 |
+
"Organization",
|
| 815 |
+
"PYTORCH_WEIGHTS_NAME",
|
| 816 |
+
"Padding",
|
| 817 |
+
"PyTorchModelHubMixin",
|
| 818 |
+
"QuestionAnsweringInput",
|
| 819 |
+
"QuestionAnsweringInputData",
|
| 820 |
+
"QuestionAnsweringOutputElement",
|
| 821 |
+
"QuestionAnsweringParameters",
|
| 822 |
+
"REPO_TYPE_DATASET",
|
| 823 |
+
"REPO_TYPE_MODEL",
|
| 824 |
+
"REPO_TYPE_SPACE",
|
| 825 |
+
"RepoCard",
|
| 826 |
+
"RepoFile",
|
| 827 |
+
"RepoFolder",
|
| 828 |
+
"RepoStorageInfo",
|
| 829 |
+
"RepoUrl",
|
| 830 |
+
"ResolvedRevision",
|
| 831 |
+
"Sandbox",
|
| 832 |
+
"SandboxCommandResult",
|
| 833 |
+
"SandboxPool",
|
| 834 |
+
"SandboxProcess",
|
| 835 |
+
"SentenceSimilarityInput",
|
| 836 |
+
"SentenceSimilarityInputData",
|
| 837 |
+
"SpaceCard",
|
| 838 |
+
"SpaceCardData",
|
| 839 |
+
"SpaceHardware",
|
| 840 |
+
"SpaceInfo",
|
| 841 |
+
"SpaceRuntime",
|
| 842 |
+
"SpaceSearchResult",
|
| 843 |
+
"SpaceSecret",
|
| 844 |
+
"SpaceStage",
|
| 845 |
+
"SpaceStorage",
|
| 846 |
+
"SpaceTemplate",
|
| 847 |
+
"SpaceVariable",
|
| 848 |
+
"StateDictSplit",
|
| 849 |
+
"SummarizationInput",
|
| 850 |
+
"SummarizationOutput",
|
| 851 |
+
"SummarizationParameters",
|
| 852 |
+
"SummarizationTruncationStrategy",
|
| 853 |
+
"SyncOperation",
|
| 854 |
+
"SyncPlan",
|
| 855 |
+
"TF2_WEIGHTS_NAME",
|
| 856 |
+
"TF_WEIGHTS_NAME",
|
| 857 |
+
"TableQuestionAnsweringInput",
|
| 858 |
+
"TableQuestionAnsweringInputData",
|
| 859 |
+
"TableQuestionAnsweringOutputElement",
|
| 860 |
+
"TableQuestionAnsweringParameters",
|
| 861 |
+
"Text2TextGenerationInput",
|
| 862 |
+
"Text2TextGenerationOutput",
|
| 863 |
+
"Text2TextGenerationParameters",
|
| 864 |
+
"Text2TextGenerationTruncationStrategy",
|
| 865 |
+
"TextClassificationInput",
|
| 866 |
+
"TextClassificationOutputElement",
|
| 867 |
+
"TextClassificationOutputTransform",
|
| 868 |
+
"TextClassificationParameters",
|
| 869 |
+
"TextGenerationInput",
|
| 870 |
+
"TextGenerationInputGenerateParameters",
|
| 871 |
+
"TextGenerationInputGrammarType",
|
| 872 |
+
"TextGenerationOutput",
|
| 873 |
+
"TextGenerationOutputBestOfSequence",
|
| 874 |
+
"TextGenerationOutputDetails",
|
| 875 |
+
"TextGenerationOutputFinishReason",
|
| 876 |
+
"TextGenerationOutputPrefillToken",
|
| 877 |
+
"TextGenerationOutputToken",
|
| 878 |
+
"TextGenerationStreamOutput",
|
| 879 |
+
"TextGenerationStreamOutputStreamDetails",
|
| 880 |
+
"TextGenerationStreamOutputToken",
|
| 881 |
+
"TextToAudioEarlyStoppingEnum",
|
| 882 |
+
"TextToAudioGenerationParameters",
|
| 883 |
+
"TextToAudioInput",
|
| 884 |
+
"TextToAudioOutput",
|
| 885 |
+
"TextToAudioParameters",
|
| 886 |
+
"TextToImageInput",
|
| 887 |
+
"TextToImageOutput",
|
| 888 |
+
"TextToImageParameters",
|
| 889 |
+
"TextToSpeechEarlyStoppingEnum",
|
| 890 |
+
"TextToSpeechGenerationParameters",
|
| 891 |
+
"TextToSpeechInput",
|
| 892 |
+
"TextToSpeechOutput",
|
| 893 |
+
"TextToSpeechParameters",
|
| 894 |
+
"TextToVideoInput",
|
| 895 |
+
"TextToVideoOutput",
|
| 896 |
+
"TextToVideoParameters",
|
| 897 |
+
"TokenClassificationAggregationStrategy",
|
| 898 |
+
"TokenClassificationInput",
|
| 899 |
+
"TokenClassificationOutputElement",
|
| 900 |
+
"TokenClassificationParameters",
|
| 901 |
+
"TranslationInput",
|
| 902 |
+
"TranslationOutput",
|
| 903 |
+
"TranslationParameters",
|
| 904 |
+
"TranslationTruncationStrategy",
|
| 905 |
+
"TypeEnum",
|
| 906 |
+
"User",
|
| 907 |
+
"UserLikes",
|
| 908 |
+
"VideoClassificationInput",
|
| 909 |
+
"VideoClassificationOutputElement",
|
| 910 |
+
"VideoClassificationOutputTransform",
|
| 911 |
+
"VideoClassificationParameters",
|
| 912 |
+
"VisualQuestionAnsweringInput",
|
| 913 |
+
"VisualQuestionAnsweringInputData",
|
| 914 |
+
"VisualQuestionAnsweringOutputElement",
|
| 915 |
+
"VisualQuestionAnsweringParameters",
|
| 916 |
+
"Volume",
|
| 917 |
+
"WebhookInfo",
|
| 918 |
+
"WebhookPayload",
|
| 919 |
+
"WebhookPayloadComment",
|
| 920 |
+
"WebhookPayloadDiscussion",
|
| 921 |
+
"WebhookPayloadDiscussionChanges",
|
| 922 |
+
"WebhookPayloadEvent",
|
| 923 |
+
"WebhookPayloadMovedTo",
|
| 924 |
+
"WebhookPayloadRepo",
|
| 925 |
+
"WebhookPayloadUrl",
|
| 926 |
+
"WebhookPayloadWebhook",
|
| 927 |
+
"WebhookWatchedItem",
|
| 928 |
+
"WebhooksServer",
|
| 929 |
+
"ZeroShotClassificationInput",
|
| 930 |
+
"ZeroShotClassificationOutputElement",
|
| 931 |
+
"ZeroShotClassificationParameters",
|
| 932 |
+
"ZeroShotImageClassificationInput",
|
| 933 |
+
"ZeroShotImageClassificationOutputElement",
|
| 934 |
+
"ZeroShotImageClassificationParameters",
|
| 935 |
+
"ZeroShotObjectDetectionBoundingBox",
|
| 936 |
+
"ZeroShotObjectDetectionInput",
|
| 937 |
+
"ZeroShotObjectDetectionOutputElement",
|
| 938 |
+
"ZeroShotObjectDetectionParameters",
|
| 939 |
+
"_CACHED_NO_EXIST",
|
| 940 |
+
"_save_pretrained_fastai",
|
| 941 |
+
"accept_access_request",
|
| 942 |
+
"add_collection_item",
|
| 943 |
+
"add_space_secret",
|
| 944 |
+
"add_space_variable",
|
| 945 |
+
"attach_huggingface_oauth",
|
| 946 |
+
"auth_check",
|
| 947 |
+
"auth_list",
|
| 948 |
+
"auth_switch",
|
| 949 |
+
"batch_bucket_files",
|
| 950 |
+
"bucket_info",
|
| 951 |
+
"cached_assets_path",
|
| 952 |
+
"cancel_access_request",
|
| 953 |
+
"cancel_job",
|
| 954 |
+
"change_discussion_status",
|
| 955 |
+
"check_cli_update",
|
| 956 |
+
"close_session",
|
| 957 |
+
"comment_discussion",
|
| 958 |
+
"copy_files",
|
| 959 |
+
"create_branch",
|
| 960 |
+
"create_bucket",
|
| 961 |
+
"create_collection",
|
| 962 |
+
"create_commit",
|
| 963 |
+
"create_discussion",
|
| 964 |
+
"create_inference_endpoint",
|
| 965 |
+
"create_inference_endpoint_from_catalog",
|
| 966 |
+
"create_pull_request",
|
| 967 |
+
"create_repo",
|
| 968 |
+
"create_scheduled_job",
|
| 969 |
+
"create_scheduled_uv_job",
|
| 970 |
+
"create_tag",
|
| 971 |
+
"create_webhook",
|
| 972 |
+
"dataset_info",
|
| 973 |
+
"delete_branch",
|
| 974 |
+
"delete_bucket",
|
| 975 |
+
"delete_collection",
|
| 976 |
+
"delete_collection_item",
|
| 977 |
+
"delete_file",
|
| 978 |
+
"delete_folder",
|
| 979 |
+
"delete_inference_endpoint",
|
| 980 |
+
"delete_repo",
|
| 981 |
+
"delete_scheduled_job",
|
| 982 |
+
"delete_space_secret",
|
| 983 |
+
"delete_space_storage",
|
| 984 |
+
"delete_space_variable",
|
| 985 |
+
"delete_space_volumes",
|
| 986 |
+
"delete_tag",
|
| 987 |
+
"delete_webhook",
|
| 988 |
+
"disable_space_dev_mode",
|
| 989 |
+
"disable_webhook",
|
| 990 |
+
"download_bucket_files",
|
| 991 |
+
"dump_environment_info",
|
| 992 |
+
"duplicate_repo",
|
| 993 |
+
"duplicate_space",
|
| 994 |
+
"edit_discussion_comment",
|
| 995 |
+
"enable_space_dev_mode",
|
| 996 |
+
"enable_webhook",
|
| 997 |
+
"eval_result_entries_to_yaml",
|
| 998 |
+
"export_entries_as_dduf",
|
| 999 |
+
"export_folder_as_dduf",
|
| 1000 |
+
"fetch_job_logs",
|
| 1001 |
+
"fetch_job_metrics",
|
| 1002 |
+
"fetch_space_logs",
|
| 1003 |
+
"file_exists",
|
| 1004 |
+
"from_pretrained_fastai",
|
| 1005 |
+
"get_async_session",
|
| 1006 |
+
"get_bucket_file_metadata",
|
| 1007 |
+
"get_bucket_paths_info",
|
| 1008 |
+
"get_cached_repo_tree",
|
| 1009 |
+
"get_collection",
|
| 1010 |
+
"get_dataset_leaderboard",
|
| 1011 |
+
"get_dataset_tags",
|
| 1012 |
+
"get_discussion_details",
|
| 1013 |
+
"get_full_repo_name",
|
| 1014 |
+
"get_hf_file_metadata",
|
| 1015 |
+
"get_inference_endpoint",
|
| 1016 |
+
"get_local_safetensors_metadata",
|
| 1017 |
+
"get_model_tags",
|
| 1018 |
+
"get_organization_overview",
|
| 1019 |
+
"get_paths_info",
|
| 1020 |
+
"get_repo_discussions",
|
| 1021 |
+
"get_safetensors_metadata",
|
| 1022 |
+
"get_session",
|
| 1023 |
+
"get_space_runtime",
|
| 1024 |
+
"get_space_secrets",
|
| 1025 |
+
"get_space_variables",
|
| 1026 |
+
"get_token",
|
| 1027 |
+
"get_torch_storage_id",
|
| 1028 |
+
"get_torch_storage_size",
|
| 1029 |
+
"get_user_overview",
|
| 1030 |
+
"get_webhook",
|
| 1031 |
+
"grant_access",
|
| 1032 |
+
"hf_hub_download",
|
| 1033 |
+
"hf_hub_url",
|
| 1034 |
+
"hf_raise_for_status",
|
| 1035 |
+
"hffs",
|
| 1036 |
+
"inspect_job",
|
| 1037 |
+
"inspect_scheduled_job",
|
| 1038 |
+
"interpreter_login",
|
| 1039 |
+
"is_offline_mode",
|
| 1040 |
+
"kernel_info",
|
| 1041 |
+
"list_accepted_access_requests",
|
| 1042 |
+
"list_bucket_tree",
|
| 1043 |
+
"list_buckets",
|
| 1044 |
+
"list_collections",
|
| 1045 |
+
"list_daily_papers",
|
| 1046 |
+
"list_dataset_parquet_files",
|
| 1047 |
+
"list_datasets",
|
| 1048 |
+
"list_inference_catalog",
|
| 1049 |
+
"list_inference_endpoints",
|
| 1050 |
+
"list_jobs",
|
| 1051 |
+
"list_jobs_hardware",
|
| 1052 |
+
"list_lfs_files",
|
| 1053 |
+
"list_liked_repos",
|
| 1054 |
+
"list_models",
|
| 1055 |
+
"list_organization_followers",
|
| 1056 |
+
"list_organization_members",
|
| 1057 |
+
"list_papers",
|
| 1058 |
+
"list_pending_access_requests",
|
| 1059 |
+
"list_rejected_access_requests",
|
| 1060 |
+
"list_repo_commits",
|
| 1061 |
+
"list_repo_files",
|
| 1062 |
+
"list_repo_likers",
|
| 1063 |
+
"list_repo_refs",
|
| 1064 |
+
"list_repo_tree",
|
| 1065 |
+
"list_space_templates",
|
| 1066 |
+
"list_spaces",
|
| 1067 |
+
"list_spaces_hardware",
|
| 1068 |
+
"list_user_followers",
|
| 1069 |
+
"list_user_following",
|
| 1070 |
+
"list_user_repos",
|
| 1071 |
+
"list_webhooks",
|
| 1072 |
+
"load_state_dict_from_file",
|
| 1073 |
+
"load_torch_model",
|
| 1074 |
+
"logging",
|
| 1075 |
+
"login",
|
| 1076 |
+
"logout",
|
| 1077 |
+
"merge_pull_request",
|
| 1078 |
+
"metadata_eval_result",
|
| 1079 |
+
"metadata_load",
|
| 1080 |
+
"metadata_save",
|
| 1081 |
+
"metadata_update",
|
| 1082 |
+
"model_info",
|
| 1083 |
+
"move_bucket",
|
| 1084 |
+
"move_repo",
|
| 1085 |
+
"notebook_login",
|
| 1086 |
+
"paper_info",
|
| 1087 |
+
"parse_eval_result_entries",
|
| 1088 |
+
"parse_hf_uri",
|
| 1089 |
+
"parse_huggingface_oauth",
|
| 1090 |
+
"parse_local_safetensors_file_metadata",
|
| 1091 |
+
"parse_safetensors_file_metadata",
|
| 1092 |
+
"pause_inference_endpoint",
|
| 1093 |
+
"pause_space",
|
| 1094 |
+
"permanently_delete_lfs_files",
|
| 1095 |
+
"preupload_lfs_files",
|
| 1096 |
+
"push_to_hub_fastai",
|
| 1097 |
+
"read_dduf_file",
|
| 1098 |
+
"read_paper",
|
| 1099 |
+
"reject_access_request",
|
| 1100 |
+
"rename_discussion",
|
| 1101 |
+
"repo_exists",
|
| 1102 |
+
"repo_info",
|
| 1103 |
+
"repo_type_and_id_from_hf_id",
|
| 1104 |
+
"request_space_hardware",
|
| 1105 |
+
"request_space_storage",
|
| 1106 |
+
"resolve_revision",
|
| 1107 |
+
"restart_space",
|
| 1108 |
+
"resume_inference_endpoint",
|
| 1109 |
+
"resume_scheduled_job",
|
| 1110 |
+
"revision_exists",
|
| 1111 |
+
"run_as_future",
|
| 1112 |
+
"run_job",
|
| 1113 |
+
"run_uv_job",
|
| 1114 |
+
"save_torch_model",
|
| 1115 |
+
"save_torch_state_dict",
|
| 1116 |
+
"scale_to_zero_inference_endpoint",
|
| 1117 |
+
"scan_cache_dir",
|
| 1118 |
+
"search_spaces",
|
| 1119 |
+
"set_async_client_factory",
|
| 1120 |
+
"set_client_factory",
|
| 1121 |
+
"set_space_sleep_time",
|
| 1122 |
+
"set_space_volumes",
|
| 1123 |
+
"snapshot_download",
|
| 1124 |
+
"space_info",
|
| 1125 |
+
"split_state_dict_into_shards_factory",
|
| 1126 |
+
"split_torch_state_dict_into_shards",
|
| 1127 |
+
"super_squash_history",
|
| 1128 |
+
"suspend_scheduled_job",
|
| 1129 |
+
"sync_bucket",
|
| 1130 |
+
"sync_job_volume",
|
| 1131 |
+
"trigger_scheduled_job",
|
| 1132 |
+
"try_to_load_from_cache",
|
| 1133 |
+
"typer_factory",
|
| 1134 |
+
"unlike",
|
| 1135 |
+
"update_collection_item",
|
| 1136 |
+
"update_collection_metadata",
|
| 1137 |
+
"update_collection_resource_group",
|
| 1138 |
+
"update_inference_endpoint",
|
| 1139 |
+
"update_job_labels",
|
| 1140 |
+
"update_repo_settings",
|
| 1141 |
+
"update_scheduled_job_labels",
|
| 1142 |
+
"update_webhook",
|
| 1143 |
+
"upload_file",
|
| 1144 |
+
"upload_folder",
|
| 1145 |
+
"upload_large_folder",
|
| 1146 |
+
"verify_repo_checksums",
|
| 1147 |
+
"wait_for_job",
|
| 1148 |
+
"wait_for_space",
|
| 1149 |
+
"webhook_endpoint",
|
| 1150 |
+
"whoami",
|
| 1151 |
+
]
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
def _attach(package_name, submodules=None, submod_attrs=None):
|
| 1155 |
+
"""Attach lazily loaded submodules, functions, or other attributes.
|
| 1156 |
+
|
| 1157 |
+
Typically, modules import submodules and attributes as follows:
|
| 1158 |
+
|
| 1159 |
+
```py
|
| 1160 |
+
import mysubmodule
|
| 1161 |
+
import anothersubmodule
|
| 1162 |
+
|
| 1163 |
+
from .foo import someattr
|
| 1164 |
+
```
|
| 1165 |
+
|
| 1166 |
+
The idea is to replace a package's `__getattr__`, `__dir__`, such that all imports
|
| 1167 |
+
work exactly the way they would with normal imports, except that the import occurs
|
| 1168 |
+
upon first use.
|
| 1169 |
+
|
| 1170 |
+
The typical way to call this function, replacing the above imports, is:
|
| 1171 |
+
|
| 1172 |
+
```python
|
| 1173 |
+
__getattr__, __dir__ = lazy.attach(
|
| 1174 |
+
__name__,
|
| 1175 |
+
['mysubmodule', 'anothersubmodule'],
|
| 1176 |
+
{'foo': ['someattr']}
|
| 1177 |
+
)
|
| 1178 |
+
```
|
| 1179 |
+
This functionality requires Python 3.7 or higher.
|
| 1180 |
+
|
| 1181 |
+
Args:
|
| 1182 |
+
package_name (`str`):
|
| 1183 |
+
Typically use `__name__`.
|
| 1184 |
+
submodules (`set`):
|
| 1185 |
+
List of submodules to attach.
|
| 1186 |
+
submod_attrs (`dict`):
|
| 1187 |
+
Dictionary of submodule -> list of attributes / functions.
|
| 1188 |
+
These attributes are imported as they are used.
|
| 1189 |
+
|
| 1190 |
+
Returns:
|
| 1191 |
+
__getattr__, __dir__, __all__
|
| 1192 |
+
|
| 1193 |
+
"""
|
| 1194 |
+
if submod_attrs is None:
|
| 1195 |
+
submod_attrs = {}
|
| 1196 |
+
|
| 1197 |
+
if submodules is None:
|
| 1198 |
+
submodules = set()
|
| 1199 |
+
else:
|
| 1200 |
+
submodules = set(submodules)
|
| 1201 |
+
|
| 1202 |
+
attr_to_modules = {attr: mod for mod, attrs in submod_attrs.items() for attr in attrs}
|
| 1203 |
+
|
| 1204 |
+
def __getattr__(name):
|
| 1205 |
+
if name in submodules:
|
| 1206 |
+
try:
|
| 1207 |
+
return importlib.import_module(f"{package_name}.{name}")
|
| 1208 |
+
except Exception as e:
|
| 1209 |
+
print(f"Error importing {package_name}.{name}: {e}")
|
| 1210 |
+
raise
|
| 1211 |
+
elif name in attr_to_modules:
|
| 1212 |
+
submod_path = f"{package_name}.{attr_to_modules[name]}"
|
| 1213 |
+
try:
|
| 1214 |
+
submod = importlib.import_module(submod_path)
|
| 1215 |
+
except Exception as e:
|
| 1216 |
+
print(f"Error importing {submod_path}: {e}")
|
| 1217 |
+
raise
|
| 1218 |
+
attr = getattr(submod, name)
|
| 1219 |
+
|
| 1220 |
+
# If the attribute lives in a file (module) with the same
|
| 1221 |
+
# name as the attribute, ensure that the attribute and *not*
|
| 1222 |
+
# the module is accessible on the package.
|
| 1223 |
+
if name == attr_to_modules[name]:
|
| 1224 |
+
pkg = sys.modules[package_name]
|
| 1225 |
+
pkg.__dict__[name] = attr
|
| 1226 |
+
|
| 1227 |
+
return attr
|
| 1228 |
+
else:
|
| 1229 |
+
raise AttributeError(f"No {package_name} attribute {name}")
|
| 1230 |
+
|
| 1231 |
+
def __dir__():
|
| 1232 |
+
return __all__
|
| 1233 |
+
|
| 1234 |
+
return __getattr__, __dir__
|
| 1235 |
+
|
| 1236 |
+
|
| 1237 |
+
__getattr__, __dir__ = _attach(__name__, submodules=[], submod_attrs=_SUBMOD_ATTRS)
|
| 1238 |
+
|
| 1239 |
+
if os.environ.get("EAGER_IMPORT", ""):
|
| 1240 |
+
for attr in __all__:
|
| 1241 |
+
__getattr__(attr)
|
| 1242 |
+
|
| 1243 |
+
# WARNING: any content below this statement is generated automatically. Any manual edit
|
| 1244 |
+
# will be lost when re-generating this file !
|
| 1245 |
+
#
|
| 1246 |
+
# To update the static imports, please run the following command and commit the changes.
|
| 1247 |
+
# ```
|
| 1248 |
+
# # Use script
|
| 1249 |
+
# python utils/check_static_imports.py --update
|
| 1250 |
+
#
|
| 1251 |
+
# # Or run style on codebase
|
| 1252 |
+
# make style
|
| 1253 |
+
# ```
|
| 1254 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 1255 |
+
from ._buckets import (
|
| 1256 |
+
BucketFile, # noqa: F401
|
| 1257 |
+
BucketFileMetadata, # noqa: F401
|
| 1258 |
+
BucketFolder, # noqa: F401
|
| 1259 |
+
BucketInfo, # noqa: F401
|
| 1260 |
+
BucketUrl, # noqa: F401
|
| 1261 |
+
SyncOperation, # noqa: F401
|
| 1262 |
+
SyncPlan, # noqa: F401
|
| 1263 |
+
)
|
| 1264 |
+
from ._commit_scheduler import CommitScheduler # noqa: F401
|
| 1265 |
+
from ._eval_results import (
|
| 1266 |
+
EvalResultEntry, # noqa: F401
|
| 1267 |
+
eval_result_entries_to_yaml, # noqa: F401
|
| 1268 |
+
parse_eval_result_entries, # noqa: F401
|
| 1269 |
+
)
|
| 1270 |
+
from ._inference_endpoints import (
|
| 1271 |
+
InferenceEndpoint, # noqa: F401
|
| 1272 |
+
InferenceEndpointError, # noqa: F401
|
| 1273 |
+
InferenceEndpointStatus, # noqa: F401
|
| 1274 |
+
InferenceEndpointTimeoutError, # noqa: F401
|
| 1275 |
+
InferenceEndpointType, # noqa: F401
|
| 1276 |
+
)
|
| 1277 |
+
from ._jobs_api import (
|
| 1278 |
+
JobAccelerator, # noqa: F401
|
| 1279 |
+
JobDurations, # noqa: F401
|
| 1280 |
+
JobHardware, # noqa: F401
|
| 1281 |
+
JobHardwareInfo, # noqa: F401
|
| 1282 |
+
JobInfo, # noqa: F401
|
| 1283 |
+
JobInitiator, # noqa: F401
|
| 1284 |
+
JobOwner, # noqa: F401
|
| 1285 |
+
JobStage, # noqa: F401
|
| 1286 |
+
JobStatus, # noqa: F401
|
| 1287 |
+
)
|
| 1288 |
+
from ._login import (
|
| 1289 |
+
auth_list, # noqa: F401
|
| 1290 |
+
auth_switch, # noqa: F401
|
| 1291 |
+
interpreter_login, # noqa: F401
|
| 1292 |
+
login, # noqa: F401
|
| 1293 |
+
logout, # noqa: F401
|
| 1294 |
+
notebook_login, # noqa: F401
|
| 1295 |
+
)
|
| 1296 |
+
from ._oauth import (
|
| 1297 |
+
OAuthInfo, # noqa: F401
|
| 1298 |
+
OAuthOrgInfo, # noqa: F401
|
| 1299 |
+
OAuthUserInfo, # noqa: F401
|
| 1300 |
+
attach_huggingface_oauth, # noqa: F401
|
| 1301 |
+
parse_huggingface_oauth, # noqa: F401
|
| 1302 |
+
)
|
| 1303 |
+
from ._revision import ResolvedRevision # noqa: F401
|
| 1304 |
+
from ._sandbox import (
|
| 1305 |
+
Sandbox, # noqa: F401
|
| 1306 |
+
SandboxCommandResult, # noqa: F401
|
| 1307 |
+
SandboxPool, # noqa: F401
|
| 1308 |
+
SandboxProcess, # noqa: F401
|
| 1309 |
+
)
|
| 1310 |
+
from ._snapshot_download import (
|
| 1311 |
+
get_cached_repo_tree, # noqa: F401
|
| 1312 |
+
snapshot_download, # noqa: F401
|
| 1313 |
+
)
|
| 1314 |
+
from ._space_api import (
|
| 1315 |
+
SpaceHardware, # noqa: F401
|
| 1316 |
+
SpaceRuntime, # noqa: F401
|
| 1317 |
+
SpaceSecret, # noqa: F401
|
| 1318 |
+
SpaceStage, # noqa: F401
|
| 1319 |
+
SpaceStorage, # noqa: F401
|
| 1320 |
+
SpaceVariable, # noqa: F401
|
| 1321 |
+
Volume, # noqa: F401
|
| 1322 |
+
)
|
| 1323 |
+
from ._tensorboard_logger import HFSummaryWriter # noqa: F401
|
| 1324 |
+
from ._webhooks_payload import (
|
| 1325 |
+
WebhookPayload, # noqa: F401
|
| 1326 |
+
WebhookPayloadComment, # noqa: F401
|
| 1327 |
+
WebhookPayloadDiscussion, # noqa: F401
|
| 1328 |
+
WebhookPayloadDiscussionChanges, # noqa: F401
|
| 1329 |
+
WebhookPayloadEvent, # noqa: F401
|
| 1330 |
+
WebhookPayloadMovedTo, # noqa: F401
|
| 1331 |
+
WebhookPayloadRepo, # noqa: F401
|
| 1332 |
+
WebhookPayloadUrl, # noqa: F401
|
| 1333 |
+
WebhookPayloadWebhook, # noqa: F401
|
| 1334 |
+
)
|
| 1335 |
+
from ._webhooks_server import (
|
| 1336 |
+
WebhooksServer, # noqa: F401
|
| 1337 |
+
webhook_endpoint, # noqa: F401
|
| 1338 |
+
)
|
| 1339 |
+
from .cli._cli_utils import (
|
| 1340 |
+
check_cli_update, # noqa: F401
|
| 1341 |
+
typer_factory, # noqa: F401
|
| 1342 |
+
)
|
| 1343 |
+
from .community import (
|
| 1344 |
+
Discussion, # noqa: F401
|
| 1345 |
+
DiscussionComment, # noqa: F401
|
| 1346 |
+
DiscussionCommit, # noqa: F401
|
| 1347 |
+
DiscussionEvent, # noqa: F401
|
| 1348 |
+
DiscussionStatusChange, # noqa: F401
|
| 1349 |
+
DiscussionTitleChange, # noqa: F401
|
| 1350 |
+
DiscussionWithDetails, # noqa: F401
|
| 1351 |
+
)
|
| 1352 |
+
from .constants import (
|
| 1353 |
+
CONFIG_NAME, # noqa: F401
|
| 1354 |
+
FLAX_WEIGHTS_NAME, # noqa: F401
|
| 1355 |
+
HUGGINGFACE_CO_URL_HOME, # noqa: F401
|
| 1356 |
+
HUGGINGFACE_CO_URL_TEMPLATE, # noqa: F401
|
| 1357 |
+
PYTORCH_WEIGHTS_NAME, # noqa: F401
|
| 1358 |
+
REPO_TYPE_DATASET, # noqa: F401
|
| 1359 |
+
REPO_TYPE_MODEL, # noqa: F401
|
| 1360 |
+
REPO_TYPE_SPACE, # noqa: F401
|
| 1361 |
+
TF2_WEIGHTS_NAME, # noqa: F401
|
| 1362 |
+
TF_WEIGHTS_NAME, # noqa: F401
|
| 1363 |
+
is_offline_mode, # noqa: F401
|
| 1364 |
+
)
|
| 1365 |
+
from .fastai_utils import (
|
| 1366 |
+
_save_pretrained_fastai, # noqa: F401
|
| 1367 |
+
from_pretrained_fastai, # noqa: F401
|
| 1368 |
+
push_to_hub_fastai, # noqa: F401
|
| 1369 |
+
)
|
| 1370 |
+
from .file_download import (
|
| 1371 |
+
_CACHED_NO_EXIST, # noqa: F401
|
| 1372 |
+
DryRunFileInfo, # noqa: F401
|
| 1373 |
+
HfFileMetadata, # noqa: F401
|
| 1374 |
+
get_hf_file_metadata, # noqa: F401
|
| 1375 |
+
hf_hub_download, # noqa: F401
|
| 1376 |
+
hf_hub_url, # noqa: F401
|
| 1377 |
+
try_to_load_from_cache, # noqa: F401
|
| 1378 |
+
)
|
| 1379 |
+
from .hf_api import (
|
| 1380 |
+
Collection, # noqa: F401
|
| 1381 |
+
CollectionItem, # noqa: F401
|
| 1382 |
+
CommitInfo, # noqa: F401
|
| 1383 |
+
CommitOperation, # noqa: F401
|
| 1384 |
+
CommitOperationAdd, # noqa: F401
|
| 1385 |
+
CommitOperationCopy, # noqa: F401
|
| 1386 |
+
CommitOperationDelete, # noqa: F401
|
| 1387 |
+
DatasetInfo, # noqa: F401
|
| 1388 |
+
DatasetLeaderboardEntry, # noqa: F401
|
| 1389 |
+
GitCommitInfo, # noqa: F401
|
| 1390 |
+
GitRefInfo, # noqa: F401
|
| 1391 |
+
GitRefs, # noqa: F401
|
| 1392 |
+
HfApi, # noqa: F401
|
| 1393 |
+
KernelInfo, # noqa: F401
|
| 1394 |
+
ModelInfo, # noqa: F401
|
| 1395 |
+
Organization, # noqa: F401
|
| 1396 |
+
RepoFile, # noqa: F401
|
| 1397 |
+
RepoFolder, # noqa: F401
|
| 1398 |
+
RepoStorageInfo, # noqa: F401
|
| 1399 |
+
RepoUrl, # noqa: F401
|
| 1400 |
+
SpaceInfo, # noqa: F401
|
| 1401 |
+
SpaceSearchResult, # noqa: F401
|
| 1402 |
+
SpaceTemplate, # noqa: F401
|
| 1403 |
+
User, # noqa: F401
|
| 1404 |
+
UserLikes, # noqa: F401
|
| 1405 |
+
WebhookInfo, # noqa: F401
|
| 1406 |
+
WebhookWatchedItem, # noqa: F401
|
| 1407 |
+
accept_access_request, # noqa: F401
|
| 1408 |
+
add_collection_item, # noqa: F401
|
| 1409 |
+
add_space_secret, # noqa: F401
|
| 1410 |
+
add_space_variable, # noqa: F401
|
| 1411 |
+
auth_check, # noqa: F401
|
| 1412 |
+
batch_bucket_files, # noqa: F401
|
| 1413 |
+
bucket_info, # noqa: F401
|
| 1414 |
+
cancel_access_request, # noqa: F401
|
| 1415 |
+
cancel_job, # noqa: F401
|
| 1416 |
+
change_discussion_status, # noqa: F401
|
| 1417 |
+
comment_discussion, # noqa: F401
|
| 1418 |
+
copy_files, # noqa: F401
|
| 1419 |
+
create_branch, # noqa: F401
|
| 1420 |
+
create_bucket, # noqa: F401
|
| 1421 |
+
create_collection, # noqa: F401
|
| 1422 |
+
create_commit, # noqa: F401
|
| 1423 |
+
create_discussion, # noqa: F401
|
| 1424 |
+
create_inference_endpoint, # noqa: F401
|
| 1425 |
+
create_inference_endpoint_from_catalog, # noqa: F401
|
| 1426 |
+
create_pull_request, # noqa: F401
|
| 1427 |
+
create_repo, # noqa: F401
|
| 1428 |
+
create_scheduled_job, # noqa: F401
|
| 1429 |
+
create_scheduled_uv_job, # noqa: F401
|
| 1430 |
+
create_tag, # noqa: F401
|
| 1431 |
+
create_webhook, # noqa: F401
|
| 1432 |
+
dataset_info, # noqa: F401
|
| 1433 |
+
delete_branch, # noqa: F401
|
| 1434 |
+
delete_bucket, # noqa: F401
|
| 1435 |
+
delete_collection, # noqa: F401
|
| 1436 |
+
delete_collection_item, # noqa: F401
|
| 1437 |
+
delete_file, # noqa: F401
|
| 1438 |
+
delete_folder, # noqa: F401
|
| 1439 |
+
delete_inference_endpoint, # noqa: F401
|
| 1440 |
+
delete_repo, # noqa: F401
|
| 1441 |
+
delete_scheduled_job, # noqa: F401
|
| 1442 |
+
delete_space_secret, # noqa: F401
|
| 1443 |
+
delete_space_storage, # noqa: F401
|
| 1444 |
+
delete_space_variable, # noqa: F401
|
| 1445 |
+
delete_space_volumes, # noqa: F401
|
| 1446 |
+
delete_tag, # noqa: F401
|
| 1447 |
+
delete_webhook, # noqa: F401
|
| 1448 |
+
disable_space_dev_mode, # noqa: F401
|
| 1449 |
+
disable_webhook, # noqa: F401
|
| 1450 |
+
download_bucket_files, # noqa: F401
|
| 1451 |
+
duplicate_repo, # noqa: F401
|
| 1452 |
+
duplicate_space, # noqa: F401
|
| 1453 |
+
edit_discussion_comment, # noqa: F401
|
| 1454 |
+
enable_space_dev_mode, # noqa: F401
|
| 1455 |
+
enable_webhook, # noqa: F401
|
| 1456 |
+
fetch_job_logs, # noqa: F401
|
| 1457 |
+
fetch_job_metrics, # noqa: F401
|
| 1458 |
+
fetch_space_logs, # noqa: F401
|
| 1459 |
+
file_exists, # noqa: F401
|
| 1460 |
+
get_bucket_file_metadata, # noqa: F401
|
| 1461 |
+
get_bucket_paths_info, # noqa: F401
|
| 1462 |
+
get_collection, # noqa: F401
|
| 1463 |
+
get_dataset_leaderboard, # noqa: F401
|
| 1464 |
+
get_dataset_tags, # noqa: F401
|
| 1465 |
+
get_discussion_details, # noqa: F401
|
| 1466 |
+
get_full_repo_name, # noqa: F401
|
| 1467 |
+
get_inference_endpoint, # noqa: F401
|
| 1468 |
+
get_local_safetensors_metadata, # noqa: F401
|
| 1469 |
+
get_model_tags, # noqa: F401
|
| 1470 |
+
get_organization_overview, # noqa: F401
|
| 1471 |
+
get_paths_info, # noqa: F401
|
| 1472 |
+
get_repo_discussions, # noqa: F401
|
| 1473 |
+
get_safetensors_metadata, # noqa: F401
|
| 1474 |
+
get_space_runtime, # noqa: F401
|
| 1475 |
+
get_space_secrets, # noqa: F401
|
| 1476 |
+
get_space_variables, # noqa: F401
|
| 1477 |
+
get_user_overview, # noqa: F401
|
| 1478 |
+
get_webhook, # noqa: F401
|
| 1479 |
+
grant_access, # noqa: F401
|
| 1480 |
+
inspect_job, # noqa: F401
|
| 1481 |
+
inspect_scheduled_job, # noqa: F401
|
| 1482 |
+
kernel_info, # noqa: F401
|
| 1483 |
+
list_accepted_access_requests, # noqa: F401
|
| 1484 |
+
list_bucket_tree, # noqa: F401
|
| 1485 |
+
list_buckets, # noqa: F401
|
| 1486 |
+
list_collections, # noqa: F401
|
| 1487 |
+
list_daily_papers, # noqa: F401
|
| 1488 |
+
list_dataset_parquet_files, # noqa: F401
|
| 1489 |
+
list_datasets, # noqa: F401
|
| 1490 |
+
list_inference_catalog, # noqa: F401
|
| 1491 |
+
list_inference_endpoints, # noqa: F401
|
| 1492 |
+
list_jobs, # noqa: F401
|
| 1493 |
+
list_jobs_hardware, # noqa: F401
|
| 1494 |
+
list_lfs_files, # noqa: F401
|
| 1495 |
+
list_liked_repos, # noqa: F401
|
| 1496 |
+
list_models, # noqa: F401
|
| 1497 |
+
list_organization_followers, # noqa: F401
|
| 1498 |
+
list_organization_members, # noqa: F401
|
| 1499 |
+
list_papers, # noqa: F401
|
| 1500 |
+
list_pending_access_requests, # noqa: F401
|
| 1501 |
+
list_rejected_access_requests, # noqa: F401
|
| 1502 |
+
list_repo_commits, # noqa: F401
|
| 1503 |
+
list_repo_files, # noqa: F401
|
| 1504 |
+
list_repo_likers, # noqa: F401
|
| 1505 |
+
list_repo_refs, # noqa: F401
|
| 1506 |
+
list_repo_tree, # noqa: F401
|
| 1507 |
+
list_space_templates, # noqa: F401
|
| 1508 |
+
list_spaces, # noqa: F401
|
| 1509 |
+
list_spaces_hardware, # noqa: F401
|
| 1510 |
+
list_user_followers, # noqa: F401
|
| 1511 |
+
list_user_following, # noqa: F401
|
| 1512 |
+
list_user_repos, # noqa: F401
|
| 1513 |
+
list_webhooks, # noqa: F401
|
| 1514 |
+
merge_pull_request, # noqa: F401
|
| 1515 |
+
model_info, # noqa: F401
|
| 1516 |
+
move_bucket, # noqa: F401
|
| 1517 |
+
move_repo, # noqa: F401
|
| 1518 |
+
paper_info, # noqa: F401
|
| 1519 |
+
parse_local_safetensors_file_metadata, # noqa: F401
|
| 1520 |
+
parse_safetensors_file_metadata, # noqa: F401
|
| 1521 |
+
pause_inference_endpoint, # noqa: F401
|
| 1522 |
+
pause_space, # noqa: F401
|
| 1523 |
+
permanently_delete_lfs_files, # noqa: F401
|
| 1524 |
+
preupload_lfs_files, # noqa: F401
|
| 1525 |
+
read_paper, # noqa: F401
|
| 1526 |
+
reject_access_request, # noqa: F401
|
| 1527 |
+
rename_discussion, # noqa: F401
|
| 1528 |
+
repo_exists, # noqa: F401
|
| 1529 |
+
repo_info, # noqa: F401
|
| 1530 |
+
repo_type_and_id_from_hf_id, # noqa: F401
|
| 1531 |
+
request_space_hardware, # noqa: F401
|
| 1532 |
+
request_space_storage, # noqa: F401
|
| 1533 |
+
resolve_revision, # noqa: F401
|
| 1534 |
+
restart_space, # noqa: F401
|
| 1535 |
+
resume_inference_endpoint, # noqa: F401
|
| 1536 |
+
resume_scheduled_job, # noqa: F401
|
| 1537 |
+
revision_exists, # noqa: F401
|
| 1538 |
+
run_as_future, # noqa: F401
|
| 1539 |
+
run_job, # noqa: F401
|
| 1540 |
+
run_uv_job, # noqa: F401
|
| 1541 |
+
scale_to_zero_inference_endpoint, # noqa: F401
|
| 1542 |
+
search_spaces, # noqa: F401
|
| 1543 |
+
set_space_sleep_time, # noqa: F401
|
| 1544 |
+
set_space_volumes, # noqa: F401
|
| 1545 |
+
space_info, # noqa: F401
|
| 1546 |
+
super_squash_history, # noqa: F401
|
| 1547 |
+
suspend_scheduled_job, # noqa: F401
|
| 1548 |
+
sync_bucket, # noqa: F401
|
| 1549 |
+
sync_job_volume, # noqa: F401
|
| 1550 |
+
trigger_scheduled_job, # noqa: F401
|
| 1551 |
+
unlike, # noqa: F401
|
| 1552 |
+
update_collection_item, # noqa: F401
|
| 1553 |
+
update_collection_metadata, # noqa: F401
|
| 1554 |
+
update_collection_resource_group, # noqa: F401
|
| 1555 |
+
update_inference_endpoint, # noqa: F401
|
| 1556 |
+
update_job_labels, # noqa: F401
|
| 1557 |
+
update_repo_settings, # noqa: F401
|
| 1558 |
+
update_scheduled_job_labels, # noqa: F401
|
| 1559 |
+
update_webhook, # noqa: F401
|
| 1560 |
+
upload_file, # noqa: F401
|
| 1561 |
+
upload_folder, # noqa: F401
|
| 1562 |
+
upload_large_folder, # noqa: F401
|
| 1563 |
+
verify_repo_checksums, # noqa: F401
|
| 1564 |
+
wait_for_job, # noqa: F401
|
| 1565 |
+
wait_for_space, # noqa: F401
|
| 1566 |
+
whoami, # noqa: F401
|
| 1567 |
+
)
|
| 1568 |
+
from .hf_file_system import (
|
| 1569 |
+
HfFileSystem, # noqa: F401
|
| 1570 |
+
HfFileSystemFile, # noqa: F401
|
| 1571 |
+
HfFileSystemResolvedPath, # noqa: F401
|
| 1572 |
+
HfFileSystemStreamFile, # noqa: F401
|
| 1573 |
+
hffs, # noqa: F401
|
| 1574 |
+
)
|
| 1575 |
+
from .hub_mixin import (
|
| 1576 |
+
ModelHubMixin, # noqa: F401
|
| 1577 |
+
PyTorchModelHubMixin, # noqa: F401
|
| 1578 |
+
)
|
| 1579 |
+
from .inference._client import (
|
| 1580 |
+
InferenceClient, # noqa: F401
|
| 1581 |
+
InferenceTimeoutError, # noqa: F401
|
| 1582 |
+
)
|
| 1583 |
+
from .inference._generated._async_client import AsyncInferenceClient # noqa: F401
|
| 1584 |
+
from .inference._generated.types import (
|
| 1585 |
+
AudioClassificationInput, # noqa: F401
|
| 1586 |
+
AudioClassificationOutputElement, # noqa: F401
|
| 1587 |
+
AudioClassificationOutputTransform, # noqa: F401
|
| 1588 |
+
AudioClassificationParameters, # noqa: F401
|
| 1589 |
+
AudioToAudioInput, # noqa: F401
|
| 1590 |
+
AudioToAudioOutputElement, # noqa: F401
|
| 1591 |
+
AutomaticSpeechRecognitionEarlyStoppingEnum, # noqa: F401
|
| 1592 |
+
AutomaticSpeechRecognitionGenerationParameters, # noqa: F401
|
| 1593 |
+
AutomaticSpeechRecognitionInput, # noqa: F401
|
| 1594 |
+
AutomaticSpeechRecognitionOutput, # noqa: F401
|
| 1595 |
+
AutomaticSpeechRecognitionOutputChunk, # noqa: F401
|
| 1596 |
+
AutomaticSpeechRecognitionParameters, # noqa: F401
|
| 1597 |
+
ChatCompletionInput, # noqa: F401
|
| 1598 |
+
ChatCompletionInputFunctionDefinition, # noqa: F401
|
| 1599 |
+
ChatCompletionInputFunctionName, # noqa: F401
|
| 1600 |
+
ChatCompletionInputGrammarType, # noqa: F401
|
| 1601 |
+
ChatCompletionInputJSONSchema, # noqa: F401
|
| 1602 |
+
ChatCompletionInputMessage, # noqa: F401
|
| 1603 |
+
ChatCompletionInputMessageChunk, # noqa: F401
|
| 1604 |
+
ChatCompletionInputMessageChunkType, # noqa: F401
|
| 1605 |
+
ChatCompletionInputResponseFormatJSONObject, # noqa: F401
|
| 1606 |
+
ChatCompletionInputResponseFormatJSONSchema, # noqa: F401
|
| 1607 |
+
ChatCompletionInputResponseFormatText, # noqa: F401
|
| 1608 |
+
ChatCompletionInputStreamOptions, # noqa: F401
|
| 1609 |
+
ChatCompletionInputTool, # noqa: F401
|
| 1610 |
+
ChatCompletionInputToolCall, # noqa: F401
|
| 1611 |
+
ChatCompletionInputToolChoiceClass, # noqa: F401
|
| 1612 |
+
ChatCompletionInputToolChoiceEnum, # noqa: F401
|
| 1613 |
+
ChatCompletionInputURL, # noqa: F401
|
| 1614 |
+
ChatCompletionOutput, # noqa: F401
|
| 1615 |
+
ChatCompletionOutputComplete, # noqa: F401
|
| 1616 |
+
ChatCompletionOutputFunctionDefinition, # noqa: F401
|
| 1617 |
+
ChatCompletionOutputLogprob, # noqa: F401
|
| 1618 |
+
ChatCompletionOutputLogprobs, # noqa: F401
|
| 1619 |
+
ChatCompletionOutputMessage, # noqa: F401
|
| 1620 |
+
ChatCompletionOutputToolCall, # noqa: F401
|
| 1621 |
+
ChatCompletionOutputTopLogprob, # noqa: F401
|
| 1622 |
+
ChatCompletionOutputUsage, # noqa: F401
|
| 1623 |
+
ChatCompletionStreamOutput, # noqa: F401
|
| 1624 |
+
ChatCompletionStreamOutputChoice, # noqa: F401
|
| 1625 |
+
ChatCompletionStreamOutputDelta, # noqa: F401
|
| 1626 |
+
ChatCompletionStreamOutputDeltaToolCall, # noqa: F401
|
| 1627 |
+
ChatCompletionStreamOutputFunction, # noqa: F401
|
| 1628 |
+
ChatCompletionStreamOutputLogprob, # noqa: F401
|
| 1629 |
+
ChatCompletionStreamOutputLogprobs, # noqa: F401
|
| 1630 |
+
ChatCompletionStreamOutputTopLogprob, # noqa: F401
|
| 1631 |
+
ChatCompletionStreamOutputUsage, # noqa: F401
|
| 1632 |
+
DepthEstimationInput, # noqa: F401
|
| 1633 |
+
DepthEstimationOutput, # noqa: F401
|
| 1634 |
+
DocumentQuestionAnsweringInput, # noqa: F401
|
| 1635 |
+
DocumentQuestionAnsweringInputData, # noqa: F401
|
| 1636 |
+
DocumentQuestionAnsweringOutputElement, # noqa: F401
|
| 1637 |
+
DocumentQuestionAnsweringParameters, # noqa: F401
|
| 1638 |
+
FeatureExtractionInput, # noqa: F401
|
| 1639 |
+
FeatureExtractionInputTruncationDirection, # noqa: F401
|
| 1640 |
+
FillMaskInput, # noqa: F401
|
| 1641 |
+
FillMaskOutputElement, # noqa: F401
|
| 1642 |
+
FillMaskParameters, # noqa: F401
|
| 1643 |
+
ImageClassificationInput, # noqa: F401
|
| 1644 |
+
ImageClassificationOutputElement, # noqa: F401
|
| 1645 |
+
ImageClassificationOutputTransform, # noqa: F401
|
| 1646 |
+
ImageClassificationParameters, # noqa: F401
|
| 1647 |
+
ImageSegmentationInput, # noqa: F401
|
| 1648 |
+
ImageSegmentationOutputElement, # noqa: F401
|
| 1649 |
+
ImageSegmentationParameters, # noqa: F401
|
| 1650 |
+
ImageSegmentationSubtask, # noqa: F401
|
| 1651 |
+
ImageTextToImageInput, # noqa: F401
|
| 1652 |
+
ImageTextToImageOutput, # noqa: F401
|
| 1653 |
+
ImageTextToImageParameters, # noqa: F401
|
| 1654 |
+
ImageTextToImageTargetSize, # noqa: F401
|
| 1655 |
+
ImageTextToVideoInput, # noqa: F401
|
| 1656 |
+
ImageTextToVideoOutput, # noqa: F401
|
| 1657 |
+
ImageTextToVideoParameters, # noqa: F401
|
| 1658 |
+
ImageTextToVideoTargetSize, # noqa: F401
|
| 1659 |
+
ImageToImageInput, # noqa: F401
|
| 1660 |
+
ImageToImageOutput, # noqa: F401
|
| 1661 |
+
ImageToImageParameters, # noqa: F401
|
| 1662 |
+
ImageToImageTargetSize, # noqa: F401
|
| 1663 |
+
ImageToTextEarlyStoppingEnum, # noqa: F401
|
| 1664 |
+
ImageToTextGenerationParameters, # noqa: F401
|
| 1665 |
+
ImageToTextInput, # noqa: F401
|
| 1666 |
+
ImageToTextOutput, # noqa: F401
|
| 1667 |
+
ImageToTextParameters, # noqa: F401
|
| 1668 |
+
ImageToVideoInput, # noqa: F401
|
| 1669 |
+
ImageToVideoOutput, # noqa: F401
|
| 1670 |
+
ImageToVideoParameters, # noqa: F401
|
| 1671 |
+
ImageToVideoTargetSize, # noqa: F401
|
| 1672 |
+
ObjectDetectionBoundingBox, # noqa: F401
|
| 1673 |
+
ObjectDetectionInput, # noqa: F401
|
| 1674 |
+
ObjectDetectionOutputElement, # noqa: F401
|
| 1675 |
+
ObjectDetectionParameters, # noqa: F401
|
| 1676 |
+
Padding, # noqa: F401
|
| 1677 |
+
QuestionAnsweringInput, # noqa: F401
|
| 1678 |
+
QuestionAnsweringInputData, # noqa: F401
|
| 1679 |
+
QuestionAnsweringOutputElement, # noqa: F401
|
| 1680 |
+
QuestionAnsweringParameters, # noqa: F401
|
| 1681 |
+
SentenceSimilarityInput, # noqa: F401
|
| 1682 |
+
SentenceSimilarityInputData, # noqa: F401
|
| 1683 |
+
SummarizationInput, # noqa: F401
|
| 1684 |
+
SummarizationOutput, # noqa: F401
|
| 1685 |
+
SummarizationParameters, # noqa: F401
|
| 1686 |
+
SummarizationTruncationStrategy, # noqa: F401
|
| 1687 |
+
TableQuestionAnsweringInput, # noqa: F401
|
| 1688 |
+
TableQuestionAnsweringInputData, # noqa: F401
|
| 1689 |
+
TableQuestionAnsweringOutputElement, # noqa: F401
|
| 1690 |
+
TableQuestionAnsweringParameters, # noqa: F401
|
| 1691 |
+
Text2TextGenerationInput, # noqa: F401
|
| 1692 |
+
Text2TextGenerationOutput, # noqa: F401
|
| 1693 |
+
Text2TextGenerationParameters, # noqa: F401
|
| 1694 |
+
Text2TextGenerationTruncationStrategy, # noqa: F401
|
| 1695 |
+
TextClassificationInput, # noqa: F401
|
| 1696 |
+
TextClassificationOutputElement, # noqa: F401
|
| 1697 |
+
TextClassificationOutputTransform, # noqa: F401
|
| 1698 |
+
TextClassificationParameters, # noqa: F401
|
| 1699 |
+
TextGenerationInput, # noqa: F401
|
| 1700 |
+
TextGenerationInputGenerateParameters, # noqa: F401
|
| 1701 |
+
TextGenerationInputGrammarType, # noqa: F401
|
| 1702 |
+
TextGenerationOutput, # noqa: F401
|
| 1703 |
+
TextGenerationOutputBestOfSequence, # noqa: F401
|
| 1704 |
+
TextGenerationOutputDetails, # noqa: F401
|
| 1705 |
+
TextGenerationOutputFinishReason, # noqa: F401
|
| 1706 |
+
TextGenerationOutputPrefillToken, # noqa: F401
|
| 1707 |
+
TextGenerationOutputToken, # noqa: F401
|
| 1708 |
+
TextGenerationStreamOutput, # noqa: F401
|
| 1709 |
+
TextGenerationStreamOutputStreamDetails, # noqa: F401
|
| 1710 |
+
TextGenerationStreamOutputToken, # noqa: F401
|
| 1711 |
+
TextToAudioEarlyStoppingEnum, # noqa: F401
|
| 1712 |
+
TextToAudioGenerationParameters, # noqa: F401
|
| 1713 |
+
TextToAudioInput, # noqa: F401
|
| 1714 |
+
TextToAudioOutput, # noqa: F401
|
| 1715 |
+
TextToAudioParameters, # noqa: F401
|
| 1716 |
+
TextToImageInput, # noqa: F401
|
| 1717 |
+
TextToImageOutput, # noqa: F401
|
| 1718 |
+
TextToImageParameters, # noqa: F401
|
| 1719 |
+
TextToSpeechEarlyStoppingEnum, # noqa: F401
|
| 1720 |
+
TextToSpeechGenerationParameters, # noqa: F401
|
| 1721 |
+
TextToSpeechInput, # noqa: F401
|
| 1722 |
+
TextToSpeechOutput, # noqa: F401
|
| 1723 |
+
TextToSpeechParameters, # noqa: F401
|
| 1724 |
+
TextToVideoInput, # noqa: F401
|
| 1725 |
+
TextToVideoOutput, # noqa: F401
|
| 1726 |
+
TextToVideoParameters, # noqa: F401
|
| 1727 |
+
TokenClassificationAggregationStrategy, # noqa: F401
|
| 1728 |
+
TokenClassificationInput, # noqa: F401
|
| 1729 |
+
TokenClassificationOutputElement, # noqa: F401
|
| 1730 |
+
TokenClassificationParameters, # noqa: F401
|
| 1731 |
+
TranslationInput, # noqa: F401
|
| 1732 |
+
TranslationOutput, # noqa: F401
|
| 1733 |
+
TranslationParameters, # noqa: F401
|
| 1734 |
+
TranslationTruncationStrategy, # noqa: F401
|
| 1735 |
+
TypeEnum, # noqa: F401
|
| 1736 |
+
VideoClassificationInput, # noqa: F401
|
| 1737 |
+
VideoClassificationOutputElement, # noqa: F401
|
| 1738 |
+
VideoClassificationOutputTransform, # noqa: F401
|
| 1739 |
+
VideoClassificationParameters, # noqa: F401
|
| 1740 |
+
VisualQuestionAnsweringInput, # noqa: F401
|
| 1741 |
+
VisualQuestionAnsweringInputData, # noqa: F401
|
| 1742 |
+
VisualQuestionAnsweringOutputElement, # noqa: F401
|
| 1743 |
+
VisualQuestionAnsweringParameters, # noqa: F401
|
| 1744 |
+
ZeroShotClassificationInput, # noqa: F401
|
| 1745 |
+
ZeroShotClassificationOutputElement, # noqa: F401
|
| 1746 |
+
ZeroShotClassificationParameters, # noqa: F401
|
| 1747 |
+
ZeroShotImageClassificationInput, # noqa: F401
|
| 1748 |
+
ZeroShotImageClassificationOutputElement, # noqa: F401
|
| 1749 |
+
ZeroShotImageClassificationParameters, # noqa: F401
|
| 1750 |
+
ZeroShotObjectDetectionBoundingBox, # noqa: F401
|
| 1751 |
+
ZeroShotObjectDetectionInput, # noqa: F401
|
| 1752 |
+
ZeroShotObjectDetectionOutputElement, # noqa: F401
|
| 1753 |
+
ZeroShotObjectDetectionParameters, # noqa: F401
|
| 1754 |
+
)
|
| 1755 |
+
from .inference._mcp.agent import Agent # noqa: F401
|
| 1756 |
+
from .inference._mcp.mcp_client import MCPClient # noqa: F401
|
| 1757 |
+
from .repocard import (
|
| 1758 |
+
DatasetCard, # noqa: F401
|
| 1759 |
+
ModelCard, # noqa: F401
|
| 1760 |
+
RepoCard, # noqa: F401
|
| 1761 |
+
SpaceCard, # noqa: F401
|
| 1762 |
+
metadata_eval_result, # noqa: F401
|
| 1763 |
+
metadata_load, # noqa: F401
|
| 1764 |
+
metadata_save, # noqa: F401
|
| 1765 |
+
metadata_update, # noqa: F401
|
| 1766 |
+
)
|
| 1767 |
+
from .repocard_data import (
|
| 1768 |
+
CardData, # noqa: F401
|
| 1769 |
+
DatasetCardData, # noqa: F401
|
| 1770 |
+
EvalResult, # noqa: F401
|
| 1771 |
+
ModelCardData, # noqa: F401
|
| 1772 |
+
SpaceCardData, # noqa: F401
|
| 1773 |
+
)
|
| 1774 |
+
from .serialization import (
|
| 1775 |
+
StateDictSplit, # noqa: F401
|
| 1776 |
+
get_torch_storage_id, # noqa: F401
|
| 1777 |
+
get_torch_storage_size, # noqa: F401
|
| 1778 |
+
load_state_dict_from_file, # noqa: F401
|
| 1779 |
+
load_torch_model, # noqa: F401
|
| 1780 |
+
save_torch_model, # noqa: F401
|
| 1781 |
+
save_torch_state_dict, # noqa: F401
|
| 1782 |
+
split_state_dict_into_shards_factory, # noqa: F401
|
| 1783 |
+
split_torch_state_dict_into_shards, # noqa: F401
|
| 1784 |
+
)
|
| 1785 |
+
from .serialization._dduf import (
|
| 1786 |
+
DDUFEntry, # noqa: F401
|
| 1787 |
+
export_entries_as_dduf, # noqa: F401
|
| 1788 |
+
export_folder_as_dduf, # noqa: F401
|
| 1789 |
+
read_dduf_file, # noqa: F401
|
| 1790 |
+
)
|
| 1791 |
+
from .utils import (
|
| 1792 |
+
ASYNC_CLIENT_FACTORY_T, # noqa: F401
|
| 1793 |
+
CLIENT_FACTORY_T, # noqa: F401
|
| 1794 |
+
CachedFileInfo, # noqa: F401
|
| 1795 |
+
CachedIncompleteFileInfo, # noqa: F401
|
| 1796 |
+
CachedRepoInfo, # noqa: F401
|
| 1797 |
+
CachedRevisionInfo, # noqa: F401
|
| 1798 |
+
CacheNotFound, # noqa: F401
|
| 1799 |
+
CorruptedCacheException, # noqa: F401
|
| 1800 |
+
DeleteCacheStrategy, # noqa: F401
|
| 1801 |
+
HFCacheInfo, # noqa: F401
|
| 1802 |
+
HfUri, # noqa: F401
|
| 1803 |
+
cached_assets_path, # noqa: F401
|
| 1804 |
+
close_session, # noqa: F401
|
| 1805 |
+
dump_environment_info, # noqa: F401
|
| 1806 |
+
get_async_session, # noqa: F401
|
| 1807 |
+
get_session, # noqa: F401
|
| 1808 |
+
get_token, # noqa: F401
|
| 1809 |
+
hf_raise_for_status, # noqa: F401
|
| 1810 |
+
logging, # noqa: F401
|
| 1811 |
+
parse_hf_uri, # noqa: F401
|
| 1812 |
+
scan_cache_dir, # noqa: F401
|
| 1813 |
+
set_async_client_factory, # noqa: F401
|
| 1814 |
+
set_client_factory, # noqa: F401
|
| 1815 |
+
)
|