diff --git a/doc/api/pymongo/collection.rst b/doc/api/pymongo/collection.rst index f88105992..40a1afe61 100644 --- a/doc/api/pymongo/collection.rst +++ b/doc/api/pymongo/collection.rst @@ -36,6 +36,8 @@ .. automethod:: replace_one .. automethod:: update_one .. 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]]]]]) diff --git a/pymongo/collection.py b/pymongo/collection.py index acefa12ea..ac405fd27 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -37,7 +37,7 @@ from pymongo.helpers import _check_write_command_response, _command from pymongo.message import _INSERT, _UPDATE, _DELETE from pymongo.options import ReturnDocument, _WriteOp from pymongo.read_preferences import ReadPreference -from pymongo.results import BulkWriteResult, InsertOneResult, UpdateResult +from pymongo.results import * from pymongo.write_concern import WriteConcern @@ -832,8 +832,38 @@ class Collection(common.BaseObject): 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) - concern = kwargs or self.write_concern.document + 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): + """Internal delete helper.""" + if not isinstance(filter, collections.Mapping): + raise TypeError("filter must be a mapping type") + concern = (write_concern or self.write_concern).document safe = concern.get("w") != 0 client = self.database.connection @@ -843,7 +873,7 @@ class Collection(common.BaseObject): if concern: command['writeConcern'] = concern - docs = [SON([('q', spec_or_id), ('limit', int(not multi))])] + docs = [SON([('q', filter), ('limit', int(not multi))])] results = message._do_batched_write_command( self.database.name + '.$cmd', _DELETE, command, @@ -856,7 +886,7 @@ class Collection(common.BaseObject): else: # Legacy OP_DELETE return client._send_message( - message.delete(self.__full_name, spec_or_id, safe, + message.delete(self.__full_name, filter, safe, concern, self.codec_options, int(not multi)), safe) diff --git a/pymongo/results.py b/pymongo/results.py index 60c04e48c..5bec1135a 100644 --- a/pymongo/results.py +++ b/pymongo/results.py @@ -96,6 +96,28 @@ class UpdateResult(_WriteResult): return self.__raw_result.get("upserted") +class DeleteResult(_WriteResult): + """The return type for :meth:`~pymongo.collection.Collection.delete_one` + and :meth:`~pymongo.collection.Collection.delete_many`""" + + __slots__ = ("__raw_result", "__acknowledged") + + def __init__(self, raw_result, acknowledged): + self.__raw_result = raw_result + super(DeleteResult, self).__init__(acknowledged) + + @property + def raw_result(self): + """The raw result document returned by the server.""" + return self.__raw_result + + @property + def deleted_count(self): + """The number of documents deleted.""" + self._raise_if_unacknowledged("deleted_count") + return self.__raw_result.get("n", 0) + + class BulkWriteResult(_WriteResult): """An object wrapper for bulk API write results.""" diff --git a/test/test_collection.py b/test/test_collection.py index d93f1c1ab..365097ead 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 InsertOneResult, UpdateResult +from pymongo.results import DeleteResult, InsertOneResult, UpdateResult from pymongo.son_manipulator import SONManipulator from pymongo.write_concern import WriteConcern from test.test_client import IntegrationTest @@ -676,6 +676,55 @@ class TestCollection(IntegrationTest): self.db.test.remove() self.assertEqual(0, self.db.test.count()) + def test_delete_one(self): + self.db.test.drop() + + self.db.test.insert_one({"x": 1}) + self.db.test.insert_one({"y": 1}) + self.db.test.insert_one({"z": 1}) + + result = self.db.test.delete_one({"x": 1}) + self.assertTrue(isinstance(result, DeleteResult)) + self.assertEqual(1, result.deleted_count) + self.assertTrue(result.acknowledged) + self.assertEqual(2, self.db.test.count()) + + result = self.db.test.delete_one({"y": 1}) + self.assertTrue(isinstance(result, DeleteResult)) + self.assertEqual(1, result.deleted_count) + self.assertTrue(result.acknowledged) + self.assertEqual(1, self.db.test.count()) + + db = self.db.connection.get_database(self.db.name, + write_concern=WriteConcern(w=0)) + result = db.test.delete_one({"z": 1}) + self.assertTrue(isinstance(result, DeleteResult)) + self.assertRaises(InvalidOperation, lambda: result.deleted_count) + self.assertFalse(result.acknowledged) + self.assertEqual(0, self.db.test.count()) + + def test_delete_many(self): + self.db.test.drop() + + self.db.test.insert_one({"x": 1}) + self.db.test.insert_one({"x": 1}) + self.db.test.insert_one({"y": 1}) + self.db.test.insert_one({"y": 1}) + + result = self.db.test.delete_many({"x": 1}) + self.assertTrue(isinstance(result, DeleteResult)) + self.assertEqual(2, result.deleted_count) + self.assertTrue(result.acknowledged) + self.assertEqual(0, self.db.test.count({"x": 2})) + + db = self.db.connection.get_database(self.db.name, + write_concern=WriteConcern(w=0)) + result = db.test.delete_many({"y": 1}) + self.assertTrue(isinstance(result, DeleteResult)) + self.assertRaises(InvalidOperation, lambda: result.deleted_count) + self.assertFalse(result.acknowledged) + self.assertEqual(0, self.db.test.count()) + def test_find_w_fields(self): db = self.db db.test.remove({})