Skip to content

Commit dee74f2

Browse files
committed
PYTHON-2707 Limit the use of 'master'
This commit limits the use of the word 'master' as much as possible without breaking API or breaking documentation links. PyMongo 4.0 will include backward breaking API changes to do more.
1 parent ef718b5 commit dee74f2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+677
-619
lines changed

doc/api/pymongo/hello.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
:orphan:
2+
3+
:mod:`hello` -- A wrapper for hello command responses.
4+
======================================================
5+
6+
.. automodule:: pymongo.hello
7+
8+
.. autoclass:: pymongo.hello.Hello(doc)
9+
10+
.. autoattribute:: document

doc/api/pymongo/ismaster.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
:orphan:
22

3-
:mod:`ismaster` -- A wrapper for ismaster command responses.
4-
============================================================
3+
:mod:`ismaster` -- **DEPRECATED** A wrapper for hello command responses.
4+
========================================================================
55

66
.. automodule:: pymongo.ismaster
77

doc/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ Deprecations
4343
:meth:`~pymongo.database.Database.command` helper directly.
4444
- Deprecated :exc:`~pymongo.errors.NotMasterError`. Users should
4545
use :exc:`~pymongo.errors.NotPrimaryError` instead.
46+
- Deprecated :class:`~pymongo.ismaster.IsMaster` and :mod:`~pymongo.ismaster`
47+
which will be removed in PyMongo 4.0 and are replaced by
48+
:class:`~pymongo.hello.Hello` and :mod:`~pymongo.hello` which provide the
49+
same API.
4650

4751
.. _PYTHON-2466: https://jira.mongodb.org/browse/PYTHON-2466
4852
.. _PYTHON-1690: https://jira.mongodb.org/browse/PYTHON-1690

doc/migrate-to-pymongo3.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,13 +433,13 @@ can be changed to this with PyMongo 2.9 or later:
433433
>>> from pymongo.errors import ConnectionFailure
434434
>>> client = MongoClient(connect=False)
435435
>>> try:
436-
... result = client.admin.command("ismaster")
436+
... result = client.admin.command("ping")
437437
... except ConnectionFailure:
438438
... print("Server not available")
439439
>>>
440440

441441
Any operation can be used to determine if the server is available. We choose
442-
the "ismaster" command here because it is cheap and does not require auth, so
442+
the "ping" command here because it is cheap and does not require auth, so
443443
it is a simple way to check whether the server is available.
444444

445445
The max_pool_size parameter is removed
@@ -516,9 +516,10 @@ Removed features with no migration path
516516
MasterSlaveConnection is removed
517517
................................
518518

519-
Master slave deployments are deprecated in MongoDB. Starting with MongoDB 3.0
520-
a replica set can have up to 50 members and that limit is likely to be
521-
removed in later releases. We recommend migrating to replica sets instead.
519+
Master slave deployments are no longer supported by MongoDB. Starting with
520+
MongoDB 3.0 a replica set can have up to 50 members and that limit is likely
521+
to be removed in later releases. We recommend migrating to replica sets
522+
instead.
522523

523524
Requests are removed
524525
....................

pymongo/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,8 @@ def from_credentials(creds):
624624
def speculate_command(self):
625625
raise NotImplementedError
626626

627-
def parse_response(self, ismaster):
628-
self.speculative_authenticate = ismaster.speculative_authenticate
627+
def parse_response(self, hello):
628+
self.speculative_authenticate = hello.speculative_authenticate
629629

630630
def speculate_succeeded(self):
631631
return bool(self.speculative_authenticate)

pymongo/common.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
MIN_SUPPORTED_WIRE_VERSION = 2
6161
MAX_SUPPORTED_WIRE_VERSION = 13
6262

63-
# Frequency to call ismaster on servers, in seconds.
63+
# Frequency to call hello on servers, in seconds.
6464
HEARTBEAT_FREQUENCY = 10
6565

6666
# Frequency to process kill-cursors, in seconds. See MongoClient.close_cursor.
@@ -75,7 +75,7 @@
7575
# longest it is willing to wait for a new primary to be found.
7676
SERVER_SELECTION_TIMEOUT = 30
7777

78-
# Spec requires at least 500ms between ismaster calls.
78+
# Spec requires at least 500ms between hello calls.
7979
MIN_HEARTBEAT_INTERVAL = 0.5
8080

8181
# Spec requires at least 60s between SRV rescans.
@@ -132,13 +132,13 @@ def partition_node(node):
132132

133133

134134
def clean_node(node):
135-
"""Split and normalize a node name from an ismaster response."""
135+
"""Split and normalize a node name from a hello response."""
136136
host, port = partition_node(node)
137137

