From 7c1060cfecd7f2daf44e377d7a78bc4ad2565664 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Mon, 25 Jan 2021 16:22:00 -0800 Subject: [PATCH] PYTHON-1307 Remove SONManipulator APIs (#557) --- doc/api/pymongo/collection.rst | 4 +- doc/api/pymongo/cursor.rst | 4 +- doc/api/pymongo/index.rst | 1 - doc/api/pymongo/son_manipulator.rst | 6 - doc/changelog.rst | 25 +++- doc/migrate-to-pymongo4.rst | 32 +++++ pymongo/bulk.py | 6 +- pymongo/collection.py | 20 +-- pymongo/command_cursor.py | 3 +- pymongo/cursor.py | 19 +-- pymongo/database.py | 129 ------------------- pymongo/son_manipulator.py | 192 ---------------------------- test/test_cursor.py | 8 +- test/test_legacy_api.py | 23 +--- test/test_son_manipulator.py | 124 ------------------ 15 files changed, 72 insertions(+), 524 deletions(-) delete mode 100644 doc/api/pymongo/son_manipulator.rst delete mode 100644 pymongo/son_manipulator.py delete mode 100644 test/test_son_manipulator.py diff --git a/doc/api/pymongo/collection.rst b/doc/api/pymongo/collection.rst index ada01352e..55712c45b 100644 --- a/doc/api/pymongo/collection.rst +++ b/doc/api/pymongo/collection.rst @@ -47,8 +47,8 @@ .. automethod:: aggregate .. automethod:: aggregate_raw_batches .. automethod:: watch - .. automethod:: find(filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, manipulate=True, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None) - .. automethod:: find_raw_batches(filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, manipulate=True, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None) + .. automethod:: find(filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None, allow_disk_use=None) + .. automethod:: find_raw_batches(filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, allow_disk_use=None) .. automethod:: find_one(filter=None, *args, **kwargs) .. automethod:: find_one_and_delete .. automethod:: find_one_and_replace(filter, replacement, projection=None, sort=None, return_document=ReturnDocument.BEFORE, hint=None, session=None, **kwargs) diff --git a/doc/api/pymongo/cursor.rst b/doc/api/pymongo/cursor.rst index 4b9943f9a..ffede1237 100644 --- a/doc/api/pymongo/cursor.rst +++ b/doc/api/pymongo/cursor.rst @@ -15,7 +15,7 @@ .. autoattribute:: EXHAUST :annotation: - .. autoclass:: pymongo.cursor.Cursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, manipulate=True, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None) + .. autoclass:: pymongo.cursor.Cursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None, allow_disk_use=None) :members: .. describe:: c[index] @@ -24,4 +24,4 @@ .. automethod:: __getitem__ - .. autoclass:: pymongo.cursor.RawBatchCursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None) + .. autoclass:: pymongo.cursor.RawBatchCursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, allow_disk_use=None) diff --git a/doc/api/pymongo/index.rst b/doc/api/pymongo/index.rst index 99688e841..1a54a5b42 100644 --- a/doc/api/pymongo/index.rst +++ b/doc/api/pymongo/index.rst @@ -47,7 +47,6 @@ Sub-modules: read_concern read_preferences results - son_manipulator server_api uri_parser write_concern diff --git a/doc/api/pymongo/son_manipulator.rst b/doc/api/pymongo/son_manipulator.rst deleted file mode 100644 index 87503e6f8..000000000 --- a/doc/api/pymongo/son_manipulator.rst +++ /dev/null @@ -1,6 +0,0 @@ -:mod:`son_manipulator` -- Manipulators that can edit SON documents as they are saved or retrieved -================================================================================================= - -.. automodule:: pymongo.son_manipulator - :synopsis: Manipulators that can edit SON documents as they are saved or retrieved - :members: diff --git a/doc/changelog.rst b/doc/changelog.rst index db50129f3..f900e8fcb 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -30,11 +30,11 @@ Breaking Changes in 4.0 - Removed :meth:`pymongo.collection.Collection.parallel_scan`. - Removed :meth:`pymongo.collection.Collection.ensure_index`. - Removed :meth:`pymongo.collection.Collection.reindex`. -- Removed :meth:`pymongo.collection.Collection.save` -- Removed :meth:`pymongo.collection.Collection.insert` -- Removed :meth:`pymongo.collection.Collection.update` -- Removed :meth:`pymongo.collection.Collection.remove` -- Removed :meth:`pymongo.collection.Collection.find_and_modify` +- Removed :meth:`pymongo.collection.Collection.save`. +- Removed :meth:`pymongo.collection.Collection.insert`. +- Removed :meth:`pymongo.collection.Collection.update`. +- Removed :meth:`pymongo.collection.Collection.remove`. +- Removed :meth:`pymongo.collection.Collection.find_and_modify`. - Removed :meth:`pymongo.mongo_client.MongoClient.close_cursor`. Use :meth:`pymongo.cursor.Cursor.close` instead. - Removed :meth:`pymongo.mongo_client.MongoClient.kill_cursors`. @@ -43,6 +43,21 @@ Breaking Changes in 4.0 - Removed :meth:`pymongo.mongo_client.MongoClient.set_cursor_manager`. - Removed :mod:`pymongo.thread_util`. - Removed :class:`~pymongo.mongo_replica_set_client.MongoReplicaSetClient`. +- Removed :mod:`pymongo.son_manipulator`, + :class:`pymongo.son_manipulator.SONManipulator`, + :class:`pymongo.son_manipulator.ObjectIdInjector`, + :class:`pymongo.son_manipulator.ObjectIdShuffler`, + :class:`pymongo.son_manipulator.AutoReference`, + :class:`pymongo.son_manipulator.NamespaceInjector`, + :meth:`pymongo.database.Database.add_son_manipulator`, + :attr:`pymongo.database.Database.outgoing_copying_manipulators`, + :attr:`pymongo.database.Database.outgoing_manipulators`, + :attr:`pymongo.database.Database.incoming_copying_manipulators`, and + :attr:`pymongo.database.Database.incoming_manipulators`. +- Removed the ``manipulate`` parameter from + :meth:`~pymongo.collection.Collection.find`, + :meth:`~pymongo.collection.Collection.find_one`, and + :meth:`~pymongo.cursor.Cursor`. Notable improvements .................... diff --git a/doc/migrate-to-pymongo4.rst b/doc/migrate-to-pymongo4.rst index cf05114ca..9840bcb46 100644 --- a/doc/migrate-to-pymongo4.rst +++ b/doc/migrate-to-pymongo4.rst @@ -274,6 +274,38 @@ can be changed to this:: .. _reIndex command: https://docs.mongodb.com/manual/reference/command/reIndex/ +SONManipulator is removed +------------------------- + +Removed :mod:`pymongo.son_manipulator`, +:class:`pymongo.son_manipulator.SONManipulator`, +:class:`pymongo.son_manipulator.ObjectIdInjector`, +:class:`pymongo.son_manipulator.ObjectIdShuffler`, +:class:`pymongo.son_manipulator.AutoReference`, +:class:`pymongo.son_manipulator.NamespaceInjector`, +:meth:`pymongo.database.Database.add_son_manipulator`, +:attr:`pymongo.database.Database.outgoing_copying_manipulators`, +:attr:`pymongo.database.Database.outgoing_manipulators`, +:attr:`pymongo.database.Database.incoming_copying_manipulators`, and +:attr:`pymongo.database.Database.incoming_manipulators`. + +Removed the ``manipulate`` parameter from +:meth:`~pymongo.collection.Collection.find`, +:meth:`~pymongo.collection.Collection.find_one`, and +:meth:`~pymongo.cursor.Cursor`. + +The :class:`pymongo.son_manipulator.SONManipulator` API has limitations as a +technique for transforming your data and was deprecated in PyMongo 3.0. +Instead, it is more flexible and straightforward to transform outgoing +documents in your own code before passing them to PyMongo, and transform +incoming documents after receiving them from PyMongo. + +Alternatively, if your application uses the ``SONManipulator`` API to convert +custom types to BSON, the :class:`~bson.codec_options.TypeCodec` and +:class:`~bson.codec_options.TypeRegistry` APIs may be a suitable alternative. +For more information, see the +:doc:`custom type example `. + Removed features with no migration path --------------------------------------- diff --git a/pymongo/bulk.py b/pymongo/bulk.py index 573a4b7ee..751feaf25 100644 --- a/pymongo/bulk.py +++ b/pymongo/bulk.py @@ -478,9 +478,9 @@ class _Bulk(object): sock_info, operation['q'], doc, - operation['upsert'], - check_keys, - operation['multi'], + upsert=operation['upsert'], + check_keys=check_keys, + multi=operation['multi'], write_concern=write_concern, op_id=op_id, ordered=self.ordered, diff --git a/pymongo/collection.py b/pymongo/collection.py index d600f2707..32415b7a0 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -554,15 +554,9 @@ class Collection(common.BaseObject): def _insert_one( self, doc, ordered, - check_keys, manipulate, write_concern, op_id, bypass_doc_val, + check_keys, write_concern, op_id, bypass_doc_val, session): """Internal helper for inserting a single document.""" - if manipulate: - doc = self.__database._apply_incoming_manipulators(doc, self) - if not isinstance(doc, RawBSONDocument) and '_id' not in doc: - doc['_id'] = ObjectId() - doc = self.__database._apply_incoming_copying_manipulators(doc, - self) write_concern = write_concern or self.write_concern acknowledged = write_concern.acknowledged command = SON([('insert', self.name), @@ -646,7 +640,7 @@ class Collection(common.BaseObject): write_concern = self._write_concern_for(session) return InsertOneResult( self._insert_one( - document, ordered=True, check_keys=True, manipulate=False, + document, ordered=True, check_keys=True, write_concern=write_concern, op_id=None, bypass_doc_val=bypass_document_validation, session=session), write_concern.acknowledged) @@ -712,14 +706,12 @@ class Collection(common.BaseObject): return InsertManyResult(inserted_ids, write_concern.acknowledged) def _update(self, sock_info, criteria, document, upsert=False, - check_keys=True, multi=False, manipulate=False, + check_keys=True, multi=False, write_concern=None, op_id=None, ordered=True, bypass_doc_val=False, collation=None, array_filters=None, hint=None, session=None, retryable_write=False): """Internal update / replace helper.""" common.validate_boolean("upsert", upsert) - if manipulate: - document = self.__database._fix_incoming(document, self) collation = validate_collation_or_none(collation) write_concern = write_concern or self.write_concern acknowledged = write_concern.acknowledged @@ -801,7 +793,7 @@ class Collection(common.BaseObject): def _update_retryable( self, criteria, document, upsert=False, - check_keys=True, multi=False, manipulate=False, + check_keys=True, multi=False, write_concern=None, op_id=None, ordered=True, bypass_doc_val=False, collation=None, array_filters=None, hint=None, session=None): @@ -809,7 +801,7 @@ class Collection(common.BaseObject): def _update(session, sock_info, retryable_write): return self._update( sock_info, criteria, document, upsert=upsert, - check_keys=check_keys, multi=multi, manipulate=manipulate, + check_keys=check_keys, multi=multi, write_concern=write_concern, op_id=op_id, ordered=ordered, bypass_doc_val=bypass_doc_val, collation=collation, array_filters=array_filters, hint=hint, session=session, @@ -1346,8 +1338,6 @@ class Collection(common.BaseObject): oplogReplay query flag. Default: False. - `batch_size` (optional): Limits the number of documents returned in a single batch. - - `manipulate` (optional): **DEPRECATED** - If True, apply any - outgoing SON manipulators before returning. Default: True. - `collation` (optional): An instance of :class:`~pymongo.collation.Collation`. This option is only supported on MongoDB 3.4 and above. diff --git a/pymongo/command_cursor.py b/pymongo/command_cursor.py index 4c82f754a..afcb764c7 100644 --- a/pymongo/command_cursor.py +++ b/pymongo/command_cursor.py @@ -268,8 +268,7 @@ class CommandCursor(object): if not len(self.__data) and not self.__killed and get_more_allowed: self._refresh() if len(self.__data): - coll = self.__collection - return coll.database._fix_outgoing(self.__data.popleft(), coll) + return self.__data.popleft() else: return None diff --git a/pymongo/cursor.py b/pymongo/cursor.py index 7ada005d8..b051b068a 100644 --- a/pymongo/cursor.py +++ b/pymongo/cursor.py @@ -108,7 +108,7 @@ class Cursor(object): limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, - modifiers=None, batch_size=0, manipulate=True, + modifiers=None, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None, @@ -181,7 +181,6 @@ class Cursor(object): self.__max_await_time_ms = None self.__max = max self.__min = min - self.__manipulate = manipulate self.__collation = validate_collation_or_none(collation) self.__return_key = return_key self.__show_record_id = show_record_id @@ -281,7 +280,7 @@ class Cursor(object): values_to_clone = ("spec", "projection", "skip", "limit", "max_time_ms", "max_await_time_ms", "comment", "max", "min", "ordering", "explain", "hint", - "batch_size", "max_scan", "manipulate", + "batch_size", "max_scan", "query_flags", "modifiers", "collation", "empty", "show_record_id", "return_key", "allow_disk_use", "snapshot", "exhaust") @@ -1198,12 +1197,7 @@ class Cursor(object): if self.__empty: raise StopIteration if len(self.__data) or self._refresh(): - if self.__manipulate: - _db = self.__collection.database - return _db._fix_outgoing(self.__data.popleft(), - self.__collection) - else: - return self.__data.popleft() + return self.__data.popleft() else: raise StopIteration @@ -1277,15 +1271,8 @@ class RawBatchCursor(Cursor): .. mongodoc:: cursors """ - manipulate = kwargs.get('manipulate') - kwargs['manipulate'] = False super(RawBatchCursor, self).__init__(*args, **kwargs) - # Throw only after cursor's initialized, to prevent errors in __del__. - if manipulate: - raise InvalidOperation( - "Cannot use RawBatchCursor with manipulate=True") - def _unpack_response(self, response, cursor_id, codec_options, user_fields=None, legacy_response=False): return response.raw_response(cursor_id) diff --git a/pymongo/database.py b/pymongo/database.py index 73fcab760..f965a7b53 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -30,7 +30,6 @@ from pymongo.errors import (CollectionInvalid, OperationFailure) from pymongo.message import _first_batch from pymongo.read_preferences import ReadPreference -from pymongo.son_manipulator import SONManipulator _INDEX_REGEX = {"name": {"$regex": r"^(?!.*\$)"}} @@ -108,38 +107,6 @@ class Database(common.BaseObject): self.__name = name self.__client = client - self.__incoming_manipulators = [] - self.__incoming_copying_manipulators = [] - self.__outgoing_manipulators = [] - self.__outgoing_copying_manipulators = [] - - def add_son_manipulator(self, manipulator): - """Add a new son manipulator to this database. - - **DEPRECATED** - `add_son_manipulator` is deprecated. - - .. versionchanged:: 3.0 - Deprecated add_son_manipulator. - """ - warnings.warn("add_son_manipulator is deprecated", - DeprecationWarning, stacklevel=2) - base = SONManipulator() - def method_overwritten(instance, method): - """Test if this method has been overridden.""" - return (getattr( - instance, method).__func__ != getattr(base, method).__func__) - - if manipulator.will_copy(): - if method_overwritten(manipulator, "transform_incoming"): - self.__incoming_copying_manipulators.insert(0, manipulator) - if method_overwritten(manipulator, "transform_outgoing"): - self.__outgoing_copying_manipulators.insert(0, manipulator) - else: - if method_overwritten(manipulator, "transform_incoming"): - self.__incoming_manipulators.insert(0, manipulator) - if method_overwritten(manipulator, "transform_outgoing"): - self.__outgoing_manipulators.insert(0, manipulator) - @property def client(self): """The client instance for this :class:`Database`.""" @@ -150,66 +117,6 @@ class Database(common.BaseObject): """The name of this :class:`Database`.""" return self.__name - @property - def incoming_manipulators(self): - """**DEPRECATED**: All incoming SON manipulators. - - .. versionchanged:: 3.5 - Deprecated. - - .. versionadded:: 2.0 - """ - warnings.warn("Database.incoming_manipulators() is deprecated", - DeprecationWarning, stacklevel=2) - - return [manipulator.__class__.__name__ - for manipulator in self.__incoming_manipulators] - - @property - def incoming_copying_manipulators(self): - """**DEPRECATED**: All incoming SON copying manipulators. - - .. versionchanged:: 3.5 - Deprecated. - - .. versionadded:: 2.0 - """ - warnings.warn("Database.incoming_copying_manipulators() is deprecated", - DeprecationWarning, stacklevel=2) - - return [manipulator.__class__.__name__ - for manipulator in self.__incoming_copying_manipulators] - - @property - def outgoing_manipulators(self): - """**DEPRECATED**: All outgoing SON manipulators. - - .. versionchanged:: 3.5 - Deprecated. - - .. versionadded:: 2.0 - """ - warnings.warn("Database.outgoing_manipulators() is deprecated", - DeprecationWarning, stacklevel=2) - - return [manipulator.__class__.__name__ - for manipulator in self.__outgoing_manipulators] - - @property - def outgoing_copying_manipulators(self): - """**DEPRECATED**: All outgoing SON copying manipulators. - - .. versionchanged:: 3.5 - Deprecated. - - .. versionadded:: 2.0 - """ - warnings.warn("Database.outgoing_copying_manipulators() is deprecated", - DeprecationWarning, stacklevel=2) - - return [manipulator.__class__.__name__ - for manipulator in self.__outgoing_copying_manipulators] - def with_options(self, codec_options=None, read_preference=None, write_concern=None, read_concern=None): """Get a clone of this database changing the specified settings. @@ -407,42 +314,6 @@ class Database(common.BaseObject): read_preference, write_concern, read_concern, session=s, **kwargs) - def _apply_incoming_manipulators(self, son, collection): - """Apply incoming manipulators to `son`.""" - for manipulator in self.__incoming_manipulators: - son = manipulator.transform_incoming(son, collection) - return son - - def _apply_incoming_copying_manipulators(self, son, collection): - """Apply incoming copying manipulators to `son`.""" - for manipulator in self.__incoming_copying_manipulators: - son = manipulator.transform_incoming(son, collection) - return son - - def _fix_incoming(self, son, collection): - """Apply manipulators to an incoming SON object before it gets stored. - - :Parameters: - - `son`: the son object going into the database - - `collection`: the collection the son object is being saved in - """ - son = self._apply_incoming_manipulators(son, collection) - son = self._apply_incoming_copying_manipulators(son, collection) - return son - - def _fix_outgoing(self, son, collection): - """Apply manipulators to a SON object as it comes out of the database. - - :Parameters: - - `son`: the son object coming out of the database - - `collection`: the collection the son object was saved in - """ - for manipulator in reversed(self.__outgoing_manipulators): - son = manipulator.transform_outgoing(son, collection) - for manipulator in reversed(self.__outgoing_copying_manipulators): - son = manipulator.transform_outgoing(son, collection) - return son - def aggregate(self, pipeline, session=None, **kwargs): """Perform a database-level aggregation. diff --git a/pymongo/son_manipulator.py b/pymongo/son_manipulator.py deleted file mode 100644 index a371d6493..000000000 --- a/pymongo/son_manipulator.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright 2009-present MongoDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""**DEPRECATED**: Manipulators that can edit SON objects as they enter and exit -a database. - -The :class:`~pymongo.son_manipulator.SONManipulator` API has limitations as a -technique for transforming your data. Instead, it is more flexible and -straightforward to transform outgoing documents in your own code before passing -them to PyMongo, and transform incoming documents after receiving them from -PyMongo. SON Manipulators will be removed from PyMongo in 4.0. - -PyMongo does **not** apply SON manipulators to documents passed to -the modern methods :meth:`~pymongo.collection.Collection.bulk_write`, -:meth:`~pymongo.collection.Collection.insert_one`, -:meth:`~pymongo.collection.Collection.insert_many`, -:meth:`~pymongo.collection.Collection.update_one`, or -:meth:`~pymongo.collection.Collection.update_many`. SON manipulators are -**not** applied to documents returned by the modern methods -:meth:`~pymongo.collection.Collection.find_one_and_delete`, -:meth:`~pymongo.collection.Collection.find_one_and_replace`, and -:meth:`~pymongo.collection.Collection.find_one_and_update`. -""" - -from collections import abc - -from bson.dbref import DBRef -from bson.objectid import ObjectId -from bson.son import SON - - -class SONManipulator(object): - """A base son manipulator. - - This manipulator just saves and restores objects without changing them. - """ - - def will_copy(self): - """Will this SON manipulator make a copy of the incoming document? - - Derived classes that do need to make a copy should override this - method, returning True instead of False. All non-copying manipulators - will be applied first (so that the user's document will be updated - appropriately), followed by copying manipulators. - """ - return False - - def transform_incoming(self, son, collection): - """Manipulate an incoming SON object. - - :Parameters: - - `son`: the SON object to be inserted into the database - - `collection`: the collection the object is being inserted into - """ - if self.will_copy(): - return SON(son) - return son - - def transform_outgoing(self, son, collection): - """Manipulate an outgoing SON object. - - :Parameters: - - `son`: the SON object being retrieved from the database - - `collection`: the collection this object was stored in - """ - if self.will_copy(): - return SON(son) - return son - - -class ObjectIdInjector(SONManipulator): - """A son manipulator that adds the _id field if it is missing. - - .. versionchanged:: 2.7 - ObjectIdInjector is no longer used by PyMongo, but remains in this - module for backwards compatibility. - """ - - def transform_incoming(self, son, collection): - """Add an _id field if it is missing. - """ - if not "_id" in son: - son["_id"] = ObjectId() - return son - - -# This is now handled during BSON encoding (for performance reasons), -# but I'm keeping this here as a reference for those implementing new -# SONManipulators. -class ObjectIdShuffler(SONManipulator): - """A son manipulator that moves _id to the first position. - """ - - def will_copy(self): - """We need to copy to be sure that we are dealing with SON, not a dict. - """ - return True - - def transform_incoming(self, son, collection): - """Move _id to the front if it's there. - """ - if not "_id" in son: - return son - transformed = SON({"_id": son["_id"]}) - transformed.update(son) - return transformed - - -class NamespaceInjector(SONManipulator): - """A son manipulator that adds the _ns field. - """ - - def transform_incoming(self, son, collection): - """Add the _ns field to the incoming object - """ - son["_ns"] = collection.name - return son - - -class AutoReference(SONManipulator): - """Transparently reference and de-reference already saved embedded objects. - - This manipulator should probably only be used when the NamespaceInjector is - also being used, otherwise it doesn't make too much sense - documents can - only be auto-referenced if they have an *_ns* field. - - NOTE: this will behave poorly if you have a circular reference. - - TODO: this only works for documents that are in the same database. To fix - this we'll need to add a DatabaseInjector that adds *_db* and then make - use of the optional *database* support for DBRefs. - """ - - def __init__(self, db): - self.database = db - - def will_copy(self): - """We need to copy so the user's document doesn't get transformed refs. - """ - return True - - def transform_incoming(self, son, collection): - """Replace embedded documents with DBRefs. - """ - - def transform_value(value): - if isinstance(value, abc.MutableMapping): - if "_id" in value and "_ns" in value: - return DBRef(value["_ns"], transform_value(value["_id"])) - else: - return transform_dict(SON(value)) - elif isinstance(value, list): - return [transform_value(v) for v in value] - return value - - def transform_dict(object): - for (key, value) in object.items(): - object[key] = transform_value(value) - return object - - return transform_dict(SON(son)) - - def transform_outgoing(self, son, collection): - """Replace DBRefs with embedded documents. - """ - - def transform_value(value): - if isinstance(value, DBRef): - return self.database.dereference(value) - elif isinstance(value, list): - return [transform_value(v) for v in value] - elif isinstance(value, abc.MutableMapping): - return transform_dict(SON(value)) - return value - - def transform_dict(object): - for (key, value) in object.items(): - object[key] = transform_value(value) - return object - - return transform_dict(SON(son)) diff --git a/test/test_cursor.py b/test/test_cursor.py index f7e726398..582d0641e 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -908,7 +908,7 @@ class TestCursor(IntegrationTest): self.assertEqual(cursor, cursor.rewind()) - # manipulate, oplog_reply, and snapshot are all deprecated. + # oplog_reply, and snapshot are all deprecated. @ignore_deprecations def test_clone(self): self.db.test.insert_many([{"x": i} for i in range(1, 4)]) @@ -956,7 +956,6 @@ class TestCursor(IntegrationTest): allow_partial_results=True, oplog_replay=True, batch_size=123, - manipulate=False, collation={'locale': 'en_US'}, hint=[("_id", 1)], max_scan=100, @@ -1466,11 +1465,6 @@ class TestRawBatchCursor(IntegrationTest): self.assertEqual(1, len(batches)) self.assertEqual(docs, decode_all(batches[0])) - def test_manipulate(self): - c = self.db.test - with self.assertRaises(InvalidOperation): - c.find_raw_batches(manipulate=True) - def test_explain(self): c = self.db.test c.insert_one({}) diff --git a/test/test_legacy_api.py b/test/test_legacy_api.py index b4e65d128..30dda8754 100644 --- a/test/test_legacy_api.py +++ b/test/test_legacy_api.py @@ -14,10 +14,7 @@ """Test various legacy / deprecated API features.""" -import itertools import sys -import threading -import time import uuid sys.path[0:0] = [""] @@ -25,25 +22,16 @@ sys.path[0:0] = [""] from bson.binary import PYTHON_LEGACY, STANDARD from bson.code import Code from bson.codec_options import CodecOptions -from bson.objectid import ObjectId from bson.son import SON -from pymongo import ASCENDING, DESCENDING, GEOHAYSTACK +from pymongo import ASCENDING, GEOHAYSTACK from pymongo.common import partition_node from pymongo.errors import (BulkWriteError, ConfigurationError, - DuplicateKeyError, InvalidDocument, InvalidOperation, - OperationFailure, - WriteConcernError, - WTimeoutError) + OperationFailure) from pymongo.operations import IndexModel -from pymongo.son_manipulator import (AutoReference, - NamespaceInjector, - ObjectIdShuffler, - SONManipulator) -from pymongo.write_concern import WriteConcern -from test import client_context, qcheck, unittest, SkipTest +from test import client_context, unittest, SkipTest from test.test_client import IntegrationTest from test.test_bulk import BulkTestBase, BulkAuthorizationTestBase from test.utils import (DeprecationFilter, @@ -64,11 +52,6 @@ class TestDeprecations(IntegrationTest): def tearDownClass(cls): cls.deprecation_filter.stop() - def test_add_son_manipulator_deprecation(self): - db = self.client.pymongo_test - self.assertRaises(DeprecationWarning, - lambda: db.add_son_manipulator(AutoReference(db))) - def test_geoHaystack_deprecation(self): self.addCleanup(self.db.test.drop) keys = [("pos", GEOHAYSTACK), ("type", ASCENDING)] diff --git a/test/test_son_manipulator.py b/test/test_son_manipulator.py deleted file mode 100644 index b4b9544f5..000000000 --- a/test/test_son_manipulator.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2009-present MongoDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests for SONManipulators. -""" - -import sys -import warnings - -sys.path[0:0] = [""] - -from bson.son import SON -from pymongo import MongoClient -from pymongo.son_manipulator import (NamespaceInjector, - ObjectIdInjector, - ObjectIdShuffler, - SONManipulator) -from test import client_context, qcheck, unittest - - -class TestSONManipulator(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.warn_context = warnings.catch_warnings() - cls.warn_context.__enter__() - warnings.simplefilter("ignore", DeprecationWarning) - - client = MongoClient( - client_context.host, client_context.port, connect=False) - cls.db = client.pymongo_test - - @classmethod - def tearDownClass(cls): - cls.warn_context.__exit__() - cls.warn_context = None - - def test_basic(self): - manip = SONManipulator() - collection = self.db.test - - def incoming_is_identity(son): - return son == manip.transform_incoming(son, collection) - qcheck.check_unittest(self, incoming_is_identity, - qcheck.gen_mongo_dict(3)) - - def outgoing_is_identity(son): - return son == manip.transform_outgoing(son, collection) - qcheck.check_unittest(self, outgoing_is_identity, - qcheck.gen_mongo_dict(3)) - - def test_id_injection(self): - manip = ObjectIdInjector() - collection = self.db.test - - def incoming_adds_id(son): - son = manip.transform_incoming(son, collection) - assert "_id" in son - return True - qcheck.check_unittest(self, incoming_adds_id, - qcheck.gen_mongo_dict(3)) - - def outgoing_is_identity(son): - return son == manip.transform_outgoing(son, collection) - qcheck.check_unittest(self, outgoing_is_identity, - qcheck.gen_mongo_dict(3)) - - def test_id_shuffling(self): - manip = ObjectIdShuffler() - collection = self.db.test - - def incoming_moves_id(son_in): - son = manip.transform_incoming(son_in, collection) - if not "_id" in son: - return True - for (k, v) in son.items(): - self.assertEqual(k, "_id") - break - # Key order matters in SON equality test, - # matching collections.OrderedDict - if isinstance(son_in, SON): - return son_in.to_dict() == son.to_dict() - return son_in == son - - self.assertTrue(incoming_moves_id({})) - self.assertTrue(incoming_moves_id({"_id": 12})) - self.assertTrue(incoming_moves_id({"hello": "world", "_id": 12})) - self.assertTrue(incoming_moves_id(SON([("hello", "world"), - ("_id", 12)]))) - - def outgoing_is_identity(son): - return son == manip.transform_outgoing(son, collection) - qcheck.check_unittest(self, outgoing_is_identity, - qcheck.gen_mongo_dict(3)) - - def test_ns_injection(self): - manip = NamespaceInjector() - collection = self.db.test - - def incoming_adds_ns(son): - son = manip.transform_incoming(son, collection) - assert "_ns" in son - return son["_ns"] == collection.name - qcheck.check_unittest(self, incoming_adds_ns, - qcheck.gen_mongo_dict(3)) - - def outgoing_is_identity(son): - return son == manip.transform_outgoing(son, collection) - qcheck.check_unittest(self, outgoing_is_identity, - qcheck.gen_mongo_dict(3)) - -if __name__ == "__main__": - unittest.main()