New write concern API PYTHON-427

This change deprecates get|set|unset_lasterror_options,
replacing them with a write_concern attribute that can be
accessed directly. See the write_concern docstring for
an example of its use.
This commit is contained in:
behackett 2012-11-06 14:44:46 -08:00
parent 594a95740d
commit 8dd2670182
8 changed files with 164 additions and 27 deletions

View File

@ -23,12 +23,10 @@
.. autoattribute:: read_preference
.. autoattribute:: tag_sets
.. autoattribute:: secondary_acceptable_latency_ms
.. autoattribute:: write_concern
.. autoattribute:: slave_okay
.. autoattribute:: safe
.. autoattribute:: uuid_subtype
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options
.. automethod:: insert(doc_or_docs[, manipulate=True[, safe=False[, check_keys=True[, continue_on_error=False[, **kwargs]]]]])
.. automethod:: save(to_save[, manipulate=True[, safe=False[, check_keys=True[, **kwargs]]]])
.. automethod:: update(spec, document[, upsert=False[, manipulate=False[, safe=False[, multi=False[, check_keys=True[, **kwargs]]]]]])
@ -51,4 +49,7 @@
.. automethod:: map_reduce
.. automethod:: inline_map_reduce
.. automethod:: find_and_modify
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options

View File

@ -28,12 +28,10 @@
.. autoattribute:: read_preference
.. autoattribute:: tag_sets
.. autoattribute:: secondary_acceptable_latency_ms
.. autoattribute:: write_concern
.. autoattribute:: slave_okay
.. autoattribute:: safe
.. autoattribute:: is_locked
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options
.. automethod:: database_names
.. automethod:: drop_database
.. automethod:: copy_database(from_name, to_name[, from_host=None[, username=None[, password=None]]])
@ -45,3 +43,6 @@
.. automethod:: set_cursor_manager
.. automethod:: fsync
.. automethod:: unlock
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options

View File

@ -21,6 +21,7 @@
.. autoattribute:: read_preference
.. autoattribute:: tag_sets
.. autoattribute:: secondary_acceptable_latency_ms
.. autoattribute:: write_concern
.. autoattribute:: slave_okay
.. autoattribute:: safe
.. automethod:: get_lasterror_options

View File

@ -29,11 +29,12 @@
.. autoattribute:: read_preference
.. autoattribute:: tag_sets
.. autoattribute:: secondary_acceptable_latency_ms
.. autoattribute:: write_concern
.. autoattribute:: safe
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options
.. automethod:: database_names
.. automethod:: drop_database
.. automethod:: copy_database(from_name, to_name[, from_host=None[, username=None[, password=None]]])
.. automethod:: close_cursor
.. automethod:: get_lasterror_options
.. automethod:: set_lasterror_options
.. automethod:: unset_lasterror_options

View File

