Skip to content

Commit

Permalink
PYTHON-674 python 2/3 single-source for the pymongo module
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Lovett committed Apr 23, 2014
1 parent f5c71a5 commit d494105
Show file tree
Hide file tree
Showing 17 changed files with 262 additions and 208 deletions.
3 changes: 2 additions & 1 deletion bson/json_util.py
Expand Up @@ -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)
Expand Down
21 changes: 18 additions & 3 deletions bson/py3compat.py
Expand Up @@ -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
Expand All @@ -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)
3 changes: 1 addition & 2 deletions pymongo/__init__.py
Expand Up @@ -14,7 +14,6 @@

"""Python driver for MongoDB."""


ASCENDING = 1
"""Ascending sort order."""
DESCENDING = -1
Expand Down Expand Up @@ -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))

Expand Down
46 changes: 21 additions & 25 deletions pymongo/auth.py
Expand Up @@ -14,24 +14,20 @@

"""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:
import kerberos
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

Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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))


Expand All @@ -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))])
Expand Down
24 changes: 13 additions & 11 deletions pymongo/bulk.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -378,15 +380,15 @@ 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)
full_result['writeErrors'].append(error)
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.
Expand Down

0 comments on commit d494105

Please sign in to comment.