Skip to content

Commit 71e083d

Browse files
committed
PYTHON-820 - count and distinct changes to comply with CRUD.
1 parent 06b35d4 commit 71e083d

File tree

3 files changed

+70
-32
lines changed

3 files changed

+70
-32
lines changed

pymongo/collection.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -940,16 +940,42 @@ def process_cursor(cursor):
940940
cursor['cursor'],
941941
address) for cursor in result['cursors']]
942942

943-
def count(self):
943+
def count(self, filter=None, **kwargs):
944944
"""Get the number of documents in this collection.
945945
946-
To get the number of documents matching a specific query use
947-
:meth:`pymongo.cursor.Cursor.count`.
946+
All optional count parameters should be passed as keyword arguments
947+
to this method. Valid options include:
948+
949+
- `hint` (string or list of tuples): The index to use. Specify either
950+
the index name as a string or the index specification as a list of
951+
tuples (e.g. [('a', pymongo.ASCENDING), ('b', pymongo.ASCENDING)]).
952+
- `limit` (int): The maximum number of documents to count.
953+
- `skip` (int): The number of matching documents to skip before
954+
returning results.
955+
- `maxTimeMS` (int): The maximum amount of time to allow the count
956+
command to run, in milliseconds.
948957
949958
The :meth:`count` method obeys the :attr:`read_preference` of
950959
this :class:`Collection`.
960+
961+
:Parameters:
962+
- `filter` (optional): A query document that selects which documents
963+
to count in the collection.
964+
- `**kwargs` (optional): See list of options above.
951965
"""
952-
return self.find().count()
966+
cmd = SON([("count", self.__name)])
967+
if filter is not None:
968+
if "query" in kwargs:
969+
raise ConfigurationError("can't pass both filter and query")
970+
kwargs["query"] = filter
971+
if "hint" in kwargs and not isinstance(kwargs["hint"], string_type):
972+
kwargs["hint"] = helpers._index_document(kwargs["hint"])
973+
cmd.update(kwargs)
974+
res = self._command(cmd,
975+
allowable_errors=["ns missing"])[0]
976+
if res.get("errmsg", "") == "ns missing":
977+
return 0
978+
return int(res["n"])
953979

954980
def create_index(self, key_or_list, cache_for=300, **kwargs):
955981
"""Creates an index on this collection.
@@ -1503,23 +1529,40 @@ def rename(self, new_name, **kwargs):
15031529
_command(self.__database.connection, "admin.$cmd", cmd,
15041530
ReadPreference.PRIMARY, CodecOptions())
15051531

1506-
def distinct(self, key):
1532+
def distinct(self, key, filter=None, **kwargs):
15071533
"""Get a list of distinct values for `key` among all documents
15081534
in this collection.
15091535
15101536
Raises :class:`TypeError` if `key` is not an instance of
15111537
:class:`basestring` (:class:`str` in python 3).
15121538
1513-
To get the distinct values for a key in the result set of a
1514-
query use :meth:`~pymongo.cursor.Cursor.distinct`.
1539+
All optional distinct parameters should be passed as keyword arguments
1540+
to this method. Valid options include:
1541+
1542+
- `maxTimeMS` (int): The maximum amount of time to allow the count
1543+
command to run, in milliseconds.
15151544
15161545
The :meth:`distinct` method obeys the :attr:`read_preference` of
15171546
this :class:`Collection`.
15181547
15191548
:Parameters:
1520-
- `key`: name of key for which we want to get the distinct values
1549+
- `key`: name of the field for which we want to get the distinct
1550+
values
1551+
- `filter` (optional): A query document that specifies the documents
1552+
from which to retrieve the distinct values.
1553+
- `**kwargs` (optional): See list of options above.
15211554
"""
1522-
return self.find().distinct(key)
1555+
if not isinstance(key, string_type):
1556+
raise TypeError("key must be an "
1557+
"instance of %s" % (string_type.__name__,))
1558+
cmd = SON([("distinct", self.__name),
1559+
("key", key)])
1560+
if filter is not None:
1561+
if "query" in kwargs:
1562+
raise ConfigurationError("can't pass both filter and query")
1563+
kwargs["query"] = filter
1564+
cmd.update(kwargs)
1565+
return self._command(cmd)[0]["values"]
15231566

15241567
def map_reduce(self, map, reduce, out, full_response=False, **kwargs):
15251568
"""Perform a map/reduce operation on this collection.

