From ac68832209656d351d9d52600d1ed2447a1c7688 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 15:58:33 +0300 Subject: [PATCH 01/19] during the token sum operation, a container token is not created if there is a token without external links among the operands --- cantok/tokens/abstract/abstract_token.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index d29dbe6..5c3dd8a 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -57,14 +57,21 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': from cantok import SimpleToken nested_tokens = [] + container_token: Optional[AbstractToken] = None for token in self, item: if isinstance(token, SimpleToken) and getrefcount(token) < 6: nested_tokens.extend(token.tokens) + elif not isinstance(token, SimpleToken) and getrefcount(token) < 6 and container_token is None: + container_token = token else: nested_tokens.append(token) - return SimpleToken(*nested_tokens) + if container_token is None: + return SimpleToken(*nested_tokens) + else: + container_token.tokens.extend(nested_tokens) + return container_token def __bool__(self) -> bool: return self.keep_on() From 017057edad09e1d033b0d7a73335e3f5238d875c Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 17:30:31 +0300 Subject: [PATCH 02/19] fix for default tokens sums --- cantok/tokens/abstract/abstract_token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 5c3dd8a..3597fec 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -54,7 +54,7 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': if not isinstance(item, AbstractToken): raise TypeError('Cancellation Token can only be combined with another Cancellation Token.') - from cantok import SimpleToken + from cantok import SimpleToken, DefaultToken nested_tokens = [] container_token: Optional[AbstractToken] = None @@ -62,7 +62,7 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': for token in self, item: if isinstance(token, SimpleToken) and getrefcount(token) < 6: nested_tokens.extend(token.tokens) - elif not isinstance(token, SimpleToken) and getrefcount(token) < 6 and container_token is None: + elif not isinstance(token, SimpleToken) and not isinstance(token, DefaultToken) and getrefcount(token) < 6 and container_token is None: container_token = token else: nested_tokens.append(token) From a7a6f967b172b70304d278204c634679846e5c7f Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 18:54:10 +0300 Subject: [PATCH 03/19] all old tests fixed for the new optimization --- tests/units/tokens/test_condition_token.py | 23 ++++++++--------- tests/units/tokens/test_simple_token.py | 18 ++++++-------- tests/units/tokens/test_timeout_token.py | 29 +++++++++++----------- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 1126888..617a47d 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -345,9 +345,8 @@ def condition(): def test_quasitemp_condition_token_plus_temp_simple_token(): token = ConditionToken(lambda: False) + SimpleToken() - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], ConditionToken) + assert isinstance(token, ConditionToken) + assert len(token.tokens) == 0 def test_not_quasitemp_condition_token_plus_temp_simple_token(): @@ -364,11 +363,11 @@ def test_quasitemp_condition_token_plus_not_temp_simple_token(): simple_token = SimpleToken() token = ConditionToken(lambda: False) + simple_token - assert isinstance(token, SimpleToken) + assert isinstance(token, ConditionToken) assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[0], ConditionToken) - assert token.tokens[1] is simple_token + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) + assert token.tokens[0] is simple_token def test_not_quasitemp_condition_token_plus_not_temp_simple_token(): @@ -387,9 +386,8 @@ def test_not_quasitemp_condition_token_plus_not_temp_simple_token(): def test_quasitemp_condition_token_plus_temp_simple_token_reverse(): token = SimpleToken() + ConditionToken(lambda: False) - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], ConditionToken) + assert isinstance(token, ConditionToken) + assert len(token.tokens) == 0 def test_not_quasitemp_condition_token_plus_temp_simple_token_reverse(): @@ -406,10 +404,9 @@ def test_quasitemp_condition_token_plus_not_temp_simple_token_reverse(): simple_token = SimpleToken() token = simple_token + ConditionToken(lambda: False) - assert isinstance(token, SimpleToken) + assert isinstance(token, ConditionToken) assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[1], ConditionToken) + assert len(token.tokens) == 1 assert token.tokens[0] is simple_token diff --git a/tests/units/tokens/test_simple_token.py b/tests/units/tokens/test_simple_token.py index 99ec299..7bead65 100644 --- a/tests/units/tokens/test_simple_token.py +++ b/tests/units/tokens/test_simple_token.py @@ -195,28 +195,24 @@ def test_sum_of_2_temp_timeout_tokens_throw_temp_simple_tokens(): def test_sum_of_2_temp_timeout_tokens_throw_right_temp_simple_token(): token = TimeoutToken(1) + SimpleToken(TimeoutToken(2)) - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 2 + assert isinstance(token, TimeoutToken) + assert len(token.tokens) == 1 + assert token.timeout == 1 assert isinstance(token.tokens[0], TimeoutToken) - assert token.tokens[0].timeout == 1 - - assert isinstance(token.tokens[1], TimeoutToken) - assert token.tokens[1].timeout == 2 + assert token.tokens[0].timeout == 2 def test_sum_of_2_temp_timeout_tokens_throw_left_temp_simple_token(): token = SimpleToken(TimeoutToken(1)) + TimeoutToken(2) - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 2 + assert isinstance(token, TimeoutToken) + assert len(token.tokens) == 1 + assert token.timeout == 2 assert isinstance(token.tokens[0], TimeoutToken) assert token.tokens[0].timeout == 1 - assert isinstance(token.tokens[1], TimeoutToken) - assert token.tokens[1].timeout == 2 - def test_sum_of_2_not_temp_timeout_tokens_throw_temp_simple_tokens(): first_timeout_token = TimeoutToken(1) diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index 50f6cf7..b9dbcfd 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -224,9 +224,9 @@ def test_timeout_wait(): def test_quasitemp_timeout_token_plus_temp_simple_token(): token = TimeoutToken(1) + SimpleToken() - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], TimeoutToken) + assert isinstance(token, TimeoutToken) + assert len(token.tokens) == 0 + assert token.timeout == 1 def test_not_quasitemp_timeout_token_plus_temp_simple_token(): @@ -243,11 +243,11 @@ def test_quasitemp_timeout_token_plus_not_temp_simple_token(): simple_token = SimpleToken() token = TimeoutToken(1) + simple_token - assert isinstance(token, SimpleToken) + assert isinstance(token, TimeoutToken) assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[0], TimeoutToken) - assert token.tokens[1] is simple_token + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) + assert token.tokens[0] is simple_token def test_not_quasitemp_timeout_token_plus_not_temp_simple_token(): @@ -266,9 +266,9 @@ def test_not_quasitemp_timeout_token_plus_not_temp_simple_token(): def test_quasitemp_timeout_token_plus_temp_simple_token_reverse(): token = SimpleToken() + TimeoutToken(1) - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], TimeoutToken) + assert isinstance(token, TimeoutToken) + assert len(token.tokens) == 0 + assert token.timeout == 1 def test_not_quasitemp_timeout_token_plus_temp_simple_token_reverse(): @@ -285,10 +285,11 @@ def test_quasitemp_timeout_token_plus_not_temp_simple_token_reverse(): simple_token = SimpleToken() token = simple_token + TimeoutToken(1) - assert isinstance(token, SimpleToken) + assert isinstance(token, TimeoutToken) + assert token.timeout == 1 assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[1], TimeoutToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) assert token.tokens[0] is simple_token @@ -316,7 +317,7 @@ def test_timeout_is_more_important_than_cache(): assert report.from_token is inner_token assert report.cause == CancelCause.CANCELLED - sleep(sleep_time * 2) + sleep(sleep_time * 10) for report in token.get_report(True), token.get_report(False): assert report is not None From 728fcfe05640e36091e41f4d484082e6c909a4fd Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 19:04:37 +0300 Subject: [PATCH 04/19] fix a bug with not ignoring default tokens in sums + new tests for it --- cantok/tokens/abstract/abstract_token.py | 4 +++- tests/units/tokens/test_default_token.py | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 3597fec..2097e58 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -62,7 +62,9 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': for token in self, item: if isinstance(token, SimpleToken) and getrefcount(token) < 6: nested_tokens.extend(token.tokens) - elif not isinstance(token, SimpleToken) and not isinstance(token, DefaultToken) and getrefcount(token) < 6 and container_token is None: + elif isinstance(token, DefaultToken): + pass + elif not isinstance(token, SimpleToken) and getrefcount(token) < 6 and container_token is None: container_token = token else: nested_tokens.append(token) diff --git a/tests/units/tokens/test_default_token.py b/tests/units/tokens/test_default_token.py index 18ae8eb..1fc4c29 100644 --- a/tests/units/tokens/test_default_token.py +++ b/tests/units/tokens/test_default_token.py @@ -3,7 +3,7 @@ import pytest import full_match -from cantok import DefaultToken, SimpleToken, ImpossibleCancelError +from cantok import DefaultToken, SimpleToken, TimeoutToken, ImpossibleCancelError def test_dafault_token_is_not_cancelled_by_default(): @@ -93,3 +93,19 @@ def test_default_token_plus_not_temp_simple_token(): assert len(sum.tokens) == 1 assert sum is not simple_token assert sum.tokens[0] is simple_token + + +def test_temp_default_token_plus_temp_timeout_token(): + token = DefaultToken() + TimeoutToken(1) + + assert isinstance(token, TimeoutToken) + assert token.timeout == 1 + assert len(token.tokens) == 0 + + +def test_temp_timeout_token_plus_temp_default_token(): + token = TimeoutToken(1) + DefaultToken() + + assert isinstance(token, TimeoutToken) + assert token.timeout == 1 + assert len(token.tokens) == 0 From 73a52e24a1832b46ff5ed339a7b15830832685ec Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 22:39:29 +0300 Subject: [PATCH 05/19] nested tokens optimization --- cantok/tokens/abstract/abstract_token.py | 22 +++++++++-- .../tokens/abstract/test_abstract_token.py | 37 +++++++++++++++++++ tests/units/tokens/test_condition_token.py | 5 +++ tests/units/tokens/test_counter_token.py | 5 +++ tests/units/tokens/test_simple_token.py | 8 ++++ tests/units/tokens/test_timeout_token.py | 5 +++ 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 2097e58..3ec0a98 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -1,7 +1,7 @@ from sys import getrefcount from abc import ABC, abstractmethod from threading import RLock -from typing import List, Dict, Awaitable, Optional, Union, Any +from typing import List, Dict, Tuple, Awaitable, Optional, Union, Any from cantok.errors import CancellationError @@ -15,11 +15,22 @@ class AbstractToken(ABC): rollback_if_nondirect_polling = False def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None: - from cantok import DefaultToken + from cantok import DefaultToken, SimpleToken self.cached_report: Optional[CancellationReport] = None - self.tokens: List[AbstractToken] = [token for token in tokens if not isinstance(token, DefaultToken)] self._cancelled: bool = cancelled + self.tokens: List[AbstractToken] = [] + + for token in tokens: + if isinstance(token, DefaultToken): + pass + #elif token._cancelled: + # self.cancel() + # self.tokens = [] + # break + else: + self.tokens.append(token) + self.lock: RLock = RLock() def __repr__(self) -> str: @@ -58,9 +69,12 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': nested_tokens = [] container_token: Optional[AbstractToken] = None + cancel_result: bool = False for token in self, item: - if isinstance(token, SimpleToken) and getrefcount(token) < 6: + if token._cancelled: + return SimpleToken(cancelled=True) + elif isinstance(token, SimpleToken) and getrefcount(token) < 6: nested_tokens.extend(token.tokens) elif isinstance(token, DefaultToken): pass diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index 8377ce5..a95a7e4 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -669,3 +669,40 @@ def test_superpower_is_more_important_than_cache(first_token_fabric, second_toke assert isinstance(report, CancellationReport) assert report.from_token is token assert report.cause == CancelCause.CANCELLED + + +@pytest.mark.parametrize( + 'token_fabric', + ALL_TOKENS_FABRICS, +) +def test_just_neste_temp_simple_token_to_another_token(token_fabric): + token = token_fabric(SimpleToken()) + + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) + assert token + + +@pytest.mark.parametrize( + 'token_fabric', + ALL_TOKENS_FABRICS, +) +def test_any_token_plus_temp_cancelled_simple_token_gives_cancelled_simple_token(token_fabric): + token = token_fabric() + SimpleToken(cancelled=True) + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 0 + assert not token + + +@pytest.mark.parametrize( + 'token_fabric', + ALL_TOKENS_FABRICS, +) +def test_any_token_plus_temp_cancelled_simple_token_gives_cancelled_simple_token(token_fabric): + simple_token = SimpleToken(cancelled=True) + token = token_fabric() + simple_token + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 0 + assert not token diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 617a47d..967b234 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -441,3 +441,8 @@ def test_condition_function_is_more_important_than_cache(): assert isinstance(report, CancellationReport) assert report.from_token is token assert report.cause == CancelCause.SUPERPOWER + + +def test_zero_condition_token_report_is_about_superpower(): + for report in ConditionToken(lambda: True).get_report(True), ConditionToken(lambda: True).get_report(False): + assert report.cause == CancelCause.SUPERPOWER diff --git a/tests/units/tokens/test_counter_token.py b/tests/units/tokens/test_counter_token.py index 1fd106e..6dc7a0a 100644 --- a/tests/units/tokens/test_counter_token.py +++ b/tests/units/tokens/test_counter_token.py @@ -311,3 +311,8 @@ def test_not_quasitemp_counter_token_plus_not_temp_simple_token_reverse(): assert isinstance(token.tokens[1], CounterToken) assert token.tokens[1] is counter_token assert token.tokens[0] is simple_token + + +def test_zero_counter_token_report_is_about_superpower(): + for report in CounterToken(0).get_report(True), CounterToken(0).get_report(False): + assert report.cause == CancelCause.SUPERPOWER diff --git a/tests/units/tokens/test_simple_token.py b/tests/units/tokens/test_simple_token.py index 7bead65..33a74a4 100644 --- a/tests/units/tokens/test_simple_token.py +++ b/tests/units/tokens/test_simple_token.py @@ -364,3 +364,11 @@ def test_sum_of_not_temp_condition_token_and_not_temp_timeout_token_throw_temp_s assert token.tokens[1] is timeout_token assert isinstance(token.tokens[1], TimeoutToken) assert token.tokens[1].timeout == 1 + + +def test_temp_timeout_token_plus_temp_cancelled_simple_token(): + token = TimeoutToken(1) + SimpleToken(cancelled=True) + + assert isinstance(token, SimpleToken) + assert not token + assert len(token.tokens) == 0 diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index b9dbcfd..15549c5 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -324,3 +324,8 @@ def test_timeout_is_more_important_than_cache(): assert isinstance(report, CancellationReport) assert report.from_token is token assert report.cause == CancelCause.SUPERPOWER + + +def test_zero_timeout_token_report_is_about_superpower(): + for report in TimeoutToken(0).get_report(True), TimeoutToken(0).get_report(False): + assert report.cause == CancelCause.SUPERPOWER From 586b325dde0c7cd320a8f699359bddee1c0a5f07 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 23:15:02 +0300 Subject: [PATCH 06/19] +1 test --- cantok/tokens/counter_token.py | 2 +- .../tokens/abstract/test_abstract_token.py | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cantok/tokens/counter_token.py b/cantok/tokens/counter_token.py index 7e737a8..4963f62 100644 --- a/cantok/tokens/counter_token.py +++ b/cantok/tokens/counter_token.py @@ -16,7 +16,7 @@ def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False self.initial_counter = counter self.direct = direct self.rollback_if_nondirect_polling = self.direct - + def function() -> bool: with self.lock: if not self.counter: diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index a95a7e4..a98db48 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -142,7 +142,7 @@ def test_str(token_fabric): 'second_token_fabric', ALL_TOKENS_FABRICS, ) -def test_add_tokens(first_token_fabric, second_token_fabric): +def test_add_not_temp_tokens(first_token_fabric, second_token_fabric): first_token = first_token_fabric() second_token = second_token_fabric() @@ -154,6 +154,30 @@ def test_add_tokens(first_token_fabric, second_token_fabric): assert tokens_sum.tokens[1] is second_token +@pytest.mark.parametrize( + ['first_token_class', 'first_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + #(CounterToken, [15]), + ], +) +@pytest.mark.parametrize( + ['second_token_class', 'second_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + #(CounterToken, [15]), + ], +) +def test_add_temp_tokens(first_token_class, second_token_class, first_arguments, second_arguments): + tokens_sum = first_token_class(*first_arguments) + second_token_class(*second_arguments) + + assert isinstance(tokens_sum, first_token_class) + assert len(tokens_sum.tokens) == 1 + assert isinstance(tokens_sum.tokens[0], second_token_class) + + @pytest.mark.parametrize( 'first_token_fabric', ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, From c90db886b375f7ef27aa1ee0a7ff2e3a6d8057ae Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Tue, 6 Aug 2024 23:58:48 +0300 Subject: [PATCH 07/19] beautiful repr for condition tokens --- cantok/tokens/condition_token.py | 21 ++++++++--- tests/units/tokens/test_condition_token.py | 44 ++++++++++++++-------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/cantok/tokens/condition_token.py b/cantok/tokens/condition_token.py index 8e3538f..7972b40 100644 --- a/cantok/tokens/condition_token.py +++ b/cantok/tokens/condition_token.py @@ -1,5 +1,6 @@ from typing import Callable, Dict, Any from contextlib import suppress +from types import LambdaType from cantok import AbstractToken from cantok.errors import ConditionCancellationError @@ -57,13 +58,23 @@ def run_function(self) -> bool: return result def text_representation_of_superpower(self) -> str: - return repr(self.function) + result = self.function.__name__ + + if result == '': + return 'λ' + + return result def get_extra_kwargs(self) -> Dict[str, Any]: - return { - 'suppress_exceptions': self.suppress_exceptions, - 'default': self.default, - } + result = {} + + if not self.suppress_exceptions: + result['suppress_exceptions'] = self.suppress_exceptions + + if self.default is not False: + result['default'] = self.default + + return result def get_superpower_exception_message(self) -> str: return 'The cancellation condition was satisfied.' diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 967b234..0fb4195 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -99,22 +99,6 @@ def test_condition_function_returning_not_bool_value(): ConditionToken(lambda: 'kek', suppress_exceptions=False).cancelled -@pytest.mark.parametrize( - 'suppress_exceptions_flag', - [True, False], -) -@pytest.mark.parametrize( - 'default_flag', - [True, False], -) -def test_test_representaion_of_extra_kwargs(suppress_exceptions_flag, default_flag): - assert ConditionToken( - lambda: False, - suppress_exceptions=suppress_exceptions_flag, - default=default_flag, - ).text_representation_of_extra_kwargs() == f'suppress_exceptions={suppress_exceptions_flag}, default={default_flag}' - - @pytest.mark.parametrize( 'default', [True, False], @@ -446,3 +430,31 @@ def test_condition_function_is_more_important_than_cache(): def test_zero_condition_token_report_is_about_superpower(): for report in ConditionToken(lambda: True).get_report(True), ConditionToken(lambda: True).get_report(False): assert report.cause == CancelCause.SUPERPOWER + + +def test_creating_condition_token_with_no_suppress_exceptions_is_not_calling_condition(): + calls = [] + + token = ConditionToken(lambda: calls.append(True) is None, suppress_exceptions=False) + + assert not calls + + +def test_repr_of_condition_token(): + def function(): return False + + assert repr(ConditionToken(lambda: False)) == 'ConditionToken(λ)' + assert repr(ConditionToken(lambda: False, ConditionToken(lambda: False))) == 'ConditionToken(λ, ConditionToken(λ))' + assert repr(ConditionToken(lambda: False, suppress_exceptions=True)) == 'ConditionToken(λ)' + assert repr(ConditionToken(lambda: False, suppress_exceptions=False)) == 'ConditionToken(λ, suppress_exceptions=False)' + assert repr(ConditionToken(lambda: False, default=False)) == 'ConditionToken(λ)' + assert repr(ConditionToken(lambda: False, default=True)) == 'ConditionToken(λ, default=True)' + assert repr(ConditionToken(lambda: False, suppress_exceptions=False, default=True)) == 'ConditionToken(λ, suppress_exceptions=False, default=True)' + + assert repr(ConditionToken(function)) == 'ConditionToken(function)' + assert repr(ConditionToken(function, ConditionToken(function))) == 'ConditionToken(function, ConditionToken(function))' + assert repr(ConditionToken(function, suppress_exceptions=True)) == 'ConditionToken(function)' + assert repr(ConditionToken(function, suppress_exceptions=False)) == 'ConditionToken(function, suppress_exceptions=False)' + assert repr(ConditionToken(function, default=False)) == 'ConditionToken(function)' + assert repr(ConditionToken(function, default=True)) == 'ConditionToken(function, default=True)' + assert repr(ConditionToken(function, suppress_exceptions=False, default=True)) == 'ConditionToken(function, suppress_exceptions=False, default=True)' From d36de6bf072efc80a3e6c0865897be612d80833a Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:08:25 +0300 Subject: [PATCH 08/19] beautiful __repr__ for counter tokens --- cantok/tokens/counter_token.py | 10 ++++++---- tests/units/tokens/test_counter_token.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cantok/tokens/counter_token.py b/cantok/tokens/counter_token.py index 4963f62..1db53ae 100644 --- a/cantok/tokens/counter_token.py +++ b/cantok/tokens/counter_token.py @@ -16,7 +16,7 @@ def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False self.initial_counter = counter self.direct = direct self.rollback_if_nondirect_polling = self.direct - + def function() -> bool: with self.lock: if not self.counter: @@ -33,9 +33,11 @@ def text_representation_of_superpower(self) -> str: return str(self.counter) def get_extra_kwargs(self) -> Dict[str, Any]: - return { - 'direct': self.direct, - } + if not self.direct: + return { + 'direct': self.direct, + } + return {} def get_superpower_data(self) -> Dict[str, Any]: return {'counter': self.counter} diff --git a/tests/units/tokens/test_counter_token.py b/tests/units/tokens/test_counter_token.py index 6dc7a0a..f139573 100644 --- a/tests/units/tokens/test_counter_token.py +++ b/tests/units/tokens/test_counter_token.py @@ -316,3 +316,21 @@ def test_not_quasitemp_counter_token_plus_not_temp_simple_token_reverse(): def test_zero_counter_token_report_is_about_superpower(): for report in CounterToken(0).get_report(True), CounterToken(0).get_report(False): assert report.cause == CancelCause.SUPERPOWER + + +def test_repr_for_counter_token(): + assert repr(CounterToken(0)) == 'CounterToken(0)' + assert repr(CounterToken(1)) == 'CounterToken(1)' + assert repr(CounterToken(10000)) == 'CounterToken(10000)' + + assert repr(CounterToken(10000, CounterToken(10000))) == 'CounterToken(10000, CounterToken(10000))' + assert repr(CounterToken(10000, CounterToken(10000), CounterToken(10000))) == 'CounterToken(10000, CounterToken(10000), CounterToken(10000))' + + assert repr(CounterToken(10000, direct=True)) == 'CounterToken(10000)' + assert repr(CounterToken(10000, direct=False)) == 'CounterToken(10000, direct=False)' + + assert repr(CounterToken(10000, cancelled=False)) == 'CounterToken(10000)' + assert repr(CounterToken(10000, cancelled=True)) == 'CounterToken(10000, cancelled=True)' + + assert repr(CounterToken(10000, direct=False, cancelled=True)) == 'CounterToken(10000, cancelled=True, direct=False)' + assert repr(CounterToken(10000, CounterToken(10000), direct=False, cancelled=True)) == 'CounterToken(10000, CounterToken(10000), cancelled=True, direct=False)' From 7a65907d6cac729928c05a0043dcc6dc177d88b7 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:09:45 +0300 Subject: [PATCH 09/19] new asserts for repr test --- tests/units/tokens/test_condition_token.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 0fb4195..40e6cde 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -450,6 +450,7 @@ def function(): return False assert repr(ConditionToken(lambda: False, default=False)) == 'ConditionToken(λ)' assert repr(ConditionToken(lambda: False, default=True)) == 'ConditionToken(λ, default=True)' assert repr(ConditionToken(lambda: False, suppress_exceptions=False, default=True)) == 'ConditionToken(λ, suppress_exceptions=False, default=True)' + assert repr(ConditionToken(lambda: False, suppress_exceptions=False, default=True, cancelled=True)) == 'ConditionToken(λ, cancelled=True, suppress_exceptions=False, default=True)' assert repr(ConditionToken(function)) == 'ConditionToken(function)' assert repr(ConditionToken(function, ConditionToken(function))) == 'ConditionToken(function, ConditionToken(function))' @@ -458,3 +459,5 @@ def function(): return False assert repr(ConditionToken(function, default=False)) == 'ConditionToken(function)' assert repr(ConditionToken(function, default=True)) == 'ConditionToken(function, default=True)' assert repr(ConditionToken(function, suppress_exceptions=False, default=True)) == 'ConditionToken(function, suppress_exceptions=False, default=True)' + + assert repr(ConditionToken(function, suppress_exceptions=False, default=True, cancelled=True)) == 'ConditionToken(function, cancelled=True, suppress_exceptions=False, default=True)' From 5fdebedebbbefbac44a5b7f7441afe20f00aa34c Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:17:24 +0300 Subject: [PATCH 10/19] new asserts for repr test --- tests/units/tokens/abstract/test_abstract_token.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index a98db48..a5d0bdb 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -176,6 +176,7 @@ def test_add_temp_tokens(first_token_class, second_token_class, first_arguments, assert isinstance(tokens_sum, first_token_class) assert len(tokens_sum.tokens) == 1 assert isinstance(tokens_sum.tokens[0], second_token_class) + assert len(tokens_sum.tokens[0].tokens) == 0 @pytest.mark.parametrize( From e5f99c657b6d0c19a8e65d85f8cdd10597e36cec Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:34:09 +0300 Subject: [PATCH 11/19] fix of variable counting for counter tokens + tests --- cantok/tokens/counter_token.py | 20 +++++++++++----- .../tokens/abstract/test_abstract_token.py | 4 ++-- tests/units/tokens/test_counter_token.py | 24 +++++++++---------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cantok/tokens/counter_token.py b/cantok/tokens/counter_token.py index 1db53ae..7a683c2 100644 --- a/cantok/tokens/counter_token.py +++ b/cantok/tokens/counter_token.py @@ -12,25 +12,33 @@ def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False if counter < 0: raise ValueError('The counter must be greater than or equal to zero.') - self.counter = counter self.initial_counter = counter self.direct = direct self.rollback_if_nondirect_polling = self.direct + counter_bag = {'counter': counter} + self.counter_bag = counter_bag + def function() -> bool: - with self.lock: - if not self.counter: + with counter_bag['lock']: + if not counter_bag['counter']: return True - self.counter -= 1 + counter_bag['counter'] -= 1 return False super().__init__(function, *tokens, cancelled=cancelled) + self.counter_bag['lock'] = self.lock + + @property + def counter(self) -> int: + return self.counter_bag['counter'] + def superpower_rollback(self, superpower_data: Dict[str, Any]) -> None: - self.counter = superpower_data['counter'] + self.counter_bag['counter'] = superpower_data['counter'] def text_representation_of_superpower(self) -> str: - return str(self.counter) + return str(self.counter_bag['counter']) def get_extra_kwargs(self) -> Dict[str, Any]: if not self.direct: diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index a5d0bdb..6907f3b 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -159,7 +159,7 @@ def test_add_not_temp_tokens(first_token_fabric, second_token_fabric): [ (TimeoutToken, [15]), (ConditionToken, [lambda: False]), - #(CounterToken, [15]), + (CounterToken, [15]), ], ) @pytest.mark.parametrize( @@ -167,7 +167,7 @@ def test_add_not_temp_tokens(first_token_fabric, second_token_fabric): [ (TimeoutToken, [15]), (ConditionToken, [lambda: False]), - #(CounterToken, [15]), + (CounterToken, [15]), ], ) def test_add_temp_tokens(first_token_class, second_token_class, first_arguments, second_arguments): diff --git a/tests/units/tokens/test_counter_token.py b/tests/units/tokens/test_counter_token.py index f139573..d491206 100644 --- a/tests/units/tokens/test_counter_token.py +++ b/tests/units/tokens/test_counter_token.py @@ -232,9 +232,8 @@ def test_decrement_counter_after_zero(): def test_quasitemp_counter_token_plus_temp_simple_token(): token = CounterToken(0) + SimpleToken() - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], CounterToken) + assert isinstance(token, CounterToken) + assert len(token.tokens) == 0 def test_not_quasitemp_counter_token_plus_temp_simple_token(): @@ -251,11 +250,11 @@ def test_quasitemp_counter_token_plus_not_temp_simple_token(): simple_token = SimpleToken() token = CounterToken(1) + simple_token - assert isinstance(token, SimpleToken) + assert isinstance(token, CounterToken) assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[0], CounterToken) - assert token.tokens[1] is simple_token + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) + assert token.tokens[0] is simple_token def test_not_quasitemp_counter_token_plus_not_temp_simple_token(): @@ -274,9 +273,8 @@ def test_not_quasitemp_counter_token_plus_not_temp_simple_token(): def test_quasitemp_counter_token_plus_temp_simple_token_reverse(): token = SimpleToken() + CounterToken(1) - assert isinstance(token, SimpleToken) - assert len(token.tokens) == 1 - assert isinstance(token.tokens[0], CounterToken) + assert isinstance(token, CounterToken) + assert len(token.tokens) == 0 def test_not_quasitemp_counter_token_plus_temp_simple_token_reverse(): @@ -293,10 +291,10 @@ def test_quasitemp_counter_token_plus_not_temp_simple_token_reverse(): simple_token = SimpleToken() token = simple_token + CounterToken(1) - assert isinstance(token, SimpleToken) + assert isinstance(token, CounterToken) assert token is not simple_token - assert len(token.tokens) == 2 - assert isinstance(token.tokens[1], CounterToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], SimpleToken) assert token.tokens[0] is simple_token From 7be7fbb3668d0cb65accb39d8296c605c6aec90b Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:39:01 +0300 Subject: [PATCH 12/19] mypy ignore comments --- cantok/tokens/condition_token.py | 4 ++-- cantok/tokens/counter_token.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cantok/tokens/condition_token.py b/cantok/tokens/condition_token.py index 7972b40..cab6e7f 100644 --- a/cantok/tokens/condition_token.py +++ b/cantok/tokens/condition_token.py @@ -59,7 +59,7 @@ def run_function(self) -> bool: def text_representation_of_superpower(self) -> str: result = self.function.__name__ - + if result == '': return 'λ' @@ -72,7 +72,7 @@ def get_extra_kwargs(self) -> Dict[str, Any]: result['suppress_exceptions'] = self.suppress_exceptions if self.default is not False: - result['default'] = self.default + result['default'] = self.default # type: ignore[assignment] return result diff --git a/cantok/tokens/counter_token.py b/cantok/tokens/counter_token.py index 7a683c2..449b2cb 100644 --- a/cantok/tokens/counter_token.py +++ b/cantok/tokens/counter_token.py @@ -20,7 +20,7 @@ def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False self.counter_bag = counter_bag def function() -> bool: - with counter_bag['lock']: + with counter_bag['lock']: # type: ignore[attr-defined] if not counter_bag['counter']: return True counter_bag['counter'] -= 1 @@ -28,7 +28,7 @@ def function() -> bool: super().__init__(function, *tokens, cancelled=cancelled) - self.counter_bag['lock'] = self.lock + self.counter_bag['lock'] = self.lock # type: ignore[assignment] @property def counter(self) -> int: From c6ea45a5cbdf192d0dfc807ae494cbf8af8ce945 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:46:57 +0300 Subject: [PATCH 13/19] renamed a test --- tests/units/tokens/abstract/test_abstract_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index 6907f3b..69a7a4b 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -724,7 +724,7 @@ def test_any_token_plus_temp_cancelled_simple_token_gives_cancelled_simple_token 'token_fabric', ALL_TOKENS_FABRICS, ) -def test_any_token_plus_temp_cancelled_simple_token_gives_cancelled_simple_token(token_fabric): +def test_any_token_plus_cancelled_simple_token_gives_cancelled_simple_token(token_fabric): simple_token = SimpleToken(cancelled=True) token = token_fabric() + simple_token From 9deeedf8484e0a708daf8566910c604cd7bbad0f Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:51:37 +0300 Subject: [PATCH 14/19] new tests --- .../tokens/abstract/test_abstract_token.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index 69a7a4b..eeb15b4 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -179,6 +179,58 @@ def test_add_temp_tokens(first_token_class, second_token_class, first_arguments, assert len(tokens_sum.tokens[0].tokens) == 0 +@pytest.mark.parametrize( + ['first_token_class', 'first_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + (CounterToken, [15]), + ], +) +@pytest.mark.parametrize( + ['second_token_class', 'second_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + (CounterToken, [15]), + ], +) +def test_add_not_temp_token_and_temp_token(first_token_class, second_token_class, first_arguments, second_arguments): + first_token = first_token_class(*first_arguments) + tokens_sum = first_token + second_token_class(*second_arguments) + + assert isinstance(tokens_sum, second_token_class) + assert len(tokens_sum.tokens) == 1 + assert isinstance(tokens_sum.tokens[0], first_token_class) + assert len(tokens_sum.tokens[0].tokens) == 0 + + +@pytest.mark.parametrize( + ['first_token_class', 'first_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + (CounterToken, [15]), + ], +) +@pytest.mark.parametrize( + ['second_token_class', 'second_arguments'], + [ + (TimeoutToken, [15]), + (ConditionToken, [lambda: False]), + (CounterToken, [15]), + ], +) +def test_add_temp_token_and_not_temp_token(first_token_class, second_token_class, first_arguments, second_arguments): + second_token = second_token_class(*second_arguments) + tokens_sum = first_token_class(*first_arguments) + second_token + + assert isinstance(tokens_sum, first_token_class) + assert len(tokens_sum.tokens) == 1 + assert isinstance(tokens_sum.tokens[0], second_token_class) + assert len(tokens_sum.tokens[0].tokens) == 0 + + @pytest.mark.parametrize( 'first_token_fabric', ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, From b039101f5d2dc4a4bc60c1c6f31727686ca86b6c Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:53:41 +0300 Subject: [PATCH 15/19] no extra imports --- cantok/tokens/abstract/abstract_token.py | 5 ++--- cantok/tokens/condition_token.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 3ec0a98..2533148 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -1,7 +1,7 @@ from sys import getrefcount from abc import ABC, abstractmethod from threading import RLock -from typing import List, Dict, Tuple, Awaitable, Optional, Union, Any +from typing import List, Dict, Awaitable, Optional, Union, Any from cantok.errors import CancellationError @@ -15,7 +15,7 @@ class AbstractToken(ABC): rollback_if_nondirect_polling = False def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None: - from cantok import DefaultToken, SimpleToken + from cantok import DefaultToken self.cached_report: Optional[CancellationReport] = None self._cancelled: bool = cancelled @@ -69,7 +69,6 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': nested_tokens = [] container_token: Optional[AbstractToken] = None - cancel_result: bool = False for token in self, item: if token._cancelled: diff --git a/cantok/tokens/condition_token.py b/cantok/tokens/condition_token.py index cab6e7f..d99e8cb 100644 --- a/cantok/tokens/condition_token.py +++ b/cantok/tokens/condition_token.py @@ -1,6 +1,5 @@ from typing import Callable, Dict, Any from contextlib import suppress -from types import LambdaType from cantok import AbstractToken from cantok.errors import ConditionCancellationError From a0955a21a676cfae3898febe6e1a7735dd6a416e Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:54:29 +0300 Subject: [PATCH 16/19] no extra variable --- tests/units/tokens/test_condition_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 40e6cde..2a4fb6a 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -435,7 +435,7 @@ def test_zero_condition_token_report_is_about_superpower(): def test_creating_condition_token_with_no_suppress_exceptions_is_not_calling_condition(): calls = [] - token = ConditionToken(lambda: calls.append(True) is None, suppress_exceptions=False) + ConditionToken(lambda: calls.append(True) is None, suppress_exceptions=False) assert not calls From 309d5cc528c588a87dcd591840f0ce8f8d1d6d21 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 00:59:51 +0300 Subject: [PATCH 17/19] no extra condition --- cantok/tokens/abstract/abstract_token.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 2533148..286168e 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -24,10 +24,6 @@ def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None: for token in tokens: if isinstance(token, DefaultToken): pass - #elif token._cancelled: - # self.cancel() - # self.tokens = [] - # break else: self.tokens.append(token) From 99dd6fd0f87ebebb74488a1687fc8763cdde4a35 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 01:04:32 +0300 Subject: [PATCH 18/19] new version tag --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5a5a2af..f8ff70f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cantok" -version = "0.0.27" +version = "0.0.28" authors = [ { name="Evgeniy Blinov", email="zheni-b@yandex.ru" }, ] From 511176648324f7cce610b1a2ed3d7f9b33cb7f54 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 7 Aug 2024 01:30:51 +0300 Subject: [PATCH 19/19] documentation for new adding optimizations --- docs/types_of_tokens/SimpleToken.md | 9 --------- docs/what_are_tokens/summation.md | 30 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/types_of_tokens/SimpleToken.md b/docs/types_of_tokens/SimpleToken.md index 8e43abb..6e8f9da 100644 --- a/docs/types_of_tokens/SimpleToken.md +++ b/docs/types_of_tokens/SimpleToken.md @@ -9,13 +9,4 @@ token.cancel() print(token.cancelled) #> True ``` -`SimpleToken` is also implicitly generated by the operation of summing two other tokens: - -```python -from cantok import CounterToken, TimeoutToken - -print(repr(CounterToken(5) + TimeoutToken(5))) -#> SimpleToken(CounterToken(5, direct=True), TimeoutToken(5)) -``` - 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. diff --git a/docs/what_are_tokens/summation.md b/docs/what_are_tokens/summation.md index fa4600e..5e8c317 100644 --- a/docs/what_are_tokens/summation.md +++ b/docs/what_are_tokens/summation.md @@ -1,10 +1,10 @@ -Any tokens can be summed up among themselves. The summation operation generates another [`SimpleToken`](../types_of_tokens/SimpleToken.md) that includes the previous 2: +Tokens can be summed using the operator `+`: ```python -from cantok import SimpleToken, TimeoutToken - -print(repr(SimpleToken() + TimeoutToken(5))) -#> SimpleToken(TimeoutToken(5)) +first_token = TimeoutToken(5) +second_token = ConditionToken(lambda: True) +print(repr(first_token + second_token)) +#> SimpleToken(TimeoutToken(5), ConditionToken(λ)) ``` 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): 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. ... ``` + +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. + +You may notice that some tokens disappear altogether during summation: + +```python +print(repr(SimpleToken() + TimeoutToken(5))) +#> TimeoutToken(5) +print(repr(SimpleToken(cancelled=True) + TimeoutToken(5))) +#> SimpleToken(cancelled=True) +``` + +In addition, you can not be afraid to sum more than 2 tokens - this does not generate anything superfluous: + +```python +print(repr(TimeoutToken(5) + ConditionToken(lambda: False) + CounterToken(23))) +#> TimeoutToken(5, ConditionToken(λ), CounterToken(23)) +``` + +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.