Skip to content

Commit

Permalink
Merge pull request #34 from pomponchik/develop
Browse files Browse the repository at this point in the history
0.0.23
  • Loading branch information
pomponchik authored Jul 15, 2024
2 parents 7c86ee3 + ac55024 commit 108b08c
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests_and_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
os: [macos-13, ubuntu-latest, windows-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ And use:
from random import randint
from cantok import ConditionToken, CounterToken, TimeoutToken


token = ConditionToken(lambda: randint(1, 100_000) == 1984) + CounterToken(400_000, direct=False) + TimeoutToken(1)
counter = 0

Expand Down
3 changes: 2 additions & 1 deletion cantok/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
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
from cantok.tokens.default_token import DefaultToken as DefaultToken # noqa: F401
from cantok.tokens.timeout_token import TimeoutToken as TimeoutToken

from cantok.errors import CancellationError as CancellationError, ConditionCancellationError as ConditionCancellationError, CounterCancellationError as CounterCancellationError, TimeoutCancellationError as TimeoutCancellationError # noqa: F401
from cantok.errors import CancellationError as CancellationError, ConditionCancellationError as ConditionCancellationError, CounterCancellationError as CounterCancellationError, TimeoutCancellationError as TimeoutCancellationError, ImpossibleCancelError as ImpossibleCancelError # noqa: F401


TimeOutToken = TimeoutToken
4 changes: 4 additions & 0 deletions cantok/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any


class CancellationError(Exception):
def __init__(self, message: str, token: Any):
self.token = token
Expand All @@ -13,3 +14,6 @@ class CounterCancellationError(CancellationError):

class TimeoutCancellationError(CancellationError):
pass

class ImpossibleCancelError(CancellationError):
pass
36 changes: 36 additions & 0 deletions cantok/tokens/default_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from cantok import AbstractToken
from cantok.errors import ImpossibleCancelError


class DefaultToken(AbstractToken):
exception = ImpossibleCancelError

def __init__(self) -> None:
super().__init__()

def superpower(self) -> bool:
return False

def text_representation_of_superpower(self) -> str:
return ''

def get_superpower_exception_message(self) -> str:
return 'You cannot cancel a default token.' # pragma: no cover

@property
def cancelled(self) -> bool:
return False

@cancelled.setter
def cancelled(self, new_value: bool) -> None:
if new_value == True:
self.raise_superpower_exception()

def keep_on(self) -> bool:
return True

def is_cancelled(self, direct: bool = True) -> bool:
return False

def cancel(self) -> 'AbstractToken': # type: ignore[return]
self.raise_superpower_exception()
Binary file added docs/assets/presentation_1.pptx
Binary file not shown.
44 changes: 44 additions & 0 deletions docs/ecosystem/projects/regular_functions_calling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
To run a function regularly, use [metronomes](https://github.com/pomponchik/metronomes). Just wrap your code in a context manager:

```python
from time import sleep
from metronomes import Metronome

with Metronome(0.2, lambda: print('go!')):
sleep(1)
#> go!
#> go!
#> go!
#> go!
#> go!
```

You can also manually control the start and stop of the metronome:

```python
metronome = Metronome(0.2, lambda: print('go!'))

metronome.start()
sleep(1)
metronome.stop()
#> go!
#> go!
#> go!
#> go!
#> go!
```

And of course, the cancellation token can be used as an optional argument:

```python
from cantok import TimeoutToken

metronome = Metronome(0.2, lambda: None, token=TimeoutToken(1))

metronome.start()
print(metronome.stopped)
#> False
sleep(1.5) # Here I specify a little more time than in the constructor of the token itself, since a small margin is needed for operations related to the creation of the metronome object itself.
print(metronome.stopped)
#> True
```
18 changes: 18 additions & 0 deletions docs/types_of_tokens/DefaultToken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
`DefaultToken` is a type of token that cannot be revoked. Otherwise, it behaves like a regular token, but if you try to cancel it, you will get an exception:

```python
from cantok import AbstractToken, DefaultToken

DefaultToken().cancel()
#> ...
#> cantok.errors.ImpossibleCancelError: You cannot cancel a default token.
```

In addition, you cannot embed other tokens in `DefaultToken`.

It is best to use `DefaultToken` as the default argument for functions:

```python
def function(token: AbstractToken = DefaultToken()):
...
```
2 changes: 1 addition & 1 deletion docs/what_are_tokens/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ token.check()
# cantok.errors.TimeoutCancellationError: The timeout of 1 seconds has expired.
```

Each type of token has a corresponding type of exception that can be raised in this case:
Each type of token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md)) has a corresponding type of exception that can be raised in this case:

- [`SimpleToken`](../types_of_tokens/SimpleToken.md) -> `CancellationError`
- [`ConditionToken`](../types_of_tokens/ConditionToken.md) -> `ConditionCancellationError`
Expand Down
8 changes: 6 additions & 2 deletions docs/what_are_tokens/in_general.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
A token is an object that can tell you whether to continue the action you started, or whether it has already been canceled.

There are 4 types of tokens in this library:
There are 4 main types of tokens in this library:

- [`SimpleToken`](../types_of_tokens/SimpleToken.md)
- [`ConditionToken`](../types_of_tokens/ConditionToken.md)
- [`TimeoutToken`](../types_of_tokens/TimeoutToken.md)
- [`CounterToken`](../types_of_tokens/CounterToken.md)

Additionally, there is a 5th type that cannot be cancelled:

- [`DefaultToken`](../types_of_tokens/DefaultToken.md)

Each of them has its own characteristics, but they also have something in common:

- Each token can be canceled manually, and some types of tokens can cancel themselves when a condition or timeout occurs. It doesn't matter how the token was canceled, you work with it the same way.
- Each token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md)) can be canceled manually, and some types of tokens can cancel themselves when a condition or timeout occurs. It doesn't matter how the token was canceled, you work with it the same way.

- All types of tokens are thread-safe and can be used from multiple threads/coroutines. However, they are not intended to be shared from multiple processes.

Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ nav:
- ConditionToken: types_of_tokens/ConditionToken.md
- TimeoutToken: types_of_tokens/TimeoutToken.md
- CounterToken: types_of_tokens/CounterToken.md
- DefaultToken: types_of_tokens/DefaultToken.md
- Ecosystem:
- About the ecosystem: ecosystem/about_ecosystem.md
- Projects:
- Subprocess Management: ecosystem/projects/subprocess_management.md
- Regular functions calling: ecosystem/projects/regular_functions_calling.md
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
Expand Down
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.22"
version = "0.0.23"
authors = [
{ name="Evgeniy Blinov", email="zheni-b@yandex.ru" },
]
Expand Down
1 change: 1 addition & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ mypy==1.4.1
ruff==0.0.290
mkdocs-material==9.2.7
mutmut==2.4.4
full_match==0.0.1
7 changes: 5 additions & 2 deletions tests/units/test_errors.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import pytest

from cantok import AbstractToken, SimpleToken, ConditionToken, TimeoutToken, CounterToken
from cantok import CancellationError, ConditionCancellationError, TimeoutCancellationError, CounterCancellationError
from cantok import AbstractToken, SimpleToken, ConditionToken, TimeoutToken, CounterToken, DefaultToken
from cantok import CancellationError, ConditionCancellationError, TimeoutCancellationError, CounterCancellationError, ImpossibleCancelError


def test_exception_inheritance_hierarchy():
assert issubclass(ConditionCancellationError, CancellationError)
assert issubclass(TimeoutCancellationError, CancellationError)
assert issubclass(CounterCancellationError, CancellationError)
assert issubclass(ImpossibleCancelError, CancellationError)


def test_exception_inheritance_hierarchy_from_view_of_tokens_classes():
assert issubclass(ConditionToken.exception, SimpleToken.exception)
assert issubclass(TimeoutToken.exception, SimpleToken.exception)
assert issubclass(CounterToken.exception, SimpleToken.exception)
assert issubclass(DefaultToken.exception, SimpleToken.exception)

assert SimpleToken.exception is CancellationError
assert ConditionToken.exception is ConditionCancellationError
assert TimeoutToken.exception is TimeoutCancellationError
assert CounterToken.exception is CounterCancellationError
assert DefaultToken.exception is ImpossibleCancelError
21 changes: 11 additions & 10 deletions tests/units/tokens/test_abstract_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

from cantok.tokens.abstract_token import AbstractToken, CancelCause, CancellationReport
from cantok import SimpleToken, ConditionToken, TimeoutToken, CounterToken, CancellationError
from cantok import SimpleToken, ConditionToken, TimeoutToken, CounterToken, DefaultToken, CancellationError


ALL_TOKEN_CLASSES = [SimpleToken, ConditionToken, TimeoutToken, CounterToken]
Expand All @@ -31,6 +31,7 @@ def test_cant_instantiate_abstract_token():
)
def test_cancelled_true_as_parameter(token_fabric, cancelled_flag):
token = token_fabric(cancelled=cancelled_flag)

assert token.cancelled == cancelled_flag
assert token.is_cancelled() == cancelled_flag
assert token.keep_on() == (not cancelled_flag)
Expand Down Expand Up @@ -77,7 +78,7 @@ def test_change_attribute_cancelled(token_fabric, first_cancelled_flag, second_c

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_repr(token_fabric):
token = token_fabric()
Expand Down Expand Up @@ -120,11 +121,11 @@ def test_str(token_fabric):

@pytest.mark.parametrize(
'first_token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
@pytest.mark.parametrize(
'second_token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_add_tokens(first_token_fabric, second_token_fabric):
first_token = first_token_fabric()
Expand All @@ -140,7 +141,7 @@ def test_add_tokens(first_token_fabric, second_token_fabric):

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
@pytest.mark.parametrize(
'another_object',
Expand Down Expand Up @@ -179,7 +180,7 @@ def test_check_cancelled_token(token_fabric):

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_check_superpower_not_raised(token_fabric):
token = token_fabric()
Expand Down Expand Up @@ -226,7 +227,7 @@ def test_check_cancelled_token_nested(token_fabric_1, token_fabric_2):

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_get_report_not_cancelled(token_fabric):
token = token_fabric()
Expand Down Expand Up @@ -271,7 +272,7 @@ def test_get_report_cancelled(token_fabric_1, token_fabric_2):

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_type_conversion_not_cancelled(token_fabric):
token = token_fabric()
Expand Down Expand Up @@ -330,7 +331,7 @@ def test_repr_if_nested_token_is_cancelled(token_fabric_1, token_fabric_2, cance
)
@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
@pytest.mark.parametrize(
'do_await',
Expand All @@ -351,7 +352,7 @@ def test_wait_wrong_parameters(token_fabric, parameters, do_await):

@pytest.mark.parametrize(
'token_fabric',
ALL_TOKENS_FABRICS,
ALL_TOKENS_FABRICS + [DefaultToken],
)
def test_async_wait_timeout(token_fabric):
timeout = 0.0001
Expand Down
64 changes: 64 additions & 0 deletions tests/units/tokens/test_default_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import sys

import pytest
import full_match

from cantok import DefaultToken, SimpleToken, ImpossibleCancelError


def test_dafault_token_is_not_cancelled_by_default():
token = DefaultToken()

assert bool(token)
assert token.cancelled == False
assert token.is_cancelled() == False
assert token.keep_on() == True

token.check()


def test_you_can_set_cancelled_attribute_as_false():
token = DefaultToken()

token.cancelled = False

assert bool(token)
assert token.cancelled == False
assert token.is_cancelled() == False
assert token.keep_on() == True

token.check()


def test_you_cant_set_true_as_cancelled_attribute():
token = DefaultToken()

with pytest.raises(ImpossibleCancelError, match=full_match('You cannot cancel a default token.')):
token.cancelled = True

assert token.cancelled == False


def test_you_cannot_cancel_default_token_by_standard_way():
token = DefaultToken()

with pytest.raises(ImpossibleCancelError, match=full_match('You cannot cancel a default token.')):
token.cancel()

assert token.cancelled == False


def test_str_for_default_token():
assert str(DefaultToken()) == '<DefaultToken (not cancelled)>'


@pytest.mark.skipif(sys.version_info >= (3, 10), reason='Format of this exception messages was changed.')
def test_you_cannot_neste_another_token_to_default_one_old_pythons():
with pytest.raises(TypeError, match=full_match('__init__() takes 1 positional argument but 2 were given')):
DefaultToken(SimpleToken(TypeError))


@pytest.mark.skipif(sys.version_info < (3, 10), reason='Format of this exception messages was changed.')
def test_you_cannot_neste_another_token_to_default_one_new_pythons():
with pytest.raises(TypeError, match=full_match('DefaultToken.__init__() takes 1 positional argument but 2 were given')):
DefaultToken(SimpleToken(TypeError))

0 comments on commit 108b08c

Please sign in to comment.