From ebcca913568ec0eb0bc4a7d68354a0e339f48538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl> Date: Wed, 28 Jul 2021 23:31:48 +0200 Subject: [PATCH] axed callHierarchy --- CHANGELOG.md | 1 + docs/coding/call_hierarchy.rst | 70 ----- docs/index.rst | 1 - satella/__init__.py | 2 +- satella/coding/call_hierarchy.py | 325 ----------------------- tests/test_coding/test_call_hierarchy.py | 73 ----- 6 files changed, 2 insertions(+), 470 deletions(-) delete mode 100644 docs/coding/call_hierarchy.rst delete mode 100644 satella/coding/call_hierarchy.py delete mode 100644 tests/test_coding/test_call_hierarchy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f535d16..abc8f4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ * deprecated `call_and_return_stdout` * deprecated `retry(call_on_failure=)` * added default parameter to `ThreadCollection` +* axed entire CallHierarchy diff --git a/docs/coding/call_hierarchy.rst b/docs/coding/call_hierarchy.rst deleted file mode 100644 index 11c8dcfb..00000000 --- a/docs/coding/call_hierarchy.rst +++ /dev/null @@ -1,70 +0,0 @@ -============== -Call hierarchy -============== - -..note:: This package is purely experimental! - -Satella enables you to define function call and their trees programmatically, so you can -for example express such a condition "call a function C with given args when either call of function A with given args or function B with given args returned True". - -You can specify different argument sets for execution of such a tree, ie. you can provide different argument sets -and just tell your function to use the _i_-th one. - -It additionally supports optional parallelization of function calls, if given an Executor. - -.. autoclass:: satella.coding.call_hierarchy.Call - :members: - -.. autoclass:: satella.coding.call_hierarchy.CallWithArgumentSet - :members: - -.. autoclass:: satella.coding.call_hierarchy.CallIf - :members: - -.. autoclass:: satella.coding.call_hierarchy.Reduce - :members: - -.. autoclass:: satella.coding.call_hierarchy.ExecutionEnvironment - :members: - -You should run the callables in such a way - - :: - - def add(a, b): - print(a+b) - - call_1 = CallWithArgumentSet(add, 0) - ee = ExecutionEnvironment([((1, 2), {})]) - ee(call_1) - -but in a pinch you can just type - - :: - - def add(a, b): - print(a+b) - - call_1 = CallWithArgumentSet(add, 0) - call_1(1, 2) - -Note that you need to go through ExecutionEnvironment if you want to make use of parallelism. - -.. autofunction:: satella.coding.call_hierarchy.call_with_ee - -.. autofunction:: satella.coding.call_hierarchy.call - -.. autofunction:: satella.coding.call_hierarchy.package_for_execution - -While inside such calls, you can use the following functions: - -.. autofunction:: satella.coding.call_hierarchy.current_ee - -.. autofunction:: satella.coding.call_hierarchy.current_call - -.. autofunction:: satella.coding.call_hierarchy.current_args - -.. autofunction:: satella.coding.call_hierarchy.current_kwargs - -.. autofunction:: satella.coding.call_hierarchy.current_history - diff --git a/docs/index.rst b/docs/index.rst index 7a13933c..fe5dbf0c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,6 @@ Visit the project's page at GitHub_! coding/futures coding/structures coding/decorators - coding/call_hierarchy coding/predicates coding/concurrent coding/sequences diff --git a/satella/__init__.py b/satella/__init__.py index 88c43039..0551dbf7 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.17.16a5' +__version__ = '2.17.16a6' diff --git a/satella/coding/call_hierarchy.py b/satella/coding/call_hierarchy.py deleted file mode 100644 index 6d9d91e8..00000000 --- a/satella/coding/call_hierarchy.py +++ /dev/null @@ -1,325 +0,0 @@ -import threading -import typing as tp -import warnings -from concurrent.futures import Executor - -from satella.coding.decorators.decorators import wraps -from satella.warnings import ExperimentalWarning - -local_ee = threading.local() - -__all__ = ['Call', 'CallIf', 'CallWithArgumentSet', 'ExecutionEnvironment', 'call_with_ee', - 'package_for_execution', 'current_call', 'current_args', 'current_history', - 'current_kwargs', 'current_ee'] - - -def push_call_stack(cs: tp.Callable, args: tuple = (), kwargs: tp.Optional[dict] = None) -> None: - kwargs = kwargs or {} - arg_tuple = ((cs, args, kwargs),) - if not hasattr(local_ee, 'cs'): - local_ee.cs = arg_tuple - else: - local_ee.cs = local_ee.cs + arg_tuple - - -def pop_call_stack() -> tp.Optional[tp.Tuple[tp.Callable, tuple, dict]]: - if not hasattr(local_ee, 'cs'): - return None - cs = local_ee.cs - if not cs: - return None - v = cs[-1] - local_ee.cs = local_ee.cs[:-1] - return v - - -def before_call(fun): - @wraps(fun) - def inner(self, *args, **kwargs): - if not hasattr(local_ee, 'ee'): - v = call_with_ee(fun, ExecutionEnvironment([(args, kwargs)])) - return v(self, *args, **kwargs) - else: - return fun(self, *args, **kwargs) - - return inner - - -class Call: - """ - A call to given function with a given set of arguments - """ - __slots__ = 'fn', 'args', 'kwargs' - - def __init__(self, fn, *args, **kwargs): - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - self.fn = fn - self.args = args - self.kwargs = kwargs - - @before_call - def __call__(self, *args, **kwargs): - """ - Call this callable. - - If an execution environment is already defined, it will be used. If not, - a new execution environment will be defined with the 0-th set of arguments - as args, kwargs. - - :param args: args to use as the 0-th set of arguments - :param kwargs: kwargs to use as the 0-th set of arguments - :return: return value - """ - push_call_stack(self, self.args, self.kwargs) - try: - return self.fn(*self.args, **self.kwargs) - finally: - pop_call_stack() - - -class CallWithArgumentSet(Call): - """ - Call a function with a set of arguments provided by the environment - """ - __slots__ = 'fn', 'arg_set_no' - - def __init__(self, fn, arg_set_no: int = 0): - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - self.fn = fn - self.arg_set_no = arg_set_no - - @before_call - def __call__(self, *args, **kwargs): - try: - ee = local_ee.ee - except AttributeError: - raise RuntimeError('Execution environment is required!') - args, kwargs = ee[self.arg_set_no] - push_call_stack(self, args, kwargs) - try: - return self.fn(*args, **kwargs) - finally: - pop_call_stack() - - -class CallIf(Call): - """ - Call a function only if fn_if_call returned True - """ - __slots__ = 'fn_to_call', 'fn_call_if' - - def __init__(self, fn_if_call: Call, fn_to_call: Call): - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - self.fn_to_call = fn_to_call - self.fn_call_if = fn_if_call - - @before_call - def __call__(self, *args, **kwargs): - if self.fn_call_if(): - return self.fn_to_call() - - -class Reduce(Call): - """ - A call consisting of calling other calls (possibly in parallel). - - It's result will be the combination of some other function calls by a given operator, - starting with a starting value. - - By default the starting operator just discards the results. - - :param callables: callables to call in parallel - :param reducing_op: a callable/2 that takes previous result (or the starting value) and current - callable result, returning a new starting value - :param starting_value: starting value - :param do_parallel: whether try to execute these calls in parallel, if possible. - Parallel execution will be done only if an executor is given in the execution environment. - """ - __slots__ = 'reducing_op', 'starting_value', 'do_parallel', 'callables' - - def __init__(self, *callables: Call, - reducing_op: tp.Callable[[tp.Any, tp.Any], tp.Any] = lambda a, b: None, - starting_value: tp.Any = 0, - do_parallel: bool = True): - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - self.reducing_op = reducing_op - self.starting_value = starting_value - self.do_parallel = do_parallel - self.callables = callables - - @before_call - def __call__(self, *args, **kwargs): - push_call_stack(self) - if self.do_parallel: - if local_ee.ee.executor is not None: - executor = local_ee.ee.executor - sv = self.starting_value - futures = [executor.submit(call_with_ee(callable_, local_ee.ee, local_ee.cs)) - for callable_ in self.callables] - for future in futures: - sv = self.reducing_op(sv, future.result()) - return sv - sv = self.starting_value - for callable_ in self.callables: - b = callable_() - sv = self.reducing_op(sv, b) - pop_call_stack() - return sv - - -class ExecutionEnvironment: - """ - This has no __slots__ so you can add anything here really - """ - - def __init__(self, argument_sets: tp.Iterable[tp.Tuple[tp.Tuple[tp.Any], tp.Dict]], - executor: tp.Optional[Executor] = None, - cs=()): - self.arg_sets = [] - for args, kwargs in argument_sets: - self.arg_sets.append((args, kwargs)) - self.executor = executor - self.cs = cs - - def _set_call_stack_to(self, cs: tp.Optional[tp.List[tp.Callable]] = None): - return ExecutionEnvironment(self.arg_sets, self.executor, cs) - - def __call__(self, callable_: Call, *args, **kwargs): - """ - Run a given callable within the current EE. - - :param callable_: callable to run - :return: value returned by that callable - """ - had_cs = hasattr(local_ee, 'cs') - if had_cs: - prev_cs = local_ee.cs - - local_ee.cs = self.cs - - had_ee = hasattr(local_ee, 'ee') - if had_ee: - prev_ee = local_ee.ee - local_ee.ee = self - try: - return callable_(*args, **kwargs) - finally: - if had_ee: - local_ee.ee = prev_ee - else: - del local_ee.ee - if had_cs: - local_ee.cs = prev_cs - else: - del local_ee.cs - - def __getitem__(self, item: int) -> tp.Tuple[tp.Tuple, tp.Dict]: - """Return the n-th argument set""" - try: - v = self.arg_sets[item] - except IndexError: - v = (), {} - return v - - -def call_with_ee(callable_: tp.Callable, ee: ExecutionEnvironment, - _copy_call_stack_from: tp.Optional[tp.List[tp.Callable]] = None) -> tp.Callable: - """ - Return a callable that will invoke the target callable with specified execution environment, - but only if an EE is not defined right now. - - To explicitly provide an execution environment use this: - - >>> call_1 = Call(...) - >>> ee = ExecutionEnvironment() - >>> ee(call_1) - - :param callable_: callable to invoke - :param ee: execution environment to use - :param _copy_call_stack_from: used internally, don't use - :return: a new callable - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - - def inner(*args, **kwargs): - if not hasattr(local_ee, 'ee'): - if _copy_call_stack_from is not None: - ef = ee._set_call_stack_to(_copy_call_stack_from) - else: - ef = ee - return ef(callable_, *args, **kwargs) - else: - return callable_(*args, **kwargs) - - return inner - - -def package_for_execution(clbl: tp.Callable, ee: ExecutionEnvironment) -> tp.Callable: - """ - Return a callable that, when called, will call specified callable in target - execution environment. - - :param clbl: callable to run - :param ee: EE to use - :return: a callable - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - - def inner(): - return ee(clbl) - - return inner - - -def call(fun): - """ - Make the decorated function a callable, with it's args and kwargs to be set as a 0-th argument - set. - """ - a = CallWithArgumentSet(fun, 0) - return wraps(fun)(a) - - -def current_ee() -> ExecutionEnvironment: - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - return local_ee.ee - - -def current_call() -> Call: - """ - Return currently processed Call, or None if not available - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - return local_ee.cs[-1][0] - - -def current_args() -> tp.Optional[tuple]: - """ - Return currently used positional arguments, or None if not available - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - if not local_ee.cs: - return None - return local_ee.cs[-1][1] - - -def current_kwargs() -> tp.Optional[dict]: - """ - Return currently used kwargs, or None if not available - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - if not local_ee.cs: - return None - return local_ee.cs[-1][2] - - -def current_history() -> tp.Tuple[tp.Tuple[Call, tuple, dict]]: - """ - Return a tuple of subsequent calls that led to this call. - - Position 0 will have the absolutely first call in the hierarchy, where -1 will have the current - one. - :return: a tuple of tuples (Call instance, args tuple, kwargs dict) - """ - warnings.warn('This module is experimental, use at your own peril', ExperimentalWarning) - return local_ee.cs diff --git a/tests/test_coding/test_call_hierarchy.py b/tests/test_coding/test_call_hierarchy.py deleted file mode 100644 index d1ee8a01..00000000 --- a/tests/test_coding/test_call_hierarchy.py +++ /dev/null @@ -1,73 +0,0 @@ -import unittest -from concurrent.futures.thread import ThreadPoolExecutor - -from satella.coding.call_hierarchy import Call, CallWithArgumentSet, Reduce, CallIf, \ - ExecutionEnvironment, package_for_execution, call, current_args, current_kwargs, current_history - - -class TestCallHierarchy(unittest.TestCase): - def test_call(self): - call_ = Call(lambda: 5) - self.assertEqual(call_(), 5) - - def test_exec_parallel(self): - arg_sets = [] - - def mult(y): - return y*2 - - for value in [1, 2, 3, 4, 5, 6, 7]: - arg_sets.append(((value, ), {})) - - calls = [] - for i, _ in enumerate(arg_sets): - calls.append(CallWithArgumentSet(mult, i)) - call_v = Reduce(*calls, reducing_op=lambda a, b: a+b, starting_value=0) - tpe = ThreadPoolExecutor(max_workers=4) - - ee = ExecutionEnvironment(arg_sets, tpe) - pp = package_for_execution(call_v, ee) - self.assertEqual(pp(), 56) - - def test_current(self): - @call - def nested(a: int): - self.assertEqual(len(current_history()), 2) - - @call - def call_me(a: int): - self.assertEqual(a, 5) - self.assertEqual(current_args(), (5, )) - self.assertEqual(current_kwargs(), {}) - nested() - - call_me(5) - - def test_arg_sets(self): - def add(a, b): - return a+b - - call1 = CallWithArgumentSet(add, 0) - call2 = CallWithArgumentSet(add, 1) - call_v = Reduce(call1, call2, reducing_op=lambda a, b: a+b, starting_value=0) - ee = ExecutionEnvironment([((1, 2), {}), ((3, 4), {})]) - self.assertEqual(ee(call_v), 10) - - def test_call_if(self): - a = {'test': True, 'b': 0} - - def a_is_true(): - nonlocal a - return a['test'] - - def incr_b(): - nonlocal a - a['b'] += 1 - - call_if_true = CallIf(a_is_true, incr_b) - - call_if_true() - self.assertEqual(a['b'], 1) - a['test'] = False - call_if_true() - self.assertEqual(a['b'], 1) -- GitLab