Skip to content

PYTHON-3568 Intellisense highlights multiple PyMongo methods because of CodecOptions #1139

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

Merged
merged 16 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 6 additions & 2 deletions .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
cache-dependency-path: 'setup.py'
- name: Install dependencies
run: |
python -m pip install -U pip mypy==0.990
python -m pip install -U pip mypy==0.990 pyright==1.1.290
pip install -e ".[zstd, encryption, ocsp]"
- name: Run mypy
run: |
Expand All @@ -67,7 +67,11 @@ jobs:
mypy --install-types --non-interactive bson/codec_options.py
mypy --install-types --non-interactive --disable-error-code var-annotated --disable-error-code attr-defined --disable-error-code union-attr --disable-error-code assignment --disable-error-code no-redef --disable-error-code index --allow-redefinition --allow-untyped-globals --exclude "test/mypy_fails/*.*" test
python -m pip install -U typing_extensions
mypy --install-types --non-interactive test/test_mypy.py
mypy --install-types --non-interactive test/test_typing.py test/test_typing_strict.py
mypy --strict test/test_typing_strict.py
pyright test/test_typing.py test/test_typing_strict.py
echo '{"strict": ["tests/test_typing_strict.py"]}"'' >> pyrightconfig.json
pyright test/test_typing_strict.py

linkcheck:
name: Check Links
Expand Down
4 changes: 2 additions & 2 deletions doc/examples/type_hints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type of document object returned when decoding BSON documents.
Due to `limitations in mypy`_, the default
values for generic document types are not yet provided (they will eventually be ``Dict[str, any]``).

For a larger set of examples that use types, see the PyMongo `test_mypy module`_.
For a larger set of examples that use types, see the PyMongo `test_typing module`_.

