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/CHANGELOG.rst b/bindings/python/CHANGELOG.rst index a70c34d22..c99bc6102 100644 --- a/bindings/python/CHANGELOG.rst +++ b/bindings/python/CHANGELOG.rst @@ -1,6 +1,18 @@ Changelog ========= +Changes in Version 1.19.0 +------------------------- + +- Bundle libmongocrypt 1.20.2 in release wheels. +- 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 33abd2f09..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,8 +115,8 @@ 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 - with the "textPreview" algorithm encoded as a BSON document. + - `string_opts` (bytes): Options for explicit encryption + with the "string" algorithm encoded as a BSON document. :Returns: The encrypted BSON value. @@ -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 f61372f23..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,8 +187,8 @@ 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 - with the "textPreview" algorithm encoded as a BSON document. + - `string_opts` (bytes): Options for explicit encryption + with the "string" algorithm encoded as a BSON document. .. versionchanged:: 1.3 Added the `query_type` and `contention_factor` parameters. @@ -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 846ee647f..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,8 +115,8 @@ 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 - with the "textPreview" algorithm encoded as a BSON document. + - `string_opts` (bytes): Options for explicit encryption + with the "string" algorithm encoded as a BSON document. :Returns: The encrypted BSON value. @@ -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/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/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 diff --git a/bindings/python/test/test_mongocrypt.py b/bindings/python/test/test_mongocrypt.py index c49f18b3b..1d5f51162 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_fails(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 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( + 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_fails(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 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( + 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: