Skip to content

Commit dde4a65

Browse files
Luke Lovettbehackett
authored andcommitted
PYTHON-981 - Implement ReadConcern.
1 parent 5396444 commit dde4a65

17 files changed

+376
-77
lines changed

pymongo/client_options.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from pymongo.errors import ConfigurationError
2222
from pymongo.monitoring import _EventListeners
2323
from pymongo.pool import PoolOptions
24+
from pymongo.read_concern import ReadConcern
2425
from pymongo.read_preferences import make_read_preference
2526
from pymongo.ssl_support import get_ssl_context
2627
from pymongo.write_concern import WriteConcern
@@ -55,6 +56,12 @@ def _parse_write_concern(options):
5556
return WriteConcern(concern, wtimeout, j, fsync)
5657

5758

59+
def _parse_read_concern(options):
60+
"""Parse read concern options."""
61+
concern = options.get('readconcernlevel')
62+
return ReadConcern(concern)
63+
64+
5865
def _parse_ssl_options(options):
5966
"""Parse ssl options."""
6067
use_ssl = options.get('ssl')
@@ -122,6 +129,7 @@ def __init__(self, username, password, database, options):
122129
self.__read_preference = _parse_read_preference(options)
123130
self.__replica_set_name = options.get('replicaset')
124131
self.__write_concern = _parse_write_concern(options)
132+
self.__read_concern = _parse_read_concern(options)
125133
self.__connect = options.get('connect')
126134

127135
@property
@@ -173,3 +181,8 @@ def replica_set_name(self):
173181
def write_concern(self):
174182
"""A :class:`~pymongo.write_concern.WriteConcern` instance."""
175183
return self.__write_concern
184+
185+
@property
186+
def read_concern(self):
187+
"""A :class:`~pymongo.read_concern.ReadConcern` instance."""
188+
return self.__read_concern

pymongo/collection.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from pymongo.errors import ConfigurationError, InvalidName, OperationFailure
3636
from pymongo.helpers import _check_write_command_response
3737
from pymongo.operations import _WriteOp, IndexModel
38+
from pymongo.read_concern import DEFAULT_READ_CONCERN
3839
from pymongo.read_preferences import ReadPreference
3940
from pymongo.results import (BulkWriteResult,
4041
DeleteResult,
@@ -71,7 +72,8 @@ class Collection(common.BaseObject):
7172
"""
7273

7374
def __init__(self, database, name, create=False, codec_options=None,
74-
read_preference=None, write_concern=None, **kwargs):
75+
read_preference=None, write_concern=None, read_concern=None,
76+
**kwargs):
7577
"""Get / create a Mongo collection.
7678
7779
Raises :class:`TypeError` if `name` is not an instance of
@@ -100,9 +102,15 @@ def __init__(self, database, name, create=False, codec_options=None,
100102
- `write_concern` (optional): An instance of
101103
:class:`~pymongo.write_concern.WriteConcern`. If ``None`` (the
102104
default) database.write_concern is used.
105+
- `read_concern` (optional): An instance of
106+
:class:`~pymongo.read_concern.ReadConcern`. If ``None`` (the
107+
default) database.read_concern is used.
103108
- `**kwargs` (optional): additional keyword arguments will
104109
be passed as options for the create collection command
105110
111+
.. versionchanged:: 3.2
112+
Added the read_concern option.
113+
106114
.. versionchanged:: 3.0
107115
Added the codec_options, read_preference, and write_concern options.
108116
Removed the uuid_subtype attribute.
@@ -128,7 +136,8 @@ def __init__(self, database, name, create=False, codec_options=None,
128136
super(Collection, self).__init__(
129137
codec_options or database.codec_options,
130138
read_preference or database.read_preference,
131-
write_concern or database.write_concern)
139+
write_concern or database.write_concern,
140+
read_concern or database.read_concern)
132141

133142
if not isinstance(name, string_type):
134143
raise TypeError("name must be an instance "
@@ -164,7 +173,8 @@ def _socket_for_writes(self):
164173

165174
def _command(self, sock_info, command, slave_ok=False,
166175
read_preference=None,
167-
codec_options=None, check=True, allowable_errors=None):
176+
codec_options=None, check=True, allowable_errors=None,
177+
read_concern=DEFAULT_READ_CONCERN):
168178
"""Internal command helper.
169179
170180
:Parameters:
@@ -175,6 +185,8 @@ def _command(self, sock_info, command, slave_ok=False,
175185
:class:`~bson.codec_options.CodecOptions`.
176186
- `check`: raise OperationFailure if there are errors
177187
- `allowable_errors`: errors to ignore if `check` is True
188+
- `read_concern` (optional) - An instance of
189+
:class:`~pymongo.read_concern.ReadConcern`.
178190
179191
:Returns:
180192
@@ -188,7 +200,8 @@ def _command(self, sock_info, command, slave_ok=False,
188200
read_preference or self.read_preference,
189201
codec_options or self.codec_options,
190202
check,
191-
allowable_errors)
203+
allowable_errors,
204+
read_concern=read_concern)
192205

193206
def __create(self, options):
194207
"""Sends a create command with the given options.
@@ -254,7 +267,8 @@ def database(self):
254267
return self.__database
255268

256269
def with_options(
257-
self, codec_options=None, read_preference=None, write_concern=None):
270+
self, codec_options=None, read_preference=None,
271+
write_concern=None, read_concern=None):
258272
"""Get a clone of this collection changing the specified settings.
259273
260274
>>> coll1.read_preference
@@ -279,13 +293,18 @@ def with_options(
279293
:class:`~pymongo.write_concern.WriteConcern`. If ``None`` (the
280294
default) the :attr:`write_concern` of this :class:`Collection`
281295
is used.
296+
- `read_concern` (optional): An instance of
297+
:class:`~pymongo.read_concern.ReadConcern`. If ``None`` (the
298+
default) the :attr:`read_concern` of this :class:`Collection`
299+
is used.
282300
"""
283301
return Collection(self.__database,
284302
self.__name,
285303
False,
286304
codec_options or self.codec_options,
287305
read_preference or self.read_preference,
288-
write_concern or self.write_concern)
306+
write_concern or self.write_concern,
307+
read_concern or self.read_concern)
289308

