Skip to content

Commit 87708a2

Browse files
committed
PYTHON-1169 - Default maxStalenessSeconds is -1.
1 parent 873dc22 commit 87708a2

File tree

8 files changed

+77
-42
lines changed

8 files changed

+77
-42
lines changed

pymongo/client_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def _parse_read_preference(options):
4444

4545
mode = options.get('readpreference', 0)
4646
tags = options.get('readpreferencetags')
47-
max_staleness = options.get('maxstalenessseconds')
47+
max_staleness = options.get('maxstalenessseconds', -1)
4848
return make_read_preference(mode, tags, max_staleness)
4949

5050

pymongo/common.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,10 @@ def validate_timeout_or_zero(option, value):
292292

293293

294294
def validate_max_staleness(option, value):
295-
"""Validates a timeout specified in seconds returning
296-
a value in floating point seconds.
297-
"""
298-
if value is None:
299-
return value
295+
"""Validates maxStalenessSeconds according to the Max Staleness Spec."""
296+
if value == -1 or value == "-1":
297+
# Default: No maximum staleness.
298+
return -1
300299
return validate_positive_float(option, value)
301300

302301

pymongo/max_staleness_selectors.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
def _validate_max_staleness(max_staleness,
3535
heartbeat_frequency,
3636
idle_write_period):
37+
# We checked for max staleness -1 before this, it must be positive here.
3738
if max_staleness < heartbeat_frequency + idle_write_period:
3839
raise ConfigurationError(
3940
"maxStalenessSeconds must be at least heartbeatFrequencyMS +"
@@ -46,7 +47,6 @@ def _validate_max_staleness(max_staleness,
4647
def _with_primary(max_staleness, selection):
4748
"""Apply max_staleness, in seconds, to a Selection with a known primary."""
4849
primary = selection.primary
49-
assert primary
5050

5151
# Server Selection Spec: If the TopologyType is ReplicaSetWithPrimary, a
5252
# client MUST raise an error if maxStaleness < heartbeatFrequency +
@@ -83,7 +83,6 @@ def _no_primary(max_staleness, selection):
8383

8484
# Secondary we've most recently checked.
8585
srecent = selection.secondary_with_max_last_update_time()
86-
assert srecent
8786

8887
sds = []
8988

@@ -111,7 +110,7 @@ def _no_primary(max_staleness, selection):
111110

112111
def select(max_staleness, selection):
113112
"""Apply max_staleness, in seconds, to a Selection."""
114-
if not max_staleness:
113+
if max_staleness == -1:
115114
return selection
116115

117116
if selection.primary:

pymongo/message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def _maybe_add_read_preference(spec, read_preference):
8282
if mode and (
8383
mode != ReadPreference.SECONDARY_PREFERRED.mode
8484
or tag_sets != [{}]
85-
or max_staleness):
85+
or max_staleness != -1):
8686

8787
if "$query" not in spec:
8888
spec = SON([("$query", spec)])

pymongo/mongo_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ def __init__(
255255
- `maxStalenessSeconds`: (integer or float) The maximum estimated
256256
length of time a replica set secondary can fall behind the primary
257257
in replication before it will no longer be selected for operations.
258-
Defaults to ``None`` (no limit).
258+
Defaults to ``-1``, meaning no maximum.
259259
260260
| **SSL configuration:**
261261

pymongo/read_preferences.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,22 @@ def _validate_tag_sets(tag_sets):
6262
return tag_sets
6363

6464

65+
# Distinct from the validator in common.py for URI option "maxStalenessSeconds".
6566
def _validate_max_staleness(max_staleness):
6667
"""Validate max_staleness."""
67-
if max_staleness is None:
68-
return 0.0
69-
70-
errmsg = "max_staleness must be an integer or float"
71-
try:
72-
max_staleness = float(max_staleness)
73-
except ValueError:
74-
raise ValueError(errmsg)
75-
except TypeError:
76-
raise TypeError(errmsg)
77-
78-
if not 0 < max_staleness < 1e9:
79-
raise ValueError(
80-
"max_staleness must be greater than 0 and less than one billion")
68+
if max_staleness != -1:
69+
errmsg = "max_staleness must be an integer or float"
70+
try:
71+
max_staleness = float(max_staleness)
72+
except ValueError:
73+
raise ValueError(errmsg)
74+
except TypeError:
75+
raise TypeError(errmsg)
76+
77+
if not 0 < max_staleness < 1e9:
78+
raise ValueError(
79+
"max_staleness must be greater than 0"
80+
" and less than one billion")
8181

8282
return max_staleness
8383

@@ -88,7 +88,7 @@ class _ServerMode(object):
8888

8989
__slots__ = ("__mongos_mode", "__mode", "__tag_sets", "__max_staleness")
9090

91-
def __init__(self, mode, tag_sets=None, max_staleness=None):
91+
def __init__(self, mode, tag_sets=None, max_staleness=-1):
9292
self.__mongos_mode = _MONGOS_MODES[mode]
9393
self.__mode = mode
9494
self.__tag_sets = _validate_tag_sets(tag_sets)
@@ -107,7 +107,7 @@ def document(self):
107107
doc = {'mode': self.__mongos_mode}
108108
if self.__tag_sets not in (None, [{}]):
109109
doc['tags'] = self.__tag_sets
110-
if self.__max_staleness:
110+
if self.__max_staleness != -1:
111111
doc['maxStalenessSeconds'] = self.__max_staleness
112112
return doc
113113

@@ -136,9 +136,7 @@ def tag_sets(self):
136136
def max_staleness(self):
137137
"""The maximum estimated length of time (in seconds) a replica set
138138
secondary can fall behind the primary in replication before it will
139-
no longer be selected for operations."""
140-
if not self.__max_staleness:
141-
return None
139+
no longer be selected for operations, or -1 for no maximum."""
142140
return self.__max_staleness
143141

144142
@property
@@ -152,11 +150,11 @@ def min_wire_version(self):
152150
`min_wire_version`, or the driver raises
153151
:exc:`~pymongo.errors.ConfigurationError`.
154152
"""
155-
return 5 if self.__max_staleness else 0
153+
return 0 if self.__max_staleness == -1 else 5
156154

157155
def __repr__(self):
158156
return "%s(tag_sets=%r, max_staleness=%r)" % (
159-
self.name, self.__tag_sets, self.max_staleness)
157+
self.name, self.__tag_sets, self.__max_staleness)
160158

161159
def __eq__(self, other):
162160
if isinstance(other, _ServerMode):
@@ -182,7 +180,7 @@ def __setstate__(self, value):
182180
self.__mode = value['mode']
183181
self.__mongos_mode = _MONGOS_MODES[self.__mode]
184182
self.__tag_sets = _validate_tag_sets(value['tag_sets'])
185-
self.__max_staleness = value['max_staleness']
183+
self.__max_staleness = _validate_max_staleness(value['max_staleness'])
186184

187185

188186
class Primary(_ServerMode):
@@ -227,9 +225,10 @@ class PrimaryPreferred(_ServerMode):
227225
- `max_staleness`: (integer or float, in seconds) The maximum estimated
228226
length of time a replica set secondary can fall behind the primary in
229227
replication before it will no longer be selected for operations.
228+
Default -1, meaning no maximum.
230229
"""
231230

232-
def __init__(self, tag_sets=None, max_staleness=None):
231+
def __init__(self, tag_sets=None, max_staleness=-1):
233232
super(PrimaryPreferred, self).__init__(_PRIMARY_PREFERRED,
234233
tag_sets,
235234
max_staleness)
@@ -260,9 +259,10 @@ class Secondary(_ServerMode):
260259
- `max_staleness`: (integer or float, in seconds) The maximum estimated
261260
length of time a replica set secondary can fall behind the primary in
262261
replication before it will no longer be selected for operations.
262+
Default -1, meaning no maximum.
263263
"""
264264

265-
def __init__(self, tag_sets=None, max_staleness=None):
265+
def __init__(self, tag_sets=None, max_staleness=-1):
266266
super(Secondary, self).__init__(_SECONDARY, tag_sets, max_staleness)
267267

268268
def __call__(self, selection):
@@ -288,9 +288,10 @@ class SecondaryPreferred(_ServerMode):
288288
- `max_staleness`: (integer or float, in seconds) The maximum estimated
289289
length of time a replica set secondary can fall behind the primary in
290290
replication before it will no longer be selected for operations.
291+
Default -1, meaning no maximum.
291292
"""
292293

293-
def __init__(self, tag_sets=None, max_staleness=None):
294+
def __init__(self, tag_sets=None, max_staleness=-1):
294295
super(SecondaryPreferred, self).__init__(_SECONDARY_PREFERRED,
295296
tag_sets,
296297
max_staleness)
@@ -323,9 +324,10 @@ class Nearest(_ServerMode):
323324
- `max_staleness`: (integer or float, in seconds) The maximum estimated
324325
length of time a replica set secondary can fall behind the primary in
325326
replication before it will no longer be selected for operations.
327+
Default -1, meaning no maximum.
326328
"""
327329

328-
def __init__(self, tag_sets=None, max_staleness=None):
330+
def __init__(self, tag_sets=None, max_staleness=-1):
329331
super(Nearest, self).__init__(_NEAREST, tag_sets, max_staleness)
330332

331333
def __call__(self, selection):
@@ -340,12 +342,12 @@ def __call__(self, selection):
340342
Secondary, SecondaryPreferred, Nearest)
341343

