Skip to content

Commit 1407864

Browse files
authored
Raise an error if peer certificate chain APIs aren't available in Python <3.13
1 parent 51412ad commit 1407864

File tree

3 files changed

+43
-12
lines changed

3 files changed

+43
-12
lines changed

src/truststore/__init__.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,32 @@
55
if _sys.version_info < (3, 10):
66
raise ImportError("truststore requires Python 3.10 or later")
77

8+
# Detect Python runtimes which don't implement SSLObject.get_unverified_chain() API
9+
# This API only became public in Python 3.13 but was available in CPython and PyPy since 3.10.
10+
if _sys.version_info < (3, 13):
11+
try:
12+
import ssl as _ssl
13+
except ImportError:
14+
raise ImportError("truststore requires the 'ssl' module")
15+
else:
16+
_sslmem = _ssl.MemoryBIO()
17+
_sslobj = _ssl.create_default_context().wrap_bio(
18+
_sslmem,
19+
_sslmem,
20+
)
21+
try:
22+
while not hasattr(_sslobj, "get_unverified_chain"):
23+
_sslobj = _sslobj._sslobj # type: ignore[attr-defined]
24+
except AttributeError:
25+
raise ImportError(
26+
"truststore requires peer certificate chain APIs to be available"
27+
) from None
28+
29+
del _ssl, _sslobj, _sslmem # noqa: F821
30+
831
from ._api import SSLContext, extract_from_ssl, inject_into_ssl # noqa: E402
932

1033
del _api, _sys # type: ignore[name-defined] # noqa: F821
1134

1235
__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"]
13-
__version__ = "0.9.1"
36+
__version__ = "0.9.2"

tests/requests_with_inject.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Used by the test: test_inject.py::test_requests_work_with_inject
2+
3+
import truststore
4+
5+
truststore.inject_into_ssl()
6+
7+
import requests # noqa: E402
8+
9+
resp = requests.request("GET", "https://example.com")
10+
assert resp.status_code == 200

tests/test_inject.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import asyncio
2+
import pathlib
23
import ssl
4+
import subprocess
5+
import sys
36

47
import httpx
58
import pytest
6-
import requests
79
import urllib3
810
from aiohttp import ClientSession
911

@@ -97,16 +99,12 @@ async def test_aiohttp_works_with_inject(server: Server) -> None:
9799
assert resp.status == 200
98100

99101

100-
@pytest.mark.asyncio
101-
@pytest.mark.usefixtures("inject_truststore")
102-
async def test_requests_works_with_inject(server: Server) -> None:
103-
def test_requests():
104-
with requests.Session() as http:
105-
resp = http.request("GET", server.base_url)
106-
assert resp.status_code == 200
107-
108-
thread = asyncio.to_thread(test_requests)
109-
await thread
102+
def test_requests_works_with_inject() -> None:
103+
# We completely isolate the requests module because
104+
# pytest or some other part of our test infra is messing
105+
# with the order it's loaded into modules.
106+
script = pathlib.Path(__file__).parent / "requests_with_inject.py"
107+
subprocess.check_output([sys.executable, script])
110108

111109

112110
@pytest.mark.asyncio

0 commit comments

Comments
 (0)