Skip to content

Commit 7387a6a

Browse files
authored
PYTHON-5531 Add support for textPreview (#1065)
1 parent 53485ab commit 7387a6a

File tree

7 files changed

+90
-0
lines changed

7 files changed

+90
-0
lines changed

bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ async def encrypt(
9393
contention_factor=None,
9494
range_opts=None,
9595
is_expression=False,
96+
text_opts=None,
9697
):
9798
"""Encrypts a BSON value.
9899
@@ -114,6 +115,8 @@ async def encrypt(
114115
with the "range" algorithm encoded as a BSON document.
115116
- `is_expression` (boolean): True if this is an encryptExpression()
116117
context. Defaults to False.
118+
- `text_opts` (bytes): Options for explicit encryption
119+
with the "textPreview" algorithm encoded as a BSON document.
117120
118121
:Returns:
119122
The encrypted BSON value.
@@ -122,6 +125,8 @@ async def encrypt(
122125
Added the `query_type` and `contention_factor` parameters.
123126
.. versionchanged:: 1.5
124127
Added the `range_opts` and `is_expression` parameters.
128+
.. versionchanged:: 1.16
129+
Added the `text_opts` parameter.
125130
"""
126131
# CDRIVER-3275 key_alt_name needs to be wrapped in a bson document.
127132
if key_alt_name is not None:
@@ -134,6 +139,7 @@ async def encrypt(
134139
contention_factor,
135140
range_opts,
136141
is_expression,
142+
text_opts,
137143
)
138144
with self.mongocrypt.explicit_encryption_context(value, opts) as ctx:
139145
return await run_state_machine(ctx, self.callback)

bindings/python/pymongocrypt/mongocrypt.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,11 @@ def __init__(self, ctx, kms_providers, value, opts):
501501
):
502502
self._raise_from_status()
503503

504+
if opts.text_opts is not None:
505+
with MongoCryptBinaryIn(opts.text_opts) as text_opts:
506+
if not lib.mongocrypt_ctx_setopt_algorithm_text(ctx, text_opts.bin):
507+
self._raise_from_status()
508+
504509
with MongoCryptBinaryIn(value) as binary:
505510
if opts.is_expression:
506511
if not lib.mongocrypt_ctx_explicit_encrypt_expression_init(

bindings/python/pymongocrypt/options.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ def __init__(
170170
contention_factor=None,
171171
range_opts=None,
172172
is_expression=False,
173+
text_opts=None,
173174
):
174175
"""Options for explicit encryption.
175176
@@ -186,11 +187,15 @@ def __init__(
186187
with the "range" algorithm encoded as a BSON document.
187188
- `is_expression` (boolean): True if this is an encryptExpression()
188189
context. Defaults to False.
190+
- `text_opts` (bytes): Options for explicit encryption
191+
with the "textPreview" algorithm encoded as a BSON document.
189192
190193
.. versionchanged:: 1.3
191194
Added the `query_type` and `contention_factor` parameters.
192195
.. versionchanged:: 1.5
193196
Added the `range_opts` and `is_expression` parameters.
197+
.. versionchanged:: 1.16
198+
Added the `text_opts` parameter.
194199
"""
195200
self.algorithm = algorithm
196201
self.key_id = key_id
@@ -212,6 +217,11 @@ def __init__(
212217
)
213218
self.range_opts = range_opts
214219
self.is_expression = is_expression
220+
if text_opts is not None and not isinstance(text_opts, bytes):
221+
raise TypeError(
222+
f"text_opts must be an bytes or None, not: {type(text_opts)}"
223+
)
224+
self.text_opts = text_opts
215225

216226

217227
class DataKeyOpts:

bindings/python/pymongocrypt/synchronous/explicit_encrypter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def encrypt(
9393
contention_factor=None,
9494
range_opts=None,
9595
is_expression=False,
96+
text_opts=None,
9697
):
9798
"""Encrypts a BSON value.
9899
@@ -114,6 +115,8 @@ def encrypt(
114115
with the "range" algorithm encoded as a BSON document.
115116
- `is_expression` (boolean): True if this is an encryptExpression()
116117
context. Defaults to False.
118+
- `text_opts` (bytes): Options for explicit encryption
119+
with the "textPreview" algorithm encoded as a BSON document.
117120
118121
:Returns:
119122
The encrypted BSON value.
@@ -122,6 +125,8 @@ def encrypt(
122125
Added the `query_type` and `contention_factor` parameters.
123126
.. versionchanged:: 1.5
124127
Added the `range_opts` and `is_expression` parameters.
128+
.. versionchanged:: 1.16
129+
Added the `text_opts` parameter.
125130
"""
126131
# CDRIVER-3275 key_alt_name needs to be wrapped in a bson document.
127132
if key_alt_name is not None:
@@ -134,6 +139,7 @@ def encrypt(
134139
contention_factor,
135140
range_opts,
136141
is_expression,
142+
text_opts,
137143
)
138144
with self.mongocrypt.explicit_encryption_context(value, opts) as ctx:
139145
return run_state_machine(ctx, self.callback)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"v": {
3+
"$binary": "EqQAAAADdHMAhQAAAANlAH0AAAAFZAAgAAAAACmNEIGYF35VPFzmyWuvaCXPAVtXyzAMQ3fdWSFJ3Ji9BXMAIAAAAAA3qH8Y7MnTiDaHYF8L84k4YsWj2IP25sY5lBUo5s2aOgVsACAAAAAAVdUcnSWSe5b9XPo4L/3LELFwsT0PwiNBPYNs3UIhEWQAABJjbQAAAAAAAAAAAAhjZgABCGRmAAEA",
4+
"$type": "06"
5+
}
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"caseSensitive": true,
3+
"diacriticSensitive": true,
4+
"prefix": {
5+
"strMaxQueryLength": 10,
6+
"strMinQueryLength": 2
7+
}
8+
}

