improvements to the db.command api

This commit is contained in:
Mike Dirolf 2010-03-19 16:17:59 -04:00
parent d7ffcf3b65
commit b989e3e34d
8 changed files with 97 additions and 80 deletions

View File

@ -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

View File

@ -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"]]

View File

@ -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

View File

@ -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.

View File

@ -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):

View File

@ -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})

View File

@ -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)

View File

@ -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