Skip to content

Commit ff55b81

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents 3c7100c + adf8817 commit ff55b81

26 files changed

+294
-150
lines changed

doc/api/pymongo/asynchronous/mongo_client.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
.. autoclass:: pymongo.asynchronous.mongo_client.AsyncMongoClient(host='localhost', port=27017, document_class=dict, tz_aware=False, connect=True, **kwargs)
88

9-
.. automethod:: aclose
9+
.. automethod:: close
1010

1111
.. describe:: c[db_name] || c.db_name
1212

gridfs/asynchronous/grid_file.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,8 +1892,16 @@ async def next(self) -> AsyncGridOut:
18921892
next_file = await super().next()
18931893
return AsyncGridOut(self._root_collection, file_document=next_file, session=self.session)
18941894

1895-
async def to_list(self) -> list[AsyncGridOut]:
1896-
return [x async for x in self] # noqa: C416,RUF100
1895+
async def to_list(self, length: Optional[int] = None) -> list[AsyncGridOut]:
1896+
"""Convert the cursor to a list."""
1897+
if length is None:
1898+
return [x async for x in self] # noqa: C416,RUF100
1899+
if length < 1:
1900+
raise ValueError("to_list() length must be greater than 0")
1901+
ret = []
1902+
for _ in range(length):
1903+
ret.append(await self.next())
1904+
return ret
18971905

18981906
__anext__ = next
18991907

gridfs/synchronous/grid_file.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1878,8 +1878,16 @@ def next(self) -> GridOut:
18781878
next_file = super().next()
18791879
return GridOut(self._root_collection, file_document=next_file, session=self.session)
18801880

1881-
def to_list(self) -> list[GridOut]:
1882-
return [x for x in self] # noqa: C416,RUF100
1881+
def to_list(self, length: Optional[int] = None) -> list[GridOut]:
1882+
"""Convert the cursor to a list."""
1883+
if length is None:
1884+
return [x for x in self] # noqa: C416,RUF100
1885+
if length < 1:
1886+
raise ValueError("to_list() length must be greater than 0")
1887+
ret = []
1888+
for _ in range(length):
1889+
ret.append(self.next())
1890+
return ret
18831891

18841892
__next__ = next
18851893

pymongo/asynchronous/collection.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,9 +1893,7 @@ def find(self, *args: Any, **kwargs: Any) -> AsyncCursor[_DocumentType]:
18931893
"""
18941894
return AsyncCursor(self, *args, **kwargs)
18951895

1896-
async def find_raw_batches(
1897-
self, *args: Any, **kwargs: Any
1898-
) -> AsyncRawBatchCursor[_DocumentType]:
1896+
def find_raw_batches(self, *args: Any, **kwargs: Any) -> AsyncRawBatchCursor[_DocumentType]:
18991897
"""Query the database and retrieve batches of raw BSON.
19001898
19011899
Similar to the :meth:`find` method but returns a
@@ -1907,7 +1905,7 @@ async def find_raw_batches(
19071905
:mod:`bson` module.
19081906
19091907
>>> import bson
1910-
>>> cursor = await db.test.find_raw_batches()
1908+
>>> cursor = db.test.find_raw_batches()
19111909
>>> async for batch in cursor:
19121910
... print(bson.decode_all(batch))
19131911

pymongo/asynchronous/command_cursor.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,17 @@ async def _try_next(self, get_more_allowed: bool) -> Optional[_DocumentType]:
346346
else:
347347
return None
348348

349-
async def _next_batch(self, result: list) -> bool:
350-
"""Get all available documents from the cursor."""
349+
async def _next_batch(self, result: list, total: Optional[int] = None) -> bool:
350+
"""Get all or some available documents from the cursor."""
351351
if not len(self._data) and not self._killed:
352352
await self._refresh()
353353
if len(self._data):
354-
result.extend(self._data)
355-
self._data.clear()
354+
if total is None:
355+
result.extend(self._data)
356+
self._data.clear()
357+
else:
358+
for _ in range(min(len(self._data), total)):
359+
result.append(self._data.popleft())
356360
return True
357361
else:
358362
return False
@@ -381,21 +385,32 @@ async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]:
381385
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
382386
await self.close()
383387

