8000 Adds type-test for callbacks · dry-python/classes@d9292a2 · GitHub
[go: up one dir, main page]

Skip to content

Commit d9292a2

Browse files
committed
Adds type-test for callbacks
1 parent 04fd58e commit d9292a2

File tree

7 files changed

+74
-20
lines changed

7 files changed

+74
-20
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Smart, pythonic, ad-hoc, typed polymorphism for Python.
3232
pip install classes
3333
```
3434

35-
You might also want to [configure](https://classes.readthedocs.io/en/latest/pages/container.html#type-safety)
35+
You also need to [configure](https://classes.readthedocs.io/en/latest/pages/container.html#type-safety)
3636
`mypy` correctly and install our plugin
3737
to fix [this existing issue](https://github.com/python/mypy/issues/3157):
3838

@@ -43,6 +43,8 @@ plugins =
4343
classes.contrib.mypy.typeclass_plugin
4444
```
4545

46+
**Without this step**, your project will report type-violations here and there.
47+
4648
We also recommend to use the same `mypy` settings [we use](https://github.com/wemake-services/wemake-python-styleguide/blob/master/styles/mypy.toml).
4749

4850
Make sure you know how to get started, [check out our docs](https://classes.readthedocs.io/en/latest/)!

classes/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
so mypy's ``implicit_reexport`` rule will be happy.
88
"""
99

10-
from classes.typeclass import TypeClass as TypeClass
1110
from classes.typeclass import typeclass as typeclass

classes/contrib/mypy/typeclass_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ def _filter_out_unified_types(self, type_) -> bool:
9696
class _TypedDecoratorPlugin(Plugin):
9797
def get_method_signature_hook(self, fullname: str):
9898
"""Here we fix the calling method types to accept only valid types."""
99-
if fullname == 'classes.typeclass.TypeClass.__call__':
99+
if fullname == 'classes.typeclass._TypeClass.__call__':
100100
return _adjust_call_signature
101101
return None
102102

103103
def get_method_hook(self, fullname: str):
104104
"""Here we adjust the typeclass with new allowed types."""
105-
if fullname == 'classes.typeclass.TypeClass.instance':
105+
if fullname == 'classes.typeclass._TypeClass.instance':
106106
return _AdjustInstanceSignature().instance
107107
return None
108108

classes/typeclass.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
from typing_extensions import Literal
66

7-
_TypeClassType = TypeVar('_TypeClassType', contravariant=True)
7+
_TypeClassType = TypeVar('_TypeClassType')
88
_ReturnType = TypeVar('_ReturnType')
99
_CallbackType = TypeVar('_CallbackType')
1010
_InstanceType = TypeVar('_InstanceType')
1111

1212

13-
class TypeClass(Generic[_TypeClassType, _ReturnType, _CallbackType]):
13+
class _TypeClass(Generic[_TypeClassType, _ReturnType, _CallbackType]):
1414
"""
1515
That's how we represent typeclasses.
1616
@@ -20,7 +20,7 @@ class TypeClass(Generic[_TypeClassType, _ReturnType, _CallbackType]):
2020
.. code:: python
2121
2222
>>> from typing import Callable
23-
>>> from classes import TypeClass, typeclass
23+
>>> from classes import typeclass
2424
>>> @typeclass
2525
... def used(instance, other: int) -> int:
2626
... '''Example typeclass to be used later.'''
@@ -30,7 +30,7 @@ class TypeClass(Generic[_TypeClassType, _ReturnType, _CallbackType]):
3030
... return instance + other
3131
...
3232
>>> def accepts_typeclass(
33-
... callback: TypeClass[int, int, Callable[[int, int], int]],
33+
... callback: Callable[[int, int], int],
3434
... ) -> int:
3535
... return callback(1, 3)
3636
...
@@ -92,6 +92,13 @@ def __call__(
9292
We don't guarantee the order of types inside groups.
9393
Use correct types, do not rely on our order.
9494
95+
Callbacks
96+
~~~~~~~~~
97+
98+
Since, we define ``__call__`` method for this class,
99+
it can be used and typechecked everywhere,
100+
where a regular ``Callable`` is expected.
101+
95102
"""
96103
instance_type = type(instance)
97104
implementation = self._instances.get(instance_type, None)
@@ -109,7 +116,7 @@ def __call__(
109116
)
110117

111118
@overload
112-
def instance( # noqa: D102
119+
def instance(
113120
self,
114121
type_argument: Type[_InstanceType],
115122
*,
@@ -121,7 +128,7 @@ def instance( # noqa: D102
121128
...
122129

123130
@overload
124-
def instance( # noqa: D102
131+
def instance(
125132
self,
126133
type_argument,
127134
*,
@@ -159,7 +166,7 @@ def typeclass(
159166
signature: _CallbackType,
160167
# By default `_TypeClassType` and `_ReturnType` are `nothing`,
161168
# but we enhance them via mypy plugin later:
162-
) -> TypeClass[_TypeClassType, _ReturnType, _CallbackType]:
169+
) -> _TypeClass[_TypeClassType, _ReturnType, _CallbackType]:
163170
"""
164171
Function to define typeclasses.
165172
@@ -318,4 +325,4 @@ def typeclass(
318325
Remember, that generic protocols have the same limitation as generic types.
319326
320327
"""
321-
return TypeClass(signature)
328+
return _TypeClass(signature)

tests/test_typeclass/test_callback.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Callable
44

5-
from classes import TypeClass, typeclass
5+
from classes import typeclass
66

77

88
@typeclass
@@ -17,7 +17,7 @@ def _example_str(instance: str) -> int:
1717

1818
def _callback(
1919
instance: str,
20-
callback: TypeClass[str, int, Callable[[str], int]],
20+
callback: Callable[[str], int],
2121
) -> int:
2222
return callback(instance)
2323

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
- case: typeclass_callback_correct
2+
disable_cache: true
3+
main: |
4+
from typing import Callable
5+
from classes import typeclass
6+
7+
@typeclass
8+
def example(instance, attr: bool) -> bool:
9+
...
10+
11+
@example.instance(int)
12+
@example.instance(float)
13+
def _example_int_float(intance, attr: bool) -> bool:
14+
...
15+
16+
17+
def accepts_typeclass(callback: Callable[[int, bool], bool]) -> bool:
18+
return callback(1, True)
19+
20+
reveal_type(accepts_typeclass(example)) # N: Revealed type is 'builtins.bool'
21+
22+
23+
- case: typeclass_callback_wrong
24+
disable_cache: true
25+
main: |
26+
from typing import Callable
27+
from classes import typeclass
28+
29+
@typeclass
30+
def example(instance, attr: bool) -> bool:
31+
...
32+
33+
@example.instance(int)
34+
@example.instance(float)
35+
def _example_int_float(intance, attr: bool) -> bool:
36+
...
37+
38+
39+
def accepts_typeclass(callback: Callable[[str, bool], bool]) -> bool:
40+
return callback('a', True)
41+
42+
reveal_type(accepts_typeclass(example))
43+
out: |
44+
main:17: error: Argument 1 to "accepts_typeclass" has incompatible type "_TypeClass[Union[int, float], bool, Callable[[int, bool], bool]]"; expected "Callable[[str, bool], bool]"
45+
main:17: note: "_TypeClass[Union[int, float], bool, Callable[[int, bool], bool]].__call__" has type "Callable[[Arg(Union[int, float], 'instance'), VarArg(Any), KwArg(Any)], bool]"
46+
main:17: note: Revealed type is 'builtins.bool'

typesafety/test_typeclass/test_typeclass.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def example(instance, arg: str, other: int, *, attr: bool) -> bool:
88
...
99
10-
reveal_type(example) # N: Revealed type is 'classes.typeclass.TypeClass[Any, builtins.bool, def (instance: Any, arg: builtins.str, other: builtins.int, *, attr: builtins.bool) -> builtins.bool]'
10+
reveal_type(example) # N: Revealed type is 'classes.typeclass._TypeClass[Any, builtins.bool, def (instance: Any, arg: builtins.str, other: builtins.int, *, attr: builtins.bool) -> builtins.bool]'
1111
1212
1313
- case: typeclass_instance
@@ -23,7 +23,7 @@
2323
def _example_str(instance: str, arg: str) -> bool:
2424
...
2525
26-
reveal_type(example) # N: Revealed type is 'classes.typeclass.TypeClass[builtins.str*, builtins.bool, def (builtins.str*, arg: builtins.str) -> builtins.bool]'
26+
reveal_type(example) # N: Revealed type is 'classes.typeclass._TypeClass[builtins.str*, builtins.bool, def (builtins.str*, arg: builtins.str) -> builtins.bool]'
2727
2828
2929
- case: typeclass_instance_union
@@ -44,7 +44,7 @@
4444
def _example_str(instance: str, arg: str) -> bool:
4545
...
4646
47-
reveal_type(example) # N: Revealed type is 'classes.typeclass.TypeClass[Union[builtins.str*, builtins.int*, builtins.float*], builtins.bool, def (builtins.str*, arg: builtins.str) -> builtins.bool]'
47+
reveal_type(example) # N: Revealed type is 'classes.typeclass._TypeClass[Union[builtins.str*, builtins.int*, builtins.float*], builtins.bool, def (builtins.str*, arg: builtins.str) -> builtins.bool]'
4848
4949
5050
- case: typeclass_incorrect_instance_callback1
@@ -63,7 +63,7 @@
6363
reveal_type(example)
6464
out: |
6565
main:7: error: Argument 1 has incompatible type "Callable[[str, str], bool]"; expected "Callable[[int, str], bool]"
66-
main:11: note: Revealed type is 'classes.typeclass.TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'
66+
main:11: note: Revealed type is 'classes.typeclass._TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'
6767
6868
6969
- case: typeclass_incorrect_instance_callback2
@@ -82,7 +82,7 @@
8282
reveal_type(example)
8383
out: |
8484
main:7: error: Argument 1 has incompatible type "Callable[[int], bool]"; expected "Callable[[int, str], bool]"
85-
main:11: note: Revealed type is 'classes.typeclass.TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'
85+
main:11: note: Revealed type is 'classes.typeclass._TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'
8686
8787
8888
- case: typeclass_incorrect_instance_callback3
@@ -101,4 +101,4 @@
101101
reveal_type(example)
102102
out: |
103103
main:7: error: Argument 1 has incompatible type "Callable[[Any, int], bool]"; expected "Callable[[int, str], bool]"
104-
main:11: note: Revealed type is 'classes.typeclass.TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'
104+
main:11: note: Revealed type is 'classes.t 335C ypeclass._TypeClass[builtins.int*, builtins.bool, def (builtins.int*, arg: builtins.str) -> builtins.bool]'

0 commit comments

Comments
 (0)
0