diff --git a/README.rst b/README.rst index 2aac7371c..40ea14865 100644 --- a/README.rst +++ b/README.rst @@ -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')} diff --git a/bson/binary.py b/bson/binary.py index 679d0398d..c6bc7ba84 100644 --- a/bson/binary.py +++ b/bson/binary.py @@ -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() diff --git a/doc/api/pymongo/collection.rst b/doc/api/pymongo/collection.rst index bf8c4ca82..62417f930 100644 --- a/doc/api/pymongo/collection.rst +++ b/doc/api/pymongo/collection.rst @@ -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) diff --git a/doc/examples/aggregation.rst b/doc/examples/aggregation.rst index e1cc1a17f..0b3f43550 100644 --- a/doc/examples/aggregation.rst +++ b/doc/examples/aggregation.rst @@ -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: diff --git a/doc/examples/bulk.rst b/doc/examples/bulk.rst index a8beb5fb3..48c0f8534 100644 --- a/doc/examples/bulk.rst +++ b/doc/examples/bulk.rst @@ -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) ... diff --git a/doc/examples/geo.rst b/doc/examples/geo.rst index adf1939c3..022184ccd 100644 --- a/doc/examples/geo.rst +++ b/doc/examples/geo.rst @@ -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`:: diff --git a/doc/examples/high_availability.rst b/doc/examples/high_availability.rst index 3afd732b4..2f3101343 100644 --- a/doc/examples/high_availability.rst +++ b/doc/examples/high_availability.rst @@ -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('...')} diff --git a/doc/python3.rst b/doc/python3.rst index ffd56decc..5b76eef6f 100644 --- a/doc/python3.rst +++ b/doc/python3.rst @@ -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')} diff --git a/doc/tutorial.rst b/doc/tutorial.rst index cf3961d7c..cdf528080 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -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 `_. -.. 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 - diff --git a/pymongo/bulk.py b/pymongo/bulk.py index 22aefa2f4..74476458f 100644 --- a/pymongo/bulk.py +++ b/pymongo/bulk.py @@ -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) diff --git a/pymongo/collection.py b/pymongo/collection.py index 3bb3c5d14..bfcaeec1c 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -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=, - wtimeout=, j=, or fsync=. 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=` 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=, - wtimeout=, j=, or fsync=. 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=` 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=, - wtimeout=, j=, or fsync=. 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=` 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=, - wtimeout=, j=, or fsync=. 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=` 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 diff --git a/test/test_bulk.py b/test/test_bulk.py index 9734782ed..e5edca8ce 100644 --- a/test/test_bulk.py +++ b/test/test_bulk.py @@ -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 diff --git a/test/test_collection.py b/test/test_collection.py index f4aa1565a..765e8bdc3 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -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() diff --git a/test/test_legacy_api.py b/test/test_legacy_api.py index 5678944aa..1be5ffd72 100644 --- a/test/test_legacy_api.py +++ b/test/test_legacy_api.py @@ -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.