Skip to content

Commit 13b6648

Browse files
authored
Merge branch 'master' into master
2 parents d0ea9f8 + 4b23574 commit 13b6648

59 files changed

Lines changed: 1085 additions & 1503 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,10 @@ this is where our previously generated `client.pem` comes in:
211211
```
212212
import httpx
213213
214-
proxies = {"all": "http://127.0.0.1:8080/"}
214+
ssl_context = httpx.SSLContext()
215+
ssl_context.load_verify_locations("/path/to/client.pem")
215216
216-
with httpx.Client(proxies=proxies, verify="/path/to/client.pem") as client:
217+
with httpx.Client(proxy="http://127.0.0.1:8080/", ssl_context=ssl_context) as client:
217218
response = client.get("https://example.org")
218219
print(response.status_code) # should print 200
219220
```

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- uses: "actions/checkout@v4"
1818
- uses: "actions/setup-python@v5"
1919
with:
20-
python-version: 3.8
20+
python-version: 3.9
2121
- name: "Install dependencies"
2222
run: "scripts/install"
2323
- name: "Build package & docs"

.github/workflows/test-suite.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
push:
66
branches: ["master"]
77
pull_request:
8-
branches: ["master"]
8+
branches: ["master", "version-*"]
99

1010
jobs:
1111
tests:
@@ -14,7 +14,7 @@ jobs:
1414

1515
strategy:
1616
matrix:
17-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
17+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1818

1919
steps:
2020
- uses: "actions/checkout@v4"