342344

343-
def make_read_preference(mode, tag_sets, max_staleness=None):
345+
def make_read_preference(mode, tag_sets, max_staleness=-1):
344346
if mode == _PRIMARY:
345347
if tag_sets not in (None, [{}]):
346348
raise ConfigurationError("Read preference primary "
347349
"cannot be combined with tags")
348-
if max_staleness:
350+
if max_staleness != -1:
349351
raise ConfigurationError("Read preference primary cannot be "
350352
"combined with maxStalenessSeconds")
351353
return Primary()

test/test_max_staleness.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import time
2020
import sys
21+
import warnings
2122

2223
sys.path[0:0] = [""]
2324

@@ -162,7 +163,7 @@ def run_scenario(self):
162163
mode_string = pref_def.get('mode', 'primary')
163164
mode_string = mode_string[:1].lower() + mode_string[1:]
164165
mode = read_preferences.read_pref_mode_from_name(mode_string)
165-
max_staleness = pref_def.get('maxStalenessSeconds')
166+
max_staleness = pref_def.get('maxStalenessSeconds', -1)
166167
tag_sets = pref_def.get('tag_sets')
167168

168169
if scenario_def.get('error'):
@@ -224,11 +225,19 @@ def create_tests():
224225

225226
class TestMaxStaleness(unittest.TestCase):
226227
def test_max_staleness(self):
228+
client = MongoClient()
229+
self.assertEqual(-1, client.read_preference.max_staleness)
230+
231+
client = MongoClient("mongodb://a/?readPreference=secondary")
232+
self.assertEqual(-1, client.read_preference.max_staleness)
233+
227234
# These tests are specified in max-staleness-tests.rst.
228235
with self.assertRaises(ConfigurationError):
236+
# Default read pref "primary" can't be used with max staleness.
229237
MongoClient("mongodb://a/?maxStalenessSeconds=120")
230238

