Skip to content

Commit 9845688

Browse files
committed
PYTHON-5909 Add GA support for Queryable Encryption string queries
1 parent c18a78b commit 9845688

6 files changed

Lines changed: 952 additions & 338 deletions

File tree

doc/changelog.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ PyMongo 4.18 brings a number of changes including:
3030
- Fixed a bug on Windows, and on macOS when using PyOpenSSL, where
3131
``SSL_CERT_FILE``/``SSL_CERT_DIR`` were merged with, rather than replacing,
3232
the OS/certifi certificate store.
33+
- Added general availability support for Queryable Encryption prefix, suffix,
34+
and substring queries against MongoDB 9.0+, which requires libmongocrypt
35+
1.20.0 or later:
36+
37+
- Added :attr:`~pymongo.encryption.Algorithm.STRING` and
38+
:class:`~pymongo.encryption_options.StringOpts`, replacing
39+
``Algorithm.TEXTPREVIEW`` and ``TextOpts``, which are now deprecated.
40+
- Added :attr:`~pymongo.encryption.QueryType.PREFIX`,
41+
:attr:`~pymongo.encryption.QueryType.SUFFIX`, and
42+
:attr:`~pymongo.encryption.QueryType.SUBSTRING`. The corresponding
43+
``PREFIXPREVIEW``, ``SUFFIXPREVIEW``, and ``SUBSTRINGPREVIEW`` query types
44+
remain for experimental use with MongoDB versions before 9.0.
45+
- Added the ``string_opts`` parameter to
46+
:meth:`~pymongo.encryption.ClientEncryption.encrypt` and
47+
:meth:`~pymongo.asynchronous.encryption.AsyncClientEncryption.encrypt`,
48+
deprecating ``text_opts``.
3349

3450
Changes in Version 4.17.0 (2026/04/20)
3551
--------------------------------------

