diff --git a/pymongo/asynchronous/uri_parser.py b/pymongo/asynchronous/uri_parser.py index 47c6d7203..11a6a6299 100644 --- a/pymongo/asynchronous/uri_parser.py +++ b/pymongo/asynchronous/uri_parser.py @@ -29,6 +29,7 @@ from pymongo.uri_parser_shared import ( 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 52b59b8fe..da0f86d72 100644 --- a/pymongo/synchronous/uri_parser.py +++ b/pymongo/synchronous/uri_parser.py @@ -29,6 +29,7 @@ from pymongo.uri_parser_shared import ( 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 0cef176bf..59168d1e9 100644 --- a/pymongo/uri_parser_shared.py +++ b/pymongo/uri_parser_shared.py @@ -54,6 +54,57 @@ SRV_SCHEME = "mongodb+srv://" 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 46799201f..2798afe7d 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -91,8 +91,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 83a4adf17..4f8ee30d1 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -91,8 +91,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 ec1c6c164..ed1a53ea2 100644 --- a/test/test_uri_parser.py +++ b/test/test_uri_parser.py @@ -142,9 +142,9 @@ class TestURI(unittest.TestCase): 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 @@ class TestURI(unittest.TestCase): 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 @@ class TestURI(unittest.TestCase): ) 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 @@ class TestURI(unittest.TestCase): ) 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 @@ class TestURI(unittest.TestCase): 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 @@ class TestURI(unittest.TestCase): ) 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 @@ class TestURI(unittest.TestCase): 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 @@ class TestURI(unittest.TestCase): 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 @@ class TestURI(unittest.TestCase): "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 @@ class TestURI(unittest.TestCase): ) 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 @@ class TestURI(unittest.TestCase): ) 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 @@ class TestURI(unittest.TestCase): ) 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 aeb0be94b..8f673cff4 100644 --- a/test/test_uri_spec.py +++ b/test/test_uri_spec.py @@ -27,7 +27,7 @@ sys.path[0:0] = [""] 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 create_test(test, test_workdir): # 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)