Skip to content

Commit 8322387

Browse files
committed
fix tests so they test all the methods
1 parent 3a063b8 commit 8322387

File tree

5 files changed

+55
-35
lines changed

5 files changed

+55
-35
lines changed

.github/workflows/test-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
cache-dependency-path: 'setup.py'
6060
- name: Install dependencies
6161
run: |
62-
python -m pip install -U pip mypy
62+
python -m pip install -U pip mypy typing_extensions
6363
pip install -e ".[zstd, encryption, ocsp]"
6464
- name: Run mypy
6565
run: |

doc/examples/type_hints.rst

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,14 @@ Note that when using :class:`~bson.son.SON`, the key and value types must be giv
9292
Typed Collection
9393
----------------
9494

95-
You can use :py:class:`~typing_extensions.TypedDict` (Python 3.8+) when using a well-defined schema for the data in a :class:`~pymongo.collection.Collection`.
96-
Note that all `schema_validation`_ for inserts and updates is done on the server. This is due to the fact that these methods automatically add
97-
an "_id" field. In the example below the "_id" field is marked by the :py:class:`~typing_extensions.NotRequired` notation to allow it to be accessed when reading
98-
from `result`. If it is simply not included in the definition, then it will be automatically added, but it will raise a type-checking error if you attempt to access it.
99-
Another option would be to generate the "_id" field yourself, and make it a required field. This would give the expected behavior, but would then also prevent you from
100-
relying on PyMongo to insert the "_id" field.
95+
You can use :py:class:`~typing_extensions.TypedDict` (Python 3.8+) when using a well-defined schema for the data in a
96+
:class:`~pymongo.collection.Collection`. Note that all `schema validation`_ for inserts and updates is done on the server.
97+
These methods automatically add an "_id" field. In the example below the "_id" field is
98+
marked by the :py:class:`~typing_extensions.NotRequired` notation to allow it to be accessed when reading from
99+
``result``. If it is simply not included in the definition, then it will be automatically added, but it will raise a
100+
type-checking error if you attempt to access it. Another option would be to generate the "_id" field yourself, and make
101+
it a required field. This would give the expected behavior, but would then also prevent you from relying on PyMongo to
102+
insert the "_id" field.
101103

102104
.. doctest::
103105

pymongo/operations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
from pymongo.collation import validate_collation_or_none
2020
from pymongo.common import validate_boolean, validate_is_mapping, validate_list
2121
from pymongo.helpers import _gen_index_name, _index_document, _index_list
22-
from pymongo.typings import _CollationIn, _DocumentIn, _Pipeline
22+
from pymongo.typings import _CollationIn, _DocumentIn, _DocumentType, _Pipeline
2323

2424

2525
class InsertOne(object):
2626
"""Represents an insert_one operation."""
2727

2828
__slots__ = ("_doc",)
2929

