Compare commits
21 Commits
master
...
tomchristi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5234f3ac | ||
|
|
6c483c9e07 | ||
|
|
4e1c691636 | ||
|
|
2d2eab7fa8 | ||
|
|
92d92956a5 | ||
|
|
5560472f7d | ||
|
|
670a156b56 | ||
|
|
218db9b6db | ||
|
|
e49cc07bc9 | ||
|
|
4321f008ed | ||
|
|
8bff380cec | ||
|
|
2f220ec3f5 | ||
|
|
3933a88c2b | ||
|
|
cc72d348f8 | ||
|
|
15ffaf9e1a | ||
|
|
c1a88bc873 | ||
|
|
83fbd71063 | ||
|
|
b413005684 | ||
|
|
d861e9c179 | ||
|
|
9bcb36a634 | ||
|
|
3769c569ed |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -15,9 +15,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v4"
|
- uses: "actions/checkout@v4"
|
||||||
- uses: "actions/setup-python@v6"
|
- uses: "actions/setup-python@v5"
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.8
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: "scripts/install"
|
run: "scripts/install"
|
||||||
- name: "Build package & docs"
|
- name: "Build package & docs"
|
||||||
|
|||||||
4
.github/workflows/test-suite.yml
vendored
4
.github/workflows/test-suite.yml
vendored
@ -14,11 +14,11 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v4"
|
- uses: "actions/checkout@v4"
|
||||||
- uses: "actions/setup-python@v6"
|
- uses: "actions/setup-python@v5"
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python-version }}"
|
python-version: "${{ matrix.python-version }}"
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
|||||||
37
CHANGELOG.md
37
CHANGELOG.md
@ -4,47 +4,34 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [UNRELEASED]
|
## 0.28.0 (...)
|
||||||
|
|
||||||
### Removed
|
TODO... writeup `truststore` switch & 3.10+ requirement.
|
||||||
|
|
||||||
* Drop support for Python 3.8
|
The 0.28 release includes a limited set of backwards incompatible changes.
|
||||||
|
|
||||||
### Added
|
**Backwards incompatible changes**:
|
||||||
|
|
||||||
* Expose `FunctionAuth` from the public API. (#3699)
|
SSL configuration has been significantly simplified.
|
||||||
|
|
||||||
## 0.28.1 (6th December, 2024)
|
* The `verify` argument no longer accepts string arguments. Explicitly specified certificate stores can still be enabled through the SSL configuration API.
|
||||||
|
* The `cert` argument has now been removed. Client side certificates can still be enabled through the SSL configuration API.
|
||||||
|
* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer used. These environment variables can be enabled manually although should be obsoleted by our switch to `truststore`.
|
||||||
|
|
||||||
* Fix SSL case where `verify=False` together with client side certificates.
|
For users of the standard `verify=True` or `verify=False` cases this should require no changes.
|
||||||
|
|
||||||
## 0.28.0 (28th November, 2024)
|
|
||||||
|
|
||||||
Be aware that the default *JSON request bodies now use a more compact representation*. This is generally considered a prefered style, tho may require updates to test suites.
|
For information on configuring more complex SSL cases, please see the [SSL documentation](docs/advanced/ssl.md).
|
||||||
|
|
||||||
The 0.28 release includes a limited set of deprecations...
|
|
||||||
|
|
||||||
**Deprecations**:
|
|
||||||
|
|
||||||
We are working towards a simplified SSL configuration API.
|
|
||||||
|
|
||||||
*For users of the standard `verify=True` or `verify=False` cases, or `verify=<ssl_context>` case this should require no changes. The following cases have been deprecated...*
|
|
||||||
|
|
||||||
* The `verify` argument as a string argument is now deprecated and will raise warnings.
|
|
||||||
* The `cert` argument is now deprecated and will raise warnings.
|
|
||||||
|
|
||||||
Our revised [SSL documentation](docs/advanced/ssl.md) covers how to implement the same behaviour with a more constrained API.
|
|
||||||
|
|
||||||
**The following changes are also included**:
|
**The following changes are also included**:
|
||||||
|
|
||||||
|
* The undocumented `URL.raw` property has now been deprecated, and will raise warnings.
|
||||||
* The deprecated `proxies` argument has now been removed.
|
* The deprecated `proxies` argument has now been removed.
|
||||||
* The deprecated `app` argument has now been removed.
|
* The deprecated `app` argument has now been removed.
|
||||||
* JSON request bodies use a compact representation. (#3363)
|
* Ensure JSON request bodies are compact. (#3363)
|
||||||
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
|
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
|
||||||
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
|
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
|
||||||
* Treat `socks5h` as a valid proxy scheme. (#3178)
|
* Treat `socks5h` as a valid proxy scheme. (#3178)
|
||||||
* Cleanup `Request()` method signature in line with `client.request()` and `httpx.request()`. (#3378)
|
* Cleanup `Request()` method signature in line with `client.request()` and `httpx.request()`. (#3378)
|
||||||
* Bugfix: When passing `params={}`, always strictly update rather than merge with an existing querystring. (#3364)
|
|
||||||
|
|
||||||
## 0.27.2 (27th August, 2024)
|
## 0.27.2 (27th August, 2024)
|
||||||
|
|
||||||
|
|||||||
@ -101,7 +101,7 @@ Or, to include the optional HTTP/2 support, use:
|
|||||||
$ pip install httpx[http2]
|
$ pip install httpx[http2]
|
||||||
```
|
```
|
||||||
|
|
||||||
HTTPX requires Python 3.9+.
|
HTTPX requires Python 3.10+.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ The HTTPX project relies on these excellent libraries:
|
|||||||
|
|
||||||
* `httpcore` - The underlying transport implementation for `httpx`.
|
* `httpcore` - The underlying transport implementation for `httpx`.
|
||||||
* `h11` - HTTP/1.1 support.
|
* `h11` - HTTP/1.1 support.
|
||||||
* `certifi` - SSL certificates.
|
* `truststore` - System SSL certificates.
|
||||||
* `idna` - Internationalized domain name support.
|
* `idna` - Internationalized domain name support.
|
||||||
* `sniffio` - Async library autodetection.
|
* `sniffio` - Async library autodetection.
|
||||||
|
|
||||||
|
|||||||
@ -270,9 +270,8 @@ multipart file encoding is available by passing a dictionary with the
|
|||||||
name of the payloads as keys and either tuple of elements or a file-like object or a string as values.
|
name of the payloads as keys and either tuple of elements or a file-like object or a string as values.
|
||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> with open('report.xls', 'rb') as report_file:
|
>>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
|
||||||
... files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}
|
>>> r = httpx.post("https://httpbin.org/post", files=files)
|
||||||
... r = httpx.post("https://httpbin.org/post", files=files)
|
|
||||||
>>> print(r.text)
|
>>> print(r.text)
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
@ -319,10 +318,7 @@ To do that, pass a list of `(field, <file>)` items instead of a dictionary, allo
|
|||||||
For instance this request sends 2 files, `foo.png` and `bar.png` in one request on the `images` form field:
|
For instance this request sends 2 files, `foo.png` and `bar.png` in one request on the `images` form field:
|
||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> with open('foo.png', 'rb') as foo_file, open('bar.png', 'rb') as bar_file:
|
>>> files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
|
||||||
... files = [
|
('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
|
||||||
... ('images', ('foo.png', foo_file, 'image/png')),
|
>>> r = httpx.post("https://httpbin.org/post", files=files)
|
||||||
... ('images', ('bar.png', bar_file, 'image/png')),
|
|
||||||
... ]
|
|
||||||
... r = httpx.post("https://httpbin.org/post", files=files)
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,26 +1,28 @@
|
|||||||
When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it uses a bundle of SSL certificates (a.k.a. CA bundle) delivered by a trusted certificate authority (CA).
|
When making a request over HTTPS we need to verify the identity of the requested host. We rely on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser.
|
||||||
|
|
||||||
### Enabling and disabling verification
|
### SSL verification
|
||||||
|
|
||||||
By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...
|
By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...
|
||||||
|
|
||||||
```pycon
|
```python
|
||||||
>>> httpx.get("https://expired.badssl.com/")
|
>>> httpx.get("https://expired.badssl.com/")
|
||||||
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
|
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
|
||||||
```
|
```
|
||||||
|
|
||||||
You can disable SSL verification completely and allow insecure requests...
|
If you're confident that you want to visit a site with an invalid certificate you can disable SSL verification completely...
|
||||||
|
|
||||||
```pycon
|
```python
|
||||||
>>> httpx.get("https://expired.badssl.com/", verify=False)
|
>>> httpx.get("https://expired.badssl.com/", verify=False)
|
||||||
<Response [200 OK]>
|
<Response [200 OK]>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuring client instances
|
### Custom SSL configurations
|
||||||
|
|
||||||
If you're using a `Client()` instance you should pass any `verify=<...>` configuration when instantiating the client.
|
If you're using a `Client()` instance you can pass the `verify=<...>` configuration when instantiating the client.
|
||||||
|
|
||||||
By default the [certifi CA bundle](https://certifiio.readthedocs.io/en/latest/) is used for SSL verification.
|
```python
|
||||||
|
>>> client = httpx.Client(verify=True)
|
||||||
|
```
|
||||||
|
|
||||||
For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...
|
For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...
|
||||||
|
|
||||||
@ -28,35 +30,13 @@ For more complex configurations you can pass an [SSL Context](https://docs.pytho
|
|||||||
import certifi
|
import certifi
|
||||||
import httpx
|
import httpx
|
||||||
import ssl
|
import ssl
|
||||||
|
import certifi
|
||||||
|
|
||||||
# This SSL context is equivalent to the default `verify=True`.
|
# Use certifi for certificate validation, rather than the system truststore.
|
||||||
ctx = ssl.create_default_context(cafile=certifi.where())
|
ctx = ssl.create_default_context(cafile=certifi.where())
|
||||||
client = httpx.Client(verify=ctx)
|
client = httpx.Client(verify=ctx)
|
||||||
```
|
```
|
||||||
|
|
||||||
Using [the `truststore` package](https://truststore.readthedocs.io/) to support system certificate stores...
|
|
||||||
|
|
||||||
```python
|
|
||||||
import ssl
|
|
||||||
import truststore
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
# Use system certificate stores.
|
|
||||||
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
||||||
client = httpx.Client(verify=ctx)
|
|
||||||
```
|
|
||||||
|
|
||||||
Loding an alternative certificate verification store using [the standard SSL context API](https://docs.python.org/3/library/ssl.html)...
|
|
||||||
|
|
||||||
```python
|
|
||||||
import httpx
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
# Use an explicitly configured certificate store.
|
|
||||||
ctx = ssl.create_default_context(cafile="path/to/certs.pem") # Either cafile or capath.
|
|
||||||
client = httpx.Client(verify=ctx)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Client side certificates
|
### Client side certificates
|
||||||
|
|
||||||
Client side certificates allow a remote server to verify the client. They tend to be used within private organizations to authenticate requests to remote servers.
|
Client side certificates allow a remote server to verify the client. They tend to be used within private organizations to authenticate requests to remote servers.
|
||||||
@ -71,11 +51,23 @@ client = httpx.Client(verify=ctx)
|
|||||||
|
|
||||||
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
|
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
|
||||||
|
|
||||||
`httpx` does respect the `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables by default. For details, refer to [the section on the environment variables page](../environment_variables.md#ssl_cert_file).
|
Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html).
|
||||||
|
|
||||||
|
These environment variables shouldn't be necessary since they're obsoleted by `truststore`. They can be enabled if required like so...
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured.
|
||||||
|
# Otherwise default to certifi.
|
||||||
|
ctx = ssl.create_default_context(
|
||||||
|
cafile=os.environ.get("SSL_CERT_FILE", certifi.where()),
|
||||||
|
capath=os.environ.get("SSL_CERT_DIR"),
|
||||||
|
)
|
||||||
|
client = httpx.Client(verify=ctx)
|
||||||
|
```
|
||||||
|
|
||||||
### Making HTTPS requests to a local server
|
### Making HTTPS requests to a local server
|
||||||
|
|
||||||
When making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.
|
When making requests to local servers such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.
|
||||||
|
|
||||||
If you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it...
|
If you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it...
|
||||||
|
|
||||||
|
|||||||
15
docs/api.md
15
docs/api.md
@ -159,18 +159,3 @@ what gets sent over the wire.*
|
|||||||
* `def delete(name, [domain], [path])`
|
* `def delete(name, [domain], [path])`
|
||||||
* `def clear([domain], [path])`
|
* `def clear([domain], [path])`
|
||||||
* *Standard mutable mapping interface*
|
* *Standard mutable mapping interface*
|
||||||
|
|
||||||
## `Proxy`
|
|
||||||
|
|
||||||
*A configuration of the proxy server.*
|
|
||||||
|
|
||||||
```pycon
|
|
||||||
>>> proxy = Proxy("http://proxy.example.com:8030")
|
|
||||||
>>> client = Client(proxy=proxy)
|
|
||||||
```
|
|
||||||
|
|
||||||
* `def __init__(url, [ssl_context], [auth], [headers])`
|
|
||||||
* `.url` - **URL**
|
|
||||||
* `.auth` - **tuple[str, str]**
|
|
||||||
* `.headers` - **Headers**
|
|
||||||
* `.ssl_context` - **SSLContext**
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ To make asynchronous requests, you'll need an `AsyncClient`.
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
Use [IPython](https://ipython.readthedocs.io/en/stable/) or Python 3.9+ with `python -m asyncio` to try this code interactively, as they support executing `async`/`await` expressions in the console.
|
Use [IPython](https://ipython.readthedocs.io/en/stable/) or Python 3.8+ with `python -m asyncio` to try this code interactively, as they support executing `async`/`await` expressions in the console.
|
||||||
|
|
||||||
## API Differences
|
## API Differences
|
||||||
|
|
||||||
|
|||||||
@ -226,7 +226,3 @@ For both query params (`params=`) and form data (`data=`), `requests` supports s
|
|||||||
In HTTPX, event hooks may access properties of requests and responses, but event hook callbacks cannot mutate the original request/response.
|
In HTTPX, event hooks may access properties of requests and responses, but event hook callbacks cannot mutate the original request/response.
|
||||||
|
|
||||||
If you are looking for more control, consider checking out [Custom Transports](advanced/transports.md#custom-transports).
|
If you are looking for more control, consider checking out [Custom Transports](advanced/transports.md#custom-transports).
|
||||||
|
|
||||||
## Exceptions and Errors
|
|
||||||
|
|
||||||
`requests` exception hierarchy is slightly different to the `httpx` exception hierarchy. `requests` exposes a top level `RequestException`, where as `httpx` exposes a top level `HTTPError`. see the exceptions exposes in requests [here](https://requests.readthedocs.io/en/latest/_modules/requests/exceptions/). See the `httpx` error hierarchy [here](https://www.python-httpx.org/exceptions/).
|
|
||||||
|
|||||||
@ -51,29 +51,3 @@ python -c "import httpx; httpx.get('http://example.com')"
|
|||||||
python -c "import httpx; httpx.get('http://127.0.0.1:5000/my-api')"
|
python -c "import httpx; httpx.get('http://127.0.0.1:5000/my-api')"
|
||||||
python -c "import httpx; httpx.get('https://www.python-httpx.org')"
|
python -c "import httpx; httpx.get('https://www.python-httpx.org')"
|
||||||
```
|
```
|
||||||
|
|
||||||
## `SSL_CERT_FILE`
|
|
||||||
|
|
||||||
Valid values: a filename
|
|
||||||
|
|
||||||
If this environment variable is set then HTTPX will load
|
|
||||||
CA certificate from the specified file instead of the default
|
|
||||||
location.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```console
|
|
||||||
SSL_CERT_FILE=/path/to/ca-certs/ca-bundle.crt python -c "import httpx; httpx.get('https://example.com')"
|
|
||||||
```
|
|
||||||
|
|
||||||
## `SSL_CERT_DIR`
|
|
||||||
|
|
||||||
Valid values: a directory following an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html).
|
|
||||||
|
|
||||||
If this environment variable is set and the directory follows an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html) (ie. you ran `c_rehash`) then HTTPX will load CA certificates from this directory instead of the default location.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```console
|
|
||||||
SSL_CERT_DIR=/path/to/ca-certs/ python -c "import httpx; httpx.get('https://example.com')"
|
|
||||||
```
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 1.9 MiB |
@ -145,6 +145,6 @@ To include the optional brotli and zstandard decoders support, use:
|
|||||||
$ pip install httpx[brotli,zstd]
|
$ pip install httpx[brotli,zstd]
|
||||||
```
|
```
|
||||||
|
|
||||||
HTTPX requires Python 3.9+
|
HTTPX requires Python 3.8+
|
||||||
|
|
||||||
[sync-support]: https://github.com/encode/httpx/issues/572
|
[sync-support]: https://github.com/encode/httpx/issues/572
|
||||||
|
|||||||
@ -20,6 +20,8 @@ httpx.get("https://www.example.com")
|
|||||||
Will send debug level output to the console, or wherever `stdout` is directed too...
|
Will send debug level output to the console, or wherever `stdout` is directed too...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
DEBUG [2024-09-28 17:27:40] httpx - load_ssl_context verify=True cert=None
|
||||||
|
DEBUG [2024-09-28 17:27:40] httpx - load_verify_locations cafile='/Users/karenpetrosyan/oss/karhttpx/.venv/lib/python3.9/site-packages/certifi/cacert.pem'
|
||||||
DEBUG [2024-09-28 17:27:40] httpcore.connection - connect_tcp.started host='www.example.com' port=443 local_address=None timeout=5.0 socket_options=None
|
DEBUG [2024-09-28 17:27:40] httpcore.connection - connect_tcp.started host='www.example.com' port=443 local_address=None timeout=5.0 socket_options=None
|
||||||
DEBUG [2024-09-28 17:27:41] httpcore.connection - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x101f1e8e0>
|
DEBUG [2024-09-28 17:27:41] httpcore.connection - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x101f1e8e0>
|
||||||
DEBUG [2024-09-28 17:27:41] httpcore.connection - start_tls.started ssl_context=SSLContext(verify=True) server_hostname='www.example.com' timeout=5.0
|
DEBUG [2024-09-28 17:27:41] httpcore.connection - start_tls.started ssl_context=SSLContext(verify=True) server_hostname='www.example.com' timeout=5.0
|
||||||
@ -78,4 +80,4 @@ logging.config.dictConfig(LOGGING_CONFIG)
|
|||||||
httpx.get('https://www.example.com')
|
httpx.get('https://www.example.com')
|
||||||
```
|
```
|
||||||
|
|
||||||
The exact formatting of the debug logging may be subject to change across different versions of `httpx` and `httpcore`. If you need to rely on a particular format it is recommended that you pin installation of these packages to fixed versions.
|
The exact formatting of the debug logging may be subject to change across different versions of `httpx` and `httpcore`. If you need to rely on a particular format it is recommended that you pin installation of these packages to fixed versions.
|
||||||
@ -174,9 +174,8 @@ Form encoded data can also include multiple values from a given key.
|
|||||||
You can also upload files, using HTTP multipart encoding:
|
You can also upload files, using HTTP multipart encoding:
|
||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> with open('report.xls', 'rb') as report_file:
|
>>> files = {'upload-file': open('report.xls', 'rb')}
|
||||||
... files = {'upload-file': report_file}
|
>>> r = httpx.post("https://httpbin.org/post", files=files)
|
||||||
... r = httpx.post("https://httpbin.org/post", files=files)
|
|
||||||
>>> print(r.text)
|
>>> print(r.text)
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
@ -191,9 +190,8 @@ You can also explicitly set the filename and content type, by using a tuple
|
|||||||
of items for the file value:
|
of items for the file value:
|
||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> with open('report.xls', 'rb') as report_file:
|
>>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
|
||||||
... files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}
|
>>> r = httpx.post("https://httpbin.org/post", files=files)
|
||||||
... r = httpx.post("https://httpbin.org/post", files=files)
|
|
||||||
>>> print(r.text)
|
>>> print(r.text)
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
@ -208,9 +206,8 @@ If you need to include non-file data fields in the multipart form, use the `data
|
|||||||
|
|
||||||
```pycon
|
```pycon
|
||||||
>>> data = {'message': 'Hello, world!'}
|
>>> data = {'message': 'Hello, world!'}
|
||||||
>>> with open('report.xls', 'rb') as report_file:
|
>>> files = {'file': open('report.xls', 'rb')}
|
||||||
... files = {'file': report_file}
|
>>> r = httpx.post("https://httpbin.org/post", data=data, files=files)
|
||||||
... r = httpx.post("https://httpbin.org/post", data=data, files=files)
|
|
||||||
>>> print(r.text)
|
>>> print(r.text)
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
|
|||||||
@ -2,83 +2,31 @@
|
|||||||
|
|
||||||
As HTTPX usage grows, there is an expanding community of developers building tools and libraries that integrate with HTTPX, or depend on HTTPX. Here are some of them.
|
As HTTPX usage grows, there is an expanding community of developers building tools and libraries that integrate with HTTPX, or depend on HTTPX. Here are some of them.
|
||||||
|
|
||||||
<!-- NOTE: Entries are alphabetised. -->
|
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
### Hishel
|
|
||||||
|
|
||||||
[GitHub](https://github.com/karpetrosyan/hishel) - [Documentation](https://hishel.com/)
|
|
||||||
|
|
||||||
An elegant HTTP Cache implementation for HTTPX and HTTP Core.
|
|
||||||
|
|
||||||
### HTTPX-Auth
|
|
||||||
|
|
||||||
[GitHub](https://github.com/Colin-b/httpx_auth) - [Documentation](https://colin-b.github.io/httpx_auth/)
|
|
||||||
|
|
||||||
Provides authentication classes to be used with HTTPX's [authentication parameter](advanced/authentication.md#customizing-authentication).
|
|
||||||
|
|
||||||
### httpx-caching
|
|
||||||
|
|
||||||
[Github](https://github.com/johtso/httpx-caching)
|
|
||||||
|
|
||||||
This package adds caching functionality to HTTPX
|
|
||||||
|
|
||||||
### httpx-secure
|
|
||||||
|
|
||||||
[GitHub](https://github.com/Zaczero/httpx-secure)
|
|
||||||
|
|
||||||
Drop-in SSRF protection for httpx with DNS caching and custom validation support.
|
|
||||||
|
|
||||||
### httpx-socks
|
|
||||||
|
|
||||||
[GitHub](https://github.com/romis2012/httpx-socks)
|
|
||||||
|
|
||||||
Proxy (HTTP, SOCKS) transports for httpx.
|
|
||||||
|
|
||||||
### httpx-sse
|
|
||||||
|
|
||||||
[GitHub](https://github.com/florimondmanca/httpx-sse)
|
|
||||||
|
|
||||||
Allows consuming Server-Sent Events (SSE) with HTTPX.
|
|
||||||
|
|
||||||
### httpx-retries
|
|
||||||
|
|
||||||
[GitHub](https://github.com/will-ockmore/httpx-retries) - [Documentation](https://will-ockmore.github.io/httpx-retries/)
|
|
||||||
|
|
||||||
A retry layer for HTTPX.
|
|
||||||
|
|
||||||
### httpx-ws
|
### httpx-ws
|
||||||
|
|
||||||
[GitHub](https://github.com/frankie567/httpx-ws) - [Documentation](https://frankie567.github.io/httpx-ws/)
|
[GitHub](https://github.com/frankie567/httpx-ws) - [Documentation](https://frankie567.github.io/httpx-ws/)
|
||||||
|
|
||||||
WebSocket support for HTTPX.
|
WebSocket support for HTTPX.
|
||||||
|
|
||||||
### pytest-HTTPX
|
### httpx-socks
|
||||||
|
|
||||||
[GitHub](https://github.com/Colin-b/pytest_httpx) - [Documentation](https://colin-b.github.io/pytest_httpx/)
|
[GitHub](https://github.com/romis2012/httpx-socks)
|
||||||
|
|
||||||
Provides a [pytest](https://docs.pytest.org/en/latest/) fixture to mock HTTPX within test cases.
|
Proxy (HTTP, SOCKS) transports for httpx.
|
||||||
|
|
||||||
### RESPX
|
### Hishel
|
||||||
|
|
||||||
[GitHub](https://github.com/lundberg/respx) - [Documentation](https://lundberg.github.io/respx/)
|
[GitHub](https://github.com/karpetrosyan/hishel) - [Documentation](https://hishel.com/)
|
||||||
|
|
||||||
A utility for mocking out HTTPX.
|
An elegant HTTP Cache implementation for HTTPX and HTTP Core.
|
||||||
|
|
||||||
### rpc.py
|
|
||||||
|
|
||||||
[Github](https://github.com/abersheeran/rpc.py) - [Documentation](https://github.com/abersheeran/rpc.py#rpcpy)
|
|
||||||
|
|
||||||
A fast and powerful RPC framework based on ASGI/WSGI. Use HTTPX as the client of the RPC service.
|
|
||||||
|
|
||||||
## Libraries with HTTPX support
|
|
||||||
|
|
||||||
### Authlib
|
### Authlib
|
||||||
|
|
||||||
[GitHub](https://github.com/lepture/authlib) - [Documentation](https://docs.authlib.org/en/latest/)
|
[GitHub](https://github.com/lepture/authlib) - [Documentation](https://docs.authlib.org/en/latest/)
|
||||||
|
|
||||||
A python library for building OAuth and OpenID Connect clients and servers. Includes an [OAuth HTTPX client](https://docs.authlib.org/en/latest/client/httpx.html).
|
The ultimate Python library in building OAuth and OpenID Connect clients and servers. Includes an [OAuth HTTPX client](https://docs.authlib.org/en/latest/client/httpx.html).
|
||||||
|
|
||||||
### Gidgethub
|
### Gidgethub
|
||||||
|
|
||||||
@ -86,20 +34,58 @@ A python library for building OAuth and OpenID Connect clients and servers. Incl
|
|||||||
|
|
||||||
An asynchronous GitHub API library. Includes [HTTPX support](https://gidgethub.readthedocs.io/en/latest/httpx.html).
|
An asynchronous GitHub API library. Includes [HTTPX support](https://gidgethub.readthedocs.io/en/latest/httpx.html).
|
||||||
|
|
||||||
### httpdbg
|
### HTTPX-Auth
|
||||||
|
|
||||||
[GitHub](https://github.com/cle-b/httpdbg) - [Documentation](https://httpdbg.readthedocs.io/)
|
[GitHub](https://github.com/Colin-b/httpx_auth) - [Documentation](https://colin-b.github.io/httpx_auth/)
|
||||||
|
|
||||||
A tool for python developers to easily debug the HTTP(S) client requests in a python program.
|
Provides authentication classes to be used with HTTPX [authentication parameter](advanced/authentication.md#customizing-authentication).
|
||||||
|
|
||||||
|
### pytest-HTTPX
|
||||||
|
|
||||||
|
[GitHub](https://github.com/Colin-b/pytest_httpx) - [Documentation](https://colin-b.github.io/pytest_httpx/)
|
||||||
|
|
||||||
|
Provides `httpx_mock` [pytest](https://docs.pytest.org/en/latest/) fixture to mock HTTPX within test cases.
|
||||||
|
|
||||||
|
### RESPX
|
||||||
|
|
||||||
|
[GitHub](https://github.com/lundberg/respx) - [Documentation](https://lundberg.github.io/respx/)
|
||||||
|
|
||||||
|
A utility for mocking out the Python HTTPX library.
|
||||||
|
|
||||||
|
### rpc.py
|
||||||
|
|
||||||
|
[Github](https://github.com/abersheeran/rpc.py) - [Documentation](https://github.com/abersheeran/rpc.py#rpcpy)
|
||||||
|
|
||||||
|
An fast and powerful RPC framework based on ASGI/WSGI. Use HTTPX as the client of the RPC service.
|
||||||
|
|
||||||
### VCR.py
|
### VCR.py
|
||||||
|
|
||||||
[GitHub](https://github.com/kevin1024/vcrpy) - [Documentation](https://vcrpy.readthedocs.io/)
|
[GitHub](https://github.com/kevin1024/vcrpy) - [Documentation](https://vcrpy.readthedocs.io/)
|
||||||
|
|
||||||
Record and repeat requests.
|
A utility for record and repeat an http request.
|
||||||
|
|
||||||
|
### httpx-caching
|
||||||
|
|
||||||
|
[Github](https://github.com/johtso/httpx-caching)
|
||||||
|
|
||||||
|
This package adds caching functionality to HTTPX
|
||||||
|
|
||||||
|
### httpx-sse
|
||||||
|
|
||||||
|
[GitHub](https://github.com/florimondmanca/httpx-sse)
|
||||||
|
|
||||||
|
Allows consuming Server-Sent Events (SSE) with HTTPX.
|
||||||
|
|
||||||
|
### robox
|
||||||
|
|
||||||
|
[Github](https://github.com/danclaudiupop/robox)
|
||||||
|
|
||||||
|
A library for scraping the web built on top of HTTPX.
|
||||||
|
|
||||||
## Gists
|
## Gists
|
||||||
|
|
||||||
|
<!-- NOTE: this list is in alphabetical order. -->
|
||||||
|
|
||||||
### urllib3-transport
|
### urllib3-transport
|
||||||
|
|
||||||
[GitHub](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e)
|
[GitHub](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e)
|
||||||
|
|||||||
@ -50,7 +50,6 @@ __all__ = [
|
|||||||
"DecodingError",
|
"DecodingError",
|
||||||
"delete",
|
"delete",
|
||||||
"DigestAuth",
|
"DigestAuth",
|
||||||
"FunctionAuth",
|
|
||||||
"get",
|
"get",
|
||||||
"head",
|
"head",
|
||||||
"Headers",
|
"Headers",
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
__title__ = "httpx"
|
__title__ = "httpx"
|
||||||
__description__ = "A next generation HTTP client, for Python 3."
|
__description__ = "A next generation HTTP client, for Python 3."
|
||||||
__version__ = "0.28.1"
|
__version__ = "0.28.0"
|
||||||
|
|||||||
@ -51,7 +51,7 @@ def request(
|
|||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
@ -136,7 +136,7 @@ def stream(
|
|||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> typing.Iterator[Response]:
|
) -> typing.Iterator[Response]:
|
||||||
"""
|
"""
|
||||||
@ -180,7 +180,7 @@ def get(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -216,7 +216,7 @@ def options(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -252,7 +252,7 @@ def head(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -292,7 +292,7 @@ def post(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -333,7 +333,7 @@ def put(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -374,7 +374,7 @@ def patch(
|
|||||||
auth: AuthTypes | None = None,
|
auth: AuthTypes | None = None,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
@ -412,7 +412,7 @@ def delete(
|
|||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -16,7 +16,7 @@ if typing.TYPE_CHECKING: # pragma: no cover
|
|||||||
from hashlib import _Hash
|
from hashlib import _Hash
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Auth", "BasicAuth", "DigestAuth", "FunctionAuth", "NetRCAuth"]
|
__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]
|
||||||
|
|
||||||
|
|
||||||
class Auth:
|
class Auth:
|
||||||
|
|||||||
@ -33,7 +33,6 @@ from ._transports.default import AsyncHTTPTransport, HTTPTransport
|
|||||||
from ._types import (
|
from ._types import (
|
||||||
AsyncByteStream,
|
AsyncByteStream,
|
||||||
AuthTypes,
|
AuthTypes,
|
||||||
CertTypes,
|
|
||||||
CookieTypes,
|
CookieTypes,
|
||||||
HeaderTypes,
|
HeaderTypes,
|
||||||
ProxyTypes,
|
ProxyTypes,
|
||||||
@ -620,6 +619,8 @@ class Client(BaseClient):
|
|||||||
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
|
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
|
||||||
enabled. Defaults to `False`.
|
enabled. Defaults to `False`.
|
||||||
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
||||||
|
* **proxies** - *(optional)* A dictionary mapping proxy keys to proxy
|
||||||
|
URLs.
|
||||||
* **timeout** - *(optional)* The timeout configuration to use when sending
|
* **timeout** - *(optional)* The timeout configuration to use when sending
|
||||||
requests.
|
requests.
|
||||||
* **limits** - *(optional)* The limits configuration to use.
|
* **limits** - *(optional)* The limits configuration to use.
|
||||||
@ -643,9 +644,7 @@ class Client(BaseClient):
|
|||||||
params: QueryParamTypes | None = None,
|
params: QueryParamTypes | None = None,
|
||||||
headers: HeaderTypes | None = None,
|
headers: HeaderTypes | None = None,
|
||||||
cookies: CookieTypes | None = None,
|
cookies: CookieTypes | None = None,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
@ -657,6 +656,7 @@ class Client(BaseClient):
|
|||||||
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
||||||
base_url: URL | str = "",
|
base_url: URL | str = "",
|
||||||
transport: BaseTransport | None = None,
|
transport: BaseTransport | None = None,
|
||||||
|
trust_env: bool = True,
|
||||||
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -687,8 +687,6 @@ class Client(BaseClient):
|
|||||||
|
|
||||||
self._transport = self._init_transport(
|
self._transport = self._init_transport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -700,8 +698,6 @@ class Client(BaseClient):
|
|||||||
else self._init_proxy_transport(
|
else self._init_proxy_transport(
|
||||||
proxy,
|
proxy,
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -717,9 +713,7 @@ class Client(BaseClient):
|
|||||||
|
|
||||||
def _init_transport(
|
def _init_transport(
|
||||||
self,
|
self,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
@ -730,8 +724,6 @@ class Client(BaseClient):
|
|||||||
|
|
||||||
return HTTPTransport(
|
return HTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -740,17 +732,13 @@ class Client(BaseClient):
|
|||||||
def _init_proxy_transport(
|
def _init_proxy_transport(
|
||||||
self,
|
self,
|
||||||
proxy: Proxy,
|
proxy: Proxy,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
) -> BaseTransport:
|
) -> BaseTransport:
|
||||||
return HTTPTransport(
|
return HTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -1357,8 +1345,7 @@ class AsyncClient(BaseClient):
|
|||||||
params: QueryParamTypes | None = None,
|
params: QueryParamTypes | None = None,
|
||||||
headers: HeaderTypes | None = None,
|
headers: HeaderTypes | None = None,
|
||||||
cookies: CookieTypes | None = None,
|
cookies: CookieTypes | None = None,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: ProxyTypes | None = None,
|
||||||
@ -1401,8 +1388,6 @@ class AsyncClient(BaseClient):
|
|||||||
|
|
||||||
self._transport = self._init_transport(
|
self._transport = self._init_transport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -1415,8 +1400,6 @@ class AsyncClient(BaseClient):
|
|||||||
else self._init_proxy_transport(
|
else self._init_proxy_transport(
|
||||||
proxy,
|
proxy,
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -1431,9 +1414,7 @@ class AsyncClient(BaseClient):
|
|||||||
|
|
||||||
def _init_transport(
|
def _init_transport(
|
||||||
self,
|
self,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
@ -1444,8 +1425,6 @@ class AsyncClient(BaseClient):
|
|||||||
|
|
||||||
return AsyncHTTPTransport(
|
return AsyncHTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -1454,17 +1433,13 @@ class AsyncClient(BaseClient):
|
|||||||
def _init_proxy_transport(
|
def _init_proxy_transport(
|
||||||
self,
|
self,
|
||||||
proxy: Proxy,
|
proxy: Proxy,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
) -> AsyncBaseTransport:
|
) -> AsyncBaseTransport:
|
||||||
return AsyncHTTPTransport(
|
return AsyncHTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
cert=cert,
|
|
||||||
trust_env=trust_env,
|
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
@ -1723,7 +1698,7 @@ class AsyncClient(BaseClient):
|
|||||||
|
|
||||||
if not isinstance(request.stream, AsyncByteStream):
|
if not isinstance(request.stream, AsyncByteStream):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Attempted to send a sync request with an AsyncClient instance."
|
"Attempted to send an sync request with an AsyncClient instance."
|
||||||
)
|
)
|
||||||
|
|
||||||
with request_context(request=request):
|
with request_context(request=request):
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from ._models import Headers
|
from ._models import Headers
|
||||||
from ._types import CertTypes, HeaderTypes, TimeoutTypes
|
from ._types import HeaderTypes, TimeoutTypes
|
||||||
from ._urls import URL
|
from ._urls import URL
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
@ -20,53 +19,20 @@ class UnsetType:
|
|||||||
UNSET = UnsetType()
|
UNSET = UnsetType()
|
||||||
|
|
||||||
|
|
||||||
def create_ssl_context(
|
def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext:
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
) -> ssl.SSLContext:
|
|
||||||
import ssl
|
import ssl
|
||||||
import warnings
|
|
||||||
|
|
||||||
import certifi
|
import truststore
|
||||||
|
|
||||||
if verify is True:
|
if verify is True:
|
||||||
if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover
|
return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"])
|
|
||||||
elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover
|
|
||||||
ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"])
|
|
||||||
else:
|
|
||||||
# Default case...
|
|
||||||
ctx = ssl.create_default_context(cafile=certifi.where())
|
|
||||||
elif verify is False:
|
elif verify is False:
|
||||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
ctx.check_hostname = False
|
ssl_context.check_hostname = False
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
elif isinstance(verify, str): # pragma: nocover
|
return ssl_context
|
||||||
message = (
|
|
||||||
"`verify=<str>` is deprecated. "
|
|
||||||
"Use `verify=ssl.create_default_context(cafile=...)` "
|
|
||||||
"or `verify=ssl.create_default_context(capath=...)` instead."
|
|
||||||
)
|
|
||||||
warnings.warn(message, DeprecationWarning)
|
|
||||||
if os.path.isdir(verify):
|
|
||||||
return ssl.create_default_context(capath=verify)
|
|
||||||
return ssl.create_default_context(cafile=verify)
|
|
||||||
else:
|
|
||||||
ctx = verify
|
|
||||||
|
|
||||||
if cert: # pragma: nocover
|
return verify
|
||||||
message = (
|
|
||||||
"`cert=...` is deprecated. Use `verify=<ssl_context>` instead,"
|
|
||||||
"with `.load_cert_chain()` to configure the certificate chain."
|
|
||||||
)
|
|
||||||
warnings.warn(message, DeprecationWarning)
|
|
||||||
if isinstance(cert, str):
|
|
||||||
ctx.load_cert_chain(cert)
|
|
||||||
else:
|
|
||||||
ctx.load_cert_chain(*cert)
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
class Timeout:
|
class Timeout:
|
||||||
|
|||||||
@ -331,7 +331,9 @@ class StreamClosed(StreamError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
message = "Attempted to read or stream content, but the stream has been closed."
|
message = (
|
||||||
|
"Attempted to read or stream content, but the stream has " "been closed."
|
||||||
|
)
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -398,7 +398,7 @@ class Request:
|
|||||||
self.method = method.upper()
|
self.method = method.upper()
|
||||||
self.url = URL(url) if params is None else URL(url, params=params)
|
self.url = URL(url) if params is None else URL(url, params=params)
|
||||||
self.headers = Headers(headers)
|
self.headers = Headers(headers)
|
||||||
self.extensions = {} if extensions is None else dict(extensions)
|
self.extensions = {} if extensions is None else extensions
|
||||||
|
|
||||||
if cookies:
|
if cookies:
|
||||||
Cookies(cookies).set_cookie_header(self)
|
Cookies(cookies).set_cookie_header(self)
|
||||||
@ -537,7 +537,7 @@ class Response:
|
|||||||
# the client will set `response.next_request`.
|
# the client will set `response.next_request`.
|
||||||
self.next_request: Request | None = None
|
self.next_request: Request | None = None
|
||||||
|
|
||||||
self.extensions = {} if extensions is None else dict(extensions)
|
self.extensions: ResponseExtensions = {} if extensions is None else extensions
|
||||||
self.history = [] if history is None else list(history)
|
self.history = [] if history is None else list(history)
|
||||||
|
|
||||||
self.is_closed = False
|
self.is_closed = False
|
||||||
@ -964,7 +964,7 @@ class Response:
|
|||||||
Automatically called if the response body is read to completion.
|
Automatically called if the response body is read to completion.
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.stream, SyncByteStream):
|
if not isinstance(self.stream, SyncByteStream):
|
||||||
raise RuntimeError("Attempted to call a sync close on an async stream.")
|
raise RuntimeError("Attempted to call an sync close on an async stream.")
|
||||||
|
|
||||||
if not self.is_closed:
|
if not self.is_closed:
|
||||||
self.is_closed = True
|
self.is_closed = True
|
||||||
@ -1045,7 +1045,7 @@ class Response:
|
|||||||
if self.is_closed:
|
if self.is_closed:
|
||||||
raise StreamClosed()
|
raise StreamClosed()
|
||||||
if not isinstance(self.stream, AsyncByteStream):
|
if not isinstance(self.stream, AsyncByteStream):
|
||||||
raise RuntimeError("Attempted to call an async iterator on a sync stream.")
|
raise RuntimeError("Attempted to call an async iterator on an sync stream.")
|
||||||
|
|
||||||
self.is_stream_consumed = True
|
self.is_stream_consumed = True
|
||||||
self._num_bytes_downloaded = 0
|
self._num_bytes_downloaded = 0
|
||||||
@ -1068,7 +1068,7 @@ class Response:
|
|||||||
Automatically called if the response body is read to completion.
|
Automatically called if the response body is read to completion.
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.stream, AsyncByteStream):
|
if not isinstance(self.stream, AsyncByteStream):
|
||||||
raise RuntimeError("Attempted to call an async close on a sync stream.")
|
raise RuntimeError("Attempted to call an async close on an sync stream.")
|
||||||
|
|
||||||
if not self.is_closed:
|
if not self.is_closed:
|
||||||
self.is_closed = True
|
self.is_closed = True
|
||||||
|
|||||||
@ -53,7 +53,7 @@ from .._exceptions import (
|
|||||||
WriteTimeout,
|
WriteTimeout,
|
||||||
)
|
)
|
||||||
from .._models import Request, Response
|
from .._models import Request, Response
|
||||||
from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream
|
from .._types import AsyncByteStream, ProxyTypes, SyncByteStream
|
||||||
from .._urls import URL
|
from .._urls import URL
|
||||||
from .base import AsyncBaseTransport, BaseTransport
|
from .base import AsyncBaseTransport, BaseTransport
|
||||||
|
|
||||||
@ -135,9 +135,7 @@ class ResponseStream(SyncByteStream):
|
|||||||
class HTTPTransport(BaseTransport):
|
class HTTPTransport(BaseTransport):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
@ -150,7 +148,7 @@ class HTTPTransport(BaseTransport):
|
|||||||
import httpcore
|
import httpcore
|
||||||
|
|
||||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
ssl_context = create_ssl_context(verify=verify)
|
||||||
|
|
||||||
if proxy is None:
|
if proxy is None:
|
||||||
self._pool = httpcore.ConnectionPool(
|
self._pool = httpcore.ConnectionPool(
|
||||||
@ -279,9 +277,7 @@ class AsyncResponseStream(AsyncByteStream):
|
|||||||
class AsyncHTTPTransport(AsyncBaseTransport):
|
class AsyncHTTPTransport(AsyncBaseTransport):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
verify: ssl.SSLContext | str | bool = True,
|
verify: ssl.SSLContext | bool = True,
|
||||||
cert: CertTypes | None = None,
|
|
||||||
trust_env: bool = True,
|
|
||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
@ -294,7 +290,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
|||||||
import httpcore
|
import httpcore
|
||||||
|
|
||||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
ssl_context = create_ssl_context(verify=verify)
|
||||||
|
|
||||||
if proxy is None:
|
if proxy is None:
|
||||||
self._pool = httpcore.AsyncConnectionPool(
|
self._pool = httpcore.AsyncConnectionPool(
|
||||||
@ -355,7 +351,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
|||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
|
"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',"
|
||||||
f" but got {proxy.url.scheme!r}."
|
" but got {proxy.url.scheme!r}."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def __aenter__(self: A) -> A: # Use generics for subclass support.
|
async def __aenter__(self: A) -> A: # Use generics for subclass support.
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from typing import (
|
|||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
Mapping,
|
Mapping,
|
||||||
|
MutableMapping,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
@ -57,7 +58,6 @@ TimeoutTypes = Union[
|
|||||||
"Timeout",
|
"Timeout",
|
||||||
]
|
]
|
||||||
ProxyTypes = Union["URL", str, "Proxy"]
|
ProxyTypes = Union["URL", str, "Proxy"]
|
||||||
CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]
|
|
||||||
|
|
||||||
AuthTypes = Union[
|
AuthTypes = Union[
|
||||||
Tuple[Union[str, bytes], Union[str, bytes]],
|
Tuple[Union[str, bytes], Union[str, bytes]],
|
||||||
@ -67,7 +67,7 @@ AuthTypes = Union[
|
|||||||
|
|
||||||
RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
||||||
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
||||||
ResponseExtensions = Mapping[str, Any]
|
ResponseExtensions = MutableMapping[str, Any]
|
||||||
|
|
||||||
RequestData = Mapping[str, Any]
|
RequestData = Mapping[str, Any]
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ FileTypes = Union[
|
|||||||
]
|
]
|
||||||
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
|
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
|
||||||
|
|
||||||
RequestExtensions = Mapping[str, Any]
|
RequestExtensions = MutableMapping[str, Any]
|
||||||
|
|
||||||
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
||||||
|
|
||||||
|
|||||||
@ -379,7 +379,7 @@ class URL:
|
|||||||
|
|
||||||
if ":" in userinfo:
|
if ":" in userinfo:
|
||||||
# Mask any password component.
|
# Mask any password component.
|
||||||
userinfo = f"{userinfo.split(':')[0]}:[secure]"
|
userinfo = f'{userinfo.split(":")[0]}:[secure]'
|
||||||
|
|
||||||
authority = "".join(
|
authority = "".join(
|
||||||
[
|
[
|
||||||
|
|||||||
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|||||||
name = "httpx"
|
name = "httpx"
|
||||||
description = "The next generation HTTP client."
|
description = "The next generation HTTP client."
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.8"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Tom Christie", email = "tom@tomchristie.com" },
|
{ name = "Tom Christie", email = "tom@tomchristie.com" },
|
||||||
]
|
]
|
||||||
@ -20,15 +20,15 @@ classifiers = [
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
|
||||||
"Topic :: Internet :: WWW/HTTP",
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"certifi",
|
"truststore==0.10.0",
|
||||||
"httpcore==1.*",
|
"httpcore==1.*",
|
||||||
"anyio",
|
"anyio",
|
||||||
"idna",
|
"idna",
|
||||||
@ -43,7 +43,7 @@ brotli = [
|
|||||||
cli = [
|
cli = [
|
||||||
"click==8.*",
|
"click==8.*",
|
||||||
"pygments==2.*",
|
"pygments==2.*",
|
||||||
"rich>=10,<15",
|
"rich>=10,<14",
|
||||||
]
|
]
|
||||||
http2 = [
|
http2 = [
|
||||||
"h2>=3,<5",
|
"h2>=3,<5",
|
||||||
|
|||||||
@ -11,19 +11,19 @@ chardet==5.2.0
|
|||||||
# Documentation
|
# Documentation
|
||||||
mkdocs==1.6.1
|
mkdocs==1.6.1
|
||||||
mkautodoc==0.2.0
|
mkautodoc==0.2.0
|
||||||
mkdocs-material==9.6.18
|
mkdocs-material==9.5.39
|
||||||
|
|
||||||
# Packaging
|
# Packaging
|
||||||
build==1.3.0
|
build==1.2.2
|
||||||
twine==6.1.0
|
twine==5.1.1
|
||||||
|
|
||||||
# Tests & Linting
|
# Tests & Linting
|
||||||
coverage[toml]==7.10.6
|
coverage[toml]==7.6.1
|
||||||
cryptography==45.0.7
|
cryptography==43.0.1
|
||||||
mypy==1.17.1
|
mypy==1.11.2
|
||||||
pytest==8.4.1
|
pytest==8.3.3
|
||||||
ruff==0.12.11
|
ruff==0.6.8
|
||||||
trio==0.31.0
|
trio==0.26.2
|
||||||
trio-typing==0.10.0
|
trio-typing==0.10.0
|
||||||
trustme==1.2.1
|
trustme==1.1.0
|
||||||
uvicorn==0.35.0
|
uvicorn==0.31.0
|
||||||
|
|||||||
@ -326,7 +326,7 @@ async def test_auth_property() -> None:
|
|||||||
async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:
|
async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:
|
||||||
assert client.auth is None
|
assert client.auth is None
|
||||||
|
|
||||||
client.auth = ("user", "password123")
|
client.auth = ("user", "password123") # type: ignore
|
||||||
assert isinstance(client.auth, httpx.BasicAuth)
|
assert isinstance(client.auth, httpx.BasicAuth)
|
||||||
|
|
||||||
url = "https://example.org/"
|
url = "https://example.org/"
|
||||||
|
|||||||
@ -3,35 +3,35 @@ import httpx
|
|||||||
|
|
||||||
def test_client_base_url():
|
def test_client_base_url():
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.base_url = "https://www.example.org/"
|
client.base_url = "https://www.example.org/" # type: ignore
|
||||||
assert isinstance(client.base_url, httpx.URL)
|
assert isinstance(client.base_url, httpx.URL)
|
||||||
assert client.base_url == "https://www.example.org/"
|
assert client.base_url == "https://www.example.org/"
|
||||||
|
|
||||||
|
|
||||||
def test_client_base_url_without_trailing_slash():
|
def test_client_base_url_without_trailing_slash():
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.base_url = "https://www.example.org/path"
|
client.base_url = "https://www.example.org/path" # type: ignore
|
||||||
assert isinstance(client.base_url, httpx.URL)
|
assert isinstance(client.base_url, httpx.URL)
|
||||||
assert client.base_url == "https://www.example.org/path/"
|
assert client.base_url == "https://www.example.org/path/"
|
||||||
|
|
||||||
|
|
||||||
def test_client_base_url_with_trailing_slash():
|
def test_client_base_url_with_trailing_slash():
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.base_url = "https://www.example.org/path/"
|
client.base_url = "https://www.example.org/path/" # type: ignore
|
||||||
assert isinstance(client.base_url, httpx.URL)
|
assert isinstance(client.base_url, httpx.URL)
|
||||||
assert client.base_url == "https://www.example.org/path/"
|
assert client.base_url == "https://www.example.org/path/"
|
||||||
|
|
||||||
|
|
||||||
def test_client_headers():
|
def test_client_headers():
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.headers = {"a": "b"}
|
client.headers = {"a": "b"} # type: ignore
|
||||||
assert isinstance(client.headers, httpx.Headers)
|
assert isinstance(client.headers, httpx.Headers)
|
||||||
assert client.headers["A"] == "b"
|
assert client.headers["A"] == "b"
|
||||||
|
|
||||||
|
|
||||||
def test_client_cookies():
|
def test_client_cookies():
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.cookies = {"a": "b"}
|
client.cookies = {"a": "b"} # type: ignore
|
||||||
assert isinstance(client.cookies, httpx.Cookies)
|
assert isinstance(client.cookies, httpx.Cookies)
|
||||||
mycookies = list(client.cookies.jar)
|
mycookies = list(client.cookies.jar)
|
||||||
assert len(mycookies) == 1
|
assert len(mycookies) == 1
|
||||||
@ -42,7 +42,7 @@ def test_client_timeout():
|
|||||||
expected_timeout = 12.0
|
expected_timeout = 12.0
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
|
|
||||||
client.timeout = expected_timeout
|
client.timeout = expected_timeout # type: ignore
|
||||||
|
|
||||||
assert isinstance(client.timeout, httpx.Timeout)
|
assert isinstance(client.timeout, httpx.Timeout)
|
||||||
assert client.timeout.connect == expected_timeout
|
assert client.timeout.connect == expected_timeout
|
||||||
|
|||||||
@ -17,7 +17,7 @@ def test_client_queryparams_string():
|
|||||||
assert client.params["a"] == "b"
|
assert client.params["a"] == "b"
|
||||||
|
|
||||||
client = httpx.Client()
|
client = httpx.Client()
|
||||||
client.params = "a=b"
|
client.params = "a=b" # type: ignore
|
||||||
assert isinstance(client.params, httpx.QueryParams)
|
assert isinstance(client.params, httpx.QueryParams)
|
||||||
assert client.params["a"] == "b"
|
assert client.params["a"] == "b"
|
||||||
|
|
||||||
|
|||||||
@ -226,16 +226,3 @@ def test_request_generator_content_picklable():
|
|||||||
request.read()
|
request.read()
|
||||||
pickle_request = pickle.loads(pickle.dumps(request))
|
pickle_request = pickle.loads(pickle.dumps(request))
|
||||||
assert pickle_request.content == b"test 123"
|
assert pickle_request.content == b"test 123"
|
||||||
|
|
||||||
|
|
||||||
def test_request_params():
|
|
||||||
request = httpx.Request("GET", "http://example.com", params={})
|
|
||||||
assert str(request.url) == "http://example.com"
|
|
||||||
|
|
||||||
request = httpx.Request(
|
|
||||||
"GET", "http://example.com?c=3", params={"a": "1", "b": "2"}
|
|
||||||
)
|
|
||||||
assert str(request.url) == "http://example.com?a=1&b=2"
|
|
||||||
|
|
||||||
request = httpx.Request("GET", "http://example.com?a=1", params={})
|
|
||||||
assert str(request.url) == "http://example.com"
|
|
||||||
|
|||||||
@ -1011,10 +1011,7 @@ def test_response_decode_text_using_autodetect():
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.reason_phrase == "OK"
|
assert response.reason_phrase == "OK"
|
||||||
# The encoded byte string is consistent with either ISO-8859-1 or
|
assert response.encoding == "ISO-8859-1"
|
||||||
# WINDOWS-1252. Versions <6.0 of chardet claim the former, while chardet
|
|
||||||
# 6.0 detects the latter.
|
|
||||||
assert response.encoding in ("ISO-8859-1", "WINDOWS-1252")
|
|
||||||
assert response.text == text
|
assert response.text == text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import ssl
|
import ssl
|
||||||
import typing
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import certifi
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@ -20,26 +17,6 @@ def test_load_ssl_config_verify_non_existing_file():
|
|||||||
context.load_verify_locations(cafile="/path/to/nowhere")
|
context.load_verify_locations(cafile="/path/to/nowhere")
|
||||||
|
|
||||||
|
|
||||||
def test_load_ssl_with_keylog(monkeypatch: typing.Any) -> None:
|
|
||||||
monkeypatch.setenv("SSLKEYLOGFILE", "test")
|
|
||||||
context = httpx.create_ssl_context()
|
|
||||||
assert context.keylog_filename == "test"
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_ssl_config_verify_existing_file():
|
|
||||||
context = httpx.create_ssl_context()
|
|
||||||
context.load_verify_locations(capath=certifi.where())
|
|
||||||
assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED
|
|
||||||
assert context.check_hostname is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_ssl_config_verify_directory():
|
|
||||||
context = httpx.create_ssl_context()
|
|
||||||
context.load_verify_locations(capath=Path(certifi.where()).parent)
|
|
||||||
assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED
|
|
||||||
assert context.check_hostname is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file):
|
def test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file):
|
||||||
context = httpx.create_ssl_context()
|
context = httpx.create_ssl_context()
|
||||||
context.load_cert_chain(cert_pem_file, cert_private_key_file)
|
context.load_cert_chain(cert_pem_file, cert_private_key_file)
|
||||||
|
|||||||
@ -489,18 +489,18 @@ def test_response_invalid_argument():
|
|||||||
def test_ensure_ascii_false_with_french_characters():
|
def test_ensure_ascii_false_with_french_characters():
|
||||||
data = {"greeting": "Bonjour, ça va ?"}
|
data = {"greeting": "Bonjour, ça va ?"}
|
||||||
response = httpx.Response(200, json=data)
|
response = httpx.Response(200, json=data)
|
||||||
assert "ça va" in response.text, (
|
assert (
|
||||||
"ensure_ascii=False should preserve French accented characters"
|
"ça va" in response.text
|
||||||
)
|
), "ensure_ascii=False should preserve French accented characters"
|
||||||
assert response.headers["Content-Type"] == "application/json"
|
assert response.headers["Content-Type"] == "application/json"
|
||||||
|
|
||||||
|
|
||||||
def test_separators_for_compact_json():
|
def test_separators_for_compact_json():
|
||||||
data = {"clé": "valeur", "liste": [1, 2, 3]}
|
data = {"clé": "valeur", "liste": [1, 2, 3]}
|
||||||
response = httpx.Response(200, json=data)
|
response = httpx.Response(200, json=data)
|
||||||
assert response.text == '{"clé":"valeur","liste":[1,2,3]}', (
|
assert (
|
||||||
"separators=(',', ':') should produce a compact representation"
|
response.text == '{"clé":"valeur","liste":[1,2,3]}'
|
||||||
)
|
), "separators=(',', ':') should produce a compact representation"
|
||||||
assert response.headers["Content-Type"] == "application/json"
|
assert response.headers["Content-Type"] == "application/json"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user