Merge branch 'master' into fix/url-params-merge-652

This commit is contained in:
J. F. Zhang 2025-10-21 17:36:32 +08:00 committed by GitHub
commit beb3ec9464
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 68 additions and 48 deletions

View File

@ -15,7 +15,7 @@ jobs:
steps:
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v5"
- uses: "actions/setup-python@v6"
with:
python-version: 3.9
- name: "Install dependencies"

View File

@ -18,7 +18,7 @@ jobs:
steps:
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v5"
- uses: "actions/setup-python@v6"
with:
python-version: "${{ matrix.python-version }}"
allow-prereleases: true

View File

@ -71,19 +71,7 @@ client = httpx.Client(verify=ctx)
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
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). If you want to use these they need to be enabled explicitly.
For example...
```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)
```
`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).
### Making HTTPS requests to a local server

View File

@ -226,3 +226,7 @@ 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.
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/).

View File

@ -51,3 +51,29 @@ 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('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: 1.9 MiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -20,8 +20,6 @@ httpx.get("https://www.example.com")
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: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
@ -80,4 +78,4 @@ logging.config.dictConfig(LOGGING_CONFIG)
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.

View File

@ -191,7 +191,7 @@ You can also explicitly set the filename and content type, by using a tuple
of items for the file value:
```pycon
>>> with open('report.xls', 'rb') report_file:
>>> with open('report.xls', 'rb') as report_file:
... files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}
... r = httpx.post("https://httpbin.org/post", files=files)
>>> print(r.text)

View File

@ -24,6 +24,12 @@ Provides authentication classes to be used with HTTPX's [authentication paramete
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)

View File

@ -331,9 +331,7 @@ class StreamClosed(StreamError):
"""
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)

View File

@ -43,7 +43,7 @@ brotli = [
cli = [
"click==8.*",
"pygments==2.*",
"rich>=10,<14",
"rich>=10,<15",
]
http2 = [
"h2>=3,<5",

View File

@ -11,19 +11,19 @@ chardet==5.2.0
# Documentation
mkdocs==1.6.1
mkautodoc==0.2.0
mkdocs-material==9.5.47
mkdocs-material==9.6.18
# Packaging
build==1.2.2.post1
twine==6.0.1
build==1.3.0
twine==6.1.0
# Tests & Linting
coverage[toml]==7.6.1
cryptography==44.0.1
mypy==1.13.0
pytest==8.3.4
ruff==0.8.1
trio==0.27.0
coverage[toml]==7.10.6
cryptography==45.0.7
mypy==1.17.1
pytest==8.4.1
ruff==0.12.11
trio==0.31.0
trio-typing==0.10.0
trustme==1.2.0
uvicorn==0.32.1
trustme==1.2.1
uvicorn==0.35.0

View File

@ -326,7 +326,7 @@ async def test_auth_property() -> None:
async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:
assert client.auth is None
client.auth = ("user", "password123") # type: ignore
client.auth = ("user", "password123")
assert isinstance(client.auth, httpx.BasicAuth)
url = "https://example.org/"

View File

@ -3,35 +3,35 @@ import httpx
def test_client_base_url():
client = httpx.Client()
client.base_url = "https://www.example.org/" # type: ignore
client.base_url = "https://www.example.org/"
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == "https://www.example.org/"
def test_client_base_url_without_trailing_slash():
client = httpx.Client()
client.base_url = "https://www.example.org/path" # type: ignore
client.base_url = "https://www.example.org/path"
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == "https://www.example.org/path/"
def test_client_base_url_with_trailing_slash():
client = httpx.Client()
client.base_url = "https://www.example.org/path/" # type: ignore
client.base_url = "https://www.example.org/path/"
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == "https://www.example.org/path/"
def test_client_headers():
client = httpx.Client()
client.headers = {"a": "b"} # type: ignore
client.headers = {"a": "b"}
assert isinstance(client.headers, httpx.Headers)
assert client.headers["A"] == "b"
def test_client_cookies():
client = httpx.Client()
client.cookies = {"a": "b"} # type: ignore
client.cookies = {"a": "b"}
assert isinstance(client.cookies, httpx.Cookies)
mycookies = list(client.cookies.jar)
assert len(mycookies) == 1
@ -42,7 +42,7 @@ def test_client_timeout():
expected_timeout = 12.0
client = httpx.Client()
client.timeout = expected_timeout # type: ignore
client.timeout = expected_timeout
assert isinstance(client.timeout, httpx.Timeout)
assert client.timeout.connect == expected_timeout

View File

@ -17,7 +17,7 @@ def test_client_queryparams_string():
assert client.params["a"] == "b"
client = httpx.Client()
client.params = "a=b" # type: ignore
client.params = "a=b"
assert isinstance(client.params, httpx.QueryParams)
assert client.params["a"] == "b"

View File

@ -489,18 +489,18 @@ def test_response_invalid_argument():
def test_ensure_ascii_false_with_french_characters():
data = {"greeting": "Bonjour, ça va ?"}
response = httpx.Response(200, json=data)
assert (
"ça va" in response.text
), "ensure_ascii=False should preserve French accented characters"
assert "ça va" in response.text, (
"ensure_ascii=False should preserve French accented characters"
)
assert response.headers["Content-Type"] == "application/json"
def test_separators_for_compact_json():
data = {"clé": "valeur", "liste": [1, 2, 3]}
response = httpx.Response(200, json=data)
assert (
response.text == '{"clé":"valeur","liste":[1,2,3]}'
), "separators=(',', ':') should produce a compact representation"
assert response.text == '{"clé":"valeur","liste":[1,2,3]}', (
"separators=(',', ':') should produce a compact representation"
)
assert response.headers["Content-Type"] == "application/json"