Skip to content

Commit b36a434

Browse files
author
Luke Lovett
committed
PYTHON-1101 - Support sending writeConcern for commands that write.
1 parent b057cd4 commit b36a434

File tree

11 files changed

+270
-43
lines changed

11 files changed

+270
-43
lines changed

pymongo/collection.py

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ def _socket_for_writes(self):
178178
def _command(self, sock_info, command, slave_ok=False,
179179
read_preference=None,
180180
codec_options=None, check=True, allowable_errors=None,
181-
read_concern=DEFAULT_READ_CONCERN):
181+
read_concern=DEFAULT_READ_CONCERN,
182+
write_concern=None,
183+
parse_write_concern_error=False):
182184
"""Internal command helper.
183185
184186
:Parameters:
@@ -191,21 +193,29 @@ def _command(self, sock_info, command, slave_ok=False,
191193
- `allowable_errors`: errors to ignore if `check` is True
192194
- `read_concern` (optional) - An instance of
193195
:class:`~pymongo.read_concern.ReadConcern`.
196+
- `write_concern`: An instance of
197+
:class:`~pymongo.write_concern.WriteConcern`. This option is only
198+
valid for MongoDB 3.4 and above.
199+
- `parse_write_concern_error` (optional): Whether to parse a
200+
``writeConcernError`` field in the command response.
194201
195202
:Returns:
196203
197204
# todo: don't return address
198205
199206
(result document, address of server the command was run on)
200207
"""
201-
return sock_info.command(self.__database.name,
202-
command,
203-
slave_ok,
204-
read_preference or self.read_preference,
205-
codec_options or self.codec_options,
206-
check,
207-
allowable_errors,
208-
read_concern=read_concern)
208+
return sock_info.command(
209+
self.__database.name,
210+
command,
211+
slave_ok,
212+
read_preference or self.read_preference,
213+
codec_options or self.codec_options,
214+
check,
215+
allowable_errors,
216+
read_concern=read_concern,
217+
write_concern=write_concern,
218+
parse_write_concern_error=parse_write_concern_error)
209219