290309
def initialize_unordered_bulk_op(self):
291310
"""Initialize an unordered batch of write operations.
@@ -1059,7 +1078,8 @@ def parallel_scan(self, num_cursors):
10591078
('numCursors', num_cursors)])
10601079

10611080
with self._socket_for_reads() as (sock_info, slave_ok):
1062-
result = self._command(sock_info, cmd, slave_ok)
1081+
result = self._command(sock_info, cmd, slave_ok,
1082+
read_concern=self.read_concern)
10631083

10641084
return [CommandCursor(self, cursor['cursor'], sock_info.address)
10651085
for cursor in result['cursors']]
@@ -1068,7 +1088,8 @@ def _count(self, cmd):
10681088
"""Internal count helper."""
10691089
with self._socket_for_reads() as (sock_info, slave_ok):
10701090
res = self._command(sock_info, cmd, slave_ok,
1071-
allowable_errors=["ns missing"])
1091+
allowable_errors=["ns missing"],
1092+
read_concern=self.read_concern)
10721093
if res.get("errmsg", "") == "ns missing":
10731094
return 0
10741095
return int(res["n"])
@@ -1522,7 +1543,8 @@ def aggregate(self, pipeline, **kwargs):
15221543

15231544
cmd.update(kwargs)
15241545

1525-
result = self._command(sock_info, cmd, slave_ok)
1546+
result = self._command(sock_info, cmd, slave_ok,
1547+
read_concern=self.read_concern)
15261548

15271549
if "cursor" in result:
15281550
cursor = result["cursor"]
@@ -1653,7 +1675,8 @@ def distinct(self, key, filter=None, **kwargs):
16531675
kwargs["query"] = filter
16541676
cmd.update(kwargs)
16551677
with self._socket_for_reads() as (sock_info, slave_ok):
1656-
return self._command(sock_info, cmd, slave_ok)["values"]
1678+
return self._command(sock_info, cmd, slave_ok,
1679+
read_concern=self.read_concern)["values"]
16571680

16581681
def map_reduce(self, map, reduce, out, full_response=False, **kwargs):
16591682
"""Perform a map/reduce operation on this collection.

