PYTHON-1307 Remove SONManipulator APIs (#557)
This commit is contained in:
parent
cf877e95c7
commit
7c1060cfec
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -47,7 +47,6 @@ Sub-modules:
|
||||
read_concern
|
||||
read_preferences
|
||||
results
|
||||
son_manipulator
|
||||
server_api
|
||||
uri_parser
|
||||
write_concern
|
||||
|
||||
@ -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:
|
||||
@ -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
|
||||
....................
|
||||
|
||||
@ -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 <examples/custom_type>`.
|
||||
|
||||
Removed features with no migration path
|
||||
---------------------------------------
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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))
|
||||
@ -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({})
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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()
|
||||
Loading…
Reference in New Issue
Block a user