From b20cbd1e72b0237858817d530a0189cbe48e3f6b Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Thu, 25 Jul 2024 15:16:52 +0300 Subject: [PATCH 01/31] flat sums for three and more tokens --- cantok/tokens/abstract_token.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index b34483a..6428626 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -113,7 +113,15 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken': from cantok import SimpleToken - return SimpleToken(self, item) + nested_tokens = [] + + for token in self, item: + if isinstance(token, SimpleToken) and getrefcount(token) < 6: + nested_tokens.extend(token.tokens) + else: + nested_tokens.append(token) + + return SimpleToken(*nested_tokens) def __bool__(self) -> bool: return self.keep_on() From 95d2c1d54761a81098c24e5fb085e995b17c4cc6 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Fri, 2 Aug 2024 22:47:02 +0300 Subject: [PATCH 02/31] new tests --- tests/units/tokens/test_abstract_token.py | 26 ++++++++++++ tests/units/tokens/test_simple_token.py | 49 +++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 5d17dee..daeabbf 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -138,6 +138,32 @@ def test_add_tokens(first_token_fabric, second_token_fabric): assert tokens_sum.tokens[1] is second_token +@pytest.mark.parametrize( + 'first_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +@pytest.mark.parametrize( + 'second_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +@pytest.mark.parametrize( + 'third_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +def test_add_three_tokens_except_simple_token(first_token_fabric, second_token_fabric, third_token_fabric): + first_token = first_token_fabric() + second_token = second_token_fabric() + third_token = third_token_fabric() + + tokens_sum = first_token + second_token + third_token + + assert isinstance(tokens_sum, SimpleToken) + assert len(tokens_sum.tokens) == 3 + assert tokens_sum.tokens[0] is first_token + assert tokens_sum.tokens[1] is second_token + assert tokens_sum.tokens[2] is third_token + + @pytest.mark.parametrize( 'second_token_fabric', ALL_TOKENS_FABRICS, diff --git a/tests/units/tokens/test_simple_token.py b/tests/units/tokens/test_simple_token.py index d92c392..5184f2d 100644 --- a/tests/units/tokens/test_simple_token.py +++ b/tests/units/tokens/test_simple_token.py @@ -110,3 +110,52 @@ def test_get_report_cancelled_nested(cancelled_flag, cancelled_flag_nested, from assert report.from_token is nested_token else: assert report.from_token is token + + +def test_sum_of_2_temp_simple_tokens(): + token = SimpleToken() + SimpleToken() + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 0 + + +def test_sum_of_1_temp_and_1_not_temp_simple_tokens(): + second_token = SimpleToken() + result = SimpleToken() + second_token + + assert isinstance(result, SimpleToken) + assert len(result.tokens) == 1 + assert result.tokens[0] is second_token + + +def test_sum_of_1_not_temp_and_1_temp_simple_tokens(): + first_token = SimpleToken() + result = first_token + SimpleToken() + + assert isinstance(result, SimpleToken) + assert len(result.tokens) == 1 + assert result.tokens[0] is first_token + + +def test_sum_of_2_not_temp_simple_tokens(): + first_token = SimpleToken() + second_token = SimpleToken() + result = first_token + second_token + + assert isinstance(result, SimpleToken) + assert len(result.tokens) == 2 + assert result.tokens[0] is first_token + assert result.tokens[1] is second_token + + +def test_sum_of_3_not_temp_simple_tokens(): + first_token = SimpleToken() + second_token = SimpleToken() + third_token = SimpleToken() + result = first_token + second_token + third_token + + assert isinstance(result, SimpleToken) + assert len(result.tokens) == 3 + assert result.tokens[0] is first_token + assert result.tokens[1] is second_token + assert result.tokens[2] is third_token From 1b1f08dc99a1e155b596cd1390bd5686b27f1bdf Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Fri, 2 Aug 2024 23:14:39 +0300 Subject: [PATCH 03/31] new tests --- tests/units/tokens/test_condition_token.py | 84 ++++++++++++++++++++++ tests/units/tokens/test_counter_token.py | 84 ++++++++++++++++++++++ tests/units/tokens/test_default_token.py | 7 ++ tests/units/tokens/test_simple_token.py | 7 ++ tests/units/tokens/test_timeout_token.py | 84 ++++++++++++++++++++++ 5 files changed, 266 insertions(+) diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 05c01d4..f67c66e 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -340,3 +340,87 @@ def condition(): assert counter == 5 assert token.cancelled == False assert counter == 6 + + +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) + + +def test_not_quasitemp_condition_token_plus_temp_simple_token(): + condition_token = ConditionToken(lambda: False) + token = condition_token + SimpleToken() + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], ConditionToken) + assert token.tokens[0] is condition_token + + +def test_quasitemp_condition_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + token = ConditionToken(lambda: False) + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], ConditionToken) + assert token.tokens[1] is simple_token + + +def test_not_quasitemp_condition_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + condition_token = ConditionToken(lambda: False) + token = condition_token + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], ConditionToken) + assert token.tokens[0] is condition_token + assert token.tokens[1] is 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) + + +def test_not_quasitemp_condition_token_plus_temp_simple_token_reverse(): + condition_token = ConditionToken(lambda: False) + token = SimpleToken() + condition_token + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], ConditionToken) + assert token.tokens[0] is condition_token + + +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 token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], ConditionToken) + assert token.tokens[0] is simple_token + + +def test_not_quasitemp_condition_token_plus_not_temp_simple_token_reverse(): + simple_token = SimpleToken() + condition_token = ConditionToken(lambda: False) + token = simple_token + condition_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], ConditionToken) + assert token.tokens[1] is condition_token + assert token.tokens[0] is simple_token diff --git a/tests/units/tokens/test_counter_token.py b/tests/units/tokens/test_counter_token.py index ea6474b..51fddf6 100644 --- a/tests/units/tokens/test_counter_token.py +++ b/tests/units/tokens/test_counter_token.py @@ -227,3 +227,87 @@ def test_decrement_counter_after_zero(): token.is_cancelled() assert token.counter == 0 + + +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) + + +def test_not_quasitemp_counter_token_plus_temp_simple_token(): + counter_token = CounterToken(1) + token = counter_token + SimpleToken() + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], CounterToken) + assert token.tokens[0] is counter_token + + +def test_quasitemp_counter_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + token = CounterToken(1) + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], CounterToken) + assert token.tokens[1] is simple_token + + +def test_not_quasitemp_counter_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + counter_token = CounterToken(1) + token = counter_token + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], CounterToken) + assert token.tokens[0] is counter_token + assert token.tokens[1] is 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) + + +def test_not_quasitemp_counter_token_plus_temp_simple_token_reverse(): + counter_token = CounterToken(1) + token = SimpleToken() + counter_token + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], CounterToken) + assert token.tokens[0] is counter_token + + +def test_quasitemp_counter_token_plus_not_temp_simple_token_reverse(): + simple_token = SimpleToken() + token = simple_token + CounterToken(1) + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], CounterToken) + assert token.tokens[0] is simple_token + + +def test_not_quasitemp_counter_token_plus_not_temp_simple_token_reverse(): + simple_token = SimpleToken() + counter_token = CounterToken(1) + token = simple_token + counter_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], CounterToken) + assert token.tokens[1] is counter_token + assert token.tokens[0] is simple_token diff --git a/tests/units/tokens/test_default_token.py b/tests/units/tokens/test_default_token.py index 45df234..ab3c4d6 100644 --- a/tests/units/tokens/test_default_token.py +++ b/tests/units/tokens/test_default_token.py @@ -69,3 +69,10 @@ def test_default_plus_default(): assert isinstance(empty_sum, SimpleToken) assert len(empty_sum.tokens) == 0 + + +def test_default_plus_default_plus_default(): + empty_sum = DefaultToken() + DefaultToken() + DefaultToken() + + assert isinstance(empty_sum, SimpleToken) + assert len(empty_sum.tokens) == 0 diff --git a/tests/units/tokens/test_simple_token.py b/tests/units/tokens/test_simple_token.py index 5184f2d..757ec21 100644 --- a/tests/units/tokens/test_simple_token.py +++ b/tests/units/tokens/test_simple_token.py @@ -119,6 +119,13 @@ def test_sum_of_2_temp_simple_tokens(): assert len(token.tokens) == 0 +def test_sum_of_5_temp_simple_tokens(): + token = SimpleToken() + SimpleToken() + SimpleToken() + SimpleToken() + SimpleToken() + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 0 + + def test_sum_of_1_temp_and_1_not_temp_simple_tokens(): second_token = SimpleToken() result = SimpleToken() + second_token diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index ef1a432..55c0db2 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -207,3 +207,87 @@ def test_timeout_wait(): finish_time = perf_counter() assert sleep_duration <= finish_time - start_time + + +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) + + +def test_not_quasitemp_timeout_token_plus_temp_simple_token(): + timeout_token = TimeoutToken(1) + token = timeout_token + SimpleToken() + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], TimeoutToken) + assert token.tokens[0] is timeout_token + + +def test_quasitemp_timeout_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + token = TimeoutToken(1) + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], TimeoutToken) + assert token.tokens[1] is simple_token + + +def test_not_quasitemp_timeout_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + timeout_token = TimeoutToken(1) + token = timeout_token + simple_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[0], TimeoutToken) + assert token.tokens[0] is timeout_token + assert token.tokens[1] is 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) + + +def test_not_quasitemp_timeout_token_plus_temp_simple_token_reverse(): + timeout_token = TimeoutToken(1) + token = SimpleToken() + timeout_token + + assert isinstance(token, SimpleToken) + assert len(token.tokens) == 1 + assert isinstance(token.tokens[0], TimeoutToken) + assert token.tokens[0] is timeout_token + + +def test_quasitemp_timeout_token_plus_not_temp_simple_token_reverse(): + simple_token = SimpleToken() + token = simple_token + TimeoutToken(1) + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], TimeoutToken) + assert token.tokens[0] is simple_token + + +def test_not_quasitemp_timeout_token_plus_not_temp_simple_token_reverse(): + simple_token = SimpleToken() + timeout_token = TimeoutToken(1) + token = simple_token + timeout_token + + assert isinstance(token, SimpleToken) + assert token is not simple_token + assert len(token.tokens) == 2 + assert isinstance(token.tokens[1], TimeoutToken) + assert token.tokens[1] is timeout_token + assert token.tokens[0] is simple_token From 88d06fae9cd2fecebc5a84d1821fbfc9ba4bfd7d Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Fri, 2 Aug 2024 23:19:04 +0300 Subject: [PATCH 04/31] new tests --- tests/units/tokens/test_default_token.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/units/tokens/test_default_token.py b/tests/units/tokens/test_default_token.py index ab3c4d6..18ae8eb 100644 --- a/tests/units/tokens/test_default_token.py +++ b/tests/units/tokens/test_default_token.py @@ -76,3 +76,20 @@ def test_default_plus_default_plus_default(): assert isinstance(empty_sum, SimpleToken) assert len(empty_sum.tokens) == 0 + + +def test_default_token_plus_temp_simple_token(): + empty_sum = DefaultToken() + SimpleToken() + + assert isinstance(empty_sum, SimpleToken) + assert len(empty_sum.tokens) == 0 + + +def test_default_token_plus_not_temp_simple_token(): + simple_token = SimpleToken() + sum = DefaultToken() + simple_token + + assert isinstance(sum, SimpleToken) + assert len(sum.tokens) == 1 + assert sum is not simple_token + assert sum.tokens[0] is simple_token From 9217bb456d2ebf1075989bd36d7b97a572269a4b Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Fri, 2 Aug 2024 23:25:24 +0300 Subject: [PATCH 05/31] new tests --- tests/units/tokens/test_abstract_token.py | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index daeabbf..c56b0f2 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -164,6 +164,66 @@ def test_add_three_tokens_except_simple_token(first_token_fabric, second_token_f assert tokens_sum.tokens[2] is third_token +@pytest.mark.parametrize( + 'first_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +def test_add_another_token_and_temp_simple_token(first_token_fabric): + first_token = first_token_fabric() + + tokens_sum = first_token + SimpleToken() + + assert isinstance(tokens_sum, SimpleToken) + assert len(tokens_sum.tokens) == 1 + assert tokens_sum.tokens[0] is first_token + + +@pytest.mark.parametrize( + 'second_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +def test_add_temp_simple_token_and_another_token(second_token_fabric): + second_token = second_token_fabric() + + tokens_sum = SimpleToken() + second_token + + assert isinstance(tokens_sum, SimpleToken) + assert len(tokens_sum.tokens) == 1 + assert tokens_sum.tokens[0] is second_token + + +@pytest.mark.parametrize( + 'first_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +def test_add_another_token_and_not_temp_simple_token(first_token_fabric): + simple_token = SimpleToken() + first_token = first_token_fabric() + + tokens_sum = first_token + simple_token + + assert isinstance(tokens_sum, SimpleToken) + assert len(tokens_sum.tokens) == 2 + assert tokens_sum.tokens[0] is first_token + assert tokens_sum.tokens[1] is simple_token + + +@pytest.mark.parametrize( + 'second_token_fabric', + [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], +) +def test_add_not_temp_simple_token_and_another_token(second_token_fabric): + simple_token = SimpleToken() + second_token = second_token_fabric() + + tokens_sum = simple_token + second_token + + assert isinstance(tokens_sum, SimpleToken) + assert len(tokens_sum.tokens) == 2 + assert tokens_sum.tokens[0] is simple_token + assert tokens_sum.tokens[1] is second_token + + @pytest.mark.parametrize( 'second_token_fabric', ALL_TOKENS_FABRICS, From 5ec41d17383c5f11fca9d4bcd9051c080f21b7f8 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 02:03:11 +0300 Subject: [PATCH 06/31] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c31a4e1..f96843c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv +venv2 .pytest_cache *.egg-info build From f26b86320ba6d6ed75c8d1e4f47c10a23b6ed042 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 11:59:28 +0300 Subject: [PATCH 07/31] fix a bug when nested token was cancelled and you still can set the 'cancelled' attribute as False --- cantok/tokens/abstract_token.py | 11 ++++++----- tests/units/tokens/test_abstract_token.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index 6428626..57ca12a 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -132,11 +132,12 @@ def cancelled(self) -> bool: @cancelled.setter def cancelled(self, new_value: bool) -> None: - if new_value == True: - self._cancelled = True - else: - if self._cancelled == True: - raise ValueError('You cannot restore a cancelled token.') + with self.lock: + if new_value == True: + self._cancelled = True + else: + if self.is_cancelled(): + raise ValueError('You cannot restore a cancelled token.') def keep_on(self) -> bool: return not self.is_cancelled() diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index c56b0f2..5929976 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -4,6 +4,7 @@ from threading import Thread import pytest +import full_match from cantok.tokens.abstract_token import AbstractToken, CancelCause, CancellationReport from cantok import SimpleToken, ConditionToken, TimeoutToken, CounterToken, DefaultToken, CancellationError @@ -75,6 +76,17 @@ def test_change_attribute_cancelled(token_fabric, first_cancelled_flag, second_c token.check() +@pytest.mark.parametrize( + 'token_fabric', + ALL_TOKENS_FABRICS, +) +def test_set_cancelled_false_if_this_token_is_not_cancelled_but_nested_token_is(token_fabric): + token = token_fabric(SimpleToken(cancelled=True)) + + with pytest.raises(ValueError, match=full_match('You cannot restore a cancelled token.')): + token.cancelled = False + + @pytest.mark.parametrize( 'token_fabric', ALL_TOKENS_FABRICS + [DefaultToken], From 7100292c11cba658027eeeb73db5581fcc512c72 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 12:06:24 +0300 Subject: [PATCH 08/31] added some type hints --- cantok/tokens/abstract_token.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index 57ca12a..1784c4a 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -75,9 +75,9 @@ class AbstractToken(ABC): def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None: from cantok import DefaultToken - self.tokens = [token for token in tokens if not isinstance(token, DefaultToken)] - self._cancelled = cancelled - self.lock = RLock() + self.tokens: List[AbstractToken] = [token for token in tokens if not isinstance(token, DefaultToken)] + self._cancelled: bool = cancelled + self.lock: RLock = RLock() def __repr__(self) -> str: chunks = [] From 16388f90ba2339586bc0a1ff1ec951ac5cd2c39b Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 12:08:18 +0300 Subject: [PATCH 09/31] micro-refactoring --- cantok/tokens/abstract_token.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index 1784c4a..acbacc7 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -168,12 +168,11 @@ def get_report(self, direct: bool = True) -> CancellationReport: cause=CancelCause.CANCELLED, from_token=self, ) - else: - if self.check_superpower(direct): - return CancellationReport( - cause=CancelCause.SUPERPOWER, - from_token=self, - ) + elif self.check_superpower(direct): + return CancellationReport( + cause=CancelCause.SUPERPOWER, + from_token=self, + ) for token in self.tokens: report = token.get_report(direct=False) From 8deb01e232b7bd6ca7ea1609cde5d250816769ba Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 12:23:53 +0300 Subject: [PATCH 10/31] caching of cancellation reports + optimization of dataclass size --- cantok/tokens/abstract_token.py | 6 +++++- tests/units/tokens/test_abstract_token.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index acbacc7..0f685b2 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -63,7 +63,7 @@ async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_ token_for_check.check() -@dataclass +@dataclass(frozen=True, slots=True) class CancellationReport: cause: CancelCause from_token: 'AbstractToken' @@ -75,6 +75,7 @@ class AbstractToken(ABC): def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None: from cantok import DefaultToken + 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.lock: RLock = RLock() @@ -173,10 +174,13 @@ def get_report(self, direct: bool = True) -> CancellationReport: cause=CancelCause.SUPERPOWER, from_token=self, ) + elif self.cached_report is not None: + return self.cached_report for token in self.tokens: report = token.get_report(direct=False) if report.cause != CancelCause.NOT_CANCELLED: + self.cached_report = report return report return CancellationReport( diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 5929976..0ff1fe1 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -2,6 +2,8 @@ from functools import partial from time import perf_counter, sleep from threading import Thread +from dataclasses import FrozenInstanceError +from sys import getsizeof import pytest import full_match @@ -21,6 +23,25 @@ def test_cant_instantiate_abstract_token(): AbstractToken() +def test_cant_change_cancellation_report(): + report = CancellationReport( + cause=CancelCause.NOT_CANCELLED, + from_token=SimpleToken(), + ) + + with pytest.raises(FrozenInstanceError): + report.from_token = TimeoutToken(1) + + +def test_size_of_report_is_not_so_big(): + report = CancellationReport( + cause=CancelCause.NOT_CANCELLED, + from_token=SimpleToken(), + ) + + assert getsizeof(report) <= 48 + + @pytest.mark.parametrize( 'cancelled_flag', [True, False], From adf6b3349a9d36a3dc4ca75d32ba5bd5c7ac2f44 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:15:15 +0300 Subject: [PATCH 11/31] test --- tests/units/tokens/test_abstract_token.py | 39 ++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 0ff1fe1..820cd0f 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -17,7 +17,6 @@ ALL_TOKENS_FABRICS = [partial(token_class, *arguments) for token_class, arguments in zip(ALL_TOKEN_CLASSES, ALL_ARGUMENTS_FOR_TOKEN_CLASSES)] - def test_cant_instantiate_abstract_token(): with pytest.raises(TypeError): AbstractToken() @@ -565,3 +564,41 @@ def test_insert_default_token_to_another_tokens(token_fabric): assert not isinstance(token, DefaultToken) assert len(token.tokens) == 0 + + +@pytest.mark.parametrize( + 'first_token_fabric', + ALL_TOKENS_FABRICS, +) +@pytest.mark.parametrize( + 'second_token_fabric', + ALL_TOKENS_FABRICS, +) +@pytest.mark.parametrize( + 'action', + [ + lambda x: x.cancelled, + lambda x: x.keep_on(), + lambda x: bool(x), + lambda x: bool(x), + lambda x: x.is_cancelled(), + lambda x: x.get_report(True), + lambda x: x.get_report(False), + ], +) +def test_report_cache_is_working_in_simple_case(first_token_fabric, second_token_fabric, action): + token = first_token_fabric(second_token_fabric(cancelled=True)) + + assert token.cached_report is None + + action(token) + + cached_report = token.cached_report + + assert cached_report is not None + assert isinstance(cached_report, CancellationReport) + assert cached_report.from_token.is_cancelled() + assert cached_report.cause == CancelCause.CANCELLED + + assert token.get_report(True) is cached_report + assert token.get_report(False) is cached_report From 8a00f67f8f6aecc8c7010086efbd188d9c70eda7 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:21:53 +0300 Subject: [PATCH 12/31] new version tag --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 66e1ef9..f42c185 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cantok" -version = "0.0.25" +version = "0.0.26" authors = [ { name="Evgeniy Blinov", email="zheni-b@yandex.ru" }, ] From d1c3f7cfd36341e539a26e0c5c1dec03ec82ef7f Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:26:45 +0300 Subject: [PATCH 13/31] mypy ignore comment --- cantok/tokens/abstract_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index 0f685b2..b62862a 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -63,7 +63,7 @@ async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_ token_for_check.check() -@dataclass(frozen=True, slots=True) +@dataclass(frozen=True, slots=True) # type: ignore[call-overload, unused-ignore] class CancellationReport: cause: CancelCause from_token: 'AbstractToken' From 8106ae5e01dcba48900cd8c94c5c9380e833f165 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:36:28 +0300 Subject: [PATCH 14/31] text representation of timeout tokens without the monotonic flag --- cantok/tokens/timeout_token.py | 8 +++++--- tests/units/tokens/test_timeout_token.py | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cantok/tokens/timeout_token.py b/cantok/tokens/timeout_token.py index b6b00d9..026d1ae 100644 --- a/cantok/tokens/timeout_token.py +++ b/cantok/tokens/timeout_token.py @@ -34,9 +34,11 @@ def text_representation_of_superpower(self) -> str: return str(self.timeout) def get_extra_kwargs(self) -> Dict[str, Any]: - return { - 'monotonic': self.monotonic, - } + if self.monotonic: + return { + 'monotonic': self.monotonic, + } + return {} def get_superpower_exception_message(self) -> str: return f'The timeout of {self.timeout} seconds has expired.' diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index 55c0db2..813e0f6 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -88,9 +88,21 @@ def test_timeout_expired(options): def test_text_representaion_of_extra_kwargs(): - assert TimeoutToken(5, monotonic=False).text_representation_of_extra_kwargs() == 'monotonic=False' + assert TimeoutToken(5, monotonic=False).text_representation_of_extra_kwargs() == '' assert TimeoutToken(5, monotonic=True).text_representation_of_extra_kwargs() == 'monotonic=True' - assert TimeoutToken(5).text_representation_of_extra_kwargs() == 'monotonic=False' + assert TimeoutToken(5).text_representation_of_extra_kwargs() == '' + + +@pytest.mark.parametrize( + ['options', 'repr_string'], + [ + ({}, 'TimeoutToken(1)'), + ({'monotonic': True}, 'TimeoutToken(1, monotonic=True)'), + ({'monotonic': False}, 'TimeoutToken(1)'), + ], +) +def test_repr_of_timeout_token(options, repr_string): + assert repr(TimeoutToken(1, **options)) == repr_string def test_check_superpower_raised(): From ae549b8c7d7bf512dcb66ef00803daf2c2eb3e69 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:39:20 +0300 Subject: [PATCH 15/31] documentation without the monotonic flags for timeout tokens --- docs/types_of_tokens/SimpleToken.md | 2 +- docs/what_are_tokens/summation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/types_of_tokens/SimpleToken.md b/docs/types_of_tokens/SimpleToken.md index c984dbc..14e93ff 100644 --- a/docs/types_of_tokens/SimpleToken.md +++ b/docs/types_of_tokens/SimpleToken.md @@ -15,7 +15,7 @@ print(token.cancelled) # True from cantok import CounterToken, TimeoutToken print(repr(CounterToken(5) + TimeoutToken(5))) -# SimpleToken(CounterToken(5, direct=True), TimeoutToken(5, monotonic=False)) +# 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 641c17f..fea5fff 100644 --- a/docs/what_are_tokens/summation.md +++ b/docs/what_are_tokens/summation.md @@ -4,7 +4,7 @@ Any tokens can be summed up among themselves. The summation operation generates from cantok import SimpleToken, TimeoutToken print(repr(SimpleToken() + TimeoutToken(5))) -# SimpleToken(SimpleToken(), TimeoutToken(5, monotonic=False)) +# SimpleToken(SimpleToken(), TimeoutToken(5)) ``` 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: From fd270ab30563faea1dcc64b71af67d9490f4cc9f Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 15:47:43 +0300 Subject: [PATCH 16/31] old pythons adaptation (there was no __slots__ support for dataclasses) --- cantok/tokens/abstract_token.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract_token.py index b62862a..b9c9507 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract_token.py @@ -1,5 +1,5 @@ import weakref -from sys import getrefcount +from sys import getrefcount, version_info from enum import Enum from time import sleep as sync_sleep from asyncio import sleep as async_sleep @@ -63,7 +63,12 @@ async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_ token_for_check.check() -@dataclass(frozen=True, slots=True) # type: ignore[call-overload, unused-ignore] +if version_info >= (3, 10): + addictional_fields: Dict[str, bool] = {'slots': True} +else: + addictional_fields = {} + +@dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore] class CancellationReport: cause: CancelCause from_token: 'AbstractToken' From c7ae77ab56f2d6533e71d416d3aa8362729622cf Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:01:23 +0300 Subject: [PATCH 17/31] all things for abstract token now are in an own directory --- cantok/__init__.py | 2 +- cantok/tokens/abstract/__init__.py | 0 .../tokens/{ => abstract}/abstract_token.py | 71 ++----------------- cantok/tokens/abstract/cancel_cause.py | 7 ++ cantok/tokens/abstract/coroutine_wrapper.py | 53 ++++++++++++++ cantok/tokens/abstract/report.py | 16 +++++ tests/units/tokens/test_abstract_token.py | 2 +- tests/units/tokens/test_condition_token.py | 2 +- tests/units/tokens/test_counter_token.py | 2 +- tests/units/tokens/test_simple_token.py | 2 +- tests/units/tokens/test_timeout_token.py | 2 +- 11 files changed, 87 insertions(+), 72 deletions(-) create mode 100644 cantok/tokens/abstract/__init__.py rename cantok/tokens/{ => abstract}/abstract_token.py (74%) create mode 100644 cantok/tokens/abstract/cancel_cause.py create mode 100644 cantok/tokens/abstract/coroutine_wrapper.py create mode 100644 cantok/tokens/abstract/report.py diff --git a/cantok/__init__.py b/cantok/__init__.py index 7f6c695..c76e1ad 100644 --- a/cantok/__init__.py +++ b/cantok/__init__.py @@ -1,4 +1,4 @@ -from cantok.tokens.abstract_token import AbstractToken as AbstractToken # noqa: F401 +from cantok.tokens.abstract.abstract_token import AbstractToken as AbstractToken # noqa: F401 from cantok.tokens.simple_token import SimpleToken as SimpleToken # noqa: F401 from cantok.tokens.condition_token import ConditionToken as ConditionToken # noqa: F401 from cantok.tokens.counter_token import CounterToken as CounterToken # noqa: F401 diff --git a/cantok/tokens/abstract/__init__.py b/cantok/tokens/abstract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cantok/tokens/abstract_token.py b/cantok/tokens/abstract/abstract_token.py similarity index 74% rename from cantok/tokens/abstract_token.py rename to cantok/tokens/abstract/abstract_token.py index b9c9507..8561c33 100644 --- a/cantok/tokens/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -1,77 +1,16 @@ import weakref -from sys import getrefcount, version_info -from enum import Enum -from time import sleep as sync_sleep -from asyncio import sleep as async_sleep +from sys import getrefcount from abc import ABC, abstractmethod from threading import RLock from dataclasses import dataclass from typing import List, Dict, Awaitable, Optional, Union, Any -from types import TracebackType -from collections.abc import Coroutine -from cantok.errors import CancellationError - - -class CancelCause(Enum): - CANCELLED = 1 - SUPERPOWER = 2 - NOT_CANCELLED = 3 - -class WaitCoroutineWrapper(Coroutine): # type: ignore[type-arg] - def __init__(self, step: Union[int, float], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: - self.step = step - self.token_for_wait = token_for_wait - self.token_for_check = token_for_check - - self.flags: Dict[str, bool] = {} - self.coroutine = self.async_wait(step, self.flags, token_for_wait, token_for_check) - - weakref.finalize(self, self.sync_wait, step, self.flags, token_for_wait, token_for_check, self.coroutine) - - def __await__(self) -> Any: - return self.coroutine.__await__() - - def send(self, value: Any) -> Any: - return self.coroutine.send(value) - - def throw(self, exception_type: Any, value: Optional[Any] = None, traceback: Optional[TracebackType] = None) -> Any: - pass # pragma: no cover - def close(self) -> None: - pass # pragma: no cover - - @staticmethod - def sync_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken', wrapped_coroutine: Coroutine) -> None: # type: ignore[type-arg] - if not flags.get('used', False): - if getrefcount(wrapped_coroutine) < 5: - wrapped_coroutine.close() - - while token_for_wait: - sync_sleep(step) - - token_for_check.check() - - @staticmethod - async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: - flags['used'] = True - - while token_for_wait: - await async_sleep(step) - - await async_sleep(0) - - token_for_check.check() - -if version_info >= (3, 10): - addictional_fields: Dict[str, bool] = {'slots': True} -else: - addictional_fields = {} +from cantok.errors import CancellationError +from cantok.tokens.abstract.cancel_cause import CancelCause +from cantok.tokens.abstract.report import CancellationReport +from cantok.tokens.abstract.coroutine_wrapper import WaitCoroutineWrapper -@dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore] -class CancellationReport: - cause: CancelCause - from_token: 'AbstractToken' class AbstractToken(ABC): exception = CancellationError diff --git a/cantok/tokens/abstract/cancel_cause.py b/cantok/tokens/abstract/cancel_cause.py new file mode 100644 index 0000000..127edc6 --- /dev/null +++ b/cantok/tokens/abstract/cancel_cause.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class CancelCause(Enum): + CANCELLED = 1 + SUPERPOWER = 2 + NOT_CANCELLED = 3 diff --git a/cantok/tokens/abstract/coroutine_wrapper.py b/cantok/tokens/abstract/coroutine_wrapper.py new file mode 100644 index 0000000..6016273 --- /dev/null +++ b/cantok/tokens/abstract/coroutine_wrapper.py @@ -0,0 +1,53 @@ +import weakref +from sys import getrefcount +from typing import Dict, Union, Optional, Any +from types import TracebackType +from collections.abc import Coroutine +from time import sleep as sync_sleep +from asyncio import sleep as async_sleep + + +class WaitCoroutineWrapper(Coroutine): # type: ignore[type-arg] + def __init__(self, step: Union[int, float], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: # type: ignore[name-defined] + self.step = step + self.token_for_wait = token_for_wait + self.token_for_check = token_for_check + + self.flags: Dict[str, bool] = {} + self.coroutine = self.async_wait(step, self.flags, token_for_wait, token_for_check) + + weakref.finalize(self, self.sync_wait, step, self.flags, token_for_wait, token_for_check, self.coroutine) + + def __await__(self) -> Any: + return self.coroutine.__await__() + + def send(self, value: Any) -> Any: + return self.coroutine.send(value) + + def throw(self, exception_type: Any, value: Optional[Any] = None, traceback: Optional[TracebackType] = None) -> Any: + pass # pragma: no cover + + def close(self) -> None: + pass # pragma: no cover + + @staticmethod + def sync_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken', wrapped_coroutine: Coroutine) -> None: # type: ignore[type-arg, name-defined] + if not flags.get('used', False): + if getrefcount(wrapped_coroutine) < 5: + wrapped_coroutine.close() + + while token_for_wait: + sync_sleep(step) + + token_for_check.check() + + @staticmethod + async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: # type: ignore[name-defined] + flags['used'] = True + + while token_for_wait: + await async_sleep(step) + + await async_sleep(0) + + token_for_check.check() diff --git a/cantok/tokens/abstract/report.py b/cantok/tokens/abstract/report.py new file mode 100644 index 0000000..a61c911 --- /dev/null +++ b/cantok/tokens/abstract/report.py @@ -0,0 +1,16 @@ +from typing import Dict +from dataclasses import dataclass +from sys import version_info + +from cantok.tokens.abstract.cancel_cause import CancelCause + + +if version_info >= (3, 10): + addictional_fields: Dict[str, bool] = {'slots': True} +else: + addictional_fields = {} + +@dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore] +class CancellationReport: + cause: CancelCause + from_token: 'AbstractToken' # type: ignore[name-defined] diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 820cd0f..db28564 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -8,7 +8,7 @@ import pytest import full_match -from cantok.tokens.abstract_token import AbstractToken, CancelCause, CancellationReport +from cantok.tokens.abstract.abstract_token import AbstractToken, CancelCause, CancellationReport from cantok import SimpleToken, ConditionToken, TimeoutToken, CounterToken, DefaultToken, CancellationError diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index f67c66e..7be748d 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -4,7 +4,7 @@ import pytest -from cantok.tokens.abstract_token import CancelCause, CancellationReport +from cantok.tokens.abstract.abstract_token import CancelCause, CancellationReport from cantok import SimpleToken, ConditionToken, ConditionCancellationError diff --git a/tests/units/tokens/test_counter_token.py b/tests/units/tokens/test_counter_token.py index 51fddf6..1fd106e 100644 --- a/tests/units/tokens/test_counter_token.py +++ b/tests/units/tokens/test_counter_token.py @@ -2,7 +2,7 @@ import pytest -from cantok.tokens.abstract_token import CancelCause, CancellationReport +from cantok.tokens.abstract.abstract_token import CancelCause, CancellationReport from cantok import CounterToken, SimpleToken, CounterCancellationError diff --git a/tests/units/tokens/test_simple_token.py b/tests/units/tokens/test_simple_token.py index 757ec21..5b6ce2a 100644 --- a/tests/units/tokens/test_simple_token.py +++ b/tests/units/tokens/test_simple_token.py @@ -1,6 +1,6 @@ import pytest -from cantok.tokens.abstract_token import CancelCause, CancellationReport +from cantok.tokens.abstract.abstract_token import CancelCause, CancellationReport from cantok import SimpleToken, CancellationError diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index 813e0f6..2b54360 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -3,7 +3,7 @@ import pytest -from cantok.tokens.abstract_token import CancelCause, CancellationReport +from cantok.tokens.abstract.abstract_token import CancelCause, CancellationReport from cantok import SimpleToken, TimeoutToken, TimeoutCancellationError From d19d585cb4a17c4b98b173ec0f20cda7341be969 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:03:45 +0300 Subject: [PATCH 18/31] no extra imports --- cantok/tokens/abstract/abstract_token.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 8561c33..d29dbe6 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -1,8 +1,6 @@ -import weakref from sys import getrefcount from abc import ABC, abstractmethod from threading import RLock -from dataclasses import dataclass from typing import List, Dict, Awaitable, Optional, Union, Any From 340883d2f5dee4beb0ea6c04af631aff2effb301 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:05:02 +0300 Subject: [PATCH 19/31] ignore F821 ruff error in the hole project --- .ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruff.toml b/.ruff.toml index ec5b9a4..af28669 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1 +1 @@ -ignore = ['E501', 'E712'] +ignore = ['E501', 'E712', 'F821'] From 09ccf21eb46c4435f1f0e59c8083e8c48e9dd632 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:09:11 +0300 Subject: [PATCH 20/31] no cover comments for python versions variable code --- cantok/tokens/abstract/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cantok/tokens/abstract/report.py b/cantok/tokens/abstract/report.py index a61c911..c88312a 100644 --- a/cantok/tokens/abstract/report.py +++ b/cantok/tokens/abstract/report.py @@ -6,9 +6,9 @@ if version_info >= (3, 10): - addictional_fields: Dict[str, bool] = {'slots': True} + addictional_fields: Dict[str, bool] = {'slots': True} # pragma: no cover else: - addictional_fields = {} + addictional_fields = {} # pragma: no cover @dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore] class CancellationReport: From 1b08849d1e807e8a4d7cd3c1942cd34e01185a10 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:12:21 +0300 Subject: [PATCH 21/31] +1 print in a test --- tests/units/tokens/test_abstract_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index db28564..8d66548 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -37,7 +37,7 @@ def test_size_of_report_is_not_so_big(): cause=CancelCause.NOT_CANCELLED, from_token=SimpleToken(), ) - + print(getsizeof(report)) assert getsizeof(report) <= 48 From 1b700cd39aca764a2c100c3f8e2635a533e5b376 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:18:46 +0300 Subject: [PATCH 22/31] no check size of report for old pythons --- tests/units/tokens/test_abstract_token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 8d66548..3905950 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -32,12 +32,13 @@ def test_cant_change_cancellation_report(): report.from_token = TimeoutToken(1) +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Format of this exception messages was changed.') def test_size_of_report_is_not_so_big(): report = CancellationReport( cause=CancelCause.NOT_CANCELLED, from_token=SimpleToken(), ) - print(getsizeof(report)) + assert getsizeof(report) <= 48 From 21ef00e8a0ca3a453d2dc974f2ef00e42d2a9b83 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:20:17 +0300 Subject: [PATCH 23/31] ignore comment --- tests/units/tokens/test_abstract_token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/test_abstract_token.py index 3905950..be8bec7 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/test_abstract_token.py @@ -3,7 +3,7 @@ from time import perf_counter, sleep from threading import Thread from dataclasses import FrozenInstanceError -from sys import getsizeof +from sys import getsizeof, version_info import pytest import full_match @@ -32,7 +32,7 @@ def test_cant_change_cancellation_report(): report.from_token = TimeoutToken(1) -@pytest.mark.skipif(sys.version_info < (3, 8), reason='Format of this exception messages was changed.') +@pytest.mark.skipif(version_info < (3, 8), reason='There is no support of __slots__ for dataclasses in old pythons.') def test_size_of_report_is_not_so_big(): report = CancellationReport( cause=CancelCause.NOT_CANCELLED, From fbc4826c8882ac2e315dfab3422187cccc9bde5f Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:27:29 +0300 Subject: [PATCH 24/31] replaced some tests --- tests/units/tokens/abstract/__init__.py | 0 .../{ => abstract}/test_abstract_token.py | 22 --------------- tests/units/tokens/abstract/test_report.py | 27 +++++++++++++++++++ 3 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 tests/units/tokens/abstract/__init__.py rename tests/units/tokens/{ => abstract}/test_abstract_token.py (96%) create mode 100644 tests/units/tokens/abstract/test_report.py diff --git a/tests/units/tokens/abstract/__init__.py b/tests/units/tokens/abstract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/units/tokens/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py similarity index 96% rename from tests/units/tokens/test_abstract_token.py rename to tests/units/tokens/abstract/test_abstract_token.py index be8bec7..9c91407 100644 --- a/tests/units/tokens/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -2,8 +2,6 @@ from functools import partial from time import perf_counter, sleep from threading import Thread -from dataclasses import FrozenInstanceError -from sys import getsizeof, version_info import pytest import full_match @@ -22,26 +20,6 @@ def test_cant_instantiate_abstract_token(): AbstractToken() -def test_cant_change_cancellation_report(): - report = CancellationReport( - cause=CancelCause.NOT_CANCELLED, - from_token=SimpleToken(), - ) - - with pytest.raises(FrozenInstanceError): - report.from_token = TimeoutToken(1) - - -@pytest.mark.skipif(version_info < (3, 8), reason='There is no support of __slots__ for dataclasses in old pythons.') -def test_size_of_report_is_not_so_big(): - report = CancellationReport( - cause=CancelCause.NOT_CANCELLED, - from_token=SimpleToken(), - ) - - assert getsizeof(report) <= 48 - - @pytest.mark.parametrize( 'cancelled_flag', [True, False], diff --git a/tests/units/tokens/abstract/test_report.py b/tests/units/tokens/abstract/test_report.py new file mode 100644 index 0000000..6574553 --- /dev/null +++ b/tests/units/tokens/abstract/test_report.py @@ -0,0 +1,27 @@ +from sys import getsizeof, version_info +from dataclasses import FrozenInstanceError + +import pytest + +from cantok import SimpleToken, TimeoutToken +from cantok.tokens.abstract.abstract_token import CancelCause, CancellationReport + + +def test_cant_change_cancellation_report(): + report = CancellationReport( + cause=CancelCause.NOT_CANCELLED, + from_token=SimpleToken(), + ) + + with pytest.raises(FrozenInstanceError): + report.from_token = TimeoutToken(1) + + +@pytest.mark.skipif(version_info < (3, 8), reason='There is no support of __slots__ for dataclasses in old pythons.') +def test_size_of_report_is_not_so_big(): + report = CancellationReport( + cause=CancelCause.NOT_CANCELLED, + from_token=SimpleToken(), + ) + + assert getsizeof(report) <= 48 From 1da2c23081e40916bdc20790e8cef36be53d1a9a Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:40:59 +0300 Subject: [PATCH 25/31] new test --- .../tokens/abstract/test_abstract_token.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index 9c91407..78d223e 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -581,3 +581,40 @@ def test_report_cache_is_working_in_simple_case(first_token_fabric, second_token assert token.get_report(True) is cached_report assert token.get_report(False) is cached_report + + +@pytest.mark.parametrize( + 'first_token_fabric', + ALL_TOKENS_FABRICS, +) +@pytest.mark.parametrize( + 'second_token_fabric', + ALL_TOKENS_FABRICS, +) +@pytest.mark.parametrize( + 'action', + [ + lambda x: x.cancelled, + lambda x: x.keep_on(), + lambda x: bool(x), + lambda x: bool(x), + lambda x: x.is_cancelled(), + lambda x: x.get_report(True), + lambda x: x.get_report(False), + ], +) +def test_cache_is_using_after_self_flag(first_token_fabric, second_token_fabric, action): + token = first_token_fabric(second_token_fabric(cancelled=True)) + + action(token) + + cached_report = token.cached_report + + token.cancel() + + for new_report in token.get_report(True), token.get_report(False): + assert new_report is not cached_report + assert new_report is not None + assert isinstance(new_report, CancellationReport) + assert new_report.from_token.is_cancelled() + assert new_report.cause == CancelCause.CANCELLED From 096e8f83382a4aace5ddd038bf4556681e7801ab Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 3 Aug 2024 16:58:31 +0300 Subject: [PATCH 26/31] tests --- .../tokens/abstract/test_abstract_token.py | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/tests/units/tokens/abstract/test_abstract_token.py b/tests/units/tokens/abstract/test_abstract_token.py index 78d223e..8377ce5 100644 --- a/tests/units/tokens/abstract/test_abstract_token.py +++ b/tests/units/tokens/abstract/test_abstract_token.py @@ -11,8 +11,13 @@ ALL_TOKEN_CLASSES = [SimpleToken, ConditionToken, TimeoutToken, CounterToken] +ALL_SUPERPOWER_TOKEN_CLASSES = [ConditionToken, TimeoutToken, CounterToken] ALL_ARGUMENTS_FOR_TOKEN_CLASSES = [tuple(), (lambda: False, ), (15, ), (15, )] +ALL_CANCELLING_ARGUMENTS_FOR_TOKEN_CLASSES_WITH_SUPERPOWERS = [(lambda: True, ), (0, ), (0, )] +ALL_NOT_CANCELLING_ARGUMENTS_FOR_TOKEN_CLASSES_WITH_SUPERPOWERS = [(lambda: False, ), (15, ), (15, )] ALL_TOKENS_FABRICS = [partial(token_class, *arguments) for token_class, arguments in zip(ALL_TOKEN_CLASSES, ALL_ARGUMENTS_FOR_TOKEN_CLASSES)] +ALL_TOKENS_FABRICS_WITH_CANCELLING_SUPERPOWER = [partial(token_class, *arguments) for token_class, arguments in zip(ALL_SUPERPOWER_TOKEN_CLASSES, ALL_CANCELLING_ARGUMENTS_FOR_TOKEN_CLASSES_WITH_SUPERPOWERS)] +ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER = [partial(token_class, *arguments) for token_class, arguments in zip(ALL_SUPERPOWER_TOKEN_CLASSES, ALL_NOT_CANCELLING_ARGUMENTS_FOR_TOKEN_CLASSES_WITH_SUPERPOWERS)] def test_cant_instantiate_abstract_token(): @@ -151,15 +156,15 @@ def test_add_tokens(first_token_fabric, second_token_fabric): @pytest.mark.parametrize( 'first_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) @pytest.mark.parametrize( 'second_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) @pytest.mark.parametrize( 'third_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) def test_add_three_tokens_except_simple_token(first_token_fabric, second_token_fabric, third_token_fabric): first_token = first_token_fabric() @@ -177,7 +182,7 @@ def test_add_three_tokens_except_simple_token(first_token_fabric, second_token_f @pytest.mark.parametrize( 'first_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) def test_add_another_token_and_temp_simple_token(first_token_fabric): first_token = first_token_fabric() @@ -191,7 +196,7 @@ def test_add_another_token_and_temp_simple_token(first_token_fabric): @pytest.mark.parametrize( 'second_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) def test_add_temp_simple_token_and_another_token(second_token_fabric): second_token = second_token_fabric() @@ -205,7 +210,7 @@ def test_add_temp_simple_token_and_another_token(second_token_fabric): @pytest.mark.parametrize( 'first_token_fabric', - [x for x in ALL_TOKENS_FABRICS if x is not SimpleToken], + ALL_TOKENS_FABRICS_WITH_NOT_CANCELLING_SUPERPOWER, ) def test_add_another_token_and_not_temp_simple_token(first_token_fabric): simple_token = SimpleToken() @@ -618,3 +623,49 @@ def test_cache_is_using_after_self_flag(first_token_fabric, second_token_fabric, assert isinstance(new_report, CancellationReport) assert new_report.from_token.is_cancelled() assert new_report.cause == CancelCause.CANCELLED + + +@pytest.mark.parametrize( + 'first_token_fabric', + ALL_TOKENS_FABRICS_WITH_CANCELLING_SUPERPOWER, +) +@pytest.mark.parametrize( + 'second_token_fabric', + ALL_TOKENS_FABRICS_WITH_CANCELLING_SUPERPOWER, +) +@pytest.mark.parametrize( + 'action', + [ + lambda x: x.cancelled, + lambda x: x.keep_on(), + lambda x: bool(x), + lambda x: bool(x), + lambda x: x.is_cancelled(), + lambda x: x.get_report(True), + lambda x: x.get_report(False), + ], +) +def test_superpower_is_more_important_than_cache(first_token_fabric, second_token_fabric, action): + token = first_token_fabric(second_token_fabric(cancelled=True)) + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is token + assert report.cause == CancelCause.SUPERPOWER + + action(token) + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is token + assert report.cause == CancelCause.SUPERPOWER + + token.cancel() + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is token + assert report.cause == CancelCause.CANCELLED From c2a8ae554cdae22db468a8400e25a88f73e62acb Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sun, 4 Aug 2024 23:42:48 +0300 Subject: [PATCH 27/31] type hints --- cantok/errors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cantok/errors.py b/cantok/errors.py index 5c57dbc..24df751 100644 --- a/cantok/errors.py +++ b/cantok/errors.py @@ -2,7 +2,9 @@ class CancellationError(Exception): - def __init__(self, message: str, token: Any): + token: 'AbstractToken' # type: ignore[name-defined] + + def __init__(self, message: str, token: 'AbstractToken') -> None: # type: ignore[name-defined] self.token = token super().__init__(message) From b0467a00a8718ce3e0e85a565f32e07076352cb4 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sun, 4 Aug 2024 23:44:31 +0300 Subject: [PATCH 28/31] no extra import --- cantok/errors.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cantok/errors.py b/cantok/errors.py index 24df751..04dbec5 100644 --- a/cantok/errors.py +++ b/cantok/errors.py @@ -1,6 +1,3 @@ -from typing import Any - - class CancellationError(Exception): token: 'AbstractToken' # type: ignore[name-defined] From e9989f068cd2adaa8a3feeec4cb0f88966f94f43 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sun, 4 Aug 2024 23:57:21 +0300 Subject: [PATCH 29/31] new tests --- tests/units/tokens/test_condition_token.py | 20 ++++++++++++++++++++ tests/units/tokens/test_timeout_token.py | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tests/units/tokens/test_condition_token.py b/tests/units/tokens/test_condition_token.py index 7be748d..1126888 100644 --- a/tests/units/tokens/test_condition_token.py +++ b/tests/units/tokens/test_condition_token.py @@ -424,3 +424,23 @@ def test_not_quasitemp_condition_token_plus_not_temp_simple_token_reverse(): assert isinstance(token.tokens[1], ConditionToken) assert token.tokens[1] is condition_token assert token.tokens[0] is simple_token + + +def test_condition_function_is_more_important_than_cache(): + flag = False + inner_token = SimpleToken(cancelled=True) + token = ConditionToken(lambda: flag, inner_token) + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is inner_token + assert report.cause == CancelCause.CANCELLED + + flag = True + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is token + assert report.cause == CancelCause.SUPERPOWER diff --git a/tests/units/tokens/test_timeout_token.py b/tests/units/tokens/test_timeout_token.py index 2b54360..50f6cf7 100644 --- a/tests/units/tokens/test_timeout_token.py +++ b/tests/units/tokens/test_timeout_token.py @@ -303,3 +303,23 @@ def test_not_quasitemp_timeout_token_plus_not_temp_simple_token_reverse(): assert isinstance(token.tokens[1], TimeoutToken) assert token.tokens[1] is timeout_token assert token.tokens[0] is simple_token + + +def test_timeout_is_more_important_than_cache(): + sleep_time = 0.0001 + inner_token = SimpleToken(cancelled=True) + token = TimeoutToken(sleep_time, inner_token) + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is inner_token + assert report.cause == CancelCause.CANCELLED + + sleep(sleep_time * 2) + + for report in token.get_report(True), token.get_report(False): + assert report is not None + assert isinstance(report, CancellationReport) + assert report.from_token is token + assert report.cause == CancelCause.SUPERPOWER From 20627dc9e4f506be3dafbbd6238e7e661447ceab Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 5 Aug 2024 00:00:16 +0300 Subject: [PATCH 30/31] new package metadata: keywords --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f42c185..71fba0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,11 @@ classifiers = [ 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', ] +keywords = [ + 'cancellation tokens', + 'parallel programming', + 'concurrency', +] [tool.setuptools.package-data] "cantok" = ["py.typed"] From 6ae8b1e84f2c30d7e1d4b76075aaed79842af7d6 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 5 Aug 2024 00:02:55 +0300 Subject: [PATCH 31/31] documentation --- docs/what_are_tokens/summation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/what_are_tokens/summation.md b/docs/what_are_tokens/summation.md index fea5fff..fa4600e 100644 --- a/docs/what_are_tokens/summation.md +++ b/docs/what_are_tokens/summation.md @@ -4,7 +4,7 @@ Any tokens can be summed up among themselves. The summation operation generates from cantok import SimpleToken, TimeoutToken print(repr(SimpleToken() + TimeoutToken(5))) -# SimpleToken(SimpleToken(), TimeoutToken(5)) +#> SimpleToken(TimeoutToken(5)) ``` 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: