Skip to content

Commit 8dd2670

Browse files
committed
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.
1 parent 594a957 commit 8dd2670

File tree

8 files changed

+164
-27
lines changed

8 files changed

+164
-27
lines changed

doc/api/pymongo/collection.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@
2323
.. autoattribute:: read_preference
2424
.. autoattribute:: tag_sets
2525
.. autoattribute:: secondary_acceptable_latency_ms
26+
.. autoattribute:: write_concern
2627
.. autoattribute:: slave_okay
2728
.. autoattribute:: safe
2829
.. autoattribute:: uuid_subtype
29-
.. automethod:: get_lasterror_options
30-
.. automethod:: set_lasterror_options
31-
.. automethod:: unset_lasterror_options
3230
.. automethod:: insert(doc_or_docs[, manipulate=True[, safe=False[, check_keys=True[, continue_on_error=False[, **kwargs]]]]])
3331
.. automethod:: save(to_save[, manipulate=True[, safe=False[, check_keys=True[, **kwargs]]]])
3432
.. automethod:: update(spec, document[, upsert=False[, manipulate=False[, safe=False[, multi=False[, check_keys=True[, **kwargs]]]]]])
@@ -51,4 +49,7 @@
5149
.. automethod:: map_reduce
5250
.. automethod:: inline_map_reduce
5351
.. automethod:: find_and_modify
52+
.. automethod:: get_lasterror_options
53+
.. automethod:: set_lasterror_options
54+
.. automethod:: unset_lasterror_options
5455

doc/api/pymongo/connection.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,10 @@
2828
.. autoattribute:: read_preference
2929
.. autoattribute:: tag_sets
3030
.. autoattribute:: secondary_acceptable_latency_ms
31+
.. autoattribute:: write_concern
3132
.. autoattribute:: slave_okay
3233
.. autoattribute:: safe
3334
.. autoattribute:: is_locked
34-
.. automethod:: get_lasterror_options
35-
.. automethod:: set_lasterror_options
36-
.. automethod:: unset_lasterror_options
3735
.. automethod:: database_names
3836
.. automethod:: drop_database
3937
.. automethod:: copy_database(from_name, to_name[, from_host=None[, username=None[, password=None]]])
@@ -45,3 +43,6 @@
4543
.. automethod:: set_cursor_manager
4644
.. automethod:: fsync
4745
.. automethod:: unlock
46+
.. automethod:: get_lasterror_options
47+
.. automethod:: set_lasterror_options
48+
.. automethod:: unset_lasterror_options

doc/api/pymongo/database.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
.. autoattribute:: read_preference
2222
.. autoattribute:: tag_sets
2323
.. autoattribute:: secondary_acceptable_latency_ms
24+
.. autoattribute:: write_concern
2425
.. autoattribute:: slave_okay
2526
.. autoattribute:: safe
2627
.. automethod:: get_lasterror_options

doc/api/pymongo/replica_set_connection.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
.. autoattribute:: read_preference
3030
.. autoattribute:: tag_sets
3131
.. autoattribute:: secondary_acceptable_latency_ms
32+
.. autoattribute:: write_concern
3233
.. autoattribute:: safe
33-
.. automethod:: get_lasterror_options
34-
.. automethod:: set_lasterror_options
35-
.. automethod:: unset_lasterror_options
3634
.. automethod:: database_names
3735
.. automethod:: drop_database
3836
.. automethod:: copy_database(from_name, to_name[, from_host=None[, username=None[, password=None]]])
3937
.. automethod:: close_cursor
38+
.. automethod:: get_lasterror_options
39+
.. automethod:: set_lasterror_options
40+
.. automethod:: unset_lasterror_options

pymongo/collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(self, database, name, create=False, **kwargs):
8888
secondary_acceptable_latency_ms=(
8989
database.secondary_acceptable_latency_ms),
9090
safe=database.safe,
91-
**(database.get_lasterror_options()))
91+
**database.write_concern)
9292

9393
if not isinstance(name, basestring):
9494
raise TypeError("name must be an instance "

pymongo/common.py

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ def validate(option, value):
182182
])
183183

184184

