Skip to content
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
2 changes: 1 addition & 1 deletion cantok/tokens/abstract/abstract_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def wait(self, step: Union[int, float] = 0.0001, timeout: Optional[Union[int, fl
>>>
>>> token = TimeoutToken(5)
>>> token.wait() # blocks for ~5 seconds, then returns
>>> asyncio.run(token.wait()) # non-blocking, inside an asyncio event loop
>>> asyncio.run(TimeoutToken(5).wait()) # non-blocking, inside an asyncio event loop
"""
if step < 0:
raise ValueError('The token polling iteration time cannot be less than zero.')
Expand Down
6 changes: 3 additions & 3 deletions cantok/tokens/abstract/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from cantok.tokens.abstract.cancel_cause import CancelCause

if version_info >= (3, 10):
addictional_fields: Dict[str, bool] = {'slots': True} # pragma: no cover
additional_fields: Dict[str, bool] = {'slots': True} # pragma: no cover
else:
addictional_fields = {} # pragma: no cover
additional_fields = {} # pragma: no cover

@dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore]
@dataclass(frozen=True, **additional_fields) # type: ignore[call-overload, unused-ignore]
class CancellationReport:
cause: CancelCause
from_token: 'AbstractToken' # type: ignore[name-defined]
7 changes: 4 additions & 3 deletions cantok/tokens/counter_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ class CounterToken(ConditionToken):
iterations of a loop without tracking state externally.

:param counter: Number of iterations before cancellation. Must be >= 0.
:param direct: If True (default), counter decrements even when polled
indirectly through a parent token. If False, indirect polls
are rolled back, so only direct checks consume the counter.
:param direct: If False, the counter decrements even when polled
indirectly through a parent token. If True (default),
indirect polls are rolled back, so only direct checks
consume the counter.

>>> token = CounterToken(3)
>>> while token:
Expand Down
2 changes: 1 addition & 1 deletion docs/ecosystem/about_ecosystem.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
We recommend adding token support to all your libraries where possible. If you have written such a project or added token support to an existing project, please let us know by writing an [issue](https://github.com/pomponchik/cantok/issues/new). If possible, information about the project will be added here.
We recommend adding token support to all your libraries where possible. If you have written such a project or added token support to an existing project, please let us know by opening an [issue](https://github.com/pomponchik/cantok/issues/new). If possible, information about the project will be added here.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![logo](https://raw.githubusercontent.com/pomponchik/cantok/main/docs/assets/logo_5.png)


Cancellation Token is a pattern that allows us to refuse to continue calculations that we no longer need. It is implemented out of the box in many programming languages, for example in [C#](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) and in [Go](https://pkg.go.dev/context). However, there was still no sane implementation in Python, until the [cantok](https://github.com/pomponchik/cantok) library appeared.
Cancellation Token is a pattern that allows us to cancel calculations that we no longer need. It is implemented out of the box in many programming languages, for example in [C#](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) and in [Go](https://pkg.go.dev/context). However, there was still no sane implementation in Python, until the [cantok](https://github.com/pomponchik/cantok) library appeared.
2 changes: 1 addition & 1 deletion docs/quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ while token:
print(counter)
```

In this code, we use a token that describes several restrictions: on the [number of iterations](types_of_tokens/CounterToken.md) of the loop, on [time](types_of_tokens/TimeoutToken.md), as well as on the [occurrence](types_of_tokens/ConditionToken.md) of a random unlikely event. When any of the indicated events occur, the loop stops.
In this code, we use a token that describes several restrictions: on the [number of iterations](types_of_tokens/CounterToken.md) of the loop, on [time](types_of_tokens/TimeoutToken.md), as well as on the [occurrence](types_of_tokens/ConditionToken.md) of a random unlikely event. When any of the listed events occur, the loop stops.

In fact, the library's capabilities are much broader. Read the documentation below.
6 changes: 3 additions & 3 deletions docs/the_pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

Cancellation Token is a pattern that allows us to cancel calculations that we no longer need. It is implemented out of the box in many programming languages, for example in [C#](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) and in [Go](https://pkg.go.dev/context). However, there was still no sane implementation in Python, until the [cantok](https://github.com/pomponchik/cantok) library appeared.

The essence of the pattern is that we pass special objects to functions and constructors, by which the executed code can understand whether it should continue its execution or not. When deciding whether to allow code execution to continue, this object can both take into account the restrictions imposed on it, such as the maximum code execution time, and receive signals about the need to stop from the outside, for example from another thread or a coroutine. Thus, we do not nail down the logic associated with stopping code execution, for example, by directly tracking loop counters, but implement [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) of this restriction.
The essence of the pattern is that we pass special objects to functions and constructors, through which the executing code can determine whether it should continue its execution. When deciding whether to allow code execution to continue, this object can both take into account the restrictions imposed on it, such as the maximum code execution time, and receive signals about the need to stop from the outside, for example from another thread or a coroutine. Thus, we do not nail down the logic associated with stopping code execution, for example, by directly tracking loop counters, but implement [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) of this restriction.

In addition, the pattern assumes that various restrictions can be combined in unlimited combinations with each other: if at least one of the restrictions is not met, code execution will be interrupted. It is assumed that each function in the call stack will call other functions, passing its token directly to them, or wrapping it in another token with stricter restrictions.
In addition, the pattern assumes that various restrictions can be combined with each other without limit: if at least one of the restrictions is not met, code execution will be interrupted. It is assumed that each function in the call stack will call other functions, passing its token directly to them, or wrapping it in another token with stricter restrictions.

Unlike other ways of stopping code execution, tokens do not force the execution thread to be interrupted. The interruption occurs "gently", allowing the code to terminate correctly, release all held resources and restore consistency.

It is highly desirable for library developers to use this pattern for any long-running operations. Your function can accept a token as an optional argument, with a default value that imposes minimal restrictions or none at all. If the user wishes, they can pass their token to it, imposing stricter restrictions on the library code. In addition to a more convenient and extensible API, this will give the library an advantage in the form of better testability, because the restrictions are no longer hardcoded into the function, which means they can be made whatever you want for the test. In addition, the library developer no longer needs to think about all the numerous restrictions that can be imposed on their code - the user can take care of it themselves if they need to.
It is highly desirable for library developers to use this pattern for any long-running operations. Your function can accept a token as an optional argument, with a default value that imposes minimal restrictions or none at all. If the user wishes, they can pass their token to it, imposing stricter restrictions on the library code. In addition to a more convenient and extensible API, this will give the library an advantage in the form of better testability, because the restrictions are no longer hardcoded into the function, which means they can be set to any value needed for testing. In addition, the library developer no longer needs to think about all the numerous restrictions that can be imposed on their code the user can take care of it themselves if they need to.
10 changes: 5 additions & 5 deletions docs/types_of_tokens/ConditionToken.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
`ConditionToken` has a superpower: it can check arbitrary conditions. In addition to this, it can do all the same things as [`SimpleToken`](../types_of_tokens/SimpleToken.md). The condition is a function that returns an answer to the question "has the token been cancelled" (`True`/`False`), it is passed to the token as the first required argument during initialization:
`ConditionToken` has a superpower: it can check arbitrary conditions. In addition to this, it can do all the same things as [`SimpleToken`](../types_of_tokens/SimpleToken.md). The condition is a function that returns an answer to the question "should the token be cancelled" (`True`/`False`); it is passed to the token as the first required argument during initialization:

```python
from cantok import ConditionToken
Expand All @@ -19,10 +19,10 @@ def function(): raise ValueError

token = ConditionToken(function, suppress_exceptions=False)

token.cancelled # ValueError has been raised.
token.cancelled # ValueError will be raised.
```

When using exception suppression mode, the `cancelled` attribute will contain `False` by default in case of an exception. If you want to change this, pass `default=True`.
When using exception suppression mode, the `cancelled` attribute will be `False` by default in case of an exception. If you want to change this, pass `default=True`.

```python
def function(): raise ValueError
Expand All @@ -44,7 +44,7 @@ token.check()
#> 2
```

By analogy with `before`, you can pass a function that will be executed after checking the condition as the `after` argument:
By analogy with `before`, you can pass a function as the `after` argument that will be executed after checking the condition:

```python
token = ConditionToken(lambda: print(1), after=lambda: print(2))
Expand All @@ -54,7 +54,7 @@ token.check()
#> 2
```

`ConditionToken` has another feature. If the condition has evaluated to True at least once and cancelled the token, then the condition is no longer polled and the token is permanently considered cancelled. You can change this by manipulating the `caching` parameter when creating a token. By setting it to `False`, you will make sure that the condition is polled every time.
`ConditionToken` has another feature. If the condition has returned True at least once and cancelled the token, then the condition is no longer polled and the token is permanently considered cancelled. You can change this by manipulating the `caching` parameter when creating a token. By setting it to `False`, you will make sure that the condition is polled every time.

```python
counter = 0
Expand Down
2 changes: 1 addition & 1 deletion docs/types_of_tokens/CounterToken.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
`CounterToken` is the least intuitive of the tokens provided by this library. Do not use it if you are not sure that you understand how it works correctly. However, it can be very useful in situations where you want to limit the number of attempts to perform an operation.
`CounterToken` is the least intuitive of the tokens provided by this library. Do not use it if you are not sure that you understand how it works. However, it can be very useful in situations where you want to limit the number of attempts to perform an operation.

`CounterToken` is initialized with an integer greater than or equal to zero. Each time cancellation is checked, this number is decremented by one. When this number becomes zero, the token is considered cancelled:

Expand Down
4 changes: 2 additions & 2 deletions docs/types_of_tokens/DefaultToken.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
`DefaultToken` is a type of token that cannot be cancelled. Otherwise, it behaves like a regular token, but if you try to cancel it, you will get an exception:
`DefaultToken` is a type of token that cannot be cancelledif you try to cancel it, an exception will be raised:

```python
from cantok import AbstractToken, DefaultToken
Expand All @@ -8,7 +8,7 @@ DefaultToken().cancel()
#> cantok.errors.ImpossibleCancelError: You cannot cancel a default token.
```

In addition, you cannot embed other tokens in `DefaultToken`.
In addition, you cannot embed other tokens in `DefaultToken`. In all other respects, it behaves like a regular token.

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

Expand Down
2 changes: 1 addition & 1 deletion docs/types_of_tokens/SimpleToken.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The base token is `SimpleToken`. It has no built-in automation that can cancel it. The only way to cancel `SimpleToken` is to explicitly call the `cancel()` method from it.
The base token is `SimpleToken`. It has no built-in automation that can cancel it. The only way to cancel `SimpleToken` is to explicitly call the `cancel()` method on it.

```python
from cantok import SimpleToken
Expand Down
2 changes: 1 addition & 1 deletion docs/types_of_tokens/TimeoutToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ sleep(10)
print(token.cancelled) #> True
```

Just like `ConditionToken`, `TimeoutToken` can include other tokens:
Just like `ConditionToken`, `TimeoutToken` can embed other tokens:

```python
token = TimeoutToken(45, SimpleToken(), TimeoutToken(5), CounterToken(20)) # Includes all additional restrictions of the passed tokens.
Expand Down
6 changes: 3 additions & 3 deletions docs/what_are_tokens/cancel_and_read_the_status.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Each token object has a `cancelled` attribute and a `cancel()` method. By the attribute, you can find out whether this token has been cancelled:
Each token object has a `cancelled` attribute and a `cancel()` method. Using the attribute, you can find out whether this token has been cancelled:

```python
from cantok import SimpleToken
Expand All @@ -9,7 +9,7 @@ token.cancel()
print(token.cancelled) #> True
```

The cancelled attribute is dynamically calculated and takes into account, among other things, specific conditions that are checked by a specific token. Here is an example with a [token that measures time](../types_of_tokens/TimeoutToken.md):
The cancelled attribute is dynamically calculated and takes into account, among other things, conditions specific to that token type. Here is an example with a [token that measures time](../types_of_tokens/TimeoutToken.md):

```python
from time import sleep
Expand Down Expand Up @@ -62,7 +62,7 @@ print(bool(token)) #> False
print(token.keep_on()) #> False
```

There is another method that is close in meaning to `is_cancelled()` — `check()`. It does nothing if the token is not cancelled, or raises an exception if cancelled. If the token was cancelled by calling the `cancel()` method, a `CancellationError` exception will be raised:
There is another method that is close in meaning to `is_cancelled()` — `check()`. It does nothing if the token is not cancelled, or raises an exception if it is cancelled. If the token was cancelled by calling the `cancel()` method, a `CancellationError` exception will be raised:

```python
from cantok import SimpleToken
Expand Down
2 changes: 1 addition & 1 deletion docs/what_are_tokens/embedding.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
You can embed an unlimited number of other tokens in one token by passing them as arguments during initialization. Each time checking whether it has been cancelled, the token first checks its cancellation rules, and if it has not been canceled itself, then it checks the tokens nested in it. Thus, one cancelled token nested in another non-cancelled token cancels it:
You can embed an unlimited number of other tokens in one token by passing them as arguments during initialization. Each time it checks whether it has been cancelled, the token first checks its cancellation rules, and if it has not been cancelled itself, then it checks the tokens nested in it. Thus, one cancelled token nested in another non-cancelled token cancels it:

```python
from cantok import SimpleToken
Expand Down
6 changes: 3 additions & 3 deletions docs/what_are_tokens/exceptions.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
When a token is cancelled, you can call the `check()` method from it and an exception will be raised:
When a token is cancelled, you can call the `check()` method on it and an exception will be raised:

```python
from cantok import TimeoutToken
Expand All @@ -17,9 +17,9 @@ Each type of token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md))
- [`TimeoutToken`](../types_of_tokens/TimeoutToken.md) -> `TimeoutCancellationError`
- [`CounterToken`](../types_of_tokens/CounterToken.md) -> `CounterCancellationError`

When you call the `check()` method on any token, one of two things will happen. If it (or any of the tokens nested in it) has been cancelled by calling the `cancel()` method, `CancellationError` will always be raised. But if the cancellation occurred as a result of the unique ability of the token, such as for `TimeoutToken` - timeout expiration, then an exception specific to this type of token will be raised.
When you call the `check()` method on any token, one of two things will happen. If it (or any of the tokens nested in it) has been cancelled by calling the `cancel()` method, `CancellationError` will always be raised. But if the cancellation occurred as a result of the unique ability of the token, such as timeout expiration for `TimeoutToken`, then an exception specific to this type of token will be raised.

`ConditionCancellationError`, `TimeoutCancellationError` and `CounterCancellationError` are inherited from `CancellationError`, so if you're not sure which exception specifically you're catching, catch `CancellationError`. But also all the listed exceptions can always be imported separately:
`ConditionCancellationError`, `TimeoutCancellationError`, and `CounterCancellationError` are inherited from `CancellationError`, so if you're not sure which specific exception you're catching, catch `CancellationError`. All of the listed exceptions can also be imported separately:

```python
from cantok import CancellationError, ConditionCancellationError, TimeoutCancellationError, CounterCancellationError
Expand Down
4 changes: 2 additions & 2 deletions docs/what_are_tokens/in_general.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ Additionally, there is a 5th type that cannot be cancelled:

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

- Each token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md)) can be cancelled manually, and some types of tokens can cancel themselves when a condition or timeout occurs. It doesn't matter how the token was cancelled, you work with it the same way.
- Each token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md)) can be cancelled manually, and some types of tokens can cancel themselves when a condition or timeout occurs. It doesn't matter how the token was cancelled; 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 across multiple processes.

- Token cancellation is a one-way operation. A token that has already been cancelled cannot be restored.

- All token classes inherit from `AbstractToken` and have a single interface that defines how they can be cancelled, how to find out their status, how to wait for their cancellation and much more. If you are writing a function that accepts an unknown token type, you can use `AbstractToken` to hint types:
- All token classes inherit from `AbstractToken` and have a single interface that defines how they can be cancelled, how to find out their status, how to wait for their cancellation, and much more. If you are writing a function that accepts an unknown token type, you can use `AbstractToken` for type hints:

```python
from cantok import AbstractToken
Expand Down
Loading
Loading