138138
# Normalize hostname to lowercase, since DNS is case-insensitive:
139139
# http://tools.ietf.org/html/rfc4343
140140
# This prevents useless rediscovery if "foo.com" is in the seed list but
141-
# "FOO.com" is in the ismaster response.
141+
# "FOO.com" is in the hello response.
142142
return host.lower(), port
143143

144144

@@ -977,4 +977,4 @@ def update(self, other):
977977
self[key] = other[key]
978978

979979
def cased_key(self, key):
980-
return self.__casedkeys[key.lower()]
980+
return self.__casedkeys[key.lower()]

pymongo/compression_support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
except ImportError:
3535
_HAVE_ZSTD = False
3636

37-
from pymongo.hello import HelloCompat
37+
from pymongo.hello_compat import HelloCompat
3838
from pymongo.monitoring import _SENSITIVE_COMMANDS
3939

4040
_SUPPORTED_COMPRESSORS = set(["snappy", "zlib", "zstd"])

pymongo/database.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
ConfigurationError,
3131
InvalidName,
3232
OperationFailure)
33+
from pymongo.hello_compat import HelloCompat
3334
from pymongo.message import _first_batch
3435
from pymongo.read_preferences import ReadPreference
3536
from pymongo.son_manipulator import SONManipulator
@@ -1230,7 +1231,7 @@ def error(self):
12301231
error_msg = error.get("err", "")
12311232
if error_msg is None:
12321233
return None
1233-
if error_msg.startswith("not master"):
1234+
if error_msg.startswith(HelloCompat.LEGACY_ERROR):
12341235
# Reset primary server and request check, if another thread isn't
12351236
# doing so already.
12361237
primary = self.__client.primary

pymongo/hello.py

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,186 @@
1414

1515
"""Helpers for the 'hello' and legacy hello commands."""
1616

17+
import itertools
1718

