Skip to content

Conversation

@juliusgeo
Copy link
Contributor

No description provided.

@juliusgeo
Copy link
Contributor Author

(python3.8) ➜  mongo-python-driver git:(PYTHON-3454) mypy --install-types --non-interactive test/test_mypy.py
Success: no issues found in 1 source file

Verified to work in Python 3.8+.

@juliusgeo juliusgeo marked this pull request as ready for review October 20, 2022 18:11

You can use :py:class:`~typing.TypedDict` (Python 3.8+) when using a well-defined schema for the data in a :class:`~pymongo.collection.Collection`:
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`.
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
Copy link
Member

Choose a reason for hiding this comment

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

I think you need to remove the underscore to match the link below "schema validation"

Copy link
Member

Choose a reason for hiding this comment

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

Also wrap lines at 80 or 100 chars.

)

@_csot.apply
def insert_many(
Copy link
Member

Choose a reason for hiding this comment

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

We should add a mypy test for insert_many too.

Copy link
Member

Choose a reason for hiding this comment

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

And bulkWrite InsertOne if possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

test/utils.py Outdated
that are configured on the client.
"""
hello = client.admin.command(HelloCompat.LEGACY_CMD)
hello: Any = client.admin.command(HelloCompat.LEGACY_CMD)
Copy link
Member

Choose a reason for hiding this comment

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

Can we use dict instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


You can use :py:class:`~typing.TypedDict` (Python 3.8+) when using a well-defined schema for the data in a :class:`~pymongo.collection.Collection`:
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`.
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
Copy link
Member

Choose a reason for hiding this comment

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

Also wrap lines at 80 or 100 chars.


try:
from typing import TypedDict # type: ignore[attr-defined]
from typing_extensions import NotRequired, TypedDict
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this mean these tests will be skipped even when TypedDict exists because typing_extensions is not installed in CI?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added it to CI.

)

