From b837d2d59c176218fb47c8fd9acaf6fe0a56b270 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 25 Mar 2025 14:17:58 -0400 Subject: [PATCH 1/3] PYTHON-4933 - Allow drivers to set bypassDocumentValidation: false on write commands --- pymongo/asynchronous/bulk.py | 6 +- pymongo/asynchronous/collection.py | 35 +- pymongo/synchronous/bulk.py | 6 +- pymongo/synchronous/collection.py | 35 +- .../unified/bypassDocumentValidation.json | 493 ++++++++++++++++++ test/utils_shared.py | 6 + 6 files changed, 547 insertions(+), 34 deletions(-) create mode 100644 test/crud/unified/bypassDocumentValidation.json diff --git a/pymongo/asynchronous/bulk.py b/pymongo/asynchronous/bulk.py index 1ea6fd60d9..6050d4c97c 100644 --- a/pymongo/asynchronous/bulk.py +++ b/pymongo/asynchronous/bulk.py @@ -87,7 +87,7 @@ def __init__( self, collection: AsyncCollection[_DocumentType], ordered: bool, - bypass_document_validation: bool, + bypass_document_validation: Optional[bool] = None, comment: Optional[str] = None, let: Optional[Any] = None, ) -> None: @@ -516,8 +516,8 @@ async def _execute_command( if self.comment: cmd["comment"] = self.comment _csot.apply_write_concern(cmd, write_concern) - if self.bypass_doc_val: - cmd["bypassDocumentValidation"] = True + if self.bypass_doc_val is not None: + cmd["bypassDocumentValidation"] = self.bypass_doc_val if self.let is not None and run.op_type in (_DELETE, _UPDATE): cmd["let"] = self.let if session: diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index b87f207760..b9699b8b0d 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -701,7 +701,7 @@ async def bulk_write( self, requests: Sequence[_WriteOp[_DocumentType]], ordered: bool = True, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[AsyncClientSession] = None, comment: Optional[Any] = None, let: Optional[Mapping] = None, @@ -800,8 +800,8 @@ async def _insert_one( ordered: bool, write_concern: WriteConcern, op_id: Optional[int], - bypass_doc_val: bool, session: Optional[AsyncClientSession], + bypass_doc_val: Optional[bool] = None, comment: Optional[Any] = None, ) -> Any: """Internal helper for inserting a single document.""" @@ -814,8 +814,8 @@ async def _insert_one( async def _insert_command( session: Optional[AsyncClientSession], conn: AsyncConnection, retryable_write: bool ) -> None: - if bypass_doc_val: - command["bypassDocumentValidation"] = True + if bypass_doc_val is not None: + command["bypassDocumentValidation"] = bypass_doc_val result = await conn.command( self._database.name, @@ -840,7 +840,7 @@ async def _insert_command( async def insert_one( self, document: Union[_DocumentType, RawBSONDocument], - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[AsyncClientSession] = None, comment: Optional[Any] = None, ) -> InsertOneResult: @@ -906,7 +906,7 @@ async def insert_many( self, documents: Iterable[Union[_DocumentType, RawBSONDocument]], ordered: bool = True, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[AsyncClientSession] = None, comment: Optional[Any] = None, ) -> InsertManyResult: @@ -986,7 +986,7 @@ async def _update( write_concern: Optional[WriteConcern] = None, op_id: Optional[int] = None, ordered: bool = True, - bypass_doc_val: Optional[bool] = False, + bypass_doc_val: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -1041,8 +1041,8 @@ async def _update( if comment is not None: command["comment"] = comment # Update command. - if bypass_doc_val: - command["bypassDocumentValidation"] = True + if bypass_doc_val is not None: + command["bypassDocumentValidation"] = bypass_doc_val # The command result has to be published for APM unmodified # so we make a shallow copy here before adding updatedExisting. @@ -1082,7 +1082,7 @@ async def _update_retryable( write_concern: Optional[WriteConcern] = None, op_id: Optional[int] = None, ordered: bool = True, - bypass_doc_val: Optional[bool] = False, + bypass_doc_val: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -1128,7 +1128,7 @@ async def replace_one( filter: Mapping[str, Any], replacement: Mapping[str, Any], upsert: bool = False, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, collation: Optional[_CollationIn] = None, hint: Optional[_IndexKeyHint] = None, session: Optional[AsyncClientSession] = None, @@ -1237,7 +1237,7 @@ async def update_one( filter: Mapping[str, Any], update: Union[Mapping[str, Any], _Pipeline], upsert: bool = False, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -2948,11 +2948,12 @@ async def aggregate( returning aggregate results using a cursor. - `collation` (optional): An instance of :class:`~pymongo.collation.Collation`. + - `bypassDocumentValidation` (bool): If ``True``, allows the + write to opt-out of document level validation. :return: A :class:`~pymongo.asynchronous.command_cursor.AsyncCommandCursor` over the result set. - .. versionchanged:: 4.1 Added ``comment`` parameter. Added ``let`` parameter. @@ -3356,7 +3357,13 @@ async def find_one_and_delete( if comment is not None: kwargs["comment"] = comment return await self._find_and_modify( - filter, projection, sort, let=let, hint=hint, session=session, **kwargs + filter, + projection, + sort, + let=let, + hint=hint, + session=session, + **kwargs, ) async def find_one_and_replace( diff --git a/pymongo/synchronous/bulk.py b/pymongo/synchronous/bulk.py index f54dcdd42d..fd079b1b91 100644 --- a/pymongo/synchronous/bulk.py +++ b/pymongo/synchronous/bulk.py @@ -87,7 +87,7 @@ def __init__( self, collection: Collection[_DocumentType], ordered: bool, - bypass_document_validation: bool, + bypass_document_validation: Optional[bool] = None, comment: Optional[str] = None, let: Optional[Any] = None, ) -> None: @@ -516,8 +516,8 @@ def _execute_command( if self.comment: cmd["comment"] = self.comment _csot.apply_write_concern(cmd, write_concern) - if self.bypass_doc_val: - cmd["bypassDocumentValidation"] = True + if self.bypass_doc_val is not None: + cmd["bypassDocumentValidation"] = self.bypass_doc_val if self.let is not None and run.op_type in (_DELETE, _UPDATE): cmd["let"] = self.let if session: diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index e63ed70fc2..c501cfee4d 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -700,7 +700,7 @@ def bulk_write( self, requests: Sequence[_WriteOp[_DocumentType]], ordered: bool = True, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[ClientSession] = None, comment: Optional[Any] = None, let: Optional[Mapping] = None, @@ -799,8 +799,8 @@ def _insert_one( ordered: bool, write_concern: WriteConcern, op_id: Optional[int], - bypass_doc_val: bool, session: Optional[ClientSession], + bypass_doc_val: Optional[bool] = None, comment: Optional[Any] = None, ) -> Any: """Internal helper for inserting a single document.""" @@ -813,8 +813,8 @@ def _insert_one( def _insert_command( session: Optional[ClientSession], conn: Connection, retryable_write: bool ) -> None: - if bypass_doc_val: - command["bypassDocumentValidation"] = True + if bypass_doc_val is not None: + command["bypassDocumentValidation"] = bypass_doc_val result = conn.command( self._database.name, @@ -839,7 +839,7 @@ def _insert_command( def insert_one( self, document: Union[_DocumentType, RawBSONDocument], - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[ClientSession] = None, comment: Optional[Any] = None, ) -> InsertOneResult: @@ -905,7 +905,7 @@ def insert_many( self, documents: Iterable[Union[_DocumentType, RawBSONDocument]], ordered: bool = True, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, session: Optional[ClientSession] = None, comment: Optional[Any] = None, ) -> InsertManyResult: @@ -985,7 +985,7 @@ def _update( write_concern: Optional[WriteConcern] = None, op_id: Optional[int] = None, ordered: bool = True, - bypass_doc_val: Optional[bool] = False, + bypass_doc_val: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -1040,8 +1040,8 @@ def _update( if comment is not None: command["comment"] = comment # Update command. - if bypass_doc_val: - command["bypassDocumentValidation"] = True + if bypass_doc_val is not None: + command["bypassDocumentValidation"] = bypass_doc_val # The command result has to be published for APM unmodified # so we make a shallow copy here before adding updatedExisting. @@ -1081,7 +1081,7 @@ def _update_retryable( write_concern: Optional[WriteConcern] = None, op_id: Optional[int] = None, ordered: bool = True, - bypass_doc_val: Optional[bool] = False, + bypass_doc_val: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -1127,7 +1127,7 @@ def replace_one( filter: Mapping[str, Any], replacement: Mapping[str, Any], upsert: bool = False, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, collation: Optional[_CollationIn] = None, hint: Optional[_IndexKeyHint] = None, session: Optional[ClientSession] = None, @@ -1236,7 +1236,7 @@ def update_one( filter: Mapping[str, Any], update: Union[Mapping[str, Any], _Pipeline], upsert: bool = False, - bypass_document_validation: bool = False, + bypass_document_validation: Optional[bool] = None, collation: Optional[_CollationIn] = None, array_filters: Optional[Sequence[Mapping[str, Any]]] = None, hint: Optional[_IndexKeyHint] = None, @@ -2941,11 +2941,12 @@ def aggregate( returning aggregate results using a cursor. - `collation` (optional): An instance of :class:`~pymongo.collation.Collation`. + - `bypassDocumentValidation` (bool): If ``True``, allows the + write to opt-out of document level validation. :return: A :class:`~pymongo.command_cursor.CommandCursor` over the result set. - .. versionchanged:: 4.1 Added ``comment`` parameter. Added ``let`` parameter. @@ -3349,7 +3350,13 @@ def find_one_and_delete( if comment is not None: kwargs["comment"] = comment return self._find_and_modify( - filter, projection, sort, let=let, hint=hint, session=session, **kwargs + filter, + projection, + sort, + let=let, + hint=hint, + session=session, + **kwargs, ) def find_one_and_replace( diff --git a/test/crud/unified/bypassDocumentValidation.json b/test/crud/unified/bypassDocumentValidation.json new file mode 100644 index 0000000000..aff2d37f81 --- /dev/null +++ b/test/crud/unified/bypassDocumentValidation.json @@ -0,0 +1,493 @@ +{ + "description": "bypassDocumentValidation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.2", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with $out passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + }, + "commandName": "aggregate", + "databaseName": "crud" + } + } + ] + } + ] + }, + { + "description": "BulkWrite passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndReplace passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "ReplaceOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 32 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + } + ] +} diff --git a/test/utils_shared.py b/test/utils_shared.py index 2c52445968..3cc7b676b8 100644 --- a/test/utils_shared.py +++ b/test/utils_shared.py @@ -615,6 +615,12 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map, with_txn_callbac # Aggregate uses "batchSize", while find uses batch_size. elif (arg_name == "batchSize" or arg_name == "allowDiskUse") and opname == "aggregate": continue + elif ( + arg_name == "bypassDocumentValidation" + and opname == "aggregate" + or "find_one_and" in opname + ): + continue elif arg_name == "timeoutMode": raise unittest.SkipTest("PyMongo does not support timeoutMode") # Requires boolean returnDocument. From 81b868801d63f67dc5a9a5bed3a5c1f2b9bedf3b Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 27 Mar 2025 09:50:01 -0400 Subject: [PATCH 2/3] Address review --- pymongo/asynchronous/bulk.py | 2 +- pymongo/asynchronous/collection.py | 10 ++-------- pymongo/synchronous/bulk.py | 2 +- pymongo/synchronous/collection.py | 10 ++-------- test/utils_shared.py | 6 ++---- 5 files changed, 8 insertions(+), 22 deletions(-) diff --git a/pymongo/asynchronous/bulk.py b/pymongo/asynchronous/bulk.py index 6050d4c97c..91e08f61b3 100644 --- a/pymongo/asynchronous/bulk.py +++ b/pymongo/asynchronous/bulk.py @@ -87,7 +87,7 @@ def __init__( self, collection: AsyncCollection[_DocumentType], ordered: bool, - bypass_document_validation: Optional[bool] = None, + bypass_document_validation: Optional[bool], comment: Optional[str] = None, let: Optional[Any] = None, ) -> None: diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index b9699b8b0d..95e3712083 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -800,8 +800,8 @@ async def _insert_one( ordered: bool, write_concern: WriteConcern, op_id: Optional[int], + bypass_doc_val: Optional[bool], session: Optional[AsyncClientSession], - bypass_doc_val: Optional[bool] = None, comment: Optional[Any] = None, ) -> Any: """Internal helper for inserting a single document.""" @@ -3357,13 +3357,7 @@ async def find_one_and_delete( if comment is not None: kwargs["comment"] = comment return await self._find_and_modify( - filter, - projection, - sort, - let=let, - hint=hint, - session=session, - **kwargs, + filter, projection, sort, let=let, hint=hint, session=session, **kwargs ) async def find_one_and_replace( diff --git a/pymongo/synchronous/bulk.py b/pymongo/synchronous/bulk.py index fd079b1b91..3823ef354d 100644 --- a/pymongo/synchronous/bulk.py +++ b/pymongo/synchronous/bulk.py @@ -87,7 +87,7 @@ def __init__( self, collection: Collection[_DocumentType], ordered: bool, - bypass_document_validation: Optional[bool] = None, + bypass_document_validation: Optional[bool], comment: Optional[str] = None, let: Optional[Any] = None, ) -> None: diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index c501cfee4d..f54ac73554 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -799,8 +799,8 @@ def _insert_one( ordered: bool, write_concern: WriteConcern, op_id: Optional[int], + bypass_doc_val: Optional[bool], session: Optional[ClientSession], - bypass_doc_val: Optional[bool] = None, comment: Optional[Any] = None, ) -> Any: """Internal helper for inserting a single document.""" @@ -3350,13 +3350,7 @@ def find_one_and_delete( if comment is not None: kwargs["comment"] = comment return self._find_and_modify( - filter, - projection, - sort, - let=let, - hint=hint, - session=session, - **kwargs, + filter, projection, sort, let=let, hint=hint, session=session, **kwargs ) def find_one_and_replace( diff --git a/test/utils_shared.py b/test/utils_shared.py index 3cc7b676b8..e0789b6632 100644 --- a/test/utils_shared.py +++ b/test/utils_shared.py @@ -615,10 +615,8 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map, with_txn_callbac # Aggregate uses "batchSize", while find uses batch_size. elif (arg_name == "batchSize" or arg_name == "allowDiskUse") and opname == "aggregate": continue - elif ( - arg_name == "bypassDocumentValidation" - and opname == "aggregate" - or "find_one_and" in opname + elif arg_name == "bypassDocumentValidation" and ( + opname == "aggregate" or "find_one_and" in opname ): continue elif arg_name == "timeoutMode": From 334f55bfa2733cb92395aaa6976bc44ade631fc3 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 27 Mar 2025 10:21:27 -0400 Subject: [PATCH 3/3] Fix docstring --- pymongo/asynchronous/collection.py | 4 ++-- pymongo/synchronous/collection.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index 95e3712083..7fb20b7ab3 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -2948,12 +2948,12 @@ async def aggregate( returning aggregate results using a cursor. - `collation` (optional): An instance of :class:`~pymongo.collation.Collation`. - - `bypassDocumentValidation` (bool): If ``True``, allows the - write to opt-out of document level validation. + - `bypassDocumentValidation` (bool): If ``True``, allows the write to opt-out of document level validation. :return: A :class:`~pymongo.asynchronous.command_cursor.AsyncCommandCursor` over the result set. + .. versionchanged:: 4.1 Added ``comment`` parameter. Added ``let`` parameter. diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index f54ac73554..8a71768318 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -2941,12 +2941,12 @@ def aggregate( returning aggregate results using a cursor. - `collation` (optional): An instance of :class:`~pymongo.collation.Collation`. - - `bypassDocumentValidation` (bool): If ``True``, allows the - write to opt-out of document level validation. + - `bypassDocumentValidation` (bool): If ``True``, allows the write to opt-out of document level validation. :return: A :class:`~pymongo.command_cursor.CommandCursor` over the result set. + .. versionchanged:: 4.1 Added ``comment`` parameter. Added ``let`` parameter.