Skip to content
Draft
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
16 changes: 16 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ 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+, which requires 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``.

Changes in Version 4.17.0 (2026/04/20)
--------------------------------------
Expand Down
90 changes: 78 additions & 12 deletions pymongo/asynchronous/encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import socket
import time as time # noqa: PLC0414 # needed in sync version
import uuid
import warnings
import weakref
from collections.abc import AsyncGenerator, Iterator, Mapping, MutableMapping, Sequence
from copy import deepcopy
Expand Down Expand Up @@ -65,7 +66,7 @@
from pymongo.encryption_options import (
AutoEncryptionOpts,
RangeOpts,
TextOpts,
StringOpts,
check_min_pymongocrypt,
)
from pymongo.errors import (
Expand Down Expand Up @@ -529,8 +530,15 @@ class Algorithm(str, enum.Enum):

.. versionadded:: 4.4
"""
STRING = "String"
"""String.

.. versionadded:: 4.18
"""
TEXTPREVIEW = "TextPreview"
"""**BETA** - TextPreview.
"""**DEPRECATED** - TextPreview.

.. note:: Support for TextPreview is deprecated. Use :attr:`Algorithm.STRING` instead.

.. versionadded:: 4.15
"""
Expand Down Expand Up @@ -559,25 +567,77 @@ class QueryType(str, enum.Enum):
.. versionadded:: 4.4
"""

PREFIX = "prefix"
"""Used to encrypt a value for a prefix query.

Used for the ``$encStrStartsWith`` operator. Requires MongoDB 9.0+.

.. versionadded:: 4.18
"""

SUFFIX = "suffix"
"""Used to encrypt a value for a suffix query.

Used for the ``$encStrEndsWith`` operator. Requires MongoDB 9.0+.

.. versionadded:: 4.18
"""

SUBSTRING = "substring"
"""Used to encrypt a value for a substring query.

Used for the ``$encStrContains`` operator. Requires MongoDB 9.0+.

.. versionadded:: 4.18
"""

PREFIXPREVIEW = "prefixPreview"
"""**BETA** - Used to encrypt a value for a prefixPreview query.

.. note:: The preview query types are for experimental workloads only and
are only supported by MongoDB versions before 9.0. Use
:attr:`QueryType.PREFIX` instead.

.. versionadded:: 4.15
"""

SUFFIXPREVIEW = "suffixPreview"
"""**BETA** - Used to encrypt a value for a suffixPreview query.

.. note:: The preview query types are for experimental workloads only and
are only supported by MongoDB versions before 9.0. Use
:attr:`QueryType.SUFFIX` instead.

.. versionadded:: 4.15
"""

SUBSTRINGPREVIEW = "substringPreview"
"""**BETA** - Used to encrypt a value for a substringPreview query.

.. note:: The preview query types are for experimental workloads only and
are only supported by MongoDB versions before 9.0. Use
:attr:`QueryType.SUBSTRING` instead.

.. versionadded:: 4.15
"""


def _resolve_string_opts(
string_opts: Optional[StringOpts], text_opts: Optional[StringOpts]
) -> Optional[StringOpts]:
"""Resolve the deprecated text_opts alias for string_opts."""
if text_opts is None:
return string_opts
if string_opts is not None:
raise ConfigurationError("Cannot set both string_opts and text_opts")
warnings.warn(
"The text_opts parameter is deprecated. Use string_opts instead.",
DeprecationWarning,
stacklevel=3,
)
return text_opts


def _create_mongocrypt_options(**kwargs: Any) -> MongoCryptOptions:
# For compat with pymongocrypt <1.13, avoid setting the default key_expiration_ms.
if kwargs.get("key_expiration_ms") is None:
Expand Down Expand Up @@ -917,7 +977,7 @@ async def _encrypt_helper(
contention_factor: Optional[int] = None,
range_opts: Optional[RangeOpts] = None,
is_expression: bool = False,
text_opts: Optional[TextOpts] = None,
string_opts: Optional[StringOpts] = None,
) -> Any:
self._check_closed()
if isinstance(key_id, uuid.UUID):
Expand All @@ -937,10 +997,10 @@ async def _encrypt_helper(
range_opts.document,
codec_options=self._codec_options,
)
text_opts_bytes = None
if text_opts:
text_opts_bytes = encode(
text_opts.document,
string_opts_bytes = None
if string_opts:
string_opts_bytes = encode(
string_opts.document,
codec_options=self._codec_options,
)
with _wrap_encryption_errors():
Expand All @@ -953,8 +1013,9 @@ async def _encrypt_helper(
contention_factor=contention_factor,
range_opts=range_opts_bytes,
is_expression=is_expression,
# pymongocrypt still names this parameter text_opts.
# For compatibility with pymongocrypt < 1.16:
**{"text_opts": text_opts_bytes} if text_opts_bytes else {},
**{"text_opts": string_opts_bytes} if string_opts_bytes else {},
)
return decode(encrypted_doc)["v"]

Expand All @@ -967,7 +1028,8 @@ async def encrypt(
query_type: Optional[str] = None,
contention_factor: Optional[int] = None,
range_opts: Optional[RangeOpts] = None,
text_opts: Optional[TextOpts] = None,
string_opts: Optional[StringOpts] = None,
text_opts: Optional[StringOpts] = None,
) -> Binary:
"""Encrypt a BSON value with a given key and algorithm.

Expand All @@ -988,11 +1050,15 @@ async def encrypt(
used.
:param range_opts: Index options for `range` queries. See
:class:`RangeOpts` for some valid options.
:param text_opts: Index options for `textPreview` queries. See
:class:`TextOpts` for some valid options.
:param string_opts: Index options for `prefix`, `suffix`, and
`substring` queries. See :class:`StringOpts` for some valid options.
:param text_opts: **DEPRECATED** - Alias for `string_opts`.

:return: The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.

.. versionchanged:: 4.18
Added the `string_opts` parameter and deprecated `text_opts`.

.. versionchanged:: 4.9
Added the `text_opts` parameter.

Expand All @@ -1016,7 +1082,7 @@ async def encrypt(
contention_factor=contention_factor,
range_opts=range_opts,
is_expression=False,
text_opts=text_opts,
string_opts=_resolve_string_opts(string_opts, text_opts),
),
)

Expand Down
48 changes: 34 additions & 14 deletions pymongo/encryption_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from __future__ import annotations

import warnings
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Optional, TypedDict

Expand Down Expand Up @@ -312,10 +313,8 @@ def document(self) -> dict[str, Any]:
return doc


class TextOpts:
"""**BETA** Options to configure encrypted queries using the text algorithm.

TextOpts is currently unstable API and subject to backwards breaking changes."""
class StringOpts:
"""Options to configure encrypted queries using the string algorithm."""

def __init__(
self,
Expand All @@ -325,15 +324,16 @@ def __init__(
case_sensitive: Optional[bool] = None,
diacritic_sensitive: Optional[bool] = None,
) -> None:
"""Options to configure encrypted queries using the text algorithm.
"""Options to configure encrypted queries using the string algorithm.

:param substring: Further options to support substring queries.
:param prefix: Further options to support prefix queries.
:param suffix: Further options to support suffix queries.
:param case_sensitive: Whether text indexes for this field are case sensitive.
:param diacritic_sensitive: Whether text indexes for this field are diacritic sensitive.
:param case_sensitive: Whether string indexes for this field are case sensitive.
:param diacritic_sensitive: Whether string indexes for this field are diacritic sensitive.

.. versionadded:: 4.15
.. versionadded:: 4.18
``StringOpts`` replaces ``TextOpts``, which is deprecated.
"""
self.substring = substring
self.prefix = prefix
Expand All @@ -357,9 +357,9 @@ def document(self) -> dict[str, Any]:


class SubstringOpts(TypedDict):
"""**BETA** Options for substring text queries.
"""Options for substring string queries.

SubstringOpts is currently unstable API and subject to backwards breaking changes.
.. versionadded:: 4.15
"""

# strMaxLength is the maximum allowed length to insert. Inserting longer strings will error.
Expand All @@ -371,9 +371,9 @@ class SubstringOpts(TypedDict):


class PrefixOpts(TypedDict):
"""**BETA** Options for prefix text queries.
"""Options for prefix string queries.

PrefixOpts is currently unstable API and subject to backwards breaking changes.
.. versionadded:: 4.15
"""

# strMinQueryLength is the minimum allowed query length. Querying with a shorter string will error.
Expand All @@ -383,12 +383,32 @@ class PrefixOpts(TypedDict):


class SuffixOpts(TypedDict):
"""**BETA** Options for suffix text queries.
"""Options for suffix string queries.

SuffixOpts is currently unstable API and subject to backwards breaking changes.
.. versionadded:: 4.15
"""

# strMinQueryLength is the minimum allowed query length. Querying with a shorter string will error.
strMinQueryLength: int
# strMaxQueryLength is the maximum allowed query length. Querying with a longer string will error.
strMaxQueryLength: int


class TextOpts(StringOpts):
"""**DEPRECATED** Options to configure encrypted queries using the text algorithm.

.. note:: ``TextOpts`` is deprecated. Use :class:`StringOpts` instead.

.. versionadded:: 4.15

.. versionchanged:: 4.18
Deprecated in favor of :class:`StringOpts`.
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn(
"TextOpts is deprecated. Use StringOpts instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)
Loading
Loading