Skip to content

Commit 4704bb8

Browse files
committed
PYTHON-677 - Switch internals to new WriteConcern class
1 parent d4a94d3 commit 4704bb8

File tree

5 files changed

+134
-82
lines changed

5 files changed

+134
-82
lines changed

pymongo/collection.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,15 +383,16 @@ def gen():
383383
ids.append(doc.get('_id'))
384384
yield doc
385385

386-
safe, options = self._get_write_mode(kwargs)
386+
concern = kwargs or self.write_concern
387+
safe = concern.get("w") != 0
387388

388389
if client.max_wire_version > 1 and safe:
389390
# Insert command
390391
command = SON([('insert', self.name),
391392
('ordered', not continue_on_error)])
392393

393-
if options:
394-
command['writeConcern'] = options
394+
if concern:
395+
command['writeConcern'] = concern
395396

396397
results = message._do_batched_write_command(
397398
self.database.name + ".$cmd", _INSERT, command,
@@ -400,7 +401,7 @@ def gen():
400401
else:
401402
# Legacy batched OP_INSERT
402403
message._do_batched_insert(self.__full_name, gen(), check_keys,
403-
safe, options, continue_on_error,
404+
safe, concern, continue_on_error,
404405
self.uuid_subtype, client)
405406

406407
if return_one:
@@ -515,7 +516,8 @@ def update(self, spec, document, upsert=False, manipulate=False,
515516
if manipulate:
516517
document = self.__database._fix_incoming(document, self)
517518

518-
safe, options = self._get_write_mode(kwargs)
519+
concern = kwargs or self.write_concern
520+
safe = concern.get("w") != 0
519521

520522
if document:
521523
# If a top level key begins with '$' this is a modify operation
@@ -530,8 +532,8 @@ def update(self, spec, document, upsert=False, manipulate=False,
530532
if client.max_wire_version > 1 and safe:
531533
# Update command
532534
command = SON([('update', self.name)])
533-
if options:
534-
command['writeConcern'] = options
535+
if concern:
536+
command['writeConcern'] = concern
535537

536538
docs = [SON([('q', spec), ('u', document),
537539
('multi', multi), ('upsert', upsert)])]
@@ -554,7 +556,7 @@ def update(self, spec, document, upsert=False, manipulate=False,
554556
# Legacy OP_UPDATE
555557
return client._send_message(
556558
message.update(self.__full_name, upsert, multi,
557-
spec, document, safe, options,
559+
spec, document, safe, concern,
558560
check_keys, self.uuid_subtype), safe)
559561

560562
def drop(self):
@@ -640,7 +642,8 @@ def remove(self, spec_or_id=None, multi=True, **kwargs):
640642
if not isinstance(spec_or_id, dict):
641643
spec_or_id = {"_id": spec_or_id}
642644

643-
safe, options = self._get_write_mode(kwargs)
645+
concern = kwargs or self.write_concern
646+
safe = concern.get("w") != 0
644647

645648
client = self.database.connection
646649

@@ -649,8 +652,8 @@ def remove(self, spec_or_id=None, multi=True, **kwargs):
649652
if client.max_wire_version > 1 and safe:
650653
# Delete command
651654
command = SON([('delete', self.name)])
652-
if options:
653-
command['writeConcern'] = options
655+
if concern:
656+
command['writeConcern'] = concern
654657

655658
docs = [SON([('q', spec_or_id), ('limit', int(not multi))])]
656659

@@ -666,7 +669,7 @@ def remove(self, spec_or_id=None, multi=True, **kwargs):
666669
# Legacy OP_DELETE
667670
return client._send_message(
668671
message.delete(self.__full_name, spec_or_id, safe,
669-
options, self.uuid_subtype, int(not multi)), safe)
672+
concern, self.uuid_subtype, int(not multi)), safe)
670673

671674
def find_one(self, spec_or_id=None, *args, **kwargs):
672675
"""Get a single document from the database.

pymongo/common.py

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from pymongo.auth import MECHANISMS
2121
from pymongo.errors import ConfigurationError
22+
from pymongo.write_concern import WriteConcern
2223
from bson.binary import (OLD_UUID_SUBTYPE, UUID_SUBTYPE,
2324
JAVA_LEGACY, CSHARP_LEGACY)
2425

@@ -245,8 +246,7 @@ def validate_read_preference_tags(name, value):
245246
return [tags]
246247

247248

248-
249-
# jounal is an alias for j,
249+
# journal is an alias for j,
250250
# wtimeoutms is an alias for wtimeout,
251251
VALIDATORS = {
252252
'replicaset': validate_basestring,
@@ -310,22 +310,6 @@ def validate(option, value):
310310
])
311311

312312

313-
class WriteConcern(dict):
314-
315-
def __init__(self, *args, **kwargs):
316-
"""A subclass of dict that overrides __setitem__ to
317-
validate write concern options.
318-
"""
319-
super(WriteConcern, self).__init__(*args, **kwargs)
320-
321-
def __setitem__(self, key, value):
322-
if key not in WRITE_CONCERN_OPTIONS:
323-
raise ConfigurationError("%s is not a valid write "
324-
"concern option." % (key,))
325-
key, value = validate(key, value)
326-
super(WriteConcern, self).__setitem__(key, value)
327-
328-
329313
class BaseObject(object):
330314
"""A base class that provides attributes and methods common
331315
to multiple pymongo classes.
@@ -337,20 +321,12 @@ def __init__(self, **options):
337321

338322
self.__read_pref = read_preferences.ReadPreference.PRIMARY
339323
self.__uuid_subtype = OLD_UUID_SUBTYPE
340-
self.__write_concern = WriteConcern()
324+
self.__write_concern = None
341325
self.__set_options(options)
342326

343-
def __set_write_concern_option(self, option, value):
344-
"""Validates and sets getlasterror options for this
345-
object (MongoClient, Database, Collection, etc.)
346-
"""
347-
if value is None:
348-
self.__write_concern.pop(option, None)
349-
else:
350-
self.__write_concern[option] = value
351-
352327
def __set_options(self, options):
353328
"""Validates and sets all options passed to this object."""
329+
wc_opts = {}
354330
for option, value in options.iteritems():
355331
if option == 'read_preference':
356332
self.__read_pref = validate_read_preference(option, value)
@@ -365,25 +341,20 @@ def __set_options(self, options):
365341
elif option == 'uuidrepresentation':
366342
self.__uuid_subtype = validate_uuid_subtype(option, value)
367343
elif option in WRITE_CONCERN_OPTIONS:
368-
if option == 'journal':
369-
self.__set_write_concern_option('j', value)
370-
elif option == 'wtimeoutms':
371-
self.__set_write_concern_option('wtimeout', value)
344+
if option == "journal":
345+
wc_opts["j"] = value
346+
elif option == "wtimeoutms":
347+
wc_opts["wtimeout"] = value
372348
else:
373-
self.__set_write_concern_option(option, value)
349+
wc_opts[option] = value
350+
self.__write_concern = WriteConcern(**wc_opts)
374351

375352
def __set_write_concern(self, value):
376353
"""Property setter for write_concern."""
377354
if not isinstance(value, dict):
378355
raise ConfigurationError("write_concern must be an "
379356
"instance of dict or a subclass.")
380-
# Make a copy here to avoid users accidentally setting the
381-
# same dict on multiple instances.
382-
wc = WriteConcern()
383-
for k, v in value.iteritems():
384-
# Make sure we validate each option.
385-
wc[k] = v
386-
self.__write_concern = wc
357+
self.__write_concern = WriteConcern(**value)
387358

388359
def __get_write_concern(self):
389360
"""The default write concern for this instance.
@@ -435,7 +406,7 @@ def __get_write_concern(self):
435406
"""
436407
# To support dict style access we have to return the actual
437408
# WriteConcern here, not a copy.
438-
return self.__write_concern
409+
return self.__write_concern.document
439410

440411
write_concern = property(__get_write_concern, __set_write_concern)
441412

@@ -477,22 +448,7 @@ def _get_wc_override(self):
477448
We don't want to override user write concern options if write concern
478449
is already enabled.
479450
"""
480-
if self.__write_concern.get('w') != 0:
451+
if self.__write_concern.acknowledged:
481452
return {}
482453
return {'w': 1}
483454

484-
def _get_write_mode(self, options):
485-
"""Get the current write mode.
486-
487-
Determines if the current write is acknowledged or not based on the
488-
inherited write_concern values, or passed options.
489-
490-
:Parameters:
491-
- `options`: overriding write concern options.
492-
493-
.. versionadded:: 2.3
494-
"""
495-
write_concern = options or self.__write_concern
496-
if write_concern.get('w') == 0:
497-
return False, {}
498-
return True, write_concern

pymongo/write_concern.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2014 MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tools for working with write concerns."""
16+
17+
from bson.py3compat import integer_types, string_type
18+
from pymongo.errors import ConfigurationError
19+
20+
class WriteConcern(object):
21+
"""WriteConcern
22+
23+
:Parameters:
24+
- `w`: (integer or string) Used with replication, write operations
25+
will block until they have been replicated to the specified number
26+
or tagged set of servers. `w=<integer>` always includes the replica
27+
set primary (e.g. w=3 means write to the primary and wait until
28+
replicated to **two** secondaries). **w=0 disables acknowledgement
29+
of write operations and can not be used with other write concern
30+
options.**
31+
- `wtimeout`: (integer) Used in conjunction with `w`. Specify a value
32+
in milliseconds to control how long to wait for write propagation
33+
to complete. If replication does not complete in the given
34+
timeframe, a timeout exception is raised.
35+
- `j`: If ``True`` block until write operations have been committed
36+
to the journal. Cannot be used in combination with `fsync`. Prior
37+
to MongoDB 2.6 this option was ignored if the server was running
38+
without journaling. Starting with MongoDB 2.6 write operations will
39+
fail with an exception if this option is used when the server is
40+
running without journaling.
41+
- `fsync`: If ``True`` and the server is running without journaling,
42+
blocks until the server has synced all data files to disk. If the
43+
server is running with journaling, this acts the same as the `j`
44+
option, blocking until write operations have been committed to the
45+
journal. Cannot be used in combination with `j`.
46+
"""
47+
48+
__slots__ = ("__document", "__acknowledged")
49+
50+
def __init__(self, w=None, wtimeout=None, j=None, fsync=None):
51+
self.__document = {}
52+
self.__acknowledged = True
53+
54+
if wtimeout is not None:
55+
if not isinstance(wtimeout, integer_types):
56+
raise ConfigurationError("wtimeout must be an integer")
57+
self.__document["wtimeout"] = wtimeout
58+
59+
if j is not None:
60+
if not isinstance(j, bool):
61+
raise ConfigurationError("j must be True or False")
62+
self.__document["j"] = j
63+
64+
if fsync is not None:
65+
if not isinstance(fsync, bool):
66+
raise ConfigurationError("fsync must be True or False")
67+
if j and fsync:
68+
raise ConfigurationError("Can't set both j "
69+
"and fsync at the same time")
70+
self.__document["fsync"] = fsync
71+
72+
if self.__document and w == 0:
73+
raise ConfigurationError("Can not use w value "
74+
"of 0 with other options")
75+
if w is not None:
76+
if isinstance(w, integer_types):
77+
self.__acknowledged = w > 0
78+
elif not isinstance(w, string_type):
79+
raise ConfigurationError("w must be an integer or string")
80+
self.__document["w"] = w
81+
82+
@property
83+
def document(self):
84+
"""The document representation of this write concern.
85+
"""
86+
return self.__document
87+
88+
@property
89+
def acknowledged(self):
90+
"""If ``True`` write operations will wait for acknowledgement before
91+
returning.
92+
"""
93+
return self.__acknowledged
94+
95+
# This doesn't keep the options in order. Do we care?
96+
def __repr__(self):
97+
return ("WriteConcern(%s)" % (
98+
", ".join("%s=%s" % kvt for kvt in self.document.items()),))
99+
100+
def __eq__(self, other):
101+
return self.document == other.document
102+
103+
def __ne__(self, other):
104+
return self.document != other.document
105+

test/test_collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ def test_insert_multiple_with_duplicate(self):
833833
)
834834

835835
db.drop_collection("test")
836-
db.write_concern['w'] = 0
836+
db.write_concern = {"w": 0}
837837
db.test.ensure_index([('i', ASCENDING)], unique=True)
838838

839839
# No error

test/test_common.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,18 +217,6 @@ def test_write_concern(self):
217217
coll.write_concern = wc
218218
self.assertEqual(wc.to_dict(), coll.write_concern)
219219

220-
def f():
221-
c.write_concern = {'foo': 'bar'}
222-
self.assertRaises(ConfigurationError, f)
223-
224-
def f():
225-
c.write_concern['foo'] = 'bar'
226-
self.assertRaises(ConfigurationError, f)
227-
228-
def f():
229-
c.write_concern = [('foo', 'bar')]
230-
self.assertRaises(ConfigurationError, f)
231-
232220
def test_mongo_client(self):
233221
m = MongoClient(pair, w=0)
234222
coll = m.pymongo_test.write_concern_test

0 commit comments

Comments
 (0)