PYTHON-5653: fix - correct return type annotation for find_one_and_* methods to include None (#2615)

Co-authored-by: Jib <jib.adegunloye@mongodb.com>
Co-authored-by: Casey Clements <caseyclements@users.noreply.github.com>
This commit is contained in:
Cal Jacobson 2025-11-25 14:36:33 -06:00 committed by GitHub
parent 3d76c84b2a
commit 222a55f8cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 8 deletions

View File

@ -17,6 +17,8 @@ PyMongo 4.16 brings a number of changes including:
- Removed support for Eventlet.
Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency.
- Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions.
- Fixed return type annotation for ``find_one_and_*`` methods on :class:`~pymongo.asynchronous.collection.AsyncCollection`
and :class:`~pymongo.synchronous.collection.Collection` to include ``None``.
Changes in Version 4.15.5 (2025/XX/XX)
--------------------------------------

View File

@ -107,3 +107,4 @@ The following is a list of people who have contributed to
- Jeffrey A. Clark (aclark4life)
- Steven Silvester (blink1073)
- Noah Stapp (NoahStapp)
- Cal Jacobson (cj81499)

View File

@ -3310,7 +3310,7 @@ class AsyncCollection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and deletes it, returning the document.
>>> await db.test.count_documents({'x': 1})
@ -3320,6 +3320,10 @@ class AsyncCollection(common.BaseObject, Generic[_DocumentType]):
>>> await db.test.count_documents({'x': 1})
1
Returns ``None`` if no document matches the filter.
>>> await db.test.find_one_and_delete({'_exists': False})
If multiple documents match *filter*, a *sort* can be applied.
>>> async for doc in db.test.find({'x': 1}):
@ -3402,10 +3406,22 @@ class AsyncCollection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and replaces it, returning either the
original or the replaced document.
>>> await db.test.find_one({'x': 1})
{'_id': 0, 'x': 1}
>>> await db.test.find_one_and_replace({'x': 1}, {'y': 2})
{'_id': 0, 'x': 1}
>>> await db.test.find_one({'x': 1})
>>> await db.test.find_one({'y': 2})
{'_id': 0, 'y': 2}
Returns ``None`` if no document matches the filter.
>>> await db.test.find_one_and_replace({'_exists': False}, {'x': 1})
The :meth:`find_one_and_replace` method differs from
:meth:`find_one_and_update` by replacing the document matched by
*filter*, rather than modifying the existing document.
@ -3510,13 +3526,17 @@ class AsyncCollection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and updates it, returning either the
original or the updated document.
>>> await db.test.find_one({'_id': 665})
{'_id': 665, 'done': False, 'count': 25}
>>> await db.test.find_one_and_update(
... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}})
{'_id': 665, 'done': False, 'count': 25}}
{'_id': 665, 'done': False, 'count': 25}
>>> await db.test.find_one({'_id': 665})
{'_id': 665, 'done': True, 'count': 26}
Returns ``None`` if no document matches the filter.

View File

@ -3303,7 +3303,7 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and deletes it, returning the document.
>>> db.test.count_documents({'x': 1})
@ -3313,6 +3313,10 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
>>> db.test.count_documents({'x': 1})
1
Returns ``None`` if no document matches the filter.
>>> db.test.find_one_and_delete({'_exists': False})
If multiple documents match *filter*, a *sort* can be applied.
>>> for doc in db.test.find({'x': 1}):
@ -3395,10 +3399,22 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and replaces it, returning either the
original or the replaced document.
>>> db.test.find_one({'x': 1})
{'_id': 0, 'x': 1}
>>> db.test.find_one_and_replace({'x': 1}, {'y': 2})
{'_id': 0, 'x': 1}
>>> db.test.find_one({'x': 1})
>>> db.test.find_one({'y': 2})
{'_id': 0, 'y': 2}
Returns ``None`` if no document matches the filter.
>>> db.test.find_one_and_replace({'_exists': False}, {'x': 1})
The :meth:`find_one_and_replace` method differs from
:meth:`find_one_and_update` by replacing the document matched by
*filter*, rather than modifying the existing document.
@ -3503,13 +3519,17 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
let: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
) -> _DocumentType:
) -> Optional[_DocumentType]:
"""Finds a single document and updates it, returning either the
original or the updated document.
>>> db.test.find_one({'_id': 665})
{'_id': 665, 'done': False, 'count': 25}
>>> db.test.find_one_and_update(
... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}})
{'_id': 665, 'done': False, 'count': 25}}
{'_id': 665, 'done': False, 'count': 25}
>>> db.test.find_one({'_id': 665})
{'_id': 665, 'done': True, 'count': 26}
Returns ``None`` if no document matches the filter.