Skip to content

Commit ccd012e

Browse files
authored
Merge pull request #39 from pomponchik/develop
0.0.28
2 parents 86f330d + 5111766 commit ccd012e

File tree

12 files changed

+329
-102
lines changed

12 files changed

+329
-102
lines changed

cantok/tokens/abstract/abstract_token.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None:
1818
from cantok import DefaultToken
1919

2020
self.cached_report: Optional[CancellationReport] = None
21-
self.tokens: List[AbstractToken] = [token for token in tokens if not isinstance(token, DefaultToken)]
2221
self._cancelled: bool = cancelled
22+
self.tokens: List[AbstractToken] = []
23+
24+
for token in tokens:
25+
if isinstance(token, DefaultToken):
26+
pass
27+
else:
28+
self.tokens.append(token)
29+
2330
self.lock: RLock = RLock()
2431

2532
def __repr__(self) -> str:
@@ -54,17 +61,28 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken':
5461
if not isinstance(item, AbstractToken):
5562
raise TypeError('Cancellation Token can only be combined with another Cancellation Token.')
5663

57-
from cantok import SimpleToken
64+
from cantok import SimpleToken, DefaultToken
5865

5966
nested_tokens = []
67+
container_token: Optional[AbstractToken] = None
6068

6169
for token in self, item:
62-
if isinstance(token, SimpleToken) and getrefcount(token) < 6:
70+
if token._cancelled:
71+
return SimpleToken(cancelled=True)
72+
elif isinstance(token, SimpleToken) and getrefcount(token) < 6:
6373
nested_tokens.extend(token.tokens)
74+
elif isinstance(token, DefaultToken):
75+
pass
76+
elif not isinstance(token, SimpleToken) and getrefcount(token) < 6 and container_token is None:
77+
container_token = token
6478
else:
6579
nested_tokens.append(token)
6680

67-
return SimpleToken(*nested_tokens)
81+
if container_token is None:
82+
return SimpleToken(*nested_tokens)
83+
else:
84+
container_token.tokens.extend(nested_tokens)
85+
return container_token
6886

6987
def __bool__(self) -> bool:
7088
return self.keep_on()

cantok/tokens/condition_token.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,23 @@ def run_function(self) -> bool:
5757
return result
5858

5959
def text_representation_of_superpower(self) -> str:
60-
return repr(self.function)
60+
result = self.function.__name__
61+
62+
if result == '<lambda>':
63+
return 'λ'
64+
65+
return result
6166

6267
def get_extra_kwargs(self) -> Dict[str, Any]:
63-
return {
64-
'suppress_exceptions': self.suppress_exceptions,
65-
'default': self.default,
66-
}
68+
result = {}
69+
70+
if not self.suppress_exceptions:
71+
result['suppress_exceptions'] = self.suppress_exceptions
72+
73+
if self.default is not False:
74+
result['default'] = self.default # type: ignore[assignment]
75+
76+
return result
6777

6878
def get_superpower_exception_message(self) -> str:
6979
return 'The cancellation condition was satisfied.'

cantok/tokens/counter_token.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,40 @@ def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False
1212
if counter < 0:
1313
raise ValueError('The counter must be greater than or equal to zero.')
1414

15-
self.counter = counter
1615
self.initial_counter = counter
1716
self.direct = direct
1817
self.rollback_if_nondirect_polling = self.direct
1918

19+
counter_bag = {'counter': counter}
20+
self.counter_bag = counter_bag
21+
2022
def function() -> bool:
21-
with self.lock:
22-
if not self.counter:
23+
with counter_bag['lock']: # type: ignore[attr-defined]
24+
if not counter_bag['counter']:
2325
return True
24-
self.counter -= 1
26+
counter_bag['counter'] -= 1
2527
return False
2628

2729
super().__init__(function, *tokens, cancelled=cancelled)
2830

31+
self.counter_bag['lock'] = self.lock # type: ignore[assignment]
32+
33+
@property
34+
def counter(self) -> int:
35+
return self.counter_bag['counter']
36+
2937
def superpower_rollback(self, superpower_data: Dict[str, Any]) -> None:
30-
self.counter = superpower_data['counter']
38+
self.counter_bag['counter'] = superpower_data['counter']
3139

3240
def text_representation_of_superpower(self) -> str:
33-
return str(self.counter)
41+
return str(self.counter_bag['counter'])
3442

