Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e9cabc8

Browse files
made dependencies on certifi and httpcore only load when required (#3377)
Co-authored-by: Tom Christie <[email protected]>
1 parent eeb5e3c commit e9cabc8

7 files changed

Lines changed: 75 additions & 26 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7-
## Version 0.28.0
7+
## [Unreleased]
88

9-
Version 0.28.0 introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
9+
This release introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
1010

1111
* Added `httpx.SSLContext` class and `ssl_context` parameter. (#3022, #3335)
1212
* 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
1515
* The `URL.raw` property has now been removed.
1616
* Ensure JSON request bodies are compact. (#3363)
1717
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
18+
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)
1819

1920
## 0.27.2 (27th August, 2024)
2021

‎httpx/__version__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "httpx"
22
__description__ = "A next generation HTTP client, for Python 3."
3-
__version__ = "0.28.0"
3+
__version__ = "0.27.2"

‎httpx/_config.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import typing
77
import warnings
88

9-
import certifi
10-
119
from ._models import Headers
1210
from ._types import HeaderTypes, TimeoutTypes
1311
from ._urls import URL
@@ -77,6 +75,8 @@ def __init__(
7775
self,
7876
verify: bool = True,
7977
) -> None:
78+
import certifi
79+
8080
# ssl.SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
8181
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
8282
# by default. (from `ssl.create_default_context`)

‎httpx/_main.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import typing
77

88
import click
9-
import httpcore
109
import pygments.lexers
1110
import pygments.util
1211
import rich.console
@@ -21,6 +20,9 @@
2120
from ._models import Response
2221
from ._status_codes import codes
2322

23+
if typing.TYPE_CHECKING:
24+
import httpcore # pragma: no cover
25+
2426

2527
def print_help() -> None:
2628
console = rich.console.Console()

‎httpx/_transports/default.py‎

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
from __future__ import annotations
2828

2929
import contextlib
30-
import ssl
3130
import typing
3231
from types import TracebackType
3332

34-
import httpcore
33+
if typing.TYPE_CHECKING:
34+
import ssl # pragma: no cover
35+
36+
import httpx # pragma: no cover
3537

3638
from .._config import DEFAULT_LIMITS, Limits, Proxy, SSLContext, create_ssl_context
3739
from .._exceptions import (
@@ -66,9 +68,35 @@
6668

6769
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
6870

71+
HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}
72+
73+
74+
def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:
75+
import httpcore
76+
77+
return {
78+
httpcore.TimeoutException: TimeoutException,
79+
httpcore.ConnectTimeout: ConnectTimeout,
80+
httpcore.ReadTimeout: ReadTimeout,
81+
httpcore.WriteTimeout: WriteTimeout,
82+
httpcore.PoolTimeout: PoolTimeout,
83+
httpcore.NetworkError: NetworkError,
84+
httpcore.ConnectError: ConnectError,
85+
httpcore.ReadError: ReadError,
86+
httpcore.WriteError: WriteError,
87+
httpcore.ProxyError: ProxyError,
88+
httpcore.UnsupportedProtocol: UnsupportedProtocol,
89+
httpcore.ProtocolError: ProtocolError,
90+
httpcore.LocalProtocolError: LocalProtocolError,
91+
httpcore.RemoteProtocolError: RemoteProtocolError,
92+
}
93+
6994

7095
@contextlib.contextmanager
7196
def map_httpcore_exceptions() -> typing.Iterator[None]:
97+
global HTTPCORE_EXC_MAP
98+
if len(HTTPCORE_EXC_MAP) == 0:
99+
HTTPCORE_EXC_MAP = _load_httpcore_exceptions()
72100
try:
73101
yield
74102
except Exception as exc:
@@ -90,24 +118,6 @@ def map_httpcore_exceptions() -> typing.Iterator[None]:
90118
raise mapped_exc(message) from exc
91119

92120

93-
HTTPCORE_EXC_MAP = {
94-
httpcore.TimeoutException: TimeoutException,
95-
httpcore.ConnectTimeout: ConnectTimeout,
96-
httpcore.ReadTimeout: ReadTimeout,
97-
httpcore.WriteTimeout: WriteTimeout,
98-
httpcore.PoolTimeout: PoolTimeout,
99-
httpcore.NetworkError: NetworkError,
100-
httpcore.ConnectError: ConnectError,
101-
httpcore.ReadError: ReadError,
102-
httpcore.WriteError: WriteError,
103-
httpcore.ProxyError: ProxyError,
104-
httpcore.UnsupportedProtocol: UnsupportedProtocol,
105-
httpcore.ProtocolError: ProtocolError,
106-
httpcore.LocalProtocolError: LocalProtocolError,
107-
httpcore.RemoteProtocolError: RemoteProtocolError,
108-
}
109-
110-
111121
class ResponseStream(SyncByteStream):
112122
def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None:
113123
self._httpcore_stream = httpcore_stream
@@ -138,6 +148,8 @@ def __init__(
138148
verify: typing.Any = None,
139149
cert: typing.Any = None,
140150
) -> None:
151+
import httpcore
152+
141153
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
142154
if verify is not None or cert is not None: # pragma: nocover
143155
# Deprecated...
@@ -225,6 +237,7 @@ def handle_request(
225237
request: Request,
226238
) -> Response:
227239
assert isinstance(request.stream, SyncByteStream)
240+
import httpcore
228241

229242
req = httpcore.Request(
230243
method=request.method,
@@ -284,6 +297,8 @@ def __init__(
284297
verify: typing.Any = None,
285298
cert: typing.Any = None,
286299
) -> None:
300+
import httpcore
301+
287302
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
288303
if verify is not None or cert is not None: # pragma: nocover
289304
# Deprecated...
@@ -371,6 +386,7 @@ async def handle_async_request(
371386
request: Request,
372387
) -> Response:
373388
assert isinstance(request.stream, AsyncByteStream)
389+
import httpcore
374390

375391
req = httpcore.Request(
376392
method=request.method,

‎tests/test_api.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,18 @@ def test_stream(server):
8585
def test_get_invalid_url():
8686
with pytest.raises(httpx.UnsupportedProtocol):
8787
httpx.get("invalid://example.org")
88+
89+
90+
# check that httpcore isn't imported until we do a request
91+
def test_httpcore_lazy_loading(server):
92+
import sys
93+
94+
# unload our module if it is already loaded
95+
if "httpx" in sys.modules:
96+
del sys.modules["httpx"]
97+
del sys.modules["httpcore"]
98+
import httpx
99+
100+
assert "httpcore" not in sys.modules
101+
_response = httpx.get(server.url)
102+
assert "httpcore" in sys.modules

‎tests/test_config.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,18 @@ def test_proxy_with_auth_from_url():
188188
def test_invalid_proxy_scheme():
189189
with pytest.raises(ValueError):
190190
httpx.Proxy("invalid://example.com")
191+
192+
193+
def test_certifi_lazy_loading():
194+
global httpx, certifi
195+
import sys
196+
197+
del sys.modules["httpx"]
198+
del sys.modules["certifi"]
199+
del httpx
200+
del certifi
201+
import httpx
202+
203+
assert "certifi" not in sys.modules
204+
_context = httpx.SSLContext()
205+
assert "certifi" in sys.modules

0 commit comments

Comments
 (0)