diff --git a/CHANGELOG.md b/CHANGELOG.md index 291a6a613e11f096df942a52ded5c80886684cae..5681236cdbc29fc3ac119f4c054da40899a67c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * fixed circular import * added __len__ to PeekableQueue * fixed CallableGroup, added new class +* added enum to DictDeleter +* added faster enumeration to PeekableQueue # v2.25.5 diff --git a/docs/coding/sequences.rst b/docs/coding/sequences.rst index 777cd9ad5ac2fb75e715e31fc114174e33a76fb7..7ee25075aa7c73bfbcfc4ba35daf00c405b2bd71 100644 --- a/docs/coding/sequences.rst +++ b/docs/coding/sequences.rst @@ -312,5 +312,9 @@ ListDeleter DictDeleter ----------- +.. autoclass:: satella.coding.IterMode: + :members: + + .. autoclass:: satella.coding.DictDeleter :members: diff --git a/satella/__init__.py b/satella/__init__.py index abf44da4c2c18520c722effa9d727fcfcc463780..e9c30031ab69b3fbb5a14c73c509ee4a390abf9f 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.25.6a7' +__version__ = '2.25.6a8' diff --git a/satella/coding/concurrent/callablegroup.py b/satella/coding/concurrent/callablegroup.py index cf59aee7a38b939d4a0cc9d21887a5dc48769d81..ac79846da369925556c36c7575a6a9bf531d7116 100644 --- a/satella/coding/concurrent/callablegroup.py +++ b/satella/coding/concurrent/callablegroup.py @@ -4,7 +4,7 @@ import copy import time import typing as tp -from satella.coding.deleters import DictDeleter +from satella.coding.deleters import DictDeleter, IterMode from satella.coding.typing import T, NoArgCallable @@ -33,16 +33,16 @@ class CancellableCallback: def __bool__(self) -> bool: return not self.cancelled - def __init__(self, callback_fun: tp.Callable, one_shotted=False): + def __init__(self, callback_fun: tp.Callable): self.callback_fun = callback_fun self.cancelled = False - self.one_shotted = one_shotted + self.one_shotted = False def __hash__(self): return hash(id(self)) def __eq__(self, other: CancellableCallback) -> bool: - return id(self) == id(other) and self.one_shotted == other.one_shotted and self.cancelled == other.cancelled + return id(self) == id(other) and self.cancelled == other.cancelled def __call__(self, *args, **kwargs): if not self.cancelled: @@ -55,7 +55,7 @@ class CancellableCallback: self.cancelled = True -def _callable_to_cancellablecallback(callback: NoArgCallable[[T], None], one_shot=False) -> CancellableCallback: +def _callable_to_cancellablecallback(callback: NoArgCallable[[T], None]) -> CancellableCallback: if isinstance(callback, NoArgCallable[[T], None]): return CancellableCallback(callback) elif isinstance(callback, CancellableCallback): @@ -111,8 +111,7 @@ class CallableGroup(tp.Generic[T]): __slots__ = 'callables', 'gather', 'swallow_exceptions', def __init__(self, gather: bool = True, swallow_exceptions: bool = False): - - self.callables = collections.OrderedDict() # type: tp.Dict[tp.Callable, tuple[bool, int]] + self.callables = collections.OrderedDict() # type: tp.Dict[tp.Callable, tuple[bool]] self.gather = gather # type: bool self.swallow_exceptions = swallow_exceptions # type: bool @@ -130,12 +129,14 @@ class CallableGroup(tp.Generic[T]): def remove_cancelled(self) -> None: """ - Remove it's entries that are CancelledCallbacks and that were cancelled + Remove it's entries that are CancelledCallbacks and that were cancelled and move one shots """ - with DictDeleter(self.callables) as dd: - for callable_ in dd: + with DictDeleter(self.callables, iter_mode=IterMode.ITER_VALUES) as dd: + for callable_, oneshot in dd: if isinstance(callable_, CancellableCallback) and not callable_: dd.delete() + if oneshot: + dd.delete() def add_many(self, callable_: tp.Sequence[tp.Union[NoArgCallable[T], tp.Tuple[NoArgCallable[T], bool]]]) -> CancellableCallbackGroup: @@ -154,8 +155,13 @@ class CallableGroup(tp.Generic[T]): cancellable_callbacks = [] for clbl in callable_: - canc_callback = _callable_to_cancellablecallback(clbl) - self.add(canc_callback, one_shot=canc_callback.one_shotted) + if isinstance(clbl, tuple): + clbl_, one_shot = clbl + canc_callback = _callable_to_cancellablecallback(clbl_) + else: + one_shot = False + cancellable_callbacks.append(canc_callback) + self.add(canc_callback, one_shot=one_shot) return CancellableCallbackGroup(cancellable_callbacks) def add(self, callable_: tp.Union[CancellableCallback, NoArgCallable[T]], @@ -180,10 +186,10 @@ class CallableGroup(tp.Generic[T]): Do not pass a CancellableCallback, you'll get your own """ if not isinstance(callable_, CancellableCallback): - callable_ = CancellableCallback(callable_) + callable_ = _callable_to_cancellablecallback(callable_) + self.callables[callable_] = one_shot if callable_ in self.callables: return callable_ - self.callables[callable_] = one_shot return callable_ def __call__(self, *args, **kwargs) -> tp.Optional[tp.List[T]]: diff --git a/satella/coding/concurrent/queue.py b/satella/coding/concurrent/queue.py index fce6a064783cf0b38cee5287f0e5718f3806325a..264e1f4401e9b43db08eb3d6c118e82e95a2ace1 100644 --- a/satella/coding/concurrent/queue.py +++ b/satella/coding/concurrent/queue.py @@ -75,18 +75,21 @@ class PeekableQueue(tp.Generic[T]): @rethrow_as(WouldWaitMore, Empty) def __get(self, timeout, item_getter) -> T: - self.lock.acquire() - if len(self.queue): - # Fast path - try: - return item_getter(self.queue) - finally: - self.lock.release() - else: - if timeout is None: - return self.__get_timeout_none(item_getter) + try: + self.lock.acquire() + if len(self.queue): + # Fast path + try: + return item_getter(self.queue) + finally: + self.lock.release() else: - return self.__get_timeout(item_getter, timeout) + if timeout is None: + return self.__get_timeout_none(item_getter) + else: + return self.__get_timeout(item_getter, timeout) + finally: + self.items_count -= 1 def get(self, timeout: tp.Optional[float] = None) -> T: """ @@ -97,7 +100,10 @@ class PeekableQueue(tp.Generic[T]): :return: the item :raise Empty: queue was empty """ - return self.__get(timeout, lambda queue: queue.popleft()) + try: + return self.__get(timeout, lambda queue: queue.popleft()) + finally: + self.items_count -= 1 def peek(self, timeout: tp.Optional[float] = None) -> T: """ diff --git a/satella/coding/deleters.py b/satella/coding/deleters.py index 0fb14279d5f0fd7602ff584eb7fffb8feb74f9a3..a5a27af3ffd25f88ebcf8d374bf7fd2bbac8d852 100644 --- a/satella/coding/deleters.py +++ b/satella/coding/deleters.py @@ -1,12 +1,14 @@ import collections import copy +import enum import typing as tp from satella.coding.typing import T -ITER_KEYS = 0 -ITER_VALUES = 1 -ITER_ITEMS = 2 +class IterMode(enum.IntEnum): + ITER_KEYS = 0 + ITER_VALUES = 1 + ITER_ITEMS = 2 class DictDeleter: @@ -37,9 +39,9 @@ class DictDeleter: __slots__ = ('dict_to_process', 'current_iterator', 'keys_to_delete', 'iter_mode', 'current_key') - def __init__(self, dict_to_process: collections.abc.MutableMapping): + def __init__(self, dict_to_process: collections.abc.MutableMapping, iter_mode: IterMode.ITER_KEYS): self.dict_to_process = dict_to_process - self.iter_mode = ITER_KEYS + self.iter_mode = iter_mode def __enter__(self): self.keys_to_delete = set() @@ -52,23 +54,23 @@ class DictDeleter: def __next__(self): key, value = next(self.current_iterator) # raises StopIteration self.current_key = key - if self.iter_mode == ITER_ITEMS: + if self.iter_mode == IterMode.ITER_ITEMS: return key, value - elif self.iter_mode == ITER_VALUES: + elif self.iter_mode == IterMode.ITER_VALUES: return value - elif self.iter_mode == ITER_KEYS: + elif self.iter_mode == IterMode.ITER_KEYS: return key def items(self) -> 'DictDeleter': - self.iter_mode = ITER_ITEMS + self.iter_mode = IterMode.ITER_ITEMS return self def keys(self) -> 'DictDeleter': - self.iter_mode = ITER_KEYS + self.iter_mode = IterMode.ITER_KEYS return self def values(self) -> 'DictDeleter': - self.iter_mode = ITER_VALUES + self.iter_mode = IterMode.ITER_VALUES return self def delete(self) -> None: diff --git a/tests/test_coding/test_concurrent.py b/tests/test_coding/test_concurrent.py index 957002bc7119912d9bd7692f6ac5a758f9a72724..a0c58a36d6c081e6c715a6a8f0f00135b01520a0 100644 --- a/tests/test_coding/test_concurrent.py +++ b/tests/test_coding/test_concurrent.py @@ -274,7 +274,7 @@ class TestConcurrent(unittest.TestCase): cbgroup.add(CancellableCallback(p, one_shotted=False)) cbgroup() self.assertEqual(a[1],3) - self.assertEqual(a[3], 4) + self.assertEqual(a[3], 5) self.assertEqual(a[4], 7) cbgroup() self.assertEqual(a[4], 8)