Skip to content

Commit

Permalink
Merge pull request #37 from pomponchik/develop
Browse files Browse the repository at this point in the history
0.0.26
  • Loading branch information
pomponchik authored Aug 4, 2024
2 parents f2f2c15 + 6ae8b1e commit fcba83c
Show file tree
Hide file tree
Showing 21 changed files with 768 additions and 97 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
venv
venv2
.pytest_cache
*.egg-info
build
Expand Down
2 changes: 1 addition & 1 deletion .ruff.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ignore = ['E501', 'E712']
ignore = ['E501', 'E712', 'F821']
2 changes: 1 addition & 1 deletion cantok/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cantok.tokens.abstract_token import AbstractToken as AbstractToken # noqa: F401
from cantok.tokens.abstract.abstract_token import AbstractToken as AbstractToken # noqa: F401
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
Expand Down
7 changes: 3 additions & 4 deletions cantok/errors.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from typing import Any


class CancellationError(Exception):
def __init__(self, message: str, token: Any):
token: 'AbstractToken' # type: ignore[name-defined]

def __init__(self, message: str, token: 'AbstractToken') -> None: # type: ignore[name-defined]
self.token = token
super().__init__(message)

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,72 +1,14 @@
import weakref
from sys import getrefcount
from enum import Enum
from time import sleep as sync_sleep
from asyncio import sleep as async_sleep
from abc import ABC, abstractmethod
from threading import RLock
from dataclasses import dataclass
from typing import List, Dict, Awaitable, Optional, Union, Any
from types import TracebackType
from collections.abc import Coroutine

from cantok.errors import CancellationError


class CancelCause(Enum):
CANCELLED = 1
SUPERPOWER = 2
NOT_CANCELLED = 3

class WaitCoroutineWrapper(Coroutine): # type: ignore[type-arg]
def __init__(self, step: Union[int, float], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None:
self.step = step
self.token_for_wait = token_for_wait
self.token_for_check = token_for_check

self.flags: Dict[str, bool] = {}
self.coroutine = self.async_wait(step, self.flags, token_for_wait, token_for_check)

weakref.finalize(self, self.sync_wait, step, self.flags, token_for_wait, token_for_check, self.coroutine)

def __await__(self) -> Any:
return self.coroutine.__await__()

def send(self, value: Any) -> Any:
return self.coroutine.send(value)

def throw(self, exception_type: Any, value: Optional[Any] = None, traceback: Optional[TracebackType] = None) -> Any:
pass # pragma: no cover

def close(self) -> None:
pass # pragma: no cover

@staticmethod
def sync_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken', wrapped_coroutine: Coroutine) -> None: # type: ignore[type-arg]
if not flags.get('used', False):
if getrefcount(wrapped_coroutine) < 5:
wrapped_coroutine.close()

while token_for_wait:
sync_sleep(step)

token_for_check.check()

@staticmethod
async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None:
flags['used'] = True

while token_for_wait:
await async_sleep(step)

await async_sleep(0)

token_for_check.check()
from cantok.errors import CancellationError
from cantok.tokens.abstract.cancel_cause import CancelCause
from cantok.tokens.abstract.report import CancellationReport
from cantok.tokens.abstract.coroutine_wrapper import WaitCoroutineWrapper

@dataclass
class CancellationReport:
cause: CancelCause
from_token: 'AbstractToken'

class AbstractToken(ABC):
exception = CancellationError
Expand All @@ -75,9 +17,10 @@ class AbstractToken(ABC):
def __init__(self, *tokens: 'AbstractToken', cancelled: bool = False) -> None:
from cantok import DefaultToken

self.tokens = [token for token in tokens if not isinstance(token, DefaultToken)]
self._cancelled = cancelled
self.lock = RLock()
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.lock: RLock = RLock()

def __repr__(self) -> str:
chunks = []
Expand Down Expand Up @@ -113,7 +56,15 @@ def __add__(self, item: 'AbstractToken') -> 'AbstractToken':

from cantok import SimpleToken

return SimpleToken(self, item)
nested_tokens = []

for token in self, item:
if isinstance(token, SimpleToken) and getrefcount(token) < 6:
nested_tokens.extend(token.tokens)
else:
nested_tokens.append(token)

return SimpleToken(*nested_tokens)

def __bool__(self) -> bool:
return self.keep_on()
Expand All @@ -124,11 +75,12 @@ def cancelled(self) -> bool:

@cancelled.setter
def cancelled(self, new_value: bool) -> None:
if new_value == True:
self._cancelled = True
else:
if self._cancelled == True:
raise ValueError('You cannot restore a cancelled token.')
with self.lock:
if new_value == True:
self._cancelled = True
else:
if self.is_cancelled():
raise ValueError('You cannot restore a cancelled token.')

def keep_on(self) -> bool:
return not self.is_cancelled()
Expand Down Expand Up @@ -159,16 +111,18 @@ def get_report(self, direct: bool = True) -> CancellationReport:
cause=CancelCause.CANCELLED,
from_token=self,
)
else:
if self.check_superpower(direct):
return CancellationReport(
cause=CancelCause.SUPERPOWER,
from_token=self,
)
elif self.check_superpower(direct):
return CancellationReport(
cause=CancelCause.SUPERPOWER,
from_token=self,
)
elif self.cached_report is not None:
return self.cached_report