pymongo/asynchronous/encryption.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import socket
2323
import time as time # noqa: PLC0414 # needed in sync version
2424
import uuid
25+
import warnings
2526
import weakref
2627
from collections.abc import AsyncGenerator, Iterator, Mapping, MutableMapping, Sequence
2728
from copy import deepcopy
@@ -65,7 +66,7 @@
6566
from pymongo.encryption_options import (
6667
AutoEncryptionOpts,
6768
RangeOpts,
68-
TextOpts,
69+
StringOpts,
6970
check_min_pymongocrypt,
7071
)
7172
from pymongo.errors import (
@@ -529,8 +530,15 @@ class Algorithm(str, enum.Enum):
529530
530531
.. versionadded:: 4.4
531532
"""
533+
STRING = "String"
534+
"""String.
535+
536+
.. versionadded:: 4.18
537+
"""
532538
TEXTPREVIEW = "TextPreview"
533-
"""**BETA** - TextPreview.
539+
"""**DEPRECATED** - TextPreview.
540+
541+
.. note:: Support for TextPreview is deprecated. Use :attr:`Algorithm.STRING` instead.
534542
535543
.. versionadded:: 4.15
536544
"""
@@ -559,25 +567,77 @@ class QueryType(str, enum.Enum):
559567
.. versionadded:: 4.4
560568
"""
561569

570+
PREFIX = "prefix"
571+
"""Used to encrypt a value for a prefix query.
572+
573+
Used for the ``$encStrStartsWith`` operator. Requires MongoDB 9.0+.
574+
575+
.. versionadded:: 4.18
576+
"""
577+
578+
SUFFIX = "suffix"
579+
"""Used to encrypt a value for a suffix query.
580+
581+
Used for the ``$encStrEndsWith`` operator. Requires MongoDB 9.0+.
582+
583+
.. versionadded:: 4.18
584+
"""
585+
586+
SUBSTRING = "substring"
587+
"""Used to encrypt a value for a substring query.
588+
589+
Used for the ``$encStrContains`` operator. Requires MongoDB 9.0+.
590+
591+
.. versionadded:: 4.18
592+
"""
593+
562594
PREFIXPREVIEW = "prefixPreview"
563595
"""**BETA** - Used to encrypt a value for a prefixPreview query.
564596
597+
.. note:: The preview query types are for experimental workloads only and
598+
are only supported by MongoDB versions before 9.0. Use
599+
:attr:`QueryType.PREFIX` instead.
600+
565601
.. versionadded:: 4.15
566602
"""
567603

568604
SUFFIXPREVIEW = "suffixPreview"
569605
"""**BETA** - Used to encrypt a value for a suffixPreview query.
570606
607+
.. note:: The preview query types are for experimental workloads only and
608+
are only supported by MongoDB versions before 9.0. Use
609+
:attr:`QueryType.SUFFIX` instead.
610+
571611
.. versionadded:: 4.15
572612
"""
573613

574614
SUBSTRINGPREVIEW = "substringPreview"
575615
"""**BETA** - Used to encrypt a value for a substringPreview query.
576616
617+
.. note:: The preview query types are for experimental workloads only and
618+
are only supported by MongoDB versions before 9.0. Use
619+
:attr:`QueryType.SUBSTRING` instead.
620+
577621
.. versionadded:: 4.15
578622
"""
579623

580624

625+
def _resolve_string_opts(
626+
string_opts: Optional[StringOpts], text_opts: Optional[StringOpts]
627+
) -> Optional[StringOpts]:
628+
"""Resolve the deprecated text_opts alias for string_opts."""
629+
if text_opts is None:
630+
return string_opts
631+
if string_opts is not None:
632+
raise ConfigurationError("Cannot set both string_opts and text_opts")
633+
warnings.warn(
634+
"The text_opts parameter is deprecated. Use string_opts instead.",
635+
DeprecationWarning,
636+
stacklevel=3,
637+
)
638+
return text_opts
639+
640+
581641
def _create_mongocrypt_options(**kwargs: Any) -> MongoCryptOptions:
582642
# For compat with pymongocrypt <1.13, avoid setting the default key_expiration_ms.
583643
if kwargs.get("key_expiration_ms") is None:
@@ -917,7 +977,7 @@ async def _encrypt_helper(
917977
contention_factor: Optional[int] = None,
918978
range_opts: Optional[RangeOpts] = None,
919979
is_expression: bool = False,
920-
text_opts: Optional[TextOpts] = None,
980+
string_opts: Optional[StringOpts] = None,
921981
) -> Any:
922982
self._check_closed()
923983
if isinstance(key_id, uuid.UUID):
@@ -937,10 +997,10 @@ async def _encrypt_helper(
937997
range_opts.document,
938998
codec_options=self._codec_options,
939999
)
940-
text_opts_bytes = None
941-
if text_opts:
942-
text_opts_bytes = encode(
943-
text_opts.document,
1000+
string_opts_bytes = None
1001+
if string_opts:
1002+
string_opts_bytes = encode(
1003+
string_opts.document,
9441004
codec_options=self._codec_options,
9451005
)
9461006
with _wrap_encryption_errors():
@@ -953,8 +1013,9 @@ async def _encrypt_helper(
9531013
contention_factor=contention_factor,
9541014
range_opts=range_opts_bytes,
9551015
is_expression=is_expression,
1016+
# pymongocrypt still names this parameter text_opts.
9561017
# For compatibility with pymongocrypt < 1.16:
957-
**{"text_opts": text_opts_bytes} if text_opts_bytes else {},
1018+
**{"text_opts": string_opts_bytes} if string_opts_bytes else {},
9581019
)
9591020
return decode(encrypted_doc)["v"]
9601021

@@ -967,7 +1028,8 @@ async def encrypt(
9671028
query_type: Optional[str] = None,
9681029
contention_factor: Optional[int] = None,
9691030
range_opts: Optional[RangeOpts] = None,
970-
text_opts: Optional[TextOpts] = None,
1031+
string_opts: Optional[StringOpts] = None,
1032+
text_opts: Optional[StringOpts] = None,
9711033
) -> Binary:
9721034
"""Encrypt a BSON value with a given key and algorithm.
9731035
@@ -988,11 +1050,15 @@ async def encrypt(
9881050
used.
9891051
:param range_opts: Index options for `range` queries. See
9901052
:class:`RangeOpts` for some valid options.
991-
:param text_opts: Index options for `textPreview` queries. See
992-
:class:`TextOpts` for some valid options.
1053+
:param string_opts: Index options for `prefix`, `suffix`, and
1054+
`substring` queries. See :class:`StringOpts` for some valid options.
1055+
:param text_opts: **DEPRECATED** - Alias for `string_opts`.
9931056
9941057
:return: The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
9951058
1059+
.. versionchanged:: 4.18
1060+
Added the `string_opts` parameter and deprecated `text_opts`.
1061+
9961062
.. versionchanged:: 4.9
9971063
Added the `text_opts` parameter.
9981064
@@ -1016,7 +1082,7 @@ async def encrypt(
10161082
contention_factor=contention_factor,
10171083
range_opts=range_opts,
10181084
is_expression=False,
1019-
text_opts=text_opts,
1085+
string_opts=_resolve_string_opts(string_opts, text_opts),
10201086
),
10211087
)
10221088

pymongo/encryption_options.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from __future__ import annotations
2121

22+
import warnings
2223
from collections.abc import Mapping
2324
from typing import TYPE_CHECKING, Any, Optional, TypedDict
2425

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

314315

315-
class TextOpts:
316-
"""**BETA** Options to configure encrypted queries using the text algorithm.
317-
318-
TextOpts is currently unstable API and subject to backwards breaking changes."""
316+
class StringOpts:
317+
"""Options to configure encrypted queries using the string algorithm."""
319318

320319
def __init__(
321320
self,
@@ -325,15 +324,16 @@ def __init__(
325324
case_sensitive: Optional[bool] = None,
326325
diacritic_sensitive: Optional[bool] = None,
327326
) -> None:
328-
"""Options to configure encrypted queries using the text algorithm.
327+
"""Options to configure encrypted queries using the string algorithm.
329328
330329
:param substring: Further options to support substring queries.
331330
:param prefix: Further options to support prefix queries.
332331
:param suffix: Further options to support suffix queries.
333-
:param case_sensitive: Whether text indexes for this field are case sensitive.
334-
:param diacritic_sensitive: Whether text indexes for this field are diacritic sensitive.
332+
:param case_sensitive: Whether string indexes for this field are case sensitive.
333+
:param diacritic_sensitive: Whether string indexes for this field are diacritic sensitive.
335334
336-
.. versionadded:: 4.15
335+
.. versionadded:: 4.18
336+
``StringOpts`` replaces ``TextOpts``, which is deprecated.
337337
"""
338338
self.substring = substring
339339
self.prefix = prefix
@@ -357,9 +357,9 @@ def document(self) -> dict[str, Any]:
357357

358358

359359
class SubstringOpts(TypedDict):
360-
"""**BETA** Options for substring text queries.
360+
"""Options for substring string queries.
361361
362-
SubstringOpts is currently unstable API and subject to backwards breaking changes.
362+
.. versionadded:: 4.15
363363
"""
364364

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

372372

373373
class PrefixOpts(TypedDict):
374-
"""**BETA** Options for prefix text queries.
374+
"""Options for prefix string queries.
375375
376-
PrefixOpts is currently unstable API and subject to backwards breaking changes.
376+
.. versionadded:: 4.15
377377
"""
378378

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

384384

385385
class SuffixOpts(TypedDict):
386-
"""**BETA** Options for suffix text queries.
386+
"""Options for suffix string queries.
387387
388-
SuffixOpts is currently unstable API and subject to backwards breaking changes.
388+
.. versionadded:: 4.15
389389
"""
390390

391391
# strMinQueryLength is the minimum allowed query length. Querying with a shorter string will error.
392392
strMinQueryLength: int
393393
# strMaxQueryLength is the maximum allowed query length. Querying with a longer string will error.
394394
strMaxQueryLength: int
395+
396+
397+
class TextOpts(StringOpts):
398+
"""**DEPRECATED** Options to configure encrypted queries using the text algorithm.
399+
400+
.. note:: ``TextOpts`` is deprecated. Use :class:`StringOpts` instead.
401+
402+
.. versionadded:: 4.15
403+
404+
.. versionchanged:: 4.18
405+
Deprecated in favor of :class:`StringOpts`.
406+
"""
407+
408+
def __init__(self, *args: Any, **kwargs: Any) -> None:
409+
warnings.warn(
410+
"TextOpts is deprecated. Use StringOpts instead.",
411+
DeprecationWarning,
412+
stacklevel=2,
413+
)
414+
super().__init__(*args, **kwargs)

0 commit comments

Comments
 (0)