From 8d072d59e8c123215eda5d7e57820066f6abf67c Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Wed, 20 May 2015 17:35:43 -0700 Subject: [PATCH] PYTHON-880 - Backport WriteConcern class from 3.x. --- doc/api/pymongo/index.rst | 1 + doc/api/pymongo/write_concern.rst | 6 ++ pymongo/write_concern.py | 109 ++++++++++++++++++++++++++++++ test/test_write_concern.py | 81 ++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 doc/api/pymongo/write_concern.rst create mode 100644 pymongo/write_concern.py create mode 100644 test/test_write_concern.py diff --git a/doc/api/pymongo/index.rst b/doc/api/pymongo/index.rst index d187932a3..a4b39d828 100644 --- a/doc/api/pymongo/index.rst +++ b/doc/api/pymongo/index.rst @@ -44,3 +44,4 @@ Sub-modules: son_manipulator cursor_manager uri_parser + write_concern diff --git a/doc/api/pymongo/write_concern.rst b/doc/api/pymongo/write_concern.rst new file mode 100644 index 000000000..5c7b4b39f --- /dev/null +++ b/doc/api/pymongo/write_concern.rst @@ -0,0 +1,6 @@ +:mod:`write_concern` -- Tools for specifying write concern +========================================================== + +.. automodule:: pymongo.write_concern + :synopsis: Tools for specifying write concern. + :members: diff --git a/pymongo/write_concern.py b/pymongo/write_concern.py new file mode 100644 index 000000000..0e2d9242d --- /dev/null +++ b/pymongo/write_concern.py @@ -0,0 +1,109 @@ +# Copyright 2014-2015 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for working with write concerns.""" + +from pymongo.errors import ConfigurationError + +class WriteConcern(object): + """WriteConcern backport from PyMongo 3.x + + :Parameters: + - `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`. + + .. versionadded:: 2.9 + """ + + __slots__ = ("__document", "__acknowledged") + + def __init__(self, w=None, wtimeout=None, j=None, fsync=None): + self.__document = {} + self.__acknowledged = True + + if wtimeout is not None: + if not isinstance(wtimeout, (int, long)): + raise TypeError("wtimeout must be an integer") + self.__document["wtimeout"] = wtimeout + + if j is not None: + if not isinstance(j, bool): + raise TypeError("j must be True or False") + self.__document["j"] = j + + if fsync is not None: + if not isinstance(fsync, bool): + raise TypeError("fsync must be True or False") + if j and fsync: + raise ConfigurationError("Can't set both j " + "and fsync at the same time") + self.__document["fsync"] = fsync + + if self.__document and w == 0: + raise ConfigurationError("Can not use w value " + "of 0 with other options") + if w is not None: + if isinstance(w, (int, long)): + self.__acknowledged = w > 0 + elif not isinstance(w, basestring): + raise TypeError("w must be an integer or string") + self.__document["w"] = w + + @property + def document(self): + """The document representation of this write concern. + + .. note:: + :class:`WriteConcern` is immutable. Mutating the value of + :attr:`document` does not mutate this :class:`WriteConcern`. + """ + return self.__document.copy() + + @property + def acknowledged(self): + """If ``True`` write operations will wait for acknowledgement before + returning. + """ + return self.__acknowledged + + def __repr__(self): + return ("WriteConcern(%s)" % ( + ", ".join("%s=%s" % kvt for kvt in self.document.items()),)) + + def __eq__(self, other): + return self.document == other.document + + def __ne__(self, other): + return self.document != other.document + diff --git a/test/test_write_concern.py b/test/test_write_concern.py new file mode 100644 index 000000000..19c91741e --- /dev/null +++ b/test/test_write_concern.py @@ -0,0 +1,81 @@ +# Copyright 2009-2014 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the son module.""" + +import sys +import unittest + +sys.path[0:0] = [""] + +from pymongo.errors import ConfigurationError +from pymongo.write_concern import WriteConcern + +class TestWriteConcern(unittest.TestCase): + + def test_validation(self): + + # No error. + WriteConcern(w=100) + WriteConcern(w=-1) + WriteConcern(w="majority") + WriteConcern(w="foo") + WriteConcern(w=u"foo") + WriteConcern(wtimeout=1000) + WriteConcern(j=True) + WriteConcern(fsync=True) + + self.assertRaises( + TypeError, WriteConcern, w=1.0) + self.assertRaises( + TypeError, WriteConcern, j=1.0) + self.assertRaises( + TypeError, WriteConcern, fsync=1.0) + self.assertRaises( + TypeError, WriteConcern, wtimeout=1.0) + self.assertRaises( + ConfigurationError, WriteConcern, j=True, fsync=True) + self.assertRaises( + ConfigurationError, WriteConcern, w=0, j=True) + self.assertRaises( + ConfigurationError, WriteConcern, w=0, fsync=True) + self.assertRaises( + ConfigurationError, WriteConcern, w=0, wtimeout=1000) + + def test_document(self): + self.assertEqual({}, WriteConcern().document) + self.assertEqual({"w": 0}, WriteConcern(w=0).document) + self.assertEqual( + {"w": 5, "wtimeout": 1000, "j": True, "fsync": False}, + WriteConcern(w=5, wtimeout=1000, j=True, fsync=False).document) + + def test_acknowledged(self): + self.assertTrue(WriteConcern().acknowledged) + self.assertTrue(WriteConcern(j=True, fsync=False).acknowledged) + self.assertFalse(WriteConcern(w=0).acknowledged) + self.assertFalse(WriteConcern(w=-1).acknowledged) + + def test_immutable(self): + wcn = WriteConcern() + doc = wcn.document + doc["w"] = 5 + self.assertEqual({}, wcn.document) + + def test_equality(self): + self.assertEqual(WriteConcern(), WriteConcern()) + self.assertEqual(WriteConcern(w=5), WriteConcern(w=5)) + self.assertNotEqual(WriteConcern(w=5), WriteConcern(w=1)) + +if __name__ == "__main__": + unittest.main()