18-
class HelloCompat:
19-
CMD = 'hello'
20-
LEGACY_CMD = 'ismaster'
21-
PRIMARY = 'isWritablePrimary'
22-
LEGACY_PRIMARY = 'ismaster'
19+
from bson.py3compat import imap
20+
from pymongo import common
21+
from pymongo.hello_compat import HelloCompat
22+
from pymongo.server_type import SERVER_TYPE
23+
24+
25+
def _get_server_type(doc):
26+
"""Determine the server type from a hello response."""
27+
if not doc.get('ok'):
28+
return SERVER_TYPE.Unknown
29+
30+
if doc.get('serviceId'):
31+
return SERVER_TYPE.LoadBalancer
32+
elif doc.get('isreplicaset'):
33+
return SERVER_TYPE.RSGhost
34+
elif doc.get('setName'):
35+
if doc.get('hidden'):
36+
return SERVER_TYPE.RSOther
37+
elif doc.get(HelloCompat.PRIMARY):
38+
return SERVER_TYPE.RSPrimary
39+
elif doc.get(HelloCompat.LEGACY_PRIMARY):
40+
return SERVER_TYPE.RSPrimary
41+
elif doc.get('secondary'):
42+
return SERVER_TYPE.RSSecondary
43+
elif doc.get('arbiterOnly'):
44+
return SERVER_TYPE.RSArbiter
45+
else:
46+
return SERVER_TYPE.RSOther
47+
elif doc.get('msg') == 'isdbgrid':
48+
return SERVER_TYPE.Mongos
49+
else:
50+
return SERVER_TYPE.Standalone
51+
52+
53+
class Hello(object):
54+
"""Parse a hello response from the server."""
55+
__slots__ = ('_doc', '_server_type', '_is_writable', '_is_readable',
56+
'_awaitable')
57+
58+
def __init__(self, doc, awaitable=False):
59+
self._server_type = _get_server_type(doc)
60+
self._doc = doc
61+
self._is_writable = self._server_type in (
62+
SERVER_TYPE.RSPrimary,
63+
SERVER_TYPE.Standalone,
64+
SERVER_TYPE.Mongos,
65+
SERVER_TYPE.LoadBalancer)
66+
67+
self._is_readable = (
68+
self.server_type == SERVER_TYPE.RSSecondary
69+
or self._is_writable)
70+
self._awaitable = awaitable
71+
72+
@property
73+
def document(self):
74+
"""The complete hello command response document.
75+
76+
.. versionadded:: 3.4
77+
"""
78+
return self._doc.copy()
79+
80+
@property
81+
def server_type(self):
82+
return self._server_type
83+
84+
@property
85+
def all_hosts(self):
86+
"""List of hosts, passives, and arbiters known to this server."""
87+
return set(imap(common.clean_node, itertools.chain(
88+
self._doc.get('hosts', []),
89+
self._doc.get('passives', []),
90+
self._doc.get('arbiters', []))))
91+
92+
@property
93+
def tags(self):
94+
"""Replica set member tags or empty dict."""
95+
return self._doc.get('tags', {})
96+
97+
@property
98+
def primary(self):
99+
"""This server's opinion about who the primary is, or None."""
100+
if self._doc.get('primary'):
101+
return common.partition_node(self._doc['primary'])
102+
else:
103+
return None
104+
105+
@property
106+
def replica_set_name(self):
107+
"""Replica set name or None."""
108+
return self._doc.get('setName')
109+
110+
@property
111+
def max_bson_size(self):
112+
return self._doc.get('maxBsonObjectSize', common.MAX_BSON_SIZE)
113+
114+
@property
115+
def max_message_size(self):
116+
return self._doc.get('maxMessageSizeBytes', 2 * self.max_bson_size)
117+
118+
@property
119+
def max_write_batch_size(self):
120+
return self._doc.get('maxWriteBatchSize', common.MAX_WRITE_BATCH_SIZE)
121+
122+
@property
123+
def min_wire_version(self):
124+
return self._doc.get('minWireVersion', common.MIN_WIRE_VERSION)
125+
126+
@property
127+
def max_wire_version(self):
128+
return self._doc.get('maxWireVersion', common.MAX_WIRE_VERSION)
129+
130+
@property
131+
def set_version(self):
132+
return self._doc.get('setVersion')
133+
134+
@property
135+
def election_id(self):
136+
return self._doc.get('electionId')
137+
138+
@property
139+
def cluster_time(self):
140+
return self._doc.get('$clusterTime')
141+
142+
@property
143+
def logical_session_timeout_minutes(self):
144+
return self._doc.get('logicalSessionTimeoutMinutes')
145+
146+
@property
147+
def is_writable(self):
148+
return self._is_writable
149+
150+
@property
151+
def is_readable(self):
152+
return self._is_readable
153+
154+
@property
155+
def me(self):
156+
me = self._doc.get('me')
157+
if me:
158+
return common.clean_node(me)
159+
160+
@property
161+
def last_write_date(self):
162+
return self._doc.get('lastWrite', {}).get('lastWriteDate')
163+
164+
@property
165+
def compressors(self):
166+
return self._doc.get('compression')
167+
168+
@property
169+
def sasl_supported_mechs(self):
170+
"""Supported authentication mechanisms for the current user.
171+
172+
For example::
173+
174+
>>> hello.sasl_supported_mechs
175+
["SCRAM-SHA-1", "SCRAM-SHA-256"]
176+
177+
"""
178+
return self._doc.get('saslSupportedMechs', [])
179+
180+
@property
181+
def speculative_authenticate(self):
182+
"""The speculativeAuthenticate field."""
183+
return self._doc.get('speculativeAuthenticate')
184+
185+
@property
186+
def topology_version(self):
187+
return self._doc.get('topologyVersion')
188+
189+
@property
190+
def awaitable(self):
191+
return self._awaitable
192+
193+
@property
194+
def service_id(self):
195+
return self._doc.get('serviceId')
196+
197+
@property
198+
def hello_ok(self):
199+
return self._doc.get('helloOk', False)

pymongo/hello_compat.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2021-present 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+
"""Compatibility enum of working with hello and legay hello."""
16+
17+
class HelloCompat:
18+
CMD = 'hello'
19+
LEGACY_CMD = 'ismaster'
20+
PRIMARY = 'isWritablePrimary'
21+
LEGACY_PRIMARY = 'ismaster'
22+
LEGACY_ERROR = 'not master'
23+

pymongo/helpers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
WriteError,
2929
WriteConcernError,
3030
WTimeoutError)
31+
from pymongo.hello_compat import HelloCompat
3132

3233
# From the SDAM spec, the "node is shutting down" codes.
3334
_SHUTDOWN_CODES = frozenset([
@@ -151,7 +152,7 @@ def _check_command_response(response, max_wire_version,
151152
if code is not None:
152153
if code in _NOT_MASTER_CODES:
153154
raise NotPrimaryError(errmsg, response)
154-
elif "not master" in errmsg or "node is recovering" in errmsg:
155+
elif HelloCompat.LEGACY_ERROR in errmsg or "node is recovering" in errmsg:
155156
raise NotPrimaryError(errmsg, response)
156157

157158
# Other errors
@@ -183,7 +184,7 @@ def _check_gle_response(result, max_wire_version):
183184
if error_msg is None:
184185
return result
185186

186-
if error_msg.startswith("not master"):
187+
if error_msg.startswith(HelloCompat.LEGACY_ERROR):
187188
raise NotPrimaryError(error_msg, result)
188189

189190
details = result

0 commit comments

Comments
 (0)