pymongo/cursor.py

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -655,28 +655,22 @@ def count(self, with_limit_and_skip=False):
655655
"""
656656
if not isinstance(with_limit_and_skip, bool):
657657
raise TypeError("with_limit_and_skip must be an instance of bool")
658-
cmd = SON([("count", self.__collection.name),
659-
("query", self.__spec),
660-
("fields", self.__fields)])
658+
options = {"query": self.__spec}
661659
if self.__max_time_ms is not None:
662-
cmd["maxTimeMS"] = self.__max_time_ms
660+
options["maxTimeMS"] = self.__max_time_ms
663661
if self.__comment:
664-
cmd['$comment'] = self.__comment
662+
options["$comment"] = self.__comment
665663

666664
if self.__hint is not None:
667-
cmd['hint'] = self.__hint
665+
options["hint"] = self.__hint
668666

669667
if with_limit_and_skip:
670668
if self.__limit:
671-
cmd["limit"] = self.__limit
669+
options["limit"] = self.__limit
672670
if self.__skip:
673-
cmd["skip"] = self.__skip
671+
options["skip"] = self.__skip
674672

675-
res = self.__collection._command(cmd,
676-
allowable_errors=["ns missing"])[0]
677-
if res.get("errmsg", "") == "ns missing":
678-
return 0
679-
return int(res["n"])
673+
return self.__collection.count(**options)
680674

681675
def distinct(self, key):
682676
"""Get a list of distinct values for `key` among all documents
@@ -695,20 +689,15 @@ def distinct(self, key):
695689
696690
.. seealso:: :meth:`pymongo.collection.Collection.distinct`
697691
"""
698-
if not isinstance(key, string_type):
699-
raise TypeError("key must be an "
700-
"instance of %s" % (string_type.__name__,))
701-
702-
cmd = SON([("distinct", self.__collection.name),
703-
("key", key)])
692+
options = {}
704693
if self.__spec:
705-
cmd["query"] = self.__spec
694+
options["query"] = self.__spec
706695
if self.__max_time_ms is not None:
707-
cmd['maxTimeMS'] = self.__max_time_ms
696+
options['maxTimeMS'] = self.__max_time_ms
708697
if self.__comment:
709-
cmd['$comment'] = self.__comment
698+
options['$comment'] = self.__comment
710699

711-
return self.__collection._command(cmd)[0]["values"]
700+
return self.__collection.distinct(key, **options)
712701

713702
def explain(self):
714703
"""Returns an explain plan record for this cursor.

test/test_collection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,10 @@ def test_count(self):
12701270
db.test.save({'foo': 'bar'})
12711271
db.test.save({'foo': 'baz'})
12721272
self.assertEqual(db.test.find({'foo': 'bar'}).count(), 1)
1273+
self.assertEqual(db.test.count({'foo': 'bar'}), 1)
12731274
self.assertEqual(db.test.find({'foo': re.compile(r'ba.*')}).count(), 2)
1275+
self.assertEqual(
1276+
db.test.count({'foo': re.compile(r'ba.*')}), 2)
12741277

12751278
@client_context.require_version_min(2, 1, 0)
12761279
def test_aggregate(self):
@@ -1684,7 +1687,10 @@ def test_distinct(self):
16841687

16851688
distinct = test.find({'a': {'$gt': 1}}).distinct("a")
16861689
distinct.sort()
1690+
self.assertEqual([2, 3], distinct)
16871691

1692+
distinct = test.distinct('a', {'a': {'$gt': 1}})
1693+
distinct.sort()
16881694
self.assertEqual([2, 3], distinct)
16891695

16901696
self.db.drop_collection("test")

0 commit comments

Comments
 (0)