Skip to content

Commit 0743c0b

Browse files
committed
PYTHON-2121 add directConnection URI option
1 parent 9a9f42b commit 0743c0b

Some content is hidden

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

44 files changed

+899
-61
lines changed

doc/changelog.rst

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Version 3.11 adds support for MongoDB 4.4. Highlights include:
1212
support. It will also be installed when using the "tls" extra if the
1313
version of Python in use is older than 2.7.9.
1414
- Support for the :ref:`MONGODB-AWS` authentication mechanism.
15+
- Support for the ``directConnection`` URI option and kwarg to
16+
:class:`~pymongo.mongo_client.MongoClient`.
1517
- Added index hinting support to the
1618
:meth:`~pymongo.collection.Collection.replace_one`,
1719
:meth:`~pymongo.collection.Collection.update_one`,

pymongo/client_options.py

+6
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def __init__(self, username, password, database, options):
151151
self.__codec_options = _parse_codec_options(options)
152152
self.__credentials = _parse_credentials(
153153
username, password, database, options)
154+
self.__direct_connection = options.get('directconnection')
154155
self.__local_threshold_ms = options.get(
155156
'localthresholdms', common.LOCAL_THRESHOLD_MS)
156157
# self.__server_selection_timeout is in seconds. Must use full name for
@@ -191,6 +192,11 @@ def credentials(self):
191192
"""A :class:`~pymongo.auth.MongoCredentials` instance or None."""
192193
return self.__credentials
193194

195+
@property
196+
def direct_connection(self):
197+
"""Whether to connect to the deployment in 'Single' topology."""
198+
return self.__direct_connection
199+
194200
@property
195201
def local_threshold_ms(self):
196202
"""The local threshold for this instance."""

pymongo/common.py

+1
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ def validate_tzinfo(dummy, value):
594594
'authsource': validate_string,
595595
'compressors': validate_compressors,
596596
'connecttimeoutms': validate_timeout_or_none,
597+
'directconnection': validate_boolean_or_string,
597598
'heartbeatfrequencyms': validate_timeout_or_none,
598599
'journal': validate_boolean_or_string,
599600
'localthresholdms': validate_positive_float_or_zero,