210220
def __create(self, options):
211221
"""Sends a create command with the given options.
@@ -217,7 +227,9 @@ def __create(self, options):
217227
cmd.update(options)
218228
with self._socket_for_writes() as sock_info:
219229
self._command(
220-
sock_info, cmd, read_preference=ReadPreference.PRIMARY)
230+
sock_info, cmd, read_preference=ReadPreference.PRIMARY,
231+
write_concern=self.write_concern,
232+
parse_write_concern_error=True)
221233

222234
def __getattr__(self, name):
223235
"""Get a sub-collection of this collection by name.
@@ -1268,6 +1280,13 @@ def create_indexes(self, indexes):
12681280
introduced in MongoDB **2.6** and cannot be used with earlier
12691281
versions.
12701282
1283+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1284+
this collection is automatically applied to this operation when using
1285+
MongoDB >= 3.4.
1286+
1287+
.. versionchanged:: 3.4
1288+
Apply this collection's write concern automatically to this operation
1289+
when connected to MongoDB >= 3.4.
12711290
.. versionadded:: 3.0
12721291
"""
12731292
if not isinstance(indexes, list):
@@ -1285,7 +1304,9 @@ def gen_indexes():
12851304
('indexes', list(gen_indexes()))])
12861305
with self._socket_for_writes() as sock_info:
12871306
self._command(
1288-
sock_info, cmd, read_preference=ReadPreference.PRIMARY)
1307+
sock_info, cmd, read_preference=ReadPreference.PRIMARY,
1308+
write_concern=self.write_concern,
1309+
parse_write_concern_error=True)
12891310
return names
12901311

12911312
def __create_index(self, keys, index_options):
@@ -1303,7 +1324,9 @@ def __create_index(self, keys, index_options):
13031324
cmd = SON([('createIndexes', self.name), ('indexes', [index])])
13041325
try:
13051326
self._command(
1306-
sock_info, cmd, read_preference=ReadPreference.PRIMARY)
1327+
sock_info, cmd, read_preference=ReadPreference.PRIMARY,
1328+
write_concern=self.write_concern,
1329+
parse_write_concern_error=True)
13071330
except OperationFailure as exc:
13081331
if exc.code in common.COMMAND_NOT_FOUND_CODES:
13091332
index["ns"] = self.__full_name
@@ -1374,13 +1397,20 @@ def create_index(self, keys, **kwargs):
13741397
13751398
.. note:: `partialFilterExpression` requires server version **>= 3.2**
13761399
1400+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1401+
this collection is automatically applied to this operation when using
1402+
MongoDB >= 3.4.
1403+
13771404
:Parameters:
13781405
- `keys`: a single key or a list of (key, direction)
13791406
pairs specifying the index to create
13801407
- `**kwargs` (optional): any additional index creation
13811408
options (see the above list) should be passed as keyword
13821409
arguments
13831410
1411+
.. versionchanged:: 3.4
1412+
Apply this collection's write concern automatically to this operation
1413+
when connected to MongoDB >= 3.4.
13841414
.. versionchanged:: 3.2
13851415
Added partialFilterExpression to support partial indexes.
13861416
.. versionchanged:: 3.0
@@ -1436,6 +1466,15 @@ def drop_indexes(self):
14361466
14371467
Can be used on non-existant collections or collections with no indexes.
14381468
Raises OperationFailure on an error.
1469+
1470+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1471+
this collection is automatically applied to this operation when using
1472+
MongoDB >= 3.4.
1473+
1474+
.. versionchanged:: 3.4
1475+
Apply this collection's write concern automatically to this operation
1476+
when connected to MongoDB >= 3.4.
1477+
14391478
"""
14401479
self.__database.client._purge_index(self.__database.name, self.__name)
14411480
self.drop_index("*")
@@ -1459,6 +1498,15 @@ def drop_index(self, index_or_name):
14591498
14601499
:Parameters:
14611500
- `index_or_name`: index (or name of index) to drop
1501+
1502+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1503+
this collection is automatically applied to this operation when using
1504+
MongoDB >= 3.4.
1505+
1506+
.. versionchanged:: 3.4
1507+
Apply this collection's write concern automatically to this operation
1508+
when connected to MongoDB >= 3.4.
1509+
14621510
"""
14631511
name = index_or_name
14641512
if isinstance(index_or_name, list):
@@ -1474,19 +1522,32 @@ def drop_index(self, index_or_name):
14741522
self._command(sock_info,
14751523
cmd,
14761524
read_preference=ReadPreference.PRIMARY,
1477-
allowable_errors=["ns not found"])
1525+
allowable_errors=["ns not found"],
1526+
write_concern=self.write_concern,
1527+
parse_write_concern_error=True)
14781528

14791529
def reindex(self):
14801530
"""Rebuilds all indexes on this collection.
14811531
14821532
.. warning:: reindex blocks all other operations (indexes
14831533
are built in the foreground) and will be slow for large
14841534
collections.
1535+
1536+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1537+
this collection is automatically applied to this operation when using
1538+
MongoDB >= 3.4.
1539+
1540+
.. versionchanged:: 3.4
1541+
Apply this collection's write concern automatically to this operation
1542+
when connected to MongoDB >= 3.4.
1543+
14851544
"""
14861545
cmd = SON([("reIndex", self.__name)])
14871546
with self._socket_for_writes() as sock_info:
14881547
return self._command(
1489-
sock_info, cmd, read_preference=ReadPreference.PRIMARY)
1548+
sock_info, cmd, read_preference=ReadPreference.PRIMARY,
1549+
write_concern=self.write_concern,
1550+
parse_write_concern_error=True)
14901551

14911552
def list_indexes(self):
14921553
"""Get a cursor over the index documents for this collection.
@@ -1625,6 +1686,10 @@ def aggregate(self, pipeline, **kwargs):
16251686
use :meth:`~pymongo.database.Database.command` instead. An
16261687
example is included in the :ref:`aggregate-examples` documentation.
16271688
1689+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1690+
this collection is automatically applied to this operation when using
1691+
MongoDB >= 3.4.
1692+
16281693
:Parameters:
16291694
- `pipeline`: a list of aggregation pipeline stages
16301695
- `**kwargs` (optional): See list of options above.
@@ -1633,6 +1698,9 @@ def aggregate(self, pipeline, **kwargs):
16331698
A :class:`~pymongo.command_cursor.CommandCursor` over the result
16341699
set.
16351700
1701+
.. versionchanged:: 3.4
1702+
Apply this collection's write concern automatically to this operation
1703+
when connected to MongoDB >= 3.4.
16361704
.. versionchanged:: 3.0
16371705
The :meth:`aggregate` method always returns a CommandCursor. The
16381706
pipeline argument must be a list.
@@ -1674,18 +1742,25 @@ def aggregate(self, pipeline, **kwargs):
16741742
if batch_size is not None:
16751743
kwargs["cursor"]["batchSize"] = batch_size
16761744

1745+
dollar_out = pipeline and '$out' in pipeline[-1]
1746+
if (sock_info.max_wire_version >= 5 and dollar_out and
1747+
self.write_concern):
1748+
cmd['writeConcern'] = self.write_concern.document
1749+
16771750
cmd.update(kwargs)
16781751

16791752
# Apply this Collection's read concern if $out is not in the
16801753
# pipeline.
16811754
if sock_info.max_wire_version >= 4 and 'readConcern' not in cmd:
1682-
if pipeline and '$out' in pipeline[-1]:
1683-
result = self._command(sock_info, cmd, slave_ok)
1755+
if dollar_out:
1756+
result = self._command(sock_info, cmd, slave_ok,
1757+
parse_write_concern_error=True)
16841758
else:
16851759
result = self._command(sock_info, cmd, slave_ok,
16861760
read_concern=self.read_concern)
16871761
else:
1688-
result = self._command(sock_info, cmd, slave_ok)
1762+
result = self._command(sock_info, cmd, slave_ok,
1763+
parse_write_concern_error=dollar_out)
16891764

16901765
if "cursor" in result:
16911766
cursor = result["cursor"]
@@ -1764,6 +1839,15 @@ def rename(self, new_name, **kwargs):
17641839
- `**kwargs` (optional): additional arguments to the rename command
17651840
may be passed as keyword arguments to this helper method
17661841
(i.e. ``dropTarget=True``)
1842+
1843+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1844+
this collection is automatically applied to this operation when using
1845+
MongoDB >= 3.4.
1846+
1847+
.. versionchanged:: 3.4
1848+
Apply this collection's write concern automatically to this operation
1849+
when connected to MongoDB >= 3.4.
1850+
17671851
"""
17681852
if not isinstance(new_name, string_type):
17691853
raise TypeError("new_name must be an "
@@ -1778,9 +1862,11 @@ def rename(self, new_name, **kwargs):
17781862

17791863
new_name = "%s.%s" % (self.__database.name, new_name)
17801864
cmd = SON([("renameCollection", self.__full_name), ("to", new_name)])
1781-
cmd.update(kwargs)
17821865
with self._socket_for_writes() as sock_info:
1783-
sock_info.command('admin', cmd)
1866+
if sock_info.max_wire_version >= 5 and self.write_concern:
1867+
cmd['writeConcern'] = self.write_concern.document
1868+
cmd.update(kwargs)
1869+
sock_info.command('admin', cmd, parse_write_concern_error=True)
17841870

17851871
def distinct(self, key, filter=None, **kwargs):
17861872
"""Get a list of distinct values for `key` among all documents
@@ -1848,6 +1934,14 @@ def map_reduce(self, map, reduce, out, full_response=False, **kwargs):
18481934
mapReduce on a secondary use the :meth:`inline_map_reduce` method
18491935
instead.
18501936
1937+
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
1938+
this collection is automatically applied to this operation (if the
1939+
output is not inline) when using MongoDB >= 3.4.
1940+
1941+
.. versionchanged:: 3.4
1942+
Apply this collection's write concern automatically to this operation
1943+
when connected to MongoDB >= 3.4.
1944+
18511945
.. seealso:: :doc:`/examples/aggregation`
18521946
18531947
.. versionchanged:: 2.2
@@ -1856,6 +1950,7 @@ def map_reduce(self, map, reduce, out, full_response=False, **kwargs):
18561950
.. _map reduce command: http://docs.mongodb.org/manual/reference/command/mapReduce/
18571951
18581952
.. mongodoc:: mapreduce
1953+
18591954
"""
18601955
if not isinstance(out, (string_type, collections.Mapping)):
18611956
raise TypeError("'out' must be an instance of "
@@ -1865,17 +1960,24 @@ def map_reduce(self, map, reduce, out, full_response=False, **kwargs):
18651960
("map", map),
18661961
("reduce", reduce),
18671962
("out", out)])
1868-
cmd.update(kwargs)
18691963

1964+
inline = 'inline' in cmd['out']
18701965
with self._socket_for_primary_reads() as (sock_info, slave_ok):
1966+
if (sock_info.max_wire_version >= 5 and self.write_concern and
1967+
not inline):
1968+
cmd['writeConcern'] = self.write_concern.document
1969+
cmd.update(kwargs)
18711970
if (sock_info.max_wire_version >= 4 and 'readConcern' not in cmd and
1872-
'inline' in cmd['out']):
1971+
inline):
1972+
# No need to parse 'writeConcernError' here, since the command
1973+
# is an inline map reduce.
18731974
response = self._command(
18741975
sock_info, cmd, slave_ok, ReadPreference.PRIMARY,
18751976
read_concern=self.read_concern)
18761977
else:
18771978
response = self._command(
1878-
sock_info, cmd, slave_ok, ReadPreference.PRIMARY)
1979+
sock_info, cmd, slave_ok, ReadPreference.PRIMARY,
1980+
parse_write_concern_error=not inline)
18791981

18801982
if full_response or not response.get('result'):
18811983
return response

pymongo/database.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -378,19 +378,27 @@ def _fix_outgoing(self, son, collection):
378378

379379
def _command(self, sock_info, command, slave_ok=False, value=1, check=True,
380380
allowable_errors=None, read_preference=ReadPreference.PRIMARY,
381-
codec_options=DEFAULT_CODEC_OPTIONS, **kwargs):
381+
codec_options=DEFAULT_CODEC_OPTIONS,
382+
write_concern=None,
383+
parse_write_concern_error=False, **kwargs):
382384
"""Internal command helper."""
383385
if isinstance(command, string_type):
384386
command = SON([(command, value)])
387+
388+
if sock_info.max_wire_version >= 5 and write_concern:
389+
command['writeConcern'] = write_concern.document
390+
385391
command.update(kwargs)
386392

387-
return sock_info.command(self.__name,
388-
command,
389-
slave_ok,
390-
read_preference,
391-
codec_options,
392-
check,
393-
allowable_errors)
393+
return sock_info.command(
394+
self.__name,
395+
command,
396+
slave_ok,
397+
read_preference,
398+
codec_options,
399+
check,
400+
allowable_errors,
401+
parse_write_concern_error=parse_write_concern_error)
394402

395403
def command(self, command, value=1, check=True,
396404
allowable_errors=None, read_preference=ReadPreference.PRIMARY,
@@ -539,6 +547,15 @@ def drop_collection(self, name_or_collection):
539547
:Parameters:
540548
- `name_or_collection`: the name of a collection to drop or the
541549
collection object itself
550+
551+
.. note:: The :attr:`~pymongo.database.Database.write_concern` of
552+
this database is automatically applied to this operation when using
553+
MongoDB >= 3.4.
554+
555+
.. versionchanged:: 3.4
556+
Apply this database's write concern automatically to this operation
557+
when connected to MongoDB >= 3.4.
558+
542559
"""
543560
name = name_or_collection
544561
if isinstance(name, Collection):
@@ -550,7 +567,13 @@ def drop_collection(self, name_or_collection):
550567

551568
self.__client._purge_index(self.__name, name)
552569

553-
self.command("drop", _unicode(name), allowable_errors=["ns not found"])
570+
with self.__client._socket_for_reads(
571+
ReadPreference.PRIMARY) as (sock_info, slave_ok):
572+
return self._command(
573+
sock_info, 'drop', slave_ok, _unicode(name),
574+
allowable_errors=['ns not found'],
575+
write_concern=self.write_concern,
576+
parse_write_concern_error=True)
554577

555578
def validate_collection(self, name_or_collection,
556579
scandata=False, full=False):

0 commit comments

Comments
 (0)