Skip to content

Commit a61ea06

Browse files
authored
PYTHON-3090 Clean up Database Command Typing (#879)
1 parent b737b84 commit a61ea06

File tree

11 files changed

+131
-21
lines changed

11 files changed

+131
-21
lines changed

.github/workflows/test-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ jobs:
6464
- name: Run mypy
6565
run: |
6666
mypy --install-types --non-interactive bson gridfs tools pymongo
67-
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 --exclude "test/mypy_fails/*.*" test
67+
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

bson/codec_options.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class TypeCodec(TypeEncoder, TypeDecoder):
115115

116116
_Codec = Union[TypeEncoder, TypeDecoder, TypeCodec]
117117
_Fallback = Callable[[Any], Any]
118+
_DocumentClass = Union[Type[MutableMapping], Type["RawBSONDocument"]]
118119

119120

120121
class TypeRegistry(object):
@@ -293,7 +294,7 @@ class CodecOptions(_options_base):
293294

294295
def __new__(
295296
cls: Type["CodecOptions"],
296-
document_class: Union[Type[MutableMapping], Type["RawBSONDocument"]] = dict,
297+
document_class: _DocumentClass = dict,
297298
tz_aware: bool = False,
298299
uuid_representation: Optional[int] = UuidRepresentation.UNSPECIFIED,
299300
unicode_decode_error_handler: Optional[str] = "strict",

mypy.ini

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ ignore_missing_imports = True
3232
[mypy-snappy.*]
3333
ignore_missing_imports = True
3434

35-
[mypy-test.*]
36-
allow_redefinition = true
37-
allow_untyped_globals = true
35+
[mypy-test.test_mypy]
36+
warn_unused_ignores = false
3837

3938
[mypy-winkerberos.*]
4039
ignore_missing_imports = True

pymongo/database.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
Optional,
2525
Sequence,
2626
Union,
27+
cast,
2728
)
2829

2930
from bson.codec_options import DEFAULT_CODEC_OPTIONS, CodecOptions
@@ -37,7 +38,7 @@
3738
from pymongo.command_cursor import CommandCursor
3839
from pymongo.errors import CollectionInvalid, InvalidName
3940
from pymongo.read_preferences import ReadPreference, _ServerMode
40-
from pymongo.typings import _CollationIn, _DocumentType, _Pipeline
41+
from pymongo.typings import _CollationIn, _DocumentOut, _DocumentType, _Pipeline
4142

4243

4344
def _check_name(name):
@@ -620,7 +621,7 @@ def command(
620621
session: Optional["ClientSession"] = None,
621622
comment: Optional[Any] = None,
622623
**kwargs: Any,
623-
) -> Dict[str, Any]:
624+
) -> _DocumentOut:
624625
"""Issue a MongoDB command.
625626
626627
Send command `command` to the database and return the
@@ -974,7 +975,7 @@ def validate_collection(
974975
if background is not None:
975976
cmd["background"] = background
976977

977-
result = self.command(cmd, session=session)
978+
result = cast(dict, self.command(cmd, session=session))
978979

979980
valid = True
980981
# Pre 1.9 results

pymongo/encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def _get_internal_client(encrypter, mongo_client):
290290
db, coll = opts._key_vault_namespace.split(".", 1)
291291
key_vault_coll = key_vault_client[db][coll]
292292

293-
mongocryptd_client = MongoClient(
293+
mongocryptd_client: MongoClient = MongoClient(
294294
opts._mongocryptd_uri, connect=False, serverSelectionTimeoutMS=_MONGOCRYPTD_TIMEOUT_MS
295295
)
296296

pymongo/mongo_client.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def __init__(
120120
self,
121121
host: Optional[Union[str, Sequence[str]]] = None,
122122
port: Optional[int] = None,
123-
document_class: Type[_DocumentType] = dict,
123+
document_class: Optional[Type[_DocumentType]] = None,
124124
tz_aware: Optional[bool] = None,
125125
connect: Optional[bool] = None,
126126
type_registry: Optional[TypeRegistry] = None,
@@ -652,7 +652,7 @@ def __init__(
652652
self.__init_kwargs: Dict[str, Any] = {
653653
"host": host,
654654
"port": port,
655-
"document_class": document_class,
655+
"document_class": document_class or dict,
656656
"tz_aware": tz_aware,
657657
"connect": connect,
658658
"type_registry": type_registry,
@@ -676,7 +676,7 @@ def __init__(
676676

677677
# Parse options passed as kwargs.
678678
keyword_opts = common._CaseInsensitiveDictionary(kwargs)
679-
keyword_opts["document_class"] = document_class
679+
keyword_opts["document_class"] = document_class or dict
680680

681681
seeds = set()
682682
username = None
@@ -1717,8 +1717,11 @@ def server_info(self, session: Optional[client_session.ClientSession] = None) ->
17171717
.. versionchanged:: 3.6
17181718
Added ``session`` parameter.
17191719
"""
1720-
return self.admin.command(
1721-
"buildinfo", read_preference=ReadPreference.PRIMARY, session=session
1720+
return cast(
1721+
dict,
1722+
self.admin.command(
1723+
"buildinfo", read_preference=ReadPreference.PRIMARY, session=session
1724+
),
17221725
)
17231726

17241727
def list_databases(

pymongo/typings.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from typing import (
1717
TYPE_CHECKING,
1818
Any,
19-
Dict,
2019
Mapping,
2120
MutableMapping,
2221
Optional,
@@ -36,6 +35,5 @@
3635
_CollationIn = Union[Mapping[str, Any], "Collation"]
3736
_DocumentIn = Union[MutableMapping[str, Any], "RawBSONDocument"]
3837
_Pipeline = Sequence[Mapping[str, Any]]
39-
_DocumentType = TypeVar(
40-
"_DocumentType", Mapping[str, Any], MutableMapping[str, Any], Dict[str, Any]
41-
)
38+
_DocumentOut = _DocumentIn
39+
_DocumentType = TypeVar("_DocumentType", bound=Mapping[str, Any])

test/mypy_fails/raw_bson_document.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from bson.raw_bson import RawBSONDocument
2+
from pymongo import MongoClient
3+
4+
client = MongoClient(document_class=RawBSONDocument)
5+
coll = client.test.test
6+
doc = {"my": "doc"}
7+
coll.insert_one(doc)
8+
retreived = coll.find_one({"_id": doc["_id"]})
9+
assert retreived is not None
10+
assert len(retreived.raw) > 0
11+
retreived[
12+
"foo"
13+
] = "bar" # error: Unsupported target for indexed assignment ("RawBSONDocument") [index]

test/mypy_fails/typedict_client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import TypedDict
2+
3+
from pymongo import MongoClient
4+
5+
6+
class Movie(TypedDict):
7+
name: str
8+
year: int
9+
10+
11+
client: MongoClient[Movie] = MongoClient()
12+
coll = client.test.test
13+
retreived = coll.find_one({"_id": "foo"})
14+
assert retreived is not None
15+
assert retreived["year"] == 1
16+
assert (
17+
retreived["name"] == 2
18+
) # error: Non-overlapping equality check (left operand type: "str", right operand type: "Literal[2]") [comparison-overlap]

test/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ def test_repr(self):
709709
# Used to test 'eval' below.
710710
import bson # noqa: F401
711711

712-
client = MongoClient(
712+
client = MongoClient( # type: ignore[type-var]
713713
"mongodb://localhost:27017,localhost:27018/?replicaSet=replset"
714714
"&connectTimeoutMS=12345&w=1&wtimeoutms=100",
715715
connect=False,

0 commit comments

Comments
 (0)