185+
class WriteConcern(dict):
186+
187+
def __init__(self, *args, **kwargs):
188+
"""A subclass of dict that overrides __setitem__ to
189+
validate write concern options.
190+
"""
191+
super(WriteConcern, self).__init__(*args, **kwargs)
192+
193+
def __setitem__(self, key, value):
194+
if key not in SAFE_OPTIONS:
195+
raise ConfigurationError("%s is not a valid write "
196+
"concern option." % (key,))
197+
key, value = validate(key, value)
198+
super(WriteConcern, self).__setitem__(key, value)
199+
200+
185201
class BaseObject(object):
186202
"""A base class that provides attributes and methods common
187203
to multiple pymongo classes.
@@ -196,7 +212,7 @@ def __init__(self, **options):
196212
self.__tag_sets = [{}]
197213
self.__secondary_acceptable_latency_ms = 15
198214
self.__safe = None
199-
self.__safe_opts = {}
215+
self.__write_concern = WriteConcern()
200216
self.__set_options(options)
201217
if (self.__read_pref == ReadPreference.PRIMARY
202218
and self.__tag_sets != [{}]
@@ -212,16 +228,14 @@ def __init__(self, **options):
212228
"but write concerns have been set making safe True. "
213229
"Please set safe to True.", UserWarning)
214230

215-
def __set_safe_option(self, option, value, check=False):
231+
def __set_safe_option(self, option, value):
216232
"""Validates and sets getlasterror options for this
217233
object (Connection, Database, Collection, etc.)
218234
"""
219235
if value is None:
220-
self.__safe_opts.pop(option, None)
236+
self.__write_concern.pop(option, None)
221237
else:
222-
if check:
223-
option, value = validate(option, value)
224-
self.__safe_opts[option] = value
238+
self.__write_concern[option] = value
225239
self.__safe = True
226240

227241
def __set_options(self, options):
@@ -247,6 +261,47 @@ def __set_options(self, options):
247261
else:
248262
self.__set_safe_option(option, value)
249263

264+
def __set_write_concern(self, value):
265+
"""Property setter for write_concern."""
266+
if not isinstance(value, dict):
267+
raise ConfigurationError("write_concern must be an "
268+
"instance of dict or a subclass.")
269+
# Make a copy here to avoid users accidentally setting the
270+
# same dict on multiple instances.
271+
wc = WriteConcern()
272+
for k, v in value.iteritems():
273+
# Make sure we validate each option.
274+
wc[k] = v
275+
self.__write_concern = wc
276+
277+
def __get_write_concern(self):
278+
"""The write concern for this instance.
279+
280+
Supports dict style access for getting/setting write concern
281+
options. Valid options include w=<int/string>, wtimeout=<int>,
282+
j=<bool>, fsync=<bool>.
283+
284+
>>> c.write_concern
285+
{}
286+
>>> c.write_concern = {'w': 2, 'wtimeout': 1000}
287+
>>> c.write_concern
288+
{'wtimeout': 1000, 'w': 2}
289+
>>> c.write_concern['j'] = True
290+
>>> c.write_concern
291+
{'wtimeout': 1000, 'j': True, 'w': 2}
292+
>>> c.write_concern = {'j': True}
293+
>>> c.write_concern
294+
{'j': True}
295+
296+
.. note:: Accessing :attr:`write_concern` returns its value
297+
(a subclass of :class:`dict`), not a copy.
298+
"""
299+
# To support dict style access we have to return the actual
300+
# WriteConcern here, not a copy.
301+
return self.__write_concern
302+
303+
write_concern = property(__get_write_concern, __set_write_concern)
304+
250305
def __get_slave_okay(self):
251306
"""DEPRECATED. Use `read_preference` instead.
252307
@@ -334,44 +389,61 @@ def __set_safe(self, value):
334389
safe = property(__get_safe, __set_safe)
335390

