Skip to content

Commit

Permalink
Merge pull request #39 from pomponchik/develop
Browse files Browse the repository at this point in the history
0.0.28
  • Loading branch information
pomponchik authored Aug 6, 2024
2 parents 86f330d + 5111766 commit ccd012e
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 102 deletions.
26 changes: 22 additions & 4 deletions cantok/tokens/abstract/abstract_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ 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.tokens: List[AbstractToken] = []

for token in tokens:
if isinstance(token, DefaultToken):
pass
else:
self.tokens.append(token)

self.lock: RLock = RLock()

def __repr__(self) -> str:
Expand Down Expand Up @@ -54,17 +61,28 @@ 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

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
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()
Expand Down
20 changes: 15 additions & 5 deletions cantok/tokens/condition_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,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 == '<lambda>':
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 # type: ignore[assignment]

return result

def get_superpower_exception_message(self) -> str:
return 'The cancellation condition was satisfied.'
28 changes: 19 additions & 9 deletions cantok/tokens/counter_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,40 @@ 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']: # type: ignore[attr-defined]
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 # type: ignore[assignment]

@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]:
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}
Expand Down
9 changes: 0 additions & 9 deletions docs/types_of_tokens/SimpleToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
30 changes: 25 additions & 5 deletions docs/what_are_tokens/summation.md
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
]
Expand Down
116 changes: 115 additions & 1 deletion tests/units/tokens/abstract/test_abstract_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -154,6 +154,83 @@ 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)
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,
Expand Down Expand Up @@ -669,3 +746,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_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
Loading

0 comments on commit ccd012e

Please sign in to comment.