Skip to content

PYTHON-3606 - Document best practice for closing MongoClients and cursors #2465

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions pymongo/asynchronous/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,15 @@ def find(self, *args: Any, **kwargs: Any) -> AsyncCursor[_DocumentType]:
improper type. Returns an instance of
:class:`~pymongo.asynchronous.cursor.AsyncCursor` corresponding to this query.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include this note in any other places? Like Cursor or CommandCursor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We explicitly say users shouldn't create those classes directly. Is adding this still useful for our own internal documentation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering because this is only in find() but there are other methods that create cursors, the most important being aggregate().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll add it to all public methods that return a Cursor or CommandCursor.

If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with collection.find() as cursor:
async for doc in cursor:
print(doc)

The :meth:`find` method obeys the :attr:`read_preference` of
this :class:`AsyncCollection`.

Expand Down Expand Up @@ -2503,6 +2512,15 @@ async def list_indexes(
...
SON([('v', 2), ('key', SON([('_id', 1)])), ('name', '_id_')])

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with await collection.list_indexes() as cursor:
async for index in cursor:
print(index)

:param session: a
:class:`~pymongo.asynchronous.client_session.AsyncClientSession`.
:param comment: A user-provided comment to attach to this
Expand Down Expand Up @@ -2620,6 +2638,15 @@ async def list_search_indexes(
) -> AsyncCommandCursor[Mapping[str, Any]]:
"""Return a cursor over search indexes for the current collection.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with await collection.list_search_indexes() as cursor:
async for index in cursor:
print(index)

:param name: If given, the name of the index to search
for. Only indexes with matching index names will be returned.
If not given, all search indexes for the current collection
Expand Down Expand Up @@ -2922,6 +2949,15 @@ async def aggregate(
.. note:: The :attr:`~pymongo.asynchronous.collection.AsyncCollection.write_concern` of
this collection is automatically applied to this operation.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with await collection.aggregate() as cursor:
async for operation in cursor:
print(operation)

:param pipeline: a list of aggregation pipeline stages
:param session: a
:class:`~pymongo.asynchronous.client_session.AsyncClientSession`.
Expand Down
18 changes: 16 additions & 2 deletions pymongo/asynchronous/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,15 +643,20 @@ async def aggregate(
.. code-block:: python

# Lists all operations currently running on the server.
with client.admin.aggregate([{"$currentOp": {}}]) as cursor:
for operation in cursor:
async with await client.admin.aggregate([{"$currentOp": {}}]) as cursor:
async for operation in cursor:
print(operation)

The :meth:`aggregate` method obeys the :attr:`read_preference` of this
:class:`AsyncDatabase`, except when ``$out`` or ``$merge`` are used, in
which case :attr:`~pymongo.read_preferences.ReadPreference.PRIMARY`
is used.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement.

.. note:: This method does not support the 'explain' option. Please
use :meth:`~pymongo.asynchronous.database.AsyncDatabase.command` instead.

Expand Down Expand Up @@ -1154,6 +1159,15 @@ async def list_collections(
) -> AsyncCommandCursor[MutableMapping[str, Any]]:
"""Get a cursor over the collections of this database.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with await database.list_collections() as cursor:
async for collection in cursor:
print(collection)

:param session: a
:class:`~pymongo.asynchronous.client_session.AsyncClientSession`.
:param filter: A query document to filter the list of
Expand Down
15 changes: 15 additions & 0 deletions pymongo/asynchronous/mongo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ def __init__(
exception (recognizing that the operation failed) and then continue to
execute.

Best practice is to call :meth:`AsyncMongoClient.close` when the client is no longer needed,
or use the client in a with statement::

async with AsyncMongoClient(url) as client:
# Use client here.

The `host` parameter can be a full `mongodb URI
<https://dochub.mongodb.org/core/connections>`_, in addition to
a simple hostname. It can also be a list of hostnames but no more
Expand Down Expand Up @@ -2345,6 +2351,15 @@ async def list_databases(
) -> AsyncCommandCursor[dict[str, Any]]:
"""Get a cursor over the databases of the connected server.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`AsyncCursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

async with await client.list_databases() as cursor:
async for database in cursor:
print(database)

:param session: a
:class:`~pymongo.asynchronous.client_session.AsyncClientSession`.
:param comment: A user-provided comment to attach to this
Expand Down
36 changes: 36 additions & 0 deletions pymongo/synchronous/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,15 @@ def find(self, *args: Any, **kwargs: Any) -> Cursor[_DocumentType]:
improper type. Returns an instance of
:class:`~pymongo.cursor.Cursor` corresponding to this query.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with collection.find() as cursor:
for doc in cursor:
print(doc)

The :meth:`find` method obeys the :attr:`read_preference` of
this :class:`Collection`.

Expand Down Expand Up @@ -2500,6 +2509,15 @@ def list_indexes(
...
SON([('v', 2), ('key', SON([('_id', 1)])), ('name', '_id_')])

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with collection.list_indexes() as cursor:
for index in cursor:
print(index)

:param session: a
:class:`~pymongo.client_session.ClientSession`.
:param comment: A user-provided comment to attach to this
Expand Down Expand Up @@ -2617,6 +2635,15 @@ def list_search_indexes(
) -> CommandCursor[Mapping[str, Any]]:
"""Return a cursor over search indexes for the current collection.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with collection.list_search_indexes() as cursor:
for index in cursor:
print(index)

:param name: If given, the name of the index to search
for. Only indexes with matching index names will be returned.
If not given, all search indexes for the current collection
Expand Down Expand Up @@ -2915,6 +2942,15 @@ def aggregate(
.. note:: The :attr:`~pymongo.collection.Collection.write_concern` of
this collection is automatically applied to this operation.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with collection.aggregate() as cursor:
for operation in cursor:
print(operation)

:param pipeline: a list of aggregation pipeline stages
:param session: a
:class:`~pymongo.client_session.ClientSession`.
Expand Down
14 changes: 14 additions & 0 deletions pymongo/synchronous/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,11 @@ def aggregate(
which case :attr:`~pymongo.read_preferences.ReadPreference.PRIMARY`
is used.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement.

.. note:: This method does not support the 'explain' option. Please
use :meth:`~pymongo.database.Database.command` instead.

Expand Down Expand Up @@ -1148,6 +1153,15 @@ def list_collections(
) -> CommandCursor[MutableMapping[str, Any]]:
"""Get a cursor over the collections of this database.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with database.list_collections() as cursor:
for collection in cursor:
print(collection)

:param session: a
:class:`~pymongo.client_session.ClientSession`.
:param filter: A query document to filter the list of
Expand Down
15 changes: 15 additions & 0 deletions pymongo/synchronous/mongo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ def __init__(
exception (recognizing that the operation failed) and then continue to
execute.

Best practice is to call :meth:`MongoClient.close` when the client is no longer needed,
or use the client in a with statement::

with MongoClient(url) as client:
# Use client here.

The `host` parameter can be a full `mongodb URI
<https://dochub.mongodb.org/core/connections>`_, in addition to
a simple hostname. It can also be a list of hostnames but no more
Expand Down Expand Up @@ -2335,6 +2341,15 @@ def list_databases(
) -> CommandCursor[dict[str, Any]]:
"""Get a cursor over the databases of the connected server.

Cursors are closed automatically when they are exhausted (the last batch of data is retrieved from the database).
If a cursor is not exhausted, it will be closed automatically upon garbage collection, which leaves resources open but unused for a potentially long period of time.
To avoid this, best practice is to call :meth:`Cursor.close` when the cursor is no longer needed,
or use the cursor in a with statement::

with client.list_databases() as cursor:
for database in cursor:
print(database)

:param session: a
:class:`~pymongo.client_session.ClientSession`.
:param comment: A user-provided comment to attach to this
Expand Down
Loading