336391
def get_lasterror_options(self):
337-
"""Returns a dict of the getlasterror options set
338-
on this instance.
392+
"""DEPRECATED: Use :attr:`write_concern` instead.
393+
394+
Returns a dict of the getlasterror options set on this instance.
339395
396+
.. versionchanged:: 2.3+
397+
Deprecated get_lasterror_options.
340398
.. versionadded:: 2.0
341399
"""
342-
return self.__safe_opts.copy()
400+
warnings.warn("get_lasterror_options is deprecated. Please use "
401+
"write_concern instead.", DeprecationWarning)
402+
return self.__write_concern.copy()
343403

344404
def set_lasterror_options(self, **kwargs):
345-
"""Set getlasterror options for this instance.
405+
"""DEPRECATED: Use :attr:`write_concern` instead.
346406
347-
Valid options include j=<bool>, w=<int>, wtimeout=<int>,
407+
Set getlasterror options for this instance.
408+
409+
Valid options include j=<bool>, w=<int/string>, wtimeout=<int>,
348410
and fsync=<bool>. Implies safe=True.
349411
350412
:Parameters:
351413
- `**kwargs`: Options should be passed as keyword
352414
arguments (e.g. w=2, fsync=True)
353415
416+
.. versionchanged:: 2.3+
417+
Deprecated set_lasterror_options.
354418
.. versionadded:: 2.0
355419
"""
420+
warnings.warn("set_lasterror_options is deprecated. Please use "
421+
"write_concern instead.", DeprecationWarning)
356422
for key, value in kwargs.iteritems():
357-
self.__set_safe_option(key, value, check=True)
423+
self.__set_safe_option(key, value)
358424

359425
def unset_lasterror_options(self, *options):
360-
"""Unset getlasterror options for this instance.
426+
"""DEPRECATED: Use :attr:`write_concern` instead.
427+
428+
Unset getlasterror options for this instance.
361429
362430
If no options are passed unsets all getlasterror options.
363431
This does not set `safe` to False.
364432
365433
:Parameters:
366434
- `*options`: The list of options to unset.
367435
436+
.. versionchanged:: 2.3+
437+
Deprecated unset_lasterror_options.
368438
.. versionadded:: 2.0
369439
"""
440+
warnings.warn("unset_lasterror_options is deprecated. Please use "
441+
"write_concern instead.", DeprecationWarning)
370442
if len(options):
371443
for option in options:
372-
self.__safe_opts.pop(option, None)
444+
self.__write_concern.pop(option, None)
373445
else:
374-
self.__safe_opts = {}
446+
self.__write_concern = WriteConcern()
375447

376448
def _get_safe_and_lasterror_options(self, safe=None, **options):
377449
"""Get the current safe mode and any getLastError options.
@@ -392,5 +464,5 @@ def _get_safe_and_lasterror_options(self, safe=None, **options):
392464
if safe or options:
393465
safe = True
394466
if not options:
395-
options.update(self.get_lasterror_options())
467+
options.update(self.__write_concern)
396468
return safe, options

pymongo/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, connection, name):
6767
secondary_acceptable_latency_ms=(
6868
connection.secondary_acceptable_latency_ms),
6969
safe=connection.safe,
70-
**(connection.get_lasterror_options()))
70+
**connection.write_concern)
7171