@_csot.apply
def insert_many(
Copy link
Member

Choose a reason for hiding this comment

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

And bulkWrite InsertOne if possible?

assert retreived["year"] == 1
assert retreived["name"] == "a"

# TODO: mypy --install-types --non-interactive test/test_mypy.py
Copy link
Member

Choose a reason for hiding this comment

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

Remove the todo?

assert type(out["_id"]) == ObjectId

@only_type_check
def test_typeddict_document_type_empty(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Instead of empty what about MovieWithoutId?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

You can use :py:class:`~typing.TypedDict` (Python 3.8+) when using a well-defined schema for the data in a :class:`~pymongo.collection.Collection`:
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`.
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
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
Copy link
Member

Choose a reason for hiding this comment

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

This is due to the fact that these methods automatically add an "_id" field.

Schema validation has nothing to do with drivers adding the _id field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

(coll.insert_one, mov),
(coll.bulk_write, [InsertOne(mov)]),
]:
meth(arg) # type:ignore[operator]
Copy link
Member

Choose a reason for hiding this comment

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

How can we put a type ignore here? insert_many/insert_one/bulk_write working without raising a mypy error is the main purpose of this bug fix. I think we're loosing sight of what's important here because the PR is muddled by tests for find_one/_id/NotRequired. Can you open a new ticket for the NotRequired stuff? To fix the bug in PYTHON-3454 I believe we only need to mention schema validation should be configured on the server and add a test like this to confirm the bug is fixed:

    def test_typeddict_document_type_insertion(self) -> None:
        coll: Collection[Movie] = self.db.test
        mov = Movie(name="THX-1138", year=1971)
        coll.insert_one(mov)
        coll.insert_many([mov])
        coll.bulk_write([InsertOne(mov)])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm finding it hard to separate out the NotRequired stuff in a way that makes sense (in the documentation). I have modified the tests to be similar to what you have, but with the NotRequired stuff as well.

Copy link
Member

Choose a reason for hiding this comment

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

Let's remove everything about _id, NotRequired, typing_extensions, and all the other tests besides the one above. All that is extra stuff that's only tangentially related to the issue. We already have tests that find_one correctly yields the collection generic type: https://github.com/mongodb/mongo-python-driver/blob/4.3.2/test/test_mypy.py#L299-L305

Copy link
Contributor Author

@juliusgeo juliusgeo Oct 25, 2022

Choose a reason for hiding this comment

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

I'm confused how it is tangentially related to the issue when the actual issue at hand is not just whether it accepts a TypedDict, but whether it is also being type-checked, no? The title specifically mentions inserting data, but also mentions enforcing type safety, which is why I included that in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also that test does check that the generic type is yielded, it does not also test that the correct MyPy errors occur during the various different ways you can specify _id.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, that is strange. Wouldn't that be an argument in favor of clarifying the _id field in this PR? The fact that the user will have to explicitly specify the _id if they want it to be type-checked. I'm not sure how this would not be very confusing without a full explanation of what is going on.

Copy link
Member

Choose a reason for hiding this comment

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

In the original example the TypeDict did not have an explicit _id field.

Copy link
Contributor Author

@juliusgeo juliusgeo Oct 27, 2022

Choose a reason for hiding this comment

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

Yes, but I view that as a deficiency in the original example. The _id field will always be inserted, and there are three ways that the user can use that with type-checking:

  1. Movie - would require changing our examples to pass in an explicit _id field.
  2. MovieWithoutId - the _id field is just ignored and you cannot type check the _id field (what our previous example was doing).
  3. ImplicitMovie - would not require passing in an _id, but would allow type-checking.

I don't think that the user just wanted the error to go away, but also wanted it to be type-checked. This would leave us with Movie and ImplicitMovie, one of which would require the user to always pass in an _id, and the other would have the expected behavior but requires NotRequired. Both of these options require an explanation to the user, though, because it depends on how they are using the TypedDict.

Copy link
Member

Choose a reason for hiding this comment

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

Not including the _id is still a valid choice, if it is not relevant to your application.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a valid choice, but it has caveats to it and I'm afraid users will be confused if they expect to be able to type check the _id field just as any other field

Copy link
Member

@blink1073 blink1073 left a comment

Choose a reason for hiding this comment

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

LGTM!

coll.insert_one(bad_movie)
coll.insert_many([bad_mov]) # type:ignore[list-item]
coll.insert_many(
[{"name": "THX-1138", "year": "WRONG TYPE"}]
Copy link
Member

Choose a reason for hiding this comment

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

The auto-formatter got us here, you need to move the ignore comment

@juliusgeo juliusgeo requested a review from blink1073 October 28, 2022 19:50
@blink1073
Copy link
Member

There's also some unused imports causing the lint to fail.

__slots__ = ("_doc",)

def __init__(self, document: _DocumentIn) -> None:
def __init__(self, document: Union[_DocumentIn, _DocumentType]) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Can we defer this change to PYTHON-3493?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

.. doctest::

>>> from typing import TypedDict
>>> from typing_extensions import TypedDict
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove typing_extensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

- name: Install dependencies
run: |
python -m pip install -U pip mypy
python -m pip install -U pip mypy typing_extensions
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove typing_extensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


# Not available in Python 3.7
from typing_extensions import TypedDict

Copy link
Member

Choose a reason for hiding this comment

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

Let's revert these changes and go back to using typing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

Choose a reason for hiding this comment

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

We will need to keep using typing_extensions in this file, or else split off a different file to run mypy on Python 3.7. The reason it was working before is that we were inadvertently ignoring errors when running this file.

Copy link
Member

Choose a reason for hiding this comment

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

The reason it was working before is that we were inadvertently ignoring errors when running this file.

Can you explain? How was this fixed?

Copy link
Member

Choose a reason for hiding this comment

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

We added a new invocation in GitHub Actions in this PR to run this file without disabling several error checks.

Copy link
Member

Choose a reason for hiding this comment

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

I see thanks.

@juliusgeo juliusgeo requested a review from ShaneHarvey October 28, 2022 21:32
import unittest
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List

try:
Copy link
Member

@ShaneHarvey ShaneHarvey Oct 31, 2022

Choose a reason for hiding this comment

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

We need to keep this in a try/except so that python test/test_mypy.py (or python3.7 setup.py test -s test.test_mypy) works even when typing_extensions is not installed. It's fine for the typing checking job to depend on typing_extensions, but not the test suite itself.

@juliusgeo juliusgeo requested a review from ShaneHarvey October 31, 2022 17:49
@juliusgeo juliusgeo merged commit 9083821 into mongodb:master Nov 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants