diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index 20f4742..19d9939 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -8,7 +8,7 @@ assignees: pomponchik ## It's cool that you're here! -Documentation is an important part of the project, we strive to make it high-quality and keep it up to date. Please adjust this template by outlining your proposal. +Documentation is an important part of the project; we strive to make it high-quality and keep it up to date. Please adjust this template by outlining your proposal. ## Type of action @@ -18,7 +18,7 @@ What do you want to do: remove something, add it, or change it? ## Where? -Specify which part of the documentation you want to make a change to? For example, the name of an existing documentation section or the line number in a file `README.md`. +Specify which part of the documentation you want to make a change to. For example, the name of an existing documentation section or the line number in a file `README.md`. ## The essence diff --git a/.gitignore b/.gitignore index a1c96fb..825653f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ test.py html .qwen .claude +CLAUDE.md diff --git a/README.md b/README.md index 71ab1fe..d8dfb76 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ![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. +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. ## Quick start diff --git a/cantok/tokens/abstract/abstract_token.py b/cantok/tokens/abstract/abstract_token.py index 1b5d502..5a77aee 100644 --- a/cantok/tokens/abstract/abstract_token.py +++ b/cantok/tokens/abstract/abstract_token.py @@ -11,6 +11,34 @@ class AbstractToken(ABC): + """ + Abstract base class for all cancellation tokens. + + A cancellation token represents a signal that can be used to cooperatively + cancel a long-running operation. Most subclasses add an automatic cancellation + condition (superpower) evaluated on every check; SimpleToken and DefaultToken + rely on manual cancellation only. + + Tokens can be composed with the + operator: the resulting token is cancelled + when any of the combined tokens is cancelled. + + Use AbstractToken as a type hint when a function accepts any token type. + Pass DefaultToken() as the default to make the token optional: + + >>> def run(token: AbstractToken = DefaultToken()) -> bool: + ... return token.keep_on() + >>> run() # DefaultToken never cancels + True + >>> run(SimpleToken().cancel()) # cancelled token passed explicitly + False + + The idiomatic loop pattern: + + >>> token = SimpleToken() + >>> while token: + ... ... # loop exits when token is cancelled + """ + exception = CancellationError _rollback_if_nondirect_polling = False @@ -112,6 +140,20 @@ def __bool__(self) -> bool: @property def cancelled(self) -> bool: + """ + Whether the token is currently cancelled. + + Evaluated dynamically on each access, taking into account the token's own + cancellation rules and all embedded tokens. Setting to True cancels the token; + setting to False on an already cancelled token raises ValueError. + + >>> token = SimpleToken() + >>> token.cancelled + False + >>> token.cancel() + >>> token.cancelled + True + """ return self.is_cancelled() @cancelled.setter @@ -123,12 +165,53 @@ def cancelled(self, new_value: bool) -> None: raise ValueError('You cannot restore a cancelled token.') def keep_on(self) -> bool: + """ + Returns True if the token is not cancelled, False otherwise. + The opposite of is_cancelled(). + + >>> token = SimpleToken() + >>> token.keep_on() + True + >>> token.cancel() + >>> token.keep_on() + False + """ return not self.is_cancelled() def is_cancelled(self, direct: bool = True) -> bool: + """ + Returns True if the token is cancelled, False otherwise. + + :param direct: When False, tokens with rollback behaviour (e.g. CounterToken + with direct=True) do not apply their side effects while being + polled indirectly through a parent token. Defaults to True. + + >>> token = SimpleToken() + >>> token.is_cancelled() + False + >>> token.cancel() + >>> token.is_cancelled() + True + """ return self._get_report(direct=direct).cause != CancelCause.NOT_CANCELLED def wait(self, step: Union[int, float] = 0.0001, timeout: Optional[Union[int, float]] = None) -> Awaitable: # type: ignore[type-arg] + """ + Waits until the token is cancelled. + + When used with ``await``, runs non-blocking inside an asyncio event loop. + When called without ``await``, blocks the current thread. + + :param step: Interval between status checks, in seconds. Defaults to 0.0001. + :param timeout: Maximum time to wait, in seconds. If exceeded, + raises TimeoutCancellationError. Defaults to None (no limit). + + >>> import asyncio + >>> + >>> token = TimeoutToken(5) + >>> token.wait() # blocks for ~5 seconds, then returns + >>> asyncio.run(token.wait()) # non-blocking, inside an asyncio event loop + """ if step < 0: raise ValueError('The token polling iteration time cannot be less than zero.') if timeout is not None and timeout < 0: @@ -146,10 +229,33 @@ def wait(self, step: Union[int, float] = 0.0001, timeout: Optional[Union[int, fl return WaitCoroutineWrapper(step, self + token, token) def cancel(self) -> 'AbstractToken': + """ + Cancels the token. Returns the token itself to allow method chaining. + + Cancellation is irreversible: once cancelled, the token cannot be restored. + + >>> token = SimpleToken() + >>> token.cancel() + >>> token.cancelled + True + """ self._cancelled = True return self def check(self) -> None: + """ + Raises an exception if the token is cancelled; does nothing otherwise. + + The exception type depends on the cancellation cause: + - Manual cancellation via cancel() raises CancellationError. + - Automatic cancellation by a specific token type raises the corresponding + subclass (e.g. TimeoutCancellationError for TimeoutToken). + + >>> token = SimpleToken() + >>> token.check() # nothing happens + >>> token.cancel() + >>> token.check() # raises CancellationError + """ with self._lock: report = self._get_report() diff --git a/cantok/tokens/condition_token.py b/cantok/tokens/condition_token.py index f7c9e24..c67c134 100644 --- a/cantok/tokens/condition_token.py +++ b/cantok/tokens/condition_token.py @@ -6,6 +6,31 @@ class ConditionToken(AbstractToken): + """ + A token that cancels automatically when a condition function returns True. + + The condition function is evaluated on every cancellation check. Once it + returns True, the result is cached by default and the token stays cancelled. + + :param function: A callable returning bool. Called on each cancellation check. + :param suppress_exceptions: If True (default), exceptions from the function + are swallowed and treated as the default value. + :param default: Value to use when the function raises and suppress_exceptions + is True. Defaults to False. + :param before: Callable invoked before the condition function on each check. + :param after: Callable invoked after the condition function on each check. + :param caching: If True (default), the token stays cancelled once the + condition has returned True, without re-evaluating it. + + >>> items = [] + >>> token = ConditionToken(lambda: len(items) >= 3) + >>> token.cancelled + False + >>> items += [1, 2, 3] + >>> token.cancelled + True + """ + exception = ConditionCancellationError def __init__(self, function: Callable[[], bool], *tokens: AbstractToken, cancelled: bool = False, suppress_exceptions: bool = True, default: bool = False, before: Callable[[], Any] = lambda: None, after: Callable[[], Any] = lambda: None, caching: bool = True): # noqa: PLR0913 @@ -25,7 +50,7 @@ def _superpower(self) -> bool: if not self._suppress_exceptions: self._before() - result = self.run_function() + result = self._run_function() self._after() return result @@ -34,13 +59,13 @@ def _superpower(self) -> bool: with suppress(Exception): self._before() with suppress(Exception): - result = self.run_function() + result = self._run_function() with suppress(Exception): self._after() return result - def run_function(self) -> bool: + def _run_function(self) -> bool: result = self._function() if not isinstance(result, bool): diff --git a/cantok/tokens/counter_token.py b/cantok/tokens/counter_token.py index c31d5aa..9a814fb 100644 --- a/cantok/tokens/counter_token.py +++ b/cantok/tokens/counter_token.py @@ -5,6 +5,23 @@ class CounterToken(ConditionToken): + """ + A token that cancels automatically after a fixed number of iterations. + + The internal counter decrements on each direct cancellation check. When it + reaches zero, the token is cancelled. Useful for limiting the number of + 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. + + >>> token = CounterToken(3) + >>> while token: + ... ... # loop body executes exactly 3 times + """ + exception = CounterCancellationError def __init__(self, counter: int, *tokens: AbstractToken, cancelled: bool = False, direct: bool = True): diff --git a/cantok/tokens/default_token.py b/cantok/tokens/default_token.py index 52f7908..179624f 100644 --- a/cantok/tokens/default_token.py +++ b/cantok/tokens/default_token.py @@ -3,6 +3,21 @@ class DefaultToken(AbstractToken): + """ + An immutable token that never cancels. + + Useful as a neutral default argument: a function that accepts a token can + receive a DefaultToken when no real cancellation is needed, without + requiring None checks. Calling cancel() raises ImpossibleCancelError. + + >>> def run(token: AbstractToken = DefaultToken()) -> bool: + ... return token.keep_on() + >>> run() # True — DefaultToken never cancels + True + >>> run(SimpleToken()) # True — SimpleToken not yet cancelled + True + """ + exception = ImpossibleCancelError def __init__(self) -> None: diff --git a/cantok/tokens/simple_token.py b/cantok/tokens/simple_token.py index a743000..77ee2c8 100644 --- a/cantok/tokens/simple_token.py +++ b/cantok/tokens/simple_token.py @@ -3,6 +3,18 @@ class SimpleToken(AbstractToken): + """ + A basic cancellation token with no automatic cancellation condition. + + Can only be cancelled explicitly by calling cancel() or setting + cancelled = True. Useful as a manual stop signal passed between threads. + + >>> token = SimpleToken() + >>> token.cancel() + >>> token.cancelled + True + """ + exception = CancellationError def _superpower(self) -> bool: diff --git a/cantok/tokens/timeout_token.py b/cantok/tokens/timeout_token.py index f23b66d..b751b55 100644 --- a/cantok/tokens/timeout_token.py +++ b/cantok/tokens/timeout_token.py @@ -6,6 +6,28 @@ class TimeoutToken(ConditionToken): + """ + A token that cancels automatically after a specified duration. + + The timeout is measured from the moment the token is created. When the + deadline is reached, any cancellation check will return True and subsequent + check() calls will raise TimeoutCancellationError. + + :param timeout: Duration in seconds before cancellation. Must be >= 0. + :param monotonic: If True, uses time.monotonic_ns() instead of + time.perf_counter(), which is unaffected by system + clock adjustments. Defaults to False. + + >>> import time + >>> + >>> token = TimeoutToken(0.1) + >>> token.cancelled + False + >>> time.sleep(0.2) + >>> token.cancelled + True + """ + exception = TimeoutCancellationError def __init__(self, timeout: Union[int, float], *tokens: AbstractToken, cancelled: bool = False, monotonic: bool = False): diff --git a/docs/ecosystem/projects/regular_functions_calling.md b/docs/ecosystem/projects/regular_functions_calling.md index 544f89a..4855805 100644 --- a/docs/ecosystem/projects/regular_functions_calling.md +++ b/docs/ecosystem/projects/regular_functions_calling.md @@ -38,7 +38,7 @@ 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. +sleep(1.5) # We specify a slightly longer sleep time than the token timeout to allow for the overhead of creating the metronome object. print(metronome.stopped) #> True ``` diff --git a/docs/index.md b/docs/index.md index 8ec0893..f4f8c2a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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. +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. diff --git a/docs/installation.md b/docs/installation.md index cafcb05..bf114f0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,4 +1,4 @@ -Install it from [Pypi](https://pypi.org/project/cantok/): +Install it from [PyPI](https://pypi.org/project/cantok/): ```bash pip install cantok diff --git a/docs/quick_start.md b/docs/quick_start.md index 1ca0911..8bd1e42 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -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 cycle, 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 cycle 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 indicated events occur, the loop stops. -In fact, the library's capabilities are much broader, read the documentation below. +In fact, the library's capabilities are much broader. Read the documentation below. diff --git a/docs/the_pattern.md b/docs/the_pattern.md index c9492e7..8b26baa 100644 --- a/docs/the_pattern.md +++ b/docs/the_pattern.md @@ -1,11 +1,11 @@ # The pattern -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. -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 take into account both the restrictions specified to 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 cycle 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, 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. -In addition, the pattern assumes that various restrictions can be combined indefinitely 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, throwing its token directly to them, or wrapping it in another token, with a stricter restriction imposed on it. +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. -Unlike other ways of interrupting code execution, tokens do not force the execution thread to be interrupted forcibly. The interruption occurs "gently", allowing the code to terminate correctly, return all occupied resources and restore consistency. +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-term composite 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, he can transfer his token there, 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 sewn directly 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 his code - the user can take care of it himself if he needs 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 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. diff --git a/docs/types_of_tokens/ConditionToken.md b/docs/types_of_tokens/ConditionToken.md index a3aef4f..0d0e64f 100644 --- a/docs/types_of_tokens/ConditionToken.md +++ b/docs/types_of_tokens/ConditionToken.md @@ -1,4 +1,4 @@ -`ConditionToken` has 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 canceled" (`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 "has the token been cancelled" (`True`/`False`), it is passed to the token as the first required argument during initialization: ```python from cantok import ConditionToken @@ -12,17 +12,17 @@ while token: print(counter) #> 5 ``` -By default, if the passed function raises an exception, it will be silently suppressed. However, you can make the raised exceptions explicit by setting the `suppress_exceptions` parameter to `False`: +By default, if the passed function raises an exception, the exception will be silently suppressed. However, you can make the raised exceptions explicit by setting the `suppress_exceptions` parameter to `False`: ```python def function(): raise ValueError token = ConditionToken(function, suppress_exceptions=False) -token.cancelled # ValueError has risen. +token.cancelled # ValueError has been raised. ``` -If you still use exception suppression mode, by default, in case of an exception, the `canceled` attribute will contain `False`. If you want to change this, pass it there as the `default` parameter - `True`. +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`. ```python def function(): raise ValueError @@ -54,7 +54,7 @@ token.check() #> 2 ``` -`ConditionToken` has another feature. If the condition has detonated at least once and canceled it, then the condition is no longer polled and the token is permanently considered canceled. 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 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. ```python counter = 0 @@ -75,4 +75,4 @@ print(token.cancelled) #> False ``` -However, we do not recommend doing this. In the vast majority of cases, you do not want your token to be able to roll back the fact of its cancellation. If the token has been cancelled once, it must remain cancelled. Manipulate the `caching` parameter only if you are sure that you understand what you are doing. +However, we do not recommend doing this. In the vast majority of cases, you do not want your token to be able to undo its cancellation. If the token has been cancelled once, it must remain cancelled. Manipulate the `caching` parameter only if you are sure that you understand what you are doing. diff --git a/docs/types_of_tokens/CounterToken.md b/docs/types_of_tokens/CounterToken.md index 6e99e26..4d82893 100644 --- a/docs/types_of_tokens/CounterToken.md +++ b/docs/types_of_tokens/CounterToken.md @@ -1,6 +1,6 @@ -`CounterToken` is the most ambiguous of the tokens presented 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 correctly. 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 zero. At each calculation of the answer to the question whether it is canceled, this number is reduced by one. When this number becomes zero, the token is considered canceled: +`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: ```python from cantok import CounterToken @@ -14,13 +14,13 @@ while token: print(counter) #> 5 ``` -The counter inside the `CounterToken` is reduced under one of three conditions: +The counter inside the `CounterToken` is decremented under one of three conditions: - Access to the `cancelled` attribute. - Calling the `is_cancelled()` method. - Calling the `keep_on()` method. -If you use `CounterToken` inside other tokens, the wrapping token can specify the status of the `CounterToken`. For security reasons, this operation does not decrease the counter. However, if for some reason you need it to decrease, pass `direct` - `False` as an argument: +If you use `CounterToken` inside other tokens, the wrapping token can query the status of the `CounterToken`. To avoid unintended side effects, querying the status does not decrease the counter. However, if for some reason you need it to decrease, pass `direct=False` as an argument: ```python from cantok import SimpleToken, CounterToken diff --git a/docs/types_of_tokens/SimpleToken.md b/docs/types_of_tokens/SimpleToken.md index 6e8f9da..0d1233b 100644 --- a/docs/types_of_tokens/SimpleToken.md +++ b/docs/types_of_tokens/SimpleToken.md @@ -9,4 +9,4 @@ token.cancel() print(token.cancelled) #> True ``` -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. +There is not much more to tell about it if you have read [the section](../what_are_tokens/in_general.md) about tokens in general. diff --git a/docs/types_of_tokens/TimeoutToken.md b/docs/types_of_tokens/TimeoutToken.md index 8f3cb59..edb1cc1 100644 --- a/docs/types_of_tokens/TimeoutToken.md +++ b/docs/types_of_tokens/TimeoutToken.md @@ -1,4 +1,4 @@ -`TimeoutToken` is automatically canceled after the time specified in seconds in the class constructor: +`TimeoutToken` is automatically cancelled after the time specified in seconds in the class constructor: ```python from time import sleep @@ -16,7 +16,7 @@ Just like `ConditionToken`, `TimeoutToken` can include other tokens: token = TimeoutToken(45, SimpleToken(), TimeoutToken(5), CounterToken(20)) # Includes all additional restrictions of the passed tokens. ``` -By default, time is measured using [`perf_counter`](https://docs.python.org/3/library/time.html#time.perf_counter) as the most accurate way to measure time. In extremely rare cases, you may need to use [monotonic](https://docs.python.org/3/library/time.html#time.monotonic_ns)-time, for this use the appropriate initialization argument: +By default, time is measured using [`perf_counter`](https://docs.python.org/3/library/time.html#time.perf_counter) as the most accurate option. In extremely rare cases, you may need to use [monotonic](https://docs.python.org/3/library/time.html#time.monotonic_ns) time; for this, use the appropriate initialization argument: ```python token = TimeoutToken(33, monotonic=True) diff --git a/docs/what_are_tokens/cancel_and_read_the_status.md b/docs/what_are_tokens/cancel_and_read_the_status.md index 3789751..256a0c9 100644 --- a/docs/what_are_tokens/cancel_and_read_the_status.md +++ b/docs/what_are_tokens/cancel_and_read_the_status.md @@ -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 canceled: +Each token object has a `cancelled` attribute and a `cancel()` method. By the attribute, you can find out whether this token has been cancelled: ```python from cantok import SimpleToken @@ -34,9 +34,9 @@ print(token.cancelled) #> True print(token.is_cancelled()) #> True ``` -Choose what you like best. To the author of the library, the use of the attribute seems more beautiful, but the method call more clearly reflects the complexity of the work that is actually being done to answer the question "has the token been canceled?". +Choose what you like best. The attribute is considered more elegant, but calling the method more clearly reflects the complexity of the work actually being done to answer the question "has the token been cancelled?". -There is another method opposite to `is_cancelled()` - `keep_on()`. It answers the opposite question, and can be used in the same situations: +There is another method opposite to `is_cancelled()` — `keep_on()`. It answers the opposite question, and can be used in the same situations: ```python from cantok import SimpleToken @@ -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 canceled, or raises an exception if canceled. If the token was canceled 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 cancelled. If the token was cancelled by calling the `cancel()` method, a `CancellationError` exception will be raised: ```python from cantok import SimpleToken diff --git a/docs/what_are_tokens/embedding.md b/docs/what_are_tokens/embedding.md index 83e3cbd..5e14e7a 100644 --- a/docs/what_are_tokens/embedding.md +++ b/docs/what_are_tokens/embedding.md @@ -1,4 +1,4 @@ -An unlimited number of other tokens can be embedded in one token as arguments during initialization. Each time checking whether it has been canceled, 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 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: ```python from cantok import SimpleToken diff --git a/docs/what_are_tokens/exceptions.md b/docs/what_are_tokens/exceptions.md index fe0d261..c874a37 100644 --- a/docs/what_are_tokens/exceptions.md +++ b/docs/what_are_tokens/exceptions.md @@ -1,4 +1,4 @@ -When a token is canceled, 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 from it and an exception will be raised: ```python from cantok import TimeoutToken @@ -7,7 +7,7 @@ token = TimeoutToken(1) token.wait() token.check() #> ... -#> cantok.errors.TimeoutCancellationError: The timeout of 1 seconds has expired. +#> cantok.errors.TimeoutCancellationError: The timeout of 1 second has expired. ``` Each type of token (except [`DefaultToken`](../types_of_tokens/DefaultToken.md)) has a corresponding type of exception that can be raised in this case: @@ -17,7 +17,7 @@ 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) was canceled 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 for `TimeoutToken` - timeout expiration, 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: @@ -25,7 +25,7 @@ When you call the `check()` method on any token, one of two things will happen. from cantok import CancellationError, ConditionCancellationError, TimeoutCancellationError, CounterCancellationError ``` -You can also choose not to import these exceptions at all. For each token class, the corresponding exception class is located in the `exception` attribute: +You can also choose not to import these exceptions at all. For each token class, the corresponding exception class is accessible as the `exception` attribute: ```python from cantok import TimeoutToken, CancellationError @@ -38,7 +38,7 @@ except CancellationError as e: print(type(e) is TimeoutToken.exception) #> True ``` -And each exception object has a `token` attribute indicating the specific token that was canceled. This can be useful in situations where several tokens are nested in one another and you want to find out which one has been canceled: +And each exception object has a `token` attribute indicating the specific token that was cancelled. This can be useful in situations where several tokens are nested in one another and you want to find out which one has been cancelled: ```python from cantok import SimpleToken, TimeoutToken, CancellationError diff --git a/docs/what_are_tokens/in_general.md b/docs/what_are_tokens/in_general.md index 4c4e38b..039c3b3 100644 --- a/docs/what_are_tokens/in_general.md +++ b/docs/what_are_tokens/in_general.md @@ -1,4 +1,4 @@ -A token is an object that can tell you whether to continue the action you started, or whether it has already been canceled. +A token is an object that can tell you whether to continue the action you started, or whether it has already been cancelled. There are 4 main types of tokens in this library: @@ -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 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 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 from multiple processes. +- 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 are inherited from `AbstractToken` and have a single interface that defines how they can be canceled, how to find out their status, how to expect 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` to hint types: ```python from cantok import AbstractToken diff --git a/docs/what_are_tokens/summation.md b/docs/what_are_tokens/summation.md index 5e8c317..2504e9c 100644 --- a/docs/what_are_tokens/summation.md +++ b/docs/what_are_tokens/summation.md @@ -7,7 +7,7 @@ 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: +This feature is convenient to use if your function has received a token with certain restrictions and wants to pass it to other called functions, imposing additional restrictions: ```python from cantok import AbstractToken, TimeoutToken @@ -18,7 +18,7 @@ def function(token: AbstractToken): ... ``` -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. +The token summation operation always generates a new token. If at least one of the operand tokens is cancelled, the sum will also be cancelled. It is also guaranteed that the cancellation of this token does not lead to the cancellation of the 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 performance reasons. You may notice that some tokens disappear altogether during summation: @@ -29,11 +29,11 @@ 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: +In addition, you can safely 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. +In fact, there are quite a few effective ways to optimize the token addition operation that are implemented in the library. This operation is well optimized, so it is recommended in all cases when you need to combine the constraints of different tokens into one. diff --git a/docs/what_are_tokens/waiting.md b/docs/what_are_tokens/waiting.md index 13b567c..0cd310a 100644 --- a/docs/what_are_tokens/waiting.md +++ b/docs/what_are_tokens/waiting.md @@ -1,4 +1,4 @@ -Each token has `wait()` method, which allows you to wait for its cancellation. +Each token has a `wait()` method, which allows you to wait for its cancellation. ```python from cantok import TimeoutToken @@ -28,9 +28,9 @@ async def main(): asyncio.run(main()) ``` -Yes, it looks like magic, it is magic. The method itself finds out how it was used: inside an expression with or without the `await` keyword. In the first case, it runs in CPU-saving mode, in the second - in non-blocking event-loop mode. +Yes, it looks like magic — it is magic. The method itself finds out how it was used: inside an expression with or without the `await` keyword. In the first case, it runs in CPU-saving mode, in the second - in non-blocking event-loop mode. In addition to the above, the `wait()` method has two optional arguments: -- **`timeout`** (`int` or `float`) - the maximum waiting time in seconds. If this time is exceeded, a [`TimeoutCancellationError` exception](../what_are_tokens/waiting.md) will be raised. By default, the `timeout` is not set. -- **`step`** (`int` or `float`, by default `0.0001`) - the duration of the iteration, once in which the token state is polled, in seconds. For obvious reasons, you cannot set this value to a number that exceeds the `timeout`. +- **`timeout`** (`int` or `float`) - the maximum waiting time in seconds. If this time is exceeded, a [`TimeoutCancellationError` exception](../what_are_tokens/exceptions.md) will be raised. By default, the `timeout` is not set. +- **`step`** (`int` or `float`, by default `0.0001`) - the duration of each iteration during which the token state is polled, in seconds. For obvious reasons, you cannot set this value to a number that exceeds the `timeout`. diff --git a/pyproject.toml b/pyproject.toml index b84d0fe..b7276f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cantok" -version = "0.0.34" +version = "0.0.35" authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }] description = 'Implementation of the "Cancellation Token" pattern' readme = "README.md"