diff --git a/doc/contributors.rst b/doc/contributors.rst index 8a5a4432..c1660fb5 100644 --- a/doc/contributors.rst +++ b/doc/contributors.rst @@ -17,3 +17,4 @@ The following is a list of people who have contributed to - Tushar Singh - Steven Silvester - Julius Park +- Doeke Buursma diff --git a/motor/core.pyi b/motor/core.pyi index a3bb96fd..14a1e14a 100644 --- a/motor/core.pyi +++ b/motor/core.pyi @@ -35,6 +35,7 @@ import pymongo.errors import pymongo.mongo_client import typing_extensions from bson import Binary, Code, CodecOptions, DBRef, Timestamp +from bson.codec_options import TypeRegistry from bson.raw_bson import RawBSONDocument from pymongo import IndexModel, ReadPreference, WriteConcern from pymongo.change_stream import ChangeStream @@ -99,7 +100,7 @@ class AgnosticClient(AgnosticBaseProperties[_DocumentType]): def __hash__(self) -> int: ... async def drop_database( self, - name_or_database: Union[str, AgnosticDatabase], + name_or_database: Union[str, AgnosticDatabase[_DocumentTypeArg]], session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None, ) -> None: ... @@ -130,7 +131,7 @@ class AgnosticClient(AgnosticBaseProperties[_DocumentType]): session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AgnosticCommandCursor: ... + ) -> AgnosticCommandCursor[dict[str, Any]]: ... async def list_database_names( self, session: Optional[AgnosticClientSession] = None, @@ -155,7 +156,16 @@ class AgnosticClient(AgnosticBaseProperties[_DocumentType]): _io_loop: Optional[Any] _framework: Any - def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def __init__( + self, + host: Optional[Union[str, Sequence[str]]] = None, + port: Optional[int] = None, + document_class: Optional[type[_DocumentType]] = None, + tz_aware: Optional[bool] = None, + connect: Optional[bool] = None, + type_registry: Optional[TypeRegistry] = None, + **kwargs: Any, + ) -> None: ... @property def io_loop(self) -> Any: ... def get_io_loop(self) -> Any: ... @@ -173,9 +183,9 @@ class AgnosticClient(AgnosticBaseProperties[_DocumentType]): comment: Optional[str] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> AgnosticChangeStream: ... - def __getattr__(self, name: str) -> AgnosticDatabase: ... - def __getitem__(self, name: str) -> AgnosticDatabase: ... + ) -> AgnosticChangeStream[_DocumentType]: ... + def __getattr__(self, name: str) -> AgnosticDatabase[_DocumentType]: ... + def __getitem__(self, name: str) -> AgnosticDatabase[_DocumentType]: ... def wrap(self, obj: Any) -> Any: ... class _MotorTransactionContext: @@ -240,7 +250,7 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): comment: Optional[Any] = None, max_await_time_ms: Optional[int] = None, **kwargs: Any, - ) -> AgnosticCommandCursor: ... + ) -> AgnosticCommandCursor[_DocumentType]: ... @overload async def command( self, @@ -290,7 +300,7 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): session: Optional[AgnosticClientSession] = None, check_exists: Optional[bool] = True, **kwargs: Any, - ) -> AgnosticCollection: ... + ) -> AgnosticCollection[_DocumentType]: ... async def dereference( self, dbref: DBRef, @@ -300,7 +310,7 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): ) -> Optional[_DocumentType]: ... async def drop_collection( self, - name_or_collection: Union[str, AgnosticCollection], + name_or_collection: Union[str, AgnosticCollection[_DocumentTypeArg]], session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None, encrypted_fields: Optional[Mapping[str, Any]] = None, @@ -312,7 +322,7 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AgnosticCollection: ... + ) -> AgnosticCollection[_DocumentType]: ... async def list_collection_names( self, session: Optional[AgnosticClientSession] = None, @@ -326,12 +336,12 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): filter: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AgnosticCommandCursor: ... + ) -> AgnosticCommandCursor[MutableMapping[str, Any]]: ... @property def name(self) -> str: ... async def validate_collection( self, - name_or_collection: Union[str, AgnosticCollection], + name_or_collection: Union[str, AgnosticCollection[_DocumentTypeArg]], scandata: bool = False, full: bool = False, session: Optional[AgnosticClientSession] = None, @@ -344,14 +354,14 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AgnosticDatabase: ... + ) -> AgnosticDatabase[_DocumentType]: ... async def _async_aggregate( self, pipeline: _Pipeline, session: Optional[AgnosticClientSession] = None, **kwargs: Any - ) -> AgnosticCommandCursor: ... - def __init__(self, client: AgnosticClient, name: str, **kwargs: Any) -> None: ... + ) -> AgnosticCommandCursor[_DocumentType]: ... + def __init__(self, client: AgnosticClient[_DocumentType], name: str, **kwargs: Any) -> None: ... def aggregate( self, pipeline: _Pipeline, *args: Any, **kwargs: Any - ) -> AgnosticLatentCommandCursor: ... + ) -> AgnosticLatentCommandCursor[_DocumentType]: ... def watch( self, pipeline: Optional[_Pipeline] = None, @@ -366,11 +376,11 @@ class AgnosticDatabase(AgnosticBaseProperties[_DocumentType]): comment: Optional[Any] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> AgnosticChangeStream: ... + ) -> AgnosticChangeStream[_DocumentType]: ... @property - def client(self) -> AgnosticClient: ... - def __getattr__(self, name: str) -> AgnosticCollection: ... - def __getitem__(self, name: str) -> AgnosticCollection: ... + def client(self) -> AgnosticClient[_DocumentType]: ... + def __getattr__(self, name: str) -> AgnosticCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> AgnosticCollection[_DocumentType]: ... def __call__(self, *args: Any, **kwargs: Any) -> None: ... def wrap(self, obj: Any) -> Any: ... def get_io_loop(self) -> Any: ... @@ -577,14 +587,14 @@ class AgnosticCollection(AgnosticBaseProperties[_DocumentType]): read_preference: Optional[ReadPreference] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AgnosticCollection[Mapping[str, Any]]: ... + ) -> AgnosticCollection[_DocumentType]: ... def list_search_indexes( self, name: Optional[str] = None, session: Optional[AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AgnosticLatentCommandCursor: ... + ) -> AgnosticLatentCommandCursor[Mapping[str, Any]]: ... async def create_search_index( self, model: Union[Mapping[str, SearchIndexModel], Any], @@ -625,17 +635,19 @@ class AgnosticCollection(AgnosticBaseProperties[_DocumentType]): _delegate: Any = None, **kwargs: Any, ) -> None: ... - def __getattr__(self, name: str) -> AgnosticCollection: ... - def __getitem__(self, name: str) -> AgnosticCollection: ... + def __getattr__(self, name: str) -> AgnosticCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> AgnosticCollection[_DocumentType]: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - def find(self, *args: Any, **kwargs: Any) -> AgnosticCursor: ... - def find_raw_batches(self, *args: Any, **kwargs: Any) -> AgnosticCursor: ... + def find(self, *args: Any, **kwargs: Any) -> AgnosticCursor[_DocumentType]: ... + def find_raw_batches( + self, *args: Any, **kwargs: Any + ) -> AgnosticRawBatchCursor[_DocumentType]: ... def aggregate( self, pipeline: _Pipeline, *args: Any, **kwargs: Any - ) -> AgnosticCommandCursor: ... + ) -> AgnosticCommandCursor[_DocumentType]: ... def aggregate_raw_batches( self, pipeline: _Pipeline, **kwargs: Any - ) -> AgnosticCommandCursor: ... + ) -> AgnosticRawBatchCursor[_DocumentType]: ... def watch( self, pipeline: Optional[_Pipeline] = None, @@ -653,13 +665,17 @@ class AgnosticCollection(AgnosticBaseProperties[_DocumentType]): ) -> Any: ... def list_indexes( self, session: Optional[AgnosticClientSession] = None, **kwargs: Any - ) -> AgnosticLatentCommandCursor: ... + ) -> AgnosticLatentCommandCursor[MutableMapping[str, Any]]: ... def wrap(self, obj: Any) -> Any: ... def get_io_loop(self) -> Any: ... -class AgnosticBaseCursor(AgnosticBase): +class AgnosticBaseCursor(AgnosticBase, Generic[_DocumentType]): def __init__( - self, cursor: Union[Cursor, CommandCursor, _LatentCursor], collection: AgnosticCollection + self, + cursor: Union[ + Cursor[_DocumentType], CommandCursor[_DocumentType], _LatentCursor[_DocumentType] + ], + collection: AgnosticCollection[_DocumentType], ) -> None: ... def address(self) -> Optional[_Address]: ... def cursor_id(self) -> Optional[int]: ... @@ -683,48 +699,50 @@ class AgnosticBaseCursor(AgnosticBase): self, length: Union[int, None], the_list: list, future: Any, get_more_result: Any ) -> None: ... def get_io_loop(self) -> Any: ... - def batch_size(self, batch_size: int) -> AgnosticBaseCursor: ... + def batch_size(self, batch_size: int) -> AgnosticBaseCursor[_DocumentType]: ... def _buffer_size(self) -> int: ... def _query_flags(self) -> Optional[int]: ... def _data(self) -> None: ... def _killed(self) -> None: ... async def close(self) -> None: ... -class AgnosticCursor(AgnosticBaseCursor): +class AgnosticCursor(AgnosticBaseCursor[_DocumentType]): __motor_class_name__: str __delegate_class__: type[Cursor] - def collation(self, collation: Optional[_CollationIn]) -> AgnosticCursor: ... + def collation(self, collation: Optional[_CollationIn]) -> AgnosticCursor[_DocumentType]: ... async def distinct(self, key: str) -> list: ... async def explain(self) -> _DocumentType: ... - def add_option(self, mask: int) -> AgnosticCursor: ... - def remove_option(self, mask: int) -> AgnosticCursor: ... - def limit(self, limit: int) -> AgnosticCursor: ... - def skip(self, skip: int) -> AgnosticCursor: ... - def max_scan(self, max_scan: Optional[int]) -> AgnosticCursor: ... + def add_option(self, mask: int) -> AgnosticCursor[_DocumentType]: ... + def remove_option(self, mask: int) -> AgnosticCursor[_DocumentType]: ... + def limit(self, limit: int) -> AgnosticCursor[_DocumentType]: ... + def skip(self, skip: int) -> AgnosticCursor[_DocumentType]: ... + def max_scan(self, max_scan: Optional[int]) -> AgnosticCursor[_DocumentType]: ... def sort( self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None - ) -> AgnosticCursor: ... - def hint(self, index: Optional[_Hint]) -> AgnosticCursor: ... - def where(self, code: Union[str, Code]) -> AgnosticCursor: ... - def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> AgnosticCursor: ... - def max_time_ms(self, max_time_ms: Optional[int]) -> AgnosticCursor: ... - def min(self, spec: _Sort) -> AgnosticCursor: ... - def max(self, spec: _Sort) -> AgnosticCursor: ... - def comment(self, comment: Any) -> AgnosticCursor: ... - def allow_disk_use(self, allow_disk_use: bool) -> AgnosticCursor: ... - def rewind(self) -> AgnosticCursor: ... - def clone(self) -> AgnosticCursor: ... - def __copy__(self) -> AgnosticCursor: ... - def __deepcopy__(self, memo: Any) -> AgnosticCursor: ... + ) -> AgnosticCursor[_DocumentType]: ... + def hint(self, index: Optional[_Hint]) -> AgnosticCursor[_DocumentType]: ... + def where(self, code: Union[str, Code]) -> AgnosticCursor[_DocumentType]: ... + def max_await_time_ms( + self, max_await_time_ms: Optional[int] + ) -> AgnosticCursor[_DocumentType]: ... + def max_time_ms(self, max_time_ms: Optional[int]) -> AgnosticCursor[_DocumentType]: ... + def min(self, spec: _Sort) -> AgnosticCursor[_DocumentType]: ... + def max(self, spec: _Sort) -> AgnosticCursor[_DocumentType]: ... + def comment(self, comment: Any) -> AgnosticCursor[_DocumentType]: ... + def allow_disk_use(self, allow_disk_use: bool) -> AgnosticCursor[_DocumentType]: ... + def rewind(self) -> AgnosticCursor[_DocumentType]: ... + def clone(self) -> AgnosticCursor[_DocumentType]: ... + def __copy__(self) -> AgnosticCursor[_DocumentType]: ... + def __deepcopy__(self, memo: Any) -> AgnosticCursor[_DocumentType]: ... def _query_flags(self) -> int: ... def _data(self) -> Any: ... def _killed(self) -> Any: ... -class AgnosticRawBatchCursor(AgnosticCursor): +class AgnosticRawBatchCursor(AgnosticCursor[_DocumentType]): __motor_class_name__: str __delegate_class__: type[RawBatchCursor] -class AgnosticCommandCursor(AgnosticBaseCursor): +class AgnosticCommandCursor(AgnosticBaseCursor[_DocumentType]): __motor_class_name__: str __delegate_class__: type[CommandCursor] @@ -732,23 +750,25 @@ class AgnosticCommandCursor(AgnosticBaseCursor): def _data(self) -> Any: ... def _killed(self) -> Any: ... -class AgnosticRawBatchCommandCursor(AgnosticCommandCursor): +class AgnosticRawBatchCommandCursor(AgnosticCommandCursor[_DocumentType]): __motor_class_name__: str __delegate_class__: type[RawBatchCommandCursor] -class _LatentCursor: - def __init__(self, collection: AgnosticCollection): ... +class _LatentCursor(Generic[_DocumentType]): + def __init__(self, collection: AgnosticCollection[_DocumentType]): ... def _CommandCursor__end_session(self, *args: Any, **kwargs: Any) -> None: ... def _CommandCursor__die(self, *args: Any, **kwargs: Any) -> None: ... - def clone(self) -> _LatentCursor: ... - def rewind(self) -> _LatentCursor: ... + def clone(self) -> _LatentCursor[_DocumentType]: ... + def rewind(self) -> _LatentCursor[_DocumentType]: ... -class AgnosticLatentCommandCursor(AgnosticCommandCursor): +class AgnosticLatentCommandCursor(AgnosticCommandCursor[_DocumentType]): __motor_class_name__: str - def __init__(self, collection: AgnosticCollection, start: Any, *args: Any, **kwargs: Any): ... + def __init__( + self, collection: AgnosticCollection[_DocumentType], start: Any, *args: Any, **kwargs: Any + ): ... def _on_started(self, original_future: Any, future: Any) -> None: ... -class AgnosticChangeStream(AgnosticBase): +class AgnosticChangeStream(AgnosticBase, Generic[_DocumentType]): __motor_class_name__: str __delegate_class__: type[ChangeStream] @@ -778,22 +798,22 @@ class AgnosticChangeStream(AgnosticBase): async def next(self) -> _DocumentType: ... async def try_next(self) -> Optional[_DocumentType]: ... async def close(self) -> None: ... - def __aiter__(self) -> AgnosticChangeStream: ... + def __aiter__(self) -> AgnosticChangeStream[_DocumentType]: ... __anext__ = next - async def __aenter__(self) -> AgnosticChangeStream: ... + async def __aenter__(self) -> AgnosticChangeStream[_DocumentType]: ... async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ... def get_io_loop(self) -> Any: ... def __enter__(self) -> None: ... def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ... -class AgnosticClientEncryption(AgnosticBase): +class AgnosticClientEncryption(AgnosticBase, Generic[_DocumentType]): __motor_class_name__: str __delegate_class__: type[ClientEncryption] def __init__( self, kms_providers: Mapping[str, Any], key_vault_namespace: str, - key_vault_client: AgnosticClient, + key_vault_client: AgnosticClient[_DocumentTypeArg], codec_options: CodecOptions, io_loop: Optional[Any] = None, kms_tls_options: Optional[Mapping[str, Any]] = None, @@ -843,17 +863,17 @@ class AgnosticClientEncryption(AgnosticBase): @property def io_loop(self) -> Any: ... def get_io_loop(self) -> Any: ... - async def __aenter__(self) -> AgnosticClientEncryption: ... + async def __aenter__(self) -> AgnosticClientEncryption[_DocumentType]: ... async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ... def __enter__(self) -> NoReturn: ... def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ... - async def get_keys(self) -> AgnosticCursor: ... + async def get_keys(self) -> AgnosticCursor[RawBSONDocument]: ... async def create_encrypted_collection( self, - database: AgnosticDatabase, + database: AgnosticDatabase[_DocumentTypeArg], name: str, encrypted_fields: Mapping[str, Any], kms_provider: Optional[str] = None, master_key: Optional[Mapping[str, Any]] = None, **kwargs: Any, - ) -> tuple[AgnosticCollection, Mapping[str, Any]]: ... + ) -> tuple[AgnosticCollection[_DocumentTypeArg], Mapping[str, Any]]: ... diff --git a/motor/motor_asyncio.pyi b/motor/motor_asyncio.pyi index c3e2719f..f8c3e7b0 100644 --- a/motor/motor_asyncio.pyi +++ b/motor/motor_asyncio.pyi @@ -1,6 +1,7 @@ from typing import Any, Mapping, MutableMapping, Optional, Union from bson import Code, CodecOptions, Timestamp +from bson.raw_bson import RawBSONDocument from pymongo.client_session import TransactionOptions from pymongo.cursor import _Hint, _Sort from pymongo.read_concern import ReadConcern @@ -26,7 +27,7 @@ __all__: list[str] = [ "AsyncIOMotorLatentCommandCursor", ] -class AsyncIOMotorClient(core.AgnosticClient): +class AsyncIOMotorClient(core.AgnosticClient[_DocumentType]): def get_database( self, name: Optional[str] = None, @@ -48,7 +49,7 @@ class AsyncIOMotorClient(core.AgnosticClient): session: Optional[core.AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AsyncIOMotorCommandCursor: ... + ) -> AsyncIOMotorCommandCursor[dict[str, Any]]: ... async def start_session( self, causal_consistency: Optional[bool] = None, @@ -69,16 +70,16 @@ class AsyncIOMotorClient(core.AgnosticClient): comment: Optional[str] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> AsyncIOMotorChangeStream: ... - def __getattr__(self, name: str) -> AsyncIOMotorDatabase: ... - def __getitem__(self, name: str) -> AsyncIOMotorDatabase: ... + ) -> AsyncIOMotorChangeStream[_DocumentType]: ... + def __getattr__(self, name: str) -> AsyncIOMotorDatabase[_DocumentType]: ... + def __getitem__(self, name: str) -> AsyncIOMotorDatabase[_DocumentType]: ... class AsyncIOMotorClientSession(core.AgnosticClientSession): @property def client(self) -> AsyncIOMotorClient: ... async def __aenter__(self) -> AsyncIOMotorClientSession: ... -class AsyncIOMotorDatabase(core.AgnosticDatabase): +class AsyncIOMotorDatabase(core.AgnosticDatabase[_DocumentType]): async def cursor_command( self, command: Union[str, MutableMapping[str, Any]], @@ -89,7 +90,7 @@ class AsyncIOMotorDatabase(core.AgnosticDatabase): comment: Optional[Any] = None, max_await_time_ms: Optional[int] = None, **kwargs: Any, - ) -> AsyncIOMotorCommandCursor: ... + ) -> AsyncIOMotorCommandCursor[_DocumentType]: ... async def create_collection( self, name: str, @@ -100,7 +101,7 @@ class AsyncIOMotorDatabase(core.AgnosticDatabase): session: Optional[core.AgnosticClientSession] = None, check_exists: Optional[bool] = True, **kwargs: Any, - ) -> AsyncIOMotorCollection: ... + ) -> AsyncIOMotorCollection[_DocumentType]: ... def get_collection( self, name: str, @@ -108,24 +109,24 @@ class AsyncIOMotorDatabase(core.AgnosticDatabase): read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AsyncIOMotorCollection: ... + ) -> AsyncIOMotorCollection[_DocumentType]: ... async def list_collections( self, session: Optional[core.AgnosticClientSession] = None, filter: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AsyncIOMotorCommandCursor: ... + ) -> AsyncIOMotorCommandCursor[MutableMapping[str, Any]]: ... def with_options( self, codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None, read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AsyncIOMotorDatabase: ... + ) -> AsyncIOMotorDatabase[_DocumentType]: ... def aggregate( self, pipeline: _Pipeline, *args: Any, **kwargs: Any - ) -> AsyncIOMotorLatentCommandCursor: ... + ) -> AsyncIOMotorLatentCommandCursor[_DocumentType]: ... def watch( self, pipeline: Optional[_Pipeline] = None, @@ -140,86 +141,90 @@ class AsyncIOMotorDatabase(core.AgnosticDatabase): comment: Optional[Any] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> AsyncIOMotorChangeStream: ... + ) -> AsyncIOMotorChangeStream[_DocumentType]: ... @property - def client(self) -> AsyncIOMotorClient: ... - def __getattr__(self, name: str) -> AsyncIOMotorCollection: ... - def __getitem__(self, name: str) -> AsyncIOMotorCollection: ... + def client(self) -> AsyncIOMotorClient[_DocumentType]: ... + def __getattr__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ... -class AsyncIOMotorCollection(core.AgnosticCollection): +class AsyncIOMotorCollection(core.AgnosticCollection[_DocumentType]): def with_options( self, codec_options: Optional[CodecOptions] = None, read_preference: Optional[ReadPreference] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> AsyncIOMotorCollection[Mapping[str, Any]]: ... + ) -> AsyncIOMotorCollection[_DocumentType]: ... def list_search_indexes( self, name: Optional[str] = None, session: Optional[core.AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> AsyncIOMotorLatentCommandCursor: ... - def __getattr__(self, name: str) -> AsyncIOMotorCollection: ... - def __getitem__(self, name: str) -> AsyncIOMotorCollection: ... - def find(self, *args: Any, **kwargs: Any) -> AsyncIOMotorCursor: ... - def find_raw_batches(self, *args: Any, **kwargs: Any) -> AsyncIOMotorCursor: ... + ) -> AsyncIOMotorLatentCommandCursor[Mapping[str, Any]]: ... + def __getattr__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> AsyncIOMotorCollection[_DocumentType]: ... + def find(self, *args: Any, **kwargs: Any) -> AsyncIOMotorCursor[_DocumentType]: ... + def find_raw_batches( + self, *args: Any, **kwargs: Any + ) -> AsyncIOMotorRawBatchCursor[_DocumentType]: ... def aggregate( self, pipeline: _Pipeline, *args: Any, **kwargs: Any - ) -> AsyncIOMotorCommandCursor: ... + ) -> AsyncIOMotorCommandCursor[_DocumentType]: ... def aggregate_raw_batches( self, pipeline: _Pipeline, **kwargs: Any - ) -> AsyncIOMotorCommandCursor: ... + ) -> AsyncIOMotorRawBatchCursor[_DocumentType]: ... def list_indexes( self, session: Optional[core.AgnosticClientSession] = None, **kwargs: Any - ) -> AsyncIOMotorLatentCommandCursor: ... + ) -> AsyncIOMotorLatentCommandCursor[MutableMapping[str, Any]]: ... -class AsyncIOMotorLatentCommandCursor(core.AgnosticLatentCommandCursor): ... +class AsyncIOMotorLatentCommandCursor(core.AgnosticLatentCommandCursor[_DocumentType]): ... -class AsyncIOMotorCursor(core.AgnosticCursor): - def collation(self, collation: Optional[_CollationIn]) -> AsyncIOMotorCursor: ... - def add_option(self, mask: int) -> AsyncIOMotorCursor: ... - def remove_option(self, mask: int) -> AsyncIOMotorCursor: ... - def limit(self, limit: int) -> AsyncIOMotorCursor: ... - def skip(self, skip: int) -> AsyncIOMotorCursor: ... - def max_scan(self, max_scan: Optional[int]) -> AsyncIOMotorCursor: ... +class AsyncIOMotorCursor(core.AgnosticCursor[_DocumentType]): + def collation(self, collation: Optional[_CollationIn]) -> AsyncIOMotorCursor[_DocumentType]: ... + def add_option(self, mask: int) -> AsyncIOMotorCursor[_DocumentType]: ... + def remove_option(self, mask: int) -> AsyncIOMotorCursor[_DocumentType]: ... + def limit(self, limit: int) -> AsyncIOMotorCursor[_DocumentType]: ... + def skip(self, skip: int) -> AsyncIOMotorCursor[_DocumentType]: ... + def max_scan(self, max_scan: Optional[int]) -> AsyncIOMotorCursor[_DocumentType]: ... def sort( self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None - ) -> AsyncIOMotorCursor: ... - def hint(self, index: Optional[_Hint]) -> AsyncIOMotorCursor: ... - def where(self, code: Union[str, Code]) -> AsyncIOMotorCursor: ... - def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> AsyncIOMotorCursor: ... - def max_time_ms(self, max_time_ms: Optional[int]) -> AsyncIOMotorCursor: ... - def min(self, spec: _Sort) -> AsyncIOMotorCursor: ... - def max(self, spec: _Sort) -> AsyncIOMotorCursor: ... - def comment(self, comment: Any) -> AsyncIOMotorCursor: ... - def allow_disk_use(self, allow_disk_use: bool) -> AsyncIOMotorCursor: ... - def rewind(self) -> AsyncIOMotorCursor: ... - def clone(self) -> AsyncIOMotorCursor: ... - def __copy__(self) -> AsyncIOMotorCursor: ... - def __deepcopy__(self, memo: Any) -> AsyncIOMotorCursor: ... + ) -> AsyncIOMotorCursor[_DocumentType]: ... + def hint(self, index: Optional[_Hint]) -> AsyncIOMotorCursor[_DocumentType]: ... + def where(self, code: Union[str, Code]) -> AsyncIOMotorCursor[_DocumentType]: ... + def max_await_time_ms( + self, max_await_time_ms: Optional[int] + ) -> AsyncIOMotorCursor[_DocumentType]: ... + def max_time_ms(self, max_time_ms: Optional[int]) -> AsyncIOMotorCursor[_DocumentType]: ... + def min(self, spec: _Sort) -> AsyncIOMotorCursor[_DocumentType]: ... + def max(self, spec: _Sort) -> AsyncIOMotorCursor[_DocumentType]: ... + def comment(self, comment: Any) -> AsyncIOMotorCursor[_DocumentType]: ... + def allow_disk_use(self, allow_disk_use: bool) -> AsyncIOMotorCursor[_DocumentType]: ... + def rewind(self) -> AsyncIOMotorCursor[_DocumentType]: ... + def clone(self) -> AsyncIOMotorCursor[_DocumentType]: ... + def __copy__(self) -> AsyncIOMotorCursor[_DocumentType]: ... + def __deepcopy__(self, memo: Any) -> AsyncIOMotorCursor[_DocumentType]: ... -class AsyncIOMotorRawBatchCursor(core.AgnosticRawBatchCursor): ... -class AsyncIOMotorCommandCursor(core.AgnosticCommandCursor): ... -class AsyncIOMotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor): ... +class AsyncIOMotorRawBatchCursor(core.AgnosticRawBatchCursor[_DocumentType]): ... +class AsyncIOMotorCommandCursor(core.AgnosticCommandCursor[_DocumentType]): ... +class AsyncIOMotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor[_DocumentType]): ... -class AsyncIOMotorChangeStream(core.AgnosticChangeStream): - def __aiter__(self) -> AsyncIOMotorChangeStream: ... - async def __aenter__(self) -> AsyncIOMotorChangeStream: ... +class AsyncIOMotorChangeStream(core.AgnosticChangeStream[_DocumentType]): + def __aiter__(self) -> AsyncIOMotorChangeStream[_DocumentType]: ... + async def __aenter__(self) -> AsyncIOMotorChangeStream[_DocumentType]: ... -class AsyncIOMotorClientEncryption(core.AgnosticClientEncryption): - async def __aenter__(self) -> AsyncIOMotorClientEncryption: ... - async def get_keys(self) -> AsyncIOMotorCursor: ... +class AsyncIOMotorClientEncryption(core.AgnosticClientEncryption[_DocumentType]): + async def __aenter__(self) -> AsyncIOMotorClientEncryption[_DocumentType]: ... + async def get_keys(self) -> AsyncIOMotorCursor[RawBSONDocument]: ... async def create_encrypted_collection( self, - database: core.AgnosticDatabase, + database: core.AgnosticDatabase[_DocumentTypeArg], name: str, encrypted_fields: Mapping[str, Any], kms_provider: Optional[str] = None, master_key: Optional[Mapping[str, Any]] = None, **kwargs: Any, - ) -> tuple[AsyncIOMotorCollection, Mapping[str, Any]]: ... + ) -> tuple[AsyncIOMotorCollection[_DocumentTypeArg], Mapping[str, Any]]: ... class AsyncIOMotorGridOutCursor(motor_gridfs.AgnosticGridOutCursor): def next_object(self) -> AsyncIOMotorGridOutCursor: ... diff --git a/motor/motor_tornado.pyi b/motor/motor_tornado.pyi index a4525be8..34a55faf 100644 --- a/motor/motor_tornado.pyi +++ b/motor/motor_tornado.pyi @@ -1,6 +1,7 @@ from typing import Any, Mapping, MutableMapping, Optional, Union from bson import Code, CodecOptions, Timestamp +from bson.raw_bson import RawBSONDocument from pymongo.client_session import TransactionOptions from pymongo.cursor import _Hint, _Sort from pymongo.read_concern import ReadConcern @@ -25,7 +26,7 @@ __all__: list[str] = [ "MotorClientEncryption", ] -class MotorClient(core.AgnosticClient): +class MotorClient(core.AgnosticClient[_DocumentType]): def get_database( self, name: Optional[str] = None, @@ -47,7 +48,7 @@ class MotorClient(core.AgnosticClient): session: Optional[core.AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> MotorCommandCursor: ... + ) -> MotorCommandCursor[dict[str, Any]]: ... async def start_session( self, causal_consistency: Optional[bool] = None, @@ -68,16 +69,16 @@ class MotorClient(core.AgnosticClient): comment: Optional[str] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> MotorChangeStream: ... - def __getattr__(self, name: str) -> MotorDatabase: ... - def __getitem__(self, name: str) -> MotorDatabase: ... + ) -> MotorChangeStream[_DocumentType]: ... + def __getattr__(self, name: str) -> MotorDatabase[_DocumentType]: ... + def __getitem__(self, name: str) -> MotorDatabase[_DocumentType]: ... class MotorClientSession(core.AgnosticClientSession): @property def client(self) -> MotorClient: ... async def __aenter__(self) -> MotorClientSession: ... -class MotorDatabase(core.AgnosticDatabase): +class MotorDatabase(core.AgnosticDatabase[_DocumentType]): async def cursor_command( self, command: Union[str, MutableMapping[str, Any]], @@ -88,7 +89,7 @@ class MotorDatabase(core.AgnosticDatabase): comment: Optional[Any] = None, max_await_time_ms: Optional[int] = None, **kwargs: Any, - ) -> MotorCommandCursor: ... + ) -> MotorCommandCursor[_DocumentType]: ... async def create_collection( self, name: str, @@ -99,7 +100,7 @@ class MotorDatabase(core.AgnosticDatabase): session: Optional[core.AgnosticClientSession] = None, check_exists: Optional[bool] = True, **kwargs: Any, - ) -> MotorCollection: ... + ) -> MotorCollection[_DocumentType]: ... def get_collection( self, name: str, @@ -107,24 +108,24 @@ class MotorDatabase(core.AgnosticDatabase): read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> MotorCollection: ... + ) -> MotorCollection[_DocumentType]: ... async def list_collections( self, session: Optional[core.AgnosticClientSession] = None, filter: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> MotorCommandCursor: ... + ) -> MotorCommandCursor[MutableMapping[str, Any]]: ... def with_options( self, codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None, read_preference: Optional[_ServerMode] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> MotorDatabase: ... + ) -> MotorDatabase[_DocumentType]: ... def aggregate( self, pipeline: _Pipeline, *args: Any, **kwargs: Any - ) -> MotorLatentCommandCursor: ... + ) -> MotorLatentCommandCursor[_DocumentType]: ... def watch( self, pipeline: Optional[_Pipeline] = None, @@ -139,82 +140,86 @@ class MotorDatabase(core.AgnosticDatabase): comment: Optional[Any] = None, full_document_before_change: Optional[str] = None, show_expanded_events: Optional[bool] = None, - ) -> MotorChangeStream: ... + ) -> MotorChangeStream[_DocumentType]: ... @property - def client(self) -> MotorClient: ... - def __getattr__(self, name: str) -> MotorCollection: ... - def __getitem__(self, name: str) -> MotorCollection: ... + def client(self) -> MotorClient[_DocumentType]: ... + def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ... -class MotorCollection(core.AgnosticCollection): +class MotorCollection(core.AgnosticCollection[_DocumentType]): def with_options( self, codec_options: Optional[CodecOptions] = None, read_preference: Optional[ReadPreference] = None, write_concern: Optional[WriteConcern] = None, read_concern: Optional[ReadConcern] = None, - ) -> MotorCollection[Mapping[str, Any]]: ... + ) -> MotorCollection[_DocumentType]: ... def list_search_indexes( self, name: Optional[str] = None, session: Optional[core.AgnosticClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> MotorLatentCommandCursor: ... - def __getattr__(self, name: str) -> MotorCollection: ... - def __getitem__(self, name: str) -> MotorCollection: ... - def find(self, *args: Any, **kwargs: Any) -> MotorCursor: ... - def find_raw_batches(self, *args: Any, **kwargs: Any) -> MotorCursor: ... - def aggregate(self, pipeline: _Pipeline, *args: Any, **kwargs: Any) -> MotorCommandCursor: ... - def aggregate_raw_batches(self, pipeline: _Pipeline, **kwargs: Any) -> MotorCommandCursor: ... + ) -> MotorLatentCommandCursor[Mapping[str, Any]]: ... + def __getattr__(self, name: str) -> MotorCollection[_DocumentType]: ... + def __getitem__(self, name: str) -> MotorCollection[_DocumentType]: ... + def find(self, *args: Any, **kwargs: Any) -> MotorCursor[_DocumentType]: ... + def find_raw_batches(self, *args: Any, **kwargs: Any) -> MotorRawBatchCursor[_DocumentType]: ... + def aggregate( + self, pipeline: _Pipeline, *args: Any, **kwargs: Any + ) -> MotorCommandCursor[_DocumentType]: ... + def aggregate_raw_batches( + self, pipeline: _Pipeline, **kwargs: Any + ) -> MotorRawBatchCursor[_DocumentType]: ... def list_indexes( self, session: Optional[core.AgnosticClientSession] = None, **kwargs: Any - ) -> MotorLatentCommandCursor: ... + ) -> MotorLatentCommandCursor[MutableMapping[str, Any]]: ... -class MotorLatentCommandCursor(core.AgnosticLatentCommandCursor): ... +class MotorLatentCommandCursor(core.AgnosticLatentCommandCursor[_DocumentType]): ... -class MotorCursor(core.AgnosticCursor): - def collation(self, collation: Optional[_CollationIn]) -> MotorCursor: ... - def add_option(self, mask: int) -> MotorCursor: ... - def remove_option(self, mask: int) -> MotorCursor: ... - def limit(self, limit: int) -> MotorCursor: ... - def skip(self, skip: int) -> MotorCursor: ... - def max_scan(self, max_scan: Optional[int]) -> MotorCursor: ... +class MotorCursor(core.AgnosticCursor[_DocumentType]): + def collation(self, collation: Optional[_CollationIn]) -> MotorCursor[_DocumentType]: ... + def add_option(self, mask: int) -> MotorCursor[_DocumentType]: ... + def remove_option(self, mask: int) -> MotorCursor[_DocumentType]: ... + def limit(self, limit: int) -> MotorCursor[_DocumentType]: ... + def skip(self, skip: int) -> MotorCursor[_DocumentType]: ... + def max_scan(self, max_scan: Optional[int]) -> MotorCursor[_DocumentType]: ... def sort( self, key_or_list: _Hint, direction: Optional[Union[int, str]] = None - ) -> MotorCursor: ... - def hint(self, index: Optional[_Hint]) -> MotorCursor: ... - def where(self, code: Union[str, Code]) -> MotorCursor: ... - def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> MotorCursor: ... - def max_time_ms(self, max_time_ms: Optional[int]) -> MotorCursor: ... - def min(self, spec: _Sort) -> MotorCursor: ... - def max(self, spec: _Sort) -> MotorCursor: ... - def comment(self, comment: Any) -> MotorCursor: ... - def allow_disk_use(self, allow_disk_use: bool) -> MotorCursor: ... - def rewind(self) -> MotorCursor: ... - def clone(self) -> MotorCursor: ... - def __copy__(self) -> MotorCursor: ... - def __deepcopy__(self, memo: Any) -> MotorCursor: ... - -class MotorRawBatchCursor(core.AgnosticRawBatchCursor): ... -class MotorCommandCursor(core.AgnosticCommandCursor): ... -class MotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor): ... - -class MotorChangeStream(core.AgnosticChangeStream): - def __aiter__(self) -> MotorChangeStream: ... - async def __aenter__(self) -> MotorChangeStream: ... - -class MotorClientEncryption(core.AgnosticClientEncryption): - async def __aenter__(self) -> MotorClientEncryption: ... - async def get_keys(self) -> MotorCursor: ... + ) -> MotorCursor[_DocumentType]: ... + def hint(self, index: Optional[_Hint]) -> MotorCursor[_DocumentType]: ... + def where(self, code: Union[str, Code]) -> MotorCursor[_DocumentType]: ... + def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ... + def max_time_ms(self, max_time_ms: Optional[int]) -> MotorCursor[_DocumentType]: ... + def min(self, spec: _Sort) -> MotorCursor[_DocumentType]: ... + def max(self, spec: _Sort) -> MotorCursor[_DocumentType]: ... + def comment(self, comment: Any) -> MotorCursor[_DocumentType]: ... + def allow_disk_use(self, allow_disk_use: bool) -> MotorCursor[_DocumentType]: ... + def rewind(self) -> MotorCursor[_DocumentType]: ... + def clone(self) -> MotorCursor[_DocumentType]: ... + def __copy__(self) -> MotorCursor[_DocumentType]: ... + def __deepcopy__(self, memo: Any) -> MotorCursor[_DocumentType]: ... + +class MotorRawBatchCursor(core.AgnosticRawBatchCursor[_DocumentType]): ... +class MotorCommandCursor(core.AgnosticCommandCursor[_DocumentType]): ... +class MotorRawBatchCommandCursor(core.AgnosticRawBatchCommandCursor[_DocumentType]): ... + +class MotorChangeStream(core.AgnosticChangeStream[_DocumentType]): + def __aiter__(self) -> MotorChangeStream[_DocumentType]: ... + async def __aenter__(self) -> MotorChangeStream[_DocumentType]: ... + +class MotorClientEncryption(core.AgnosticClientEncryption[_DocumentType]): + async def __aenter__(self) -> MotorClientEncryption[_DocumentType]: ... + async def get_keys(self) -> MotorCursor[RawBSONDocument]: ... async def create_encrypted_collection( self, - database: core.AgnosticDatabase, + database: core.AgnosticDatabase[_DocumentTypeArg], name: str, encrypted_fields: Mapping[str, Any], kms_provider: Optional[str] = None, master_key: Optional[Mapping[str, Any]] = None, **kwargs: Any, - ) -> tuple[MotorCollection, Mapping[str, Any]]: ... + ) -> tuple[MotorCollection[_DocumentTypeArg], Mapping[str, Any]]: ... class MotorGridOutCursor(motor_gridfs.AgnosticGridOutCursor): def next_object(self) -> MotorGridOutCursor: ... diff --git a/test/test_typing.py b/test/test_typing.py index 7a3dd2eb..488e1db3 100644 --- a/test/test_typing.py +++ b/test/test_typing.py @@ -15,6 +15,7 @@ """Test that each file in mypy_fails/ actually fails mypy, and test some sample client code that uses Motor typings. """ + import unittest from test.asyncio_tests import AsyncIOTestCase, asyncio_test from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, TypeVar, Union, cast @@ -22,6 +23,7 @@ from bson import CodecOptions from bson.raw_bson import RawBSONDocument from bson.son import SON +from pymongo import ASCENDING from pymongo.operations import DeleteOne, InsertOne, ReplaceOne from pymongo.read_preferences import ReadPreference @@ -214,6 +216,118 @@ async def execute_transaction(session: Any) -> None: class TestDocumentType(AsyncIOTestCase): + @only_type_check + async def test_default(self) -> None: + client: AsyncIOMotorClient = AsyncIOMotorClient() + coll = client.test.test + retrieved = await coll.find_one({"_id": "foo"}) + assert retrieved is not None + retrieved["a"] = 1 + + @only_type_check + async def test_explicit_document_type(self) -> None: + client: AsyncIOMotorClient[Dict[str, Any]] = AsyncIOMotorClient() + coll = client.test.test + retrieved = await coll.find_one({"_id": "foo"}) + assert retrieved is not None + retrieved["a"] = 1 + + @only_type_check + async def test_typeddict_document_type(self) -> None: + client: AsyncIOMotorClient[Movie] = AsyncIOMotorClient() + coll = client.test.test + retrieved = await coll.find_one({"_id": "foo"}) + assert retrieved is not None + assert retrieved["year"] == 1 + assert retrieved["name"] == "a" + + @only_type_check + async def test_typeddict_document_type_insertion(self) -> None: + client: AsyncIOMotorClient[Movie] = AsyncIOMotorClient() + coll = client.test.test + mov = {"name": "THX-1138", "year": 1971} + movie = Movie(name="THX-1138", year=1971) + await coll.insert_one(mov) # type: ignore[arg-type] + await coll.insert_one( + {"name": "THX-1138", "year": 1971} + ) # This will work because it is in-line. + await coll.insert_one(movie) + await coll.insert_many([mov]) # type: ignore[list-item] + await coll.insert_many([movie]) + bad_mov = {"name": "THX-1138", "year": "WRONG TYPE"} + bad_movie = Movie(name="THX-1138", year="WRONG TYPE") # type: ignore[typeddict-item] + await coll.insert_one(bad_mov) # type:ignore[arg-type] + await coll.insert_one({"name": "THX-1138", "year": "WRONG TYPE"}) # type: ignore[typeddict-item] + await coll.insert_one(bad_movie) + await coll.insert_many([bad_mov]) # type: ignore[list-item] + await coll.insert_many( + [{"name": "THX-1138", "year": "WRONG TYPE"}] # type: ignore[typeddict-item] + ) + await coll.insert_many([bad_movie]) + + @only_type_check + async def test_bulk_write_document_type_insertion(self) -> None: + client: AsyncIOMotorClient[MovieWithId] = AsyncIOMotorClient() + coll = client.test.test + await coll.bulk_write( + [InsertOne(Movie({"name": "THX-1138", "year": 1971}))] # type:ignore[arg-type] + ) + mov_dict = {"_id": ObjectId(), "name": "THX-1138", "year": 1971} + await coll.bulk_write( + [InsertOne(mov_dict)] # type:ignore[arg-type] + ) + await coll.bulk_write( + [ + InsertOne({"_id": ObjectId(), "name": "THX-1138", "year": 1971}) + ] # No error because it is in-line. + ) + + @only_type_check + async def test_bulk_write_document_type_replacement(self) -> None: + client: AsyncIOMotorClient[MovieWithId] = AsyncIOMotorClient() + coll = client.test.test + await coll.bulk_write( + [ReplaceOne({}, Movie({"name": "THX-1138", "year": 1971}))] # type:ignore[arg-type] + ) + mov_dict = {"_id": ObjectId(), "name": "THX-1138", "year": 1971} + await coll.bulk_write( + [ReplaceOne({}, mov_dict)] # type:ignore[arg-type] + ) + await coll.bulk_write( + [ + ReplaceOne({}, {"_id": ObjectId(), "name": "THX-1138", "year": 1971}) + ] # No error because it is in-line. + ) + + @only_type_check + async def test_raw_bson_document_type(self) -> None: + client = AsyncIOMotorClient(document_class=RawBSONDocument) + coll = client.test.test + retrieved = await coll.find_one({"_id": "foo"}) + assert retrieved is not None + assert len(retrieved.raw) > 0 + + @only_type_check + async def test_son_document_type(self) -> None: + client = AsyncIOMotorClient(document_class=SON[str, Any]) + coll = client.test.test + retrieved = await coll.find_one({"_id": "foo"}) + assert retrieved is not None + retrieved["a"] = 1 + + def test_son_document_type_runtime(self) -> None: + AsyncIOMotorClient(document_class=SON[str, Any], connect=False) + + @only_type_check + async def test_create_index(self) -> None: + client: AsyncIOMotorClient[Dict[str, str]] = AsyncIOMotorClient("test") + db = client.test + async with await client.start_session() as session: + index = await db.test.create_index( + [("user_id", ASCENDING)], unique=True, session=session + ) + assert isinstance(index, str) + @only_type_check def test_typeddict_explicit_document_type(self) -> None: out = MovieWithId(_id=ObjectId(), name="THX-1138", year=1971)