|
20 | 20 | TYPE_CHECKING, |
21 | 21 | Any, |
22 | 22 | AsyncIterator, |
23 | | - Generic, |
24 | 23 | Mapping, |
25 | 24 | NoReturn, |
26 | 25 | Optional, |
|
29 | 28 | ) |
30 | 29 |
|
31 | 30 | from bson import CodecOptions, _convert_raw_document_lists_to_streams |
32 | | -from pymongo import _csot |
33 | | -from pymongo.asynchronous.cursor import _ConnectionManager |
| 31 | +from pymongo.asynchronous.cursor_base import _AsyncCursorBase, _ConnectionManager |
34 | 32 | from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS |
35 | 33 | from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure |
36 | | -from pymongo.message import ( |
37 | | - _CursorAddress, |
38 | | - _GetMore, |
39 | | - _OpMsg, |
40 | | - _OpReply, |
41 | | - _RawBatchGetMore, |
42 | | -) |
| 34 | +from pymongo.message import _GetMore, _OpMsg, _OpReply, _RawBatchGetMore |
43 | 35 | from pymongo.response import PinnedResponse |
44 | 36 | from pymongo.typings import _Address, _DocumentOut, _DocumentType |
45 | 37 |
|
|
51 | 43 | _IS_SYNC = False |
52 | 44 |
|
53 | 45 |
|
54 | | -class AsyncCommandCursor(Generic[_DocumentType]): |
| 46 | +class AsyncCommandCursor(_AsyncCursorBase[_DocumentType]): |
55 | 47 | """An asynchronous cursor / iterator over command cursors.""" |
56 | 48 |
|
57 | 49 | _getmore_class = _GetMore |
@@ -98,8 +90,8 @@ def __init__( |
98 | 90 | f"max_await_time_ms must be an integer or None, not {type(max_await_time_ms)}" |
99 | 91 | ) |
100 | 92 |
|
101 | | - def __del__(self) -> None: |
102 | | - self._die_no_lock() |
| 93 | + def _get_namespace(self) -> str: |
| 94 | + return self._ns |
103 | 95 |
|
104 | 96 | def batch_size(self, batch_size: int) -> AsyncCommandCursor[_DocumentType]: |
105 | 97 | """Limits the number of documents returned in one batch. Each batch |
@@ -161,94 +153,12 @@ def _unpack_response( |
161 | 153 | ) -> Sequence[_DocumentOut]: |
162 | 154 | return response.unpack_response(cursor_id, codec_options, user_fields, legacy_response) |
163 | 155 |
|
164 | | - @property |
165 | | - def alive(self) -> bool: |
166 | | - """Does this cursor have the potential to return more data? |
167 | | -
|
168 | | - Even if :attr:`alive` is ``True``, :meth:`next` can raise |
169 | | - :exc:`StopIteration`. Best to use a for loop:: |
170 | | -
|
171 | | - async for doc in collection.aggregate(pipeline): |
172 | | - print(doc) |
173 | | -
|
174 | | - .. note:: :attr:`alive` can be True while iterating a cursor from |
175 | | - a failed server. In this case :attr:`alive` will return False after |
176 | | - :meth:`next` fails to retrieve the next batch of results from the |
177 | | - server. |
178 | | - """ |
179 | | - return bool(len(self._data) or (not self._killed)) |
180 | | - |
181 | | - @property |
182 | | - def cursor_id(self) -> int: |
183 | | - """Returns the id of the cursor.""" |
184 | | - return self._id |
185 | | - |
186 | | - @property |
187 | | - def address(self) -> Optional[_Address]: |
188 | | - """The (host, port) of the server used, or None. |
189 | | -
|
190 | | - .. versionadded:: 3.0 |
191 | | - """ |
192 | | - return self._address |
193 | | - |
194 | | - @property |
195 | | - def session(self) -> Optional[AsyncClientSession]: |
196 | | - """The cursor's :class:`~pymongo.asynchronous.client_session.AsyncClientSession`, or None. |
197 | | -
|
198 | | - .. versionadded:: 3.6 |
199 | | - """ |
200 | | - if self._session and not self._session._implicit: |
201 | | - return self._session |
202 | | - return None |
203 | | - |
204 | | - def _prepare_to_die(self) -> tuple[int, Optional[_CursorAddress]]: |
205 | | - already_killed = self._killed |
206 | | - self._killed = True |
207 | | - if self._id and not already_killed: |
208 | | - cursor_id = self._id |
209 | | - assert self._address is not None |
210 | | - address = _CursorAddress(self._address, self._ns) |
211 | | - else: |
212 | | - # Skip killCursors. |
213 | | - cursor_id = 0 |
214 | | - address = None |
215 | | - return cursor_id, address |
216 | | - |
217 | | - def _die_no_lock(self) -> None: |
218 | | - """Closes this cursor without acquiring a lock.""" |
219 | | - cursor_id, address = self._prepare_to_die() |
220 | | - self._collection.database.client._cleanup_cursor_no_lock( |
221 | | - cursor_id, address, self._sock_mgr, self._session |
222 | | - ) |
223 | | - if self._session and self._session._implicit: |
224 | | - self._session._attached_to_cursor = False |
225 | | - self._session = None |
226 | | - self._sock_mgr = None |
227 | | - |
228 | | - async def _die_lock(self) -> None: |
229 | | - """Closes this cursor.""" |
230 | | - cursor_id, address = self._prepare_to_die() |
231 | | - await self._collection.database.client._cleanup_cursor_lock( |
232 | | - cursor_id, |
233 | | - address, |
234 | | - self._sock_mgr, |
235 | | - self._session, |
236 | | - ) |
237 | | - if self._session and self._session._implicit: |
238 | | - self._session._attached_to_cursor = False |
239 | | - self._session = None |
240 | | - self._sock_mgr = None |
241 | | - |
242 | 156 | def _end_session(self) -> None: |
243 | 157 | if self._session and self._session._implicit: |
244 | 158 | self._session._attached_to_cursor = False |
245 | 159 | self._session._end_implicit_session() |
246 | 160 | self._session = None |
247 | 161 |
|
248 | | - async def close(self) -> None: |
249 | | - """Explicitly close / kill this cursor.""" |
250 | | - await self._die_lock() |
251 | | - |
252 | 162 | async def _send_message(self, operation: _GetMore) -> None: |
253 | 163 | """Send a getmore message and handle the response.""" |
254 | 164 | client = self._collection.database.client |
@@ -330,6 +240,9 @@ async def _refresh(self) -> int: |
330 | 240 | def __aiter__(self) -> AsyncIterator[_DocumentType]: |
331 | 241 | return self |
332 | 242 |
|
| 243 | + async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]: |
| 244 | + return self |
| 245 | + |
333 | 246 | async def next(self) -> _DocumentType: |
334 | 247 | """Advance the cursor.""" |
335 | 248 | # Block until a document is returnable. |
@@ -385,41 +298,6 @@ async def try_next(self) -> Optional[_DocumentType]: |
385 | 298 | """ |
386 | 299 | return await self._try_next(get_more_allowed=True) |
387 | 300 |
|
388 | | - async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]: |
389 | | - return self |
390 | | - |
391 | | - async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: |
392 | | - await self.close() |
393 | | - |
394 | | - @_csot.apply |
395 | | - async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: |
396 | | - """Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``. |
397 | | -
|
398 | | - To use:: |
399 | | -
|
400 | | - >>> await cursor.to_list() |
401 | | -
|
402 | | - Or, so read at most n items from the cursor:: |
403 | | -
|
404 | | - >>> await cursor.to_list(n) |
405 | | -
|
406 | | - If the cursor is empty or has no more results, an empty list will be returned. |
407 | | -
|
408 | | - .. versionadded:: 4.9 |
409 | | - """ |
410 | | - res: list[_DocumentType] = [] |
411 | | - remaining = length |
412 | | - if isinstance(length, int) and length < 1: |
413 | | - raise ValueError("to_list() length must be greater than 0") |
414 | | - while self.alive: |
415 | | - if not await self._next_batch(res, remaining): |
416 | | - break |
417 | | - if length is not None: |
418 | | - remaining = length - len(res) |
419 | | - if remaining == 0: |
420 | | - break |
421 | | - return res |
422 | | - |
423 | 301 |
|
424 | 302 | class AsyncRawBatchCommandCursor(AsyncCommandCursor[_DocumentType]): |
425 | 303 | _getmore_class = _RawBatchGetMore |
|
0 commit comments