1# Copyright 2011-present MongoDB, Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you 4# may not use this file except in compliance with the License. You 5# may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12# implied. See the License for the specific language governing 13# permissions and limitations under the License. 14 15 16"""Functions and classes common to multiple pymongo modules.""" 17 18import datetime 19import warnings 20 21from bson import SON 22from bson.binary import UuidRepresentation 23from bson.codec_options import CodecOptions, TypeRegistry 24from bson.py3compat import abc, integer_types, iteritems, string_type, PY3 25from bson.raw_bson import RawBSONDocument 26from pymongo.auth import MECHANISMS 27from pymongo.compression_support import (validate_compressors, 28 validate_zlib_compression_level) 29from pymongo.driver_info import DriverInfo 30from pymongo.server_api import ServerApi 31from pymongo.encryption_options import validate_auto_encryption_opts_or_none 32from pymongo.errors import ConfigurationError 33from pymongo.monitoring import _validate_event_listeners 34from pymongo.read_concern import ReadConcern 35from pymongo.read_preferences import _MONGOS_MODES, _ServerMode 36from pymongo.ssl_support import (validate_cert_reqs, 37 validate_allow_invalid_certs) 38from pymongo.write_concern import DEFAULT_WRITE_CONCERN, WriteConcern 39 40try: 41 from collections import OrderedDict 42 ORDERED_TYPES = (SON, OrderedDict) 43except ImportError: 44 ORDERED_TYPES = (SON,) 45 46if PY3: 47 from urllib.parse import unquote_plus 48else: 49 from urllib import unquote_plus 50 51# Defaults until we connect to a server and get updated limits. 52MAX_BSON_SIZE = 16 * (1024 ** 2) 53MAX_MESSAGE_SIZE = 2 * MAX_BSON_SIZE 54MIN_WIRE_VERSION = 0 55MAX_WIRE_VERSION = 0 56MAX_WRITE_BATCH_SIZE = 1000 57 58# What this version of PyMongo supports. 59MIN_SUPPORTED_SERVER_VERSION = "2.6" 60MIN_SUPPORTED_WIRE_VERSION = 2 61MAX_SUPPORTED_WIRE_VERSION = 13 62 63# Frequency to call hello on servers, in seconds. 64HEARTBEAT_FREQUENCY = 10 65 66# Frequency to process kill-cursors, in seconds. See MongoClient.close_cursor. 67KILL_CURSOR_FREQUENCY = 1 68 69# Frequency to process events queue, in seconds. 70EVENTS_QUEUE_FREQUENCY = 1 71 72# How long to wait, in seconds, for a suitable server to be found before 73# aborting an operation. For example, if the client attempts an insert 74# during a replica set election, SERVER_SELECTION_TIMEOUT governs the 75# longest it is willing to wait for a new primary to be found. 76SERVER_SELECTION_TIMEOUT = 30 77 78# Spec requires at least 500ms between hello calls. 79MIN_HEARTBEAT_INTERVAL = 0.5 80 81# Spec requires at least 60s between SRV rescans. 82MIN_SRV_RESCAN_INTERVAL = 60 83 84# Default connectTimeout in seconds. 85CONNECT_TIMEOUT = 20.0 86 87# Default value for maxPoolSize. 88MAX_POOL_SIZE = 100 89 90# Default value for minPoolSize. 91MIN_POOL_SIZE = 0 92 93# Default value for maxIdleTimeMS. 94MAX_IDLE_TIME_MS = None 95 96# Default value for maxIdleTimeMS in seconds. 97MAX_IDLE_TIME_SEC = None 98 99# Default value for waitQueueTimeoutMS in seconds. 100WAIT_QUEUE_TIMEOUT = None 101 102# Default value for localThresholdMS. 103LOCAL_THRESHOLD_MS = 15 104 105# Default value for retryWrites. 106RETRY_WRITES = True 107 108# Default value for retryReads. 109RETRY_READS = True 110 111# mongod/s 2.6 and above return code 59 when a command doesn't exist. 112COMMAND_NOT_FOUND_CODES = (59,) 113 114# Error codes to ignore if GridFS calls createIndex on a secondary 115UNAUTHORIZED_CODES = (13, 16547, 16548) 116 117# Maximum number of sessions to send in a single endSessions command. 118# From the driver sessions spec. 119_MAX_END_SESSIONS = 10000 120 121 122def partition_node(node): 123 """Split a host:port string into (host, int(port)) pair.""" 124 host = node 125 port = 27017 126 idx = node.rfind(':') 127 if idx != -1: 128 host, port = node[:idx], int(node[idx + 1:]) 129 if host.startswith('['): 130 host = host[1:-1] 131 return host, port 132 133 134def clean_node(node): 135 """Split and normalize a node name from a hello response.""" 136 host, port = partition_node(node) 137 138 # Normalize hostname to lowercase, since DNS is case-insensitive: 139 # http://tools.ietf.org/html/rfc4343 140 # This prevents useless rediscovery if "foo.com" is in the seed list but 141 # "FOO.com" is in the hello response. 142 return host.lower(), port 143 144 145def raise_config_error(key, dummy): 146 """Raise ConfigurationError with the given key name.""" 147 raise ConfigurationError("Unknown option %s" % (key,)) 148 149 150# Mapping of URI uuid representation options to valid subtypes. 151_UUID_REPRESENTATIONS = { 152 'unspecified': UuidRepresentation.UNSPECIFIED, 153 'standard': UuidRepresentation.STANDARD, 154 'pythonLegacy': UuidRepresentation.PYTHON_LEGACY, 155 'javaLegacy': UuidRepresentation.JAVA_LEGACY, 156 'csharpLegacy': UuidRepresentation.CSHARP_LEGACY 157} 158 159 160def validate_boolean(option, value): 161 """Validates that 'value' is True or False.""" 162 if isinstance(value, bool): 163 return value 164 raise TypeError("%s must be True or False" % (option,)) 165 166 167def validate_boolean_or_string(option, value): 168 """Validates that value is True, False, 'true', or 'false'.""" 169 if isinstance(value, string_type): 170 if value not in ('true', 'false'): 171 raise ValueError("The value of %s must be " 172 "'true' or 'false'" % (option,)) 173 return value == 'true' 174 return validate_boolean(option, value) 175 176 177def validate_integer(option, value): 178 """Validates that 'value' is an integer (or basestring representation). 179 """ 180 if isinstance(value, integer_types): 181 return value 182 elif isinstance(value, string_type): 183 try: 184 return int(value) 185 except ValueError: 186 raise ValueError("The value of %s must be " 187 "an integer" % (option,)) 188 raise TypeError("Wrong type for %s, value must be an integer" % (option,)) 189 190 191def validate_positive_integer(option, value): 192 """Validate that 'value' is a positive integer, which does not include 0. 193 """ 194 val = validate_integer(option, value) 195 if val <= 0: 196 raise ValueError("The value of %s must be " 197 "a positive integer" % (option,)) 198 return val 199 200 201def validate_non_negative_integer(option, value): 202 """Validate that 'value' is a positive integer or 0. 203 """ 204 val = validate_integer(option, value) 205 if val < 0: 206 raise ValueError("The value of %s must be " 207 "a non negative integer" % (option,)) 208 return val 209 210 211def validate_readable(option, value): 212 """Validates that 'value' is file-like and readable. 213 """ 214 if value is None: 215 return value 216 # First make sure its a string py3.3 open(True, 'r') succeeds 217 # Used in ssl cert checking due to poor ssl module error reporting 218 value = validate_string(option, value) 219 open(value, 'r').close() 220 return value 221 222 223def validate_positive_integer_or_none(option, value): 224 """Validate that 'value' is a positive integer or None. 225 """ 226 if value is None: 227 return value 228 return validate_positive_integer(option, value) 229 230 231def validate_non_negative_integer_or_none(option, value): 232 """Validate that 'value' is a positive integer or 0 or None. 233 """ 234 if value is None: 235 return value 236 return validate_non_negative_integer(option, value) 237 238 239def validate_string(option, value): 240 """Validates that 'value' is an instance of `basestring` for Python 2 241 or `str` for Python 3. 242 """ 243 if isinstance(value, string_type): 244 return value 245 raise TypeError("Wrong type for %s, value must be " 246 "an instance of %s" % (option, string_type.__name__)) 247 248 249def validate_string_or_none(option, value): 250 """Validates that 'value' is an instance of `basestring` or `None`. 251 """ 252 if value is None: 253 return value 254 return validate_string(option, value) 255 256 257def validate_int_or_basestring(option, value): 258 """Validates that 'value' is an integer or string. 259 """ 260 if isinstance(value, integer_types): 261 return value 262 elif isinstance(value, string_type): 263 try: 264 return int(value) 265 except ValueError: 266 return value 267 raise TypeError("Wrong type for %s, value must be an " 268 "integer or a string" % (option,)) 269 270 271def validate_non_negative_int_or_basestring(option, value): 272 """Validates that 'value' is an integer or string. 273 """ 274 if isinstance(value, integer_types): 275 return value 276 elif isinstance(value, string_type): 277 try: 278 val = int(value) 279 except ValueError: 280 return value 281 return validate_non_negative_integer(option, val) 282 raise TypeError("Wrong type for %s, value must be an " 283 "non negative integer or a string" % (option,)) 284 285 286def validate_positive_float(option, value): 287 """Validates that 'value' is a float, or can be converted to one, and is 288 positive. 289 """ 290 errmsg = "%s must be an integer or float" % (option,) 291 try: 292 value = float(value) 293 except ValueError: 294 raise ValueError(errmsg) 295 except TypeError: 296 raise TypeError(errmsg) 297 298 # float('inf') doesn't work in 2.4 or 2.5 on Windows, so just cap floats at 299 # one billion - this is a reasonable approximation for infinity 300 if not 0 < value < 1e9: 301 raise ValueError("%s must be greater than 0 and " 302 "less than one billion" % (option,)) 303 return value 304 305 306def validate_positive_float_or_zero(option, value): 307 """Validates that 'value' is 0 or a positive float, or can be converted to 308 0 or a positive float. 309 """ 310 if value == 0 or value == "0": 311 return 0 312 return validate_positive_float(option, value) 313 314 315def validate_timeout_or_none(option, value): 316 """Validates a timeout specified in milliseconds returning 317 a value in floating point seconds. 318 """ 319 if value is None: 320 return value 321 return validate_positive_float(option, value) / 1000.0 322 323 324def validate_timeout_or_zero(option, value): 325 """Validates a timeout specified in milliseconds returning 326 a value in floating point seconds for the case where None is an error 327 and 0 is valid. Setting the timeout to nothing in the URI string is a 328 config error. 329 """ 330 if value is None: 331 raise ConfigurationError("%s cannot be None" % (option, )) 332 if value == 0 or value == "0": 333 return 0 334 return validate_positive_float(option, value) / 1000.0 335 336 337def validate_timeout_or_none_or_zero(option, value): 338 """Validates a timeout specified in milliseconds returning 339 a value in floating point seconds. value=0 and value="0" are treated the 340 same as value=None which means unlimited timeout. 341 """ 342 if value is None or value == 0 or value == "0": 343 return None 344 return validate_positive_float(option, value) / 1000.0 345 346 347def validate_max_staleness(option, value): 348 """Validates maxStalenessSeconds according to the Max Staleness Spec.""" 349 if value == -1 or value == "-1": 350 # Default: No maximum staleness. 351 return -1 352 return validate_positive_integer(option, value) 353 354 355def validate_read_preference(dummy, value): 356 """Validate a read preference. 357 """ 358 if not isinstance(value, _ServerMode): 359 raise TypeError("%r is not a read preference." % (value,)) 360 return value 361 362 363def validate_read_preference_mode(dummy, value): 364 """Validate read preference mode for a MongoReplicaSetClient. 365 366 .. versionchanged:: 3.5 367 Returns the original ``value`` instead of the validated read preference 368 mode. 369 """ 370 if value not in _MONGOS_MODES: 371 raise ValueError("%s is not a valid read preference" % (value,)) 372 return value 373 374 375def validate_auth_mechanism(option, value): 376 """Validate the authMechanism URI option. 377 """ 378 # CRAM-MD5 is for server testing only. Undocumented, 379 # unsupported, may be removed at any time. You have 380 # been warned. 381 if value not in MECHANISMS and value != 'CRAM-MD5': 382 raise ValueError("%s must be in %s" % (option, tuple(MECHANISMS))) 383 return value 384 385 386def validate_uuid_representation(dummy, value): 387 """Validate the uuid representation option selected in the URI. 388 """ 389 try: 390 return _UUID_REPRESENTATIONS[value] 391 except KeyError: 392 raise ValueError("%s is an invalid UUID representation. " 393 "Must be one of " 394 "%s" % (value, tuple(_UUID_REPRESENTATIONS))) 395 396 397def validate_read_preference_tags(name, value): 398 """Parse readPreferenceTags if passed as a client kwarg. 399 """ 400 if not isinstance(value, list): 401 value = [value] 402 403 tag_sets = [] 404 for tag_set in value: 405 if tag_set == '': 406 tag_sets.append({}) 407 continue 408 try: 409 tags = {} 410 for tag in tag_set.split(","): 411 key, val = tag.split(":") 412 tags[unquote_plus(key)] = unquote_plus(val) 413 tag_sets.append(tags) 414 except Exception: 415 raise ValueError("%r not a valid " 416 "value for %s" % (tag_set, name)) 417 return tag_sets 418 419 420_MECHANISM_PROPS = frozenset(['SERVICE_NAME', 421 'CANONICALIZE_HOST_NAME', 422 'SERVICE_REALM', 423 'AWS_SESSION_TOKEN']) 424 425 426def validate_auth_mechanism_properties(option, value): 427 """Validate authMechanismProperties.""" 428 value = validate_string(option, value) 429 props = {} 430 for opt in value.split(','): 431 try: 432 key, val = opt.split(':') 433 except ValueError: 434 # Try not to leak the token. 435 if 'AWS_SESSION_TOKEN' in opt: 436 opt = ('AWS_SESSION_TOKEN:<redacted token>, did you forget ' 437 'to percent-escape the token with quote_plus?') 438 raise ValueError("auth mechanism properties must be " 439 "key:value pairs like SERVICE_NAME:" 440 "mongodb, not %s." % (opt,)) 441 if key not in _MECHANISM_PROPS: 442 raise ValueError("%s is not a supported auth " 443 "mechanism property. Must be one of " 444 "%s." % (key, tuple(_MECHANISM_PROPS))) 445 if key == 'CANONICALIZE_HOST_NAME': 446 props[key] = validate_boolean_or_string(key, val) 447 else: 448 props[key] = unquote_plus(val) 449 450 return props 451 452 453def validate_document_class(option, value): 454 """Validate the document_class option.""" 455 if not issubclass(value, (abc.MutableMapping, RawBSONDocument)): 456 raise TypeError("%s must be dict, bson.son.SON, " 457 "bson.raw_bson.RawBSONDocument, or a " 458 "sublass of collections.MutableMapping" % (option,)) 459 return value 460 461 462def validate_type_registry(option, value): 463 """Validate the type_registry option.""" 464 if value is not None and not isinstance(value, TypeRegistry): 465 raise TypeError("%s must be an instance of %s" % ( 466 option, TypeRegistry)) 467 return value 468 469 470def validate_list(option, value): 471 """Validates that 'value' is a list.""" 472 if not isinstance(value, list): 473 raise TypeError("%s must be a list" % (option,)) 474 return value 475 476 477def validate_list_or_none(option, value): 478 """Validates that 'value' is a list or None.""" 479 if value is None: 480 return value 481 return validate_list(option, value) 482 483 484def validate_list_or_mapping(option, value): 485 """Validates that 'value' is a list or a document.""" 486 if not isinstance(value, (abc.Mapping, list)): 487 raise TypeError("%s must either be a list or an instance of dict, " 488 "bson.son.SON, or any other type that inherits from " 489 "collections.Mapping" % (option,)) 490 491 492def validate_is_mapping(option, value): 493 """Validate the type of method arguments that expect a document.""" 494 if not isinstance(value, abc.Mapping): 495 raise TypeError("%s must be an instance of dict, bson.son.SON, or " 496 "any other type that inherits from " 497 "collections.Mapping" % (option,)) 498 499 500def validate_is_document_type(option, value): 501 """Validate the type of method arguments that expect a MongoDB document.""" 502 if not isinstance(value, (abc.MutableMapping, RawBSONDocument)): 503 raise TypeError("%s must be an instance of dict, bson.son.SON, " 504 "bson.raw_bson.RawBSONDocument, or " 505 "a type that inherits from " 506 "collections.MutableMapping" % (option,)) 507 508 509def validate_appname_or_none(option, value): 510 """Validate the appname option.""" 511 if value is None: 512 return value 513 validate_string(option, value) 514 # We need length in bytes, so encode utf8 first. 515 if len(value.encode('utf-8')) > 128: 516 raise ValueError("%s must be <= 128 bytes" % (option,)) 517 return value 518 519 520def validate_driver_or_none(option, value): 521 """Validate the driver keyword arg.""" 522 if value is None: 523 return value 524 if not isinstance(value, DriverInfo): 525 raise TypeError("%s must be an instance of DriverInfo" % (option,)) 526 return value 527 528 529def validate_server_api_or_none(option, value): 530 """Validate the server_api keyword arg.""" 531 if value is None: 532 return value 533 if not isinstance(value, ServerApi): 534 raise TypeError("%s must be an instance of ServerApi" % (option,)) 535 return value 536 537 538def validate_is_callable_or_none(option, value): 539 """Validates that 'value' is a callable.""" 540 if value is None: 541 return value 542 if not callable(value): 543 raise ValueError("%s must be a callable" % (option,)) 544 return value 545 546 547def validate_ok_for_replace(replacement): 548 """Validate a replacement document.""" 549 validate_is_mapping("replacement", replacement) 550 # Replacement can be {} 551 if replacement and not isinstance(replacement, RawBSONDocument): 552 first = next(iter(replacement)) 553 if first.startswith('$'): 554 raise ValueError('replacement can not include $ operators') 555 556 557def validate_ok_for_update(update): 558 """Validate an update document.""" 559 validate_list_or_mapping("update", update) 560 # Update cannot be {}. 561 if not update: 562 raise ValueError('update cannot be empty') 563 564 is_document = not isinstance(update, list) 565 first = next(iter(update)) 566 if is_document and not first.startswith('$'): 567 raise ValueError('update only works with $ operators') 568 569 570_UNICODE_DECODE_ERROR_HANDLERS = frozenset(['strict', 'replace', 'ignore']) 571 572 573def validate_unicode_decode_error_handler(dummy, value): 574 """Validate the Unicode decode error handler option of CodecOptions. 575 """ 576 if value not in _UNICODE_DECODE_ERROR_HANDLERS: 577 raise ValueError("%s is an invalid Unicode decode error handler. " 578 "Must be one of " 579 "%s" % (value, tuple(_UNICODE_DECODE_ERROR_HANDLERS))) 580 return value 581 582 583def validate_tzinfo(dummy, value): 584 """Validate the tzinfo option 585 """ 586 if value is not None and not isinstance(value, datetime.tzinfo): 587 raise TypeError("%s must be an instance of datetime.tzinfo" % value) 588 return value 589 590 591# Dictionary where keys are the names of public URI options, and values 592# are lists of aliases for that option. Aliases of option names are assumed 593# to have been deprecated. 594URI_OPTIONS_ALIAS_MAP = { 595 'journal': ['j'], 596 'wtimeoutms': ['wtimeout'], 597 'tls': ['ssl'], 598 'tlsallowinvalidcertificates': ['ssl_cert_reqs'], 599 'tlsallowinvalidhostnames': ['ssl_match_hostname'], 600 'tlscrlfile': ['ssl_crlfile'], 601 'tlscafile': ['ssl_ca_certs'], 602 'tlscertificatekeyfile': ['ssl_certfile'], 603 'tlscertificatekeyfilepassword': ['ssl_pem_passphrase'], 604} 605 606# Dictionary where keys are the names of URI options, and values 607# are functions that validate user-input values for that option. If an option 608# alias uses a different validator than its public counterpart, it should be 609# included here as a key, value pair. 610URI_OPTIONS_VALIDATOR_MAP = { 611 'appname': validate_appname_or_none, 612 'authmechanism': validate_auth_mechanism, 613 'authmechanismproperties': validate_auth_mechanism_properties, 614 'authsource': validate_string, 615 'compressors': validate_compressors, 616 'connecttimeoutms': validate_timeout_or_none_or_zero, 617 'directconnection': validate_boolean_or_string, 618 'heartbeatfrequencyms': validate_timeout_or_none, 619 'journal': validate_boolean_or_string, 620 'localthresholdms': validate_positive_float_or_zero, 621 'maxidletimems': validate_timeout_or_none, 622 'maxpoolsize': validate_positive_integer_or_none, 623 'maxstalenessseconds': validate_max_staleness, 624 'readconcernlevel': validate_string_or_none, 625 'readpreference': validate_read_preference_mode, 626 'readpreferencetags': validate_read_preference_tags, 627 'replicaset': validate_string_or_none, 628 'retryreads': validate_boolean_or_string, 629 'retrywrites': validate_boolean_or_string, 630 'loadbalanced': validate_boolean_or_string, 631 'serverselectiontimeoutms': validate_timeout_or_zero, 632 'sockettimeoutms': validate_timeout_or_none_or_zero, 633 'ssl_keyfile': validate_readable, 634 'tls': validate_boolean_or_string, 635 'tlsallowinvalidcertificates': validate_allow_invalid_certs, 636 'ssl_cert_reqs': validate_cert_reqs, 637 'tlsallowinvalidhostnames': lambda *x: not validate_boolean_or_string(*x), 638 'ssl_match_hostname': validate_boolean_or_string, 639 'tlscafile': validate_readable, 640 'tlscertificatekeyfile': validate_readable, 641 'tlscertificatekeyfilepassword': validate_string_or_none, 642 'tlsdisableocspendpointcheck': validate_boolean_or_string, 643 'tlsinsecure': validate_boolean_or_string, 644 'w': validate_non_negative_int_or_basestring, 645 'wtimeoutms': validate_non_negative_integer, 646 'zlibcompressionlevel': validate_zlib_compression_level, 647} 648 649# Dictionary where keys are the names of URI options specific to pymongo, 650# and values are functions that validate user-input values for those options. 651NONSPEC_OPTIONS_VALIDATOR_MAP = { 652 'connect': validate_boolean_or_string, 653 'driver': validate_driver_or_none, 654 'server_api': validate_server_api_or_none, 655 'fsync': validate_boolean_or_string, 656 'minpoolsize': validate_non_negative_integer, 657 'socketkeepalive': validate_boolean_or_string, 658 'tlscrlfile': validate_readable, 659 'tz_aware': validate_boolean_or_string, 660 'unicode_decode_error_handler': validate_unicode_decode_error_handler, 661 'uuidrepresentation': validate_uuid_representation, 662 'waitqueuemultiple': validate_non_negative_integer_or_none, 663 'waitqueuetimeoutms': validate_timeout_or_none, 664} 665 666# Dictionary where keys are the names of keyword-only options for the 667# MongoClient constructor, and values are functions that validate user-input 668# values for those options. 669KW_VALIDATORS = { 670 'document_class': validate_document_class, 671 'type_registry': validate_type_registry, 672 'read_preference': validate_read_preference, 673 'event_listeners': _validate_event_listeners, 674 'tzinfo': validate_tzinfo, 675 'username': validate_string_or_none, 676 'password': validate_string_or_none, 677 'server_selector': validate_is_callable_or_none, 678 'auto_encryption_opts': validate_auto_encryption_opts_or_none, 679} 680 681# Dictionary where keys are any URI option name, and values are the 682# internally-used names of that URI option. Options with only one name 683# variant need not be included here. Options whose public and internal 684# names are the same need not be included here. 685INTERNAL_URI_OPTION_NAME_MAP = { 686 'j': 'journal', 687 'wtimeout': 'wtimeoutms', 688 'tls': 'ssl', 689 'tlsallowinvalidcertificates': 'ssl_cert_reqs', 690 'tlsallowinvalidhostnames': 'ssl_match_hostname', 691 'tlscrlfile': 'ssl_crlfile', 692 'tlscafile': 'ssl_ca_certs', 693 'tlscertificatekeyfile': 'ssl_certfile', 694 'tlscertificatekeyfilepassword': 'ssl_pem_passphrase', 695 'tlsdisableocspendpointcheck': 'ssl_check_ocsp_endpoint', 696} 697 698# Map from deprecated URI option names to a tuple indicating the method of 699# their deprecation and any additional information that may be needed to 700# construct the warning message. 701URI_OPTIONS_DEPRECATION_MAP = { 702 # format: <deprecated option name>: (<mode>, <message>), 703 # Supported <mode> values: 704 # - 'renamed': <message> should be the new option name. Note that case is 705 # preserved for renamed options as they are part of user warnings. 706 # - 'removed': <message> may suggest the rationale for deprecating the 707 # option and/or recommend remedial action. 708 'j': ('renamed', 'journal'), 709 'wtimeout': ('renamed', 'wTimeoutMS'), 710 'ssl_cert_reqs': ('renamed', 'tlsAllowInvalidCertificates'), 711 'ssl_match_hostname': ('renamed', 'tlsAllowInvalidHostnames'), 712 'ssl_crlfile': ('renamed', 'tlsCRLFile'), 713 'ssl_ca_certs': ('renamed', 'tlsCAFile'), 714 'ssl_certfile': ('removed', ( 715 'Instead of using ssl_certfile to specify the certificate file, ' 716 'use tlsCertificateKeyFile to pass a single file containing both ' 717 'the client certificate and the private key')), 718 'ssl_keyfile': ('removed', ( 719 'Instead of using ssl_keyfile to specify the private keyfile, ' 720 'use tlsCertificateKeyFile to pass a single file containing both ' 721 'the client certificate and the private key')), 722 'ssl_pem_passphrase': ('renamed', 'tlsCertificateKeyFilePassword'), 723 'waitqueuemultiple': ('removed', ( 724 'Instead of using waitQueueMultiple to bound queuing, limit the size ' 725 'of the thread pool in your application server')) 726} 727 728# Augment the option validator map with pymongo-specific option information. 729URI_OPTIONS_VALIDATOR_MAP.update(NONSPEC_OPTIONS_VALIDATOR_MAP) 730for optname, aliases in iteritems(URI_OPTIONS_ALIAS_MAP): 731 for alias in aliases: 732 if alias not in URI_OPTIONS_VALIDATOR_MAP: 733 URI_OPTIONS_VALIDATOR_MAP[alias] = ( 734 URI_OPTIONS_VALIDATOR_MAP[optname]) 735 736# Map containing all URI option and keyword argument validators. 737VALIDATORS = URI_OPTIONS_VALIDATOR_MAP.copy() 738VALIDATORS.update(KW_VALIDATORS) 739 740# List of timeout-related options. 741TIMEOUT_OPTIONS = [ 742 'connecttimeoutms', 743 'heartbeatfrequencyms', 744 'maxidletimems', 745 'maxstalenessseconds', 746 'serverselectiontimeoutms', 747 'sockettimeoutms', 748 'waitqueuetimeoutms', 749] 750 751 752_AUTH_OPTIONS = frozenset(['authmechanismproperties']) 753 754 755def validate_auth_option(option, value): 756 """Validate optional authentication parameters. 757 """ 758 lower, value = validate(option, value) 759 if lower not in _AUTH_OPTIONS: 760 raise ConfigurationError('Unknown ' 761 'authentication option: %s' % (option,)) 762 return option, value 763 764 765def validate(option, value): 766 """Generic validation function. 767 """ 768 lower = option.lower() 769 validator = VALIDATORS.get(lower, raise_config_error) 770 value = validator(option, value) 771 return option, value 772 773 774def get_validated_options(options, warn=True): 775 """Validate each entry in options and raise a warning if it is not valid. 776 Returns a copy of options with invalid entries removed. 777 778 :Parameters: 779 - `opts`: A dict containing MongoDB URI options. 780 - `warn` (optional): If ``True`` then warnings will be logged and 781 invalid options will be ignored. Otherwise, invalid options will 782 cause errors. 783 """ 784 if isinstance(options, _CaseInsensitiveDictionary): 785 validated_options = _CaseInsensitiveDictionary() 786 get_normed_key = lambda x: x 787 get_setter_key = lambda x: options.cased_key(x) 788 else: 789 validated_options = {} 790 get_normed_key = lambda x: x.lower() 791 get_setter_key = lambda x: x 792 793 for opt, value in iteritems(options): 794 normed_key = get_normed_key(opt) 795 try: 796 validator = URI_OPTIONS_VALIDATOR_MAP.get( 797 normed_key, raise_config_error) 798 value = validator(opt, value) 799 except (ValueError, TypeError, ConfigurationError) as exc: 800 if warn: 801 warnings.warn(str(exc)) 802 else: 803 raise 804 else: 805 validated_options[get_setter_key(normed_key)] = value 806 return validated_options 807 808 809# List of write-concern-related options. 810WRITE_CONCERN_OPTIONS = frozenset([ 811 'w', 812 'wtimeout', 813 'wtimeoutms', 814 'fsync', 815 'j', 816 'journal' 817]) 818 819 820class BaseObject(object): 821 """A base class that provides attributes and methods common 822 to multiple pymongo classes. 823 824 SHOULD NOT BE USED BY DEVELOPERS EXTERNAL TO MONGODB. 825 """ 826 827 def __init__(self, codec_options, read_preference, write_concern, 828 read_concern): 829 830 if not isinstance(codec_options, CodecOptions): 831 raise TypeError("codec_options must be an instance of " 832 "bson.codec_options.CodecOptions") 833 self.__codec_options = codec_options 834 835 if not isinstance(read_preference, _ServerMode): 836 raise TypeError("%r is not valid for read_preference. See " 837 "pymongo.read_preferences for valid " 838 "options." % (read_preference,)) 839 self.__read_preference = read_preference 840 841 if not isinstance(write_concern, WriteConcern): 842 raise TypeError("write_concern must be an instance of " 843 "pymongo.write_concern.WriteConcern") 844 self.__write_concern = write_concern 845 846 if not isinstance(read_concern, ReadConcern): 847 raise TypeError("read_concern must be an instance of " 848 "pymongo.read_concern.ReadConcern") 849 self.__read_concern = read_concern 850 851 @property 852 def codec_options(self): 853 """Read only access to the :class:`~bson.codec_options.CodecOptions` 854 of this instance. 855 """ 856 return self.__codec_options 857 858 @property 859 def write_concern(self): 860 """Read only access to the :class:`~pymongo.write_concern.WriteConcern` 861 of this instance. 862 863 .. versionchanged:: 3.0 864 The :attr:`write_concern` attribute is now read only. 865 """ 866 return self.__write_concern 867 868 def _write_concern_for(self, session): 869 """Read only access to the write concern of this instance or session. 870 """ 871 # Override this operation's write concern with the transaction's. 872 if session and session.in_transaction: 873 return DEFAULT_WRITE_CONCERN 874 return self.write_concern 875 876 @property 877 def read_preference(self): 878 """Read only access to the read preference of this instance. 879 880 .. versionchanged:: 3.0 881 The :attr:`read_preference` attribute is now read only. 882 """ 883 return self.__read_preference 884 885 def _read_preference_for(self, session): 886 """Read only access to the read preference of this instance or session. 887 """ 888 # Override this operation's read preference with the transaction's. 889 if session: 890 return session._txn_read_preference() or self.__read_preference 891 return self.__read_preference 892 893 @property 894 def read_concern(self): 895 """Read only access to the :class:`~pymongo.read_concern.ReadConcern` 896 of this instance. 897 898 .. versionadded:: 3.2 899 """ 900 return self.__read_concern 901 902 903class _CaseInsensitiveDictionary(abc.MutableMapping): 904 def __init__(self, *args, **kwargs): 905 self.__casedkeys = {} 906 self.__data = {} 907 self.update(dict(*args, **kwargs)) 908 909 def __contains__(self, key): 910 return key.lower() in self.__data 911 912 def __len__(self): 913 return len(self.__data) 914 915 def __iter__(self): 916 return (key for key in self.__casedkeys) 917 918 def __repr__(self): 919 return str({self.__casedkeys[k]: self.__data[k] for k in self}) 920 921 def __setitem__(self, key, value): 922 lc_key = key.lower() 923 self.__casedkeys[lc_key] = key 924 self.__data[lc_key] = value 925 926 def __getitem__(self, key): 927 return self.__data[key.lower()] 928 929 def __delitem__(self, key): 930 lc_key = key.lower() 931 del self.__casedkeys[lc_key] 932 del self.__data[lc_key] 933 934 def __eq__(self, other): 935 if not isinstance(other, abc.Mapping): 936 return NotImplemented 937 if len(self) != len(other): 938 return False 939 for key in other: 940 if self[key] != other[key]: 941 return False 942 943 return True 944 945 def get(self, key, default=None): 946 return self.__data.get(key.lower(), default) 947 948 def pop(self, key, *args, **kwargs): 949 lc_key = key.lower() 950 self.__casedkeys.pop(lc_key, None) 951 return self.__data.pop(lc_key, *args, **kwargs) 952 953 def popitem(self): 954 lc_key, cased_key = self.__casedkeys.popitem() 955 value = self.__data.pop(lc_key) 956 return cased_key, value 957 958 def clear(self): 959 self.__casedkeys.clear() 960 self.__data.clear() 961 962 def setdefault(self, key, default=None): 963 lc_key = key.lower() 964 if key in self: 965 return self.__data[lc_key] 966 else: 967 self.__casedkeys[lc_key] = key 968 self.__data[lc_key] = default 969 return default 970 971 def update(self, other): 972 if isinstance(other, _CaseInsensitiveDictionary): 973 for key in other: 974 self[other.cased_key(key)] = other[key] 975 else: 976 for key in other: 977 self[key] = other[key] 978 979 def cased_key(self, key): 980 return self.__casedkeys[key.lower()] 981