Skip to content

Latest commit

 

History

History
275 lines (208 loc) · 9.93 KB

type_hints.rst

File metadata and controls

275 lines (208 loc) · 9.93 KB

Type Hints

As of version 4.1, PyMongo ships with type hints. With type hints, Python type checkers can easily find bugs before they reveal themselves in your code.

If your IDE is configured to use type hints, it can suggest more appropriate completions and highlight errors in your code. Some examples include PyCharm, Sublime Text, and Visual Studio Code.

You can also use the mypy tool from your command line or in Continuous Integration tests.

All of the public APIs in PyMongo are fully type hinted, and several of them support generic parameters for the 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.

If you would like to opt out of using the provided types, add the following to your mypy config: :

[mypy-pymongo]
follow_imports = False

Basic Usage

Note that a type for ~pymongo.mongo_client.MongoClient must be specified. Here we use the default, unspecified document type:

>>> from pymongo import MongoClient >>> client: MongoClient = MongoClient() >>> collection = client.test.test >>> inserted = collection.insert_one({"x": 1, "tags": ["dog", "cat"]}) >>> retrieved = collection.find_one({"x": 1}) >>> assert isinstance(retrieved, dict)

For a more accurate typing for document type you can use:

>>> from typing import Any, Dict >>> from pymongo import MongoClient >>> client: MongoClient[Dict[str, Any]] = MongoClient() >>> collection = client.test.test >>> inserted = collection.insert_one({"x": 1, "tags": ["dog", "cat"]}) >>> retrieved = collection.find_one({"x": 1}) >>> assert isinstance(retrieved, dict)

Typed Client

~pymongo.mongo_client.MongoClient is generic on the document type used to decode BSON documents.

You can specify a ~bson.raw_bson.RawBSONDocument document type:

>>> from pymongo import MongoClient >>> from bson.raw_bson import RawBSONDocument >>> client = MongoClient(document_class=RawBSONDocument) >>> collection = client.test.test >>> inserted = collection.insert_one({"x": 1, "tags": ["dog", "cat"]}) >>> result = collection.find_one({"x": 1}) >>> assert isinstance(result, RawBSONDocument)

Subclasses of :pycollections.abc.Mapping can also be used, such as ~bson.son.SON:

>>> from bson import SON >>> from pymongo import MongoClient >>> client = MongoClient(document_class=SON[str, int]) >>> collection = client.test.test >>> inserted = collection.insert_one({"x": 1, "y": 2 }) >>> result = collection.find_one({"x": 1}) >>> assert result is not None >>> assert result["x"] == 1

Note that when using ~bson.son.SON, the key and value types must be given, e.g. SON[str, Any].

Typed Collection

You can use :py~typing.TypedDict (Python 3.8+) when using a well-defined schema for the data in a ~pymongo.collection.Collection. Note that all schema validation for inserts and updates is done on the server. These methods automatically add an "_id" field.

>>> from typing import TypedDict >>> from pymongo import MongoClient >>> from pymongo.collection import Collection >>> class Movie(TypedDict): ... name: str ... year: int ... >>> client: MongoClient = MongoClient() >>> collection: Collection[Movie] = client.test.test >>> inserted = collection.insert_one(Movie(name="Jurassic Park", year=1993)) >>> result = collection.find_one({"name": "Jurassic Park"}) >>> assert result is not None >>> assert result["year"] == 1993 >>> # This will raise a type-checking error, despite being present, because it is added by PyMongo. >>> assert result["_id"]

This same typing scheme works for all of the insert methods (~pymongo.collection.Collection.insert_one, ~pymongo.collection.Collection.insert_many, and ~pymongo.collection.Collection.bulk_write). For bulk_write, :py~pymongo.operations.InsertOne, :py~pymongo.operations.ReplaceOne, and :py~pymongo.operations.ReplaceMany operators are generic.

>>> 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 raise a type-checking error, despite being present, because it is added by PyMongo. >>> assert result["_id"]

