Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.0.28 #39

Merged
merged 19 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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="[email protected]" },
]
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
Loading