3543
def get_extra_kwargs(self) -> Dict[str, Any]:
36-
return {
37-
'direct': self.direct,
38-
}
44+
if not self.direct:
45+
return {
46+
'direct': self.direct,
47+
}
48+
return {}
3949

4050
def get_superpower_data(self) -> Dict[str, Any]:
4151
return {'counter': self.counter}

docs/types_of_tokens/SimpleToken.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,4 @@ token.cancel()
99
print(token.cancelled) #> True
1010
```
1111

12-
`SimpleToken` is also implicitly generated by the operation of summing two other tokens:
13-
14-
```python
15-
from cantok import CounterToken, TimeoutToken
16-
17-
print(repr(CounterToken(5) + TimeoutToken(5)))
18-
#> SimpleToken(CounterToken(5, direct=True), TimeoutToken(5))
19-
```
20-
2112
There is not much more to tell about it if you have read [the story](../what_are_tokens/in_general.md) about tokens in general.

docs/what_are_tokens/summation.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
Any tokens can be summed up among themselves. The summation operation generates another [`SimpleToken`](../types_of_tokens/SimpleToken.md) that includes the previous 2:
1+
Tokens can be summed using the operator `+`:
22

33
```python
4-
from cantok import SimpleToken, TimeoutToken
5-
6-
print(repr(SimpleToken() + TimeoutToken(5)))
7-
#> SimpleToken(TimeoutToken(5))
4+
first_token = TimeoutToken(5)
5+
second_token = ConditionToken(lambda: True)
6+
print(repr(first_token + second_token))
7+
#> SimpleToken(TimeoutToken(5), ConditionToken(λ))
88
```
99

1010
This feature is convenient to use if your function has received a token with certain restrictions and wants to throw it into other called functions, imposing additional restrictions:
@@ -17,3 +17,23 @@ def function(token: AbstractToken):
1717
another_function(token + TimeoutToken(5)) # Imposes an additional restriction on the function being called: work for no more than 5 seconds. At the same time, it does not know anything about what restrictions were imposed earlier.
1818
...
1919
```
20+
21+
The token summation operation always generates a new token. If at least one of the operand tokens is canceled, the sum will also be canceled. It is also guaranteed that the cancellation of this token does not lead to the cancellation of operands. That is, the sum of two tokens always behaves as if it were a [`SimpleToken`](../types_of_tokens/SimpleToken.md) in which both operands were [nested](embedding.md). However, it is difficult to say exactly which token will result from summation, since the library strives to minimize the generated graph of tokens for the sake of performance.
22+
23+
You may notice that some tokens disappear altogether during summation:
24+
25+
```python
26+
print(repr(SimpleToken() + TimeoutToken(5)))
27+
#> TimeoutToken(5)
28+
print(repr(SimpleToken(cancelled=True) + TimeoutToken(5)))
29+
#> SimpleToken(cancelled=True)
30+
```
31+
32+
In addition, you can not be afraid to sum more than 2 tokens - this does not generate anything superfluous:
33+
34+
```python
35+
print(repr(TimeoutToken(5) + ConditionToken(lambda: False) + CounterToken(23)))
36+
#> TimeoutToken(5, ConditionToken(λ), CounterToken(23))
37+
```
38+
39+
In fact, there are quite a few effective ways to optimize the token addition operation that are implemented in the library. This operation is pretty well optimized, so it is recommended in all cases when you need to combine the constraints of different tokens in one.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "cantok"
7-
version = "0.0.27"
7+
version = "0.0.28"
88
authors = [
99
{ name="Evgeniy Blinov", email="zheni-b@yandex.ru" },
1010
]

tests/units/tokens/abstract/test_abstract_token.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def test_str(token_fabric):
142142
'second_token_fabric',
143143
ALL_TOKENS_FABRICS,
144144
)
145-
def test_add_tokens(first_token_fabric, second_token_fabric):
145+
def test_add_not_temp_tokens(first_token_fabric, second_token_fabric):
146146
first_token = first_token_fabric()
147147
second_token = second_token_fabric()
148148

@@ -154,6 +154,83 @@ def test_add_tokens(first_token_fabric, second_token_fabric):
154154
assert tokens_sum.tokens[1] is second_token
155155

156156

157+
@pytest.mark.parametrize(
158+
['first_token_class', 'first_arguments'],
159+
[
160+
(TimeoutToken, [15]),
161+
(ConditionToken, [lambda: False]),
162+
(CounterToken, [15]),
163+
],
164+
)
165+
@pytest.mark.parametrize(
166+
['second_token_class', 'second_arguments'],
167+
[
168+
(TimeoutToken, [15]),
169+
(ConditionToken, [lambda: False]),
170+
(CounterToken, [15]),
171+
],
172+
)
173+
def test_add_temp_tokens(first_token_class, second_token_class, first_arguments, second_arguments):
174+
tokens_sum = first_token_class(*first_arguments) + second_token_class(*second_arguments)
175+
176+
assert isinstance(tokens_sum, first_token_class)
177+
assert len(tokens_sum.tokens) == 1
178+
assert isinstance(tokens_sum.tokens[0], second_token_class)
179+
assert len(tokens_sum.tokens[0].tokens) == 0
180+
181+
182+
@pytest.mark.parametrize(
183+
['first_token_class', 'first_arguments'],
184+
[
185+
(TimeoutToken, [15]),
186+
(ConditionToken, [lambda: False]),
187+
(CounterToken, [15]),
188+
],
189+
)
190+
@pytest.mark.parametrize(
191+
['second_token_class', 'second_arguments'],
192+
[
193+
(TimeoutToken, [15]),
194+
(ConditionToken, [lambda: False]),
195+
(CounterToken, [15]),
196+
],
197+
)
198+
def test_add_not_temp_token_and_temp_token(first_token_class, second_token_class, first_arguments, second_arguments):
199+
first_token = first_token_class(*first_arguments)
200+
tokens_sum = first_token + second_token_class(*second_arguments)
201+
202+
assert isinstance(tokens_sum, second_token_class)
203+
assert len(tokens_sum.tokens) == 1
204+
assert isinstance(tokens_sum.tokens[0], first_token_class)
205+
assert len(tokens_sum.tokens[0].tokens) == 0
206+
207+
208+
@pytest.mark.parametrize(
209+
['first_token_class', 'first_arguments'],
210+
[
211+
(TimeoutToken, [15]),
212+
(ConditionToken, [lambda: False]),
213+
(CounterToken, [15]),
214+
],
215+
)
216+
@pytest.mark.parametrize(
217+
['second_token_class', 'second_arguments'],
218+
[
219+
(TimeoutToken, [15]),
220+
(ConditionToken, [lambda: False]),
221+
(CounterToken, [15]),
222+
],
223+
)
224+
def test_add_temp_token_and_not_temp_token(first_token_class, second_token_class, first_arguments, second_arguments):
225+
second_token = second_token_class(*second_arguments)
226+
tokens_sum = first_token_class(*first_arguments) + second_token
227+
228+
assert isinstance(tokens_sum, first_token_class)
229+
assert len(tokens_sum.tokens) == 1
230+
assert isinstance(tokens_sum.tokens[0], second_token_class)
231+
assert len(tokens_sum.tokens[0].tokens) == 0
232+
233+
157234
@pytest.mark.parametrize(
158235
'first_token_fabric',
159236
ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER,
@@ -669,3 +746,40 @@ def test_superpower_is_more_important_than_cache(first_token_fabric, second_toke
669746
assert isinstance(report, CancellationReport)
670747
assert report.from_token is token
671748
assert report.cause == CancelCause.CANCELLED
749+
750+
751+
@pytest.mark.parametrize(
752+
'token_fabric',
753+
ALL_TOKENS_FABRICS,
754+
)
755+
def test_just_neste_temp_simple_token_to_another_token(token_fabric):
756+
token = token_fabric(SimpleToken())
757+
758+
assert len(token.tokens) == 1
759+
assert isinstance(token.tokens[0], SimpleToken)
760+
assert token
761+
762+
763+
@pytest.mark.parametrize(
764+
'token_fabric',
765+
ALL_TOKENS_FABRICS,
766+
)
767+
def test_any_token_plus_temp_cancelled_simple_token_gives_cancelled_simple_token(token_fabric):
768+
token = token_fabric() + SimpleToken(cancelled=True)
769+
770+
assert isinstance(token, SimpleToken)
771+
assert len(token.tokens) == 0
772+
assert not token
773+
774+
775+
@pytest.mark.parametrize(
776+
'token_fabric',
777+
ALL_TOKENS_FABRICS,
778+
)
779+
def test_any_token_plus_cancelled_simple_token_gives_cancelled_simple_token(token_fabric):
780+
simple_token = SimpleToken(cancelled=True)
781+
token = token_fabric() + simple_token
782+
783+
assert isinstance(token, SimpleToken)
784+
assert len(token.tokens) == 0
785+
assert not token

0 commit comments

Comments
 (0)