Skip to content

Commit 8dbf41a

Browse files
committed
PYTHON-1827 Follow-on work for unifying URI options
1 parent 820d884 commit 8dbf41a

File tree

7 files changed

+392
-188
lines changed

7 files changed

+392
-188
lines changed

pymongo/common.py

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -700,25 +700,33 @@ def get_validated_options(options, warn=True):
700700
Returns a copy of options with invalid entries removed.
701701
702702
:Parameters:
703-
- `opts`: A dict of MongoDB URI options.
703+
- `opts`: A dict containing MongoDB URI options.
704704
- `warn` (optional): If ``True`` then warnings will be logged and
705705
invalid options will be ignored. Otherwise, invalid options will
706706
cause errors.
707707
"""
708-
validated_options = {}
708+
if isinstance(options, _CaseInsensitiveDictionary):
709+
validated_options = _CaseInsensitiveDictionary()
710+
get_normed_key = lambda x: x
711+
get_setter_key = lambda x: options.cased_key(x)
712+
else:
713+
validated_options = {}
714+
get_normed_key = lambda x: x.lower()
715+
get_setter_key = lambda x: x
716+
709717
for opt, value in iteritems(options):
710-
lower = opt.lower()
718+
normed_key = get_normed_key(opt)
711719
try:
712720
validator = URI_OPTIONS_VALIDATOR_MAP.get(
713-
lower, raise_config_error)
721+
normed_key, raise_config_error)
714722
value = validator(opt, value)
715723
except (ValueError, TypeError, ConfigurationError) as exc:
716724
if warn:
717725
warnings.warn(str(exc))
718726
else:
719727
raise
720728
else:
721-
validated_options[lower] = value
729+
validated_options[get_setter_key(normed_key)] = value
722730
return validated_options
723731

724732

@@ -814,3 +822,83 @@ def read_concern(self):
814822
.. versionadded:: 3.2
815823
"""
816824
return self.__read_concern
825+
826+
827+
class _CaseInsensitiveDictionary(abc.MutableMapping):
828+
def __init__(self, *args, **kwargs):
829+
self.__casedkeys = {}
830+
self.__data = {}
831+
self.update(dict(*args, **kwargs))
832+
833+
def __contains__(self, key):
834+
return key.lower() in self.__data
835+
836+
def __len__(self):
837+
return len(self.__data)
838+
839+
def __iter__(self):
840+
return (key for key in self.__casedkeys)
841+
842+
def __repr__(self):
843+
return str({self.__casedkeys[k]: self.__data[k] for k in self})
844+
845+
def __setitem__(self, key, value):
846+
lc_key = key.lower()
847+
self.__casedkeys[lc_key] = key
848+
self.__data[lc_key] = value
849+
850+
def __getitem__(self, key):
851+
return self.__data[key.lower()]
852+
853+
def __delitem__(self, key):
854+
lc_key = key.lower()
855+
del self.__casedkeys[lc_key]
856+
del self.__data[lc_key]
857+
858+
def __eq__(self, other):
859+
if not isinstance(other, abc.Mapping):
860+
return NotImplemented
861+
if len(self) != len(other):
862+
return False
863+
for key in other:
864+
if self[key] != other[key]:
865+
return False
866+
867+
return True
868+
869+
def get(self, key, default=None):
870+
return self.__data.get(key.lower(), default)
871+
872+
def pop(self, key, *args, **kwargs):
873+
lc_key = key.lower()
874+
self.__casedkeys.pop(lc_key, None)
875+
return self.__data.pop(lc_key, *args, **kwargs)
876+
877+
def popitem(self):
878+
lc_key, cased_key = self.__casedkeys.popitem()
879+
value = self.__data.pop(lc_key)
880+
return cased_key, value
881+
882+
def clear(self):
883+
self.__casedkeys.clear()
884+
self.__data.clear()
885+
886+
def setdefault(self, key, default=None):
887+
lc_key = key.lower()
888+
if key in self:
889+
return self.__data[lc_key]
890+
else:
891+
self.__casedkeys[lc_key] = key
892+
self.__data[lc_key] = default
893+
return default
894+
895+
def update(self, other):
896+
if isinstance(other, _CaseInsensitiveDictionary):
897+
for key in other:
898+
self[other.cased_key(key)] = other[key]
899+
else:
900+
for key in other:
901+
self[key] = other[key]
902+
903+
def cased_key(self, key):
904+
return self.__casedkeys[key.lower()]

pymongo/mongo_client.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
from collections import defaultdict
4141

42-
from bson.codec_options import DEFAULT_CODEC_OPTIONS, TypeRegistry
42+
from bson.codec_options import DEFAULT_CODEC_OPTIONS
4343
from bson.py3compat import (integer_types,
4444
string_type)
4545
from bson.son import SON
@@ -71,8 +71,8 @@
7171
from pymongo.topology import Topology
7272
from pymongo.topology_description import TOPOLOGY_TYPE
7373
from pymongo.settings import TopologySettings
74-
from pymongo.uri_parser import (_CaseInsensitiveDictionary,
75-
_handle_option_deprecations,
74+
from pymongo.uri_parser import (_handle_option_deprecations,
75+
_handle_security_options,
7676
_normalize_options)
7777
from pymongo.write_concern import DEFAULT_WRITE_CONCERN
7878

@@ -588,7 +588,7 @@ def __init__(
588588
for entity in host:
589589
if "://" in entity:
590590
res = uri_parser.parse_uri(
591-
entity, port, validate=True, warn=True)
591+
entity, port, validate=True, warn=True, normalize=False)
592592
seeds.update(res["nodelist"])
593593
username = res["username"] or username
594594
password = res["password"] or password
@@ -605,7 +605,7 @@ def __init__(
605605
monitor_class = kwargs.pop('_monitor_class', None)
606606
condition_class = kwargs.pop('_condition_class', None)
607607

608-
keyword_opts = kwargs
608+
keyword_opts = common._CaseInsensitiveDictionary(kwargs)
609609
keyword_opts['document_class'] = document_class
610610
if type_registry is not None:
611611
keyword_opts['type_registry'] = type_registry
@@ -616,15 +616,19 @@ def __init__(
616616
keyword_opts['tz_aware'] = tz_aware
617617
keyword_opts['connect'] = connect
618618

619-
# Validate kwargs options.
620-
keyword_opts = _CaseInsensitiveDictionary(
621-
dict(common.validate(k, v) for k, v in keyword_opts.items()))
622-
# Handle deprecated options in kwarg list.
619+
# Handle deprecated options in kwarg options.
623620
keyword_opts = _handle_option_deprecations(keyword_opts)
624-
# Change kwarg option names to those used internally.
625-
keyword_opts = _normalize_options(keyword_opts)
626-
# Augment URI options with kwarg options, overriding the former.
621+
# Validate kwarg options.
622+
keyword_opts = common._CaseInsensitiveDictionary(
623+
dict(common.validate(k, v) for k, v in keyword_opts.items()))
624+
625+
# Override connection string options with kwarg options.
627626
opts.update(keyword_opts)
627+
# Handle security-option conflicts in combined options.
628+
opts = _handle_security_options(opts)
629+
# Normalize combined options.
630+
opts = _normalize_options(opts)
631+
628632
# Username and password passed as kwargs override user info in URI.
629633
username = opts.get("username", username)
630634
password = opts.get("password", password)

0 commit comments

Comments
 (0)