diff --git a/pymongo/collection.py b/pymongo/collection.py index ac405fd27..19a0b2bbf 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -29,7 +29,7 @@ from bson.son import SON from pymongo import (common, helpers, message) -from pymongo.bulk import (BulkOperationBuilder, _Bulk) +from pymongo.bulk import BulkOperationBuilder, _Bulk from pymongo.command_cursor import CommandCursor from pymongo.cursor import Cursor from pymongo.errors import ConfigurationError, InvalidName, OperationFailure @@ -495,6 +495,39 @@ class Collection(common.BaseObject): 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): """Internal insert helper.""" diff --git a/pymongo/results.py b/pymongo/results.py index 5bec1135a..3b0746ce9 100644 --- a/pymongo/results.py +++ b/pymongo/results.py @@ -52,6 +52,28 @@ class InsertOneResult(_WriteResult): return self.__inserted_id +class InsertManyResult(_WriteResult): + """The return type for :meth:`~pymongo.collection.Collection.insert_many`. + """ + + __slots__ = ("__inserted_ids", "__acknowledged") + + def __init__(self, inserted_ids, acknowledged): + self.__inserted_ids = inserted_ids + super(InsertManyResult, self).__init__(acknowledged) + + @property + def inserted_ids(self): + """A list of _ids of the inserted documents, in the order provided. + + .. note:: If ``False`` is passed for the `ordered` parameter to + :meth:`~pymongo.collection.Collection.insert_many` the server + may have inserted the documents in a different order than what + is presented here. + """ + return self.__inserted_ids + + class UpdateResult(_WriteResult): """The return type for :meth:`~pymongo.collection.Collection.update_one` and :meth:`~pymongo.collection.Collection.update_many`""" diff --git a/test/test_collection.py b/test/test_collection.py index 365097ead..4b1c1e82e 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -48,7 +48,7 @@ from pymongo.errors import (ConfigurationError, WTimeoutError) from pymongo.options import ReturnDocument from pymongo.read_preferences import ReadPreference -from pymongo.results import DeleteResult, InsertOneResult, UpdateResult +from pymongo.results import * from pymongo.son_manipulator import SONManipulator from pymongo.write_concern import WriteConcern from test.test_client import IntegrationTest @@ -639,6 +639,42 @@ class TestCollection(IntegrationTest): # The insert failed duplicate key... self.assertEqual(2, db.test.count()) + def test_insert_many(self): + db = self.db + db.test.drop() + + docs = [{} for _ in range(5)] + result = db.test.insert_many(docs) + self.assertTrue(isinstance(result, InsertManyResult)) + self.assertTrue(isinstance(result.inserted_ids, list)) + self.assertEqual(5, len(result.inserted_ids)) + for doc in docs: + _id = doc["_id"] + self.assertTrue(isinstance(_id, ObjectId)) + self.assertTrue(_id in result.inserted_ids) + self.assertEqual(1, db.test.count({'_id': _id})) + self.assertTrue(result.acknowledged) + + docs = [{"_id": i} for i in range(5)] + result = db.test.insert_many(docs) + self.assertTrue(isinstance(result, InsertManyResult)) + self.assertTrue(isinstance(result.inserted_ids, list)) + self.assertEqual(5, len(result.inserted_ids)) + for doc in docs: + _id = doc["_id"] + self.assertTrue(isinstance(_id, int)) + self.assertTrue(_id in result.inserted_ids) + self.assertEqual(1, db.test.count({"_id": _id})) + self.assertTrue(result.acknowledged) + + db = db.connection.get_database(db.name, + write_concern=WriteConcern(w=0)) + docs = [{} for _ in range(5)] + result = db.test.insert_many(docs) + self.assertTrue(isinstance(result, InsertManyResult)) + self.assertFalse(result.acknowledged) + self.assertEqual(15, db.test.count()) + def test_generator_insert(self): db = self.db db.test.remove({})