384-
async def to_list(self) -> list[_DocumentType]:
388+
async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
385389
"""Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``.
386390
387391
To use::
388392
389393
>>> await cursor.to_list()
390394
395+
Or, so read at most n items from the cursor::
396+
397+
>>> await cursor.to_list(n)
398+
391399
If the cursor is empty or has no more results, an empty list will be returned.
392400
393401
.. versionadded:: 4.9
394402
"""
395403
res: list[_DocumentType] = []
404+
remaining = length
405+
if isinstance(length, int) and length < 1:
406+
raise ValueError("to_list() length must be greater than 0")
396407
while self.alive:
397-
if not await self._next_batch(res):
408+
if not await self._next_batch(res, remaining):
398409
break
410+
if length is not None:
411+
remaining = length - len(res)
412+
if remaining == 0:
413+
break
399414
return res
400415

401416

pymongo/asynchronous/cursor.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,16 +1260,20 @@ async def next(self) -> _DocumentType:
12601260
else:
12611261
raise StopAsyncIteration
12621262

1263-
async def _next_batch(self, result: list) -> bool:
1264-
"""Get all available documents from the cursor."""
1263+
async def _next_batch(self, result: list, total: Optional[int] = None) -> bool:
1264+
"""Get all or some documents from the cursor."""
12651265
if not self._exhaust_checked:
12661266
self._exhaust_checked = True
12671267
await self._supports_exhaust()
12681268
if self._empty:
12691269
return False
12701270
if len(self._data) or await self._refresh():
1271-
result.extend(self._data)
1272-
self._data.clear()
1271+
if total is None:
1272+
result.extend(self._data)
1273+
self._data.clear()
1274+
else:
1275+
for _ in range(min(len(self._data), total)):
1276+
result.append(self._data.popleft())
12731277
return True
12741278
else:
12751279
return False
@@ -1286,21 +1290,32 @@ async def __aenter__(self) -> AsyncCursor[_DocumentType]:
12861290
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
12871291
await self.close()
12881292

1289-
async def to_list(self) -> list[_DocumentType]:
1293+
async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
12901294
"""Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``.
12911295
12921296
To use::
12931297
12941298
>>> await cursor.to_list()
12951299
1300+
Or, so read at most n items from the cursor::
1301+
1302+
>>> await cursor.to_list(n)
1303+
12961304
If the cursor is empty or has no more results, an empty list will be returned.
12971305
12981306
.. versionadded:: 4.9
12991307
"""
13001308
res: list[_DocumentType] = []
1309+
remaining = length
1310+
if isinstance(length, int) and length < 1:
1311+
raise ValueError("to_list() length must be greater than 0")
13011312
while self.alive:
1302-
if not await self._next_batch(res):
1313+
if not await self._next_batch(res, remaining):
13031314
break
1315+
if length is not None:
1316+
remaining = length - len(res)
1317+
if remaining == 0:
1318+
break
13041319
return res
13051320

13061321

pymongo/asynchronous/encryption.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ async def close(self) -> None:
299299
self.client_ref = None
300300
self.key_vault_coll = None
301301
if self.mongocryptd_client:
302-
await self.mongocryptd_client.aclose()
302+
await self.mongocryptd_client.close()
303303
self.mongocryptd_client = None
304304

305305

@@ -439,7 +439,7 @@ async def close(self) -> None:
439439
self._closed = True
440440
await self._auto_encrypter.close()
441441
if self._internal_client:
442-
await self._internal_client.aclose()
442+
await self._internal_client.close()
443443
self._internal_client = None
444444

