PYTHON-821 - Deprecated legacy API.

This commit deprecates insert, update, save, and remove. Each
now raises DeprecationWarning at stacklevel 2. This change also
updates all tutorials and example documentation to use the new
CRUD API, tests the deprecations, and fixes up a few more tests
that were still using the legacy API.
This commit is contained in:
Bernie Hackett 2015-02-20 15:41:45 -08:00
parent aa090fe393
commit 839893939e
14 changed files with 448 additions and 669 deletions

View File

@ -113,11 +113,11 @@ Here's a basic example (for more see the *examples* section of the docs):
u'test'
>>> db.my_collection
Collection(Database(MongoClient('localhost', 27017), u'test'), u'my_collection')
>>> db.my_collection.save({"x": 10})
>>> db.my_collection.insert_one({"x": 10}).inserted_id
ObjectId('4aba15ebe23f6b53b0000000')
>>> db.my_collection.save({"x": 8})
>>> db.my_collection.insert_one({"x": 8}).inserted_id
ObjectId('4aba160ee23f6b543e000000')
>>> db.my_collection.save({"x": 11})
>>> db.my_collection.insert_one({"x": 11}).inserted_id
ObjectId('4aba160ee23f6b543e000002')
>>> db.my_collection.find_one()
{u'x': 10, u'_id': ObjectId('4aba15ebe23f6b53b0000000')}

View File

@ -185,10 +185,11 @@ class UUIDLegacy(Binary):
>>> import uuid
>>> from bson.binary import Binary, UUIDLegacy, STANDARD
>>> from bson.codec_options import CodecOptions
>>> my_uuid = uuid.uuid4()
>>> coll = db.get_collection('test',
... CodecOptions(uuid_representation=STANDARD))
>>> coll.insert({'uuid': Binary(my_uuid.bytes, 3)})
>>> coll.insert_one({'uuid': Binary(my_uuid.bytes, 3)}).inserted_id
ObjectId('...')
>>> coll.find({'uuid': my_uuid}).count()
0
@ -199,8 +200,8 @@ class UUIDLegacy(Binary):
>>>
>>> # Convert from subtype 3 to subtype 4
>>> doc = coll.find_one({'uuid': UUIDLegacy(my_uuid)})
>>> coll.save(doc)
ObjectId('...')
>>> coll.replace_one({"_id": doc["_id"]}, doc).matched_count
1
>>> coll.find({'uuid': UUIDLegacy(my_uuid)}).count()
0
>>> coll.find({'uuid': {'$in': [UUIDLegacy(my_uuid), my_uuid]}}).count()

View File

@ -32,6 +32,7 @@
.. autoattribute:: read_preference
.. autoattribute:: write_concern
.. automethod:: with_options
.. automethod:: bulk_write
.. automethod:: insert_one
.. automethod:: insert_many
.. automethod:: replace_one
@ -39,33 +40,32 @@
.. automethod:: update_many
.. automethod:: delete_one
.. automethod:: delete_many
.. automethod:: insert(doc_or_docs[, manipulate=True[, check_keys=True[, continue_on_error=False[, **kwargs]]]])
.. automethod:: save(to_save[, manipulate=True[, check_keys=True[, **kwargs]]])
.. automethod:: update(spec, document[, upsert=False[, manipulate=False[, multi=False[, check_keys=True[, **kwargs]]]]])
.. automethod:: remove([spec_or_id=None[, multi=True[, **kwargs]]])
.. automethod:: initialize_unordered_bulk_op
.. automethod:: initialize_ordered_bulk_op
.. automethod:: bulk_write
.. automethod:: drop
.. automethod:: aggregate
.. automethod:: find([filter=None[, projection=None[, skip=0[, limit=0[, no_cursor_timeout=False[, cursor_type=NON_TAILABLE[, sort=None[, allow_partial_results=False[, oplog_replay=False[, modifiers=None[, manipulate=True]]]]]]]]]]])
.. automethod:: find_one([filter_or_id=None[, *args[, **kwargs]]])
.. automethod:: parallel_scan
.. automethod:: find_one_and_delete
.. automethod:: find_one_and_replace(filter, replacement, projection=None, sort=None, return_document=ReturnDocument.Before, **kwargs)
.. automethod:: find_one_and_update(filter, update, projection=None, sort=None, return_document=ReturnDocument.Before, **kwargs)
.. automethod:: count
.. automethod:: distinct
.. automethod:: create_index
.. automethod:: ensure_index
.. automethod:: drop_index
.. automethod:: drop_indexes
.. automethod:: reindex
.. automethod:: index_information
.. automethod:: options
.. automethod:: aggregate
.. automethod:: group
.. automethod:: drop
.. automethod:: rename
.. automethod:: distinct
.. automethod:: options
.. automethod:: group
.. automethod:: map_reduce
.. automethod:: inline_map_reduce
.. automethod:: parallel_scan
.. automethod:: initialize_unordered_bulk_op
.. automethod:: initialize_ordered_bulk_op
.. automethod:: insert(doc_or_docs[, manipulate=True[, check_keys=True[, continue_on_error=False[, **kwargs]]]])
.. automethod:: save(to_save[, manipulate=True[, check_keys=True[, **kwargs]]])
.. automethod:: update(spec, document[, upsert=False[, manipulate=False[, multi=False[, check_keys=True[, **kwargs]]]]])
.. automethod:: remove([spec_or_id=None[, multi=True[, **kwargs]]])
.. automethod:: find_and_modify
.. automethod:: find_one_and_delete
.. automethod:: find_one_and_replace(filter, replacement, projection=None, sort=None, return_document=ReturnDocument.Before, **kwargs)
.. automethod:: find_one_and_update(filter, update, projection=None, sort=None, return_document=ReturnDocument.Before, **kwargs)

View File

@ -20,14 +20,12 @@ aggregations on:
>>> from pymongo import MongoClient
>>> db = MongoClient().aggregation_example
>>> db.things.insert({"x": 1, "tags": ["dog", "cat"]})
ObjectId('...')
>>> db.things.insert({"x": 2, "tags": ["cat"]})
ObjectId('...')
>>> db.things.insert({"x": 2, "tags": ["mouse", "cat", "dog"]})
ObjectId('...')
>>> db.things.insert({"x": 3, "tags": []})
ObjectId('...')
>>> result = db.things.insert_many([{"x": 1, "tags": ["dog", "cat"]},
... {"x": 2, "tags": ["cat"]},
... {"x": 2, "tags": ["mouse", "cat", "dog"]},
... {"x": 3, "tags": []}])
>>> result.inserted_ids
[ObjectId('...'), ObjectId('...'), ObjectId('...'), ObjectId('...')]
.. _aggregate-examples:

