Skip to content

Commit 4cdd345

Browse files
PYTHON-1670: Implement Unified URI Options (#386)
1 parent 41b4234 commit 4cdd345

22 files changed

+1092
-221
lines changed

doc/changelog.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ Changes in Version 3.8.0
1616
- :class:`~bson.objectid.ObjectId` now implements the `ObjectID specification
1717
version 0.2 <https://github.com/mongodb/specifications/blob/master/source/objectid.rst>`_.
1818

19+
20+
- Version 3.8.0 implements the `URI options specification`_ in the
21+
:meth:`~pymongo.mongo_client.MongoClient` constructor. Consequently, there are
22+
a number of changes in connection options:
23+
24+
- The ``tlsInsecure`` option has been added.
25+
- The ``tls`` option has been added. The older ``ssl`` option has been retained
26+
as an alias to the new ``tls`` option.
27+
- ``wTimeout`` has been deprecated in favor of ``wTimeoutMS``.
28+
- ``wTimeoutMS`` now overrides ``wTimeout`` if the user provides both.
29+
- ``j`` has been deprecated in favor of ``journal``.
30+
- ``journal`` now overrides ``j`` if the user provides both.
31+
- ``ssl_cert_reqs`` has been deprecated in favor of ``tlsAllowInvalidCertificates``.
32+
Instead of ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` and ``ssl.CERT_REQUIRED``, the
33+
new option expects a boolean value - ``True`` is equivalent to ``ssl.CERT_NONE``,
34+
while ``False`` is equivalent to ``ssl.CERT_REQUIRED``.
35+
- ``ssl_match_hostname`` has been deprecated in favor of ``tlsAllowInvalidHostnames``.
36+
- ``ssl_ca_certs`` has been deprecated in favor of ``tlsCAFile``.
37+
- ``ssl_certfile`` has been deprecated in favor of ``tlsCertificateKeyFile``.
38+
- ``ssl_pem_passphrase`` has been deprecated in favor of ``tlsCertificateKeyFilePassword``.
39+
40+
41+
.. _URI options specification: https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst`
42+
43+
1944
Issues Resolved
2045
...............
2146

pymongo/client_options.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ def _parse_read_preference(options):
5555
def _parse_write_concern(options):
5656
"""Parse write concern options."""
5757
concern = options.get('w')
58-
wtimeout = options.get('wtimeout', options.get('wtimeoutms'))
59-
j = options.get('j', options.get('journal'))
58+
wtimeout = options.get('wtimeoutms')
59+
j = options.get('journal')
6060
fsync = options.get('fsync')
6161
return WriteConcern(concern, wtimeout, j, fsync)
6262

pymongo/common.py

Lines changed: 123 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
from pymongo.monitoring import _validate_event_listeners
3333
from pymongo.read_concern import ReadConcern
3434
from pymongo.read_preferences import _MONGOS_MODES, _ServerMode
35-
from pymongo.ssl_support import validate_cert_reqs
35+
from pymongo.ssl_support import (validate_cert_reqs,
36+
validate_allow_invalid_certs)
3637
from pymongo.write_concern import DEFAULT_WRITE_CONCERN, WriteConcern
3738

3839
try:
@@ -524,56 +525,79 @@ def validate_tzinfo(dummy, value):
524525
return value
525526

526527

527-
# journal is an alias for j,
528-
# wtimeoutms is an alias for wtimeout,
529-
URI_VALIDATORS = {
530-
'replicaset': validate_string_or_none,
531-
'w': validate_non_negative_int_or_basestring,
532-
'wtimeout': validate_non_negative_integer,
533-
'wtimeoutms': validate_non_negative_integer,
534-
'fsync': validate_boolean_or_string,
535-
'j': validate_boolean_or_string,
528+
# Dictionary where keys are the names of public URI options, and values
529+
# are lists of aliases for that option. Aliases of option names are assumed
530+
# to have been deprecated.
531+
URI_OPTIONS_ALIAS_MAP = {
532+
'journal': ['j'],
533+
'wtimeoutms': ['wtimeout'],
534+
'tls': ['ssl'],
535+
'tlsallowinvalidcertificates': ['ssl_cert_reqs'],
536+
'tlsallowinvalidhostnames': ['ssl_match_hostname'],
537+
'tlscrlfile': ['ssl_crlfile'],
538+
'tlscafile': ['ssl_ca_certs'],
539+
'tlscertificatekeyfile': ['ssl_certfile'],
540+
'tlscertificatekeyfilepassword': ['ssl_pem_passphrase'],
541+
}
542+
543+
# Dictionary where keys are the names of URI options, and values
544+
# are functions that validate user-input values for that option. If an option
545+
# alias uses a different validator than its public counterpart, it should be
546+
# included here as a key, value pair.
547+
URI_OPTIONS_VALIDATOR_MAP = {
548+
'appname': validate_appname_or_none,
549+
'authmechanism': validate_auth_mechanism,
550+
'authmechanismproperties': validate_auth_mechanism_properties,
551+
'authsource': validate_string,
552+
'compressors': validate_compressors,
553+
'connecttimeoutms': validate_timeout_or_none,
554+
'heartbeatfrequencyms': validate_timeout_or_none,
536555
'journal': validate_boolean_or_string,
556+
'localthresholdms': validate_positive_float_or_zero,
557+
'maxidletimems': validate_timeout_or_none,
537558
'maxpoolsize': validate_positive_integer_or_none,
538-
'socketkeepalive': validate_boolean_or_string,
539-
'waitqueuemultiple': validate_non_negative_integer_or_none,
540-
'ssl': validate_boolean_or_string,
541-
'ssl_keyfile': validate_readable,
542-
'ssl_certfile': validate_readable,
543-
'ssl_pem_passphrase': validate_string_or_none,
544-
'ssl_cert_reqs': validate_cert_reqs,
545-
'ssl_ca_certs': validate_readable,
546-
'ssl_match_hostname': validate_boolean_or_string,
547-
'ssl_crlfile': validate_readable,
559+
'maxstalenessseconds': validate_max_staleness,
548560
'readconcernlevel': validate_string_or_none,
549561
'readpreference': validate_read_preference_mode,
550562
'readpreferencetags': validate_read_preference_tags,
551-
'localthresholdms': validate_positive_float_or_zero,
552-
'authmechanism': validate_auth_mechanism,
553-
'authsource': validate_string,
554-
'authmechanismproperties': validate_auth_mechanism_properties,
555-
'tz_aware': validate_boolean_or_string,
556-
'uuidrepresentation': validate_uuid_representation,
557-
'connect': validate_boolean_or_string,
558-
'minpoolsize': validate_non_negative_integer,
559-
'appname': validate_appname_or_none,
560-
'driver': validate_driver_or_none,
561-
'unicode_decode_error_handler': validate_unicode_decode_error_handler,
563+
'replicaset': validate_string_or_none,
562564
'retrywrites': validate_boolean_or_string,
563-
'compressors': validate_compressors,
565+
'serverselectiontimeoutms': validate_timeout_or_zero,
566+
'sockettimeoutms': validate_timeout_or_none,
567+
'ssl_keyfile': validate_readable,
568+
'tls': validate_boolean_or_string,
569+
'tlsallowinvalidcertificates': validate_allow_invalid_certs,
570+
'ssl_cert_reqs': validate_cert_reqs,
571+
'tlsallowinvalidhostnames': lambda *x: not validate_boolean_or_string(*x),
572+
'ssl_match_hostname': validate_boolean_or_string,
573+
'tlscafile': validate_readable,
574+
'tlscertificatekeyfile': validate_readable,
575+
'tlscertificatekeyfilepassword': validate_string_or_none,
576+
'tlsinsecure': validate_boolean_or_string,
577+
'w': validate_non_negative_int_or_basestring,
578+
'wtimeoutms': validate_non_negative_integer,
564579
'zlibcompressionlevel': validate_zlib_compression_level,
565580
}
566581

567-
TIMEOUT_VALIDATORS = {
568-
'connecttimeoutms': validate_timeout_or_none,
569-
'sockettimeoutms': validate_timeout_or_none,
582+
# Dictionary where keys are the names of URI options specific to pymongo,
583+
# and values are functions that validate user-input values for those options.
584+
NONSPEC_OPTIONS_VALIDATOR_MAP = {
585+
'connect': validate_boolean_or_string,
586+
'driver': validate_driver_or_none,
587+
'fsync': validate_boolean_or_string,
588+
'minpoolsize': validate_non_negative_integer,
589+
'socketkeepalive': validate_boolean_or_string,
590+
'tlscrlfile': validate_readable,
591+
'tz_aware': validate_boolean_or_string,
592+
'unicode_decode_error_handler': validate_unicode_decode_error_handler,
593+
'uuidrepresentation': validate_uuid_representation,
594+
'waitqueuemultiple': validate_non_negative_integer_or_none,
570595
'waitqueuetimeoutms': validate_timeout_or_none,
571-
'serverselectiontimeoutms': validate_timeout_or_zero,
572-
'heartbeatfrequencyms': validate_timeout_or_none,
573-
'maxidletimems': validate_timeout_or_none,
574-
'maxstalenessseconds': validate_max_staleness,
575596
}
576597

598+
# Dictionary where keys are the names of keyword-only options for the
599+
# MongoClient constructor, and values are functions that validate user-input
600+
# values for those options.
577601
KW_VALIDATORS = {
578602
'document_class': validate_document_class,
579603
'read_preference': validate_read_preference,
@@ -584,10 +608,57 @@ def validate_tzinfo(dummy, value):
584608
'server_selector': validate_is_callable_or_none,
585609
}
586610

587-
URI_VALIDATORS.update(TIMEOUT_VALIDATORS)
588-
VALIDATORS = URI_VALIDATORS.copy()
611+
# Dictionary where keys are any URI option name, and values are the
612+
# internally-used names of that URI option. Options with only one name
613+
# variant need not be included here. Options whose public and internal
614+
# names are the same need not be included here.
615+
INTERNAL_URI_OPTION_NAME_MAP = {
616+
'j': 'journal',
617+
'wtimeout': 'wtimeoutms',
618+
'tls': 'ssl',
619+
'tlsallowinvalidcertificates': 'ssl_cert_reqs',
620+
'tlsallowinvalidhostnames': 'ssl_match_hostname',
621+
'tlscrlfile': 'ssl_crlfile',
622+
'tlscafile': 'ssl_ca_certs',
623+
'tlscertificatekeyfile': 'ssl_certfile',
624+
'tlscertificatekeyfilepassword': 'ssl_pem_passphrase',
625+
}
626+
627+
# Map from deprecated URI option names to the updated option names.
628+
# Case is preserved for updated option names as they are part of user warnings.
629+
URI_OPTIONS_DEPRECATION_MAP = {
630+
'j': 'journal',
631+
'wtimeout': 'wTimeoutMS',
632+
'ssl_cert_reqs': 'tlsAllowInvalidCertificates',
633+
'ssl_match_hostname': 'tlsAllowInvalidHostnames',
634+
'ssl_crlfile': 'tlsCRLFile',
635+
'ssl_ca_certs': 'tlsCAFile',
636+
'ssl_pem_passphrase': 'tlsCertificateKeyFilePassword',
637+
}
638+
639+
# Augment the option validator map with pymongo-specific option information.
640+
URI_OPTIONS_VALIDATOR_MAP.update(NONSPEC_OPTIONS_VALIDATOR_MAP)
641+
for optname, aliases in iteritems(URI_OPTIONS_ALIAS_MAP):
642+
for alias in aliases:
643+
if alias not in URI_OPTIONS_VALIDATOR_MAP:
644+
URI_OPTIONS_VALIDATOR_MAP[alias] = (
645+
URI_OPTIONS_VALIDATOR_MAP[optname])
646+
647+
# Map containing all URI option and keyword argument validators.
648+
VALIDATORS = URI_OPTIONS_VALIDATOR_MAP.copy()
589649
VALIDATORS.update(KW_VALIDATORS)
590650

651+
# List of timeout-related options.
652+
TIMEOUT_OPTIONS = [
653+
'connecttimeoutms',
654+
'heartbeatfrequencyms',
655+
'maxidletimems',
656+
'maxstalenessseconds',
657+
'serverselectiontimeoutms',
658+
'sockettimeoutms',
659+
'waitqueuetimeoutms',
660+
]
661+
591662

592663
_AUTH_OPTIONS = frozenset(['authmechanismproperties'])
593664

@@ -613,15 +684,22 @@ def validate(option, value):
613684

614685
def get_validated_options(options, warn=True):
615686
"""Validate each entry in options and raise a warning if it is not valid.
616-
Returns a copy of options with invalid entries removed
687+
Returns a copy of options with invalid entries removed.
688+
689+
:Parameters:
690+
- `opts`: A dict of MongoDB URI options.
691+
- `warn` (optional): If ``True`` then warnings will be logged and
692+
invalid options will be ignored. Otherwise, invalid options will
693+
cause errors.
617694
"""
618695
validated_options = {}
619696
for opt, value in iteritems(options):
620697
lower = opt.lower()
621698
try:
622-
validator = URI_VALIDATORS.get(lower, raise_config_error)
699+
validator = URI_OPTIONS_VALIDATOR_MAP.get(
700+
lower, raise_config_error)
623701
value = validator(opt, value)
624-
except (ValueError, ConfigurationError) as exc:
702+
except (ValueError, TypeError, ConfigurationError) as exc:
625703
if warn:
626704
warnings.warn(str(exc))
627705
else:
@@ -631,6 +709,7 @@ def get_validated_options(options, warn=True):
631709
return validated_options
632710

633711

712+
# List of write-concern-related options.
634713
WRITE_CONCERN_OPTIONS = frozenset([
635714
'w',
636715
'wtimeout',

pymongo/compression_support.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@
3636

3737

3838
def validate_compressors(dummy, value):
39-
compressors = value.split(",")
39+
try:
40+
# `value` is string.
41+
compressors = value.split(",")
42+
except AttributeError:
43+
# `value` is an iterable.
44+
compressors = list(value)
45+
4046
for compressor in compressors[:]:
4147
if compressor not in _SUPPORTED_COMPRESSORS:
4248
compressors.remove(compressor)

0 commit comments

Comments
 (0)