Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .evergreen/generated_configs/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4591,6 +4591,29 @@ tasks:
- async
- free-threaded

# String query preview tests
- name: test-string-query-preview-v8.2-python3.14-noauth-ssl-replica-set
commands:
- func: run server
vars:
AUTH: noauth
SSL: ssl
TOPOLOGY: replica_set
VERSION: "8.2"
- func: run tests
vars:
AUTH: noauth
SSL: ssl
TOPOLOGY: replica_set
VERSION: "8.2"
TOOLCHAIN_VERSION: "3.14"
tags:
- test-string-query-preview
- server-8.2
- python-3.14
- replica_set-noauth-ssl
- noauth

# Test non standard tests
- name: test-non-standard-v4.2-python3.11-noauth-ssl-replica-set
commands:
Expand Down
6 changes: 6 additions & 0 deletions .evergreen/generated_configs/variants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ buildvariants:
- name: encryption-rhel8
tasks:
- name: .test-non-standard
- name: .test-string-query-preview
display_name: Encryption RHEL8
run_on:
- rhel87-small
Expand All @@ -195,6 +196,7 @@ buildvariants:
tasks:
- name: .test-non-standard !.pypy !.cov
- name: .test-non-standard-no-cov !.pypy
- name: .test-string-query-preview
display_name: Encryption macOS
run_on:
- macos-14
Expand All @@ -206,6 +208,7 @@ buildvariants:
tasks:
- name: .test-non-standard !.pypy !.cov
- name: .test-non-standard-no-cov !.pypy
- name: .test-string-query-preview
display_name: Encryption Win64
run_on:
- windows-2022-latest-small
Expand All @@ -216,6 +219,7 @@ buildvariants:
- name: encryption-crypt_shared-rhel8
tasks:
- name: .test-non-standard
- name: .test-string-query-preview
display_name: Encryption crypt_shared RHEL8
run_on:
- rhel87-small
Expand All @@ -228,6 +232,7 @@ buildvariants:
tasks:
- name: .test-non-standard !.pypy !.cov
- name: .test-non-standard-no-cov !.pypy
- name: .test-string-query-preview
display_name: Encryption crypt_shared macOS
run_on:
- macos-14
Expand All @@ -240,6 +245,7 @@ buildvariants:
tasks:
- name: .test-non-standard !.pypy !.cov
- name: .test-non-standard-no-cov !.pypy
- name: .test-string-query-preview
display_name: Encryption crypt_shared Win64
run_on:
- windows-2022-latest-small
Expand Down
36 changes: 34 additions & 2 deletions .evergreen/scripts/generate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,17 @@ def get_encryption_expansions(encryption):
):
expansions = get_encryption_expansions(encryption)
display_name = get_variant_name(encryption, host, **expansions)
tasks = [".test-non-standard"]
tasks = [".test-non-standard", ".test-string-query-preview"]
if host != "rhel8":
# Exclude PyPy (not tested with encryption on macOS/win64) and coverage tasks
# (encryption suites exceed the 60-min timeout with coverage overhead on macOS/win64).
# Also include the non-coverage companion tasks (test-non-standard-no-cov) which
# carry the "latest" server tasks without COVERAGE=1.
tasks = [".test-non-standard !.pypy !.cov", ".test-non-standard-no-cov !.pypy"]
tasks = [
".test-non-standard !.pypy !.cov",
".test-non-standard-no-cov !.pypy",
".test-string-query-preview",
]
variant = create_variant(
tasks,
display_name,
Expand Down Expand Up @@ -719,6 +723,34 @@ def create_test_non_standard_tasks():
return tasks


def create_string_query_preview_tasks():
"""Tasks for the preview Queryable Encryption string query types.

The preview query types need a server that is at least 8.2 and older than
9.0, and ALL_VERSIONS jumps straight from 8.0 to 9.0, so they have nowhere
to run without a dedicated task. setup_tests.py pins the released
pymongocrypt for 8.x, which bundles a libmongocrypt still carrying the
preview types.
"""
python = CPYTHONS[-1]
topology = "replica_set"
auth, ssl = get_standard_auth_ssl(topology)
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION="8.2")
tags = [
"test-string-query-preview",
"server-8.2",
f"python-{python}",
f"{topology}-{auth}-{ssl}",
auth,
]
name = get_task_name("test-string-query-preview", python=python, **expansions)
server_func = FunctionCall(func="run server", vars=expansions)
test_vars = expansions.copy()
test_vars["TOOLCHAIN_VERSION"] = python
test_func = FunctionCall(func="run tests", vars=test_vars)
return [EvgTask(name=name, tags=tags, commands=[server_func, test_func])]


def create_test_standard_auth_tasks():
"""We only use auth on sharded clusters"""
tasks = []
Expand Down
156 changes: 101 additions & 55 deletions .evergreen/scripts/setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
# The python version used for perf tests.
PERF_PYTHON_VERSION = "3.10.11"

# The libmongocrypt release used when LIBMONGOCRYPT_URL is not set. Must be at
# least 1.20.0 for the GA "substring" query type.
LIBMONGOCRYPT_VERSION = "1.20.2"