View File

@ -17,8 +17,8 @@ Bulk Insert
.. versionadded:: 2.6
A batch of documents can be inserted by passing a list or generator
to the :meth:`~pymongo.collection.Collection.insert` method. PyMongo
A batch of documents can be inserted by passing a list to the
:meth:`~pymongo.collection.Collection.insert_many` method. PyMongo
will automatically split the batch into smaller sub-batches based on
the maximum message size accepted by MongoDB, supporting very large
bulk insert operations.
@ -27,7 +27,7 @@ bulk insert operations.
>>> import pymongo
>>> db = pymongo.MongoClient().bulk_example
>>> db.test.insert(({'i': i} for i in xrange(10000)))
>>> db.test.insert_many([{'i': i} for i in xrange(10000)]).inserted_ids
[...]
>>> db.test.count()
10000
@ -41,13 +41,6 @@ PyMongo also supports executing mixed bulk write operations. A batch
of insert, update, and remove operations can be executed together using
the bulk write operations API.
.. note::
Though the following API will work with all versions of MongoDB, it is
designed to be used with MongoDB versions >= 2.6. Much better bulk insert
performance can be achieved with older versions of MongoDB through the
:meth:`~pymongo.collection.Collection.insert` method.
.. _ordered_bulk:
Ordered Bulk Write Operations
@ -182,7 +175,7 @@ regardless of execution order.
>>> bulk.insert({'a': 2})
>>> bulk.insert({'a': 3})
>>> try:
... bulk.execute({'w': 4, 'wtimeout': 1})
... bulk.execute({'w': 3, 'wtimeout': 1})
... except BulkWriteError as bwe:
... pprint(bwe.details)
...

View File

@ -33,14 +33,12 @@ insert a couple of example locations:
.. doctest::
>>> db.places.insert({"loc": [2, 5]})
ObjectId('...')
>>> db.places.insert({"loc": [30, 5]})
ObjectId('...')
>>> db.places.insert({"loc": [1, 2]})
ObjectId('...')
>>> db.places.insert({"loc": [4, 4]})
ObjectId('...')
>>> result = db.places.insert_many([{"loc": [2, 5]},
... {"loc": [30, 5]},
... {"loc": [1, 2]},
... {"loc": [4, 4]}])
>>> result.inserted_ids
[ObjectId('...'), ObjectId('...'), ObjectId('...'), ObjectId('...')]
Querying
--------
@ -76,8 +74,8 @@ It's also possible to query for all items within a given rectangle
>>> for doc in db.places.find({"loc": {"$within": {"$box": [[2, 2], [5, 6]]}}}):
... repr(doc)
...
"{u'loc': [4, 4], u'_id': ObjectId('...')}"
"{u'loc': [2, 5], u'_id': ObjectId('...')}"
"{u'loc': [4, 4], u'_id': ObjectId('...')}"
Or circle (specified by center point and radius):
@ -87,8 +85,8 @@ Or circle (specified by center point and radius):
... repr(doc)
...
"{u'loc': [1, 2], u'_id': ObjectId('...')}"
"{u'loc': [4, 4], u'_id': ObjectId('...')}"
"{u'loc': [2, 5], u'_id': ObjectId('...')}"
"{u'loc': [4, 4], u'_id': ObjectId('...')}"
geoNear queries are also supported using :class:`~bson.son.SON`::

View File

@ -112,7 +112,7 @@ knows about. If you wait a moment, it to discovers the whole replica set:
You need not wait for replica set discovery in your application, however.
If you need to do any operation with a MongoClient, such as a
:meth:`~pymongo.collection.Collection.find` or an
:meth:`~pymongo.collection.Collection.insert`, the client waits to discover
:meth:`~pymongo.collection.Collection.insert_one`, the client waits to discover
a suitable member before it attempts the operation.
Handling Failover
@ -125,7 +125,7 @@ example failover to illustrate how everything behaves. First, we'll
connect to the replica set and perform a couple of basic operations::
>>> db = MongoClient("localhost", replicaSet='foo').test
>>> db.test.save({"x": 1})
>>> db.test.insert_one({"x": 1})
ObjectId('...')
>>> db.test.find_one()
{u'x': 1, u'_id': ObjectId('...')}
@ -306,7 +306,7 @@ connect to a sharded cluster, using a seed list, and perform a couple of
basic operations::
>>> db = MongoClient('localhost:30000,localhost:30001,localhost:30002').test
>>> db.test.save({'x': 1})
>>> db.test.insert_one({'x': 1})
ObjectId('...')
>>> db.test.find_one()
{u'x': 1, u'_id': ObjectId('...')}

View File

@ -25,7 +25,7 @@ read it back. Notice the byte string is decoded back to :class:`bytes`::
Type "help", "copyright", "credits" or "license" for more information.
>>> import pymongo
>>> c = pymongo.MongoClient()
>>> c.test.bintest.insert({'binary': b'this is a byte string'})
>>> c.test.bintest.insert_one({'binary': b'this is a byte string'}).inserted_id
ObjectId('4f9086b1fba5222021000000')
>>> c.test.bintest.find_one()
{'binary': b'this is a byte string', '_id': ObjectId('4f9086b1fba5222021000000')}

View File

@ -119,36 +119,31 @@ converted to and from the appropriate `BSON
Inserting a Document
--------------------
To insert a document into a collection we can use the
:meth:`~pymongo.collection.Collection.insert` method:
:meth:`~pymongo.collection.Collection.insert_one` method:
.. doctest::
>>> posts = db.posts
>>> post_id = posts.insert(post)
>>> post_id = posts.insert_one(post).inserted_id
>>> post_id
ObjectId('...')
When a document is inserted a special key, ``"_id"``, is automatically
added if the document doesn't already contain an ``"_id"`` key. The value
of ``"_id"`` must be unique across the
collection. :meth:`~pymongo.collection.Collection.insert` returns the
value of ``"_id"`` for the inserted document. For more information, see the
`documentation on _id
collection. :meth:`~pymongo.collection.Collection.insert_one` returns an
instance of :class:`~pymongo.results.InsertOneResult`. For more information
on ``"_id"``, see the `documentation on _id
<http://www.mongodb.org/display/DOCS/Object+IDs>`_.
.. todo:: notes on the differences between save and insert
After inserting the first document, the *posts* collection has
actually been created on the server. We can verify this by listing all
of the collections in our database:
.. doctest::
>>> db.collection_names()
[u'system.indexes', u'posts']
.. note:: The *system.indexes* collection is a special internal
collection that was created automatically.
>>> db.collection_names(include_system_collections=False)
[u'posts']
Getting a Single Document With :meth:`~pymongo.collection.Collection.find_one`
------------------------------------------------------------------------------
@ -242,9 +237,9 @@ Bulk Inserts
------------
In order to make querying a little more interesting, let's insert a
few more documents. In addition to inserting a single document, we can
also perform *bulk insert* operations, by passing an iterable as the
first argument to :meth:`~pymongo.collection.Collection.insert`. This
will insert each document in the iterable, sending only a single
also perform *bulk insert* operations, by passing a list as the
first argument to :meth:`~pymongo.collection.Collection.insert_many`.
This will insert each document in the list, sending only a single
command to the server:
.. doctest::
@ -257,12 +252,13 @@ command to the server:
... "title": "MongoDB is fun",
... "text": "and pretty easy too!",
... "date": datetime.datetime(2009, 11, 10, 10, 45)}]
>>> posts.insert(new_posts)
>>> result = posts.insert_many(new_posts)
>>> result.inserted_ids
[ObjectId('...'), ObjectId('...')]
There are a couple of interesting things to note about this example:
- The call to :meth:`~pymongo.collection.Collection.insert` now
- The result from :meth:`~pymongo.collection.Collection.insert_many` now
returns two :class:`~bson.objectid.ObjectId` instances, one for
each inserted document.
- ``new_posts[1]`` has a different "shape" than the other posts -

