Compare commits
2 Commits
master
...
add-socket
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f88fa846d9 | ||
|
|
2f737cadc1 |
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.8", "3.9", "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
|
||||||
|
|||||||
10
CHANGELOG.md
10
CHANGELOG.md
@ -4,15 +4,9 @@ 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]
|
## Development
|
||||||
|
|
||||||
### Removed
|
* Add `socket_options` to `Client` and `AsyncClient` classes. (#3587)
|
||||||
|
|
||||||
* Drop support for Python 3.8
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Expose `FunctionAuth` from the public API. (#3699)
|
|
||||||
|
|
||||||
## 0.28.1 (6th December, 2024)
|
## 0.28.1 (6th December, 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.8+.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import certifi
|
|||||||
import httpx
|
import httpx
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
# This SSL context is equivalent to the default `verify=True`.
|
# This SSL context is equivelent to the default `verify=True`.
|
||||||
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)
|
||||||
```
|
```
|
||||||
@ -71,7 +71,19 @@ 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). 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)
|
||||||
|
```
|
||||||
|
|
||||||
### Making HTTPS requests to a local server
|
### Making HTTPS requests to a local server
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -191,7 +191,7 @@ 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:
|
>>> with open('report.xls', 'rb') report_file:
|
||||||
... files = {'upload-file': ('report.xls', report_file, '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)
|
||||||
|
|||||||
@ -24,12 +24,6 @@ Provides authentication classes to be used with HTTPX's [authentication paramete
|
|||||||
|
|
||||||
This package adds caching functionality to HTTPX
|
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
|
### httpx-socks
|
||||||
|
|
||||||
[GitHub](https://github.com/romis2012/httpx-socks)
|
[GitHub](https://github.com/romis2012/httpx-socks)
|
||||||
|
|||||||
@ -50,7 +50,6 @@ __all__ = [
|
|||||||
"DecodingError",
|
"DecodingError",
|
||||||
"delete",
|
"delete",
|
||||||
"DigestAuth",
|
"DigestAuth",
|
||||||
"FunctionAuth",
|
|
||||||
"get",
|
"get",
|
||||||
"head",
|
"head",
|
||||||
"Headers",
|
"Headers",
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -29,7 +29,7 @@ from ._exceptions import (
|
|||||||
from ._models import Cookies, Headers, Request, Response
|
from ._models import Cookies, Headers, Request, Response
|
||||||
from ._status_codes import codes
|
from ._status_codes import codes
|
||||||
from ._transports.base import AsyncBaseTransport, BaseTransport
|
from ._transports.base import AsyncBaseTransport, BaseTransport
|
||||||
from ._transports.default import AsyncHTTPTransport, HTTPTransport
|
from ._transports.default import SOCKET_OPTION, AsyncHTTPTransport, HTTPTransport
|
||||||
from ._types import (
|
from ._types import (
|
||||||
AsyncByteStream,
|
AsyncByteStream,
|
||||||
AuthTypes,
|
AuthTypes,
|
||||||
@ -653,6 +653,7 @@ class Client(BaseClient):
|
|||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
||||||
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 = "",
|
||||||
@ -693,6 +694,7 @@ class Client(BaseClient):
|
|||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
transport=transport,
|
transport=transport,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
self._mounts: dict[URLPattern, BaseTransport | None] = {
|
self._mounts: dict[URLPattern, BaseTransport | None] = {
|
||||||
URLPattern(key): None
|
URLPattern(key): None
|
||||||
@ -705,6 +707,7 @@ class Client(BaseClient):
|
|||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
for key, proxy in proxy_map.items()
|
for key, proxy in proxy_map.items()
|
||||||
}
|
}
|
||||||
@ -723,6 +726,7 @@ class Client(BaseClient):
|
|||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
transport: BaseTransport | None = None,
|
transport: BaseTransport | None = None,
|
||||||
) -> BaseTransport:
|
) -> BaseTransport:
|
||||||
if transport is not None:
|
if transport is not None:
|
||||||
@ -735,6 +739,7 @@ class Client(BaseClient):
|
|||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _init_proxy_transport(
|
def _init_proxy_transport(
|
||||||
@ -746,6 +751,7 @@ class Client(BaseClient):
|
|||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
) -> BaseTransport:
|
) -> BaseTransport:
|
||||||
return HTTPTransport(
|
return HTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
@ -755,6 +761,7 @@ class Client(BaseClient):
|
|||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
proxy=proxy,
|
proxy=proxy,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _transport_for_url(self, url: URL) -> BaseTransport:
|
def _transport_for_url(self, url: URL) -> BaseTransport:
|
||||||
@ -1366,6 +1373,7 @@ class AsyncClient(BaseClient):
|
|||||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
||||||
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 = "",
|
||||||
@ -1407,6 +1415,7 @@ class AsyncClient(BaseClient):
|
|||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
transport=transport,
|
transport=transport,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {
|
self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {
|
||||||
@ -1420,6 +1429,7 @@ class AsyncClient(BaseClient):
|
|||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
for key, proxy in proxy_map.items()
|
for key, proxy in proxy_map.items()
|
||||||
}
|
}
|
||||||
@ -1437,6 +1447,7 @@ class AsyncClient(BaseClient):
|
|||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
transport: AsyncBaseTransport | None = None,
|
transport: AsyncBaseTransport | None = None,
|
||||||
) -> AsyncBaseTransport:
|
) -> AsyncBaseTransport:
|
||||||
if transport is not None:
|
if transport is not None:
|
||||||
@ -1449,6 +1460,7 @@ class AsyncClient(BaseClient):
|
|||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _init_proxy_transport(
|
def _init_proxy_transport(
|
||||||
@ -1460,6 +1472,7 @@ class AsyncClient(BaseClient):
|
|||||||
http1: bool = True,
|
http1: bool = True,
|
||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
|
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||||
) -> AsyncBaseTransport:
|
) -> AsyncBaseTransport:
|
||||||
return AsyncHTTPTransport(
|
return AsyncHTTPTransport(
|
||||||
verify=verify,
|
verify=verify,
|
||||||
@ -1469,6 +1482,7 @@ class AsyncClient(BaseClient):
|
|||||||
http2=http2,
|
http2=http2,
|
||||||
limits=limits,
|
limits=limits,
|
||||||
proxy=proxy,
|
proxy=proxy,
|
||||||
|
socket_options=socket_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
|
def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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,6 +20,7 @@ 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",
|
||||||
@ -43,7 +44,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,20 @@ 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.47
|
||||||
|
|
||||||
# Packaging
|
# Packaging
|
||||||
build==1.3.0
|
build==1.2.2.post1
|
||||||
twine==6.1.0
|
twine==6.0.1
|
||||||
|
|
||||||
# Tests & Linting
|
# Tests & Linting
|
||||||
coverage[toml]==7.10.6
|
coverage[toml]==7.6.1
|
||||||
cryptography==45.0.7
|
cryptography==44.0.1
|
||||||
mypy==1.17.1
|
mypy==1.13.0
|
||||||
pytest==8.4.1
|
pytest==8.3.4
|
||||||
ruff==0.12.11
|
ruff==0.8.1
|
||||||
trio==0.31.0
|
trio==0.27.0
|
||||||
trio-typing==0.10.0
|
trio-typing==0.10.0
|
||||||
trustme==1.2.1
|
trustme==1.1.0; python_version < '3.9'
|
||||||
uvicorn==0.35.0
|
trustme==1.2.0; python_version >= '3.9'
|
||||||
|
uvicorn==0.32.1
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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