def is_set(var: str) -> bool:
value = os.environ.get(var, "")
Expand All @@ -72,43 +76,56 @@ def get_distro() -> Distro:
return Distro(name=name, version_id=version_id, arch=arch)


def setup_libmongocrypt():
target = ""
def get_libmongocrypt_target() -> str:
"""Return the libmongocrypt release asset target for this platform.

These are the names used by the signed release assets on GitHub, which are
keyed by libc flavor rather than by distro (MONGOCRYPT-838 moved the
per-variant release builds to a restricted bucket).
"""
if PLATFORM == "windows":
# PYTHON-2808 Ensure this machine has the CA cert for google KMS.
if is_set("TEST_FLE_GCP_AUTO"):
run_command('powershell.exe "Invoke-WebRequest -URI https://oauth2.googleapis.com/"')
target = "windows-test"
return "windows-x86_64"
if PLATFORM == "darwin":
return "macos-universal"

distro = get_distro()
arch = distro.arch
if arch in ("aarch64", "arm64"):
arch = "arm64"
elif arch in ("x86_64", "amd64"):
arch = "x86_64"

# Alpine and other musl distros need the musl build.
if "Alpine" in distro.name:
if arch not in ("x86_64", "arm64"):
raise ValueError(f"No musl libmongocrypt build for architecture {distro.arch}!")
return f"linux-{arch}-musl_1_2-nocrypto"

libc = {
"x86_64": "glibc_2_7",
"arm64": "glibc_2_17",
"ppc64le": "glibc_2_17",
"s390x": "glibc_2_7",
}.get(arch)
if libc is None:
raise ValueError(f"No libmongocrypt build for architecture {distro.arch}!")
return f"linux-{arch}-{libc}-nocrypto"

elif PLATFORM == "darwin":
target = "macos"

else:
distro = get_distro()
if distro.name.startswith("Debian"):
target = f"debian{distro.version_id}"
elif distro.name.startswith("Ubuntu"):
if distro.version_id == "20.04":
target = "debian11"
elif distro.version_id == "22.04":
target = "debian12"
elif distro.version_id == "24.04":
target = "debian13"
elif distro.name.startswith("Red Hat"):
if distro.version_id.startswith("7"):
target = "rhel-70-64-bit"
elif distro.version_id.startswith("8"):
if distro.arch == "aarch64":
target = "rhel-82-arm64"
else:
target = "rhel-80-64-bit"
def setup_libmongocrypt():
if PLATFORM == "windows" and is_set("TEST_FLE_GCP_AUTO"):
# PYTHON-2808 Ensure this machine has the CA cert for google KMS.
run_command('powershell.exe "Invoke-WebRequest -URI https://oauth2.googleapis.com/"')

if not is_set("LIBMONGOCRYPT_URL"):
if not target:
raise ValueError("Cannot find libmongocrypt target for current platform!")
url = f"https://s3.amazonaws.com/mciuploads/libmongocrypt/{target}/master/latest/libmongocrypt.tar.gz"
else:
if is_set("LIBMONGOCRYPT_URL"):
url = os.environ["LIBMONGOCRYPT_URL"]
else:
version = os.environ.get("LIBMONGOCRYPT_VERSION", LIBMONGOCRYPT_VERSION)
target = get_libmongocrypt_target()
url = (
f"https://github.com/mongodb/libmongocrypt/releases/download/"
f"{version}/libmongocrypt-{target}-{version}.tar.gz"
)

shutil.rmtree(HERE / "libmongocrypt", ignore_errors=True)

Expand All @@ -122,11 +139,25 @@ def setup_libmongocrypt():
LOGGER.info(f"Fetching {url}... done.")

run_command("ls -la libmongocrypt")
run_command("ls -la libmongocrypt/nocrypto")

if PLATFORM == "windows":
# libmongocrypt's windows dll is not marked executable.
run_command("chmod +x libmongocrypt/nocrypto/bin/mongocrypt.dll")
# libmongocrypt's windows dll is not marked executable. Use Path.chmod
# rather than shelling out: the bundled POSIX chmod cannot resolve the
# drive-lettered absolute path that get_libmongocrypt_base() returns.
dll = get_libmongocrypt_base() / "bin/mongocrypt.dll"
dll.chmod(dll.stat().st_mode | stat.S_IEXEC)


def get_libmongocrypt_base() -> Path:
"""Return the root of the extracted libmongocrypt archive.

The signed release archives put ``lib/`` at the archive root, while the
older master builds nested everything under ``nocrypto/``.
"""
base = ROOT / "libmongocrypt"
if (base / "nocrypto").exists():
return base / "nocrypto"
return base


def load_config_from_file(path: str | Path) -> dict[str, str]:
Expand Down Expand Up @@ -353,29 +384,44 @@ def handle_test_env() -> None:
UV_ARGS.append("--extra zstd")

