diff --git a/gridfs/grid_file.py b/gridfs/grid_file.py index 0fa2d5818..0c6f06c2e 100644 --- a/gridfs/grid_file.py +++ b/gridfs/grid_file.py @@ -262,8 +262,8 @@ class GridFile(object): self.__flush_write_buffer() - md5 = self.__collection.database.command(SON([("filemd5", self.__id), - ("root", self.__collection.name)]))["md5"] + md5 = self.__collection.database.command("filemd5", self.__id, + root=self.__collection.name)["md5"] grid_file = self.__collection.files.find_one({"_id": self.__id}) grid_file["md5"] = md5 diff --git a/pymongo/collection.py b/pymongo/collection.py index d735d9cee..f6226eedf 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -115,13 +115,12 @@ class Collection(object): # Send size as a float, not an int/long. BSON can only handle 32-bit # ints which conflicts w/ max collection size of 10000000000. - if options and "size" in options: - options["size"] = float(options["size"]) - - command = SON({"create": self.__name}) - command.update(options) - - self.__database.command(command) + if options: + if "size" in options: + options["size"] = float(options["size"]) + self.__database.command("create", self.__name, **options) + else: + self.__database.command("create", self.__name) def __getattr__(self, name): """Get a sub-collection of this collection by name. @@ -697,9 +696,7 @@ class Collection(object): self.__database.connection._purge_index(self.__database.name, self.__name, name) - self.__database.command(SON([("deleteIndexes", - self.__name), - ("index", name)]), + self.__database.command("deleteIndexes", self.__name, index=name, allowable_errors=["ns not found"]) def index_information(self): @@ -785,7 +782,7 @@ class Collection(object): if finalize is not None: group["finalize"] = Code(finalize) - return self.__database.command({"group":group})["retval"] + return self.__database.command("group", group)["retval"] def rename(self, new_name): """Rename this collection. @@ -809,11 +806,9 @@ class Collection(object): if new_name[0] == "." or new_name[-1] == ".": raise InvalidName("collecion names must not start or end with '.'") - rename_command = SON([("renameCollection", self.__full_name), - ("to", "%s.%s" % (self.__database.name, - new_name))]) - - self.__database.connection.admin.command(rename_command) + new_name = "%s.%s" % (self.__database.name, new_name) + self.__database.connection.admin.command("renameCollection", + self.__full_name, to=new_name) def distinct(self, key): """Get a list of distinct values for `key` among all documents @@ -863,11 +858,8 @@ class Collection(object): .. mongodoc:: mapreduce """ - command = SON([("mapreduce", self.__name), - ("map", map), ("reduce", reduce)]) - command.update(**kwargs) - - response = self.__database.command(command) + response = self.__database.command("mapreduce", self.__name, + map=map, reduce=reduce, **kwargs) if full_response: return response return self.__database[response["result"]] diff --git a/pymongo/connection.py b/pymongo/connection.py index cd70360e0..5a1c2108d 100644 --- a/pymongo/connection.py +++ b/pymongo/connection.py @@ -804,19 +804,19 @@ class Connection(object): # TODO support auth for pooling database._check_name(to_name) - command = SON([("copydb", 1), ("fromdb", from_name), ("todb", to_name)]) + command = {"fromdb": from_name, "todb": to_name} if from_host is not None: command["fromhost"] = from_host if username is not None: - nonce = self.admin.command(SON([("copydbgetnonce", 1), - ("fromhost", from_host)]))["nonce"] + nonce = self.admin.command("copydbgetnonce", + fromhost=from_host)["nonce"] command["username"] = username command["nonce"] = nonce command["key"] = helpers._auth_key(nonce, username, password) - return self.admin.command(command) + return self.admin.command("copydb", **command) def __iter__(self): return self diff --git a/pymongo/cursor.py b/pymongo/cursor.py index 7a7fedcae..fd008c340 100644 --- a/pymongo/cursor.py +++ b/pymongo/cursor.py @@ -301,9 +301,7 @@ class Cursor(object): :meth:`~pymongo.cursor.Cursor.__len__` was deprecated in favor of calling :meth:`count` with `with_limit_and_skip` set to ``True``. """ - command = SON([("count", self.__collection.name), - ("query", self.__spec), - ("fields", self.__fields)]) + command = {"query": self.__spec, "fields": self.__fields} if with_limit_and_skip: if self.__limit: @@ -311,10 +309,12 @@ class Cursor(object): if self.__skip: command["skip"] = self.__skip - response = self.__collection.database.command(command, allowable_errors=["ns missing"]) - if response.get("errmsg", "") == "ns missing": + r = self.__collection.database.command("count", self.__collection.name, + allowable_errors=["ns missing"], + **command) + if r.get("errmsg", "") == "ns missing": return 0 - return int(response["n"]) + return int(r["n"]) def distinct(self, key): """Get a list of distinct values for `key` among all documents @@ -335,12 +335,13 @@ class Cursor(object): if not isinstance(key, basestring): raise TypeError("key must be an instance of basestring") - command = SON([("distinct", self.__collection.name), ("key", key)]) - + options = {"key": key} if self.__spec: - command["query"] = self.__spec + options["query"] = self.__spec - return self.__collection.database.command(command)["values"] + return self.__collection.database.command("distinct", + self.__collection.name, + **options)["values"] def explain(self): """Returns an explain plan record for this cursor. diff --git a/pymongo/database.py b/pymongo/database.py index 944dff9ce..253c8bedc 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -230,17 +230,36 @@ class Database(object): def _command(self, command, allowable_errors=[], check=True, sock=None): warnings.warn("The '_command' method is deprecated. " "Please use 'command' instead.", DeprecationWarning) - return self.command(command, check, allowable_errors, sock) + return self.command(command, check=check, + allowable_errors=allowable_errors, _sock=sock) - # TODO api could be nicer like take a verb and a subject and then kwargs - # for options - def command(self, command, check=True, allowable_errors=[], _sock=None): + def command(self, command, value=1, + check=True, allowable_errors=[], _sock=None, **kwargs): """Issue a MongoDB command. Send command `command` to the database and return the - response. If `command` is an instance of :class:`str` then the - command ``{command: 1}`` will be sent. Otherwise, `command` - must be an instance of :class:`dict` and will be sent as is. + response. If `command` is an instance of :class:`basestring` + then the command {`command`: `value`} will be sent. Otherwise, + `command` must be an instance of :class:`dict` and will be + sent as is. + + Any additional keyword arguments will be added to the final + command document before it is sent. + + For example, a command like ``{buildinfo: 1}`` can be sent + using: + + >>> db.command("buildinfo") + + For a command where the value matters, like ``{collstats: + collection_name}`` we can do: + + >>> db.command("collstats", collection_name) + + For commands that take additional arguments we can use + kwargs. So ``{filemd5: object_id, root: file_root}`` becomes: + + >>> db.command("filemd5", object_id, root=file_root) :Parameters: - `command`: document representing the command to be issued, @@ -248,15 +267,22 @@ class Database(object): .. note:: the order of keys in the `command` document is significant (the "verb" must come first), so commands - which require multiple keys (eg, `findandmodify`) - should use an instance of :class:`~pymongo.son.SON` - instead of a Python `dict`. + which require multiple keys (e.g. `findandmodify`) + should use an instance of :class:`~pymongo.son.SON` or + a string and kwargs instead of a Python `dict`. + - `value` (optional): value to use for the command verb when + `command` is passed as a string - `check` (optional): check the response for errors, raising :class:`~pymongo.errors.OperationFailure` if there are any - - `allowable_errors`: if `check` is ``True``, error messages in this - list will be ignored by error-checking + - `allowable_errors`: if `check` is ``True``, error messages + in this list will be ignored by error-checking + - `**kwargs` (optional): additional keyword arguments will + be added to the command document before it is sent + .. versionchanged:: 1.5.1+ + Added the `value` argument for string commands, and keyword + arguments for additional command options. .. versionchanged:: 1.5 `command` can be a string in addition to a full document. .. versionadded:: 1.4 @@ -264,8 +290,10 @@ class Database(object): .. mongodoc:: commands """ - if isinstance(command, str): - command = {command: 1} + if isinstance(command, basestring): + command = SON([(command, value)]) + + command.update(kwargs) result = self["$cmd"].find_one(command, _sock=_sock, _must_use_master=True, @@ -308,7 +336,7 @@ class Database(object): if name not in self.collection_names(): return - self.command({"drop": unicode(name)}) + self.command("drop", unicode(name)) def validate_collection(self, name_or_collection): """Validate a collection. @@ -324,7 +352,7 @@ class Database(object): raise TypeError("name_or_collection must be an instance of " "(Collection, str, unicode)") - result = self.command({"validate": unicode(name)}) + result = self.command("validate", unicode(name)) info = result["result"] if info.find("exception") != -1 or info.find("corrupt") != -1: @@ -339,7 +367,7 @@ class Database(object): .. mongodoc:: profiling """ - result = self.command({"profile": -1}) + result = self.command("profile", -1) assert result["was"] >= 0 and result["was"] <= 2 return result["was"] @@ -359,7 +387,7 @@ class Database(object): if not isinstance(level, int) or level < 0 or level > 2: raise ValueError("level must be one of (OFF, SLOW_ONLY, ALL)") - self.command({"profile": level}) + self.command("profile", level) def profiling_info(self): """Returns a list containing current profiling information. @@ -496,8 +524,7 @@ class Database(object): nonce = self.command("getnonce")["nonce"] key = helpers._auth_key(nonce, name, password) try: - self.command(SON([("authenticate", 1), ("user", unicode(name)), - ("nonce", nonce), ("key", key)])) + self.command("authenticate", user=unicode(name), nonce=nonce, key=key) return True except OperationFailure: return False @@ -549,8 +576,7 @@ class Database(object): if not isinstance(code, Code): code = Code(code) - command = SON([("$eval", code), ("args", list(args))]) - result = self.command(command) + result = self.command("$eval", code, args=args) return result.get("retval", None) def __call__(self, *args, **kwargs): diff --git a/test/test_cursor.py b/test/test_cursor.py index bfcd78903..d6e7d73fd 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -303,60 +303,59 @@ class TestCursor(unittest.TestCase): db = self.db db.drop_collection("test") - client_cursors = db.command({"cursorInfo": 1})["clientCursors_size"] - by_location = db.command({"cursorInfo": 1})["byLocation_size"] + client_cursors = db.command("cursorInfo")["clientCursors_size"] + by_location = db.command("cursorInfo")["byLocation_size"] test = db.test for i in range(10000): test.insert({"i": i}) self.assertEqual(client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + db.command("cursorInfo")["clientCursors_size"]) self.assertEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) for _ in range(10): db.test.find_one() self.assertEqual(client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + db.command("cursorInfo")["clientCursors_size"]) self.assertEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) for _ in range(10): for x in db.test.find(): break self.assertEqual(client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + db.command("cursorInfo")["clientCursors_size"]) self.assertEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) a = db.test.find() for x in a: break - self.assertNotEqual( - client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + self.assertNotEqual(client_cursors, + db.command("cursorInfo")["clientCursors_size"]) self.assertNotEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) del a self.assertEqual(client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + db.command("cursorInfo")["clientCursors_size"]) self.assertEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) a = db.test.find().limit(10) for x in a: break self.assertEqual(client_cursors, - db.command({"cursorInfo": 1})["clientCursors_size"]) + db.command("cursorInfo")["clientCursors_size"]) self.assertEqual(by_location, - db.command({"cursorInfo": 1})["byLocation_size"]) + db.command("cursorInfo")["byLocation_size"]) def test_rewind(self): self.db.test.save({"x": 1}) diff --git a/test/test_database.py b/test/test_database.py index 46abe027d..6bfe80985 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -188,11 +188,11 @@ class TestDatabase(unittest.TestCase): self.assertEqual(None, db.error()) self.assertEqual(None, db.previous_error()) - db.command({"forceerror": 1}, check=False) + db.command("forceerror", check=False) self.assert_(db.error()) self.assert_(db.previous_error()) - db.command({"forceerror": 1}, check=False) + db.command("forceerror", check=False) self.assert_(db.error()) prev_error = db.previous_error() self.assertEqual(prev_error["nPrev"], 1) diff --git a/test/test_master_slave_connection.py b/test/test_master_slave_connection.py index 302a8b5d2..f95a35913 100644 --- a/test/test_master_slave_connection.py +++ b/test/test_master_slave_connection.py @@ -184,11 +184,10 @@ class TestMasterSlaveConnection(unittest.TestCase): def cursor_count(): count = 0 - res = self.connection.master.test_pymongo.command({ - "cursorInfo": 1}) + res = self.connection.master.test_pymongo.command("cursorInfo") count += res["clientCursors_size"] for slave in self.connection.slaves: - res = slave.test_pymongo.command({"cursorInfo": 1}) + res = slave.test_pymongo.command("cursorInfo") count += res["clientCursors_size"] return count