Skip to content

Commit e977fb9

Browse files
authored
Merge pull request #300 from IlyaSkriblovsky/master
Builder pattern methods on Cursor
2 parents 7e614fb + faa91f6 commit e977fb9

File tree

8 files changed

+579
-332
lines changed

8 files changed

+579
-332
lines changed

docs/source/NEWS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ API Changes
2222
- `find()` method now returns `Cursor()` instance that can be used as async generator to
2323
asynchronously iterate over results. It can still be used as Deferred too, so this change
2424
is backward-compatible.
25+
- `Cursor()` options can be by chaining its methods, for example:
26+
::
27+
async for doc in collection.find({"size": "L"}).sort({"price": 1}).limit(10).skip(5):
28+
print(doc)
2529
- `find_with_cursor()` is deprecated and will be removed in the next release.
2630

2731

tests/basic/test_collection.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def cmp(a, b):
3838
return (a > b) - (a < b)
3939

4040

41-
class TestIndexInfo(unittest.TestCase):
41+
class TestCollectionMethods(unittest.TestCase):
4242

4343
timeout = 5
4444

@@ -54,7 +54,7 @@ def tearDown(self):
5454
yield self.conn.disconnect()
5555

5656
@defer.inlineCallbacks
57-
def test_collection(self):
57+
def test_type_checking(self):
5858
self.assertRaises(TypeError, Collection, self.db, 5)
5959

6060
def make_col(base, name):
@@ -76,6 +76,7 @@ def make_col(base, name):
7676
self.assertRaises(TypeError, self.db.test.find, projection="test")
7777
self.assertRaises(TypeError, self.db.test.find, skip="test")
7878
self.assertRaises(TypeError, self.db.test.find, limit="test")
79+
self.assertRaises(TypeError, self.db.test.find, batch_size="test")
7980
self.assertRaises(TypeError, self.db.test.find, sort="test")
8081
self.assertRaises(TypeError, self.db.test.find, skip="test")
8182
self.assertRaises(TypeError, self.db.test.insert_many, [1])
@@ -105,9 +106,32 @@ def make_col(base, name):
105106
options = yield self.db.test.options()
106107
self.assertTrue(isinstance(options, dict))
107108

109+
@defer.inlineCallbacks
110+
def test_collection_names(self):
111+
coll_names = [f"coll_{i}" for i in range(10)]
112+
yield defer.gatherResults(
113+
self.db[name].insert_one({"x": 1}) for name in coll_names
114+
)
115+
116+
try:
117+
names = yield self.db.collection_names()
118+
self.assertEqual(set(coll_names), set(names))
119+
names = yield self.db.collection_names(batch_size=10)
120+
self.assertEqual(set(coll_names), set(names))
121+
finally:
122+
yield defer.gatherResults(self.db[name].drop() for name in coll_names)
123+
124+
test_collection_names.timeout = 1500
125+
126+
@defer.inlineCallbacks
127+
def test_drop_collection(self):
128+
yield self.db.test.insert_one({"x": 1})
129+
collection_names = yield self.db.collection_names()
130+
self.assertIn("test", collection_names)
131+
108132
yield self.db.drop_collection("test")
109133
collection_names = yield self.db.collection_names()
110-
self.assertFalse("test" in collection_names)
134+
self.assertNotIn("test", collection_names)
111135

112136
@defer.inlineCallbacks
113137
def test_create_index(self):

tests/basic/test_filters.py

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15+
from contextlib import asynccontextmanager, contextmanager
1516

1617
from pymongo.errors import OperationFailure
1718
from twisted.internet import defer
@@ -39,19 +40,43 @@ def tearDown(self):
3940
yield self.db.system.profile.drop()
4041
yield self.conn.disconnect()
4142

42-
@defer.inlineCallbacks
43-
def test_Hint(self):
43+
@asynccontextmanager
44+
async def _assert_single_command_with_option(self, optionname, optionvalue):
45+
# Checking that `optionname` appears in profiler log with specified value
46+
47+
await self.db.command("profile", 2)
48+
yield
49+
await self.db.command("profile", 0)
50+
51+
profile_filter = {"command." + optionname: optionvalue}
52+
cnt = await self.db.system.profile.count(profile_filter)
53+
await self.db.system.profile.drop()
54+
self.assertEqual(cnt, 1)
55+
56+
async def test_Hint(self):
4457
# find() should fail with 'bad hint' if hint specifier works correctly
4558
self.assertFailure(
4659
self.coll.find({}, sort=qf.hint([("x", 1)])), OperationFailure
4760
)
61+
self.assertFailure(self.coll.find().hint({"x": 1}), OperationFailure)
4862

4963
# create index and test it is honoured
50-
yield self.coll.create_index(qf.sort(qf.ASCENDING("x")), name="test_index")
51-
found_1 = yield self.coll.find({}, sort=qf.hint([("x", 1)]))
52-
found_2 = yield self.coll.find({}, sort=qf.hint(qf.ASCENDING("x")))
53-
found_3 = yield self.coll.find({}, sort=qf.hint("test_index"))
54-
self.assertTrue(found_1 == found_2 == found_3)
64+
await self.coll.create_index(qf.sort(qf.ASCENDING("x")), name="test_index")
65+
forms = [
66+
[("x", 1)],
67+
{"x": 1},
68+
qf.ASCENDING("x"),
69+
]
70+
for form in forms:
71+
async with self._assert_single_command_with_option("hint", {"x": 1}):
72+
await self.coll.find({}, sort=qf.hint(form))
73+
async with self._assert_single_command_with_option("hint", {"x": 1}):
74+
await self.coll.find().hint(form)
75+
76+
async with self._assert_single_command_with_option("hint", "test_index"):
77+
await self.coll.find({}, sort=qf.hint("test_index"))
78+
async with self._assert_single_command_with_option("hint", "test_index"):
79+
await self.coll.find().hint("test_index")
5580

5681
# find() should fail with 'bad hint' if hint specifier works correctly
5782
self.assertFailure(
@@ -67,13 +92,18 @@ def test_SortAscendingMultipleFields(self):
6792
qf.sort(qf.ASCENDING(["x", "y"])),
6893
qf.sort(qf.ASCENDING("x") + qf.ASCENDING("y")),
6994
)
95+
self.assertEqual(
96+
qf.sort(qf.ASCENDING(["x", "y"])),
97+
qf.sort({"x": 1, "y": 1}),
98+
)
7099

71100
def test_SortOneLevelList(self):
72101
self.assertEqual(qf.sort([("x", 1)]), qf.sort(("x", 1)))
73102

74103
def test_SortInvalidKey(self):
75104
self.assertRaises(TypeError, qf.sort, [(1, 2)])
76105
self.assertRaises(TypeError, qf.sort, [("x", 3)])
106+
self.assertRaises(TypeError, qf.sort, {"x": 3})
77107

78108
def test_SortGeoIndexes(self):
79109
self.assertEqual(qf.sort(qf.GEO2D("x")), qf.sort([("x", "2d")]))
@@ -83,45 +113,33 @@ def test_SortGeoIndexes(self):
83113
def test_TextIndex(self):
84114
self.assertEqual(qf.sort(qf.TEXT("title")), qf.sort([("title", "text")]))
85115

86-
def __3_2_or_higher(self):
87-
return self.db.command("buildInfo").addCallback(
88-
lambda info: info["versionArray"] >= [3, 2]
89-
)
90-
91-
def __3_6_or_higher(self):
92-
return self.db.command("buildInfo").addCallback(
93-
lambda info: info["versionArray"] >= [3, 6]
94-
)
95-
96-
@defer.inlineCallbacks
97-
def __test_simple_filter(self, filter, optionname, optionvalue):
98-
# Checking that `optionname` appears in profiler log with specified value
99-
100-
yield self.db.command("profile", 2)
101-
yield self.coll.find({}, sort=filter)
102-
yield self.db.command("profile", 0)
103-
104-
if (yield self.__3_6_or_higher()):
105-
profile_filter = {"command." + optionname: optionvalue}
106-
elif (yield self.__3_2_or_higher()):
107-
# query options format in system.profile have changed in MongoDB 3.2
108-
profile_filter = {"query." + optionname: optionvalue}
109-
else:
110-
profile_filter = {"query.$" + optionname: optionvalue}
111-
112-
cnt = yield self.db.system.profile.count(profile_filter)
113-
self.assertEqual(cnt, 1)
114-
115-
@defer.inlineCallbacks
116-
def test_Comment(self):
116+
async def test_SortProfile(self):
117+
forms = [
118+
qf.DESCENDING("x"),
119+
{"x": -1},
120+
[("x", -1)],
121+
("x", -1),
122+
]
123+
for form in forms:
124+
async with self._assert_single_command_with_option("sort.x", -1):
125+
await self.coll.find({}, sort=qf.sort(form))
126+
async with self._assert_single_command_with_option("sort.x", -1):
127+
await self.coll.find().sort(form)
128+
129+
async def test_Comment(self):
117130
comment = "hello world"
118131

119-
yield self.__test_simple_filter(qf.comment(comment), "comment", comment)
132+
async with self._assert_single_command_with_option("comment", comment):
133+
await self.coll.find({}, sort=qf.comment(comment))
134+
async with self._assert_single_command_with_option("comment", comment):
135+
await self.coll.find().comment(comment)
120136

121137
@defer.inlineCallbacks
122138
def test_Explain(self):
123139
result = yield self.coll.find({}, sort=qf.explain())
124140
self.assertTrue("executionStats" in result[0] or "nscanned" in result[0])
141+
result = yield self.coll.find().explain()
142+
self.assertTrue("executionStats" in result[0] or "nscanned" in result[0])
125143

126144
@defer.inlineCallbacks
127145
def test_FilterMerge(self):
@@ -136,12 +154,7 @@ def test_FilterMerge(self):
136154
yield self.coll.find({}, sort=qf.sort(qf.ASCENDING("x")) + qf.comment(comment))
137155
yield self.db.command("profile", 0)
138156

139-
if (yield self.__3_6_or_higher()):
140-
profile_filter = {"command.sort.x": 1, "command.comment": comment}
141-
elif (yield self.__3_2_or_higher()):
142-
profile_filter = {"query.sort.x": 1, "query.comment": comment}
143-
else:
144-
profile_filter = {"query.$orderby.x": 1, "query.$comment": comment}
157+
profile_filter = {"command.sort.x": 1, "command.comment": comment}
145158
cnt = yield self.db.system.profile.count(profile_filter)
146159
self.assertEqual(cnt, 1)
147160

0 commit comments

Comments
 (0)