pymongo/common.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from pymongo.auth import MECHANISMS
2626
from pymongo.errors import ConfigurationError
2727
from pymongo.monitoring import _validate_event_listeners
28+
from pymongo.read_concern import ReadConcern
2829
from pymongo.read_preferences import (read_pref_mode_from_name,
2930
_ServerMode)
3031
from pymongo.ssl_support import validate_cert_reqs
@@ -423,6 +424,7 @@ def validate_ok_for_update(update):
423424
'ssl_cert_reqs': validate_cert_reqs,
424425
'ssl_ca_certs': validate_readable,
425426
'ssl_match_hostname': validate_boolean_or_string,
427+
'readconcernlevel': validate_string_or_none,
426428
'read_preference': validate_read_preference,
427429
'readpreference': validate_read_preference_mode,
428430
'readpreferencetags': validate_read_preference_tags,
@@ -495,7 +497,8 @@ class BaseObject(object):
495497
SHOULD NOT BE USED BY DEVELOPERS EXTERNAL TO MONGODB.
496498
"""
497499

498-
def __init__(self, codec_options, read_preference, write_concern):
500+
def __init__(self, codec_options, read_preference, write_concern,
501+
read_concern):
499502

500503
if not isinstance(codec_options, CodecOptions):
501504
raise TypeError("codec_options must be an instance of "
@@ -513,6 +516,11 @@ def __init__(self, codec_options, read_preference, write_concern):
513516
"pymongo.write_concern.WriteConcern")
514517
self.__write_concern = write_concern
515518

519+
if not isinstance(read_concern, ReadConcern):
520+
raise TypeError("read_concern must be an instance of "
521+
"pymongo.read_concern.ReadConcern")
522+
self.__read_concern = read_concern
523+
516524
@property
517525
def codec_options(self):
518526
"""Read only access to the :class:`~bson.codec_options.CodecOptions`
@@ -538,3 +546,11 @@ def read_preference(self):
538546
The :attr:`read_preference` attribute is now read only.
539547
"""
540548
return self.__read_preference
549+
550+
@property
551+
def read_concern(self):
552+
"""Read only access to the read concern of this instance.
553+
554+
.. versionadded:: 3.2
555+
"""
556+
return self.__read_concern

pymongo/cursor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def __init__(self, collection, filter=None, projection=None, skip=0,
184184

185185
self.__codec_options = collection.codec_options
186186
self.__read_preference = collection.read_preference
187+
self.__read_concern = collection.read_concern
187188

188189
self.__query_flags = cursor_type
189190
if self.__read_preference != ReadPreference.PRIMARY:
@@ -985,7 +986,8 @@ def _refresh(self):
985986
self.__codec_options,
986987
self.__read_preference,
987988
self.__limit,
988-
self.__batch_size))
989+
self.__batch_size,
990+
self.__read_concern))
989991
if not self.__id:
990992
self.__killed = True
991993
elif self.__id: # Get More

pymongo/database.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import warnings
1818

1919
from bson.code import Code
20-
from bson.codec_options import CodecOptions
20+
from bson.codec_options import CodecOptions, DEFAULT_CODEC_OPTIONS
2121
from bson.dbref import DBRef
2222
from bson.objectid import ObjectId
2323
from bson.py3compat import iteritems, string_type, _unicode
@@ -51,8 +51,8 @@ class Database(common.BaseObject):
5151
"""A Mongo database.
5252
"""
5353

54-
def __init__(self, client, name, codec_options=None,
55-
read_preference=None, write_concern=None):
54+
def __init__(self, client, name, codec_options=None, read_preference=None,
55+
write_concern=None, read_concern=None):
5656
"""Get a database by client and name.
5757
5858
Raises :class:`TypeError` if `name` is not an instance of
@@ -71,9 +71,15 @@ def __init__(self, client, name, codec_options=None,
7171
- `write_concern` (optional): An instance of
7272
:class:`~pymongo.write_concern.WriteConcern`. If ``None`` (the
7373
default) client.write_concern is used.
74+
- `read_concern` (optional): An instance of
75+
:class:`~pymongo.read_concern.ReadConcern`. If ``None`` (the
76+
default) client.read_concern is used.
7477
7578
.. mongodoc:: databases
7679
80+
.. versionchanged:: 3.2
81+
Added the read_concern option.
82+
7783
.. versionchanged:: 3.0
7884
Added the codec_options, read_preference, and write_concern options.
7985
:class:`~pymongo.database.Database` no longer returns an instance
@@ -89,7 +95,8 @@ def __init__(self, client, name, codec_options=None,
8995
super(Database, self).__init__(
9096
codec_options or client.codec_options,
9197
read_preference or client.read_preference,
92-
write_concern or client.write_concern)
98+
write_concern or client.write_concern,
99+
read_concern or client.read_concern)
93100

94101
if not isinstance(name, string_type):
95102
raise TypeError("name must be an instance "
@@ -225,8 +232,8 @@ def __getitem__(self, name):
225232
"""
226233
return Collection(self, name)
227234

228-
def get_collection(self, name, codec_options=None,
229-
read_preference=None, write_concern=None):
235+
def get_collection(self, name, codec_options=None, read_preference=None,
236+
write_concern=None, read_concern=None):
230237
"""Get a :class:`~pymongo.collection.Collection` with the given name
231238
and options.
232239
@@ -259,12 +266,18 @@ def get_collection(self, name, codec_options=None,
259266
:class:`~pymongo.write_concern.WriteConcern`. If ``None`` (the
260267
default) the :attr:`write_concern` of this :class:`Database` is
261268
used.
269+
- `read_concern` (optional): An instance of
270+
:class:`~pymongo.read_concern.ReadConcern`. If ``None`` (the
271+
default) the :attr:`read_concern` of this :class:`Database` is
272+
used.
262273
"""
263274
return Collection(
264-
self, name, False, codec_options, read_preference, write_concern)
275+
self, name, False, codec_options, read_preference,
276+
write_concern, read_concern)
265277

266278
def create_collection(self, name, codec_options=None,
267-
read_preference=None, write_concern=None, **kwargs):
279+
read_preference=None, write_concern=None,
280+
read_concern=None, **kwargs):
268281
"""Create a new :class:`~pymongo.collection.Collection` in this
269282
database.
270283
@@ -298,6 +311,10 @@ def create_collection(self, name, codec_options=None,
298311
:class:`~pymongo.write_concern.WriteConcern`. If ``None`` (the
299312
default) the :attr:`write_concern` of this :class:`Database` is
300313
used.
314+
- `read_concern` (optional): An instance of
315+
:class:`~pymongo.read_concern.ReadConcern`. If ``None`` (the
316+
default) the :attr:`read_concern` of this :class:`Database` is
317+
used.
301318
- `**kwargs` (optional): additional keyword arguments will
302319
be passed as options for the create collection command
303320
@@ -311,7 +328,8 @@ def create_collection(self, name, codec_options=None,
311328
raise CollectionInvalid("collection %s already exists" % name)
312329

313330
return Collection(self, name, True, codec_options,
314-
read_preference, write_concern, **kwargs)
331+
read_preference, write_concern,
332+
read_concern, **kwargs)
315333

316334
def _apply_incoming_manipulators(self, son, collection):
317335
"""Apply incoming manipulators to `son`."""
@@ -351,7 +369,7 @@ def _fix_outgoing(self, son, collection):
351369

352370
def _command(self, sock_info, command, slave_ok=False, value=1, check=True,
353371
allowable_errors=None, read_preference=ReadPreference.PRIMARY,
354-
codec_options=CodecOptions(), **kwargs):
372+
codec_options=DEFAULT_CODEC_OPTIONS, **kwargs):
355373
"""Internal command helper."""
356374
if isinstance(command, string_type):
357375
command = SON([(command, value)])
@@ -367,7 +385,7 @@ def _command(self, sock_info, command, slave_ok=False, value=1, check=True,
367385

368386
def command(self, command, value=1, check=True,
369387
allowable_errors=None, read_preference=ReadPreference.PRIMARY,
370-
codec_options=CodecOptions(), **kwargs):
388+
codec_options=DEFAULT_CODEC_OPTIONS, **kwargs):
371389
"""Issue a MongoDB command.
372390
373391
Send command `command` to the database and return the

pymongo/helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
WriteConcernError,
3333
WTimeoutError)
3434
from pymongo.message import _Query, _convert_exception
35+
from pymongo.read_concern import DEFAULT_READ_CONCERN
3536

3637

3738
_UUNDER = u("_")
@@ -240,7 +241,7 @@ def _first_batch(sock_info, db, coll, query, ntoreturn,
240241
"""Simple query helper for retrieving a first (and possibly only) batch."""
241242
query = _Query(
242243
0, db, coll, 0, ntoreturn, query, None,
243-
codec_options, read_preference, 0, ntoreturn)
244+
codec_options, read_preference, 0, 0, DEFAULT_READ_CONCERN)
244245

245246
name = next(iter(cmd))
246247
duration = None

0 commit comments

Comments
 (0)