If you would like to opt out of using the provided types, add the following to
your `mypy config`_: ::
Expand Down Expand Up @@ -326,5 +326,5 @@ Another example is trying to set a value on a :class:`~bson.raw_bson.RawBSONDocu
.. _mypy: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html
.. _limitations in mypy: https://github.com/python/mypy/issues/3737
.. _mypy config: https://mypy.readthedocs.io/en/stable/config_file.html
.. _test_mypy module: https://github.com/mongodb/mongo-python-driver/blob/master/test/test_mypy.py
.. _test_typing module: https://github.com/mongodb/mongo-python-driver/blob/master/test/test_typing.py
.. _schema validation: https://www.mongodb.com/docs/manual/core/schema-validation/#when-to-use-schema-validation
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ignore_missing_imports = True
[mypy-snappy.*]
ignore_missing_imports = True

[mypy-test.test_mypy]
[mypy-test.test_typing]
warn_unused_ignores = True

[mypy-winkerberos.*]
Expand Down
5 changes: 3 additions & 2 deletions pymongo/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generic,
Iterable,
List,
Expand Down Expand Up @@ -116,7 +117,7 @@ def __init__(
database: "Database[_DocumentType]",
name: str,
create: Optional[bool] = False,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to use a generic TypeVar for CodecOptions instead of hardcoding Mapping[str, Any] to avoid this failure?:

Found 1 error in 1 file (checked 83 source files)
pymongo/mongo_client.py:2054: error: Argument "codec_options" to "get_database"
of "MongoClient" has incompatible type "CodecOptions[MutableMapping[str, Any]]";
expected "Optional[CodecOptions[Mapping[str, Any]]]"  [arg-type]
                codec_options=DEFAULT_CODEC_OPTIONS,
                              ^~~~~~~~~~~~~~~~~~~~~

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually we can use [_DocumentType] everywhere and satisfy pyright in strict mode.

read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -394,7 +395,7 @@ def database(self) -> "Database[_DocumentType]":

def with_options(
self,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down
12 changes: 6 additions & 6 deletions pymongo/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(
self,
client: "MongoClient[_DocumentType]",
name: str,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -152,7 +152,7 @@ def name(self) -> str:

def with_options(
self,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -239,7 +239,7 @@ def __getitem__(self, name: str) -> "Collection[_DocumentType]":
def get_collection(
self,
name: str,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -295,7 +295,7 @@ def get_collection(
def create_collection(
self,
name: str,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional["WriteConcern"] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -976,7 +976,7 @@ def _drop_helper(self, name, session=None, comment=None):
@_csot.apply
def drop_collection(
self,
name_or_collection: Union[str, Collection],
name_or_collection: Union[str, Collection[Mapping[str, Any]]],
session: Optional["ClientSession"] = None,
comment: Optional[Any] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
Expand Down Expand Up @@ -1068,7 +1068,7 @@ def drop_collection(

def validate_collection(
self,
name_or_collection: Union[str, Collection],
name_or_collection: Union[str, Collection[Mapping[str, Any]]],
scandata: bool = False,
full: bool = False,
session: Optional["ClientSession"] = None,
Expand Down
4 changes: 2 additions & 2 deletions pymongo/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import random
import struct
from io import BytesIO as _BytesIO
from typing import Any, Dict, NoReturn
from typing import Any, Mapping, NoReturn

import bson
from bson import CodecOptions, _decode_selective, _dict_to_bson, _make_c_string, encode
Expand Down Expand Up @@ -81,7 +81,7 @@
}
_FIELD_MAP = {"insert": "documents", "update": "updates", "delete": "deletes"}

_UNICODE_REPLACE_CODEC_OPTIONS: "CodecOptions[Dict[str, Any]]" = CodecOptions(
_UNICODE_REPLACE_CODEC_OPTIONS: "CodecOptions[Mapping[str, Any]]" = CodecOptions(
unicode_decode_error_handler="replace"
)

Expand Down
6 changes: 3 additions & 3 deletions pymongo/mongo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1875,7 +1875,7 @@ def list_database_names(
@_csot.apply
def drop_database(
self,
name_or_database: Union[str, database.Database],
name_or_database: Union[str, database.Database[Mapping[str, Any]]],
session: Optional[client_session.ClientSession] = None,
comment: Optional[Any] = None,
) -> None:
Expand Down Expand Up @@ -1928,7 +1928,7 @@ def drop_database(
def get_default_database(
self,
default: Optional[str] = None,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down Expand Up @@ -1989,7 +1989,7 @@ def get_default_database(
def get_database(
self,
name: Optional[str] = None,
codec_options: Optional[CodecOptions] = None,
codec_options: Optional["CodecOptions[Mapping[str, Any]]"] = None,
read_preference: Optional[_ServerMode] = None,
write_concern: Optional[WriteConcern] = None,
read_concern: Optional["ReadConcern"] = None,
Expand Down
10 changes: 5 additions & 5 deletions test/test_mypy.py → test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import sys
import tempfile
import unittest
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Union
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Mapping, Union

try:
from typing_extensions import NotRequired, TypedDict
Expand Down Expand Up @@ -422,7 +422,7 @@ def test_typeddict_not_required_document_type(self) -> None:
assert out is not None
# This should fail because the output is a Movie.
assert out["foo"] # type:ignore[typeddict-item]
assert out["_id"]
assert out["_id"] # type:ignore

@only_type_check
def test_typeddict_empty_document_type(self) -> None:
Expand All @@ -442,7 +442,7 @@ def test_typeddict_find_notrequired(self):
coll.insert_one(ImplicitMovie(name="THX-1138", year=1971))
out = coll.find_one({})
assert out is not None
assert out["_id"]
assert out["_id"] # type:ignore

@only_type_check
def test_raw_bson_document_type(self) -> None:
Expand Down Expand Up @@ -482,7 +482,7 @@ def test_default(self) -> None:
@only_type_check
def test_explicit_document_type(self) -> None:
client: MongoClient = MongoClient()
codec_options: CodecOptions[Dict[str, Any]] = CodecOptions()
codec_options: CodecOptions[Mapping[str, Any]] = CodecOptions()
result = client.admin.command("ping", codec_options=codec_options)
result["a"] = 1

Expand Down Expand Up @@ -516,7 +516,7 @@ def test_default(self) -> None:
obj["a"] = 1

def test_explicit_document_type(self) -> None:
options: CodecOptions[Dict[str, Any]] = CodecOptions()
options: CodecOptions[Mapping[str, Any]] = CodecOptions()
obj = options.document_class()
obj["a"] = 1

Expand Down
38 changes: 38 additions & 0 deletions test/test_typing_strict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2023-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Test typings in strict mode."""
import unittest
from typing import TYPE_CHECKING, Any

import pymongo
from pymongo.collection import Collection
from pymongo.database import Database


def test_generic_arguments() -> None:
"""Ensure known usages of generic arguments pass strict typing"""
if not TYPE_CHECKING:
raise unittest.SkipTest("Used for Type Checking Only")
mongo_client: pymongo.MongoClient[dict[str, Any]] = pymongo.MongoClient()
mongo_client.drop_database("foo")
mongo_client.get_default_database()
db = mongo_client.get_database("test_db")
db = Database(mongo_client, "test_db")
db.with_options()
db.validate_collection("py_test")
col = db.get_collection("py_test")
col.insert_one({"abc": 123})
col = Collection(db, "py_test")
col.with_options()