Skip to content

Commit 947fbe3

Browse files
authored
PYTHON-5421 Make parse_uri() return "options" as a dict rather than _CaseInsensitiveDictionary (#2413)
1 parent 2eb18f1 commit 947fbe3

File tree

7 files changed

+91
-23
lines changed

7 files changed

+91
-23
lines changed

pymongo/asynchronous/uri_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
SCHEME_LEN,
3030
SRV_SCHEME_LEN,
3131
_check_options,
32+
_make_options_case_sensitive,
3233
_validate_uri,
3334
split_hosts,
3435
split_options,
@@ -113,6 +114,7 @@ async def parse_uri(
113114
srv_max_hosts,
114115
)
115116
)
117+
result["options"] = _make_options_case_sensitive(result["options"])
116118
return result
117119

118120

pymongo/synchronous/uri_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
SCHEME_LEN,
3030
SRV_SCHEME_LEN,
3131
_check_options,
32+
_make_options_case_sensitive,
3233
_validate_uri,
3334
split_hosts,
3435
split_options,
@@ -113,6 +114,7 @@ def parse_uri(
113114
srv_max_hosts,
114115
)
115116
)
117+
result["options"] = _make_options_case_sensitive(result["options"])
116118
return result
117119

118120

pymongo/uri_parser_shared.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,57 @@
5454
SRV_SCHEME_LEN = len(SRV_SCHEME)
5555
DEFAULT_PORT = 27017
5656

57+
URI_OPTIONS = frozenset(
58+
[
59+
"appname",
60+
"authMechanism",
61+
"authMechanismProperties",
62+
"authSource",
63+
"compressors",
64+
"connectTimeoutMS",
65+
"directConnection",
66+
"heartbeatFrequencyMS",
67+
"journal",
68+
"loadBalanced",
69+
"localThresholdMS",
70+
"maxIdleTimeMS",
71+
"maxPoolSize",
72+
"maxConnecting",
73+
"maxStalenessSeconds",
74+
"minPoolSize",
75+
"proxyHost",
76+
"proxyPort",
77+
"proxyUsername",
78+
"proxyPassword",
79+
"readConcernLevel",
80+
"readPreference",
81+
"readPreferenceTags",
82+
"replicaSet",
83+
"retryReads",
84+
"retryWrites",
85+
"serverMonitoringMode",
86+
"serverSelectionTimeoutMS",
87+
"serverSelectionTryOnce",
88+
"socketTimeoutMS",
89+
"srvMaxHosts",
90+
"srvServiceName",
91+
"ssl",
92+
"tls",
93+
"tlsAllowInvalidCertificates",
94+
"tlsAllowInvalidHostnames",
95+
"tlsCAFile",
96+
"tlsCertificateKeyFile",
97+
"tlsCertificateKeyFilePassword",
98+
"tlsDisableCertificateRevocationCheck",
99+
"tlsDisableOCSPEndpointCheck",
100+
"tlsInsecure",
101+
"w",
102+
"waitQueueTimeoutMS",
103+
"wTimeoutMS",
104+
"zlibCompressionLevel",
105+
]
106+
)
107+
57108

