made dependencies on certifi and httpcore only load when required (#3377)
Co-authored-by: Tom Christie <tom@tomchristie.com>
This commit is contained in:
parent
eeb5e3c2a3
commit
e9cabc8e1d
@ -4,9 +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/).
|
||||
|
||||
## Version 0.28.0
|
||||
## [Unreleased]
|
||||
|
||||
Version 0.28.0 introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
|
||||
This release introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
|
||||
|
||||
* Added `httpx.SSLContext` class and `ssl_context` parameter. (#3022, #3335)
|
||||
* The `verify` and `cert` arguments have been deprecated and will now raise warnings. (#3022, #3335)
|
||||
@ -15,6 +15,7 @@ Version 0.28.0 introduces an `httpx.SSLContext()` class and `ssl_context` parame
|
||||
* The `URL.raw` property has now been removed.
|
||||
* Ensure JSON request bodies are compact. (#3363)
|
||||
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
|
||||
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
|
||||
|
||||
## 0.27.2 (27th August, 2024)
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
__title__ = "httpx"
|
||||
__description__ = "A next generation HTTP client, for Python 3."
|
||||
__version__ = "0.28.0"
|
||||
__version__ = "0.27.2"
|
||||
|
||||
@ -6,8 +6,6 @@ import sys
|
||||
import typing
|
||||
import warnings
|
||||
|
||||
import certifi
|
||||
|
||||
from ._models import Headers
|
||||
from ._types import HeaderTypes, TimeoutTypes
|
||||
from ._urls import URL
|
||||
@ -77,6 +75,8 @@ class SSLContext(ssl.SSLContext):
|
||||
self,
|
||||
verify: bool = True,
|
||||
) -> None:
|
||||
import certifi
|
||||
|
||||
# ssl.SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
|
||||
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
|
||||
# by default. (from `ssl.create_default_context`)
|
||||
|
||||
@ -6,7 +6,6 @@ import sys
|
||||
import typing
|
||||
|
||||
import click
|
||||
import httpcore
|
||||
import pygments.lexers
|
||||
import pygments.util
|
||||
import rich.console
|
||||
@ -21,6 +20,9 @@ from ._exceptions import RequestError
|
||||
from ._models import Response
|
||||
from ._status_codes import codes
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import httpcore # pragma: no cover
|
||||
|
||||
|
||||
def print_help() -> None:
|
||||
console = rich.console.Console()
|
||||
|
||||
@ -27,11 +27,13 @@ client = httpx.Client(transport=transport)
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import ssl
|
||||
import typing
|
||||
from types import TracebackType
|
||||
|
||||
import httpcore
|
||||
if typing.TYPE_CHECKING:
|
||||
import ssl # pragma: no cover
|
||||
|
||||
import httpx # pragma: no cover
|
||||
|
||||
from .._config import DEFAULT_LIMITS, Limits, Proxy, SSLContext, create_ssl_context
|
||||
from .._exceptions import (
|
||||
@ -66,9 +68,35 @@ SOCKET_OPTION = typing.Union[
|
||||
|
||||
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
|
||||
|
||||
HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}
|
||||
|
||||
|
||||
def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:
|
||||
import httpcore
|
||||
|
||||
return {
|
||||
httpcore.TimeoutException: TimeoutException,
|
||||
httpcore.ConnectTimeout: ConnectTimeout,
|
||||
httpcore.ReadTimeout: ReadTimeout,
|
||||
httpcore.WriteTimeout: WriteTimeout,
|
||||
httpcore.PoolTimeout: PoolTimeout,
|
||||
httpcore.NetworkError: NetworkError,
|
||||
httpcore.ConnectError: ConnectError,
|
||||
httpcore.ReadError: ReadError,
|
||||
httpcore.WriteError: WriteError,
|
||||
httpcore.ProxyError: ProxyError,
|
||||
httpcore.UnsupportedProtocol: UnsupportedProtocol,
|
||||
httpcore.ProtocolError: ProtocolError,
|
||||
httpcore.LocalProtocolError: LocalProtocolError,
|
||||
httpcore.RemoteProtocolError: RemoteProtocolError,
|
||||
}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def map_httpcore_exceptions() -> typing.Iterator[None]:
|
||||
global HTTPCORE_EXC_MAP
|
||||
if len(HTTPCORE_EXC_MAP) == 0:
|
||||
HTTPCORE_EXC_MAP = _load_httpcore_exceptions()
|
||||
try:
|
||||
yield
|
||||
except Exception as exc:
|
||||
@ -90,24 +118,6 @@ def map_httpcore_exceptions() -> typing.Iterator[None]:
|
||||
raise mapped_exc(message) from exc
|
||||
|
||||
|
||||
HTTPCORE_EXC_MAP = {
|
||||
httpcore.TimeoutException: TimeoutException,
|
||||
httpcore.ConnectTimeout: ConnectTimeout,
|
||||
httpcore.ReadTimeout: ReadTimeout,
|
||||
httpcore.WriteTimeout: WriteTimeout,
|
||||
httpcore.PoolTimeout: PoolTimeout,
|
||||
httpcore.NetworkError: NetworkError,
|
||||
httpcore.ConnectError: ConnectError,
|
||||
httpcore.ReadError: ReadError,
|
||||
httpcore.WriteError: WriteError,
|
||||
httpcore.ProxyError: ProxyError,
|
||||
httpcore.UnsupportedProtocol: UnsupportedProtocol,
|
||||
httpcore.ProtocolError: ProtocolError,
|
||||
httpcore.LocalProtocolError: LocalProtocolError,
|
||||
httpcore.RemoteProtocolError: RemoteProtocolError,
|
||||
}
|
||||
|
||||
|
||||
class ResponseStream(SyncByteStream):
|
||||
def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None:
|
||||
self._httpcore_stream = httpcore_stream
|
||||
@ -138,6 +148,8 @@ class HTTPTransport(BaseTransport):
|
||||
verify: typing.Any = None,
|
||||
cert: typing.Any = None,
|
||||
) -> None:
|
||||
import httpcore
|
||||
|
||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||
if verify is not None or cert is not None: # pragma: nocover
|
||||
# Deprecated...
|
||||
@ -225,6 +237,7 @@ class HTTPTransport(BaseTransport):
|
||||
request: Request,
|
||||
) -> Response:
|
||||
assert isinstance(request.stream, SyncByteStream)
|
||||
import httpcore
|
||||
|
||||
req = httpcore.Request(
|
||||
method=request.method,
|
||||
@ -284,6 +297,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
||||
verify: typing.Any = None,
|
||||
cert: typing.Any = None,
|
||||
) -> None:
|
||||
import httpcore
|
||||
|
||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||
if verify is not None or cert is not None: # pragma: nocover
|
||||
# Deprecated...
|
||||
@ -371,6 +386,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
||||
request: Request,
|
||||
) -> Response:
|
||||
assert isinstance(request.stream, AsyncByteStream)
|
||||
import httpcore
|
||||
|
||||
req = httpcore.Request(
|
||||
method=request.method,
|
||||
|
||||
@ -85,3 +85,18 @@ def test_stream(server):
|
||||
def test_get_invalid_url():
|
||||
with pytest.raises(httpx.UnsupportedProtocol):
|
||||
httpx.get("invalid://example.org")
|
||||
|
||||
|
||||
# check that httpcore isn't imported until we do a request
|
||||
def test_httpcore_lazy_loading(server):
|
||||
import sys
|
||||
|
||||
# unload our module if it is already loaded
|
||||
if "httpx" in sys.modules:
|
||||
del sys.modules["httpx"]
|
||||
del sys.modules["httpcore"]
|
||||
import httpx
|
||||
|
||||
assert "httpcore" not in sys.modules
|
||||
_response = httpx.get(server.url)
|
||||
assert "httpcore" in sys.modules
|
||||
|
||||
@ -188,3 +188,18 @@ def test_proxy_with_auth_from_url():
|
||||
def test_invalid_proxy_scheme():
|
||||
with pytest.raises(ValueError):
|
||||
httpx.Proxy("invalid://example.com")
|
||||
|
||||
|
||||
def test_certifi_lazy_loading():
|
||||
global httpx, certifi
|
||||
import sys
|
||||
|
||||
del sys.modules["httpx"]
|
||||
del sys.modules["certifi"]
|
||||
del httpx
|
||||
del certifi
|
||||
import httpx
|
||||
|
||||
assert "certifi" not in sys.modules
|
||||
_context = httpx.SSLContext()
|
||||
assert "certifi" in sys.modules
|
||||
|
||||
Loading…
Reference in New Issue
Block a user