Skip to content

Commit 71e89e7

Browse files
committed
Adds more tests
1 parent c3d8f10 commit 71e89e7

File tree

5 files changed

+342
-4
lines changed

5 files changed

+342
-4
lines changed

classes/_typeclass.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,10 @@ def __call__(
415415
try:
416416
impl = self._dispatch_cache[instance_type]
417417
except KeyError:
418-
impl = self._dispatch(instance, instance_type) # type: ignore
419-
if impl is None:
420-
impl = self._default_implemntation # type: ignore
418+
impl = self._dispatch(
419+
instance,
420+
instance_type,
421+
) or self._default_implementation
421422
self._dispatch_cache[instance_type] = impl
422423
return impl(instance, *args, **kwargs)
423424

@@ -549,7 +550,7 @@ def _dispatch(self, instance, instance_type: type) -> Optional[Callable]:
549550

550551
return _find_impl(instance_type, self._instances)
551552

552-
def _default_implemntation(self, instance, *args, **kwargs) -> NoReturn:
553+
def _default_implementation(self, instance, *args, **kwargs) -> NoReturn:
553554
"""By default raises an exception."""
554555
raise NotImplementedError(
555556
'Missing matched typeclass instance for type: {0}'.format(

tests/test_typeclass/test_cache.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from abc import ABCMeta, abstractmethod
2+
3+
from classes import typeclass
4+
5+
6+
@typeclass
7+
def my_typeclass(instance) -> int:
8+
"""Example typeclass."""
9+
10+
11+
class _MyABC(object, metaclass=ABCMeta):
12+
@abstractmethod
13+
def get_number(self) -> int:
14+
"""Example abstract method."""
15+
16+
17+
class _MyConcete(_MyABC):
18+
def get_number(self) -> int:
19+
"""Concrete method."""
20+
return 1
21+
22+
23+
class _MyRegistered(object):
24+
def get_number(self) -> int:
25+
"""Would be registered in ``_MyABC`` later."""
26+
return 2
27+
28+
29+
def _my_int(instance: int) -> int:
30+
return instance
31+
32+
33+
def _my_abc(instance: _MyABC) -> int:
34+
return instance.get_number()
35+
36+
37+
def test_cache_invalidation(): # noqa: WPS218
38+
"""Ensures that cache invalidation for ABC types work correctly."""
39+
assert not my_typeclass._dispatch_cache # noqa: WPS437
40+
assert not my_typeclass._cache_token # noqa: WPS437
41+
42+
my_typeclass.instance(_MyABC)(_my_abc)
43+
assert not my_typeclass._dispatch_cache # noqa: WPS437
44+
assert my_typeclass._cache_token # noqa: WPS437
45+
46+
assert my_typeclass(_MyConcete()) == 1
47+
assert my_typeclass._dispatch_cache # noqa: WPS437
48+
49+
_MyABC.register(_MyRegistered)
50+
assert my_typeclass(_MyRegistered()) == 2 # type: ignore
51+
assert my_typeclass._dispatch_cache # noqa: WPS437

tests/test_typeclass/test_call.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import Sized
2+
3+
import pytest
4+
5+
from classes import typeclass
6+
7+
8+
@typeclass
9+
def my_len(instance) -> int:
10+
"""Returns a length of an object."""
11+
12+
13+
@my_len.instance(object)
14+
def _my_len_object(instance: object) -> int:
15+
return -1
16+
17+
18+
@my_len.instance(Sized, is_protocol=True)
19+
def _my_len_sized(instance: Sized) -> int:
20+
return 0
21+
22+
23+
@my_len.instance(list)
24+
def _my_len_list(instance: list) -> int:
25+
return 1
26+
27+
28+
@pytest.mark.parameterized(('data_type', 'expected'), [
29+
([], 1), # direct list call
30+
('', 0), # sized protocol
31+
(None, -1), # object fallback
32+
])
33+
def test_call_order(data_type, expected):
34+
"""Ensures that call order is correct."""
35+
assert my_len(data_type) == expected

tests/test_typeclass/test_repr.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from classes import AssociatedType, typeclass
2+
3+
4+
class MyType(AssociatedType):
5+
"""Docs for type."""
6+
7+
8+
@typeclass(MyType)
9+
def my_typeclass_with_type(instance) -> str:
10+
"""Docs."""
11+
12+
13+
@typeclass
14+
def my_typeclass(instance) -> str:
15+
"""Docs."""
16+
17+
18+
def test_str():
19+
"""Ensures that ``str`` is correct."""
20+
assert str(my_typeclass) == '<typeclass "my_typeclass">'
21+
assert str(
22+
my_typeclass_with_type,
23+
) == '<typeclass "my_typeclass_with_type": "MyType">'
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
- case: typeclass_call_all_arg_types
2+
disable_cache: false
3+
main: |
4+
from classes import typeclass
5+
6+
@typeclass
7+
def args(
8+
instance, /, regular, default=1, *args, kw, kw_default=2, **kwargs,
9+
) -> str:
10+
...
11+
12+
@args.instance(int)
13+
def _args_int(
14+
instance: int, /, regular, default=1, *args, kw, kw_default=2, **kwargs,
15+
) -> str:
16+
...
17+
18+
args(1, 2, 3, 4, 5, kw=6, kw_default=7, other=8)
19+
args(1, 2, kw=6)
20+
args(1, regular=2, kw=6)
21+
22+
23+
- case: typeclass_call_instance_named
24+
disable_cache: false
25+
main: |
26+
from classes import typeclass
27+
28+
@typeclass
29+
def args(instance) -> str:
30+
...
31+
32+
@args.instance(int)
33+
def _args_int(instance: int) -> str:
34+
...
35+
36+
args(instance=1)
37+
38+
39+
- case: typeclass_call_instance_variance
40+
disable_cache: false
41+
main: |
42+
from classes import typeclass
43+
44+
class A:
45+
...
46+
47+
class B(A):
48+
...
49+
50+
class C(B):
51+
...
52+
53+
@typeclass
54+
def some(instance) -> str:
55+
...
56+
57+
@some.instance(B)
58+
def _some_b(instance: B) -> str:
59+
...
60+
61+
some(A())
62+
some(B()) # ok
63+
some(C()) # ok
64+
out: |
65+
main:20: error: Argument 1 to "some" has incompatible type "A"; expected "B"
66+
67+
68+
- case: typeclass_call_generic_instance_variance
69+
disable_cache: false
70+
main: |
71+
from classes import typeclass
72+
from typing import TypeVar, Generic
73+
74+
X = TypeVar('X')
75+
76+
class A(Generic[X]):
77+
...
78+
79+
class B(A[X]):
80+
...
81+
82+
class C(B[X]):
83+
...
84+
85+
@typeclass
86+
def some(instance: B[X]) -> X:
87+
...
88+
89+
@some.instance(B)
90+
def _some_b(instance: B[X]) -> X:
91+
...
92+
93+
a: A[int]
94+
b: B[int]
95+
c: C[int]
96+
some(a)
97+
some(b) # ok
98+
some(c) # ok
99+
out: |
100+
main:26: error: Argument 1 to "some" has incompatible type "A[int]"; expected "B[<nothing>]"
101+
102+
103+
- case: typeclass_call_variance_union1
104+
disable_cache: false
105+
main: |
106+
from classes import typeclass
107+
from typing import Union
108+
109+
class A:
110+
...
111+
112+
class B(A):
113+
...
114+
115+
class C(B):
116+
...
117+
118+
@typeclass
119+
def some(instance) -> str:
120+
...
121+
122+
@some.instance(B)
123+
@some.instance(C)
124+
def _some_b(instance: Union[B, C]) -> str:
125+
...
126+
127+
some(A())
128+
some(B()) # ok
129+
some(C()) # ok
130+
out: |
131+
main:22: error: Argument 1 to "some" has incompatible type "A"; expected "Union[B, C]"
132+
133+
134+
- case: typeclass_call_variance_union2
135+
disable_cache: false
136+
main: |
137+
from classes import typeclass
138+
from typing import Union
139+
140+
class A:
141+
...
142+
143+
class B(A):
144+
...
145+
146+
class C(B):
147+
...
148+
149+
@typeclass
150+
def some(instance) -> str:
151+
...
152+
153+
@some.instance(A)
154+
@some.instance(B)
155+
def _some_b(instance: Union[A, B]) -> str:
156+
...
157+
158+
some(A()) # ok
159+
some(B()) # ok
160+
some(C()) # ok
161+
162+
163+
- case: typeclass_call_variance_union3
164+
disable_cache: false
165+
main: |
166+
from classes import typeclass
167+
from typing import Union
168+
169+
class A:
170+
...
171+
172+
class B(A):
173+
...
174+
175+
class C(B):
176+
...
177+
178+
@typeclass
179+
def some(instance) -> str:
180+
...
181+
182+
@some.instance(A)
183+
@some.instance(C)
184+
def _some_b(instance: Union[A, C]) -> str:
185+
...
186+
187+
some(A()) # ok
188+
some(B()) # ok
189+
some(C()) # ok
190+
191+
192+
- case: typeclass_call_variance_union4
193+
disable_cache: false
194+
main: |
195+
from classes import typeclass
196+
from typing import Union
197+
198+
class A:
199+
...
200+
201+
class B(A):
202+
...
203+
204+
class C(B):
205+
...
206+
207+
@typeclass
208+
def some(instance) -> str:
209+
...
210+
211+
@some.instance(A)
212+
@some.instance(B)
213+
@some.instance(C)
214+
def _some_b(instance: Union[A, B, C]) -> str:
215+
...
216+
217+
some(A()) # ok
218+
some(B()) # ok
219+
some(C()) # ok
220+
221+
ab: Union[A, B]
222+
ac: Union[A, C]
223+
bc: Union[B, C]
224+
abc: Union[A, B, C]
225+
some(ab) # ok
226+
some(ac) # ok
227+
some(bc) # ok
228+
some(abc) # ok

0 commit comments

Comments
 (0)