From d494105aa4199cd5f0d9b47c7ae17e20e9175dc3 Mon Sep 17 00:00:00 2001 From: Luke Lovett Date: Wed, 23 Apr 2014 17:58:25 +0000 Subject: [PATCH] PYTHON-674 python 2/3 single-source for the pymongo module --- bson/json_util.py | 3 +- bson/py3compat.py | 21 +++++++-- pymongo/__init__.py | 3 +- pymongo/auth.py | 46 +++++++++---------- pymongo/bulk.py | 24 +++++----- pymongo/collection.py | 42 ++++++++++------- pymongo/command_cursor.py | 5 +- pymongo/common.py | 36 ++++++++------- pymongo/cursor.py | 33 ++++++++------ pymongo/cursor_manager.py | 9 ++-- pymongo/database.py | 71 +++++++++++++++-------------- pymongo/helpers.py | 15 +++--- pymongo/message.py | 26 +++++------ pymongo/mongo_client.py | 56 +++++++++++++---------- pymongo/mongo_replica_set_client.py | 61 +++++++++++++------------ pymongo/pool.py | 4 +- pymongo/uri_parser.py | 15 ++++-- 17 files changed, 262 insertions(+), 208 deletions(-) diff --git a/bson/json_util.py b/bson/json_util.py index e998dfdc55..da251dc40a 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -152,7 +152,8 @@ def _json_convert(obj): """ if hasattr(obj, 'iteritems') or hasattr(obj, 'items'): # PY3 support return SON(((k, _json_convert(v)) for k, v in iteritems(obj))) - elif hasattr(obj, '__iter__') and not isinstance(obj, string_types): + elif hasattr(obj, '__iter__') and not isinstance(obj, (text_type, + binary_type)): return list((_json_convert(v) for v in obj)) try: return default(obj) diff --git a/bson/py3compat.py b/bson/py3compat.py index 557bb770ac..9474dd568b 100644 --- a/bson/py3compat.py +++ b/bson/py3compat.py @@ -40,17 +40,26 @@ def bytes_from_hex(h): return bytes.fromhex(h) def iteritems(d): - return d.items() + return iter(d.items()) + + def itervalues(d): + return iter(d.values()) def reraise(exctype, value, trace=None): raise exctype(str(value)).with_traceback(trace) + def _unicode(s): + return s + binary_type = bytes text_type = str string_type = str integer_types = int next_item = "__next__" + # TODO: remove when gridfs module is made single-source + next_item = '__next__' + string_types = (bytes, text_type) else: try: from cStringIO import StringIO @@ -73,15 +82,21 @@ def bytes_from_hex(h): def iteritems(d): return d.iteritems() + def itervalues(d): + return d.itervalues() + # "raise x, y, z" raises SyntaxError in Python 3 exec("""def reraise(exctype, value, trace=None): raise exctype, str(value), trace """) + _unicode = unicode + binary_type = str string_type = basestring text_type = unicode integer_types = (int, long) - next_item = "next" -string_types = (binary_type, text_type) + # TODO: remove when gridfs module is made single-source + next_item = 'next' + string_types = (bytes, text_type) diff --git a/pymongo/__init__.py b/pymongo/__init__.py index a1933503e0..97d849754b 100644 --- a/pymongo/__init__.py +++ b/pymongo/__init__.py @@ -14,7 +14,6 @@ """Python driver for MongoDB.""" - ASCENDING = 1 """Ascending sort order.""" DESCENDING = -1 @@ -70,7 +69,7 @@ version_tuple = (3, 0, ".dev0") def get_version_string(): - if isinstance(version_tuple[-1], basestring): + if isinstance(version_tuple[-1], str): return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1] return '.'.join(map(str, version_tuple)) diff --git a/pymongo/auth.py b/pymongo/auth.py index f8d3513d9a..67c7f83549 100644 --- a/pymongo/auth.py +++ b/pymongo/auth.py @@ -14,15 +14,9 @@ """Authentication helpers.""" +from __future__ import unicode_literals + import hmac -try: - import hashlib - _MD5 = hashlib.md5 - _DMOD = _MD5 -except ImportError: # for Python < 2.5 - import md5 - _MD5 = md5.new - _DMOD = md5 HAVE_KERBEROS = True try: @@ -30,8 +24,10 @@ except ImportError: HAVE_KERBEROS = False +from hashlib import md5 + from bson.binary import Binary -from bson.py3compat import b +from bson.py3compat import b, string_type, _unicode from bson.son import SON from pymongo.errors import ConfigurationError, OperationFailure @@ -55,29 +51,29 @@ def _build_credentials_tuple(mech, source, user, passwd, extra): def _password_digest(username, password): """Get a password digest to use for authentication. """ - if not isinstance(password, basestring): - raise TypeError("password must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(password, string_type): + raise TypeError("password must be an " + "instance of %s" % (string_type.__name__,)) if len(password) == 0: raise ValueError("password can't be empty") - if not isinstance(username, basestring): - raise TypeError("username must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(username, string_type): + raise TypeError("password must be an " + "instance of %s" % (string_type.__name__,)) - md5hash = _MD5() + md5hash = md5() data = "%s:mongo:%s" % (username, password) md5hash.update(data.encode('utf-8')) - return unicode(md5hash.hexdigest()) + return _unicode(md5hash.hexdigest()) def _auth_key(nonce, username, password): """Get an auth key to use for authentication. """ digest = _password_digest(username, password) - md5hash = _MD5() - data = "%s%s%s" % (nonce, unicode(username), digest) + md5hash = md5() + data = "%s%s%s" % (nonce, _unicode(username), digest) md5hash.update(data.encode('utf-8')) - return unicode(md5hash.hexdigest()) + return _unicode(md5hash.hexdigest()) def _authenticate_gssapi(credentials, sock_info, cmd_func): @@ -114,7 +110,7 @@ def _authenticate_gssapi(credentials, sock_info, cmd_func): response, _ = cmd_func(sock_info, '$external', cmd) # Limit how many times we loop to catch protocol / library issues - for _ in xrange(10): + for _ in range(10): result = kerberos.authGSSClientStep(ctx, str(response['payload'])) if result == -1: @@ -156,7 +152,7 @@ def _authenticate_gssapi(credentials, sock_info, cmd_func): finally: kerberos.authGSSClientClean(ctx) - except kerberos.KrbError, exc: + except kerberos.KrbError as exc: raise OperationFailure(str(exc)) @@ -181,14 +177,14 @@ def _authenticate_cram_md5(credentials, sock_info, cmd_func): passwd = _password_digest(username, password) cmd = SON([('saslStart', 1), ('mechanism', 'CRAM-MD5'), - ('payload', Binary(b(''))), + ('payload', Binary(b'')), ('autoAuthorize', 1)]) response, _ = cmd_func(sock_info, source, cmd) # MD5 as implicit default digest for digestmod is deprecated # in python 3.4 - mac = hmac.HMAC(key=passwd.encode('utf-8'), digestmod=_DMOD) + mac = hmac.HMAC(key=passwd.encode('utf-8'), digestmod=md5) mac.update(response['payload']) - challenge = username.encode('utf-8') + b(' ') + b(mac.hexdigest()) + challenge = username.encode('utf-8') + b' ' + b(mac.hexdigest()) cmd = SON([('saslContinue', 1), ('conversationId', response['conversationId']), ('payload', Binary(challenge))]) diff --git a/pymongo/bulk.py b/pymongo/bulk.py index 58515f83f4..7f99616a5a 100644 --- a/pymongo/bulk.py +++ b/pymongo/bulk.py @@ -17,6 +17,8 @@ .. versionadded:: 2.7 """ +from __future__ import unicode_literals + from bson.objectid import ObjectId from bson.son import SON from pymongo.errors import (BulkWriteError, @@ -72,10 +74,10 @@ def _make_error(index, code, errmsg, operation): """Create and return an error document. """ return { - u"index": index, - u"code": code, - u"errmsg": errmsg, - u"op": operation + "index": index, + "code": code, + "errmsg": errmsg, + "op": operation } @@ -113,7 +115,7 @@ def _merge_legacy(run, full_result, result, index): full_result['nInserted'] += 1 elif run.op_type == _UPDATE: if "upserted" in result: - doc = {u"index": run.index(index), u"_id": result["upserted"]} + doc = {"index": run.index(index), "_id": result["upserted"]} full_result["upserted"].append(doc) full_result['nUpserted'] += affected else: @@ -146,7 +148,7 @@ def _merge_command(run, full_result, results): else: n_upserted = 1 index = run.index(offset) - doc = {u"index": index, u"_id": upserted} + doc = {"index": index, "_id": upserted} full_result["upserted"].append(doc) full_result["nUpserted"] += n_upserted full_result["nMatched"] += (affected - n_upserted) @@ -168,7 +170,7 @@ def _merge_command(run, full_result, results): idx = doc["index"] + offset doc["index"] = run.index(idx) # Add the failed operation to the error document. - doc[u"op"] = run.ops[idx] + doc["op"] = run.ops[idx] full_result["writeErrors"].extend(write_errors) wc_error = result.get("writeConcernError") @@ -207,7 +209,7 @@ def add_update(self, selector, update, multi=False, upsert=False): # Update can not be {} if not update: raise ValueError('update only works with $ operators') - first = iter(update).next() + first = next(iter(update)) if not first.startswith('$'): raise ValueError('update only works with $ operators') cmd = SON([('q', selector), ('u', update), @@ -221,7 +223,7 @@ def add_replace(self, selector, replacement, upsert=False): raise TypeError('replacement must be an instance of dict') # Replacement can be {} if replacement: - first = iter(replacement).next() + first = next(iter(replacement)) if first.startswith('$'): raise ValueError('replacement can not include $ operators') cmd = SON([('q', selector), ('u', replacement), @@ -378,7 +380,7 @@ def execute_legacy(self, generator, write_concern): multi=(not operation['limit']), **write_concern) _merge_legacy(run, full_result, result, idx) - except DocumentTooLarge, exc: + except DocumentTooLarge as exc: # MongoDB 2.6 uses error code 2 for "too large". error = _make_error( run.index(idx), _BAD_VALUE, str(exc), operation) @@ -386,7 +388,7 @@ def execute_legacy(self, generator, write_concern): if self.ordered: stop = True break - except OperationFailure, exc: + except OperationFailure as exc: if not exc.details: # Some error not related to the write operation # (e.g. kerberos failure). Re-raise immediately. diff --git a/pymongo/collection.py b/pymongo/collection.py index 005818816f..8f11db8803 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -14,10 +14,15 @@ """Collection level utilities for Mongo.""" +from __future__ import unicode_literals + import warnings from bson.code import Code from bson.objectid import ObjectId +from bson.py3compat import (_unicode, + integer_types, + string_type) from bson.son import SON from pymongo import (bulk, common, @@ -40,7 +45,7 @@ def _gen_index_name(keys): """Generate an index name from the set of fields it is over. """ - return u"_".join([u"%s_%s" % item for item in keys]) + return "_".join(["%s_%s" % item for item in keys]) class Collection(common.BaseObject): @@ -90,9 +95,9 @@ def __init__(self, database, name, create=False, **kwargs): uuidrepresentation=database.uuid_subtype, **database.write_concern) - if not isinstance(name, basestring): + if not isinstance(name, string_type): raise TypeError("name must be an instance " - "of %s" % (basestring.__name__,)) + "of %s" % (string_type.__name__,)) if not name or ".." in name: raise InvalidName("collection names cannot be empty") @@ -108,8 +113,8 @@ def __init__(self, database, name, create=False, **kwargs): "null character") self.__database = database - self.__name = unicode(name) - self.__full_name = u"%s.%s" % (self.__database.name, self.__name) + self.__name = _unicode(name) + self.__full_name = "%s.%s" % (self.__database.name, self.__name) if create or kwargs: self.__create(kwargs) @@ -132,7 +137,7 @@ def __getattr__(self, name): :Parameters: - `name`: the name of the collection to get """ - return Collection(self.__database, u"%s.%s" % (self.__name, name)) + return Collection(self.__database, "%s.%s" % (self.__name, name)) def __getitem__(self, name): return self.__getattr__(name) @@ -543,7 +548,7 @@ def update(self, spec, document, upsert=False, manipulate=False, # we check here. Passing a document with a mix of top level keys # starting with and without a '$' is invalid and the server will # raise an appropriate exception. - first = (document.iterkeys()).next() + first = next(iter(document)) if first.startswith('$'): check_keys = False @@ -1018,7 +1023,8 @@ def create_index(self, key_or_list, cache_for=300, **kwargs): DeprecationWarning, stacklevel=2) # The types supported by datetime.timedelta. 2to3 removes long. - if not isinstance(cache_for, (int, long, float)): + if not (isinstance(cache_for, integer_types) or + isinstance(cache_for, float)): raise TypeError("cache_for must be an integer or float.") keys = helpers._index_list(key_or_list) @@ -1037,7 +1043,7 @@ def create_index(self, key_or_list, cache_for=300, **kwargs): try: self.__database.command('createIndexes', self.name, indexes=[index]) - except OperationFailure, exc: + except OperationFailure as exc: if exc.code in (59, None): index["ns"] = self.__full_name self.__database.system.indexes.insert(index, manipulate=False, @@ -1152,7 +1158,7 @@ def drop_indexes(self): """ self.__database.connection._purge_index(self.__database.name, self.__name) - self.drop_index(u"*") + self.drop_index("*") def drop_index(self, index_or_name): """Drops the specified index on this collection. @@ -1177,7 +1183,7 @@ def drop_index(self, index_or_name): if isinstance(index_or_name, list): name = _gen_index_name(index_or_name) - if not isinstance(name, basestring): + if not isinstance(name, string_type): raise TypeError("index_or_name must be an index name or list") self.__database.connection._purge_index(self.__database.name, @@ -1357,7 +1363,7 @@ def group(self, key, condition, initial, """ group = {} - if isinstance(key, basestring): + if isinstance(key, string_type): group["$keyf"] = Code(key) elif key is not None: group = {"key": helpers._fields_list_to_dict(key)} @@ -1392,9 +1398,9 @@ def rename(self, new_name, **kwargs): .. versionadded:: 1.7 support for accepting keyword arguments for rename options """ - if not isinstance(new_name, basestring): - raise TypeError("new_name must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(new_name, string_type): + raise TypeError("new_name must be an " + "instance of %s" % (string_type.__name__,)) if not new_name or ".." in new_name: raise InvalidName("collection names cannot be empty") @@ -1469,9 +1475,9 @@ def map_reduce(self, map, reduce, out, .. mongodoc:: mapreduce """ - if not isinstance(out, (basestring, dict)): + if not isinstance(out, (string_type, dict)): raise TypeError("'out' must be an instance of " - "%s or dict" % (basestring.__name__,)) + "%s or dict" % (string_type.__name__,)) mode = read_preference or self.read_preference response = self.__database.command("mapreduce", self.__name, @@ -1642,6 +1648,8 @@ def __iter__(self): def next(self): raise TypeError("'Collection' object is not iterable") + __next__ = next + def __call__(self, *args, **kwargs): """This is only here so that some API misusages are easier to debug. """ diff --git a/pymongo/command_cursor.py b/pymongo/command_cursor.py index 690940d709..68ab3cae6c 100644 --- a/pymongo/command_cursor.py +++ b/pymongo/command_cursor.py @@ -16,6 +16,7 @@ from collections import deque +from bson.py3compat import integer_types from pymongo import helpers, message from pymongo.errors import AutoReconnect, CursorNotFound @@ -80,7 +81,7 @@ def batch_size(self, batch_size): :Parameters: - `batch_size`: The size of each batch of results requested. """ - if not isinstance(batch_size, (int, long)): + if not isinstance(batch_size, integer_types): raise TypeError("batch_size must be an integer") if batch_size < 0: raise ValueError("batch_size must be >= 0") @@ -168,6 +169,8 @@ def next(self): else: raise StopIteration + __next__ = next + def __enter__(self): return self diff --git a/pymongo/common.py b/pymongo/common.py index b23ea0494d..e32ccf569d 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -22,6 +22,7 @@ from pymongo.write_concern import WriteConcern from bson.binary import (OLD_UUID_SUBTYPE, UUID_SUBTYPE, JAVA_LEGACY, CSHARP_LEGACY) +from bson.py3compat import string_type, integer_types, iteritems HAS_SSL = True try: @@ -66,7 +67,7 @@ def validate_boolean(option, value): """ if isinstance(value, bool): return value - elif isinstance(value, basestring): + elif isinstance(value, string_type): if value not in ('true', 'false'): raise ConfigurationError("The value of %s must be " "'true' or 'false'" % (option,)) @@ -77,9 +78,9 @@ def validate_boolean(option, value): def validate_integer(option, value): """Validates that 'value' is an integer (or basestring representation). """ - if isinstance(value, (int, long)): + if isinstance(value, integer_types): return value - elif isinstance(value, basestring): + elif isinstance(value, string_type): if not value.isdigit(): raise ConfigurationError("The value of %s must be " "an integer" % (option,)) @@ -102,7 +103,7 @@ def validate_readable(option, value): """ # First make sure its a string py3.3 open(True, 'r') succeeds # Used in ssl cert checking due to poor ssl module error reporting - value = validate_basestring(option, value) + value = validate_string(option, value) open(value, 'r').close() return value @@ -132,21 +133,22 @@ def validate_positive_integer_or_none(option, value): return validate_positive_integer(option, value) -def validate_basestring(option, value): - """Validates that 'value' is an instance of `basestring`. +def validate_string(option, value): + """Validates that 'value' is an instance of `basestring` for Python 2 + or `str` for Python 3. """ - if isinstance(value, basestring): + if isinstance(value, string_type): return value - raise TypeError("Wrong type for %s, value must be an " - "instance of %s" % (option, basestring.__name__)) + raise TypeError("Wrong type for %s, value must be " + "an instance of %s" % (option, string_type.__name__)) def validate_int_or_basestring(option, value): """Validates that 'value' is an integer or string. """ - if isinstance(value, (int, long)): + if isinstance(value, integer_types): return value - elif isinstance(value, basestring): + elif isinstance(value, string_type): if value.isdigit(): return int(value) return value @@ -214,10 +216,10 @@ def validate_auth_mechanism(option, value): def validate_uuid_representation(dummy, value): """Validate the uuid representation option selected in the URI. """ - if value not in _UUID_SUBTYPES.keys(): + if value not in _UUID_SUBTYPES: raise ConfigurationError("%s is an invalid UUID representation. " "Must be one of " - "%s" % (value, _UUID_SUBTYPES.keys())) + "%s" % (value, list(_UUID_SUBTYPES))) return _UUID_SUBTYPES[value] @@ -249,7 +251,7 @@ def validate_read_preference_tags(name, value): # journal is an alias for j, # wtimeoutms is an alias for wtimeout, VALIDATORS = { - 'replicaset': validate_basestring, + 'replicaset': validate_string, 'w': validate_int_or_basestring, 'wtimeout': validate_integer, 'wtimeoutms': validate_integer, @@ -272,8 +274,8 @@ def validate_read_preference_tags(name, value): 'auto_start_request': validate_boolean, 'use_greenlets': validate_boolean, 'authmechanism': validate_auth_mechanism, - 'authsource': validate_basestring, - 'gssapiservicename': validate_basestring, + 'authsource': validate_string, + 'gssapiservicename': validate_string, 'uuidrepresentation': validate_uuid_representation, } @@ -327,7 +329,7 @@ def __init__(self, **options): def __set_options(self, options): """Validates and sets all options passed to this object.""" wc_opts = {} - for option, value in options.iteritems(): + for option, value in iteritems(options): if option == 'read_preference': self.__read_pref = validate_read_preference(option, value) elif option == 'readpreference': diff --git a/pymongo/cursor.py b/pymongo/cursor.py index b6c73e1c91..093bbf5464 100644 --- a/pymongo/cursor.py +++ b/pymongo/cursor.py @@ -18,6 +18,9 @@ from bson import RE_TYPE from bson.code import Code +from bson.py3compat import (iteritems, + integer_types, + string_type) from bson.son import SON from pymongo import helpers, message, read_preferences from pymongo.read_preferences import ReadPreference, SECONDARY_OK_COMMANDS @@ -233,7 +236,7 @@ def _clone(self, deepcopy=True): "batch_size", "max_scan", "as_class", "manipulate", "read_preference", "uuid_subtype", "compile_re", "query_flags") - data = dict((k, v) for k, v in self.__dict__.iteritems() + data = dict((k, v) for k, v in iteritems(self.__dict__) if k.startswith('_Cursor__') and k[9:] in values_to_clone) if deepcopy: data = self._deepcopy(data) @@ -318,7 +321,7 @@ def __query_spec(self): # by db.command or calling find_one on $cmd directly if self.collection.name == "$cmd": # Don't change commands that can't be sent to secondaries - command_name = spec and spec.keys()[0].lower() or "" + command_name = spec and next(iter(spec)).lower() or "" if command_name not in SECONDARY_OK_COMMANDS: return spec elif command_name == 'mapreduce': @@ -345,7 +348,8 @@ def __query_spec(self): # Checking spec.keys()[0] covers the case that the spec # was passed as an instance of SON or OrderedDict. elif ("query" in self.__spec and - (len(self.__spec) == 1 or self.__spec.keys()[0] == "query")): + (len(self.__spec) == 1 or + next(iter(self.__spec)) == "query")): return SON({"$query": self.__spec}) return self.__spec @@ -406,7 +410,7 @@ def limit(self, limit): .. mongodoc:: limit """ - if not isinstance(limit, (int, long)): + if not isinstance(limit, integer_types): raise TypeError("limit must be an integer") if self.__exhaust: raise InvalidOperation("Can't use limit and exhaust together.") @@ -437,7 +441,7 @@ def batch_size(self, batch_size): .. versionadded:: 1.9 """ - if not isinstance(batch_size, (int, long)): + if not isinstance(batch_size, integer_types): raise TypeError("batch_size must be an integer") if batch_size < 0: raise ValueError("batch_size must be >= 0") @@ -458,7 +462,7 @@ def skip(self, skip): :Parameters: - `skip`: the number of results to skip """ - if not isinstance(skip, (int, long)): + if not isinstance(skip, integer_types): raise TypeError("skip must be an integer") if skip < 0: raise ValueError("skip must be >= 0") @@ -480,7 +484,8 @@ def max_time_ms(self, max_time_ms): :Parameters: - `max_time_ms`: the time limit after which the operation is aborted """ - if not isinstance(max_time_ms, (int, long)) and max_time_ms is not None: + if (not isinstance(max_time_ms, integer_types) + and max_time_ms is not None): raise TypeError("max_time_ms must be an integer or None") self.__check_okay_to_chain() @@ -543,7 +548,7 @@ def __getitem__(self, index): self.__limit = limit return self - if isinstance(index, (int, long)): + if isinstance(index, integer_types): if index < 0: raise IndexError("Cursor instances do not support negative" "indices") @@ -727,9 +732,9 @@ def distinct(self, key): .. versionadded:: 1.2 """ - if not isinstance(key, basestring): - raise TypeError("key must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(key, string_type): + raise TypeError("key must be an " + "instance of %s" % (string_type.__name__,)) options = {"key": key} if self.__spec: @@ -758,7 +763,7 @@ def explain(self): # always use a hard limit for explains if c.__limit: c.__limit = -abs(c.__limit) - return c.next() + return next(c) def hint(self, index): """Adds a 'hint', telling Mongo the proper index to use for the query. @@ -989,6 +994,8 @@ def next(self): else: raise StopIteration + __next__ = next + def __enter__(self): return self @@ -1018,7 +1025,7 @@ def _deepcopy(self, x, memo=None): if not hasattr(x, 'items'): y, is_list, iterator = [], True, enumerate(x) else: - y, is_list, iterator = {}, False, x.iteritems() + y, is_list, iterator = {}, False, iteritems(x) if memo is None: memo = {} diff --git a/pymongo/cursor_manager.py b/pymongo/cursor_manager.py index 9bdeb71a1e..a893b2224b 100644 --- a/pymongo/cursor_manager.py +++ b/pymongo/cursor_manager.py @@ -24,6 +24,7 @@ """ import weakref +from bson.py3compat import integer_types class CursorManager(object): @@ -48,8 +49,8 @@ def close(self, cursor_id): :Parameters: - `cursor_id`: cursor id to close """ - if not isinstance(cursor_id, (int, long)): - raise TypeError("cursor_id must be an instance of (int, long)") + if not isinstance(cursor_id, integer_types): + raise TypeError("cursor_id must be an integer") self.__connection().kill_cursors([cursor_id]) @@ -83,8 +84,8 @@ def close(self, cursor_id): :Parameters: - `cursor_id`: cursor id to close """ - if not isinstance(cursor_id, (int, long)): - raise TypeError("cursor_id must be an instance of (int, long)") + if not isinstance(cursor_id, integer_types): + raise TypeError("cursor_id must be an integer") self.__dying_cursors.append(cursor_id) diff --git a/pymongo/database.py b/pymongo/database.py index a43010076a..258c354eea 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -19,6 +19,7 @@ from bson.binary import OLD_UUID_SUBTYPE from bson.code import Code from bson.dbref import DBRef +from bson.py3compat import iteritems, string_type, _unicode from bson.son import SON from pymongo import auth, common, helpers from pymongo.collection import Collection @@ -65,14 +66,14 @@ def __init__(self, connection, name): uuidrepresentation=connection.uuid_subtype, **connection.write_concern) - if not isinstance(name, basestring): + if not isinstance(name, string_type): raise TypeError("name must be an instance " - "of %s" % (basestring.__name__,)) + "of %s" % (string_type.__name__,)) if name != '$external': _check_name(name) - self.__name = unicode(name) + self.__name = _unicode(name) self.__connection = connection self.__incoming_manipulators = [] @@ -274,11 +275,11 @@ def _command(self, command, value=1, """Internal command helper. """ - if isinstance(command, basestring): + if isinstance(command, string_type): command_name = command.lower() command = SON([(command, value)]) else: - command_name = command.keys()[0].lower() + command_name = next(iter(command)).lower() as_class = kwargs.pop('as_class', None) fields = kwargs.pop('fields', None) @@ -435,13 +436,13 @@ def drop_collection(self, name_or_collection): if isinstance(name, Collection): name = name.name - if not isinstance(name, basestring): - raise TypeError("name_or_collection must be an instance of " - "%s or Collection" % (basestring.__name__,)) + if not isinstance(name, string_type): + raise TypeError("name_or_collection must be an " + "instance of %s" % (string_type.__name__,)) self.__connection._purge_index(self.__name, name) - self.command("drop", unicode(name), allowable_errors=["ns not found"]) + self.command("drop", _unicode(name), allowable_errors=["ns not found"]) def validate_collection(self, name_or_collection, scandata=False, full=False): @@ -474,11 +475,11 @@ def validate_collection(self, name_or_collection, if isinstance(name, Collection): name = name.name - if not isinstance(name, basestring): + if not isinstance(name, string_type): raise TypeError("name_or_collection must be an instance of " - "%s or Collection" % (basestring.__name__,)) + "%s or Collection" % (string_type.__name__,)) - result = self.command("validate", unicode(name), + result = self.command("validate", _unicode(name), scandata=scandata, full=full) valid = True @@ -489,7 +490,7 @@ def validate_collection(self, name_or_collection, raise CollectionInvalid("%s invalid: %s" % (name, info)) # Sharded results elif "raw" in result: - for _, res in result["raw"].iteritems(): + for _, res in iteritems(result["raw"]): if "result" in res: info = res["result"] if (info.find("exception") != -1 or @@ -626,6 +627,8 @@ def __iter__(self): def next(self): raise TypeError("'Database' object is not iterable") + __next__ = next + def _default_role(self, read_only): if self.name == "admin": if read_only: @@ -686,7 +689,7 @@ def _legacy_add_user(self, name, password, read_only, **kwargs): try: self.system.users.save(user, **self._get_wc_override()) - except OperationFailure, exc: + except OperationFailure as exc: # First admin user add fails gle in MongoDB >= 2.1.2 # See SERVER-4225 for more information. if 'login' in str(exc): @@ -722,13 +725,13 @@ def add_user(self, name, password=None, read_only=None, **kwargs): .. versionadded:: 1.4 """ - if not isinstance(name, basestring): - raise TypeError("name must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(name, string_type): + raise TypeError("name must be an " + "instance of %s" % (string_type.__name__,)) if password is not None: - if not isinstance(password, basestring): - raise TypeError("password must be an instance " - "of %s or None" % (basestring.__name__,)) + if not isinstance(password, string_type): + raise TypeError("password must be an " + "instance of %s" % (string_type.__name__,)) if len(password) == 0: raise ValueError("password can't be empty") if read_only is not None: @@ -739,7 +742,7 @@ def add_user(self, name, password=None, read_only=None, **kwargs): try: uinfo = self.command("usersInfo", name) - except OperationFailure, exc: + except OperationFailure as exc: # MongoDB >= 2.5.3 requires the use of commands to manage # users. "No such command" error didn't return an error # code (59) before MongoDB 2.4.7 so we assume that an error @@ -769,7 +772,7 @@ def remove_user(self, name): try: self.command("dropUser", name, writeConcern=self._get_wc_override()) - except OperationFailure, exc: + except OperationFailure as exc: # See comment in add_user try / except above. if exc.code in (59, None): self.system.users.remove({"user": name}, @@ -827,25 +830,25 @@ def authenticate(self, name, password=None, .. mongodoc:: authenticate """ - if not isinstance(name, basestring): - raise TypeError("name must be an instance " - "of %s" % (basestring.__name__,)) - if password is not None and not isinstance(password, basestring): - raise TypeError("password must be an instance " - "of %s" % (basestring.__name__,)) - if source is not None and not isinstance(source, basestring): - raise TypeError("source must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(name, string_type): + raise TypeError("name must be an " + "instance of %s" % (string_type.__name__,)) + if password is not None and not isinstance(password, string_type): + raise TypeError("password must be an " + "instance of %s" % (string_type.__name__,)) + if source is not None and not isinstance(source, string_type): + raise TypeError("source must be an " + "instance of %s" % (string_type.__name__,)) common.validate_auth_mechanism('mechanism', mechanism) validated_options = {} - for option, value in kwargs.iteritems(): + for option, value in iteritems(kwargs): normalized, val = common.validate_auth_option(option, value) validated_options[normalized] = val credentials = auth._build_credentials_tuple(mechanism, - source or self.name, unicode(name), - password and unicode(password) or None, + source or self.name, _unicode(name), + password and _unicode(password) or None, validated_options) self.connection._cache_credentials(self.name, credentials) return True diff --git a/pymongo/helpers.py b/pymongo/helpers.py index 0ecd1f479c..284f3623cd 100644 --- a/pymongo/helpers.py +++ b/pymongo/helpers.py @@ -21,6 +21,7 @@ import pymongo from bson.binary import OLD_UUID_SUBTYPE +from bson.py3compat import itervalues, string_type from bson.son import SON from pymongo.errors import (AutoReconnect, CursorNotFound, @@ -38,7 +39,7 @@ def _index_list(key_or_list, direction=None): if direction is not None: return [(key_or_list, direction)] else: - if isinstance(key_or_list, basestring): + if isinstance(key_or_list, string_type): return [(key_or_list, pymongo.ASCENDING)] elif not isinstance(key_or_list, (list, tuple)): raise TypeError("if no direction is specified, " @@ -54,7 +55,7 @@ def _index_document(index_list): if isinstance(index_list, dict): raise TypeError("passing a dict to sort/create_index/hint is not " "allowed - use a list of tuples instead. did you " - "mean %r?" % list(index_list.iteritems())) + "mean %r?" % list(iteritems(index_list))) elif not isinstance(index_list, (list, tuple)): raise TypeError("must use a list of (key, direction) pairs, " "not: " + repr(index_list)) @@ -63,9 +64,9 @@ def _index_document(index_list): index = SON() for (key, value) in index_list: - if not isinstance(key, basestring): + if not isinstance(key, string_type): raise TypeError("first item in each key pair must be a string") - if not isinstance(value, (basestring, int, dict)): + if not isinstance(value, (string_type, int, dict)): raise TypeError("second item in each key pair must be 1, -1, " "'2d', 'geoHaystack', or another valid MongoDB " "index specifier.") @@ -142,7 +143,7 @@ def _check_command_response(response, reset, msg=None, allowable_errors=None): # Mongos returns the error details in a 'raw' object # for some errors. if "raw" in response: - for shard in response["raw"].itervalues(): + for shard in itervalues(response["raw"]): if not shard.get("ok"): # Just grab the first error... details = shard @@ -216,9 +217,9 @@ def _fields_list_to_dict(fields): """ as_dict = {} for field in fields: - if not isinstance(field, basestring): + if not isinstance(field, string_type): raise TypeError("fields must be a list of key names, " - "each an instance of %s" % (basestring.__name__,)) + "each an instance of %s" % (string_type.__name__,)) as_dict[field] = 1 return as_dict diff --git a/pymongo/message.py b/pymongo/message.py index 17c09c0004..add4dae78e 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -44,17 +44,17 @@ _UPDATE = 1 _DELETE = 2 -_EMPTY = b('') -_BSONOBJ = b('\x03') -_ZERO_8 = b('\x00') -_ZERO_16 = b('\x00\x00') -_ZERO_32 = b('\x00\x00\x00\x00') -_ZERO_64 = b('\x00\x00\x00\x00\x00\x00\x00\x00') -_SKIPLIM = b('\x00\x00\x00\x00\xff\xff\xff\xff') +_EMPTY = b'' +_BSONOBJ = b'\x03' +_ZERO_8 = b'\x00' +_ZERO_16 = b'\x00\x00' +_ZERO_32 = b'\x00\x00\x00\x00' +_ZERO_64 = b'\x00\x00\x00\x00\x00\x00\x00\x00' +_SKIPLIM = b'\x00\x00\x00\x00\xff\xff\xff\xff' _OP_MAP = { - _INSERT: b('\x04documents\x00\x00\x00\x00\x00'), - _UPDATE: b('\x04updates\x00\x00\x00\x00\x00'), - _DELETE: b('\x04deletes\x00\x00\x00\x00\x00'), + _INSERT: b'\x04documents\x00\x00\x00\x00\x00', + _UPDATE: b'\x04updates\x00\x00\x00\x00\x00', + _DELETE: b'\x04deletes\x00\x00\x00\x00\x00', } @@ -242,7 +242,7 @@ def _insert_message(insert_message, send_safe): send_safe), send_safe) # Exception type could be OperationFailure or a subtype # (e.g. DuplicateKeyError) - except OperationFailure, exc: + except OperationFailure as exc: # Like it says, continue on error... if continue_on_error: # Store exception details to re-raise after the final batch. @@ -294,7 +294,7 @@ def _do_batched_write_command(namespace, operation, command, # Save space for message length and request id buf.write(_ZERO_64) # responseTo, opCode - buf.write(b("\x00\x00\x00\x00\xd4\x07\x00\x00")) + buf.write(b"\x00\x00\x00\x00\xd4\x07\x00\x00") # No options buf.write(_ZERO_32) # Namespace as C string @@ -380,7 +380,7 @@ def send_message(): buf.truncate() idx_offset += idx idx = 0 - key = b('0') + key = b'0' buf.write(_BSONOBJ) buf.write(key) buf.write(_ZERO_8) diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index a5b5cc45c1..fbef0a6f33 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -40,7 +40,11 @@ import time import warnings -from bson.py3compat import b +from bson.py3compat import (_unicode, + integer_types, + iteritems, + itervalues, + string_type) from pymongo import (auth, common, database, @@ -59,7 +63,7 @@ InvalidURI, OperationFailure) from pymongo.member import Member -EMPTY = b("") +EMPTY = b"" def _partition_node(node): @@ -227,7 +231,7 @@ def __init__(self, host=None, port=None, max_pool_size=100, """ if host is None: host = self.HOST - if isinstance(host, basestring): + if isinstance(host, string_type): host = [host] if port is None: port = self.PORT @@ -274,7 +278,7 @@ def __init__(self, host=None, port=None, max_pool_size=100, event_class = kwargs.pop('_event_class', None) options = {} - for option, value in kwargs.iteritems(): + for option, value in iteritems(kwargs): option, value = common.validate(option, value) options[option] = value options.update(opts) @@ -299,7 +303,7 @@ def __init__(self, host=None, port=None, max_pool_size=100, self.__ssl_cert_reqs = options.get('ssl_cert_reqs', None) self.__ssl_ca_certs = options.get('ssl_ca_certs', None) - ssl_kwarg_keys = [k for k in kwargs.keys() if k.startswith('ssl_')] + ssl_kwarg_keys = [k for k in kwargs if k.startswith('ssl_')] if self.__use_ssl == False and ssl_kwarg_keys: raise ConfigurationError("ssl has not been enabled but the " "following ssl parameters have been set: " @@ -355,7 +359,7 @@ def __init__(self, host=None, port=None, max_pool_size=100, if _connect: try: self._ensure_connected(True) - except AutoReconnect, e: + except AutoReconnect as e: # ConnectionFailure makes more sense here than AutoReconnect raise ConnectionFailure(str(e)) @@ -368,12 +372,12 @@ def __init__(self, host=None, port=None, max_pool_size=100, credentials = auth._build_credentials_tuple(mechanism, source, - unicode(username), - unicode(password), + _unicode(username), + _unicode(password), options) try: self._cache_credentials(source, credentials, _connect) - except OperationFailure, exc: + except OperationFailure as exc: raise ConfigurationError(str(exc)) def _cached(self, dbname, coll, index): @@ -479,7 +483,7 @@ def __check_auth(self, sock_info): """Authenticate using cached database credentials. """ if self.__auth_credentials or sock_info.authset: - cached = set(self.__auth_credentials.itervalues()) + cached = set(itervalues(self.__auth_credentials)) authset = sock_info.authset.copy() @@ -806,7 +810,7 @@ def __ensure_member(self): try: member, nodes = self.__find_node() return member - except Exception, e: + except Exception as e: exc = e raise finally: @@ -882,7 +886,7 @@ def __find_node(self): # The server is available but something failed, e.g. auth, # wrong replica set name, or incompatible wire protocol. raise - except Exception, why: + except Exception as why: errors.append(str(why)) if len(mongos_candidates): @@ -910,7 +914,7 @@ def __socket(self, member): connection_pool.start_request() sock_info = connection_pool.get_socket() - except socket.error, why: + except socket.error as why: self.disconnect() # Check if a unix domain socket @@ -1130,7 +1134,7 @@ def _send_message(self, message, return rv except OperationFailure: raise - except (ConnectionFailure, socket.error), e: + except (ConnectionFailure, socket.error) as e: self.disconnect() raise AutoReconnect(str(e)) except: @@ -1196,7 +1200,7 @@ def _send_message_with_response(self, message, **kwargs): try: response = self.__send_and_receive(message, sock_info) return (None, (response, sock_info, member.pool)) - except (ConnectionFailure, socket.error), e: + except (ConnectionFailure, socket.error) as e: self.disconnect() raise AutoReconnect(str(e)) finally: @@ -1315,7 +1319,7 @@ def close_cursor(self, cursor_id): :Parameters: - `cursor_id`: id of cursor to close """ - if not isinstance(cursor_id, (int, long)): + if not isinstance(cursor_id, integer_types): raise TypeError("cursor_id must be an instance of (int, long)") self.__cursor_manager.close(cursor_id) @@ -1360,9 +1364,9 @@ def drop_database(self, name_or_database): if isinstance(name, database.Database): name = name.name - if not isinstance(name, basestring): - raise TypeError("name_or_database must be an instance of " - "%s or Database" % (basestring.__name__,)) + if not isinstance(name, string_type): + raise TypeError("name_or_database must be an instance " + "of %s or a Database" % (string_type.__name__,)) self._purge_index(name) self[name].command("dropDatabase") @@ -1394,12 +1398,12 @@ def copy_database(self, from_name, to_name, .. versionadded:: 1.5 """ - if not isinstance(from_name, basestring): - raise TypeError("from_name must be an instance " - "of %s" % (basestring.__name__,)) - if not isinstance(to_name, basestring): - raise TypeError("to_name must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(from_name, string_type): + raise TypeError("from_name must be an " + "instance of %s" % (string_type.__name__,)) + if not isinstance(to_name, string_type): + raise TypeError("to_name must be an " + "instance of %s" % (string_type.__name__,)) database._check_name(to_name) @@ -1487,3 +1491,5 @@ def __iter__(self): def next(self): raise TypeError("'MongoClient' object is not iterable") + + __next__ = next diff --git a/pymongo/mongo_replica_set_client.py b/pymongo/mongo_replica_set_client.py index a8f28aac69..3236da80cf 100644 --- a/pymongo/mongo_replica_set_client.py +++ b/pymongo/mongo_replica_set_client.py @@ -39,7 +39,11 @@ import time import weakref -from bson.py3compat import b +from bson.py3compat import (_unicode, + integer_types, + iteritems, + itervalues, + string_type) from pymongo import (auth, common, database, @@ -60,7 +64,7 @@ InvalidOperation) from pymongo.thread_util import DummyLock -EMPTY = b("") +EMPTY = b"" MAX_RETRY = 3 MONITORS = set() @@ -292,7 +296,8 @@ def threadlocal(self): def __str__(self): return '' % ( - ', '.join(str(member) for member in self._host_to_member.itervalues()), + ', '.join(str(member) + for member in itervalues(self._host_to_member)), self.writer and '%s:%s' % self.writer or None) @@ -610,7 +615,7 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100, self.pool_class = kwargs.pop('_pool_class', pool.Pool) self.__monitor_class = kwargs.pop('_monitor_class', None) - for option, value in kwargs.iteritems(): + for option, value in iteritems(kwargs): option, value = common.validate(option, value) self.__opts[option] = value self.__opts.update(options) @@ -645,7 +650,7 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100, self.__ssl_cert_reqs = self.__opts.get('ssl_cert_reqs', None) self.__ssl_ca_certs = self.__opts.get('ssl_ca_certs', None) - ssl_kwarg_keys = [k for k in kwargs.keys() if k.startswith('ssl_')] + ssl_kwarg_keys = [k for k in kwargs if k.startswith('ssl_')] if self.__use_ssl is False and ssl_kwarg_keys: raise ConfigurationError("ssl has not been enabled but the " "following ssl parameters have been set: " @@ -673,7 +678,7 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100, if _connect: try: self.refresh(initial=True) - except AutoReconnect, e: + except AutoReconnect as e: # ConnectionFailure makes more sense here than AutoReconnect raise ConnectionFailure(str(e)) @@ -686,12 +691,12 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100, credentials = auth._build_credentials_tuple(mechanism, source, - unicode(username), - unicode(password), + _unicode(username), + _unicode(password), options) try: self._cache_credentials(source, credentials, _connect) - except OperationFailure, exc: + except OperationFailure as exc: raise ConfigurationError(str(exc)) # Start the monitor after we know the configuration is correct. @@ -813,7 +818,7 @@ def __check_auth(self, sock_info): """Authenticate using cached database credentials. """ if self.__auth_credentials or sock_info.authset: - cached = set(self.__auth_credentials.itervalues()) + cached = set(itervalues(self.__auth_credentials)) authset = sock_info.authset.copy() @@ -1104,7 +1109,7 @@ def refresh(self, initial=False): rs_state = self.__rs_state try: self.__rs_state = self.__create_rs_state(rs_state, initial) - except ConfigurationError, e: + except ConfigurationError as e: self.__rs_state = rs_state.clone_with_error(e) raise @@ -1170,7 +1175,7 @@ def __create_rs_state(self, rs_state, initial): if response['ismaster']: writer = node - except (ConnectionFailure, socket.error), why: + except (ConnectionFailure, socket.error) as why: if member: member.pool.discard_socket(sock_info) errors.append("%s:%d: %s" % (node[0], node[1], str(why))) @@ -1242,7 +1247,7 @@ def __create_rs_state(self, rs_state, initial): if writer: response = members[writer].ismaster_response elif members: - response = members.values()[0].ismaster_response + response = next(itervalues(members)).ismaster_response else: response = {} @@ -1509,7 +1514,7 @@ def _send_message(self, msg, with_last_error=False, return rv except OperationFailure: raise - except(ConnectionFailure, socket.error), why: + except(ConnectionFailure, socket.error) as why: member.pool.discard_socket(sock_info) if _connection_to_use in (None, -1): self.disconnect() @@ -1549,11 +1554,11 @@ def __try_read(self, member, msg, **kwargs): """ try: return self.__send_and_receive(member, msg, **kwargs) - except socket.timeout, e: + except socket.timeout as e: # Could be one slow query, don't refresh. host, port = member.host raise AutoReconnect("%s:%d: %s" % (host, port, e)) - except (socket.error, ConnectionFailure), why: + except (socket.error, ConnectionFailure) as why: # Try to replace our RSState with a clone where this member is # marked "down", to reduce exceptions on other threads, or repeated # exceptions on this thread. We accept that there's a race @@ -1629,7 +1634,7 @@ def _send_message_with_response(self, msg, return ( pinned_member.host, self.__try_read(pinned_member, msg, **kwargs)) - except AutoReconnect, why: + except AutoReconnect as why: if pref == ReadPreference.PRIMARY: self.disconnect() raise @@ -1661,7 +1666,7 @@ def _send_message_with_response(self, msg, # unless read preference changes rs_state.pin_host(member.host, pref) return member.host, response - except AutoReconnect, why: + except AutoReconnect as why: if pref == ReadPreference.PRIMARY: raise @@ -1801,7 +1806,7 @@ def close_cursor(self, cursor_id, _conn_id): :Parameters: - `cursor_id`: id of cursor to close """ - if not isinstance(cursor_id, (int, long)): + if not isinstance(cursor_id, integer_types): raise TypeError("cursor_id must be an instance of (int, long)") self._send_message(message.kill_cursors([cursor_id]), @@ -1833,9 +1838,9 @@ def drop_database(self, name_or_database): if isinstance(name, database.Database): name = name.name - if not isinstance(name, basestring): - raise TypeError("name_or_database must be an instance of " - "%s or Database" % (basestring.__name__,)) + if not isinstance(name, string_type): + raise TypeError("name_or_database must be an instance " + "of %s or a Database" % (string_type.__name__,)) self._purge_index(name) self[name].command("dropDatabase") @@ -1865,12 +1870,12 @@ def copy_database(self, from_name, to_name, .. note:: Specifying `username` and `password` requires server version **>= 1.3.3+**. """ - if not isinstance(from_name, basestring): - raise TypeError("from_name must be an instance " - "of %s" % (basestring.__name__,)) - if not isinstance(to_name, basestring): - raise TypeError("to_name must be an instance " - "of %s" % (basestring.__name__,)) + if not isinstance(from_name, string_type): + raise TypeError("from_name must be an " + "instance of %s" % (string_type.__name__,)) + if not isinstance(to_name, string_type): + raise TypeError("to_name must be an " + "instance of %s" % (string_type.__name__,)) database._check_name(to_name) diff --git a/pymongo/pool.py b/pymongo/pool.py index e3ba94e47a..c74d72ce2d 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -221,7 +221,7 @@ def create_connection(self): try: sock.connect(host) return sock - except socket.error, e: + except socket.error as e: if sock is not None: sock.close() raise e @@ -243,7 +243,7 @@ def create_connection(self): sock.settimeout(self.conn_timeout or 20.0) sock.connect(sa) return sock - except socket.error, e: + except socket.error as e: err = e if sock is not None: sock.close() diff --git a/pymongo/uri_parser.py b/pymongo/uri_parser.py index dc7359fab8..078099ede3 100644 --- a/pymongo/uri_parser.py +++ b/pymongo/uri_parser.py @@ -15,7 +15,12 @@ """Tools to parse and validate a MongoDB URI.""" -from urllib import unquote_plus +from bson.py3compat import PY3, iteritems, string_type + +if PY3: + from urllib.parse import unquote_plus +else: + from urllib import unquote_plus from pymongo.common import validate from pymongo.errors import (ConfigurationError, @@ -133,7 +138,7 @@ def parse_host(entity, default_port=DEFAULT_PORT): "address literal must be enclosed in '[' " "and ']' according to RFC 2732.") host, port = host.split(':', 1) - if isinstance(port, basestring): + if isinstance(port, string_type): if not port.isdigit(): raise ConfigurationError("Port number must be an integer.") port = int(port) @@ -149,7 +154,7 @@ def validate_options(opts): - `opts`: A dict of MongoDB URI options. """ normalized = {} - for option, value in opts.iteritems(): + for option, value in iteritems(opts): option, value = validate(option, value) # str(option) to ensure that a unicode URI results in plain 'str' # option names. 'normalized' is then suitable to be passed as kwargs @@ -322,6 +327,6 @@ def parse_uri(uri, default_port=DEFAULT_PORT): import sys try: pprint.pprint(parse_uri(sys.argv[1])) - except (InvalidURI, UnsupportedOption), e: - print e + except (InvalidURI, UnsupportedOption) as e: + print(e) sys.exit(0)