7272
if not isinstance(name, basestring):
7373
raise TypeError("name must be an instance "

test/test_common.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
"""Test the pymongo common module."""
1616

1717
import os
18+
import sys
1819
import unittest
1920
import warnings
2021

22+
sys.path[0:0] = [""]
23+
24+
from bson.son import SON
2125
from pymongo.connection import Connection
2226
from pymongo.errors import ConfigurationError, OperationFailure
2327
from test.utils import drop_collections
@@ -106,14 +110,17 @@ def test_baseobject(self):
106110
self.assertTrue(c.safe)
107111
d = {'w': 1, 'wtimeout': 300, 'fsync': True, 'j': True}
108112
self.assertEqual(d, c.get_lasterror_options())
113+
self.assertEqual(d, c.write_concern)
109114
db = c.test
110115
self.assertTrue(db.slave_okay)
111116
self.assertTrue(db.safe)
112117
self.assertEqual(d, db.get_lasterror_options())
118+
self.assertEqual(d, db.write_concern)
113119
coll = db.test
114120
self.assertTrue(coll.slave_okay)
115121
self.assertTrue(coll.safe)
116122
self.assertEqual(d, coll.get_lasterror_options())
123+
self.assertEqual(d, coll.write_concern)
117124
cursor = coll.find()
118125
self.assertTrue(cursor._Cursor__slave_okay)
119126
cursor = coll.find(slave_okay=False)
@@ -127,30 +134,39 @@ def test_baseobject(self):
127134
c.slave_okay = False
128135
self.assertFalse(c.slave_okay)
129136
self.assertEqual({}, c.get_lasterror_options())
137+
self.assertEqual({}, c.write_concern)
130138
db = c.test
131139
self.assertFalse(db.slave_okay)
132140
self.assertFalse(db.safe)
133141
self.assertEqual({}, db.get_lasterror_options())
142+
self.assertEqual({}, db.write_concern)
134143
coll = db.test
135144
self.assertFalse(coll.slave_okay)
136145
self.assertFalse(coll.safe)
137146
self.assertEqual({}, coll.get_lasterror_options())
147+
self.assertEqual({}, coll.write_concern)
138148
cursor = coll.find()
139149
self.assertFalse(cursor._Cursor__slave_okay)
140150
cursor = coll.find(slave_okay=True)
141151
self.assertTrue(cursor._Cursor__slave_okay)
142152

143153
coll.set_lasterror_options(j=True)
144154
self.assertEqual({'j': True}, coll.get_lasterror_options())
155+
self.assertEqual({'j': True}, coll.write_concern)
145156
self.assertEqual({}, db.get_lasterror_options())
157+
self.assertEqual({}, db.write_concern)
146158
self.assertFalse(db.safe)
147159
self.assertEqual({}, c.get_lasterror_options())
160+
self.assertEqual({}, c.write_concern)
148161
self.assertFalse(c.safe)
149162

150163
db.set_lasterror_options(w='majority')
151164
self.assertEqual({'j': True}, coll.get_lasterror_options())
165+
self.assertEqual({'j': True}, coll.write_concern)
152166
self.assertEqual({'w': 'majority'}, db.get_lasterror_options())
167+
self.assertEqual({'w': 'majority'}, db.write_concern)
153168
self.assertEqual({}, c.get_lasterror_options())
169+
self.assertEqual({}, c.write_concern)
154170
self.assertFalse(c.safe)
155171
db.slave_okay = True
156172
self.assertTrue(db.slave_okay)
@@ -179,6 +195,51 @@ def test_baseobject(self):
179195

180196
warnings.resetwarnings()
181197

198+
def test_write_concern(self):
199+
c = Connection(pair)
200+
201+
self.assertEqual({}, c.write_concern)
202+
wc = {'w': 2, 'wtimeout': 1000}
203+
c.write_concern = wc
204+
self.assertEqual(wc, c.write_concern)
205+
wc = {'w': 3, 'wtimeout': 1000}
206+
c.write_concern['w'] = 3
207+
self.assertEqual(wc, c.write_concern)
208+
wc = {'w': 3}
209+
del c.write_concern['wtimeout']
210+
self.assertEqual(wc, c.write_concern)
211+
212+
wc = {'w': 3, 'wtimeout': 1000}
213+
c = Connection(w=3, wtimeout=1000)
214+
self.assertEqual(wc, c.write_concern)
215+
wc = {'w': 2, 'wtimeout': 1000}
216+
c.write_concern = wc
217+
self.assertEqual(wc, c.write_concern)
218+
219+
db = c.test
220+
self.assertEqual(wc, db.write_concern)
221+
coll = db.test
222+
self.assertEqual(wc, coll.write_concern)
223+
coll.write_concern = {'j': True}
224+
self.assertEqual({'j': True}, coll.write_concern)
225+
self.assertEqual(wc, db.write_concern)
226+
227+
wc = SON([('w', 2)])
228+
coll.write_concern = wc
229+
self.assertEqual(wc.to_dict(), coll.write_concern)
230+
231+
def f():
232+
c.write_concern = {'foo': 'bar'}
233+
self.assertRaises(ConfigurationError, f)
234+
235+
def f():
236+
c.write_concern['foo'] = 'bar'
237+
self.assertRaises(ConfigurationError, f)
238+
239+
def f():
240+
c.write_concern = [('foo', 'bar')]
241+
self.assertRaises(ConfigurationError, f)
242+
182243

183244
if __name__ == "__main__":
184245
unittest.main()

0 commit comments

Comments
 (0)