for token in self.tokens:
report = token.get_report(direct=False)
if report.cause != CancelCause.NOT_CANCELLED:
self.cached_report = report
return report

return CancellationReport(
Expand Down
7 changes: 7 additions & 0 deletions cantok/tokens/abstract/cancel_cause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import Enum


class CancelCause(Enum):
CANCELLED = 1
SUPERPOWER = 2
NOT_CANCELLED = 3
53 changes: 53 additions & 0 deletions cantok/tokens/abstract/coroutine_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import weakref
from sys import getrefcount
from typing import Dict, Union, Optional, Any
from types import TracebackType
from collections.abc import Coroutine
from time import sleep as sync_sleep
from asyncio import sleep as async_sleep


class WaitCoroutineWrapper(Coroutine): # type: ignore[type-arg]
def __init__(self, step: Union[int, float], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: # type: ignore[name-defined]
self.step = step
self.token_for_wait = token_for_wait
self.token_for_check = token_for_check

self.flags: Dict[str, bool] = {}
self.coroutine = self.async_wait(step, self.flags, token_for_wait, token_for_check)

weakref.finalize(self, self.sync_wait, step, self.flags, token_for_wait, token_for_check, self.coroutine)

def __await__(self) -> Any:
return self.coroutine.__await__()

def send(self, value: Any) -> Any:
return self.coroutine.send(value)

def throw(self, exception_type: Any, value: Optional[Any] = None, traceback: Optional[TracebackType] = None) -> Any:
pass # pragma: no cover

def close(self) -> None:
pass # pragma: no cover

@staticmethod
def sync_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken', wrapped_coroutine: Coroutine) -> None: # type: ignore[type-arg, name-defined]
if not flags.get('used', False):
if getrefcount(wrapped_coroutine) < 5:
wrapped_coroutine.close()

while token_for_wait:
sync_sleep(step)

token_for_check.check()

@staticmethod
async def async_wait(step: Union[int, float], flags: Dict[str, bool], token_for_wait: 'AbstractToken', token_for_check: 'AbstractToken') -> None: # type: ignore[name-defined]
flags['used'] = True

while token_for_wait:
await async_sleep(step)

await async_sleep(0)

token_for_check.check()
16 changes: 16 additions & 0 deletions cantok/tokens/abstract/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Dict
from dataclasses import dataclass
from sys import version_info

from cantok.tokens.abstract.cancel_cause import CancelCause


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

@dataclass(frozen=True, **addictional_fields) # type: ignore[call-overload, unused-ignore]
class CancellationReport:
cause: CancelCause
from_token: 'AbstractToken' # type: ignore[name-defined]
8 changes: 5 additions & 3 deletions cantok/tokens/timeout_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ def text_representation_of_superpower(self) -> str:
return str(self.timeout)

def get_extra_kwargs(self) -> Dict[str, Any]:
return {
'monotonic': self.monotonic,
}
if self.monotonic:
return {
'monotonic': self.monotonic,
}
return {}

def get_superpower_exception_message(self) -> str:
return f'The timeout of {self.timeout} seconds has expired.'
2 changes: 1 addition & 1 deletion docs/types_of_tokens/SimpleToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ print(token.cancelled) # True
from cantok import CounterToken, TimeoutToken

print(repr(CounterToken(5) + TimeoutToken(5)))
# SimpleToken(CounterToken(5, direct=True), TimeoutToken(5, monotonic=False))
# 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.
2 changes: 1 addition & 1 deletion docs/what_are_tokens/summation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Any tokens can be summed up among themselves. The summation operation generates
from cantok import SimpleToken, TimeoutToken

print(repr(SimpleToken() + TimeoutToken(5)))
# SimpleToken(SimpleToken(), TimeoutToken(5, monotonic=False))
#> SimpleToken(TimeoutToken(5))
```

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 Down
7 changes: 6 additions & 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.25"
version = "0.0.26"
authors = [
{ name="Evgeniy Blinov", email="zheni-b@yandex.ru" },
]
Expand All @@ -28,6 +28,11 @@ classifiers = [
'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries',
]
keywords = [
'cancellation tokens',
'parallel programming',
'concurrency',
]

[tool.setuptools.package-data]
"cantok" = ["py.typed"]
Expand Down
Empty file.
Loading

0 comments on commit fcba83c

Please sign in to comment.