Typed Database

While less common, you could specify that the documents in an entire database match a well-defined shema using :py~typing.TypedDict (Python 3.8+).

>>> from typing import TypedDict >>> from pymongo import MongoClient >>> from pymongo.database import Database >>> class Movie(TypedDict): ... name: str ... year: int ... >>> client: MongoClient = MongoClient() >>> db: Database[Movie] = client.test >>> collection = db.test >>> inserted = collection.insert_one({"name": "Jurassic Park", "year": 1993 }) >>> result = collection.find_one({"name": "Jurassic Park"}) >>> assert result is not None >>> assert result["year"] == 1993

Typed Command

When using the ~pymongo.database.Database.command, you can specify the document type by providing a custom ~bson.codec_options.CodecOptions:

>>> from pymongo import MongoClient >>> from bson.raw_bson import RawBSONDocument >>> from bson import CodecOptions >>> client: MongoClient = MongoClient() >>> options = CodecOptions(RawBSONDocument) >>> result = client.admin.command("ping", codec_options=options) >>> assert isinstance(result, RawBSONDocument)

Custom :pycollections.abc.Mapping subclasses and :py~typing.TypedDict (Python 3.8+) are also supported. For :py~typing.TypedDict, use the form: options: CodecOptions[MyTypedDict] = CodecOptions(...).

Typed BSON Decoding

You can specify the document type returned by bson decoding functions by providing ~bson.codec_options.CodecOptions:

>>> from typing import Any, Dict >>> from bson import CodecOptions, encode, decode >>> class MyDict(Dict[str, Any]): ... def foo(self): ... return "bar" ... >>> options = CodecOptions(document_class=MyDict) >>> doc = {"x": 1, "y": 2 } >>> bsonbytes = encode(doc, codec_options=options) >>> rt_document = decode(bsonbytes, codec_options=options) >>> assert rt_document.foo() == "bar"

~bson.raw_bson.RawBSONDocument and :py~typing.TypedDict (Python 3.8+) are also supported. For :py~typing.TypedDict, use the form: options: CodecOptions[MyTypedDict] = CodecOptions(...).

Troubleshooting

Client Type Annotation

If you forget to add a type annotation for a ~pymongo.mongo_client.MongoClient object you may get the followig mypy error:

from pymongo import MongoClient
client = MongoClient()  # error: Need type annotation for "client"

The solution is to annotate the type as client: MongoClient or client: MongoClient[Dict[str, Any]]. See Basic Usage.

Incompatible Types

If you use the generic form of ~pymongo.mongo_client.MongoClient you may encounter a mypy error like:

from pymongo import MongoClient

client: MongoClient = MongoClient()
client.test.test.insert_many(
    {"a": 1}
)  # error: Dict entry 0 has incompatible type "str": "int";
   # expected "Mapping[str, Any]": "int"

The solution is to use client: MongoClient[Dict[str, Any]] as used in Basic Usage .

Actual Type Errors

Other times mypy will catch an actual error, like the following code:

from pymongo import MongoClient
from typing import Mapping
client: MongoClient = MongoClient()
client.test.test.insert_one(
    [{}]
)  # error: Argument 1 to "insert_one" of "Collection" has
   # incompatible type "List[Dict[<nothing>, <nothing>]]";
   # expected "Mapping[str, Any]"

In this case the solution is to use insert_one({}), passing a document instead of a list.

Another example is trying to set a value on a ~bson.raw_bson.RawBSONDocument, which is read-only.:

from bson.raw_bson import RawBSONDocument
from pymongo import MongoClient

client = MongoClient(document_class=RawBSONDocument)
coll = client.test.test
doc = {"my": "doc"}
coll.insert_one(doc)
retreived = coll.find_one({"_id": doc["_id"]})
assert retreived is not None
assert len(retreived.raw) > 0
retreived[
    "foo"
] = "bar"  # error: Unsupported target for indexed assignment
           # ("RawBSONDocument")  [index]