Compare commits
15 Commits
add-socket
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5addb64f0 | ||
|
|
ae1b9f6623 | ||
|
|
ca097c96f9 | ||
|
|
def4778d62 | ||
|
|
435e1dac89 | ||
|
|
4b23574cf8 | ||
|
|
652f051fea | ||
|
|
3fee27838e | ||
|
|
bc00d2bd9f | ||
|
|
767cf6baa6 | ||
|
|
b55d463570 | ||
|
|
15e9759e65 | ||
|
|
364697efca | ||
|
|
89102021fc | ||
|
|
4fb9528c2f |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -15,9 +15,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: "actions/checkout@v4"
|
||||
- uses: "actions/setup-python@v5"
|
||||
- uses: "actions/setup-python@v6"
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- name: "Install dependencies"
|
||||
run: "scripts/install"
|
||||
- name: "Build package & docs"
|
||||
|
||||
4
.github/workflows/test-suite.yml
vendored
4
.github/workflows/test-suite.yml
vendored
@ -14,11 +14,11 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
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
CHANGELOG.md
10
CHANGELOG.md
@ -4,9 +4,15 @@ 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/).
|
||||
|
||||
## Development
|
||||
## [UNRELEASED]
|
||||
|
||||
* Add `socket_options` to `Client` and `AsyncClient` classes. (#3587)
|
||||
### Removed
|
||||
|
||||
* Drop support for Python 3.8
|
||||
|
||||
### Added
|
||||
|
||||
* Expose `FunctionAuth` from the public API. (#3699)
|
||||
|
||||
## 0.28.1 (6th December, 2024)
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ Or, to include the optional HTTP/2 support, use:
|
||||
$ pip install httpx[http2]
|
||||
```
|
||||
|
||||
HTTPX requires Python 3.8+.
|
||||
HTTPX requires Python 3.9+.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ To make asynchronous requests, you'll need an `AsyncClient`.
|
||||
```
|
||||
|
||||
!!! tip
|
||||
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.
|
||||
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.
|
||||
|
||||
## API Differences
|
||||
|
||||
|
||||
@ -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')"
|
||||
```
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 113 KiB |
@ -145,6 +145,6 @@ To include the optional brotli and zstandard decoders support, use:
|
||||
$ pip install httpx[brotli,zstd]
|
||||
```
|
||||
|
||||
HTTPX requires Python 3.8+
|
||||
HTTPX requires Python 3.9+
|
||||
|
||||
[sync-support]: https://github.com/encode/httpx/issues/572
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -29,7 +29,7 @@ from ._exceptions import (
|
||||
from ._models import Cookies, Headers, Request, Response
|
||||
from ._status_codes import codes
|
||||
from ._transports.base import AsyncBaseTransport, BaseTransport
|
||||
from ._transports.default import SOCKET_OPTION, AsyncHTTPTransport, HTTPTransport
|
||||
from ._transports.default import AsyncHTTPTransport, HTTPTransport
|
||||
from ._types import (
|
||||
AsyncByteStream,
|
||||
AuthTypes,
|
||||
@ -653,7 +653,6 @@ class Client(BaseClient):
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
follow_redirects: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
||||
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
||||
base_url: URL | str = "",
|
||||
@ -694,7 +693,6 @@ class Client(BaseClient):
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
transport=transport,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
self._mounts: dict[URLPattern, BaseTransport | None] = {
|
||||
URLPattern(key): None
|
||||
@ -707,7 +705,6 @@ class Client(BaseClient):
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
for key, proxy in proxy_map.items()
|
||||
}
|
||||
@ -726,7 +723,6 @@ class Client(BaseClient):
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
transport: BaseTransport | None = None,
|
||||
) -> BaseTransport:
|
||||
if transport is not None:
|
||||
@ -739,7 +735,6 @@ class Client(BaseClient):
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
|
||||
def _init_proxy_transport(
|
||||
@ -751,7 +746,6 @@ class Client(BaseClient):
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
) -> BaseTransport:
|
||||
return HTTPTransport(
|
||||
verify=verify,
|
||||
@ -761,7 +755,6 @@ class Client(BaseClient):
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
proxy=proxy,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
|
||||
def _transport_for_url(self, url: URL) -> BaseTransport:
|
||||
@ -1373,7 +1366,6 @@ class AsyncClient(BaseClient):
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
follow_redirects: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
max_redirects: int = DEFAULT_MAX_REDIRECTS,
|
||||
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
||||
base_url: URL | str = "",
|
||||
@ -1415,7 +1407,6 @@ class AsyncClient(BaseClient):
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
transport=transport,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
|
||||
self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {
|
||||
@ -1429,7 +1420,6 @@ class AsyncClient(BaseClient):
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
for key, proxy in proxy_map.items()
|
||||
}
|
||||
@ -1447,7 +1437,6 @@ class AsyncClient(BaseClient):
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
transport: AsyncBaseTransport | None = None,
|
||||
) -> AsyncBaseTransport:
|
||||
if transport is not None:
|
||||
@ -1460,7 +1449,6 @@ class AsyncClient(BaseClient):
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
|
||||
def _init_proxy_transport(
|
||||
@ -1472,7 +1460,6 @@ class AsyncClient(BaseClient):
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
||||
) -> AsyncBaseTransport:
|
||||
return AsyncHTTPTransport(
|
||||
verify=verify,
|
||||
@ -1482,7 +1469,6 @@ class AsyncClient(BaseClient):
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
proxy=proxy,
|
||||
socket_options=socket_options,
|
||||
)
|
||||
|
||||
def _transport_for_url(self, url: URL) -> AsyncBaseTransport:
|
||||
|
||||
@ -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(
|
||||
[
|
||||
|
||||
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||
name = "httpx"
|
||||
description = "The next generation HTTP client."
|
||||
license = "BSD-3-Clause"
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
{ name = "Tom Christie", email = "tom@tomchristie.com" },
|
||||
]
|
||||
@ -20,7 +20,6 @@ classifiers = [
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
@ -44,7 +43,7 @@ brotli = [
|
||||
cli = [
|
||||
"click==8.*",
|
||||
"pygments==2.*",
|
||||
"rich>=10,<14",
|
||||
"rich>=10,<15",
|
||||
]
|
||||
http2 = [
|
||||
"h2>=3,<5",
|
||||
|
||||
@ -11,20 +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.1.0; python_version < '3.9'
|
||||
trustme==1.2.0; python_version >= '3.9'
|
||||
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