445445

pymongo/asynchronous/mongo_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,7 @@ async def __aenter__(self) -> AsyncMongoClient[_DocumentType]:
13781378
return self
13791379

13801380
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
1381-
await self.aclose()
1381+
await self.close()
13821382

13831383
# See PYTHON-3084.
13841384
__iter__ = None
@@ -1514,7 +1514,7 @@ async def _end_sessions(self, session_ids: list[_ServerSession]) -> None:
15141514
# command.
15151515
pass
15161516

1517-
async def aclose(self) -> None:
1517+
async def close(self) -> None:
15181518
"""Cleanup client resources and disconnect from MongoDB.
15191519
15201520
End all server sessions created by this client by sending one or more
@@ -1541,6 +1541,10 @@ async def aclose(self) -> None:
15411541
# TODO: PYTHON-1921 Encrypted MongoClients cannot be re-opened.
15421542
await self._encrypter.close()
15431543

1544+
if not _IS_SYNC:
1545+
# Add support for contextlib.aclosing.
1546+
aclose = close
1547+
15441548
async def _get_topology(self) -> Topology:
15451549
"""Get the internal :class:`~pymongo.asynchronous.topology.Topology` object.
15461550

pymongo/network_layer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ async def async_sendall(sock: Union[socket.socket, _sslConn], buf: bytes) -> Non
7777
async def _async_sendall_ssl(
7878
sock: Union[socket.socket, _sslConn], buf: bytes, loop: AbstractEventLoop
7979
) -> None:
80+
view = memoryview(buf)
8081
fd = sock.fileno()
8182
sent = 0
8283

@@ -89,7 +90,7 @@ def _is_ready(fut: Future) -> None:
8990

9091
while sent < len(buf):
9192
try:
92-
sent += sock.send(buf)
93+
sent += sock.send(view[sent:])
9394
except BLOCKING_IO_ERRORS as exc:
9495
fd = sock.fileno()
9596
# Check for closed socket.

pymongo/synchronous/command_cursor.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,17 @@ def _try_next(self, get_more_allowed: bool) -> Optional[_DocumentType]:
346346
else:
347347
return None
348348

349-
def _next_batch(self, result: list) -> bool:
350-
"""Get all available documents from the cursor."""
349+
def _next_batch(self, result: list, total: Optional[int] = None) -> bool:
350+
"""Get all or some available documents from the cursor."""
351351
if not len(self._data) and not self._killed:
352352
self._refresh()
353353
if len(self._data):
354-
result.extend(self._data)
355-
self._data.clear()
354+
if total is None:
355+
result.extend(self._data)
356+
self._data.clear()
357+
else:
358+
for _ in range(min(len(self._data), total)):
359+
result.append(self._data.popleft())
356360
return True
357361
else:
358362
return False
@@ -381,21 +385,32 @@ def __enter__(self) -> CommandCursor[_DocumentType]:
381385
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
382386
self.close()
383387

384-
def to_list(self) -> list[_DocumentType]:
388+
def to_list(self, length: Optional[int] = None) -> list[_DocumentType]:
385389
"""Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``.
386390
387391
To use::
388392
389393
>>> cursor.to_list()
390394
395+
Or, so read at most n items from the cursor::
396+
397+
>>> cursor.to_list(n)
398+
391399
If the cursor is empty or has no more results, an empty list will be returned.
392400
393401
.. versionadded:: 4.9
394402
"""
395403
res: list[_DocumentType] = []
404+
remaining = length
405+
if isinstance(length, int) and length < 1:
406+
raise ValueError("to_list() length must be greater than 0")
396407
while self.alive:
397-
if not self._next_batch(res):
408+
if not self._next_batch(res, remaining):
398409
break
410+
if length is not None:
411+
remaining = length - len(res)
412+
if remaining == 0:
413+
break
399414
return res
400415

401416

0 commit comments

Comments
 (0)