58109
def _unquoted_percent(s: str) -> bool:
59110
"""Check for unescaped percent signs.
@@ -550,3 +601,14 @@ def _validate_uri(
550601
"options": options,
551602
"fqdn": fqdn,
552603
}
604+
605+
606+
def _make_options_case_sensitive(options: _CaseInsensitiveDictionary) -> dict[str, Any]:
607+
case_sensitive = {}
608+
for option in URI_OPTIONS:
609+
if option.lower() in options:
610+
case_sensitive[option] = options[option]
611+
options.pop(option)
612+
for k, v in options.items():
613+
case_sensitive[k] = v
614+
return case_sensitive

test/asynchronous/test_discovery_and_monitoring.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ async def create_mock_topology(uri, monitor_class=DummyMonitor):
9191
replica_set_name = None
9292
direct_connection = None
9393
load_balanced = None
94-
if "replicaset" in parsed_uri["options"]:
95-
replica_set_name = parsed_uri["options"]["replicaset"]
94+
if "replicaSet" in parsed_uri["options"]:
95+
replica_set_name = parsed_uri["options"]["replicaSet"]
9696
if "directConnection" in parsed_uri["options"]:
9797
direct_connection = parsed_uri["options"]["directConnection"]
9898
if "loadBalanced" in parsed_uri["options"]:

test/test_discovery_and_monitoring.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ def create_mock_topology(uri, monitor_class=DummyMonitor):
9191
replica_set_name = None
9292
direct_connection = None
9393
load_balanced = None
94-
if "replicaset" in parsed_uri["options"]:
95-
replica_set_name = parsed_uri["options"]["replicaset"]
94+
if "replicaSet" in parsed_uri["options"]:
95+
replica_set_name = parsed_uri["options"]["replicaSet"]
9696
if "directConnection" in parsed_uri["options"]:
9797
direct_connection = parsed_uri["options"]["directConnection"]
9898
if "loadBalanced" in parsed_uri["options"]:

test/test_uri_parser.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ def test_split_options(self):
142142
self.assertTrue(split_options("wtimeoutms=500"))
143143
self.assertEqual({"fsync": True}, split_options("fsync=true"))
144144
self.assertEqual({"fsync": False}, split_options("fsync=false"))
145-
self.assertEqual({"authmechanism": "GSSAPI"}, split_options("authMechanism=GSSAPI"))
145+
self.assertEqual({"authMechanism": "GSSAPI"}, split_options("authMechanism=GSSAPI"))
146146
self.assertEqual(
147-
{"authmechanism": "SCRAM-SHA-1"}, split_options("authMechanism=SCRAM-SHA-1")
147+
{"authMechanism": "SCRAM-SHA-1"}, split_options("authMechanism=SCRAM-SHA-1")
148148
)
149149
self.assertEqual({"authsource": "foobar"}, split_options("authSource=foobar"))
150150
self.assertEqual({"maxpoolsize": 50}, split_options("maxpoolsize=50"))
@@ -290,20 +290,20 @@ def test_parse_uri(self):
290290
self.assertEqual(res, parse_uri('mongodb://localhost/test.name/with "delimiters'))
291291

292292
res = copy.deepcopy(orig)
293-
res["options"] = {"readpreference": ReadPreference.SECONDARY.mongos_mode}
293+
res["options"] = {"readPreference": ReadPreference.SECONDARY.mongos_mode}
294294
self.assertEqual(res, parse_uri("mongodb://localhost/?readPreference=secondary"))
295295

296296
# Various authentication tests
297297
res = copy.deepcopy(orig)
298-
res["options"] = {"authmechanism": "SCRAM-SHA-256"}
298+
res["options"] = {"authMechanism": "SCRAM-SHA-256"}
299299
res["username"] = "user"
300300
res["password"] = "password"
301301
self.assertEqual(
302302
res, parse_uri("mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256")
303303
)
304304

305305
res = copy.deepcopy(orig)
306-
res["options"] = {"authmechanism": "SCRAM-SHA-256", "authsource": "bar"}
306+
res["options"] = {"authMechanism": "SCRAM-SHA-256", "authSource": "bar"}
307307
res["username"] = "user"
308308
res["password"] = "password"
309309
res["database"] = "foo"
@@ -315,7 +315,7 @@ def test_parse_uri(self):
315315
)
316316

317317
res = copy.deepcopy(orig)
318-
res["options"] = {"authmechanism": "SCRAM-SHA-256"}
318+
res["options"] = {"authMechanism": "SCRAM-SHA-256"}
319319
res["username"] = "user"
320320
res["password"] = ""
321321
self.assertEqual(res, parse_uri("mongodb://user:@localhost/?authMechanism=SCRAM-SHA-256"))
@@ -327,7 +327,7 @@ def test_parse_uri(self):
327327
self.assertEqual(res, parse_uri("mongodb://user%40domain.com:password@localhost/foo"))
328328

329329
res = copy.deepcopy(orig)
330-
res["options"] = {"authmechanism": "GSSAPI"}
330+
res["options"] = {"authMechanism": "GSSAPI"}
331331
res["username"] = "[email protected]"
332332
res["password"] = "password"
333333
res["database"] = "foo"
@@ -337,7 +337,7 @@ def test_parse_uri(self):
337337
)
338338

339339
res = copy.deepcopy(orig)
340-
res["options"] = {"authmechanism": "GSSAPI"}
340+
res["options"] = {"authMechanism": "GSSAPI"}
341341
res["username"] = "[email protected]"
342342
res["password"] = ""
343343
res["database"] = "foo"
@@ -347,8 +347,8 @@ def test_parse_uri(self):
347347

348348
res = copy.deepcopy(orig)
349349
res["options"] = {
350-
"readpreference": ReadPreference.SECONDARY.mongos_mode,
351-
"readpreferencetags": [
350+
"readPreference": ReadPreference.SECONDARY.mongos_mode,
351+
"readPreferenceTags": [
352352
{"dc": "west", "use": "website"},
353353
{"dc": "east", "use": "website"},
354354
],
@@ -368,8 +368,8 @@ def test_parse_uri(self):
368368

369369
res = copy.deepcopy(orig)
370370
res["options"] = {
371-
"readpreference": ReadPreference.SECONDARY.mongos_mode,
372-
"readpreferencetags": [
371+
"readPreference": ReadPreference.SECONDARY.mongos_mode,
372+
"readPreferenceTags": [
373373
{"dc": "west", "use": "website"},
374374
{"dc": "east", "use": "website"},
375375
{},
@@ -462,6 +462,7 @@ def test_tlsinsecure_simple(self):
462462
"tlsInsecure": True,
463463
"tlsDisableOCSPEndpointCheck": True,
464464
}
465+
print(parse_uri(uri)["options"])
465466
self.assertEqual(res, parse_uri(uri)["options"])
466467

467468
def test_normalize_options(self):
@@ -479,8 +480,8 @@ def test_unquote_during_parsing(self):
479480
)
480481
res = parse_uri(uri)
481482
options: dict[str, Any] = {
482-
"authmechanism": "MONGODB-AWS",
483-
"authmechanismproperties": {"AWS_SESSION_TOKEN": unquoted_val},
483+
"authMechanism": "MONGODB-AWS",
484+
"authMechanismProperties": {"AWS_SESSION_TOKEN": unquoted_val},
484485
}
485486
self.assertEqual(options, res["options"])
486487

@@ -491,8 +492,8 @@ def test_unquote_during_parsing(self):
491492
)
492493
res = parse_uri(uri)
493494
options = {
494-
"readpreference": ReadPreference.SECONDARY.mongos_mode,
495-
"readpreferencetags": [
495+
"readPreference": ReadPreference.SECONDARY.mongos_mode,
496+
"readPreferenceTags": [
496497
{"dc": "west", unquoted_val: unquoted_val},
497498
{"dc": "east", "use": unquoted_val},
498499
],
@@ -519,7 +520,7 @@ def test_handle_colon(self):
519520
)
520521
res = parse_uri(uri)
521522
options = {
522-
"authmechanism": "MONGODB-AWS",
523+
"authMechanism": "MONGODB-AWS",
523524
"authMechanismProperties": {"AWS_SESSION_TOKEN": token},
524525
}
525526
self.assertEqual(options, res["options"])

test/test_uri_spec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from test import unittest
2828
from test.helpers import clear_warning_registry
2929

30-
from pymongo.common import INTERNAL_URI_OPTION_NAME_MAP, validate
30+
from pymongo.common import INTERNAL_URI_OPTION_NAME_MAP, _CaseInsensitiveDictionary, validate
3131
from pymongo.compression_support import _have_snappy
3232
from pymongo.synchronous.uri_parser import parse_uri
3333

@@ -169,7 +169,8 @@ def run_scenario(self):
169169
# Compare URI options.
170170
err_msg = "For option %s expected %s but got %s"
171171
if test["options"]:
172-
opts = options["options"]
172+
opts = _CaseInsensitiveDictionary()
173+
opts.update(options["options"])
173174
for opt in test["options"]:
174175
lopt = opt.lower()
175176
optname = INTERNAL_URI_OPTION_NAME_MAP.get(lopt, lopt)

0 commit comments

Comments
 (0)