Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Version history
This library adheres to
`Semantic Versioning 2.0 <https://semver.org/#semantic-versioning-200>`_.

**UNRELEASED**

- Fixed the element type of ``Deque`` (``collections.deque``), ``MutableSequence`` and
``MutableSet`` annotations not being checked, unlike the already supported ``List``,
``Sequence``, ``Set`` and ``FrozenSet``
(`#577 <https://github.com/agronholm/typeguard/pull/577>`_; PR by @sneha4175)

**4.6.0** (2026-07-26)

- Added support for type checking against the PEP 661 ``sentinel`` type (built-in on
Expand Down
24 changes: 22 additions & 2 deletions src/typeguard/_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
import types
import typing
import warnings
from collections.abc import Mapping, MutableMapping, Sequence
from collections.abc import (
Mapping,
MutableMapping,
MutableSequence,
MutableSet,
Sequence,
)
from enum import Enum
from inspect import Parameter, isclass
from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
Expand Down Expand Up @@ -330,7 +336,13 @@ def check_sequence(
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
if not isinstance(value, collections.abc.Sequence):
if origin_type is collections.deque:
if not isinstance(value, collections.deque):
raise TypeCheckError("is not a deque")
elif origin_type is MutableSequence:
if not isinstance(value, MutableSequence):
raise TypeCheckError("is not a mutable sequence")
elif not isinstance(value, collections.abc.Sequence):
raise TypeCheckError("is not a sequence")

if args and args != (Any,):
Expand All @@ -352,6 +364,9 @@ def check_set(
if origin_type is frozenset:
if not isinstance(value, frozenset):
raise TypeCheckError("is not a frozenset")
elif origin_type is MutableSet:
if not isinstance(value, MutableSet):
raise TypeCheckError("is not a mutable set")
elif not isinstance(value, AbstractSet):
raise TypeCheckError("is not a set")

Expand Down Expand Up @@ -1029,6 +1044,7 @@ def check_type_internal(
Callable: check_callable,
collections.abc.Callable: check_callable,
complex: check_number,
collections.deque: check_sequence,
dict: check_mapping,
Dict: check_mapping,
float: check_number,
Expand All @@ -1039,9 +1055,13 @@ def check_type_internal(
typing.Literal: check_literal,
Mapping: check_mapping,
MutableMapping: check_mapping,
MutableSequence: check_sequence,
MutableSet: check_set,
None: check_none,
collections.abc.Mapping: check_mapping,
collections.abc.MutableMapping: check_mapping,
collections.abc.MutableSequence: check_sequence,
collections.abc.MutableSet: check_set,
Sequence: check_sequence,
collections.abc.Sequence: check_sequence,
collections.abc.Set: check_set,
Expand Down
83 changes: 83 additions & 0 deletions tests/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Collection,
Concatenate,
ContextManager,
Deque,
Dict,
ForwardRef,
FrozenSet,
Expand All @@ -27,6 +28,8 @@
Literal,
Mapping,
MutableMapping,
MutableSequence,
MutableSet,
ParamSpec,
Protocol,
Sequence,
Expand Down Expand Up @@ -803,6 +806,86 @@ def test_set_against_frozenset(self, sample_set: set):
)


class TestDeque:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, Deque[int]).match(
"int is not a deque"
)

def test_valid(self):
check_type(collections.deque([1, 2]), Deque[int])

def test_first_check_empty(self):
check_type(collections.deque(), Deque[int])

def test_first_check_fail(self):
pytest.raises(
TypeCheckError, check_type, collections.deque(["bb"]), Deque[int]
).match("item 0 of collections.deque is not an instance of int")

def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
collections.deque([1, 2, "bb"]),
Deque[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("item 2 of collections.deque is not an instance of int")

def test_list_against_deque(self):
pytest.raises(TypeCheckError, check_type, [1, 2], Deque[int]).match(
"list is not a deque"
)


class TestMutableSequence:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, MutableSequence[int]).match(
"int is not a mutable sequence"
)

def test_valid(self):
check_type([1, 2], MutableSequence[int])

def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
[1, 2, "bb"],
MutableSequence[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("item 2 of list is not an instance of int")

def test_tuple_against_mutable_sequence(self):
pytest.raises(TypeCheckError, check_type, (1, 2), MutableSequence[int]).match(
"tuple is not a mutable sequence"
)


class TestMutableSet:
def test_bad_type(self):
pytest.raises(TypeCheckError, check_type, 5, MutableSet[int]).match(
"int is not a mutable set"
)

def test_valid(self):
check_type({1, 2}, MutableSet[int])

def test_full_check_fail(self):
pytest.raises(
TypeCheckError,
check_type,
{1, 2, "bb"},
MutableSet[int],
collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,
).match("set is not an instance of int")

def test_frozenset_against_mutable_set(self):
pytest.raises(
TypeCheckError, check_type, frozenset({1, 2}), MutableSet[int]
).match("frozenset is not a mutable set")


@pytest.mark.parametrize(
"annotated_type",
[
Expand Down