pymongo/mongo_client.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ def __init__(
205205
- `connect` (optional): if ``True`` (the default), immediately
206206
begin connecting to MongoDB in the background. Otherwise connect
207207
on the first operation.
208+
- `directConnection` (optional): if ``True``, forces this client to
209+
connect directly to the specified MongoDB host as a standalone.
210+
If ``false``, the client connects to the entire replica set of
211+
which the given MongoDB host(s) is a part. If this is ``True``
212+
and a mongodb+srv:// URI or a URI containing multiple seeds is
213+
provided, an exception will be raised.
208214
209215
| **Other optional parameters can be passed as keyword arguments:**
210216
@@ -492,8 +498,10 @@ def __init__(
492498
.. mongodoc:: connections
493499
494500
.. versionchanged:: 3.11
495-
Added the ``tlsDisableOCSPEndpointCheck`` keyword argument and
496-
URI option.
501+
Added the following keyword arguments and URI options:
502+
503+
- ``tlsDisableOCSPEndpointCheck``
504+
- ``directConnection``
497505
498506
.. versionchanged:: 3.9
499507
Added the ``retryReads`` keyword argument and URI option.
@@ -707,7 +715,8 @@ def __init__(
707715
server_selection_timeout=options.server_selection_timeout,
708716
server_selector=options.server_selector,
709717
heartbeat_frequency=options.heartbeat_frequency,
710-
fqdn=fqdn)
718+
fqdn=fqdn,
719+
direct_connection=options.direct_connection)
711720

712721
self._topology = Topology(self._topology_settings)
713722
if connect:

pymongo/server_description.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ def retryable_reads_supported(self):
216216
def topology_version(self):
217217
return self._topology_version
218218

219-
def to_unknown(self):
220-
unknown = ServerDescription(self.address)
219+
def to_unknown(self, error=None):
220+
unknown = ServerDescription(self.address, error=error)
221221
unknown._topology_version = self.topology_version
222222
return unknown
223223

pymongo/settings.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ def __init__(self,
3838
server_selection_timeout=SERVER_SELECTION_TIMEOUT,
3939
heartbeat_frequency=common.HEARTBEAT_FREQUENCY,
4040
server_selector=None,
41-
fqdn=None):
41+
fqdn=None,
42+
direct_connection=None):
4243
"""Represent MongoClient's configuration.
4344
4445
Take a list of (host, port) pairs and optional replica set name.
@@ -59,7 +60,12 @@ def __init__(self,
5960
self._server_selector = server_selector
6061
self._fqdn = fqdn
6162
self._heartbeat_frequency = heartbeat_frequency
62-
self._direct = (len(self._seeds) == 1 and not replica_set_name)
63+
64+
if direct_connection is None:
65+
self._direct = (len(self._seeds) == 1 and not self.replica_set_name)
66+
else:
67+
self._direct = direct_connection
68+
6369
self._topology_id = ObjectId()
6470
# Store the allocation traceback to catch unclosed clients in the
6571
# test suite.

pymongo/topology_description.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ def updated_topology_description(topology_description, server_description):
337337
sds[address] = server_description
338338

339339
if topology_type == TOPOLOGY_TYPE.Single:
340+
# Set server type to Unknown if replica set name does not match.
341+
if (set_name is not None and
342+
set_name != server_description.replica_set_name):
343+
error = ConfigurationError(
344+
"client is configured to connect to a replica set named "
345+
"'%s' but this node belongs to a set named '%s'" % (
346+
set_name, server_description.replica_set_name))
347+
sds[address] = server_description.to_unknown(error=error)
340348
# Single type never changes.
341349
return TopologyDescription(
342350
TOPOLOGY_TYPE.Single,
@@ -348,8 +356,11 @@ def updated_topology_description(topology_description, server_description):
348356

349357
if topology_type == TOPOLOGY_TYPE.Unknown:
350358
if server_type == SERVER_TYPE.Standalone:
351-
sds.pop(address)
352-
359+
if len(topology_description._topology_settings.seeds) == 1:
360+
topology_type = TOPOLOGY_TYPE.Single
361+
else:
362+
# Remove standalone from Topology when given multiple seeds.
363+
sds.pop(address)
353364
elif server_type not in (SERVER_TYPE.Unknown, SERVER_TYPE.RSGhost):
354365
topology_type = _SERVER_TYPE_TO_TOPOLOGY_TYPE[server_type]
355366

pymongo/uri_parser.py

+7
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
479479
fqdn = None
480480

481481
if is_srv:
482+
if options.get('directConnection'):
483+
raise ConfigurationError(
484+
"Cannot specify directConnection=true with "
485+
"%s URIs" % (SRV_SCHEME,))
482486
nodes = split_hosts(hosts, default_port=None)
483487
if len(nodes) != 1:
484488
raise InvalidURI(
@@ -508,6 +512,9 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
508512
options["ssl"] = True if validate else 'true'
509513
else:
510514
nodes = split_hosts(hosts, default_port=default_port)
515+
if len(nodes) > 1 and options.get('directConnection'):
516+
raise ConfigurationError(
517+
"Cannot specify multiple hosts with directConnection=true")
511518

512519
return {
513520
'nodelist': nodes,

test/discovery_and_monitoring/rs/discover_arbiters.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"description": "Discover arbiters",
3-
"uri": "mongodb://a/?replicaSet=rs",
2+
"description": "Discover arbiters with directConnection URI option",
3+
"uri": "mongodb://a/?directConnection=false",
44
"phases": [
55
{
66
"responses": [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"description": "Discover arbiters with replicaSet URI option",
3+
"uri": "mongodb://a/?replicaSet=rs",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"a:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": true,
12+
"hosts": [
13+
"a:27017"
14+
],
15+
"arbiters": [
16+
"b:27017"
17+
],
18+
"setName": "rs",
19+
"minWireVersion": 0,
20+
"maxWireVersion": 6
21+
}
22+
]
23+
],
24+
"outcome": {
25+
"servers": {
26+
"a:27017": {
27+
"type": "RSPrimary",
28+
"setName": "rs"
29+
},
30+
"b:27017": {
31+
"type": "Unknown",
32+
"setName": null
33+
}
34+
},
35+
"topologyType": "ReplicaSetWithPrimary",
36+
"logicalSessionTimeoutMinutes": null,
37+
"setName": "rs"
38+
}
39+
}
40+
]
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"description": "Discover ghost with directConnection URI option",
3+
"uri": "mongodb://b/?directConnection=false",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"b:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": false,
12+
"isreplicaset": true,
13+
"minWireVersion": 0,
14+
"maxWireVersion": 6
15+
}
16+
]
17+
],
18+
"outcome": {
19+
"servers": {
20+
"b:27017": {
21+
"type": "RSGhost",
22+
"setName": null
23+
}
24+
},
25+
"topologyType": "Unknown",
26+
"logicalSessionTimeoutMinutes": null,
27+
"setName": null
28+
}
29+
}
30+
]
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"description": "Discover ghost with replicaSet URI option",
3+
"uri": "mongodb://a,b/?replicaSet=rs",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"b:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": false,
12+
"isreplicaset": true,
13+
"minWireVersion": 0,
14+
"maxWireVersion": 6
15+
}
16+
]
17+
],
18+
"outcome": {
19+
"servers": {
20+
"a:27017": {
21+
"type": "Unknown",
22+
"setName": null
23+
},
24+
"b:27017": {
25+
"type": "RSGhost",
26+
"setName": null
27+
}
28+
},
29+
"topologyType": "ReplicaSetNoPrimary",
30+
"logicalSessionTimeoutMinutes": null,
31+
"setName": "rs"
32+
}
33+
}
34+
]
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"description": "Discover hidden with directConnection URI option",
3+
"uri": "mongodb://a/?directConnection=false",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"a:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": false,
12+
"secondary": true,
13+
"hidden": true,
14+
"hosts": [
15+
"c:27017",
16+
"d:27017"
17+
],
18+
"setName": "rs",
19+
"minWireVersion": 0,
20+
"maxWireVersion": 6
21+
}
22+
]
23+
],
24+
"outcome": {
25+
"servers": {
26+
"a:27017": {
27+
"type": "RSOther",
28+
"setName": "rs"
29+
},
30+
"c:27017": {
31+
"type": "Unknown",
32+
"setName": null
33+
},
34+
"d:27017": {
35+
"type": "Unknown",
36+
"setName": null
37+
}
38+
},
39+
"topologyType": "ReplicaSetNoPrimary",
40+
"logicalSessionTimeoutMinutes": null,
41+
"setName": "rs"
42+
}
43+
}
44+
]
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"description": "Discover hidden with replicaSet URI option",
3+
"uri": "mongodb://a/?replicaSet=rs",
4+
"phases": [
5+
{
6+
"responses": [
7+
[
8+
"a:27017",
9+
{
10+
"ok": 1,
11+
"ismaster": false,
12+
"secondary": true,
13+
"hidden": true,
14+
"hosts": [
15+
"c:27017",
16+
"d:27017"
17+
],
18+
"setName": "rs",
19+
"minWireVersion": 0,
20+
"maxWireVersion": 6
21+
}
22+
]
23+
],
24+
"outcome": {
25+
"servers": {
26+
"a:27017": {
27+
"type": "RSOther",
28+
"setName": "rs"
29+
},
30+
"c:27017": {
31+
"type": "Unknown",
32+
"setName": null
33+
},
34+
"d:27017": {
35+
"type": "Unknown",
36+
"setName": null
37+
}
38+
},
39+
"topologyType": "ReplicaSetNoPrimary",
40+
"logicalSessionTimeoutMinutes": null,
41+
"setName": "rs"
42+
}
43+
}
44+
]
45+
}

test/discovery_and_monitoring/rs/discover_passives.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"description": "Discover passives",
3-
"uri": "mongodb://a/?replicaSet=rs",
2+
"description": "Discover passives with directConnection URI option",
3+
"uri": "mongodb://a/?directConnection=false",
44
"phases": [
55
{
66
"responses": [

0 commit comments

Comments
 (0)