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
PYTHON-3493 Bulk Write InsertOne Should Be Parameter Of Collection Type #1106
Changes from 6 commits
2a9f80b
7292fbe
39b32b2
c9b67ac
032271e
cf8f8b0
3d0ae16
1a9e5d7
46cc1c7
dd44e38
c16532e
9c26531
c3b1226
fbb7862
be764e5
a8afc6a
e1938c4
c205357
a0ece18
62f4918
6d12caa
0d4f9bd
b68ed1b
b0b7a81
6328f59
65d18bf
99e20a7
f153b45
371b474
71f9370
be4c899
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,29 @@ These methods automatically add an "_id" field. | |
>>> # This will not be type checked, despite being present, because it is added by PyMongo. | ||
>>> assert type(result["_id"]) == ObjectId | ||
|
||
This same typing scheme works for all of the insert methods (`insert_one`, `insert_many`, and `bulk_write`). For `bulk_write`, | ||
both `InsertOne/Many` and `ReplaceOne/Many` operators are generic. | ||
|
||
.. doctest:: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs the python version guard There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Speaking of, please schedule the docs and doctest evergreen tasks when changing things like this. |
||
>>> from typing import TypedDict | ||
>>> from pymongo import MongoClient | ||
>>> from pymongo.operations import InsertOne | ||
>>> from pymongo.collection import Collection | ||
>>> class Movie(TypedDict): | ||
... name: str | ||
... year: int | ||
... | ||
>>> client: MongoClient = MongoClient() | ||
>>> collection: Collection[Movie] = client.test.test | ||
>>> inserted = collection.bulk_write([InsertOne(Movie(name="Jurassic Park", year=1993))]) | ||
>>> result = collection.find_one({"name": "Jurassic Park"}) | ||
>>> assert result is not None | ||
>>> assert result["year"] == 1993 | ||
>>> # This will not be type checked, despite being present, because it is added by PyMongo. | ||
>>> assert type(result["_id"]) == ObjectId | ||
|
||
|
||
Typed Database | ||
-------------- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,21 +13,22 @@ | |
# limitations under the License. | ||
|
||
"""Operation class definitions.""" | ||
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union | ||
from typing import Any, Dict, Generic, List, Mapping, Optional, Sequence, Tuple, Union | ||
|
||
from bson.raw_bson import RawBSONDocument | ||
from pymongo import helpers | ||
from pymongo.collation import validate_collation_or_none | ||
from pymongo.common import validate_boolean, validate_is_mapping, validate_list | ||
from pymongo.helpers import _gen_index_name, _index_document, _index_list | ||
from pymongo.typings import _CollationIn, _DocumentIn, _Pipeline | ||
from pymongo.typings import _CollationIn, _DocumentType, _Pipeline | ||
|
||
|
||
class InsertOne(object): | ||
class InsertOne(Generic[_DocumentType]): | ||
"""Represents an insert_one operation.""" | ||
|
||
__slots__ = ("_doc",) | ||
|
||
def __init__(self, document: _DocumentIn) -> None: | ||
def __init__(self, document: Union[_DocumentType, RawBSONDocument]) -> None: | ||
"""Create an InsertOne instance. | ||
|
||
For use with :meth:`~pymongo.collection.Collection.bulk_write`. | ||
|
@@ -170,15 +171,15 @@ def __ne__(self, other: Any) -> bool: | |
return not self == other | ||
|
||
|
||
class ReplaceOne(object): | ||
class ReplaceOne(Generic[_DocumentType]): | ||
"""Represents a replace_one operation.""" | ||
|
||
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_hint") | ||
|
||
def __init__( | ||
self, | ||
filter: Mapping[str, Any], | ||
replacement: Mapping[str, Any], | ||
replacement: Union[_DocumentType, RawBSONDocument], | ||
upsert: bool = False, | ||
collation: Optional[_CollationIn] = None, | ||
hint: Optional[_IndexKeyHint] = None, | ||
|
@@ -316,7 +317,7 @@ class UpdateOne(_UpdateOp): | |
def __init__( | ||
self, | ||
filter: Mapping[str, Any], | ||
update: Union[Mapping[str, Any], _Pipeline], | ||
update: Union[_DocumentType, RawBSONDocument, _Pipeline], | ||
upsert: bool = False, | ||
collation: Optional[_CollationIn] = None, | ||
array_filters: Optional[List[Mapping[str, Any]]] = None, | ||
|
@@ -374,7 +375,7 @@ class UpdateMany(_UpdateOp): | |
def __init__( | ||
self, | ||
filter: Mapping[str, Any], | ||
update: Union[Mapping[str, Any], _Pipeline], | ||
update: Union[_DocumentType, RawBSONDocument, _Pipeline], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woah, the update doc for UpdateOne and UdateMany shouldn't accept the _DocumentType. The update is a mapping like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. Those were added in without tests, hence why that wasn't noticed. I will remove those changes. |
||
upsert: bool = False, | ||
collation: Optional[_CollationIn] = None, | ||
array_filters: Optional[List[Mapping[str, Any]]] = None, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -296,7 +296,7 @@ def test_upsert(self): | |
def test_numerous_inserts(self): | ||
# Ensure we don't exceed server's maxWriteBatchSize size limit. | ||
n_docs = client_context.max_write_batch_size + 100 | ||
requests = [InsertOne({}) for _ in range(n_docs)] | ||
requests = [InsertOne[dict]({}) for _ in range(n_docs)] | ||
result = self.coll.bulk_write(requests, ordered=False) | ||
self.assertEqual(n_docs, result.inserted_count) | ||
self.assertEqual(n_docs, self.coll.count_documents({})) | ||
|
@@ -347,7 +347,7 @@ def test_bulk_write_no_results(self): | |
|
||
def test_bulk_write_invalid_arguments(self): | ||
# The requests argument must be a list. | ||
generator = (InsertOne({}) for _ in range(10)) | ||
generator = (InsertOne[dict]({}) for _ in range(10)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are these changes required? This seems annoyingly verbose for users. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is required because otherwise mypy complains:
I'm not sure why it doesn't resolve to the bound of the TypeVar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the same reason we need a type annotation on MongoClient There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see, it's just empty/untyped dictionary. A non-empty dict works without a manual type annotation:
It's a shame the error message is so opaque. |
||
with self.assertRaises(TypeError): | ||
self.coll.bulk_write(generator) # type: ignore[arg-type] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -201,7 +201,7 @@ def test_list_collection_names_filter(self): | |
db.capped.insert_one({}) | ||
db.non_capped.insert_one({}) | ||
self.addCleanup(client.drop_database, db.name) | ||
|
||
filter: dict | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a type hint for mypy so that this error does not occur:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see. I thought it was a copy/past error. Down below there's already There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, that works. |
||
# Should not send nameOnly. | ||
for filter in ({"options.capped": True}, {"options.capped": True, "name": "capped"}): | ||
results.clear() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no such thing as InsertMany or ReplaceMany.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can you make these rst links to the methods and classes (
:meth:...
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@juliusgeo ReplaceMany should be removed, there is no ReplaceMany operator