Skip to content

Commit

Permalink
Fix ReturnDict and ReturnList __init__ signatures (#452)
Browse files Browse the repository at this point in the history
* Made serializer argument for ReturnList and ReturnDict kw only.

Copied and integrated init type stubs from typeshed for ReturnList and
ReturnDict.

* added tests for ReturnList and ReturnDict init signatures

* Make ReturnDict and ReturnList generic.

Added source reference of copied __init__ definitions.
Added tests for copy and __reduce__ methods.

---------

Co-authored-by: Jakob Moosbrugger <[email protected]>
  • Loading branch information
moosbruggerj authored Aug 3, 2023
1 parent e0e9e1f commit 591e5bf
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 9 deletions.
57 changes: 48 additions & 9 deletions rest_framework-stubs/utils/serializer_helpers.pyi
Original file line number Diff line number Diff line change
@@ -1,20 +1,59 @@
from collections.abc import Iterator, MutableMapping
from typing import Any
from collections.abc import Iterable, Iterator, MutableMapping
from typing import Any, Generic, TypeVar, overload

from _typeshed import SupportsKeysAndGetItem
from rest_framework.exceptions import ErrorDetail
from rest_framework.fields import Field
from rest_framework.serializers import BaseSerializer

class ReturnDict(dict):
_T = TypeVar("_T")
_VT = TypeVar("_VT")
_KT = TypeVar("_KT")

class ReturnDict(dict[_KT, _VT], Generic[_KT, _VT]):
serializer: BaseSerializer
def __init__(self, serializer: BaseSerializer = ..., *args, **kwargs): ...
def copy(self) -> ReturnDict: ...
def __reduce__(self) -> tuple[dict, tuple[dict]]: ...
# Copied from https://github.com/python/typeshed/blob/main/stdlib/builtins.pyi `class dict`
@overload
def __init__(self, *, serializer: BaseSerializer) -> None: ...
@overload
def __init__(self: ReturnDict[str, _VT], *, serializer: BaseSerializer, **kwargs: _VT) -> None: ...
@overload
def __init__(self, __map: SupportsKeysAndGetItem[_KT, _VT], *, serializer: BaseSerializer) -> None: ...
@overload
def __init__(
self: ReturnDict[str, _VT],
__map: SupportsKeysAndGetItem[str, _VT],
*,
serializer: BaseSerializer,
**kwargs: _VT
) -> None: ...
@overload
def __init__(self, __iterable: Iterable[tuple[_KT, _VT]], *, serializer: BaseSerializer) -> None: ...
@overload
def __init__(
self: ReturnDict[str, _VT], __iterable: Iterable[tuple[str, _VT]], *, serializer: BaseSerializer, **kwargs: _VT
) -> None: ...
# Next two overloads are for dict(string.split(sep) for string in iterable)
# Cannot be Iterable[Sequence[_T]] or otherwise dict(["foo", "bar", "baz"]) is not an error
@overload
def __init__(
self: ReturnDict[str, str], __iterable: Iterable[list[str]], *, serializer: BaseSerializer
) -> None: ...
@overload
def __init__(
self: ReturnDict[bytes, bytes], __iterable: Iterable[list[bytes]], *, serializer: BaseSerializer
) -> None: ...
def copy(self) -> ReturnDict[_KT, _VT]: ...
def __reduce__(self) -> tuple[type[dict[_KT, _VT]], tuple[dict[_KT, _VT]]]: ...

class ReturnList(list):
class ReturnList(list[_T], Generic[_T]):
serializer: BaseSerializer
def __init__(self, serializer: BaseSerializer = ..., *args, **kwargs): ...
def __reduce__(self) -> tuple[dict, tuple[dict]]: ...
# Copied from https://github.com/python/typeshed/blob/main/stdlib/builtins.pyi `class list`
@overload
def __init__(self, *, serializer: BaseSerializer) -> None: ...
@overload
def __init__(self, __iterable: Iterable[_T], *, serializer: BaseSerializer) -> None: ...
def __reduce__(self) -> tuple[type[list[_T]], tuple[list[_T]]]: ...

class BoundField:
"""
Expand Down
153 changes: 153 additions & 0 deletions tests/typecheck/test_serializers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,156 @@
@cached_property
def fields(self) -> BindingDict:
return super().fields
- case: test_return_list
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnList
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ReturnList([], serializer=self)
ReturnList((), serializer=self)
ReturnList([{}], serializer=self)
ReturnList(serializer=self)
ret = ReturnList(["1"], serializer=self)
reveal_type(ret) # N: Revealed type is "rest_framework.utils.serializer_helpers.ReturnList[builtins.str]"
- case: test_return_list_reduce
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnList
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ret = ReturnList(["1"], serializer=self)
callable, args = ret.__reduce__()
reveal_type(callable(*args)) # N: Revealed type is "builtins.list[builtins.str]"
- case: test_return_list_serializer_argument_is_kw_only
parametrized:
- arg: ""
err: No overload variant of "ReturnList" matches argument type "TestSerializer"
- arg: "[],"
err: No overload variant of "ReturnList" matches argument types "List[<nothing>]", "TestSerializer"
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnList
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ReturnList({{ arg }} self)
out: |
main:6: error: {{ err }}
main:6: note: Possible overload variants:
main:6: note: def [_T] ReturnList(self, *, serializer: BaseSerializer[Any]) -> ReturnList[_T]
main:6: note: def [_T] ReturnList(self, Iterable[_T], /, *, serializer: BaseSerializer[Any]) -> ReturnList[_T]
- case: test_return_list_serializer_is_required
parametrized:
- arg: ""
err: All overload variants of "ReturnList" require at least one argument
- arg: "[]"
err: No overload variant of "ReturnList" matches argument type "List[<nothing>]"
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnList
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ReturnList({{ arg }})
out: |
main:6: error: {{ err }}
main:6: note: Possible overload variants:
main:6: note: def [_T] ReturnList(self, *, serializer: BaseSerializer[Any]) -> ReturnList[_T]
main:6: note: def [_T] ReturnList(self, Iterable[_T], /, *, serializer: BaseSerializer[Any]) -> ReturnList[_T]
- case: test_return_dict
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict
from typing import Mapping
class TestSerializer(serializers.Serializer):
def test(self) -> None:
input: Mapping[str, str] = {}
ReturnDict({}, serializer=self)
ReturnDict([("a", "a")], serializer=self)
ReturnDict(input, serializer=self)
iterable = ""
sep = " "
ReturnDict((string.split(sep) for string in iterable), serializer=self)
ReturnDict(serializer=self)
ret = ReturnDict({"a": "a"}, serializer=self)
reveal_type(ret) # N: Revealed type is "rest_framework.utils.serializer_helpers.ReturnDict[builtins.str, builtins.str]"
copy = ret.copy()
reveal_type(copy) # N: Revealed type is "rest_framework.utils.serializer_helpers.ReturnDict[builtins.str, builtins.str]"
- case: test_return_dict_reduce
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ret = ReturnDict({"a": "1"}, serializer=self)
callable, args = ret.__reduce__()
reveal_type(callable(*args)) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
- case: test_return_dict_serializer_argument_is_kw_only
parametrized:
- arg: ""
err: No overload variant of "ReturnDict" matches argument type "TestSerializer"
- arg: "{},"
err: No overload variant of "ReturnDict" matches argument types "Dict[<nothing>, <nothing>]", "TestSerializer"
- arg: "[],"
err: No overload variant of "ReturnDict" matches argument types "List[<nothing>]", "TestSerializer"
- arg: "[('a', 'a')],"
err: No overload variant of "ReturnDict" matches argument types "List[Tuple[str, str]]", "TestSerializer"
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ReturnDict({{ arg }} self)
out: |
main:6: error: {{ err }}
main:6: note: Possible overload variants:
main:6: note: def [_KT, _VT] ReturnDict(self, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, SupportsKeysAndGetItem[_KT, _VT], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, SupportsKeysAndGetItem[str, _VT], /, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[Tuple[_KT, _VT]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[Tuple[str, _VT]], /, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[List[str]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[str, str]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[List[bytes]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[bytes, bytes]
- case: test_return_dict_serializer_is_required
parametrized:
- arg: ""
err: All overload variants of "ReturnDict" require at least one argument
- arg: "{}"
err: No overload variant of "ReturnDict" matches argument type "Dict[<nothing>, <nothing>]"
- arg: "[]"
err: No overload variant of "ReturnDict" matches argument type "List[<nothing>]"
- arg: "[('a', 'a')]"
err: No overload variant of "ReturnDict" matches argument type "List[Tuple[str, str]]"
main: |
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import ReturnDict
class TestSerializer(serializers.Serializer):
def test(self) -> None:
ReturnDict({{ arg }})
out: |
main:6: error: {{ err }}
main:6: note: Possible overload variants:
main:6: note: def [_KT, _VT] ReturnDict(self, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, SupportsKeysAndGetItem[_KT, _VT], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, SupportsKeysAndGetItem[str, _VT], /, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[Tuple[_KT, _VT]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[_KT, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[Tuple[str, _VT]], /, *, serializer: BaseSerializer[Any], **kwargs: _VT) -> ReturnDict[str, _VT]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[List[str]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[str, str]
main:6: note: def [_KT, _VT] ReturnDict(self, Iterable[List[bytes]], /, *, serializer: BaseSerializer[Any]) -> ReturnDict[bytes, bytes]

0 comments on commit 591e5bf

Please sign in to comment.