if test_name in ["encryption", "kms"]:
# Check for libmongocrypt download.
if not (ROOT / "libmongocrypt").exists():
setup_libmongocrypt()
# The "String" algorithm and the GA prefix/suffix/substring query types
# need libmongocrypt 1.19.0+, which is only exercised against MongoDB
# 9.0+. Servers before 9.0 test the preview query types instead, which
# need the "textPreview" algorithm, so pin to the released pymongocrypt
# and use the libmongocrypt bundled in its wheel rather than the
# unreleased master build.
# Evergreen exposes the server version as VERSION, not MONGODB_VERSION
# (which is only set inside the separate run_server.py process).
use_pymongocrypt_text_preview = os.environ.get("VERSION", "").startswith("8.")

if not use_pymongocrypt_text_preview:
# Check for libmongocrypt download.
if not (ROOT / "libmongocrypt").exists():
setup_libmongocrypt()

if not opts.test_min_deps:
UV_ARGS.append(
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
)

# Use the nocrypto build to avoid dependency issues with older windows/python versions.
BASE = ROOT / "libmongocrypt/nocrypto"
if PLATFORM == "linux":
if (BASE / "lib/libmongocrypt.so").exists():
PYMONGOCRYPT_LIB = BASE / "lib/libmongocrypt.so"
if use_pymongocrypt_text_preview:
UV_ARGS.append("--with 'pymongocrypt<1.19'")
else:
PYMONGOCRYPT_LIB = BASE / "lib64/libmongocrypt.so"
elif PLATFORM == "darwin":
PYMONGOCRYPT_LIB = BASE / "lib/libmongocrypt.dylib"
else:
PYMONGOCRYPT_LIB = BASE / "bin/mongocrypt.dll"
if not PYMONGOCRYPT_LIB.exists():
raise RuntimeError("Cannot find libmongocrypt shared object file")
write_env("PYMONGOCRYPT_LIB", PYMONGOCRYPT_LIB.as_posix())
UV_ARGS.append(
"--with pymongocrypt@git+https://github.com/mongodb/libmongocrypt@master#subdirectory=bindings/python"
)

if not use_pymongocrypt_text_preview:
# Use the nocrypto build to avoid dependency issues with older windows/python versions.
BASE = get_libmongocrypt_base()
if PLATFORM == "linux":
if (BASE / "lib/libmongocrypt.so").exists():
PYMONGOCRYPT_LIB = BASE / "lib/libmongocrypt.so"
else:
PYMONGOCRYPT_LIB = BASE / "lib64/libmongocrypt.so"
elif PLATFORM == "darwin":
PYMONGOCRYPT_LIB = BASE / "lib/libmongocrypt.dylib"
else:
PYMONGOCRYPT_LIB = BASE / "bin/mongocrypt.dll"
if not PYMONGOCRYPT_LIB.exists():
raise RuntimeError("Cannot find libmongocrypt shared object file")
write_env("PYMONGOCRYPT_LIB", PYMONGOCRYPT_LIB.as_posix())
# PATH is updated by configure-env.sh for access to mongocryptd.

if test_name == "encryption":
Expand Down
21 changes: 21 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ PyMongo 4.18 brings a number of changes including:
- Fixed a bug on Windows, and on macOS when using PyOpenSSL, where
``SSL_CERT_FILE``/``SSL_CERT_DIR`` were merged with, rather than replacing,
the OS/certifi certificate store.
- Added general availability support for Queryable Encryption prefix, suffix,
and substring queries against MongoDB 9.0+. Prefix and suffix queries require
libmongocrypt 1.19.0 or later; substring queries require libmongocrypt 1.20.0
or later:

- Added :attr:`~pymongo.encryption.Algorithm.STRING` and
:class:`~pymongo.encryption_options.StringOpts`, replacing
``Algorithm.TEXTPREVIEW`` and ``TextOpts``, which are now deprecated.
- Added :attr:`~pymongo.encryption.QueryType.PREFIX`,
:attr:`~pymongo.encryption.QueryType.SUFFIX`, and
:attr:`~pymongo.encryption.QueryType.SUBSTRING`. The corresponding
``PREFIXPREVIEW``, ``SUFFIXPREVIEW``, and ``SUBSTRINGPREVIEW`` query types
remain for experimental use with MongoDB versions before 9.0.
- Added the ``string_opts`` parameter to
:meth:`~pymongo.encryption.ClientEncryption.encrypt` and
:meth:`~pymongo.asynchronous.encryption.AsyncClientEncryption.encrypt`,
deprecating ``text_opts``. pymongocrypt renamed this parameter in 1.19 and
accepts only one of the two names per release, so passing ``text_opts``
with pymongocrypt 1.19 or later, ``string_opts`` with pymongocrypt 1.18 or
earlier, or both names at once, raises
:exc:`~pymongo.errors.ConfigurationError`.
- Aggregation helpers now raise :exc:`~pymongo.errors.ConfigurationError` when
passed an ``aggregate`` or ``pipeline`` keyword argument. Previously these
keys silently replaced the target namespace and pipeline of the generated
Expand Down
Loading
Loading