00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 """Bits and pieces used by the driver that don't really fit elsewhere."""
00016
00017 try:
00018 import hashlib
00019 _md5func = hashlib.md5
00020 except:
00021 import md5
00022 _md5func = md5.new
00023 import struct
00024
00025 import bson
00026 from bson.son import SON
00027 import pymongo
00028 from pymongo.errors import (AutoReconnect,
00029 OperationFailure,
00030 TimeoutError)
00031
00032
00033 def _index_list(key_or_list, direction=None):
00034 """Helper to generate a list of (key, direction) pairs.
00035
00036 Takes such a list, or a single key, or a single key and direction.
00037 """
00038 if direction is not None:
00039 return [(key_or_list, direction)]
00040 else:
00041 if isinstance(key_or_list, basestring):
00042 return [(key_or_list, pymongo.ASCENDING)]
00043 elif not isinstance(key_or_list, list):
00044 raise TypeError("if no direction is specified, "
00045 "key_or_list must be an instance of list")
00046 return key_or_list
00047
00048
00049 def _index_document(index_list):
00050 """Helper to generate an index specifying document.
00051
00052 Takes a list of (key, direction) pairs.
00053 """
00054 if isinstance(index_list, dict):
00055 raise TypeError("passing a dict to sort/create_index/hint is not "
00056 "allowed - use a list of tuples instead. did you "
00057 "mean %r?" % list(index_list.iteritems()))
00058 elif not isinstance(index_list, list):
00059 raise TypeError("must use a list of (key, direction) pairs, "
00060 "not: " + repr(index_list))
00061 if not len(index_list):
00062 raise ValueError("key_or_list must not be the empty list")
00063
00064 index = SON()
00065 for (key, value) in index_list:
00066 if not isinstance(key, basestring):
00067 raise TypeError("first item in each key pair must be a string")
00068 if value not in [pymongo.ASCENDING, pymongo.DESCENDING, pymongo.GEO2D]:
00069 raise TypeError("second item in each key pair must be ASCENDING, "
00070 "DESCENDING, or GEO2D")
00071 index[key] = value
00072 return index
00073
00074
00075 def _unpack_response(response, cursor_id=None, as_class=dict, tz_aware=False):
00076 """Unpack a response from the database.
00077
00078 Check the response for errors and unpack, returning a dictionary
00079 containing the response data.
00080
00081 :Parameters:
00082 - `response`: byte string as returned from the database
00083 - `cursor_id` (optional): cursor_id we sent to get this response -
00084 used for raising an informative exception when we get cursor id not
00085 valid at server response
00086 - `as_class` (optional): class to use for resulting documents
00087 """
00088 response_flag = struct.unpack("<i", response[:4])[0]
00089 if response_flag & 1:
00090
00091 assert cursor_id is not None
00092
00093 raise OperationFailure("cursor id '%s' not valid at server" %
00094 cursor_id)
00095 elif response_flag & 2:
00096 error_object = bson.BSON(response[20:]).decode()
00097 if error_object["$err"] == "not master":
00098 raise AutoReconnect("master has changed")
00099 raise OperationFailure("database error: %s" %
00100 error_object["$err"])
00101
00102 result = {}
00103 result["cursor_id"] = struct.unpack("<q", response[4:12])[0]
00104 result["starting_from"] = struct.unpack("<i", response[12:16])[0]
00105 result["number_returned"] = struct.unpack("<i", response[16:20])[0]
00106 result["data"] = bson.decode_all(response[20:], as_class, tz_aware)
00107 assert len(result["data"]) == result["number_returned"]
00108 return result
00109
00110
00111 def _check_command_response(response, reset, msg="%s", allowable_errors=[]):
00112 if not response["ok"]:
00113 if "wtimeout" in response and response["wtimeout"]:
00114 raise TimeoutError(msg % response["errmsg"])
00115 if not response["errmsg"] in allowable_errors:
00116 if response["errmsg"] == "not master":
00117 reset()
00118 raise AutoReconnect("not master")
00119 if response["errmsg"] == "db assertion failure":
00120 ex_msg = ("db assertion failure, assertion: '%s'" %
00121 response.get("assertion", ""))
00122 if "assertionCode" in response:
00123 ex_msg += (", assertionCode: %d" %
00124 (response["assertionCode"],))
00125 raise OperationFailure(ex_msg, response.get("assertionCode"))
00126 raise OperationFailure(msg % response["errmsg"])
00127
00128
00129 def _password_digest(username, password):
00130 """Get a password digest to use for authentication.
00131 """
00132 if not isinstance(password, basestring):
00133 raise TypeError("password must be an instance of basestring")
00134 if not isinstance(username, basestring):
00135 raise TypeError("username must be an instance of basestring")
00136
00137 md5hash = _md5func()
00138 md5hash.update("%s:mongo:%s" % (username.encode('utf-8'),
00139 password.encode('utf-8')))
00140 return unicode(md5hash.hexdigest())
00141
00142
00143 def _auth_key(nonce, username, password):
00144 """Get an auth key to use for authentication.
00145 """
00146 digest = _password_digest(username, password)
00147 md5hash = _md5func()
00148 md5hash.update("%s%s%s" % (nonce, unicode(username), digest))
00149 return unicode(md5hash.hexdigest())
00150
00151
00152 def _fields_list_to_dict(fields):
00153 """Takes a list of field names and returns a matching dictionary.
00154
00155 ["a", "b"] becomes {"a": 1, "b": 1}
00156
00157 and
00158
00159 ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1}
00160 """
00161 as_dict = {}
00162 for field in fields:
00163 if not isinstance(field, basestring):
00164 raise TypeError("fields must be a list of key names as "
00165 "(string, unicode)")
00166 as_dict[field] = 1
00167 return as_dict