The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
-## 0.28.0 (...)
+## 0.28.0 (28th November, 2024)
-The 0.28 release includes a limited set of backwards incompatible changes.
+The 0.28 release includes a limited set of deprecations.
-**Backwards incompatible changes**:
+**Deprecations**:
-SSL configuration has been significantly simplified.
+We are working towards a simplified SSL configuration API.
-* 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.
+*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 users of the standard `verify=True` or `verify=False` cases this should require no changes.
+* The `verify` argument as a string argument is now deprecated and will raise warnings.
+* The `cert` argument is now deprecated and will raise warnings.
-For information on configuring more complex SSL cases, please see the [SSL documentation](docs/advanced/ssl.md).
+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)
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:
"""
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]:
"""
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:
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:
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:
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:
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:
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:
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:
"""
from ._types import (
AsyncByteStream,
AuthTypes,
+ CertTypes,
CookieTypes,
HeaderTypes,
ProxyTypes,
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,
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__(
self._transport = self._init_transport(
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
else self._init_proxy_transport(
proxy,
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
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,
return HTTPTransport(
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
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,
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,
self._transport = self._init_transport(
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
else self._init_proxy_transport(
proxy,
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
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,
return AsyncHTTPTransport(
verify=verify,
+ cert=cert,
+ trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
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,
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:
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
elif isinstance(verify, str): # pragma: nocover
- # Explicitly handle this deprecated usage pattern.
- msg = (
- "verify should be a boolean or SSLContext, since version 0.28. "
+ message = (
+ "`verify=<str>` is deprecated. "
"Use `verify=ssl.create_default_context(cafile=...)` "
- "or `verify=ssl.create_default_context(capath=...)`."
+ "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
+
+ if cert: # pragma: nocover
+ message = (
+ "`cert=...` is deprecated. Use `verify=<ssl_context>` instead,"
+ "with `.load_cert_chain()` to configure the certificate chain."
)
- raise RuntimeError(msg)
+ warnings.warn(message, DeprecationWarning)
+ if isinstance(cert, str):
+ ctx.load_cert_chain(cert)
+ else:
+ ctx.load_cert_chain(*cert)
- return verify
+ return ctx
class Timeout:
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
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,
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(
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,
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(
"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]],