Merge branch 'master' into master
This commit is contained in:
commit
b34be93072
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@ -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"
|
||||
|
||||
2
.github/workflows/test-suite.yml
vendored
2
.github/workflows/test-suite.yml
vendored
@ -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
|
||||
|
||||
@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
* Drop support for Python 3.8
|
||||
|
||||
### Added
|
||||
|
||||
* Expose `FunctionAuth` from the public API. (#3699)
|
||||
|
||||
## 0.28.1 (6th December, 2024)
|
||||
|
||||
* Fix SSL case where `verify=False` together with client side certificates.
|
||||
|
||||
@ -29,7 +29,7 @@ import certifi
|
||||
import httpx
|
||||
import ssl
|
||||
|
||||
# This SSL context is equivelent to the default `verify=True`.
|
||||
# This SSL context is equivalent to the default `verify=True`.
|
||||
ctx = ssl.create_default_context(cafile=certifi.where())
|
||||
client = httpx.Client(verify=ctx)
|
||||
```
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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/).
|
||||
|
||||
@ -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')"
|
||||
```
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -50,6 +50,7 @@ __all__ = [
|
||||
"DecodingError",
|
||||
"delete",
|
||||
"DigestAuth",
|
||||
"FunctionAuth",
|
||||
"get",
|
||||
"head",
|
||||
"Headers",
|
||||
|
||||
@ -16,7 +16,7 @@ if typing.TYPE_CHECKING: # pragma: no cover
|
||||
from hashlib import _Hash
|
||||
|
||||
|
||||
__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"]
|
||||
__all__ = ["Auth", "BasicAuth", "DigestAuth", "FunctionAuth", "NetRCAuth"]
|
||||
|
||||
|
||||
class Auth:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
@ -379,7 +379,7 @@ class URL:
|
||||
|
||||
if ":" in userinfo:
|
||||
# Mask any password component.
|
||||
userinfo = f'{userinfo.split(":")[0]}:[secure]'
|
||||
userinfo = f"{userinfo.split(':')[0]}:[secure]"
|
||||
|
||||
authority = "".join(
|
||||
[
|
||||
|
||||
@ -43,7 +43,7 @@ brotli = [
|
||||
cli = [
|
||||
"click==8.*",
|
||||
"pygments==2.*",
|
||||
"rich>=10,<14",
|
||||
"rich>=10,<15",
|
||||
]
|
||||
http2 = [
|
||||
"h2>=3,<5",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -1011,7 +1011,10 @@ def test_response_decode_text_using_autodetect():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.reason_phrase == "OK"
|
||||
assert response.encoding == "ISO-8859-1"
|
||||
# The encoded byte string is consistent with either ISO-8859-1 or
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user