Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.28.0. #3419

Merged
merged 10 commits into from
Nov 28, 2024
18 changes: 8 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@ 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/).

## 0.28.0 (...)
## 0.28.0 (28th August, 2024)
tomchristie marked this conversation as resolved.
Show resolved Hide resolved

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.

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)
Expand Down
18 changes: 9 additions & 9 deletions httpx/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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]:
"""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
"""
Expand Down
41 changes: 34 additions & 7 deletions httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ._types import (
AsyncByteStream,
AuthTypes,
CertTypes,
CookieTypes,
HeaderTypes,
ProxyTypes,
Expand Down Expand Up @@ -644,7 +645,9 @@ def __init__(
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,
Expand All @@ -656,7 +659,6 @@ def __init__(
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__(
Expand Down Expand Up @@ -687,6 +689,8 @@ def __init__(

self._transport = self._init_transport(
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -698,6 +702,8 @@ def __init__(
else self._init_proxy_transport(
proxy,
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -713,7 +719,9 @@ def __init__(

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,
Expand All @@ -724,6 +732,8 @@ def _init_transport(

return HTTPTransport(
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -732,13 +742,17 @@ def _init_transport(
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,
Expand Down Expand Up @@ -1345,7 +1359,8 @@ def __init__(
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,
Expand Down Expand Up @@ -1388,6 +1403,8 @@ def __init__(

self._transport = self._init_transport(
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -1400,6 +1417,8 @@ def __init__(
else self._init_proxy_transport(
proxy,
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -1414,7 +1433,9 @@ def __init__(

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,
Expand All @@ -1425,6 +1446,8 @@ def _init_transport(

return AsyncHTTPTransport(
verify=verify,
cert=cert,
trust_env=trust_env,
http1=http1,
http2=http2,
limits=limits,
Expand All @@ -1433,13 +1456,17 @@ def _init_transport(
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,
Expand Down
45 changes: 36 additions & 9 deletions httpx/_config.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -19,28 +20,54 @@ 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
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:
Expand Down
14 changes: 9 additions & 5 deletions httpx/_transports/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
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

Expand Down Expand Up @@ -135,7 +135,9 @@ def close(self) -> None:
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,
Expand All @@ -148,7 +150,7 @@ def __init__(
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(
Expand Down Expand Up @@ -277,7 +279,9 @@ async def aclose(self) -> None:
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,
Expand All @@ -290,7 +294,7 @@ def __init__(
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(
Expand Down
1 change: 1 addition & 0 deletions httpx/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"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]],
Expand Down