231239
with self.assertRaises(ConfigurationError):
240+
# Read pref "primary" can't be used with max staleness.
232241
MongoClient("mongodb://a/?readPreference=primary&"
233242
"maxStalenessSeconds=120")
234243

@@ -240,6 +249,32 @@ def test_max_staleness(self):
240249
"maxStalenessSeconds=1")
241250
self.assertEqual(1, client.read_preference.max_staleness)
242251

252+
client = MongoClient("mongodb://a/?readPreference=secondary&"
253+
"maxStalenessSeconds=-1")
254+
self.assertEqual(-1, client.read_preference.max_staleness)
255+
256+
client = MongoClient(maxStalenessSeconds=-1, readPreference="nearest")
257+
self.assertEqual(-1, client.read_preference.max_staleness)
258+
259+
with self.assertRaises(TypeError):
260+
# Prohibit None.
261+
MongoClient(maxStalenessSeconds=None, readPreference="nearest")
262+
263+
def test_max_staleness_zero(self):
264+
# Zero is too small.
265+
with self.assertRaises(ValueError) as ctx:
266+
rs_or_single_client(maxStalenessSeconds=0,
267+
readPreference="nearest")
268+
269+
self.assertIn("must be greater than 0", str(ctx.exception))
270+
271+
with warnings.catch_warnings(record=True) as ctx:
272+
warnings.simplefilter("always")
273+
MongoClient("mongodb://host/?maxStalenessSeconds=0"
274+
"&readPreference=nearest")
275+
276+
self.assertIn("must be greater than 0", str(ctx[0]))
277+
243278
@client_context.require_version_min(3, 3, 6) # SERVER-8858
244279
def test_last_write_date(self):
245280
# From max-staleness-tests.rst, "Parse lastWriteDate".

test/test_topology.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,12 +710,12 @@ def test_no_secondary(self):
710710

711711
self.assertMessage(
712712
'No replica set members match selector'
713-
' "Secondary(tag_sets=None, max_staleness=None)"',
713+
' "Secondary(tag_sets=None, max_staleness=-1)"',
714714
t, ReadPreference.SECONDARY)
715715

716716
self.assertMessage(
717717
"No replica set members match selector"
718-
" \"Secondary(tag_sets=[{'dc': 'ny'}], max_staleness=None)\"",
718+
" \"Secondary(tag_sets=[{'dc': 'ny'}], max_staleness=-1)\"",
719719
t, Secondary(tag_sets=[{'dc': 'ny'}]))
720720

721721
def test_bad_replica_set_name(self):

0 commit comments

Comments
 (0)