30-
def __init__(self, document: _DocumentIn) -> None:
30+
def __init__(self, document: Union[_DocumentIn, _DocumentType]) -> None:
3131
"""Create an InsertOne instance.
3232
3333
For use with :meth:`~pymongo.collection.Collection.bulk_write`.

test/test_mypy.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ImplicitMovie(TypedDict): # type: ignore[misc]
3636
name: str
3737
year: int
3838

39-
class EmptyMovie(TypedDict): # type: ignore[misc]
39+
class MovieWithoutId(TypedDict): # type: ignore[misc]
4040
name: str
4141
year: int
4242

@@ -324,43 +324,61 @@ def test_typeddict_document_type(self) -> None:
324324
assert retreived["year"] == 1
325325
assert retreived["name"] == "a"
326326

327-
# TODO: mypy --install-types --non-interactive test/test_mypy.py
328-
# run just this file in CI
329327
@only_type_check
330328
def test_typeddict_document_type_insertion(self) -> None:
331329
client: MongoClient[Movie] = MongoClient()
332330
coll: Collection[Movie] = client.test.test
333-
insert = coll.insert_one(Movie(_id=ObjectId(), name="THX-1138", year=1971))
334-
out = coll.find_one({"name": "THX-1138"})
335-
assert out is not None
336-
# This should fail because the output is a Movie.
337-
assert out["foo"] # type:ignore[typeddict-item]
338-
assert type(out["_id"]) == ObjectId
331+
mov = Movie(_id=ObjectId(), name="THX-1138", year=1971)
332+
for meth, arg in [
333+
(coll.insert_many, [mov]),
334+
(coll.insert_one, mov),
335+
(coll.bulk_write, [InsertOne(mov)]),
336+
]:
337+
meth(arg) # type:ignore[operator]
338+
out = coll.find_one({"name": "THX-1138"})
339+
assert out is not None
340+
# This should fail because the output is a Movie.
341+
assert out["foo"] # type:ignore[typeddict-item]
342+
assert type(out["_id"]) == ObjectId
343+
coll.drop()
339344

340345
# This should work the same as the test above, but this time using NotRequired to allow
341346
# automatic insertion of the _id field by insert_one.
342347
@only_type_check
343348
def test_typeddict_document_type_not_required(self) -> None:
344349
client: MongoClient[ImplicitMovie] = MongoClient()
345350
coll: Collection[ImplicitMovie] = client.test.test
346-
insert = coll.insert_one(ImplicitMovie(name="THX-1138", year=1971))
347-
out = coll.find_one({"name": "THX-1138"})
348-
assert out is not None
349-
# This should fail because the output is a Movie.
350-
assert out["foo"] # type:ignore[typeddict-item]
351-
assert type(out["_id"]) == ObjectId
351+
mov = ImplicitMovie(name="THX-1138", year=1971)
352+
for meth, arg in [
353+
(coll.insert_many, [mov]),
354+
(coll.insert_one, mov),
355+
(coll.bulk_write, [InsertOne(mov)]),
356+
]:
357+
meth(arg) # type:ignore[operator]
358+
out = coll.find_one({"name": "THX-1138"})
359+
assert out is not None
360+
# This should fail because the output is a Movie.
361+
assert out["foo"] # type:ignore[typeddict-item]
362+
assert type(out["_id"]) == ObjectId
363+
coll.drop()
352364

353365
@only_type_check
354366
def test_typeddict_document_type_empty(self) -> None:
355-
client: MongoClient[EmptyMovie] = MongoClient()
356-
coll: Collection[EmptyMovie] = client.test.test
357-
insert = coll.insert_one(EmptyMovie(name="THX-1138", year=1971))
358-
out = coll.find_one({"name": "THX-1138"})
359-
assert out is not None
360-
# This should fail because the output is a Movie.
361-
assert out["foo"] # type:ignore[typeddict-item]
362-
# This should fail because _id is not included in our TypedDict definition.
363-
assert type(out["_id"]) == ObjectId # type:ignore[typeddict-item]
367+
client: MongoClient[MovieWithoutId] = MongoClient()
368+
coll: Collection[MovieWithoutId] = client.test.test
369+
mov = MovieWithoutId(name="THX-1138", year=1971)
370+
for meth, arg in [
371+
(coll.insert_many, [mov]),
372+
(coll.insert_one, mov),
373+
(coll.bulk_write, [InsertOne(mov)]),
374+
]:
375+
meth(arg) # type:ignore[operator]
376+
out = coll.find_one({"name": "THX-1138"})
377+
assert out is not None
378+
# This should fail because the output is a Movie.
379+
assert out["foo"] # type:ignore[typeddict-item]
380+
# This should fail because _id is not included in our TypedDict definition.
381+
assert type(out["_id"]) == ObjectId # type:ignore[typeddict-item]
364382

365383
@only_type_check
366384
def test_raw_bson_document_type(self) -> None:

test/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ def ensure_all_connected(client: MongoClient) -> None:
602602
Depending on the use-case, the caller may need to clear any event listeners
603603
that are configured on the client.
604604
"""
605-
hello: Any = client.admin.command(HelloCompat.LEGACY_CMD)
605+
hello: dict = client.admin.command(HelloCompat.LEGACY_CMD)
606606
if "setName" not in hello:
607607
raise ConfigurationError("cluster is not a replica set")
608608

@@ -613,7 +613,7 @@ def ensure_all_connected(client: MongoClient) -> None:
613613
def discover():
614614
i = 0
615615
while i < 100 and connected_host_list != target_host_list:
616-
hello: Any = client.admin.command(
616+
hello: dict = client.admin.command(
617617
HelloCompat.LEGACY_CMD, read_preference=ReadPreference.SECONDARY
618618
)
619619
connected_host_list.update([hello["me"]])

0 commit comments

Comments
 (0)