-
Notifications
You must be signed in to change notification settings - Fork 1.1k
PYTHON-4260 Lazily load optional imports #1550
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
Changes from all commits
6cfceb6
638b743
757d103
e3ece41
1f2cb97
0a62ea1
f343dda
f30f0db
7b07964
2924e70
7137f10
92c8abb
0b2e31a
25c8098
ff8293a
3bd2191
c5bf32b
ccf44be
fd0772a
ab5c5dc
9af1bff
c487625
f0f5606
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#!/bin/bash -ex | ||
|
||
set -o errexit # Exit the script with error if any of the commands fail | ||
set -x | ||
|
||
. .evergreen/utils.sh | ||
|
||
if [ -z "$PYTHON_BINARY" ]; then | ||
PYTHON_BINARY=$(find_python3) | ||
fi | ||
|
||
# Use the previous commit if this was not a PR run. | ||
if [ "$BASE_SHA" == "$HEAD_SHA" ]; then | ||
BASE_SHA=$(git rev-parse HEAD~1) | ||
fi | ||
|
||
function get_import_time() { | ||
local log_file | ||
createvirtualenv "$PYTHON_BINARY" import-venv | ||
python -m pip install -q ".[aws,encryption,gssapi,ocsp,snappy,zstd]" | ||
# Import once to cache modules | ||
python -c "import pymongo" | ||
log_file="pymongo-$1.log" | ||
python -X importtime -c "import pymongo" 2> $log_file | ||
} | ||
|
||
get_import_time $HEAD_SHA | ||
git checkout $BASE_SHA | ||
get_import_time $BASE_SHA | ||
git checkout $HEAD_SHA | ||
python tools/compare_import_time.py $HEAD_SHA $BASE_SHA |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,6 +74,12 @@ Unavoidable breaking changes | |
>>> dict_to_SON(data_as_dict) | ||
SON([('driver', SON([('name', 'PyMongo'), ('version', '4.7.0.dev0')])), ('os', SON([('type', 'Darwin'), ('name', 'Darwin'), ('architecture', 'arm64'), ('version', '14.3')])), ('platform', 'CPython 3.11.6.final.0')]) | ||
|
||
- PyMongo now uses `lazy imports <https://docs.python.org/3/library/importlib.html#implementing-lazy-imports>`_ for external dependencies. | ||
If you are relying on any kind of monkey-patching of the standard library, you may need to explicitly import those external libraries in addition | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add a note that gevent+eventlet style patching still works as expected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
to ``pymongo`` before applying the patch. Note that we test with ``gevent`` and ``eventlet`` patching, and those continue to work. | ||
|
||
- The "aws" extra now requires minimum version of ``1.1.0`` for ``pymongo_auth_aws``. | ||
|
||
Changes in Version 4.6.2 | ||
------------------------ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Copyright 2024-present MongoDB, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you | ||
# may not use this file except in compliance with the License. You | ||
# may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
# implied. See the License for the specific language governing | ||
# permissions and limitations under the License. | ||
from __future__ import annotations | ||
|
||
import importlib.util | ||
import sys | ||
from types import ModuleType | ||
|
||
|
||
def lazy_import(name: str) -> ModuleType: | ||
"""Lazily import a module by name | ||
|
||
From https://docs.python.org/3/library/importlib.html#implementing-lazy-imports | ||
""" | ||
try: | ||
spec = importlib.util.find_spec(name) | ||
except ValueError: | ||
raise ModuleNotFoundError(name=name) from None | ||
if spec is None: | ||
raise ModuleNotFoundError(name=name) | ||
assert spec is not None | ||
loader = importlib.util.LazyLoader(spec.loader) # type:ignore[arg-type] | ||
spec.loader = loader | ||
module = importlib.util.module_from_spec(spec) | ||
sys.modules[name] = module | ||
loader.exec_module(module) | ||
return module |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,38 +15,16 @@ | |
"""MONGODB-AWS Authentication helpers.""" | ||
from __future__ import annotations | ||
|
||
try: | ||
import pymongo_auth_aws # type:ignore[import] | ||
from pymongo_auth_aws import ( | ||
AwsCredential, | ||
AwsSaslContext, | ||
PyMongoAuthAwsError, | ||
) | ||
from pymongo._lazy_import import lazy_import | ||
|
||
try: | ||
pymongo_auth_aws = lazy_import("pymongo_auth_aws") | ||
_HAVE_MONGODB_AWS = True | ||
except ImportError: | ||
|
||
class AwsSaslContext: # type: ignore | ||
def __init__(self, credentials: MongoCredential): | ||
pass | ||
|
||
_HAVE_MONGODB_AWS = False | ||
|
||
try: | ||
from pymongo_auth_aws.auth import ( # type:ignore[import] | ||
set_cached_credentials, | ||
set_use_cached_credentials, | ||
) | ||
|
||
# Enable credential caching. | ||
set_use_cached_credentials(True) | ||
except ImportError: | ||
|
||
def set_cached_credentials(_creds: Optional[AwsCredential]) -> None: | ||
pass | ||
|
||
|
||
from typing import TYPE_CHECKING, Any, Mapping, Optional, Type | ||
from typing import TYPE_CHECKING, Any, Mapping, Type | ||
|
||
import bson | ||
from bson.binary import Binary | ||
|
@@ -58,21 +36,6 @@ def set_cached_credentials(_creds: Optional[AwsCredential]) -> None: | |
from pymongo.pool import Connection | ||
|
||
|
||
class _AwsSaslContext(AwsSaslContext): # type: ignore | ||
# Dependency injection: | ||
def binary_type(self) -> Type[Binary]: | ||
"""Return the bson.binary.Binary type.""" | ||
return Binary | ||
|
||
def bson_encode(self, doc: Mapping[str, Any]) -> bytes: | ||
"""Encode a dictionary to BSON.""" | ||
return bson.encode(doc) | ||
|
||
def bson_decode(self, data: _ReadableBuffer) -> Mapping[str, Any]: | ||
"""Decode BSON to a dictionary.""" | ||
return bson.decode(data) | ||
|
||
|
||
def _authenticate_aws(credentials: MongoCredential, conn: Connection) -> None: | ||
"""Authenticate using MONGODB-AWS.""" | ||
if not _HAVE_MONGODB_AWS: | ||
|
@@ -84,9 +47,23 @@ def _authenticate_aws(credentials: MongoCredential, conn: Connection) -> None: | |
if conn.max_wire_version < 9: | ||
raise ConfigurationError("MONGODB-AWS authentication requires MongoDB version 4.4 or later") | ||
|
||
class AwsSaslContext(pymongo_auth_aws.AwsSaslContext): # type: ignore | ||
# Dependency injection: | ||
def binary_type(self) -> Type[Binary]: | ||
"""Return the bson.binary.Binary type.""" | ||
return Binary | ||
|
||
def bson_encode(self, doc: Mapping[str, Any]) -> bytes: | ||
"""Encode a dictionary to BSON.""" | ||
return bson.encode(doc) | ||
|
||
def bson_decode(self, data: _ReadableBuffer) -> Mapping[str, Any]: | ||
"""Decode BSON to a dictionary.""" | ||
return bson.decode(data) | ||
|
||
try: | ||
ctx = _AwsSaslContext( | ||
AwsCredential( | ||
ctx = AwsSaslContext( | ||
pymongo_auth_aws.AwsCredential( | ||
credentials.username, | ||
credentials.password, | ||
credentials.mechanism_properties.aws_session_token, | ||
|
@@ -108,14 +85,14 @@ def _authenticate_aws(credentials: MongoCredential, conn: Connection) -> None: | |
if res["done"]: | ||
# SASL complete. | ||
break | ||
except PyMongoAuthAwsError as exc: | ||
except pymongo_auth_aws.PyMongoAuthAwsError as exc: | ||
# Clear the cached credentials if we hit a failure in auth. | ||
set_cached_credentials(None) | ||
pymongo_auth_aws.set_cached_credentials(None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This behavior is subtly different. set_cached_credentials is now required when before it was optional. Let's raise the min version of pymongo_auth_aws that can be used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
# Convert to OperationFailure and include pymongo-auth-aws version. | ||
raise OperationFailure( | ||
f"{exc} (pymongo-auth-aws version {pymongo_auth_aws.__version__})" | ||
) from None | ||
except Exception: | ||
# Clear the cached credentials if we hit a failure in auth. | ||
set_cached_credentials(None) | ||
pymongo_auth_aws.set_cached_credentials(None) | ||
raise |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we still run this on mainline? The mainline test can check the previous commit (
HEAD~
) vs the current.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done