Merge branch 'master' into feature/allow-multipart-without-files
This commit is contained in:
commit
d543147c8d
31
CHANGELOG.md
31
CHANGELOG.md
@ -4,32 +4,35 @@ 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/).
|
||||
|
||||
## [Unreleased]
|
||||
## Dev
|
||||
|
||||
The 0.28 release includes a limited set of backwards incompatible changes.
|
||||
* Fix SSL case where `verify=False` together with client side certificates.
|
||||
|
||||
## 0.28.0 (28th November, 2024)
|
||||
|
||||
**Backwards incompatible changes**:
|
||||
The 0.28 release includes a limited set of deprecations.
|
||||
|
||||
SSL configuration has been significantly simplified.
|
||||
**Deprecations**:
|
||||
|
||||
* The `verify` argument no longer accepts string arguments.
|
||||
* The `cert` argument has now been removed.
|
||||
* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer automatically used.
|
||||
We are working towards a simplified SSL configuration API.
|
||||
|
||||
For users of the standard `verify=True` or `verify=False` cases this should require no changes.
|
||||
*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...*
|
||||
|
||||
For information on configuring more complex SSL cases, please see the [SSL documentation](docs/advanced/ssl.md).
|
||||
* The `verify` argument as a string argument is now deprecated and will raise warnings.
|
||||
* The `cert` argument is now deprecated and will raise warnings.
|
||||
|
||||
Our revised [SSL documentation](docs/advanced/ssl.md) covers how to implement the same behaviour with a more constrained API.
|
||||
|
||||
**The following changes are also included**:
|
||||
|
||||
* The undocumented `URL.raw` property has now been deprecated, and will raise warnings.
|
||||
* The deprecated `proxies` argument has now been removed.
|
||||
* The deprecated `app` argument has now been removed.
|
||||
* Ensure JSON request bodies are compact. (#3363)
|
||||
* JSON request bodies use a compact representation. (#3363)
|
||||
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
|
||||
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
|
||||
* Treat `socks5h` as a valid proxy scheme. (#3178)
|
||||
* Cleanup `Request()` method signature in line with `client.request()` and `httpx.request()`. (#3378)
|
||||
* Bugfix: When passing `params={}`, always strictly update rather than merge with an existing querystring. (#3364)
|
||||
|
||||
## 0.27.2 (27th August, 2024)
|
||||
|
||||
@ -617,7 +620,7 @@ See pull requests #1057, #1058.
|
||||
|
||||
* Added dedicated exception class `httpx.HTTPStatusError` for `.raise_for_status()` exceptions. (Pull #1072)
|
||||
* Added `httpx.create_ssl_context()` helper function. (Pull #996)
|
||||
* Support for proxy exlcusions like `proxies={"https://www.example.com": None}`. (Pull #1099)
|
||||
* Support for proxy exclusions like `proxies={"https://www.example.com": None}`. (Pull #1099)
|
||||
* Support `QueryParams(None)` and `client.params = None`. (Pull #1060)
|
||||
|
||||
### Changed
|
||||
@ -845,7 +848,7 @@ We believe the API is now pretty much stable, and are aiming for a 1.0 release s
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix issue with concurrent connection acquiry. (Pull #700)
|
||||
- Fix issue with concurrent connection acquisition. (Pull #700)
|
||||
- Fix write error on closing HTTP/2 connections. (Pull #699)
|
||||
|
||||
## 0.10.0 (December 29th, 2019)
|
||||
@ -1094,7 +1097,7 @@ importing modules within the package.
|
||||
|
||||
## 0.6.7 (July 8, 2019)
|
||||
|
||||
- Check for connection aliveness on re-acquiry (Pull #111)
|
||||
- Check for connection aliveness on re-acquisition (Pull #111)
|
||||
|
||||
## 0.6.6 (July 3, 2019)
|
||||
|
||||
|
||||
@ -13,9 +13,7 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
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**.
|
||||
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**.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
__title__ = "httpx"
|
||||
__description__ = "A next generation HTTP client, for Python 3."
|
||||
__version__ = "0.27.2"
|
||||
__version__ = "0.28.0"
|
||||
|
||||
@ -51,7 +51,7 @@ def request(
|
||||
proxy: ProxyTypes | None = None,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
"""
|
||||
@ -136,7 +136,7 @@ def stream(
|
||||
proxy: ProxyTypes | None = None,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
trust_env: bool = True,
|
||||
) -> typing.Iterator[Response]:
|
||||
"""
|
||||
@ -180,7 +180,7 @@ def get(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -216,7 +216,7 @@ def options(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -252,7 +252,7 @@ def head(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -292,7 +292,7 @@ def post(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -333,7 +333,7 @@ def put(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -374,7 +374,7 @@ def patch(
|
||||
auth: AuthTypes | None = None,
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
@ -412,7 +412,7 @@ def delete(
|
||||
proxy: ProxyTypes | None = None,
|
||||
follow_redirects: bool = False,
|
||||
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
trust_env: bool = True,
|
||||
) -> Response:
|
||||
"""
|
||||
|
||||
@ -33,6 +33,7 @@ from ._transports.default import AsyncHTTPTransport, HTTPTransport
|
||||
from ._types import (
|
||||
AsyncByteStream,
|
||||
AuthTypes,
|
||||
CertTypes,
|
||||
CookieTypes,
|
||||
HeaderTypes,
|
||||
ProxyTypes,
|
||||
@ -619,8 +620,6 @@ class Client(BaseClient):
|
||||
* **http2** - *(optional)* A boolean indicating if HTTP/2 support should be
|
||||
enabled. Defaults to `False`.
|
||||
* **proxy** - *(optional)* A proxy URL where all the traffic should be routed.
|
||||
* **proxies** - *(optional)* A dictionary mapping proxy keys to proxy
|
||||
URLs.
|
||||
* **timeout** - *(optional)* The timeout configuration to use when sending
|
||||
requests.
|
||||
* **limits** - *(optional)* The limits configuration to use.
|
||||
@ -644,7 +643,9 @@ class Client(BaseClient):
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: HeaderTypes | None = None,
|
||||
cookies: CookieTypes | None = None,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
proxy: ProxyTypes | None = None,
|
||||
@ -656,7 +657,6 @@ class Client(BaseClient):
|
||||
event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,
|
||||
base_url: URL | str = "",
|
||||
transport: BaseTransport | None = None,
|
||||
trust_env: bool = True,
|
||||
default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
|
||||
) -> None:
|
||||
super().__init__(
|
||||
@ -687,6 +687,8 @@ class Client(BaseClient):
|
||||
|
||||
self._transport = self._init_transport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -698,6 +700,8 @@ class Client(BaseClient):
|
||||
else self._init_proxy_transport(
|
||||
proxy,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -713,7 +717,9 @@ class Client(BaseClient):
|
||||
|
||||
def _init_transport(
|
||||
self,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
@ -724,6 +730,8 @@ class Client(BaseClient):
|
||||
|
||||
return HTTPTransport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -732,13 +740,17 @@ class Client(BaseClient):
|
||||
def _init_proxy_transport(
|
||||
self,
|
||||
proxy: Proxy,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
) -> BaseTransport:
|
||||
return HTTPTransport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -1345,7 +1357,8 @@ class AsyncClient(BaseClient):
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: HeaderTypes | None = None,
|
||||
cookies: CookieTypes | None = None,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
proxy: ProxyTypes | None = None,
|
||||
@ -1388,6 +1401,8 @@ class AsyncClient(BaseClient):
|
||||
|
||||
self._transport = self._init_transport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -1400,6 +1415,8 @@ class AsyncClient(BaseClient):
|
||||
else self._init_proxy_transport(
|
||||
proxy,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -1414,7 +1431,9 @@ class AsyncClient(BaseClient):
|
||||
|
||||
def _init_transport(
|
||||
self,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
@ -1425,6 +1444,8 @@ class AsyncClient(BaseClient):
|
||||
|
||||
return AsyncHTTPTransport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
@ -1433,13 +1454,17 @@ class AsyncClient(BaseClient):
|
||||
def _init_proxy_transport(
|
||||
self,
|
||||
proxy: Proxy,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
) -> AsyncBaseTransport:
|
||||
return AsyncHTTPTransport(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http1=http1,
|
||||
http2=http2,
|
||||
limits=limits,
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import typing
|
||||
|
||||
from ._models import Headers
|
||||
from ._types import HeaderTypes, TimeoutTypes
|
||||
from ._types import CertTypes, HeaderTypes, TimeoutTypes
|
||||
from ._urls import URL
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -19,20 +20,53 @@ class UnsetType:
|
||||
UNSET = UnsetType()
|
||||
|
||||
|
||||
def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext:
|
||||
def create_ssl_context(
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
) -> ssl.SSLContext:
|
||||
import ssl
|
||||
import warnings
|
||||
|
||||
import certifi
|
||||
|
||||
if verify is True:
|
||||
return ssl.create_default_context(cafile=certifi.where())
|
||||
if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover
|
||||
ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"])
|
||||
elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover
|
||||
ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"])
|
||||
else:
|
||||
# Default case...
|
||||
ctx = ssl.create_default_context(cafile=certifi.where())
|
||||
elif verify is False:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
return ssl_context
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
elif isinstance(verify, str): # pragma: nocover
|
||||
message = (
|
||||
"`verify=<str>` is deprecated. "
|
||||
"Use `verify=ssl.create_default_context(cafile=...)` "
|
||||
"or `verify=ssl.create_default_context(capath=...)` instead."
|
||||
)
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
if os.path.isdir(verify):
|
||||
return ssl.create_default_context(capath=verify)
|
||||
return ssl.create_default_context(cafile=verify)
|
||||
else:
|
||||
ctx = verify
|
||||
|
||||
return verify
|
||||
if cert: # pragma: nocover
|
||||
message = (
|
||||
"`cert=...` is deprecated. Use `verify=<ssl_context>` instead,"
|
||||
"with `.load_cert_chain()` to configure the certificate chain."
|
||||
)
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
if isinstance(cert, str):
|
||||
ctx.load_cert_chain(cert)
|
||||
else:
|
||||
ctx.load_cert_chain(*cert)
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class Timeout:
|
||||
|
||||
@ -175,9 +175,11 @@ class ZStandardDecoder(ContentDecoder):
|
||||
) from None
|
||||
|
||||
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
|
||||
self.seen_data = False
|
||||
|
||||
def decode(self, data: bytes) -> bytes:
|
||||
assert zstandard is not None
|
||||
self.seen_data = True
|
||||
output = io.BytesIO()
|
||||
try:
|
||||
output.write(self.decompressor.decompress(data))
|
||||
@ -190,6 +192,8 @@ class ZStandardDecoder(ContentDecoder):
|
||||
return output.getvalue()
|
||||
|
||||
def flush(self) -> bytes:
|
||||
if not self.seen_data:
|
||||
return b""
|
||||
ret = self.decompressor.flush() # note: this is a no-op
|
||||
if not self.decompressor.eof:
|
||||
raise DecodingError("Zstandard data is incomplete") # pragma: no cover
|
||||
|
||||
@ -398,7 +398,7 @@ class Request:
|
||||
self.method = method.upper()
|
||||
self.url = URL(url) if params is None else URL(url, params=params)
|
||||
self.headers = Headers(headers)
|
||||
self.extensions = {} if extensions is None else extensions
|
||||
self.extensions = {} if extensions is None else dict(extensions)
|
||||
|
||||
if cookies:
|
||||
Cookies(cookies).set_cookie_header(self)
|
||||
@ -537,7 +537,7 @@ class Response:
|
||||
# the client will set `response.next_request`.
|
||||
self.next_request: Request | None = None
|
||||
|
||||
self.extensions: ResponseExtensions = {} if extensions is None else extensions
|
||||
self.extensions = {} if extensions is None else dict(extensions)
|
||||
self.history = [] if history is None else list(history)
|
||||
|
||||
self.is_closed = False
|
||||
|
||||
@ -53,7 +53,7 @@ from .._exceptions import (
|
||||
WriteTimeout,
|
||||
)
|
||||
from .._models import Request, Response
|
||||
from .._types import AsyncByteStream, ProxyTypes, SyncByteStream
|
||||
from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream
|
||||
from .._urls import URL
|
||||
from .base import AsyncBaseTransport, BaseTransport
|
||||
|
||||
@ -135,7 +135,9 @@ class ResponseStream(SyncByteStream):
|
||||
class HTTPTransport(BaseTransport):
|
||||
def __init__(
|
||||
self,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
@ -148,7 +150,7 @@ class HTTPTransport(BaseTransport):
|
||||
import httpcore
|
||||
|
||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||
ssl_context = create_ssl_context(verify=verify)
|
||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
||||
|
||||
if proxy is None:
|
||||
self._pool = httpcore.ConnectionPool(
|
||||
@ -277,7 +279,9 @@ class AsyncResponseStream(AsyncByteStream):
|
||||
class AsyncHTTPTransport(AsyncBaseTransport):
|
||||
def __init__(
|
||||
self,
|
||||
verify: ssl.SSLContext | bool = True,
|
||||
verify: ssl.SSLContext | str | bool = True,
|
||||
cert: CertTypes | None = None,
|
||||
trust_env: bool = True,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
@ -290,7 +294,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
||||
import httpcore
|
||||
|
||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||
ssl_context = create_ssl_context(verify=verify)
|
||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
||||
|
||||
if proxy is None:
|
||||
self._pool = httpcore.AsyncConnectionPool(
|
||||
|
||||
@ -15,7 +15,6 @@ from typing import (
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
@ -58,6 +57,7 @@ TimeoutTypes = Union[
|
||||
"Timeout",
|
||||
]
|
||||
ProxyTypes = Union["URL", str, "Proxy"]
|
||||
CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]
|
||||
|
||||
AuthTypes = Union[
|
||||
Tuple[Union[str, bytes], Union[str, bytes]],
|
||||
@ -67,7 +67,7 @@ AuthTypes = Union[
|
||||
|
||||
RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
||||
ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]
|
||||
ResponseExtensions = MutableMapping[str, Any]
|
||||
ResponseExtensions = Mapping[str, Any]
|
||||
|
||||
RequestData = Mapping[str, Any]
|
||||
|
||||
@ -84,7 +84,7 @@ FileTypes = Union[
|
||||
]
|
||||
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
|
||||
|
||||
RequestExtensions = MutableMapping[str, Any]
|
||||
RequestExtensions = Mapping[str, Any]
|
||||
|
||||
__all__ = ["AsyncByteStream", "SyncByteStream"]
|
||||
|
||||
|
||||
@ -11,19 +11,20 @@ chardet==5.2.0
|
||||
# Documentation
|
||||
mkdocs==1.6.1
|
||||
mkautodoc==0.2.0
|
||||
mkdocs-material==9.5.39
|
||||
mkdocs-material==9.5.47
|
||||
|
||||
# Packaging
|
||||
build==1.2.2
|
||||
twine==5.1.1
|
||||
build==1.2.2.post1
|
||||
twine==6.0.1
|
||||
|
||||
# Tests & Linting
|
||||
coverage[toml]==7.6.1
|
||||
cryptography==43.0.1
|
||||
mypy==1.11.2
|
||||
pytest==8.3.3
|
||||
ruff==0.6.8
|
||||
trio==0.26.2
|
||||
cryptography==44.0.0
|
||||
mypy==1.13.0
|
||||
pytest==8.3.4
|
||||
ruff==0.8.1
|
||||
trio==0.27.0
|
||||
trio-typing==0.10.0
|
||||
trustme==1.1.0
|
||||
uvicorn==0.31.0
|
||||
trustme==1.1.0; python_version < '3.9'
|
||||
trustme==1.2.0; python_version >= '3.9'
|
||||
uvicorn==0.32.1
|
||||
|
||||
@ -226,3 +226,16 @@ def test_request_generator_content_picklable():
|
||||
request.read()
|
||||
pickle_request = pickle.loads(pickle.dumps(request))
|
||||
assert pickle_request.content == b"test 123"
|
||||
|
||||
|
||||
def test_request_params():
|
||||
request = httpx.Request("GET", "http://example.com", params={})
|
||||
assert str(request.url) == "http://example.com"
|
||||
|
||||
request = httpx.Request(
|
||||
"GET", "http://example.com?c=3", params={"a": "1", "b": "2"}
|
||||
)
|
||||
assert str(request.url) == "http://example.com?a=1&b=2"
|
||||
|
||||
request = httpx.Request("GET", "http://example.com?a=1", params={})
|
||||
assert str(request.url) == "http://example.com"
|
||||
|
||||
@ -100,6 +100,25 @@ def test_zstd_decoding_error():
|
||||
)
|
||||
|
||||
|
||||
def test_zstd_empty():
|
||||
headers = [(b"Content-Encoding", b"zstd")]
|
||||
response = httpx.Response(200, headers=headers, content=b"")
|
||||
assert response.content == b""
|
||||
|
||||
|
||||
def test_zstd_truncated():
|
||||
body = b"test 123"
|
||||
compressed_body = zstd.compress(body)
|
||||
|
||||
headers = [(b"Content-Encoding", b"zstd")]
|
||||
with pytest.raises(httpx.DecodingError):
|
||||
httpx.Response(
|
||||
200,
|
||||
headers=headers,
|
||||
content=compressed_body[1:3],
|
||||
)
|
||||
|
||||
|
||||
def test_zstd_multiframe():
|
||||
# test inspired by urllib3 test suite
|
||||
data = (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user