diff --git a/pymongo/asynchronous/uri_parser.py b/pymongo/asynchronous/uri_parser.py index 47c6d72031..11a6a6299c 100644 --- a/pymongo/asynchronous/uri_parser.py +++ b/pymongo/asynchronous/uri_parser.py @@ -29,6 +29,7 @@ SCHEME_LEN, SRV_SCHEME_LEN, _check_options, + _make_options_case_sensitive, _validate_uri, split_hosts, split_options, @@ -113,6 +114,7 @@ async def parse_uri( srv_max_hosts, ) ) + result["options"] = _make_options_case_sensitive(result["options"]) return result diff --git a/pymongo/synchronous/uri_parser.py b/pymongo/synchronous/uri_parser.py index 52b59b8fe8..da0f86d720 100644 --- a/pymongo/synchronous/uri_parser.py +++ b/pymongo/synchronous/uri_parser.py @@ -29,6 +29,7 @@ SCHEME_LEN, SRV_SCHEME_LEN, _check_options, + _make_options_case_sensitive, _validate_uri, split_hosts, split_options, @@ -113,6 +114,7 @@ def parse_uri( srv_max_hosts, ) ) + result["options"] = _make_options_case_sensitive(result["options"]) return result diff --git a/pymongo/uri_parser_shared.py b/pymongo/uri_parser_shared.py index 0cef176bf1..59168d1e9f 100644 --- a/pymongo/uri_parser_shared.py +++ b/pymongo/uri_parser_shared.py @@ -54,6 +54,57 @@ SRV_SCHEME_LEN = len(SRV_SCHEME) DEFAULT_PORT = 27017 +URI_OPTIONS = frozenset( + [ + "appname", + "authMechanism", + "authMechanismProperties", + "authSource", + "compressors", + "connectTimeoutMS", + "directConnection", + "heartbeatFrequencyMS", + "journal", + "loadBalanced", + "localThresholdMS", + "maxIdleTimeMS", + "maxPoolSize", + "maxConnecting", + "maxStalenessSeconds", + "minPoolSize", + "proxyHost", + "proxyPort", + "proxyUsername", + "proxyPassword", + "readConcernLevel", + "readPreference", + "readPreferenceTags", + "replicaSet", + "retryReads", + "retryWrites", + "serverMonitoringMode", + "serverSelectionTimeoutMS", + "serverSelectionTryOnce", + "socketTimeoutMS", + "srvMaxHosts", + "srvServiceName", + "ssl", + "tls", + "tlsAllowInvalidCertificates", + "tlsAllowInvalidHostnames", + "tlsCAFile", + "tlsCertificateKeyFile", + "tlsCertificateKeyFilePassword", + "tlsDisableCertificateRevocationCheck", + "tlsDisableOCSPEndpointCheck", + "tlsInsecure", + "w", + "waitQueueTimeoutMS", + "wTimeoutMS", + "zlibCompressionLevel", + ] +) + def _unquoted_percent(s: str) -> bool: """Check for unescaped percent signs. @@ -550,3 +601,14 @@ def _validate_uri( "options": options, "fqdn": fqdn, } + + +def _make_options_case_sensitive(options: _CaseInsensitiveDictionary) -> dict[str, Any]: + case_sensitive = {} + for option in URI_OPTIONS: + if option.lower() in options: + case_sensitive[option] = options[option] + options.pop(option) + for k, v in options.items(): + case_sensitive[k] = v + return case_sensitive diff --git a/test/asynchronous/test_discovery_and_monitoring.py b/test/asynchronous/test_discovery_and_monitoring.py index 70348c8daf..a41e012f36 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -90,8 +90,8 @@ async def create_mock_topology(uri, monitor_class=DummyMonitor): replica_set_name = None direct_connection = None load_balanced = None - if "replicaset" in parsed_uri["options"]: - replica_set_name = parsed_uri["options"]["replicaset"] + if "replicaSet" in parsed_uri["options"]: + replica_set_name = parsed_uri["options"]["replicaSet"] if "directConnection" in parsed_uri["options"]: direct_connection = parsed_uri["options"]["directConnection"] if "loadBalanced" in parsed_uri["options"]: diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index a0dabaaf8e..f13e940f60 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -90,8 +90,8 @@ def create_mock_topology(uri, monitor_class=DummyMonitor): replica_set_name = None direct_connection = None load_balanced = None - if "replicaset" in parsed_uri["options"]: - replica_set_name = parsed_uri["options"]["replicaset"] + if "replicaSet" in parsed_uri["options"]: + replica_set_name = parsed_uri["options"]["replicaSet"] if "directConnection" in parsed_uri["options"]: direct_connection = parsed_uri["options"]["directConnection"] if "loadBalanced" in parsed_uri["options"]: diff --git a/test/test_uri_parser.py b/test/test_uri_parser.py index ec1c6c164c..ed1a53ea26 100644 --- a/test/test_uri_parser.py +++ b/test/test_uri_parser.py @@ -142,9 +142,9 @@ def test_split_options(self): self.assertTrue(split_options("wtimeoutms=500")) self.assertEqual({"fsync": True}, split_options("fsync=true")) self.assertEqual({"fsync": False}, split_options("fsync=false")) - self.assertEqual({"authmechanism": "GSSAPI"}, split_options("authMechanism=GSSAPI")) + self.assertEqual({"authMechanism": "GSSAPI"}, split_options("authMechanism=GSSAPI")) self.assertEqual( - {"authmechanism": "SCRAM-SHA-1"}, split_options("authMechanism=SCRAM-SHA-1") + {"authMechanism": "SCRAM-SHA-1"}, split_options("authMechanism=SCRAM-SHA-1") ) self.assertEqual({"authsource": "foobar"}, split_options("authSource=foobar")) self.assertEqual({"maxpoolsize": 50}, split_options("maxpoolsize=50")) @@ -290,12 +290,12 @@ def test_parse_uri(self): self.assertEqual(res, parse_uri('mongodb://localhost/test.name/with "delimiters')) res = copy.deepcopy(orig) - res["options"] = {"readpreference": ReadPreference.SECONDARY.mongos_mode} + res["options"] = {"readPreference": ReadPreference.SECONDARY.mongos_mode} self.assertEqual(res, parse_uri("mongodb://localhost/?readPreference=secondary")) # Various authentication tests res = copy.deepcopy(orig) - res["options"] = {"authmechanism": "SCRAM-SHA-256"} + res["options"] = {"authMechanism": "SCRAM-SHA-256"} res["username"] = "user" res["password"] = "password" self.assertEqual( @@ -303,7 +303,7 @@ def test_parse_uri(self): ) res = copy.deepcopy(orig) - res["options"] = {"authmechanism": "SCRAM-SHA-256", "authsource": "bar"} + res["options"] = {"authMechanism": "SCRAM-SHA-256", "authSource": "bar"} res["username"] = "user" res["password"] = "password" res["database"] = "foo" @@ -315,7 +315,7 @@ def test_parse_uri(self): ) res = copy.deepcopy(orig) - res["options"] = {"authmechanism": "SCRAM-SHA-256"} + res["options"] = {"authMechanism": "SCRAM-SHA-256"} res["username"] = "user" res["password"] = "" self.assertEqual(res, parse_uri("mongodb://user:@localhost/?authMechanism=SCRAM-SHA-256")) @@ -327,7 +327,7 @@ def test_parse_uri(self): self.assertEqual(res, parse_uri("mongodb://user%40domain.com:password@localhost/foo")) res = copy.deepcopy(orig) - res["options"] = {"authmechanism": "GSSAPI"} + res["options"] = {"authMechanism": "GSSAPI"} res["username"] = "user@domain.com" res["password"] = "password" res["database"] = "foo" @@ -337,7 +337,7 @@ def test_parse_uri(self): ) res = copy.deepcopy(orig) - res["options"] = {"authmechanism": "GSSAPI"} + res["options"] = {"authMechanism": "GSSAPI"} res["username"] = "user@domain.com" res["password"] = "" res["database"] = "foo" @@ -347,8 +347,8 @@ def test_parse_uri(self): res = copy.deepcopy(orig) res["options"] = { - "readpreference": ReadPreference.SECONDARY.mongos_mode, - "readpreferencetags": [ + "readPreference": ReadPreference.SECONDARY.mongos_mode, + "readPreferenceTags": [ {"dc": "west", "use": "website"}, {"dc": "east", "use": "website"}, ], @@ -368,8 +368,8 @@ def test_parse_uri(self): res = copy.deepcopy(orig) res["options"] = { - "readpreference": ReadPreference.SECONDARY.mongos_mode, - "readpreferencetags": [ + "readPreference": ReadPreference.SECONDARY.mongos_mode, + "readPreferenceTags": [ {"dc": "west", "use": "website"}, {"dc": "east", "use": "website"}, {}, @@ -462,6 +462,7 @@ def test_tlsinsecure_simple(self): "tlsInsecure": True, "tlsDisableOCSPEndpointCheck": True, } + print(parse_uri(uri)["options"]) self.assertEqual(res, parse_uri(uri)["options"]) def test_normalize_options(self): @@ -479,8 +480,8 @@ def test_unquote_during_parsing(self): ) res = parse_uri(uri) options: dict[str, Any] = { - "authmechanism": "MONGODB-AWS", - "authmechanismproperties": {"AWS_SESSION_TOKEN": unquoted_val}, + "authMechanism": "MONGODB-AWS", + "authMechanismProperties": {"AWS_SESSION_TOKEN": unquoted_val}, } self.assertEqual(options, res["options"]) @@ -491,8 +492,8 @@ def test_unquote_during_parsing(self): ) res = parse_uri(uri) options = { - "readpreference": ReadPreference.SECONDARY.mongos_mode, - "readpreferencetags": [ + "readPreference": ReadPreference.SECONDARY.mongos_mode, + "readPreferenceTags": [ {"dc": "west", unquoted_val: unquoted_val}, {"dc": "east", "use": unquoted_val}, ], @@ -519,7 +520,7 @@ def test_handle_colon(self): ) res = parse_uri(uri) options = { - "authmechanism": "MONGODB-AWS", + "authMechanism": "MONGODB-AWS", "authMechanismProperties": {"AWS_SESSION_TOKEN": token}, } self.assertEqual(options, res["options"]) diff --git a/test/test_uri_spec.py b/test/test_uri_spec.py index aeb0be94b5..8f673cff4c 100644 --- a/test/test_uri_spec.py +++ b/test/test_uri_spec.py @@ -27,7 +27,7 @@ from test import unittest from test.helpers import clear_warning_registry -from pymongo.common import INTERNAL_URI_OPTION_NAME_MAP, validate +from pymongo.common import INTERNAL_URI_OPTION_NAME_MAP, _CaseInsensitiveDictionary, validate from pymongo.compression_support import _have_snappy from pymongo.synchronous.uri_parser import parse_uri @@ -169,7 +169,8 @@ def run_scenario(self): # Compare URI options. err_msg = "For option %s expected %s but got %s" if test["options"]: - opts = options["options"] + opts = _CaseInsensitiveDictionary() + opts.update(options["options"]) for opt in test["options"]: lopt = opt.lower() optname = INTERNAL_URI_OPTION_NAME_MAP.get(lopt, lopt)