@ -88,7 +88,7 @@ class Collection(common.BaseObject):
secondary_acceptable_latency_ms=(
database.secondary_acceptable_latency_ms),
safe=database.safe,
**(database.get_lasterror_options()))
**database.write_concern)
if not isinstance(name, basestring):
raise TypeError("name must be an instance "

View File

@ -182,6 +182,22 @@ SAFE_OPTIONS = frozenset([
])
class WriteConcern(dict):
def __init__(self, *args, **kwargs):
"""A subclass of dict that overrides __setitem__ to
validate write concern options.
"""
super(WriteConcern, self).__init__(*args, **kwargs)
def __setitem__(self, key, value):
if key not in SAFE_OPTIONS:
raise ConfigurationError("%s is not a valid write "
"concern option." % (key,))
key, value = validate(key, value)
super(WriteConcern, self).__setitem__(key, value)
class BaseObject(object):
"""A base class that provides attributes and methods common
to multiple pymongo classes.
@ -196,7 +212,7 @@ class BaseObject(object):
self.__tag_sets = [{}]
self.__secondary_acceptable_latency_ms = 15
self.__safe = None
self.__safe_opts = {}
self.__write_concern = WriteConcern()
self.__set_options(options)
if (self.__read_pref == ReadPreference.PRIMARY
and self.__tag_sets != [{}]
@ -212,16 +228,14 @@ class BaseObject(object):
"but write concerns have been set making safe True. "
"Please set safe to True.", UserWarning)
def __set_safe_option(self, option, value, check=False):
def __set_safe_option(self, option, value):
"""Validates and sets getlasterror options for this
object (Connection, Database, Collection, etc.)
"""
if value is None:
self.__safe_opts.pop(option, None)
self.__write_concern.pop(option, None)
else:
if check:
option, value = validate(option, value)
self.__safe_opts[option] = value
self.__write_concern[option] = value
self.__safe = True
def __set_options(self, options):
@ -247,6 +261,47 @@ class BaseObject(object):
else:
self.__set_safe_option(option, value)
def __set_write_concern(self, value):
"""Property setter for write_concern."""
if not isinstance(value, dict):
raise ConfigurationError("write_concern must be an "
"instance of dict or a subclass.")
# Make a copy here to avoid users accidentally setting the
# same dict on multiple instances.
wc = WriteConcern()
for k, v in value.iteritems():
# Make sure we validate each option.
wc[k] = v
self.__write_concern = wc
def __get_write_concern(self):
"""The write concern for this instance.
Supports dict style access for getting/setting write concern
options. Valid options include w=<int/string>, wtimeout=<int>,
j=<bool>, fsync=<bool>.
>>> c.write_concern
{}
>>> c.write_concern = {'w': 2, 'wtimeout': 1000}
>>> c.write_concern
{'wtimeout': 1000, 'w': 2}
>>> c.write_concern['j'] = True
>>> c.write_concern
{'wtimeout': 1000, 'j': True, 'w': 2}
>>> c.write_concern = {'j': True}
>>> c.write_concern
{'j': True}
.. note:: Accessing :attr:`write_concern` returns its value
(a subclass of :class:`dict`), not a copy.
"""
# To support dict style access we have to return the actual
# WriteConcern here, not a copy.
return self.__write_concern
write_concern = property(__get_write_concern, __set_write_concern)
def __get_slave_okay(self):
"""DEPRECATED. Use `read_preference` instead.
@ -334,30 +389,43 @@ class BaseObject(object):
safe = property(__get_safe, __set_safe)
def get_lasterror_options(self):
"""Returns a dict of the getlasterror options set
on this instance.
"""DEPRECATED: Use :attr:`write_concern` instead.
Returns a dict of the getlasterror options set on this instance.
.. versionchanged:: 2.3+
Deprecated get_lasterror_options.
.. versionadded:: 2.0
"""
return self.__safe_opts.copy()
warnings.warn("get_lasterror_options is deprecated. Please use "
"write_concern instead.", DeprecationWarning)
return self.__write_concern.copy()
def set_lasterror_options(self, **kwargs):
"""Set getlasterror options for this instance.
"""DEPRECATED: Use :attr:`write_concern` instead.
Valid options include j=<bool>, w=<int>, wtimeout=<int>,
Set getlasterror options for this instance.
Valid options include j=<bool>, w=<int/string>, wtimeout=<int>,
and fsync=<bool>. Implies safe=True.
:Parameters:
- `**kwargs`: Options should be passed as keyword
arguments (e.g. w=2, fsync=True)
.. versionchanged:: 2.3+
Deprecated set_lasterror_options.
.. versionadded:: 2.0
"""
warnings.warn("set_lasterror_options is deprecated. Please use "
"write_concern instead.", DeprecationWarning)
for key, value in kwargs.iteritems():
self.__set_safe_option(key, value, check=True)
self.__set_safe_option(key, value)
def unset_lasterror_options(self, *options):
"""Unset getlasterror options for this instance.
"""DEPRECATED: Use :attr:`write_concern` instead.
Unset getlasterror options for this instance.
If no options are passed unsets all getlasterror options.
This does not set `safe` to False.
@ -365,13 +433,17 @@ class BaseObject(object):
:Parameters:
- `*options`: The list of options to unset.
.. versionchanged:: 2.3+
Deprecated unset_lasterror_options.
.. versionadded:: 2.0
"""
warnings.warn("unset_lasterror_options is deprecated. Please use "
"write_concern instead.", DeprecationWarning)
if len(options):
for option in options:
self.__safe_opts.pop(option, None)
self.__write_concern.pop(option, None)
else:
self.__safe_opts = {}
self.__write_concern = WriteConcern()
def _get_safe_and_lasterror_options(self, safe=None, **options):
"""Get the current safe mode and any getLastError options.
@ -392,5 +464,5 @@ class BaseObject(object):
if safe or options:
safe = True
if not options:
options.update(self.get_lasterror_options())
options.update(self.__write_concern)
return safe, options

View File

@ -67,7 +67,7 @@ class Database(common.BaseObject):
secondary_acceptable_latency_ms=(
connection.secondary_acceptable_latency_ms),
safe=connection.safe,
**(connection.get_lasterror_options()))
**connection.write_concern)
if not isinstance(name, basestring):
raise TypeError("name must be an instance "

View File

@ -15,9 +15,13 @@
"""Test the pymongo common module."""
import os
import sys
import unittest
import warnings
sys.path[0:0] = [""]
from bson.son import SON
from pymongo.connection import Connection
from pymongo.errors import ConfigurationError, OperationFailure
from test.utils import drop_collections
@ -106,14 +110,17 @@ class TestCommon(unittest.TestCase):
self.assertTrue(c.safe)
d = {'w': 1, 'wtimeout': 300, 'fsync': True, 'j': True}
self.assertEqual(d, c.get_lasterror_options())
self.assertEqual(d, c.write_concern)
db = c.test
self.assertTrue(db.slave_okay)
self.assertTrue(db.safe)
self.assertEqual(d, db.get_lasterror_options())
self.assertEqual(d, db.write_concern)
coll = db.test
self.assertTrue(coll.slave_okay)
self.assertTrue(coll.safe)
self.assertEqual(d, coll.get_lasterror_options())
self.assertEqual(d, coll.write_concern)
cursor = coll.find()
self.assertTrue(cursor._Cursor__slave_okay)
cursor = coll.find(slave_okay=False)
@ -127,14 +134,17 @@ class TestCommon(unittest.TestCase):
c.slave_okay = False
self.assertFalse(c.slave_okay)
self.assertEqual({}, c.get_lasterror_options())
self.assertEqual({}, c.write_concern)
db = c.test
self.assertFalse(db.slave_okay)
self.assertFalse(db.safe)
self.assertEqual({}, db.get_lasterror_options())
self.assertEqual({}, db.write_concern)
coll = db.test
self.assertFalse(coll.slave_okay)
self.assertFalse(coll.safe)
self.assertEqual({}, coll.get_lasterror_options())
self.assertEqual({}, coll.write_concern)
cursor = coll.find()
self.assertFalse(cursor._Cursor__slave_okay)
cursor = coll.find(slave_okay=True)
@ -142,15 +152,21 @@ class TestCommon(unittest.TestCase):
coll.set_lasterror_options(j=True)
self.assertEqual({'j': True}, coll.get_lasterror_options())
self.assertEqual({'j': True}, coll.write_concern)
self.assertEqual({}, db.get_lasterror_options())
self.assertEqual({}, db.write_concern)
self.assertFalse(db.safe)
self.assertEqual({}, c.get_lasterror_options())
self.assertEqual({}, c.write_concern)
self.assertFalse(c.safe)
db.set_lasterror_options(w='majority')
self.assertEqual({'j': True}, coll.get_lasterror_options())
self.assertEqual({'j': True}, coll.write_concern)
self.assertEqual({'w': 'majority'}, db.get_lasterror_options())
self.assertEqual({'w': 'majority'}, db.write_concern)
self.assertEqual({}, c.get_lasterror_options())
self.assertEqual({}, c.write_concern)
self.assertFalse(c.safe)
db.slave_okay = True
self.assertTrue(db.slave_okay)
@ -179,6 +195,51 @@ class TestCommon(unittest.TestCase):
warnings.resetwarnings()
def test_write_concern(self):
c = Connection(pair)
self.assertEqual({}, c.write_concern)
wc = {'w': 2, 'wtimeout': 1000}
c.write_concern = wc
self.assertEqual(wc, c.write_concern)
wc = {'w': 3, 'wtimeout': 1000}
c.write_concern['w'] = 3
self.assertEqual(wc, c.write_concern)
wc = {'w': 3}
del c.write_concern['wtimeout']
self.assertEqual(wc, c.write_concern)
wc = {'w': 3, 'wtimeout': 1000}
c = Connection(w=3, wtimeout=1000)
self.assertEqual(wc, c.write_concern)
wc = {'w': 2, 'wtimeout': 1000}
c.write_concern = wc
self.assertEqual(wc, c.write_concern)
db = c.test
self.assertEqual(wc, db.write_concern)
coll = db.test
self.assertEqual(wc, coll.write_concern)
coll.write_concern = {'j': True}
self.assertEqual({'j': True}, coll.write_concern)
self.assertEqual(wc, db.write_concern)
wc = SON([('w', 2)])
coll.write_concern = wc
self.assertEqual(wc.to_dict(), coll.write_concern)
def f():
c.write_concern = {'foo': 'bar'}
self.assertRaises(ConfigurationError, f)
def f():
c.write_concern['foo'] = 'bar'
self.assertRaises(ConfigurationError, f)
def f():
c.write_concern = [('foo', 'bar')]
self.assertRaises(ConfigurationError, f)
if __name__ == "__main__":
unittest.main()