From bb876ac9f0d6c2cc6d542c65e52026daacd4099d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 14 Aug 2026 14:59:14 -0500 Subject: [PATCH 1/5] PYTHON-5991 - [pymongocrypt] Bundle libmongocrypt 1.20.2 Bump the bundled libmongocrypt version and regenerate the SBOM. The C sources in this repo already contain everything through the 1.20.2 tag, so binding.py needs no regeneration. Also correct the text_opts docstrings, which still referred to the "textPreview" algorithm. That name was removed in libmongocrypt 1.20.0 in favor of "string". --- bindings/python/CHANGELOG.rst | 8 ++++++++ .../asynchronous/explicit_encrypter.py | 2 +- bindings/python/pymongocrypt/options.py | 2 +- .../pymongocrypt/synchronous/explicit_encrypter.py | 2 +- bindings/python/sbom.json | 14 +++++++------- bindings/python/scripts/libmongocrypt-version.txt | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/bindings/python/CHANGELOG.rst b/bindings/python/CHANGELOG.rst index a70c34d22..d2f19d1f8 100644 --- a/bindings/python/CHANGELOG.rst +++ b/bindings/python/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +Changes in Version 1.18.2 +------------------------- + +- Bundle libmongocrypt 1.20.2 in release wheels. +- The "textPreview" algorithm has been removed. Use "string" instead. The + "substringPreview", "suffixPreview", and "prefixPreview" query types have + likewise been replaced by "substring", "suffix", and "prefix". + Changes in Version 1.18.1 ------------------------- diff --git a/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py b/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py index 33abd2f09..3c3e09e37 100644 --- a/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py +++ b/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py @@ -116,7 +116,7 @@ async def encrypt( - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - `text_opts` (bytes): Options for explicit encryption - with the "textPreview" algorithm encoded as a BSON document. + with the "string" algorithm encoded as a BSON document. :Returns: The encrypted BSON value. diff --git a/bindings/python/pymongocrypt/options.py b/bindings/python/pymongocrypt/options.py index f61372f23..dad5a31ff 100644 --- a/bindings/python/pymongocrypt/options.py +++ b/bindings/python/pymongocrypt/options.py @@ -188,7 +188,7 @@ def __init__( - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - `text_opts` (bytes): Options for explicit encryption - with the "textPreview" algorithm encoded as a BSON document. + with the "string" algorithm encoded as a BSON document. .. versionchanged:: 1.3 Added the `query_type` and `contention_factor` parameters. diff --git a/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py b/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py index 846ee647f..d7862e0ed 100644 --- a/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py +++ b/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py @@ -116,7 +116,7 @@ def encrypt( - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - `text_opts` (bytes): Options for explicit encryption - with the "textPreview" algorithm encoded as a BSON document. + with the "string" algorithm encoded as a BSON document. :Returns: The encrypted BSON value. diff --git a/bindings/python/sbom.json b/bindings/python/sbom.json index 0b2bf5cc3..be797eb39 100644 --- a/bindings/python/sbom.json +++ b/bindings/python/sbom.json @@ -1,31 +1,31 @@ { "components": [ { - "bom-ref": "pkg:github/mongodb/libmongocrypt@1.20.0", + "bom-ref": "pkg:github/mongodb/libmongocrypt@1.20.2", "externalReferences": [ { "type": "distribution", - "url": "https://github.com/mongodb/libmongocrypt/archive/1.20.0.tar.gz" + "url": "https://github.com/mongodb/libmongocrypt/archive/1.20.2.tar.gz" }, { "type": "website", - "url": "https://github.com/mongodb/libmongocrypt/tree/1.20.0" + "url": "https://github.com/mongodb/libmongocrypt/tree/1.20.2" } ], "group": "mongodb", "name": "libmongocrypt", - "purl": "pkg:github/mongodb/libmongocrypt@1.20.0", + "purl": "pkg:github/mongodb/libmongocrypt@1.20.2", "type": "library", - "version": "1.20.0" + "version": "1.20.2" } ], "dependencies": [ { - "ref": "pkg:github/mongodb/libmongocrypt@1.20.0" + "ref": "pkg:github/mongodb/libmongocrypt@1.20.2" } ], "metadata": { - "timestamp": "2026-06-30T18:17:42.887189+00:00", + "timestamp": "2026-08-14T19:41:24.430909+00:00", "tools": [ { "externalReferences": [ diff --git a/bindings/python/scripts/libmongocrypt-version.txt b/bindings/python/scripts/libmongocrypt-version.txt index 398935591..769e37e15 100644 --- a/bindings/python/scripts/libmongocrypt-version.txt +++ b/bindings/python/scripts/libmongocrypt-version.txt @@ -1 +1 @@ -1.20.0 +1.20.2 From ce1ec92d22835ffb411fbdbccb0f97f6da5d4c8e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 14 Aug 2026 15:04:48 -0500 Subject: [PATCH 2/5] Update changelog for version 1.19.0 --- bindings/python/CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/CHANGELOG.rst b/bindings/python/CHANGELOG.rst index d2f19d1f8..462225459 100644 --- a/bindings/python/CHANGELOG.rst +++ b/bindings/python/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -Changes in Version 1.18.2 +Changes in Version 1.19.0 ------------------------- - Bundle libmongocrypt 1.20.2 in release wheels. From 4a47c03ac8f113205e154ec8ba884d3064d4fb54 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 17 Aug 2026 06:40:04 -0500 Subject: [PATCH 3/5] PYTHON-5991 Rename text_opts to string_opts and cover deprecated names libmongocrypt asks drivers to expose these options as "string" rather than "text", so rename the parameter to match the algorithm it configures. Add tests for the deprecated spellings that libmongocrypt still accepts or rejects, so a future libmongocrypt bump cannot change either behavior silently: - the "textPreview" algorithm raises - "prefixPreview", "suffixPreview", and "substringPreview" produce payloads identical to "prefix", "suffix", and "substring" PYTHON-5959 drops the three query type aliases. Also correct the changelog, which claimed those three query types had already been replaced. They remain accepted as aliases. --- bindings/python/CHANGELOG.rst | 10 +- .../asynchronous/explicit_encrypter.py | 8 +- bindings/python/pymongocrypt/mongocrypt.py | 8 +- bindings/python/pymongocrypt/options.py | 12 +- .../synchronous/explicit_encrypter.py | 8 +- bindings/python/test/test_mongocrypt.py | 120 +++++++++++++++++- 6 files changed, 145 insertions(+), 21 deletions(-) diff --git a/bindings/python/CHANGELOG.rst b/bindings/python/CHANGELOG.rst index 462225459..c99bc6102 100644 --- a/bindings/python/CHANGELOG.rst +++ b/bindings/python/CHANGELOG.rst @@ -5,9 +5,13 @@ Changes in Version 1.19.0 ------------------------- - Bundle libmongocrypt 1.20.2 in release wheels. -- The "textPreview" algorithm has been removed. Use "string" instead. The - "substringPreview", "suffixPreview", and "prefixPreview" query types have - likewise been replaced by "substring", "suffix", and "prefix". +- Renamed the ``text_opts`` parameter of ``ExplicitEncryptOpts`` and + ``ExplicitEncrypter.encrypt`` to ``string_opts``, matching the "string" + algorithm it configures. This is a breaking change. +- The "textPreview" algorithm has been removed. Use "string" instead. +- The "prefixPreview", "suffixPreview", and "substringPreview" query types are + now deprecated aliases of "prefix", "suffix", and "substring". Prefer the new + names, as a future release removes the aliases. Changes in Version 1.18.1 ------------------------- diff --git a/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py b/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py index 3c3e09e37..6ffc376d0 100644 --- a/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py +++ b/bindings/python/pymongocrypt/asynchronous/explicit_encrypter.py @@ -93,7 +93,7 @@ async def encrypt( contention_factor=None, range_opts=None, is_expression=False, - text_opts=None, + string_opts=None, ): """Encrypts a BSON value. @@ -115,7 +115,7 @@ async def encrypt( with the "range" algorithm encoded as a BSON document. - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - - `text_opts` (bytes): Options for explicit encryption + - `string_opts` (bytes): Options for explicit encryption with the "string" algorithm encoded as a BSON document. :Returns: @@ -127,6 +127,8 @@ async def encrypt( Added the `range_opts` and `is_expression` parameters. .. versionchanged:: 1.16 Added the `text_opts` parameter. + .. versionchanged:: 1.19 + Renamed the `text_opts` parameter to `string_opts`. """ # CDRIVER-3275 key_alt_name needs to be wrapped in a bson document. if key_alt_name is not None: @@ -139,7 +141,7 @@ async def encrypt( contention_factor, range_opts, is_expression, - text_opts, + string_opts, ) with self.mongocrypt.explicit_encryption_context(value, opts) as ctx: return await run_state_machine(ctx, self.callback) diff --git a/bindings/python/pymongocrypt/mongocrypt.py b/bindings/python/pymongocrypt/mongocrypt.py index 8202e1959..a0e1da6cc 100644 --- a/bindings/python/pymongocrypt/mongocrypt.py +++ b/bindings/python/pymongocrypt/mongocrypt.py @@ -501,9 +501,11 @@ def __init__(self, ctx, kms_providers, value, opts): ): self._raise_from_status() - if opts.text_opts is not None: - with MongoCryptBinaryIn(opts.text_opts) as text_opts: - if not lib.mongocrypt_ctx_setopt_algorithm_text(ctx, text_opts.bin): + if opts.string_opts is not None: + with MongoCryptBinaryIn(opts.string_opts) as string_opts: + if not lib.mongocrypt_ctx_setopt_algorithm_text( + ctx, string_opts.bin + ): self._raise_from_status() with MongoCryptBinaryIn(value) as binary: diff --git a/bindings/python/pymongocrypt/options.py b/bindings/python/pymongocrypt/options.py index dad5a31ff..b275d929a 100644 --- a/bindings/python/pymongocrypt/options.py +++ b/bindings/python/pymongocrypt/options.py @@ -170,7 +170,7 @@ def __init__( contention_factor=None, range_opts=None, is_expression=False, - text_opts=None, + string_opts=None, ): """Options for explicit encryption. @@ -187,7 +187,7 @@ def __init__( with the "range" algorithm encoded as a BSON document. - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - - `text_opts` (bytes): Options for explicit encryption + - `string_opts` (bytes): Options for explicit encryption with the "string" algorithm encoded as a BSON document. .. versionchanged:: 1.3 @@ -196,6 +196,8 @@ def __init__( Added the `range_opts` and `is_expression` parameters. .. versionchanged:: 1.16 Added the `text_opts` parameter. + .. versionchanged:: 1.19 + Renamed the `text_opts` parameter to `string_opts`. """ self.algorithm = algorithm self.key_id = key_id @@ -217,11 +219,11 @@ def __init__( ) self.range_opts = range_opts self.is_expression = is_expression - if text_opts is not None and not isinstance(text_opts, bytes): + if string_opts is not None and not isinstance(string_opts, bytes): raise TypeError( - f"text_opts must be an bytes or None, not: {type(text_opts)}" + f"string_opts must be an bytes or None, not: {type(string_opts)}" ) - self.text_opts = text_opts + self.string_opts = string_opts class DataKeyOpts: diff --git a/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py b/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py index d7862e0ed..e20686d54 100644 --- a/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py +++ b/bindings/python/pymongocrypt/synchronous/explicit_encrypter.py @@ -93,7 +93,7 @@ def encrypt( contention_factor=None, range_opts=None, is_expression=False, - text_opts=None, + string_opts=None, ): """Encrypts a BSON value. @@ -115,7 +115,7 @@ def encrypt( with the "range" algorithm encoded as a BSON document. - `is_expression` (boolean): True if this is an encryptExpression() context. Defaults to False. - - `text_opts` (bytes): Options for explicit encryption + - `string_opts` (bytes): Options for explicit encryption with the "string" algorithm encoded as a BSON document. :Returns: @@ -127,6 +127,8 @@ def encrypt( Added the `range_opts` and `is_expression` parameters. .. versionchanged:: 1.16 Added the `text_opts` parameter. + .. versionchanged:: 1.19 + Renamed the `text_opts` parameter to `string_opts`. """ # CDRIVER-3275 key_alt_name needs to be wrapped in a bson document. if key_alt_name is not None: @@ -139,7 +141,7 @@ def encrypt( contention_factor, range_opts, is_expression, - text_opts, + string_opts, ) with self.mongocrypt.explicit_encryption_context(value, opts) as ctx: return run_state_machine(ctx, self.callback) diff --git a/bindings/python/test/test_mongocrypt.py b/bindings/python/test/test_mongocrypt.py index c49f18b3b..9f4000cc9 100644 --- a/bindings/python/test/test_mongocrypt.py +++ b/bindings/python/test/test_mongocrypt.py @@ -1025,7 +1025,7 @@ async def test_text_query(self): ) self.addCleanup(encrypter.close) - text_opts = bson_data("fle2-text-search/textopts.json") + string_opts = bson_data("fle2-text-search/textopts.json") expected = bson_data("fle2-text-search/encrypted-payload.json") value = bson.encode({"v": "foo"}) encrypted = await encrypter.encrypt( @@ -1034,10 +1034,68 @@ async def test_text_query(self): key_id=key_id, query_type="suffix", contention_factor=0, - text_opts=text_opts, + string_opts=string_opts, ) self.assertEqual(encrypted, expected) + async def test_textPreview_query(self): + key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" + key_id = json_data(key_path)["_id"] + encrypter = AsyncExplicitEncrypter( + MockAsyncCallback( + key_docs=[bson_data(key_path)], + kms_reply=http_data("kms-reply.txt"), + ), + self.mongo_crypt_opts(), + ) + self.addCleanup(encrypter.close) + + with self.assertRaisesRegex( + MongoCryptError, + "Algorithm 'textPreview' is deprecated, please use 'string'", + ): + await encrypter.encrypt( + bson.encode({"v": "foo"}), + "textPreview", + key_id=key_id, + query_type="suffix", + contention_factor=0, + string_opts=bson_data("fle2-text-search/textopts.json"), + ) + + async def test_deprecated_preview_query_types(self): + # The "*Preview" query types remain accepted as aliases of their + # modern names. PYTHON-5959 drops them. + key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" + key_id = json_data(key_path)["_id"] + encrypter = AsyncExplicitEncrypter( + MockAsyncCallback( + key_docs=[bson_data(key_path)], kms_reply=http_data("kms-reply.txt") + ), + self.mongo_crypt_opts(), + ) + self.addCleanup(encrypter.close) + + string_opts = bson_data("fle2-text-search/textopts.json") + value = bson.encode({"v": "foo"}) + + async def encrypt(query_type): + return await encrypter.encrypt( + value, + "string", + key_id=key_id, + query_type=query_type, + contention_factor=0, + string_opts=string_opts, + ) + + for query_type in ["prefix", "suffix", "substring"]: + with self.subTest(query_type=query_type): + self.assertEqual( + await encrypt(f"{query_type}Preview"), + await encrypt(query_type), + ) + class TestNeedKMSAzureCredentials(unittest.TestCase): maxDiff = None @@ -1495,7 +1553,7 @@ def test_text_query(self): ) self.addCleanup(encrypter.close) - text_opts = bson_data("fle2-text-search/textopts.json") + string_opts = bson_data("fle2-text-search/textopts.json") expected = bson_data("fle2-text-search/encrypted-payload.json") value = bson.encode({"v": "foo"}) encrypted = encrypter.encrypt( @@ -1504,10 +1562,64 @@ def test_text_query(self): key_id=key_id, query_type="suffix", contention_factor=0, - text_opts=text_opts, + string_opts=string_opts, ) self.assertEqual(encrypted, expected) + def test_textPreview_query(self): + key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" + key_id = json_data(key_path)["_id"] + encrypter = ExplicitEncrypter( + MockCallback( + key_docs=[bson_data(key_path)], kms_reply=http_data("kms-reply.txt") + ), + self.mongo_crypt_opts(), + ) + self.addCleanup(encrypter.close) + + with self.assertRaisesRegex( + MongoCryptError, + "Algorithm 'textPreview' is deprecated, please use 'string'", + ): + encrypter.encrypt( + bson.encode({"v": "foo"}), + "textPreview", + key_id=key_id, + query_type="suffix", + contention_factor=0, + string_opts=bson_data("fle2-text-search/textopts.json"), + ) + + def test_deprecated_preview_query_types(self): + # The "*Preview" query types remain accepted as aliases of their modern + # names. PYTHON-5959 drops them. + key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" + key_id = json_data(key_path)["_id"] + encrypter = ExplicitEncrypter( + MockCallback( + key_docs=[bson_data(key_path)], kms_reply=http_data("kms-reply.txt") + ), + self.mongo_crypt_opts(), + ) + self.addCleanup(encrypter.close) + + string_opts = bson_data("fle2-text-search/textopts.json") + value = bson.encode({"v": "foo"}) + + def encrypt(query_type): + return encrypter.encrypt( + value, + "string", + key_id=key_id, + query_type=query_type, + contention_factor=0, + string_opts=string_opts, + ) + + for query_type in ["prefix", "suffix", "substring"]: + with self.subTest(query_type=query_type): + self.assertEqual(encrypt(f"{query_type}Preview"), encrypt(query_type)) + def read(filename, **kwargs): with open(os.path.join(DATA_DIR, filename), **kwargs) as fp: From 94b69b7f6045f340ace838799f6c79c5f248bbf6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 17 Aug 2026 06:52:33 -0500 Subject: [PATCH 4/5] PYTHON-5991 Bump version to 1.19.0.dev0 and fix SBOM script name The changelog now heads the release as 1.19.0, so bump __version__ to match. hatch reads the version from this file, so the built wheel carries it. The SBOM freshness check pointed at "update-sbom.sh", which does not exist. The script that regenerates the SBOM is scripts/update-version.sh. --- bindings/python/.evergreen/test.sh | 2 +- bindings/python/pymongocrypt/version.py | 2 +- bindings/python/test/test_mongocrypt.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bindings/python/.evergreen/test.sh b/bindings/python/.evergreen/test.sh index 0c2f56dca..52344758d 100755 --- a/bindings/python/.evergreen/test.sh +++ b/bindings/python/.evergreen/test.sh @@ -95,6 +95,6 @@ EXPECTED="pkg:github/mongodb/libmongocrypt@$LIBMONGOCRYPT_VERSION" if grep -q $EXPECTED sbom.json; then echo "SBOM is up to date!" else - echo "SBOM is out of date! Run the \"update-sbom.sh\" script." + echo "SBOM is out of date! Run the \"scripts/update-version.sh\" script." exit 1 fi diff --git a/bindings/python/pymongocrypt/version.py b/bindings/python/pymongocrypt/version.py index 326bfbfde..1e35ca4a5 100644 --- a/bindings/python/pymongocrypt/version.py +++ b/bindings/python/pymongocrypt/version.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.18.2.dev0" +__version__ = "1.19.0.dev0" _MIN_LIBMONGOCRYPT_VERSION = "1.8.0" diff --git a/bindings/python/test/test_mongocrypt.py b/bindings/python/test/test_mongocrypt.py index 9f4000cc9..2de9f186a 100644 --- a/bindings/python/test/test_mongocrypt.py +++ b/bindings/python/test/test_mongocrypt.py @@ -1064,8 +1064,8 @@ async def test_textPreview_query(self): ) async def test_deprecated_preview_query_types(self): - # The "*Preview" query types remain accepted as aliases of their - # modern names. PYTHON-5959 drops them. + # The "*Preview" query types remain as aliases to the GA names. + # PYTHON-5959 drops them. key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" key_id = json_data(key_path)["_id"] encrypter = AsyncExplicitEncrypter( @@ -1591,8 +1591,8 @@ def test_textPreview_query(self): ) def test_deprecated_preview_query_types(self): - # The "*Preview" query types remain accepted as aliases of their modern - # names. PYTHON-5959 drops them. + # The "*Preview" query types remain as aliases to the GA names. + # PYTHON-5959 drops them. key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" key_id = json_data(key_path)["_id"] encrypter = ExplicitEncrypter( From adac929a8cd367d04dde7b2e7bbe4fc9a02a7b7f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 17 Aug 2026 07:28:19 -0500 Subject: [PATCH 5/5] PYTHON-5991 Rename test_textPreview_query to test_textPreview_query_fails --- bindings/python/test/test_mongocrypt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/test/test_mongocrypt.py b/bindings/python/test/test_mongocrypt.py index 2de9f186a..1d5f51162 100644 --- a/bindings/python/test/test_mongocrypt.py +++ b/bindings/python/test/test_mongocrypt.py @@ -1038,7 +1038,7 @@ async def test_text_query(self): ) self.assertEqual(encrypted, expected) - async def test_textPreview_query(self): + async def test_textPreview_query_fails(self): key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" key_id = json_data(key_path)["_id"] encrypter = AsyncExplicitEncrypter( @@ -1566,7 +1566,7 @@ def test_text_query(self): ) self.assertEqual(encrypted, expected) - def test_textPreview_query(self): + def test_textPreview_query_fails(self): key_path = "keys/ABCDEFAB123498761234123456789012-local-document.json" key_id = json_data(key_path)["_id"] encrypter = ExplicitEncrypter(