Repository: pydantic/pydantic

Issue:
Accept deque as sequence-like input data type
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this feature/change is needed
* [x] After submitting this, I commit to one of:
    * Look through open issues and helped at least one other person
    * Hit the "watch" button on this repo to receive notifications and I commit to help at least 2 people that ask questions in the future
    * Implement a Pull Request for a confirmed bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Feature Request

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /home/quan/.local/lib/python3.8/site-packages/pydantic
               python version: 3.8.2 (default, Jul 16 2020, 14:00:26)  [GCC 9.3.0]
                     platform: Linux-5.4.0-47-generic-x86_64-with-glibc2.29
     optional deps. installed: []

```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your feature hasn't been asked for before, or already implemented. -->

Code snippet:

```py
from collections import deque
from typing import List
from pydantic import BaseModel


class Ticket(BaseModel):
    numbers: List[int] = []


Ticket.parse_obj({'numbers': deque((1, 3, 5))})
```

Got error:

```py
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 455, in pydantic.main.BaseModel.parse_obj
  File "pydantic/main.py", line 346, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Ticket
numbers
  value is not a valid list (type=type_error.list)

1 validation error for Ticket
numbers
  value is not a valid list (type=type_error.list)

```

I expect to get:

```py
Ticket(numbers=[1, 3, 5])
```


Comments:

Patch:
diff --git a/docs/examples/types_iterables.py b/docs/examples/types_iterables.py
--- a/docs/examples/types_iterables.py
+++ b/docs/examples/types_iterables.py
@@ -1,4 +1,6 @@
-from typing import Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union
+from typing import (
+    Deque, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union
+)
 
 from pydantic import BaseModel
 
@@ -24,6 +26,8 @@ class Model(BaseModel):
 
     compound: Dict[Union[str, bytes], List[Set[int]]] = None
 
+    deque: Deque[int] = None
+
 
 print(Model(simple_list=['1', '2', '3']).simple_list)
 print(Model(list_of_ints=['1', '2', '3']).list_of_ints)
@@ -36,3 +40,5 @@ class Model(BaseModel):
 
 print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)
 print(Model(sequence_of_ints=(1, 2, 3, 4)).sequence_of_ints)
+
+print(Model(deque=[1, 2, 3]).deque)
diff --git a/pydantic/errors.py b/pydantic/errors.py
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -282,6 +282,10 @@ class FrozenSetError(PydanticTypeError):
     msg_template = 'value is not a valid frozenset'
 
 
+class DequeError(PydanticTypeError):
+    msg_template = 'value is not a valid deque'
+
+
 class TupleError(PydanticTypeError):
     msg_template = 'value is not a valid tuple'
 
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -1,8 +1,10 @@
 import warnings
+from collections import deque
 from collections.abc import Iterable as CollectionsIterable
 from typing import (
     TYPE_CHECKING,
     Any,
+    Deque,
     Dict,
     FrozenSet,
     Generator,
@@ -207,6 +209,7 @@ def Schema(default: Any, **kwargs: Any) -> Any:
 SHAPE_FROZENSET = 8
 SHAPE_ITERABLE = 9
 SHAPE_GENERIC = 10
+SHAPE_DEQUE = 11
 SHAPE_NAME_LOOKUP = {
     SHAPE_LIST: 'List[{}]',
     SHAPE_SET: 'Set[{}]',
@@ -214,6 +217,7 @@ def Schema(default: Any, **kwargs: Any) -> Any:
     SHAPE_SEQUENCE: 'Sequence[{}]',
     SHAPE_FROZENSET: 'FrozenSet[{}]',
     SHAPE_ITERABLE: 'Iterable[{}]',
+    SHAPE_DEQUE: 'Deque[{}]',
 }
 
 
@@ -477,6 +481,9 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
         elif issubclass(origin, FrozenSet):
             self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_FROZENSET
+        elif issubclass(origin, Deque):
+            self.type_ = get_args(self.type_)[0]
+            self.shape = SHAPE_DEQUE
         elif issubclass(origin, Sequence):
             self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
@@ -620,7 +627,7 @@ def _validate_sequence_like(  # noqa: C901 (ignore complexity)
         if errors:
             return v, errors
 
-        converted: Union[List[Any], Set[Any], FrozenSet[Any], Tuple[Any, ...], Iterator[Any]] = result
+        converted: Union[List[Any], Set[Any], FrozenSet[Any], Tuple[Any, ...], Iterator[Any], Deque[Any]] = result
 
         if self.shape == SHAPE_SET:
             converted = set(result)
@@ -628,6 +635,8 @@ def _validate_sequence_like(  # noqa: C901 (ignore complexity)
             converted = frozenset(result)
         elif self.shape == SHAPE_TUPLE_ELLIPSIS:
             converted = tuple(result)
+        elif self.shape == SHAPE_DEQUE:
+            converted = deque(result)
         elif self.shape == SHAPE_SEQUENCE:
             if isinstance(v, tuple):
                 converted = tuple(result)
@@ -635,6 +644,8 @@ def _validate_sequence_like(  # noqa: C901 (ignore complexity)
                 converted = set(result)
             elif isinstance(v, Generator):
                 converted = iter(result)
+            elif isinstance(v, deque):
+                converted = deque(result)
         return converted, None
 
     def _validate_iterable(
diff --git a/pydantic/json.py b/pydantic/json.py
--- a/pydantic/json.py
+++ b/pydantic/json.py
@@ -1,4 +1,5 @@
 import datetime
+from collections import deque
 from decimal import Decimal
 from enum import Enum
 from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
@@ -27,6 +28,7 @@ def isoformat(o: Union[datetime.date, datetime.time]) -> str:
     Decimal: float,
     Enum: lambda o: o.value,
     frozenset: list,
+    deque: list,
     GeneratorType: list,
     IPv4Address: str,
     IPv4Interface: str,
diff --git a/pydantic/utils.py b/pydantic/utils.py
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -126,7 +126,7 @@ def truncate(v: Union[str], *, max_len: int = 80) -> str:
 
 
 def sequence_like(v: Type[Any]) -> bool:
-    return isinstance(v, (list, tuple, set, frozenset, GeneratorType))
+    return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque))
 
 
 def validate_field_name(bases: List[Type['BaseModel']], field_name: str) -> None:
diff --git a/pydantic/validators.py b/pydantic/validators.py
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -1,5 +1,5 @@
 import re
-from collections import OrderedDict
+from collections import OrderedDict, deque
 from collections.abc import Hashable
 from datetime import date, datetime, time, timedelta
 from decimal import Decimal, DecimalException
@@ -10,6 +10,7 @@
     TYPE_CHECKING,
     Any,
     Callable,
+    Deque,
     Dict,
     FrozenSet,
     Generator,
@@ -245,6 +246,15 @@ def frozenset_validator(v: Any) -> FrozenSet[Any]:
         raise errors.FrozenSetError()
 
 
+def deque_validator(v: Any) -> Deque[Any]:
+    if isinstance(v, deque):
+        return v
+    elif sequence_like(v):
+        return deque(v)
+    else:
+        raise errors.DequeError()
+
+
 def enum_member_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum:
     try:
         enum_v = field.type_(v)
@@ -548,6 +558,7 @@ def check(self, config: Type['BaseConfig']) -> bool:
     (tuple, [tuple_validator]),
     (set, [set_validator]),
     (frozenset, [frozenset_validator]),
+    (deque, [deque_validator]),
     (UUID, [uuid_validator]),
     (Decimal, [decimal_validator]),
     (IPv4Interface, [ip_v4_interface_validator]),


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -3,12 +3,13 @@
 import re
 import sys
 import uuid
-from collections import OrderedDict
+from collections import OrderedDict, deque
 from datetime import date, datetime, time, timedelta
 from decimal import Decimal
 from enum import Enum, IntEnum
 from pathlib import Path
 from typing import (
+    Deque,
     Dict,
     FrozenSet,
     Iterable,
@@ -852,6 +853,7 @@ class Model(BaseModel):
         ((1, 2, '3'), [1, 2, '3']),
         ({1, 2, '3'}, list({1, 2, '3'})),
         ((i ** 2 for i in range(5)), [0, 1, 4, 9, 16]),
+        ((deque((1, 2, 3)), list(deque((1, 2, 3))))),
     ),
 )
 def test_list_success(value, result):
@@ -891,6 +893,7 @@ class Model(BaseModel):
         ((1, 2, '3'), (1, 2, '3')),
         ({1, 2, '3'}, tuple({1, 2, '3'})),
         ((i ** 2 for i in range(5)), (0, 1, 4, 9, 16)),
+        (deque([1, 2, 3]), (1, 2, 3)),
     ),
 )
 def test_tuple_success(value, result):
@@ -998,6 +1001,7 @@ class Model(BaseModel):
     (
         (int, [1, 2, 3], [1, 2, 3]),
         (int, (1, 2, 3), (1, 2, 3)),
+        (int, deque((1, 2, 3)), deque((1, 2, 3))),
         (float, {1.0, 2.0, 3.0}, {1.0, 2.0, 3.0}),
         (Set[int], [{1, 2}, {3, 4}, {5, 6}], [{1, 2}, {3, 4}, {5, 6}]),
         (Tuple[int, str], ((1, 'a'), (2, 'b'), (3, 'c')), ((1, 'a'), (2, 'b'), (3, 'c'))),
@@ -2288,15 +2292,22 @@ class FrozenSetModel(BaseModel):
     assert object_under_test.set == test_set
 
 
-def test_frozenset_field_conversion():
+@pytest.mark.parametrize(
+    'value,result',
+    [
+        ([1, 2, 3], frozenset([1, 2, 3])),
+        ({1, 2, 3}, frozenset([1, 2, 3])),
+        ((1, 2, 3), frozenset([1, 2, 3])),
+        (deque([1, 2, 3]), frozenset([1, 2, 3])),
+    ],
+)
+def test_frozenset_field_conversion(value, result):
     class FrozenSetModel(BaseModel):
         set: FrozenSet[int]
 
-    test_list = [1, 2, 3]
-    test_set = frozenset(test_list)
-    object_under_test = FrozenSetModel(set=test_list)
+    object_under_test = FrozenSetModel(set=value)
 
-    assert object_under_test.set == test_set
+    assert object_under_test.set == result
 
 
 def test_frozenset_field_not_convertible():
@@ -2361,3 +2372,82 @@ class Model(BaseModel):
     m = Model(size='1MB')
     with pytest.raises(errors.InvalidByteSizeUnit, match='byte unit'):
         m.size.to('bad_unit')
+
+
+def test_deque_success():
+    class Model(BaseModel):
+        v: deque
+
+    assert Model(v=[1, 2, 3]).v == deque([1, 2, 3])
+
+
+@pytest.mark.parametrize(
+    'cls,value,result',
+    (
+        (int, [1, 2, 3], deque([1, 2, 3])),
+        (int, (1, 2, 3), deque((1, 2, 3))),
+        (int, deque((1, 2, 3)), deque((1, 2, 3))),
+        (float, {1.0, 2.0, 3.0}, deque({1.0, 2.0, 3.0})),
+        (Set[int], [{1, 2}, {3, 4}, {5, 6}], deque([{1, 2}, {3, 4}, {5, 6}])),
+        (Tuple[int, str], ((1, 'a'), (2, 'b'), (3, 'c')), deque(((1, 'a'), (2, 'b'), (3, 'c')))),
+        (str, [w for w in 'one two three'.split()], deque(['one', 'two', 'three'])),
+        (int, frozenset([1, 2, 3]), deque([1, 2, 3])),
+    ),
+)
+def test_deque_generic_success(cls, value, result):
+    class Model(BaseModel):
+        v: Deque[cls]
+
+    assert Model(v=value).v == result
+
+
+@pytest.mark.parametrize(
+    'cls,value,errors',
+    (
+        (int, [1, 'a', 3], [{'loc': ('v', 1), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}]),
+        (int, (1, 2, 'a'), [{'loc': ('v', 2), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}]),
+        (float, range(10), [{'loc': ('v',), 'msg': 'value is not a valid sequence', 'type': 'type_error.sequence'}]),
+        (float, ('a', 2.2, 3.3), [{'loc': ('v', 0), 'msg': 'value is not a valid float', 'type': 'type_error.float'}]),
+        (float, (1.1, 2.2, 'a'), [{'loc': ('v', 2), 'msg': 'value is not a valid float', 'type': 'type_error.float'}]),
+        (
+            Set[int],
+            [{1, 2}, {2, 3}, {'d'}],
+            [{'loc': ('v', 2, 0), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}],
+        ),
+        (
+            Tuple[int, str],
+            ((1, 'a'), ('a', 'a'), (3, 'c')),
+            [{'loc': ('v', 1, 0), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}],
+        ),
+        (
+            List[int],
+            [{'a': 1, 'b': 2}, [1, 2], [2, 3]],
+            [{'loc': ('v', 0), 'msg': 'value is not a valid list', 'type': 'type_error.list'}],
+        ),
+    ),
+)
+def test_deque_fails(cls, value, errors):
+    class Model(BaseModel):
+        v: Deque[cls]
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(v=value)
+    assert exc_info.value.errors() == errors
+
+
+def test_deque_model():
+    class Model2(BaseModel):
+        x: int
+
+    class Model(BaseModel):
+        v: Deque[Model2]
+
+    seq = [Model2(x=1), Model2(x=2)]
+    assert Model(v=seq).v == deque(seq)
+
+
+def test_deque_json():
+    class Model(BaseModel):
+        v: Deque[int]
+
+    assert Model(v=deque((1, 2, 3))).json() == '{"v": [1, 2, 3]}'
diff --git a/tests/test_validators.py b/tests/test_validators.py
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1,3 +1,4 @@
+from collections import deque
 from datetime import datetime
 from itertools import product
 from typing import Dict, List, Optional, Tuple
@@ -56,6 +57,19 @@ class Model(BaseModel):
     assert Model(a=(6,)).a == frozenset({6})
 
 
+def test_deque_validation():
+    class Model(BaseModel):
+        a: deque
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(a='snap')
+    assert exc_info.value.errors() == [{'loc': ('a',), 'msg': 'value is not a valid deque', 'type': 'type_error.deque'}]
+    assert Model(a={1, 2, 3}).a == deque([1, 2, 3])
+    assert Model(a=deque({1, 2, 3})).a == deque([1, 2, 3])
+    assert Model(a=[4, 5]).a == deque([4, 5])
+    assert Model(a=(6,)).a == deque([6])
+
+
 def test_validate_whole():
     class Model(BaseModel):
         a: List[int]