Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit dd99f80

Browse files
NoahStappsleepyStick
andauthored
PYTHON-5309: [v4.12] AsyncMongoClient doesn't use PyOpenSSL (#2286) (#2319)
Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com>
1 parent fecd29c commit dd99f80

File tree

19 files changed

+240
-128
lines changed

19 files changed

+240
-128
lines changed

‎.evergreen/generated_configs/variants.yml‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -735,18 +735,20 @@ buildvariants:
735735
- macos-14
736736
batchtime: 10080
737737
expansions:
738-
TEST_NAME: pyopenssl
738+
TEST_NAME: default
739+
SUB_TEST_NAME: pyopenssl
739740
PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3
740741
- name: pyopenssl-rhel8-python3.10
741742
tasks:
742-
- name: .replica_set .auth .ssl .sync
743-
- name: .7.0 .auth .ssl .sync
743+
- name: .replica_set .auth .ssl .sync_async
744+
- name: .7.0 .auth .ssl .sync_async
744745
display_name: PyOpenSSL RHEL8 Python3.10
745746
run_on:
746747
- rhel87-small
747748
batchtime: 10080
748749
expansions:
749-
TEST_NAME: pyopenssl
750+
TEST_NAME: default
751+
SUB_TEST_NAME: pyopenssl
750752
PYTHON_BINARY: /opt/python/3.10/bin/python3
751753
- name: pyopenssl-rhel8-python3.11
752754
tasks:
@@ -757,7 +759,8 @@ buildvariants:
757759
- rhel87-small
758760
batchtime: 10080
759761
expansions:
760-
TEST_NAME: pyopenssl
762+
TEST_NAME: default
763+
SUB_TEST_NAME: pyopenssl
761764
PYTHON_BINARY: /opt/python/3.11/bin/python3
762765
- name: pyopenssl-rhel8-python3.12
763766
tasks:
@@ -768,18 +771,20 @@ buildvariants:
768771
- rhel87-small
769772
batchtime: 10080
770773
expansions:
771-
TEST_NAME: pyopenssl
774+
TEST_NAME: default
775+
SUB_TEST_NAME: pyopenssl
772776
PYTHON_BINARY: /opt/python/3.12/bin/python3
773777
- name: pyopenssl-win64-python3.13
774778
tasks:
775-
- name: .replica_set .auth .ssl .sync
776-
- name: .7.0 .auth .ssl .sync
779+
- name: .replica_set .auth .ssl .sync_async
780+
- name: .7.0 .auth .ssl .sync_async
777781
display_name: PyOpenSSL Win64 Python3.13
778782
run_on:
779783
- windows-64-vsMulti-small
780784
batchtime: 10080
781785
expansions:
782-
TEST_NAME: pyopenssl
786+
TEST_NAME: default
787+
SUB_TEST_NAME: pyopenssl
783788
PYTHON_BINARY: C:/python/Python313/python.exe
784789
- name: pyopenssl-rhel8-pypy3.10
785790
tasks:
@@ -790,7 +795,8 @@ buildvariants:
790795
- rhel87-small
791796
batchtime: 10080
792797
expansions:
793-
TEST_NAME: pyopenssl
798+
TEST_NAME: default
799+
SUB_TEST_NAME: pyopenssl
794800
PYTHON_BINARY: /opt/python/pypy3.10/bin/python3
795801

796802
# Search index tests

‎.evergreen/scripts/generate_config.py‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ def create_enterprise_auth_variants():
491491
def create_pyopenssl_variants():
492492
base_name = "PyOpenSSL"
493493
batchtime = BATCHTIME_WEEK
494-
expansions = dict(TEST_NAME="pyopenssl")
494+
expansions = dict(TEST_NAME="default", SUB_TEST_NAME="pyopenssl")
495495
variants = []
496496

497497
for python in ALL_PYTHONS:
@@ -506,14 +506,25 @@ def create_pyopenssl_variants():
506506
host = DEFAULT_HOST
507507

508508
display_name = get_variant_name(base_name, host, python=python)
509-
variant = create_variant(
510-
[f".replica_set .{auth} .{ssl} .sync", f".7.0 .{auth} .{ssl} .sync"],
511-
display_name,
512-
python=python,
513-
host=host,
514-
expansions=expansions,
515-
batchtime=batchtime,
516-
)
509+
# only need to run some on async
510+
if python in (CPYTHONS[1], CPYTHONS[-1]):
511+
variant = create_variant(
512+
[f".replica_set .{auth} .{ssl} .sync_async", f".7.0 .{auth} .{ssl} .sync_async"],
513+
display_name,
514+
python=python,
515+
host=host,
516+
expansions=expansions,
517+
batchtime=batchtime,
518+
)
519+
else:
520+
variant = create_variant(
521+
[f".replica_set .{auth} .{ssl} .sync", f".7.0 .{auth} .{ssl} .sync"],
522+
display_name,
523+
python=python,
524+
host=host,
525+
expansions=expansions,
526+
batchtime=batchtime,
527+
)
517528
variants.append(variant)
518529

519530
return variants