CHANGELOG.md

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## [UNRELEASED]
8+
9+
### Removed
10+
11+
* Drop support for Python 3.8
12+
13+
## 0.28.1 (6th December, 2024)
14+
15+
* Fix SSL case where `verify=False` together with client side certificates.
16+
17+
## 0.28.0 (28th November, 2024)
18+
19+
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.
20+
21+
The 0.28 release includes a limited set of deprecations...
22+
23+
**Deprecations**:
24+
25+
We are working towards a simplified SSL configuration API.
26+
27+
*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...*
28+
29+
* The `verify` argument as a string argument is now deprecated and will raise warnings.
30+
* The `cert` argument is now deprecated and will raise warnings.
31+
32+
Our revised [SSL documentation](docs/advanced/ssl.md) covers how to implement the same behaviour with a more constrained API.
33+
34+
**The following changes are also included**:
35+
36+
* The deprecated `proxies` argument has now been removed.
37+
* The deprecated `app` argument has now been removed.
38+
* JSON request bodies use a compact representation. (#3363)
39+
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
40+
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
41+
* Treat `socks5h` as a valid proxy scheme. (#3178)
42+
* Cleanup `Request()` method signature in line with `client.request()` and `httpx.request()`. (#3378)
43+
* Bugfix: When passing `params={}`, always strictly update rather than merge with an existing querystring. (#3364)
44+
745
## 0.27.2 (27th August, 2024)
846

947
### Fixed
@@ -590,7 +628,7 @@ See pull requests #1057, #1058.
590628

591629
* Added dedicated exception class `httpx.HTTPStatusError` for `.raise_for_status()` exceptions. (Pull #1072)
592630
* Added `httpx.create_ssl_context()` helper function. (Pull #996)
593-
* Support for proxy exlcusions like `proxies={"https://www.example.com": None}`. (Pull #1099)
631+
* Support for proxy exclusions like `proxies={"https://www.example.com": None}`. (Pull #1099)
594632
* Support `QueryParams(None)` and `client.params = None`. (Pull #1060)
595633

596634
### Changed
@@ -818,7 +856,7 @@ We believe the API is now pretty much stable, and are aiming for a 1.0 release s
818856

819857
### Fixed
820858

821-
- Fix issue with concurrent connection acquiry. (Pull #700)
859+
- Fix issue with concurrent connection acquisition. (Pull #700)
822860
- Fix write error on closing HTTP/2 connections. (Pull #699)
823861

824862
## 0.10.0 (December 29th, 2019)
@@ -1067,7 +1105,7 @@ importing modules within the package.
10671105

10681106
## 0.6.7 (July 8, 2019)
10691107

1070-
- Check for connection aliveness on re-acquiry (Pull #111)
1108+
- Check for connection aliveness on re-acquisition (Pull #111)
10711109

10721110
## 0.6.6 (July 3, 2019)
10731111

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@
1313
</a>
1414
</p>
1515

16-
HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated
17-
command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync
18-
and async APIs**.
16+
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**.
1917

2018
---
2119

2220
Install HTTPX using pip:
2321

2422
```shell
25-
pip install httpx
23+
$ pip install httpx
2624
```
2725

2826
Now, let's get started:
@@ -43,7 +41,7 @@ Now, let's get started:
4341
Or, using the command-line client.
4442

4543
```shell
46-
pip install 'httpx[cli]' # The command line client is an optional dependency.
44+
$ pip install 'httpx[cli]' # The command line client is an optional dependency.
4745
```
4846

4947
Which now allows us to use HTTPX directly from the command-line...
@@ -94,16 +92,16 @@ Plus all the standard features of `requests`...
9492
Install with pip:
9593

9694
```shell
97-
pip install httpx
95+
$ pip install httpx
9896
```
9997

10098
Or, to include the optional HTTP/2 support, use:
10199

102100
```shell
103-
pip install httpx[http2]
101+
$ pip install httpx[http2]
104102
```
105103

106-
HTTPX requires Python 3.8+.
104+
HTTPX requires Python 3.9+.
107105

108106
## Documentation
109107

docs/advanced/clients.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ multipart file encoding is available by passing a dictionary with the
270270
name of the payloads as keys and either tuple of elements or a file-like object or a string as values.
271271

272272
```pycon
273-
>>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
274-
>>> r = httpx.post("https://httpbin.org/post", files=files)
273+
>>> with open('report.xls', 'rb') as report_file:
274+
... files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}
275+
... r = httpx.post("https://httpbin.org/post", files=files)
275276
>>> print(r.text)
276277
{
277278
...
@@ -318,7 +319,10 @@ To do that, pass a list of `(field, <file>)` items instead of a dictionary, allo
318319
For instance this request sends 2 files, `foo.png` and `bar.png` in one request on the `images` form field:
319320

320321
```pycon
321-
>>> files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
322-
('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
323-
>>> r = httpx.post("https://httpbin.org/post", files=files)
322+
>>> with open('foo.png', 'rb') as foo_file, open('bar.png', 'rb') as bar_file:
323+
... files = [
324+
... ('images', ('foo.png', foo_file, 'image/png')),
325+
... ('images', ('bar.png', bar_file, 'image/png')),
326+
... ]
327+
... r = httpx.post("https://httpbin.org/post", files=files)
324328
```

docs/advanced/proxies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ This is an optional feature that requires an additional third-party library be i
7373
You can install SOCKS support using `pip`:
7474

7575
```shell
76-
pip install httpx[socks]
76+
$ pip install httpx[socks]
7777
```
7878

7979
You can now configure a client to make requests via a proxy using the SOCKS protocol:

docs/advanced/ssl.md

Lines changed: 47 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,89 @@
11
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).
22

3-
## Changing the verification defaults
3+
### Enabling and disabling verification
44

5-
By default, HTTPX uses the CA bundle provided by [Certifi](https://pypi.org/project/certifi/). This is what you want in most cases, even though some advanced situations may require you to use a different set of certificates.
5+
By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...
66

7-
If you'd like to use a custom CA bundle, you can use the `verify` parameter.
8-
9-
```python
10-
import httpx
11-
12-
r = httpx.get("https://example.org", verify="path/to/client.pem")
7+
```pycon
8+
>>> httpx.get("https://expired.badssl.com/")
9+
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
1310
```
1411

15-
Alternatively, you can pass a standard library `ssl.SSLContext`.
12+
You can disable SSL verification completely and allow insecure requests...
1613

1714
```pycon
18-
>>> import ssl
19-
>>> import httpx
20-
>>> context = ssl.create_default_context()
21-
>>> context.load_verify_locations(cafile="/tmp/client.pem")
22-
>>> httpx.get('https://example.org', verify=context)
15+
>>> httpx.get("https://expired.badssl.com/", verify=False)
2316
<Response [200 OK]>
2417
```
2518

26-
We also include a helper function for creating properly configured `SSLContext` instances.
19+
### Configuring client instances
2720

28-
```pycon
29-
>>> context = httpx.create_ssl_context()
30-
```
31-
32-
The `create_ssl_context` function accepts the same set of SSL configuration arguments
33-
(`trust_env`, `verify`, `cert` and `http2` arguments)
34-
as `httpx.Client` or `httpx.AsyncClient`
21+
If you're using a `Client()` instance you should pass any `verify=<...>` configuration when instantiating the client.
3522

36-
```pycon
37-
>>> import httpx
38-
>>> context = httpx.create_ssl_context(verify="/tmp/client.pem")
39-
>>> httpx.get('https://example.org', verify=context)
40-
<Response [200 OK]>
41-
```
23+
By default the [certifi CA bundle](https://certifiio.readthedocs.io/en/latest/) is used for SSL verification.
4224

43-
Or you can also disable the SSL verification entirely, which is _not_ recommended.
25+
For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...
4426

4527
```python
28+
import certifi
4629
import httpx
30+
import ssl
4731

48-
r = httpx.get("https://example.org", verify=False)
32+
# This SSL context is equivelent to the default `verify=True`.
33+
ctx = ssl.create_default_context(cafile=certifi.where())
34+
client = httpx.Client(verify=ctx)
4935
```
5036

51-
## SSL configuration on client instances
52-
53-
If you're using a `Client()` instance, then you should pass any SSL settings when instantiating the client.
37+
Using [the `truststore` package](https://truststore.readthedocs.io/) to support system certificate stores...
5438

5539
```python
56-
client = httpx.Client(verify=False)
57-
```
58-
59-
The `client.get(...)` method and other request methods *do not* support changing the SSL settings on a per-request basis. If you need different SSL settings in different cases you should use more that one client instance, with different settings on each. Each client will then be using an isolated connection pool with a specific fixed SSL configuration on all connections within that pool.
40+
import ssl
41+
import truststore
42+
import httpx
6043

61-
## Client Side Certificates
44+
# Use system certificate stores.
45+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
46+
client = httpx.Client(verify=ctx)
47+
```
6248

63-
You can also specify a local cert to use as a client-side certificate, either a path to an SSL certificate file, or two-tuple of (certificate file, key file), or a three-tuple of (certificate file, key file, password)
49+
Loding an alternative certificate verification store using [the standard SSL context API](https://docs.python.org/3/library/ssl.html)...
6450

6551
```python
66-
cert = "path/to/client.pem"
67-
client = httpx.Client(cert=cert)
68-
response = client.get("https://example.org")
52+
import httpx
53+
import ssl
54+
55+
# Use an explicitly configured certificate store.
56+
ctx = ssl.create_default_context(cafile="path/to/certs.pem") # Either cafile or capath.
57+
client = httpx.Client(verify=ctx)
6958
```
7059

71-
Alternatively...
60+
### Client side certificates
7261

73-
```python
74-
cert = ("path/to/client.pem", "path/to/client.key")
75-
client = httpx.Client(cert=cert)
76-
response = client.get("https://example.org")
77-
```
62+
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.
7863

79-
Or...
64+
You can specify client-side certificates, using the [`.load_cert_chain()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain) API...
8065

8166
```python
82-
cert = ("path/to/client.pem", "path/to/client.key", "password")
83-
client = httpx.Client(cert=cert)
84-
response = client.get("https://example.org")
67+
ctx = ssl.create_default_context()
68+
ctx.load_cert_chain(certfile="path/to/client.pem") # Optionally also keyfile or password.
69+
client = httpx.Client(verify=ctx)
8570
```
8671

87-
## Making HTTPS requests to a local server
72+
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
73+
74+
`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).
75+
76+
### Making HTTPS requests to a local server
8877

8978
When making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.
9079

91-
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:
80+
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...
9281

9382
1. Use [trustme](https://github.com/python-trio/trustme) to generate a pair of server key/cert files, and a client cert file.
94-
1. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)
95-
1. Tell HTTPX to use the certificates stored in `client.pem`:
83+
2. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)
84+
3. Configure `httpx` to use the certificates stored in `client.pem`.
9685

9786
```python
98-
client = httpx.Client(verify="/tmp/client.pem")
99-
response = client.get("https://localhost:8000")
87+
ctx = ssl.create_default_context(cafile="client.pem")
88+
client = httpx.Client(verify=ctx)
10089
```

docs/api.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,18 @@ what gets sent over the wire.*
159159
* `def delete(name, [domain], [path])`
160160
* `def clear([domain], [path])`
161161
* *Standard mutable mapping interface*
162+
163+
## `Proxy`
164+
165+
*A configuration of the proxy server.*
166+
167+
```pycon
168+
>>> proxy = Proxy("http://proxy.example.com:8030")
169+
>>> client = Client(proxy=proxy)
170+
```
171+
172+
* `def __init__(url, [ssl_context], [auth], [headers])`
173+
* `.url` - **URL**
174+
* `.auth` - **tuple[str, str]**
175+
* `.headers` - **Headers**
176+
* `.ssl_context` - **SSLContext**

docs/async.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ To make asynchronous requests, you'll need an `AsyncClient`.
2323
```
2424

2525
!!! tip
26-
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.
26+
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.
2727

2828
## API Differences
2929

0 commit comments

Comments
 (0)