View File

@ -30,6 +30,7 @@ from pymongo.errors import (BulkWriteError,
OperationFailure)
from pymongo.message import (_INSERT, _UPDATE, _DELETE,
insert, _do_batched_write_command)
from pymongo.write_concern import WriteConcern
_DELETE_ALL = 0
@ -272,8 +273,8 @@ class _Bulk(object):
for run in generator:
cmd = SON([(_COMMANDS[run.op_type], self.collection.name),
('ordered', self.ordered)])
if write_concern:
cmd['writeConcern'] = write_concern
if write_concern.document:
cmd['writeConcern'] = write_concern.document
results = _do_batched_write_command(
self.namespace, run.op_type, cmd,
@ -296,31 +297,34 @@ class _Bulk(object):
"""Execute all operations, returning no results (w=0).
"""
coll = self.collection
w_value = 0
# If ordered is True we have to send GLE or use write
# commands so we can abort on the first error.
if self.ordered:
w_value = 1
write_concern = WriteConcern(w=int(self.ordered))
for run in generator:
try:
if run.op_type == _INSERT:
coll.insert(run.ops,
continue_on_error=not self.ordered,
w=w_value)
coll._insert(run.ops,
self.ordered,
write_concern=write_concern)
else:
for operation in run.ops:
try:
if run.op_type == _UPDATE:
coll.update(operation['q'],
operation['u'],
upsert=operation['upsert'],
multi=operation['multi'],
w=w_value)
doc = operation['u']
check_keys = True
if doc and next(iter(doc)).startswith('$'):
check_keys = False
coll._update(operation['q'],
doc,
operation['upsert'],
check_keys,
operation['multi'],
write_concern=write_concern)
else:
coll.remove(operation['q'],
multi=(not operation['limit']),
w=w_value)
coll._delete(operation['q'],
not operation['limit'],
write_concern)
except OperationFailure:
if self.ordered:
return
@ -331,7 +335,7 @@ class _Bulk(object):
def legacy_insert(self, operation, write_concern):
"""Do a legacy insert and return the result.
"""
# We have to do this here since Collection.insert
# We have to do this here since Collection._insert
# throws away results and we need to check for jnote.
client = self.collection.database.connection
return client._send_message(
@ -359,17 +363,23 @@ class _Bulk(object):
# at a time. That means the performance of bulk insert
# will be slower here than calling Collection.insert()
if run.op_type == _INSERT:
result = self.legacy_insert(operation, write_concern)
result = self.legacy_insert(operation,
write_concern.document)
elif run.op_type == _UPDATE:
result = coll.update(operation['q'],
operation['u'],
upsert=operation['upsert'],
multi=operation['multi'],
**write_concern)
doc = operation['u']
check_keys = True
if doc and next(iter(doc)).startswith('$'):
check_keys = False
result = coll._update(operation['q'],
doc,
operation['upsert'],
check_keys,
operation['multi'],
write_concern=write_concern)
else:
result = coll.remove(operation['q'],
multi=(not operation['limit']),
**write_concern)
result = coll._delete(operation['q'],
not operation['limit'],
write_concern)
_merge_legacy(run, full_result, result, idx)
except DocumentTooLarge as exc:
# MongoDB 2.6 uses error code 2 for "too large".
@ -410,14 +420,15 @@ class _Bulk(object):
'only be executed once.')
self.executed = True
client = self.collection.database.connection
write_concern = write_concern or self.collection.write_concern.document
write_concern = (WriteConcern(**write_concern) if
write_concern else self.collection.write_concern)
if self.ordered:
generator = self.gen_ordered()
else:
generator = self.gen_unordered()
if write_concern.get('w') == 0:
if not write_concern.acknowledged:
self.execute_no_results(generator)
elif client._writable_max_wire_version() > 1:
return self.execute_command(generator, write_concern)
@ -534,11 +545,6 @@ class BulkOperationBuilder(object):
in arbitrary order (possibly in parallel on the server), reporting
any errors that occurred after attempting all operations. Defaults
to ``True``.
.. warning::
If you are using a version of MongoDB older than 2.6 you will
get much better bulk insert performance using
:meth:`~pymongo.collection.Collection.insert`.
"""
self.__bulk = _Bulk(collection, ordered)

View File

@ -284,11 +284,8 @@ class Collection(common.BaseObject):
def bulk_write(self, requests, ordered=True):
"""Send a batch of write operations to the server.
This is an alternative to the fluent bulk write API provided through
the :meth:`initialize_ordered_bulk_op` and
:meth:`initialize_unordered_bulk_op` methods. Write operations are
passed as a list using the write operation classes from the
:mod:`~pymongo.options` module::
Write operations are passed as a list using the write operation classes
from the :mod:`~pymongo.options` module::
>>> # DeleteOne, UpdateOne, and UpdateMany are also available.
...
@ -324,216 +321,8 @@ class Collection(common.BaseObject):
return BulkWriteResult(bulk_api_result, True)
return BulkWriteResult({}, False)
def save(self, to_save, manipulate=True, check_keys=True, **kwargs):
"""Save a document in this collection.
If `to_save` already has an ``"_id"`` then an :meth:`update`
(upsert) operation is performed and any existing document with
that ``"_id"`` is overwritten. Otherwise an :meth:`insert`
operation is performed. In this case if `manipulate` is ``True``
an ``"_id"`` will be added to `to_save` and this method returns
the ``"_id"`` of the saved document. If `manipulate` is ``False``
the ``"_id"`` will be added by the server but this method will
return ``None``.
Raises :class:`TypeError` if `to_save` is not an instance of
:class:`dict`.
Write concern options can be passed as keyword arguments, overriding
any global defaults. Valid options include w=<int/string>,
wtimeout=<int>, j=<bool>, or fsync=<bool>. See the parameter list below
for a detailed explanation of these options.
By default an acknowledgment is requested from the server that the
save was successful, raising :class:`~pymongo.errors.OperationFailure`
if an error occurred. **Passing w=0 disables write acknowledgement
and all other write concern options.**
:Parameters:
- `to_save`: the document to be saved
- `manipulate` (optional): manipulate the document before
saving it?
- `check_keys` (optional): check if keys start with '$' or
contain '.', raising :class:`~pymongo.errors.InvalidName`
in either case.
- `w`: (integer or string) Used with replication, write operations
will block until they have been replicated to the specified number
or tagged set of servers. `w=<integer>` always includes the replica
set primary (e.g. w=3 means write to the primary and wait until
replicated to **two** secondaries). **w=0 disables acknowledgement
of write operations and can not be used with other write concern
options.**
- `wtimeout`: (integer) Used in conjunction with `w`. Specify a value
in milliseconds to control how long to wait for write propagation
to complete. If replication does not complete in the given
timeframe, a timeout exception is raised.
- `j`: If ``True`` block until write operations have been committed
to the journal. Cannot be used in combination with `fsync`. Prior
to MongoDB 2.6 this option was ignored if the server was running
without journaling. Starting with MongoDB 2.6 write operations will
fail with an exception if this option is used when the server is
running without journaling.
- `fsync`: If ``True`` and the server is running without journaling,
blocks until the server has synced all data files to disk. If the
server is running with journaling, this acts the same as the `j`
option, blocking until write operations have been committed to the
journal. Cannot be used in combination with `j`.
:Returns:
- The ``'_id'`` value of `to_save` or ``[None]`` if `manipulate` is
``False`` and `to_save` has no '_id' field.
.. versionchanged:: 3.0
Removed the `safe` parameter
.. mongodoc:: insert
"""
if not isinstance(to_save, collections.MutableMapping):
raise TypeError("cannot save object of type %s" % type(to_save))
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
if "_id" not in to_save:
return self.__insert(to_save, True,
check_keys, manipulate, write_concern)
else:
self.__update({"_id": to_save["_id"]}, to_save, True,
check_keys, False, manipulate, write_concern)
def insert(self, doc_or_docs, manipulate=True,
check_keys=True, continue_on_error=False, **kwargs):
"""Insert a document(s) into this collection.
If `manipulate` is ``True``, the document(s) are manipulated using
any :class:`~pymongo.son_manipulator.SONManipulator` instances
that have been added to this :class:`~pymongo.database.Database`.
In this case an ``"_id"`` will be added if the document(s) does
not already contain one and the ``"id"`` (or list of ``"_id"``
values for more than one document) will be returned.
If `manipulate` is ``False`` and the document(s) does not include
an ``"_id"`` one will be added by the server. The server
does not return the ``"_id"`` it created so ``None`` is returned.
Write concern options can be passed as keyword arguments, overriding
any global defaults. Valid options include w=<int/string>,
wtimeout=<int>, j=<bool>, or fsync=<bool>. See the parameter list below
for a detailed explanation of these options.
By default an acknowledgment is requested from the server that the
insert was successful, raising :class:`~pymongo.errors.OperationFailure`
if an error occurred. **Passing w=0 disables write acknowledgement
and all other write concern options.**
:Parameters:
- `doc_or_docs`: a document or list of documents to be
inserted
- `manipulate` (optional): If ``True`` manipulate the documents
before inserting.
- `check_keys` (optional): If ``True`` check if keys start with '$'
or contain '.', raising :class:`~pymongo.errors.InvalidName` in
either case.
- `continue_on_error` (optional): If ``True``, the database will not
stop processing a bulk insert if one fails (e.g. due to duplicate
IDs). This makes bulk insert behave similarly to a series of single
inserts, except lastError will be set if any insert fails, not just
the last one. If multiple errors occur, only the most recent will
be reported by :meth:`~pymongo.database.Database.error`.
- `w`: (integer or string) Used with replication, write operations
will block until they have been replicated to the specified number
or tagged set of servers. `w=<integer>` always includes the replica
set primary (e.g. w=3 means write to the primary and wait until
replicated to **two** secondaries). **w=0 disables acknowledgement
of write operations and can not be used with other write concern
options.**
- `wtimeout`: (integer) Used in conjunction with `w`. Specify a value
in milliseconds to control how long to wait for write propagation
to complete. If replication does not complete in the given
timeframe, a timeout exception is raised.
- `j`: If ``True`` block until write operations have been committed
to the journal. Cannot be used in combination with `fsync`. Prior
to MongoDB 2.6 this option was ignored if the server was running
without journaling. Starting with MongoDB 2.6 write operations will
fail with an exception if this option is used when the server is
running without journaling.
- `fsync`: If ``True`` and the server is running without journaling,
blocks until the server has synced all data files to disk. If the
server is running with journaling, this acts the same as the `j`
option, blocking until write operations have been committed to the
journal. Cannot be used in combination with `j`.
:Returns:
- The ``'_id'`` value (or list of '_id' values) of `doc_or_docs` or
``[None]`` if manipulate is ``False`` and the documents passed
as `doc_or_docs` do not include an '_id' field.
.. note:: `continue_on_error` requires server version **>= 1.9.1**
.. versionchanged:: 3.0
Removed the `safe` parameter
.. versionadded:: 2.1
Support for continue_on_error.
.. mongodoc:: insert
"""
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self.__insert(doc_or_docs, not continue_on_error,
check_keys, manipulate, write_concern)
def insert_one(self, document):
"""Insert a single document.
:Parameters:
- `document`: The document to insert. Must be a mutable mapping
type. If the document does not have an _id field one will be
added automatically.
:Returns:
- An instance of :class:`~pymongo.results.InsertOneResult`.
"""
if not isinstance(document, collections.MutableMapping):
raise TypeError("document must be a mutable mapping type")
if "_id" not in document:
document["_id"] = ObjectId()
return InsertOneResult(self.__insert(document),
self.write_concern.acknowledged)
def insert_many(self, documents, ordered=True):
"""Insert a list of documents.
:Parameters:
- `documents`: A list of documents to insert.
- `ordered` (optional): If ``True`` (the default) documents will be
inserted on the server serially, in the order provided. If an error
occurs all remaining inserts are aborted. If ``False``, documents
will be inserted on the server in arbitrary order, possibly in
parallel, and all document inserts will be attempted.
:Returns:
An instance of :class:`~pymongo.results.InsertManyResult`.
"""
if not isinstance(documents, list) or not documents:
raise TypeError("documents must be a non-empty list")
inserted_ids = []
def gen():
"""A generator that validates documents and handles _ids."""
for document in documents:
if not isinstance(document, collections.MutableMapping):
raise TypeError("document must be a dict or other "
"subclass of collections.MutableMapping")
if "_id" not in document:
document["_id"] = ObjectId()
inserted_ids.append(document["_id"])
yield (_INSERT, document)
blk = _Bulk(self, ordered)
blk.ops = [doc for doc in gen()]
blk.execute(self.write_concern.document)
return InsertManyResult(inserted_ids, self.write_concern.acknowledged)
def __insert(self, docs, ordered=True,
check_keys=True, manipulate=False, write_concern=None):
def _insert(self, docs, ordered=True,
check_keys=True, manipulate=False, write_concern=None):
"""Internal insert helper."""
client = self.database.connection
return_one = False
@ -588,168 +377,59 @@ class Collection(common.BaseObject):
else:
return ids
def update(self, spec, document, upsert=False, manipulate=False,
multi=False, check_keys=True, **kwargs):
"""Update a document(s) in this collection.
Raises :class:`TypeError` if either `spec` or `document` is
not an instance of ``dict`` or `upsert` is not an instance of
``bool``.
Write concern options can be passed as keyword arguments, overriding
any global defaults. Valid options include w=<int/string>,
wtimeout=<int>, j=<bool>, or fsync=<bool>. See the parameter list below
for a detailed explanation of these options.
By default an acknowledgment is requested from the server that the
update was successful, raising :class:`~pymongo.errors.OperationFailure`
if an error occurred. **Passing w=0 disables write acknowledgement
and all other write concern options.**
There are many useful `update modifiers`_ which can be used
when performing updates. For example, here we use the
``"$set"`` modifier to modify some fields in a matching
document:
.. doctest::
>>> db.test.insert({"x": "y", "a": "b"})
ObjectId('...')
>>> list(db.test.find())
[{u'a': u'b', u'x': u'y', u'_id': ObjectId('...')}]
>>> db.test.update({"x": "y"}, {"$set": {"a": "c"}})
{...}
>>> list(db.test.find())
[{u'a': u'c', u'x': u'y', u'_id': ObjectId('...')}]
def insert_one(self, document):
"""Insert a single document.
:Parameters:
- `spec`: a ``dict`` or :class:`~bson.son.SON` instance
specifying elements which must be present for a document
to be updated
- `document`: a ``dict`` or :class:`~bson.son.SON`
instance specifying the document to be used for the update
or (in the case of an upsert) insert - see docs on MongoDB
`update modifiers`_
- `upsert` (optional): perform an upsert if ``True``
- `manipulate` (optional): manipulate the document before
updating? If ``True`` all instances of
:mod:`~pymongo.son_manipulator.SONManipulator` added to
this :class:`~pymongo.database.Database` will be applied
to the document before performing the update.
- `check_keys` (optional): check if keys in `document` start
with '$' or contain '.', raising
:class:`~pymongo.errors.InvalidName`. Only applies to
document replacement, not modification through $
operators.
- `multi` (optional): update all documents that match
`spec`, rather than just the first matching document. The
default value for `multi` is currently ``False``, but this
might eventually change to ``True``. It is recommended
that you specify this argument explicitly for all update
operations in order to prepare your code for that change.
- `w`: (integer or string) Used with replication, write operations
will block until they have been replicated to the specified number
or tagged set of servers. `w=<integer>` always includes the replica
set primary (e.g. w=3 means write to the primary and wait until
replicated to **two** secondaries). **w=0 disables acknowledgement
of write operations and can not be used with other write concern
options.**
- `wtimeout`: (integer) Used in conjunction with `w`. Specify a value
in milliseconds to control how long to wait for write propagation
to complete. If replication does not complete in the given
timeframe, a timeout exception is raised.
- `j`: If ``True`` block until write operations have been committed
to the journal. Cannot be used in combination with `fsync`. Prior
to MongoDB 2.6 this option was ignored if the server was running
without journaling. Starting with MongoDB 2.6 write operations will
fail with an exception if this option is used when the server is
running without journaling.
- `fsync`: If ``True`` and the server is running without journaling,
blocks until the server has synced all data files to disk. If the
server is running with journaling, this acts the same as the `j`
option, blocking until write operations have been committed to the
journal. Cannot be used in combination with `j`.
- `document`: The document to insert. Must be a mutable mapping
type. If the document does not have an _id field one will be
added automatically.
:Returns:
- A document (dict) describing the effect of the update or ``None``
if write acknowledgement is disabled.
.. versionchanged:: 3.0
Removed the `safe` parameter
.. _update modifiers: http://www.mongodb.org/display/DOCS/Updating
.. mongodoc:: update
- An instance of :class:`~pymongo.results.InsertOneResult`.
"""
if not isinstance(spec, collections.Mapping):
raise TypeError("spec must be a mapping type")
if not isinstance(document, collections.Mapping):
raise TypeError("document must be a mapping type")
if document:
# If a top level key begins with '$' this is a modify operation
# and we should skip key validation. It doesn't matter which key
# we check here. Passing a document with a mix of top level keys
# starting with and without a '$' is invalid and the server will
# raise an appropriate exception.
first = next(iter(document))
if first.startswith('$'):
check_keys = False
if not isinstance(document, collections.MutableMapping):
raise TypeError("document must be a mutable mapping type")
if "_id" not in document:
document["_id"] = ObjectId()
return InsertOneResult(self._insert(document),
self.write_concern.acknowledged)
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self.__update(spec, document, upsert,
check_keys, multi, manipulate, write_concern)
def replace_one(self, filter, replacement, upsert=False):
"""Replace a single document matching the filter.
def insert_many(self, documents, ordered=True):
"""Insert a list of documents.
:Parameters:
- `filter`: A query that matches the document to replace.
- `replacement`: The new document.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
- `documents`: A list of documents to insert.
- `ordered` (optional): If ``True`` (the default) documents will be
inserted on the server serially, in the order provided. If an error
occurs all remaining inserts are aborted. If ``False``, documents
will be inserted on the server in arbitrary order, possibly in
parallel, and all document inserts will be attempted.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
An instance of :class:`~pymongo.results.InsertManyResult`.
"""
helpers._check_ok_for_replace(replacement)
result = self.__update(filter, replacement, upsert)
return UpdateResult(result, self.write_concern.acknowledged)
if not isinstance(documents, list) or not documents:
raise TypeError("documents must be a non-empty list")
inserted_ids = []
def gen():
"""A generator that validates documents and handles _ids."""
for document in documents:
if not isinstance(document, collections.MutableMapping):
raise TypeError("document must be a dict or other "
"subclass of collections.MutableMapping")
if "_id" not in document:
document["_id"] = ObjectId()
inserted_ids.append(document["_id"])
yield (_INSERT, document)
def update_one(self, filter, update, upsert=False):
"""Update a single document matching the filter.
blk = _Bulk(self, ordered)
blk.ops = [doc for doc in gen()]
blk.execute(self.write_concern.document)
return InsertManyResult(inserted_ids, self.write_concern.acknowledged)
:Parameters:
- `filter`: A query that matches the document to update.
- `update`: The modifications to apply.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
"""
helpers._check_ok_for_update(update)
result = self.__update(filter, update, upsert, False)
return UpdateResult(result, self.write_concern.acknowledged)
def update_many(self, filter, update, upsert=False):
"""Update one or more documents that match the filter.
:Parameters:
- `filter`: A query that matches the documents to update.
- `update`: The modifications to apply.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
"""
helpers._check_ok_for_update(update)
result = self.__update(filter, update, upsert, False, True)
return UpdateResult(result, self.write_concern.acknowledged)
def __update(self, filter, document, upsert=False, check_keys=True,
multi=False, manipulate=False, write_concern=None):
def _update(self, filter, document, upsert=False, check_keys=True,
multi=False, manipulate=False, write_concern=None):
"""Internal update / replace helper."""
if not isinstance(filter, collections.Mapping):
raise TypeError("filter must be a mapping type")
@ -796,6 +476,54 @@ class Collection(common.BaseObject):
filter, document, safe, concern,
check_keys, self.codec_options), safe)
def replace_one(self, filter, replacement, upsert=False):
"""Replace a single document matching the filter.
:Parameters:
- `filter`: A query that matches the document to replace.
- `replacement`: The new document.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
"""
helpers._check_ok_for_replace(replacement)
result = self._update(filter, replacement, upsert)
return UpdateResult(result, self.write_concern.acknowledged)
def update_one(self, filter, update, upsert=False):
"""Update a single document matching the filter.
:Parameters:
- `filter`: A query that matches the document to update.
- `update`: The modifications to apply.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
"""
helpers._check_ok_for_update(update)
result = self._update(filter, update, upsert, False)
return UpdateResult(result, self.write_concern.acknowledged)
def update_many(self, filter, update, upsert=False):
"""Update one or more documents that match the filter.
:Parameters:
- `filter`: A query that matches the documents to update.
- `update`: The modifications to apply.
- `upsert` (optional): If ``True``, perform an insert if no documents
match the filter.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
"""
helpers._check_ok_for_update(update)
result = self._update(filter, update, upsert, False, True)
return UpdateResult(result, self.write_concern.acknowledged)
def drop(self):
"""Alias for :meth:`~pymongo.database.Database.drop_collection`.
@ -806,97 +534,7 @@ class Collection(common.BaseObject):
"""
self.__database.drop_collection(self.__name)
def remove(self, spec_or_id=None, multi=True, **kwargs):
"""Remove a document(s) from this collection.
.. warning:: Calls to :meth:`remove` should be performed with
care, as removed data cannot be restored.
If `spec_or_id` is ``None``, all documents in this collection
will be removed. This is not equivalent to calling
:meth:`~pymongo.database.Database.drop_collection`, however,
as indexes will not be removed.
Write concern options can be passed as keyword arguments, overriding
any global defaults. Valid options include w=<int/string>,
wtimeout=<int>, j=<bool>, or fsync=<bool>. See the parameter list below
for a detailed explanation of these options.
By default an acknowledgment is requested from the server that the
remove was successful, raising :class:`~pymongo.errors.OperationFailure`
if an error occurred. **Passing w=0 disables write acknowledgement
and all other write concern options.**
:Parameters:
- `spec_or_id` (optional): a dictionary specifying the
documents to be removed OR any other type specifying the
value of ``"_id"`` for the document to be removed
- `multi` (optional): If ``True`` (the default) remove all documents
matching `spec_or_id`, otherwise remove only the first matching
document.
- `w`: (integer or string) Used with replication, write operations
will block until they have been replicated to the specified number
or tagged set of servers. `w=<integer>` always includes the replica
set primary (e.g. w=3 means write to the primary and wait until
replicated to **two** secondaries). **w=0 disables acknowledgement
of write operations and can not be used with other write concern
options.**
- `wtimeout`: (integer) Used in conjunction with `w`. Specify a value
in milliseconds to control how long to wait for write propagation
to complete. If replication does not complete in the given
timeframe, a timeout exception is raised.
- `j`: If ``True`` block until write operations have been committed
to the journal. Cannot be used in combination with `fsync`. Prior
to MongoDB 2.6 this option was ignored if the server was running
without journaling. Starting with MongoDB 2.6 write operations will
fail with an exception if this option is used when the server is
running without journaling.
- `fsync`: If ``True`` and the server is running without journaling,
blocks until the server has synced all data files to disk. If the
server is running with journaling, this acts the same as the `j`
option, blocking until write operations have been committed to the
journal. Cannot be used in combination with `j`.
:Returns:
- A document (dict) describing the effect of the remove or ``None``
if write acknowledgement is disabled.
.. versionchanged:: 3.0
Removed the `safe` parameter
.. mongodoc:: remove
"""
if spec_or_id is None:
spec_or_id = {}
if not isinstance(spec_or_id, collections.Mapping):
spec_or_id = {"_id": spec_or_id}
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self.__delete(spec_or_id, multi, write_concern)
def delete_one(self, filter):
"""Delete a single document matching the filter.
:Parameters:
- `filter`: A query that matches the document to delete.
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
"""
return DeleteResult(self.__delete(filter, False),
self.write_concern.acknowledged)
def delete_many(self, filter):
"""Delete one or more documents matching the filter.
:Parameters:
- `filter`: A query that matches the documents to delete.
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
"""
return DeleteResult(self.__delete(filter, True),
self.write_concern.acknowledged)
def __delete(self, filter, multi, write_concern=None):
def _delete(self, filter, multi, write_concern=None):
"""Internal delete helper."""
if not isinstance(filter, collections.Mapping):
raise TypeError("filter must be a mapping type")
@ -927,6 +565,28 @@ class Collection(common.BaseObject):
concern, self.codec_options,
int(not multi)), safe)
def delete_one(self, filter):
"""Delete a single document matching the filter.
:Parameters:
- `filter`: A query that matches the document to delete.
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
"""
return DeleteResult(self._delete(filter, False),
self.write_concern.acknowledged)
def delete_many(self, filter):
"""Delete one or more documents matching the filter.
:Parameters:
- `filter`: A query that matches the documents to delete.
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
"""
return DeleteResult(self._delete(filter, True),
self.write_concern.acknowledged)
def find_one(self, filter=None, *args, **kwargs):
"""Get a single document from the database.
@ -1299,7 +959,7 @@ class Collection(common.BaseObject):
index["ns"] = self.__full_name
wcn = (self.write_concern if
self.write_concern.acknowledged else WriteConcern())
self.__database.system.indexes.__insert(
self.__database.system.indexes._insert(
index, True, False, False, wcn)
else:
raise
@ -1868,74 +1528,6 @@ class Collection(common.BaseObject):
else:
return res.get("results")
def find_and_modify(self, query={}, update=None,
upsert=False, sort=None, full_response=False,
manipulate=False, **kwargs):
"""Update and return an object.
**DEPRECATED** - Use :meth:`find_one_and_delete`,
:meth:`find_one_and_replace`, or :meth:`find_one_and_update` instead.
"""
warnings.warn("find_and_modify is deprecated, use find_one_and_delete"
", find_one_and_replace, or find_one_and_update instead",
DeprecationWarning, stacklevel=2)
if (not update and not kwargs.get('remove', None)):
raise ValueError("Must either update or remove")
if (update and kwargs.get('remove', None)):
raise ValueError("Can't do both update and remove")
# No need to include empty args
if query:
kwargs['query'] = query
if update:
kwargs['update'] = update
if upsert:
kwargs['upsert'] = upsert
if sort:
# Accept a list of tuples to match Cursor's sort parameter.
if isinstance(sort, list):
kwargs['sort'] = helpers._index_document(sort)
# Accept OrderedDict, SON, and dict with len == 1 so we
# don't break existing code already using find_and_modify.
elif (isinstance(sort, ordered_types) or
isinstance(sort, dict) and len(sort) == 1):
warnings.warn("Passing mapping types for `sort` is deprecated,"
" use a list of (key, direction) pairs instead",
DeprecationWarning, stacklevel=2)
kwargs['sort'] = sort
else:
raise TypeError("sort must be a list of (key, direction) "
"pairs, a dict of len 1, or an instance of "
"SON or OrderedDict")
fields = kwargs.pop("fields", None)
if fields is not None:
kwargs["fields"] = helpers._fields_list_to_dict(fields, "fields")
cmd = SON([("findAndModify", self.__name)])
cmd.update(kwargs)
out = self._command(cmd,
ReadPreference.PRIMARY,
allowable_errors=[_NO_OBJ_ERROR])[0]
if not out['ok']:
if out["errmsg"] == _NO_OBJ_ERROR:
return None
else:
# Should never get here b/c of allowable_errors
raise ValueError("Unexpected Error: %s" % (out,))
if full_response:
return out
else:
document = out.get('value')
if manipulate:
document = self.__database._fix_outgoing(document, self)
return document
def __find_and_modify(self, filter, projection, sort, upsert=None,
return_document=ReturnDocument.Before, **kwargs):
"""Internal findAndModify helper."""
@ -2051,6 +1643,166 @@ class Collection(common.BaseObject):
return self.__find_and_modify(filter, projection,
sort, upsert, return_document, **kwargs)
def save(self, to_save, manipulate=True, check_keys=True, **kwargs):
"""Save a document in this collection.
**DEPRECATED** - Use :meth:`insert_one` or :meth:`replace_one` instead.
.. versionchanged:: 3.0
Removed the `safe` parameter
"""
warnings.warn("save is deprecated. Use insert_one or replace_one "
"instead", DeprecationWarning, stacklevel=2)
if not isinstance(to_save, collections.MutableMapping):
raise TypeError("cannot save object of type %s" % type(to_save))
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
if "_id" not in to_save:
return self._insert(to_save, True,
check_keys, manipulate, write_concern)
else:
self._update({"_id": to_save["_id"]}, to_save, True,
check_keys, False, manipulate, write_concern)
def insert(self, doc_or_docs, manipulate=True,
check_keys=True, continue_on_error=False, **kwargs):
"""Insert a document(s) into this collection.
**DEPRECATED** - Use :meth:`insert_one` or :meth:`insert_many` instead.
.. versionchanged:: 3.0
Removed the `safe` parameter
"""
warnings.warn("insert is deprecated. Use insert_one or insert_many "
"instead.", DeprecationWarning, stacklevel=2)
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self._insert(doc_or_docs, not continue_on_error,
check_keys, manipulate, write_concern)
def update(self, spec, document, upsert=False, manipulate=False,
multi=False, check_keys=True, **kwargs):
"""Update a document(s) in this collection.
**DEPRECATED** - Use :meth:`replace_one`, :meth:`update_one`, or
:meth:`update_many` instead.
.. versionchanged:: 3.0
Removed the `safe` parameter
"""
warnings.warn("update is deprecated. Use replace_one, update_one or "
"update_many instead.", DeprecationWarning, stacklevel=2)
if not isinstance(spec, collections.Mapping):
raise TypeError("spec must be a mapping type")
if not isinstance(document, collections.Mapping):
raise TypeError("document must be a mapping type")
if document:
# If a top level key begins with '$' this is a modify operation
# and we should skip key validation. It doesn't matter which key
# we check here. Passing a document with a mix of top level keys
# starting with and without a '$' is invalid and the server will
# raise an appropriate exception.
first = next(iter(document))
if first.startswith('$'):
check_keys = False
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self._update(spec, document, upsert,
check_keys, multi, manipulate, write_concern)
def remove(self, spec_or_id=None, multi=True, **kwargs):
"""Remove a document(s) from this collection.
**DEPRECATED** - Use :meth:`delete_one` or :meth:`delete_many` instead.
.. versionchanged:: 3.0
Removed the `safe` parameter
"""
warnings.warn("remove is deprecated. Use delete_one or delete_many "
"instead.", DeprecationWarning, stacklevel=2)
if spec_or_id is None:
spec_or_id = {}
if not isinstance(spec_or_id, collections.Mapping):
spec_or_id = {"_id": spec_or_id}
write_concern = None
if kwargs:
write_concern = WriteConcern(**kwargs)
return self._delete(spec_or_id, multi, write_concern)
def find_and_modify(self, query={}, update=None,
upsert=False, sort=None, full_response=False,
manipulate=False, **kwargs):
"""Update and return an object.
**DEPRECATED** - Use :meth:`find_one_and_delete`,
:meth:`find_one_and_replace`, or :meth:`find_one_and_update` instead.
"""
warnings.warn("find_and_modify is deprecated, use find_one_and_delete"
", find_one_and_replace, or find_one_and_update instead",
DeprecationWarning, stacklevel=2)
if (not update and not kwargs.get('remove', None)):
raise ValueError("Must either update or remove")
if (update and kwargs.get('remove', None)):
raise ValueError("Can't do both update and remove")
# No need to include empty args
if query:
kwargs['query'] = query
if update:
kwargs['update'] = update
if upsert:
kwargs['upsert'] = upsert
if sort:
# Accept a list of tuples to match Cursor's sort parameter.
if isinstance(sort, list):
kwargs['sort'] = helpers._index_document(sort)
# Accept OrderedDict, SON, and dict with len == 1 so we
# don't break existing code already using find_and_modify.
elif (isinstance(sort, ordered_types) or
isinstance(sort, dict) and len(sort) == 1):
warnings.warn("Passing mapping types for `sort` is deprecated,"
" use a list of (key, direction) pairs instead",
DeprecationWarning, stacklevel=2)
kwargs['sort'] = sort
else:
raise TypeError("sort must be a list of (key, direction) "
"pairs, a dict of len 1, or an instance of "
"SON or OrderedDict")
fields = kwargs.pop("fields", None)
if fields is not None:
kwargs["fields"] = helpers._fields_list_to_dict(fields, "fields")
cmd = SON([("findAndModify", self.__name)])
cmd.update(kwargs)
out = self._command(cmd,
ReadPreference.PRIMARY,
allowable_errors=[_NO_OBJ_ERROR])[0]
if not out['ok']:
if out["errmsg"] == _NO_OBJ_ERROR:
return None
else:
# Should never get here b/c of allowable_errors
raise ValueError("Unexpected Error: %s" % (out,))
if full_response:
return out
else:
document = out.get('value')
if manipulate:
document = self.__database._fix_outgoing(document, self)
return document
def __iter__(self):
return self

View File

@ -24,7 +24,10 @@ from bson.py3compat import string_type
from pymongo import MongoClient
from pymongo.options import *
from pymongo.common import partition_node
from pymongo.errors import BulkWriteError, InvalidOperation, OperationFailure
from pymongo.errors import (BulkWriteError,
ConfigurationError,
InvalidOperation,
OperationFailure)
from pymongo.write_concern import WriteConcern
from test import (client_context,
unittest,
@ -953,7 +956,7 @@ class TestBulkWriteConcern(BulkTestBase):
batch = self.coll.initialize_ordered_bulk_op()
batch.insert({'a': 1})
self.assertRaises(
OperationFailure,
ConfigurationError,
batch.execute, {'fsync': True, 'j': True})
@client_context.require_replica_set

View File

@ -26,7 +26,6 @@ sys.path[0:0] = [""]
from bson.regex import Regex
from bson.code import Code
from bson.dbref import DBRef
from bson.objectid import ObjectId
from bson.py3compat import u, itervalues
from bson.son import SON
@ -1597,36 +1596,6 @@ class TestCollection(IntegrationTest):
c.insert_one({'bad': bad})
self.assertEqual('bar', c.find_one()['bad']['foo'])
def test_bad_dbref(self):
c = self.db.test
c.drop()
# Incomplete DBRefs.
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$ref': 'collection'}})
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$id': ObjectId()}})
ref_only = {'ref': {'$ref': 'collection'}}
id_only = {'ref': {'$id': ObjectId()}}
# Starting with MongoDB 2.5.2 this is no longer possible
# from insert, update, or findAndModify.
if not client_context.version.at_least(2, 5, 2):
# Force insert of ref without $id.
c.insert(ref_only, check_keys=False)
self.assertEqual(DBRef('collection', id=None),
c.find_one()['ref'])
c.drop()
# DBRef without $ref is decoded as normal subdocument.
c.insert(id_only, check_keys=False)
self.assertEqual(id_only, c.find_one())
def test_find_one_and(self):
c = self.db.test
c.drop()

View File

@ -21,6 +21,7 @@ import warnings
sys.path[0:0] = [""]
from bson.codec_options import CodecOptions
from bson.dbref import DBRef
from bson.objectid import ObjectId
from bson.py3compat import u
from bson.son import SON
@ -43,6 +44,42 @@ from test.utils import (oid_generated_on_client,
wait_until)
class TestDeprecations(IntegrationTest):
@classmethod
def setUpClass(cls):
super(TestDeprecations, cls).setUpClass()
cls.warn_context = warnings.catch_warnings()
cls.warn_context.__enter__()
warnings.simplefilter("error", DeprecationWarning)
@classmethod
def tearDownClass(cls):
cls.warn_context.__exit__()
cls.warn_context = None
def test_save_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.save({}))
def test_insert_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.insert({}))
def test_update_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.update({}, {}))
def test_remove_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.remove({}))
def test_find_and_modify_deprecation(self):
self.assertRaises(
DeprecationWarning,
lambda: self.db.test.find_and_modify({'i': 5}, {}))
class TestLegacy(IntegrationTest):
@classmethod
@ -180,7 +217,7 @@ class TestLegacy(IntegrationTest):
db.drop_collection("test")
self.assertEqual(db.test.find().count(), 0)
ids = db.test.insert(({"hello": u("world")}, {"hello": u("world")}))
db.test.insert(({"hello": u("world")}, {"hello": u("world")}))
self.assertEqual(db.test.find().count(), 2)
db.drop_collection("test")
@ -330,6 +367,37 @@ class TestLegacy(IntegrationTest):
wait_until(lambda: 2 == db.collection_4.count(),
'insert 2 documents', timeout=60)
def test_bad_dbref(self):
# Requires the legacy API to test.
c = self.db.test
c.drop()
# Incomplete DBRefs.
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$ref': 'collection'}})
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$id': ObjectId()}})
ref_only = {'ref': {'$ref': 'collection'}}
id_only = {'ref': {'$id': ObjectId()}}
# Starting with MongoDB 2.5.2 this is no longer possible
# from insert, update, or findAndModify.
if not client_context.version.at_least(2, 5, 2):
# Force insert of ref without $id.
c.insert(ref_only, check_keys=False)
self.assertEqual(DBRef('collection', id=None),
c.find_one()['ref'])
c.drop()
# DBRef without $ref is decoded as normal subdocument.
c.insert(id_only, check_keys=False)
self.assertEqual(id_only, c.find_one())
def test_update(self):
# Tests legacy update.
db = self.db
@ -628,11 +696,6 @@ class TestLegacy(IntegrationTest):
c.drop()
c.insert({'_id': 1, 'i': 1})
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
self.assertRaises(DeprecationWarning, lambda:
c.find_and_modify({'i': 5}, {}))
# Test that we raise DuplicateKeyError when appropriate.
# MongoDB doesn't have a code field for DuplicateKeyError
# from commands before 2.2.