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:
parent
aa090fe393
commit
839893939e
@ -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')}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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)
|
||||
...
|
||||
|
||||
@ -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`::
|
||||
|
||||
|
||||
@ -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('...')}
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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 -
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user