‎doc/changelog.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Version 4.12.1 is a bug fix release.
1515
errors such as: "NotImplementedError: Database objects do not implement truth value testing or bool()".
1616
- Fixed a bug where MongoDB cluster topology changes could cause asynchronous operations to take much longer to complete
1717
due to holding the Topology lock while closing stale connections.
18+
- Fixed a bug that would cause AsyncMongoClient to attempt to use PyOpenSSL when available, resulting in errors such as
19+
"pymongo.errors.ServerSelectionTimeoutError: 'SSLContext' object has no attribute 'wrap_bio'".
1820

1921
Issues Resolved
2022
...............

‎pymongo/asynchronous/encryption.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
from pymongo.results import BulkWriteResult, DeleteResult
8888
from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context
8989
from pymongo.typings import _DocumentType, _DocumentTypeArg
90-
from pymongo.uri_parser_shared import parse_host
90+
from pymongo.uri_parser_shared import _parse_kms_tls_options, parse_host
9191
from pymongo.write_concern import WriteConcern
9292

9393
if TYPE_CHECKING:
@@ -157,6 +157,7 @@ def __init__(
157157
self.mongocryptd_client = mongocryptd_client
158158
self.opts = opts
159159
self._spawned = False
160+
self._kms_ssl_contexts = opts._kms_ssl_contexts(_IS_SYNC)
160161

161162
async def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
162163
"""Complete a KMS request.
@@ -168,7 +169,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
168169
endpoint = kms_context.endpoint
169170
message = kms_context.message
170171
provider = kms_context.kms_provider
171-
ctx = self.opts._kms_ssl_contexts.get(provider)
172+
ctx = self._kms_ssl_contexts.get(provider)
172173
if ctx is None:
173174
# Enable strict certificate verification, OCSP, match hostname, and
174175
# SNI using the system default CA certificates.
@@ -180,6 +181,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
180181
False, # allow_invalid_certificates
181182
False, # allow_invalid_hostnames
182183
False, # disable_ocsp_endpoint_check
184+
_IS_SYNC,
183185
)
184186
# CSOT: set timeout for socket creation.
185187
connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001)
@@ -396,6 +398,8 @@ def __init__(self, client: AsyncMongoClient[_DocumentTypeArg], opts: AutoEncrypt
396398
encrypted_fields_map = _dict_to_bson(opts._encrypted_fields_map, False, _DATA_KEY_OPTS)
397399
self._bypass_auto_encryption = opts._bypass_auto_encryption
398400
self._internal_client = None
401+
# parsing kms_ssl_contexts here so that parsing errors will be raised before internal clients are created
402+
opts._kms_ssl_contexts(_IS_SYNC)
399403

400404
def _get_internal_client(
401405
encrypter: _Encrypter, mongo_client: AsyncMongoClient[_DocumentTypeArg]
@@ -675,6 +679,7 @@ def __init__(
675679
kms_tls_options=kms_tls_options,
676680
key_expiration_ms=key_expiration_ms,
677681
)
682+
self._kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC)
678683
self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO(
679684
None, key_vault_coll, None, opts
680685
)

‎pymongo/asynchronous/pool.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
from pymongo.network_layer import AsyncNetworkingInterface, async_receive_message, async_sendall
7777
from pymongo.pool_options import PoolOptions
7878
from pymongo.pool_shared import (
79+
SSLErrors,
7980
_CancellationContext,
8081
_configured_protocol_interface,
8182
_get_timeout_details,
@@ -86,7 +87,6 @@
8687
from pymongo.server_api import _add_to_command
8788
from pymongo.server_type import SERVER_TYPE
8889
from pymongo.socket_checker import SocketChecker
89-
from pymongo.ssl_support import SSLError
9090

9191
if TYPE_CHECKING:
9292
from bson import CodecOptions
@@ -638,7 +638,7 @@ async def _raise_connection_failure(self, error: BaseException) -> NoReturn:
638638
reason = ConnectionClosedReason.ERROR
639639
await self.close_conn(reason)
640640
# SSLError from PyOpenSSL inherits directly from Exception.
641-
if isinstance(error, (IOError, OSError, SSLError)):
641+
if isinstance(error, (IOError, OSError, *SSLErrors)):
642642
details = _get_timeout_details(self.opts)
643643
_raise_connection_failure(self.address, error, timeout_details=details)
644644
else:
@@ -1052,7 +1052,7 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A
10521052
reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR),
10531053
error=ConnectionClosedReason.ERROR,
10541054
)
1055-
if isinstance(error, (IOError, OSError, SSLError)):
1055+
if isinstance(error, (IOError, OSError, *SSLErrors)):
10561056
details = _get_timeout_details(self.opts)
10571057
_raise_connection_failure(self.address, error, timeout_details=details)
10581058

‎pymongo/client_options.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ def _parse_read_concern(options: Mapping[str, Any]) -> ReadConcern:
8484
return ReadConcern(concern)
8585

8686

87-
def _parse_ssl_options(options: Mapping[str, Any]) -> tuple[Optional[SSLContext], bool]:
87+
def _parse_ssl_options(
88+
options: Mapping[str, Any], is_sync: bool
89+
) -> tuple[Optional[SSLContext], bool]:
8890
"""Parse ssl options."""
8991
use_tls = options.get("tls")
9092
if use_tls is not None:
@@ -138,6 +140,7 @@ def _parse_ssl_options(options: Mapping[str, Any]) -> tuple[Optional[SSLContext]
138140
allow_invalid_certificates,
139141
allow_invalid_hostnames,
140142
disable_ocsp_endpoint_check,
143+
is_sync,
141144
)
142145
return ctx, allow_invalid_hostnames
143146
return None, allow_invalid_hostnames
@@ -167,7 +170,7 @@ def _parse_pool_options(
167170
compression_settings = CompressionSettings(
168171
options.get("compressors", []), options.get("zlibcompressionlevel", -1)
169172
)
170-
ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options)
173+
ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options, is_sync)
171174
load_balanced = options.get("loadbalanced")
172175
max_connecting = options.get("maxconnecting", common.MAX_CONNECTING)
173176
return PoolOptions(

‎pymongo/encryption_options.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
from typing import TYPE_CHECKING, Any, Mapping, Optional
2222

23+
from pymongo.uri_parser_shared import _parse_kms_tls_options
24+
2325
try:
2426
import pymongocrypt # type:ignore[import-untyped] # noqa: F401
2527

@@ -32,9 +34,9 @@
3234
from bson import int64
3335
from pymongo.common import validate_is_mapping
3436
from pymongo.errors import ConfigurationError
35-
from pymongo.uri_parser_shared import _parse_kms_tls_options
3637

3738
if TYPE_CHECKING:
39+
from pymongo.pyopenssl_context import SSLContext
3840
from pymongo.typings import _AgnosticMongoClient, _DocumentTypeArg
3941

4042

@@ -236,10 +238,22 @@ def __init__(
236238
if not any("idleShutdownTimeoutSecs" in s for s in self._mongocryptd_spawn_args):
237239
self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60")
238240
# Maps KMS provider name to a SSLContext.
239-
self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options)
241+
self._kms_tls_options = kms_tls_options
242+
self._sync_kms_ssl_contexts: Optional[dict[str, SSLContext]] = None
243+
self._async_kms_ssl_contexts: Optional[dict[str, SSLContext]] = None
240244
self._bypass_query_analysis = bypass_query_analysis
241245
self._key_expiration_ms = key_expiration_ms
242246

247+
def _kms_ssl_contexts(self, is_sync: bool) -> dict[str, SSLContext]:
248+
if is_sync:
249+
if self._sync_kms_ssl_contexts is None:
250+
self._sync_kms_ssl_contexts = _parse_kms_tls_options(self._kms_tls_options, True)
251+
return self._sync_kms_ssl_contexts
252+
else:
253+
if self._async_kms_ssl_contexts is None:
254+
self._async_kms_ssl_contexts = _parse_kms_tls_options(self._kms_tls_options, False)
255+
return self._async_kms_ssl_contexts
256+
243257

244258
class RangeOpts:
245259
"""Options to configure encrypted queries using the range algorithm."""

‎pymongo/network_layer.py‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,18 @@
4646
_HAVE_SSL = False
4747

4848
try:
49-
from pymongo.pyopenssl_context import (
50-
BLOCKING_IO_LOOKUP_ERROR,
51-
BLOCKING_IO_READ_ERROR,
52-
BLOCKING_IO_WRITE_ERROR,
53-
_sslConn,
54-
)
49+
from pymongo.pyopenssl_context import _sslConn
5550

5651
_HAVE_PYOPENSSL = True
5752
except ImportError:
5853
_HAVE_PYOPENSSL = False
59-
_sslConn = SSLSocket # type: ignore
60-
from pymongo.ssl_support import ( # type: ignore[assignment]
61-
BLOCKING_IO_LOOKUP_ERROR,
62-
BLOCKING_IO_READ_ERROR,
63-
BLOCKING_IO_WRITE_ERROR,
64-
)
54+
_sslConn = SSLSocket # type: ignore[assignment, misc]
55+
56+
from pymongo.ssl_support import (
57+
BLOCKING_IO_LOOKUP_ERROR,
58+
BLOCKING_IO_READ_ERROR,
59+
BLOCKING_IO_WRITE_ERROR,
60+
)
6561

6662
if TYPE_CHECKING:
6763
from pymongo.asynchronous.pool import AsyncConnection
@@ -71,7 +67,7 @@
7167
_UNPACK_COMPRESSION_HEADER = struct.Struct("<iiB").unpack
7268
_POLL_TIMEOUT = 0.5
7369
# Errors raised by sockets (and TLS sockets) when in non-blocking mode.
74-
BLOCKING_IO_ERRORS = (BlockingIOError, BLOCKING_IO_LOOKUP_ERROR, *ssl_support.BLOCKING_IO_ERRORS)
70+
BLOCKING_IO_ERRORS = (BlockingIOError, *BLOCKING_IO_LOOKUP_ERROR, *ssl_support.BLOCKING_IO_ERRORS)
7571

7672

7773
# These socket-based I/O methods are for KMS requests and any other network operations that do not use

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /