Skip to content

Commit 75fa14d

Browse files
authored
PYTHON-3084 MongoClient/Database/Collection should not implement Iterable (#909)
1 parent 72d8900 commit 75fa14d

File tree

6 files changed

+69
-15
lines changed

6 files changed

+69
-15
lines changed

pymongo/collection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,8 +3146,8 @@ def find_one_and_update(
31463146
**kwargs,
31473147
)
31483148

3149-
def __iter__(self) -> "Collection[_DocumentType]":
3150-
return self
3149+
# See PYTHON-3084.
3150+
__iter__ = None
31513151

31523152
def __next__(self) -> NoReturn:
31533153
raise TypeError("'Collection' object is not iterable")

pymongo/database.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,8 +1009,8 @@ def validate_collection(
10091009

10101010
return result
10111011

1012-
def __iter__(self) -> "Database[_DocumentType]":
1013-
return self
1012+
# See PYTHON-3084.
1013+
__iter__ = None
10141014

10151015
def __next__(self) -> NoReturn:
10161016
raise TypeError("'Database' object is not iterable")

pymongo/mongo_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,8 +1973,8 @@ def __enter__(self) -> "MongoClient[_DocumentType]":
19731973
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
19741974
self.close()
19751975

1976-
def __iter__(self) -> "MongoClient[_DocumentType]":
1977-
return self
1976+
# See PYTHON-3084.
1977+
__iter__ = None
19781978

19791979
def __next__(self) -> NoReturn:
19801980
raise TypeError("'MongoClient' object is not iterable")

test/test_client.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import sys
2727
import threading
2828
import time
29-
from typing import Type, no_type_check
29+
from typing import Iterable, Type, no_type_check
3030

3131
sys.path[0:0] = [""]
3232

@@ -210,10 +210,26 @@ def test_getattr(self):
210210
self.assertIn("has no attribute '_does_not_exist'", str(context.exception))
211211

212212
def test_iteration(self):
213-
def iterate():
214-
[a for a in self.client]
215-
216-
self.assertRaises(TypeError, iterate)
213+
client = self.client
214+
if "PyPy" in sys.version:
215+
msg = "'NoneType' object is not callable"
216+
else:
217+
msg = "'MongoClient' object is not iterable"
218+
# Iteration fails
219+
with self.assertRaisesRegex(TypeError, msg):
220+
for _ in client: # type: ignore[misc] # error: "None" not callable [misc]
221+
break
222+
# Index fails
223+
with self.assertRaises(TypeError):
224+
_ = client[0]
225+
# next fails
226+
with self.assertRaisesRegex(TypeError, "'MongoClient' object is not iterable"):
227+
_ = next(client)
228+
# .next() fails
229+
with self.assertRaisesRegex(TypeError, "'MongoClient' object is not iterable"):
230+
_ = client.next()
231+
# Do not implement typing.Iterable.
232+
self.assertNotIsInstance(client, Iterable)
217233

218234
def test_get_default_database(self):
219235
c = rs_or_single_client(

test/test_collection.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import sys
2222
from codecs import utf_8_decode # type: ignore
2323
from collections import defaultdict
24-
from typing import no_type_check
24+
from typing import Iterable, no_type_check
2525

2626
from pymongo.database import Database
2727

@@ -124,7 +124,26 @@ def test_getattr(self):
124124
self.assertEqual(coll2.write_concern, coll4.write_concern)
125125

126126
def test_iteration(self):
127-
self.assertRaises(TypeError, next, self.db)
127+
coll = self.db.coll
128+
if "PyPy" in sys.version:
129+
msg = "'NoneType' object is not callable"
130+
else:
131+
msg = "'Collection' object is not iterable"
132+
# Iteration fails
133+
with self.assertRaisesRegex(TypeError, msg):
134+
for _ in coll: # type: ignore[misc] # error: "None" not callable [misc]
135+
break
136+
# Non-string indices will start failing in PyMongo 5.
137+
self.assertEqual(coll[0].name, "coll.0")
138+
self.assertEqual(coll[{}].name, "coll.{}")
139+
# next fails
140+
with self.assertRaisesRegex(TypeError, "'Collection' object is not iterable"):
141+
_ = next(coll)
142+
# .next() fails
143+
with self.assertRaisesRegex(TypeError, "'Collection' object is not iterable"):
144+
_ = coll.next()
145+
# Do not implement typing.Iterable.
146+
self.assertNotIsInstance(coll, Iterable)
128147

129148

130149
class TestCollection(IntegrationTest):

test/test_database.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import re
1818
import sys
19-
from typing import Any, List, Mapping
19+
from typing import Any, Iterable, List, Mapping
2020

2121
sys.path[0:0] = [""]
2222

@@ -94,7 +94,26 @@ def test_getattr(self):
9494
self.assertIn("has no attribute '_does_not_exist'", str(context.exception))
9595

9696
def test_iteration(self):
97-
self.assertRaises(TypeError, next, self.client.pymongo_test)
97+
db = self.client.pymongo_test
98+
if "PyPy" in sys.version:
99+
msg = "'NoneType' object is not callable"
100+
else:
101+
msg = "'Database' object is not iterable"
102+
# Iteration fails
103+
with self.assertRaisesRegex(TypeError, msg):
104+
for _ in db: # type: ignore[misc] # error: "None" not callable [misc]
105+
break
106+
# Index fails
107+
with self.assertRaises(TypeError):
108+
_ = db[0]
109+
# next fails
110+
with self.assertRaisesRegex(TypeError, "'Database' object is not iterable"):
111+
_ = next(db)
112+
# .next() fails
113+
with self.assertRaisesRegex(TypeError, "'Database' object is not iterable"):
114+
_ = db.next()
115+
# Do not implement typing.Iterable.
116+
self.assertNotIsInstance(db, Iterable)
98117

99118

100119
class TestDatabase(IntegrationTest):

0 commit comments

Comments
 (0)