bindings/python/test/test_mongocrypt.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import unittest.mock
3939

4040
import respx
41+
from pymongo import MongoClient
4142
from pymongo_auth_aws.auth import AwsCredential
4243

4344
from pymongocrypt.asynchronous.auto_encrypter import AsyncAutoEncrypter
@@ -1013,6 +1014,30 @@ async def test_range_query_int32(self):
10131014
encrypted_val, adjust_range_counter(encrypted_val, expected)
10141015
)
10151016

1017+
async def test_text_query(self):
1018+
key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json"
1019+
key_id = json_data(key_path)["_id"]
1020+
encrypter = AsyncExplicitEncrypter(
1021+
MockAsyncCallback(
1022+
key_docs=[bson_data(key_path)], kms_reply=http_data("kms-reply.txt")
1023+
),
1024+
self.mongo_crypt_opts(),
1025+
)
1026+
self.addCleanup(encrypter.close)
1027+
1028+
text_opts = bson_data("fle2-text-search/textopts.json")
1029+
expected = bson_data("fle2-text-search/encrypted-payload.json")
1030+
value = bson.encode({"v": "foo"})
1031+
encrypted = await encrypter.encrypt(
1032+
value,
1033+
"textPreview",
1034+
key_id=key_id,
1035+
query_type="suffixPreview",
1036+
contention_factor=0,
1037+
text_opts=text_opts,
1038+
)
1039+
self.assertEqual(encrypted, expected)
1040+
10161041

10171042
class TestNeedKMSAzureCredentials(unittest.TestCase):
10181043
maxDiff = None
@@ -1459,6 +1484,30 @@ def test_rangePreview_query_int32(self):
14591484
is_expression=True,
14601485
)
14611486

1487+
def test_text_query(self):
1488+
key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json"
1489+
key_id = json_data(key_path)["_id"]
1490+
encrypter = ExplicitEncrypter(
1491+
MockCallback(
1492+
key_docs=[bson_data(key_path)], kms_reply=http_data("kms-reply.txt")
1493+
),
1494+
self.mongo_crypt_opts(),
1495+
)
1496+
self.addCleanup(encrypter.close)
1497+
1498+
text_opts = bson_data("fle2-text-search/textopts.json")
1499+
expected = bson_data("fle2-text-search/encrypted-payload.json")
1500+
value = bson.encode({"v": "foo"})
1501+
encrypted = encrypter.encrypt(
1502+
value,
1503+
"textPreview",
1504+
key_id=key_id,
1505+
query_type="suffixPreview",
1506+
contention_factor=0,
1507+
text_opts=text_opts,
1508+
)
1509+
self.assertEqual(encrypted, expected)
1510+
14621511

14631512
def read(filename, **kwargs):
14641513
with open(os.path.join(DATA_DIR, filename), **kwargs) as fp:

0 commit comments

Comments
 (0)