danielutils 1.1.28__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- danielutils/__init__.py +50 -0
- danielutils/abstractions/__init__.py +3 -0
- danielutils/abstractions/db/__init__.py +10 -0
- danielutils/abstractions/db/database.py +206 -0
- danielutils/abstractions/db/database_definitions.py +219 -0
- danielutils/abstractions/db/database_exceptions.py +34 -0
- danielutils/abstractions/db/database_factory.py +70 -0
- danielutils/abstractions/db/database_initializer.py +261 -0
- danielutils/abstractions/db/database_models.py +213 -0
- danielutils/abstractions/db/dependencies.py +6 -0
- danielutils/abstractions/db/implementations/__init__.py +4 -0
- danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
- danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
- danielutils/abstractions/db/implementations/redis_database.py +403 -0
- danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
- danielutils/abstractions/multiprogramming/__init__.py +3 -0
- danielutils/abstractions/multiprogramming/multi_id.py +26 -0
- danielutils/abstractions/multiprogramming/worker.py +104 -0
- danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
- danielutils/abstractions/repl.py +125 -0
- danielutils/aliases.py +174 -0
- danielutils/async_/__init__.py +7 -0
- danielutils/async_/async_cmd.py +28 -0
- danielutils/async_/async_command.py +879 -0
- danielutils/async_/async_layered_command.py +125 -0
- danielutils/async_/async_retry_executor.py +86 -0
- danielutils/async_/async_worker_pool.py +110 -0
- danielutils/async_/time_strategy.py +73 -0
- danielutils/async_/utils.py +57 -0
- danielutils/better_builtins/__init__.py +3 -0
- danielutils/better_builtins/counter.py +48 -0
- danielutils/better_builtins/frange.py +266 -0
- danielutils/better_builtins/typed_builtins/__init__.py +4 -0
- danielutils/better_builtins/typed_builtins/factory.py +126 -0
- danielutils/better_builtins/typed_builtins/tdict.py +36 -0
- danielutils/better_builtins/typed_builtins/tlist.py +223 -0
- danielutils/better_builtins/typed_builtins/tset.py +67 -0
- danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
- danielutils/colors.py +203 -0
- danielutils/context_managers/__init__.py +5 -0
- danielutils/context_managers/attr_context.py +40 -0
- danielutils/context_managers/multi_context.py +34 -0
- danielutils/context_managers/optional_context.py +33 -0
- danielutils/context_managers/state_context.py +25 -0
- danielutils/context_managers/temporary_file.py +108 -0
- danielutils/convenience.py +12 -0
- danielutils/conversions/__init__.py +2 -0
- danielutils/conversions/main_conversions.py +131 -0
- danielutils/conversions/specialized_conversions/__init__.py +2 -0
- danielutils/conversions/specialized_conversions/to_hex.py +25 -0
- danielutils/conversions/specialized_conversions/to_int.py +19 -0
- danielutils/custom_types.py +18 -0
- danielutils/data_structures/__init__.py +8 -0
- danielutils/data_structures/algorithms.py +52 -0
- danielutils/data_structures/comparer.py +47 -0
- danielutils/data_structures/default_dict.py +23 -0
- danielutils/data_structures/functions.py +35 -0
- danielutils/data_structures/graph/__init__.py +4 -0
- danielutils/data_structures/graph/binary_node.py +71 -0
- danielutils/data_structures/graph/graph.py +208 -0
- danielutils/data_structures/graph/multinode.py +101 -0
- danielutils/data_structures/graph/node.py +60 -0
- danielutils/data_structures/heap/__init__.py +3 -0
- danielutils/data_structures/heap/heap.py +119 -0
- danielutils/data_structures/heap/max_heap.py +18 -0
- danielutils/data_structures/heap/min_heap.py +18 -0
- danielutils/data_structures/queue/__init__.py +3 -0
- danielutils/data_structures/queue/atomic_queue.py +15 -0
- danielutils/data_structures/queue/priority_queue.py +110 -0
- danielutils/data_structures/queue/queue.py +92 -0
- danielutils/data_structures/stack.py +102 -0
- danielutils/data_structures/trees/__init__.py +2 -0
- danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
- danielutils/data_structures/trees/binary_tree.py +63 -0
- danielutils/date.py +43 -0
- danielutils/date_time.py +23 -0
- danielutils/decorators/__init__.py +18 -0
- danielutils/decorators/atomic.py +45 -0
- danielutils/decorators/attach.py +61 -0
- danielutils/decorators/chain_decorators.py +39 -0
- danielutils/decorators/decorate_conditionally.py +32 -0
- danielutils/decorators/delay_call.py +47 -0
- danielutils/decorators/deprecate.py +46 -0
- danielutils/decorators/final.py +39 -0
- danielutils/decorators/limit_recursion.py +63 -0
- danielutils/decorators/memo.py +68 -0
- danielutils/decorators/normalize_decorator.py +55 -0
- danielutils/decorators/overload.py +204 -0
- danielutils/decorators/partially_implemented.py +32 -0
- danielutils/decorators/processify.py +74 -0
- danielutils/decorators/property.py +42 -0
- danielutils/decorators/singleton.py +59 -0
- danielutils/decorators/threadify.py +40 -0
- danielutils/decorators/timeout.py +82 -0
- danielutils/decorators/total_ordering.py +82 -0
- danielutils/decorators/validate.py +227 -0
- danielutils/exceptions.py +90 -0
- danielutils/functions/__init__.py +13 -0
- danielutils/functions/areoneof.py +47 -0
- danielutils/functions/check_foreach.py +39 -0
- danielutils/functions/factorial.py +24 -0
- danielutils/functions/flatten.py +33 -0
- danielutils/functions/foreach.py +20 -0
- danielutils/functions/isoftype.py +475 -0
- danielutils/functions/isoneof.py +71 -0
- danielutils/functions/multiloop.py +51 -0
- danielutils/functions/parallel_for.py +47 -0
- danielutils/functions/partition.py +45 -0
- danielutils/functions/powerset.py +31 -0
- danielutils/functions/subseteq.py +25 -0
- danielutils/functions/types_subseteq.py +51 -0
- danielutils/generators/__init__.py +3 -0
- danielutils/generators/conditional_generator.py +59 -0
- danielutils/generators/generator_from_stream.py +31 -0
- danielutils/generators/join_generators.py +109 -0
- danielutils/internet.py +92 -0
- danielutils/io_.py +451 -0
- danielutils/java/__init__.py +18 -0
- danielutils/java/interfaces/__init__.py +1 -0
- danielutils/java/interfaces/comparable.py +31 -0
- danielutils/java/java_interface.py +59 -0
- danielutils/logging_/__init__.py +2 -0
- danielutils/logging_/_impl/__init__.py +4 -0
- danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
- danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
- danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
- danielutils/logging_/_impl/log_level.py +13 -0
- danielutils/logging_/_impl/logger.py +109 -0
- danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
- danielutils/logging_/utils.py +199 -0
- danielutils/lombok/__init__.py +1 -0
- danielutils/lombok/builder.py +68 -0
- danielutils/math_/__init__.py +2 -0
- danielutils/math_/constants.py +103 -0
- danielutils/math_/functions.py +34 -0
- danielutils/math_/math_print.py +52 -0
- danielutils/math_/math_symbols.py +125 -0
- danielutils/metaclasses/__init__.py +5 -0
- danielutils/metaclasses/atomic_class_meta.py +36 -0
- danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
- danielutils/metaclasses/instance_cache_meta.py +66 -0
- danielutils/metaclasses/interface.py +328 -0
- danielutils/metaclasses/overload_meta.py +48 -0
- danielutils/mock_/__init__.py +1 -0
- danielutils/mock_/mock_module.py +27 -0
- danielutils/path.py +55 -0
- danielutils/print_.py +123 -0
- danielutils/progress_bar/__init__.py +3 -0
- danielutils/progress_bar/ascii_progress_bar.py +161 -0
- danielutils/progress_bar/progress_bar.py +75 -0
- danielutils/progress_bar/progress_bar_pool.py +72 -0
- danielutils/protocols/__init__.py +3 -0
- danielutils/protocols/dictable.py +38 -0
- danielutils/protocols/evaluable.py +26 -0
- danielutils/protocols/serializable.py +51 -0
- danielutils/py.typed +0 -0
- danielutils/random_.py +47 -0
- danielutils/reflection/__init__.py +6 -0
- danielutils/reflection/declarations.py +288 -0
- danielutils/reflection/file/__init__.py +1 -0
- danielutils/reflection/file/file_reflection.py +85 -0
- danielutils/reflection/function/__init__.py +1 -0
- danielutils/reflection/function/function_reflections.py +152 -0
- danielutils/reflection/info_classes/__init__.py +6 -0
- danielutils/reflection/info_classes/argument_info.py +278 -0
- danielutils/reflection/info_classes/class_info.py +204 -0
- danielutils/reflection/info_classes/decorator_info.py +54 -0
- danielutils/reflection/info_classes/file_info.py +1180 -0
- danielutils/reflection/info_classes/folder_info.py +592 -0
- danielutils/reflection/info_classes/function_info.py +551 -0
- danielutils/reflection/info_classes/import_info.py +207 -0
- danielutils/reflection/interpreter/__init__.py +9 -0
- danielutils/reflection/interpreter/callstack.py +57 -0
- danielutils/reflection/interpreter/get_traceback.py +27 -0
- danielutils/reflection/interpreter/interpreter.py +41 -0
- danielutils/reflection/interpreter/is_debugging.py +13 -0
- danielutils/reflection/interpreter/os_.py +33 -0
- danielutils/reflection/interpreter/packages.py +33 -0
- danielutils/reflection/interpreter/python_version.py +33 -0
- danielutils/reflection/interpreter/signals.py +18 -0
- danielutils/reflection/interpreter/tracer.py +175 -0
- danielutils/reflection/module/__init__.py +3 -0
- danielutils/reflection/module/lazy_module.py +104 -0
- danielutils/reflection/module/module_reflections.py +34 -0
- danielutils/reflection/module/package_reflection.py +161 -0
- danielutils/retry_executor/__init__.py +3 -0
- danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
- danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
- danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
- danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
- danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
- danielutils/retry_executor/backoff_strategy.py +28 -0
- danielutils/retry_executor/retry_executor.py +55 -0
- danielutils/snippets/__init__.py +1 -0
- danielutils/snippets/try_get.py +28 -0
- danielutils/system/__init__.py +3 -0
- danielutils/system/independent.py +197 -0
- danielutils/system/layered_command.py +130 -0
- danielutils/system/windows/__init__.py +2 -0
- danielutils/system/windows/utils/__init__.py +1 -0
- danielutils/system/windows/utils/filetime.py +19 -0
- danielutils/system/windows/win32_ctime.py +183 -0
- danielutils/system/windows/windows.py +78 -0
- danielutils/testing/__init__.py +1 -0
- danielutils/testing/unittest_/__init__.py +2 -0
- danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
- danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
- danielutils/text.py +169 -0
- danielutils/time.py +64 -0
- danielutils/tools/__init__.py +1 -0
- danielutils/tools/unittest_test_runner/__init__.py +9 -0
- danielutils/tools/unittest_test_runner/cli.py +70 -0
- danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
- danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
- danielutils/tools/unittest_test_runner/core/execution.py +244 -0
- danielutils/tools/unittest_test_runner/core/parser.py +163 -0
- danielutils/tools/unittest_test_runner/core/runner.py +561 -0
- danielutils/tools/unittest_test_runner/models.py +82 -0
- danielutils/university/__init__.py +7 -0
- danielutils/university/computability_and_complexity/__init__.py +2 -0
- danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
- danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- danielutils/university/computability_and_complexity/languages/language.py +17 -0
- danielutils/university/computability_and_complexity/languages/sat.py +71 -0
- danielutils/university/computability_and_complexity/turing_machine.py +47 -0
- danielutils/university/databases/__init__.py +1 -0
- danielutils/university/databases/all.py +756 -0
- danielutils/university/image_proccesing/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/__init__.py +3 -0
- danielutils/university/image_proccesing/encoding/encoding.py +35 -0
- danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
- danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
- danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
- danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
- danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
- danielutils/university/linear_algebra/__init__.py +1 -0
- danielutils/university/linear_algebra/matrix.py +404 -0
- danielutils/university/machine_learning/__init__.py +0 -0
- danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
- danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
- danielutils/university/machine_learning/activation_functions/relu.py +11 -0
- danielutils/university/machine_learning/neuron.py +15 -0
- danielutils/university/oop/__init__.py +1 -0
- danielutils/university/oop/observer.py +31 -0
- danielutils/university/oop/strategy.py +9 -0
- danielutils/university/probability/__init__.py +7 -0
- danielutils/university/probability/conditional_variable/__init__.py +3 -0
- danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
- danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
- danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
- danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
- danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
- danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
- danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
- danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
- danielutils/university/probability/distributions.py +35 -0
- danielutils/university/probability/expressions/__init__.py +2 -0
- danielutils/university/probability/expressions/accumulation_expression.py +291 -0
- danielutils/university/probability/expressions/probability_expression.py +110 -0
- danielutils/university/probability/funcs/__init__.py +4 -0
- danielutils/university/probability/funcs/covariance.py +12 -0
- danielutils/university/probability/funcs/expected_value.py +41 -0
- danielutils/university/probability/funcs/probability_function.py +14 -0
- danielutils/university/probability/funcs/variance.py +17 -0
- danielutils/university/probability/operator.py +79 -0
- danielutils/university/probability/protocols.py +32 -0
- danielutils/university/probability/supp.py +101 -0
- danielutils/university/probability/transformation.py +44 -0
- danielutils/versioned_imports.py +31 -0
- danielutils-1.1.28.dist-info/METADATA +390 -0
- danielutils-1.1.28.dist-info/RECORD +287 -0
- danielutils-1.1.28.dist-info/WHEEL +5 -0
- danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
- danielutils-1.1.28.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, Tuple, List
|
|
4
|
+
|
|
5
|
+
from .async_cmd import async_cmd
|
|
6
|
+
from ..logging_.utils import get_logger
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncLayeredCommand:
|
|
11
|
+
class_capture_stdout: bool = True
|
|
12
|
+
class_capture_stderr: bool = True
|
|
13
|
+
class_raise_on_fail: bool = False
|
|
14
|
+
class_verbose: bool = False
|
|
15
|
+
_class_prev_instance: Optional['AsyncLayeredCommand'] = None
|
|
16
|
+
_id: int = 0
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def prev(self):
|
|
20
|
+
return self._prev_instance
|
|
21
|
+
|
|
22
|
+
@prev.setter
|
|
23
|
+
def prev(self, value: Optional['AsyncLayeredCommand'] = None):
|
|
24
|
+
self._prev_instance = value
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
command: Optional[str] = None,
|
|
29
|
+
*,
|
|
30
|
+
prev_instance: Optional['AsyncLayeredCommand'] = None,
|
|
31
|
+
instance_capture_stdout: Optional[bool] = None,
|
|
32
|
+
instance_capture_stderr: Optional[bool] = None,
|
|
33
|
+
instance_raise_on_fail: Optional[bool] = None,
|
|
34
|
+
instance_verbose: Optional[bool] = None
|
|
35
|
+
):
|
|
36
|
+
logger.debug("Initializing AsyncLayeredCommand with command='%s', prev_instance=%s", command, prev_instance is not None)
|
|
37
|
+
self._command = command if command is not None else ""
|
|
38
|
+
self._instance_capture_stdout = instance_capture_stdout
|
|
39
|
+
self._instance_capture_stderr = instance_capture_stderr
|
|
40
|
+
self._instance_raise_on_fail = instance_raise_on_fail
|
|
41
|
+
self._instance_verbose = instance_verbose
|
|
42
|
+
self._prev_instance = prev_instance if prev_instance is not None else AsyncLayeredCommand._class_prev_instance
|
|
43
|
+
self._cur_class_prev_instance = AsyncLayeredCommand._class_prev_instance
|
|
44
|
+
AsyncLayeredCommand._class_prev_instance = self
|
|
45
|
+
self._has_entered: bool = False
|
|
46
|
+
logger.debug("AsyncLayeredCommand initialized successfully")
|
|
47
|
+
|
|
48
|
+
def __enter__(self):
|
|
49
|
+
logger.debug("Entering AsyncLayeredCommand context")
|
|
50
|
+
self._open()
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
54
|
+
logger.debug("Exiting AsyncLayeredCommand context, exc_type=%s", exc_type)
|
|
55
|
+
if self.prev is self._cur_class_prev_instance:
|
|
56
|
+
AsyncLayeredCommand._class_prev_instance = self.prev
|
|
57
|
+
|
|
58
|
+
def _open(self) -> None:
|
|
59
|
+
logger.debug("Opening AsyncLayeredCommand")
|
|
60
|
+
self._has_entered = True
|
|
61
|
+
|
|
62
|
+
def _build_command(self, *commands: str) -> str:
|
|
63
|
+
logger.debug("Building command with %d additional commands", len(commands))
|
|
64
|
+
res = ""
|
|
65
|
+
if self.prev is not None:
|
|
66
|
+
prev = self.prev._build_command()
|
|
67
|
+
res += f"{prev} & " if prev != "" else ""
|
|
68
|
+
if self._command != "":
|
|
69
|
+
result = res + " & ".join([self._command, *commands])
|
|
70
|
+
else:
|
|
71
|
+
result = res + " & ".join(commands)
|
|
72
|
+
logger.debug("Built command: %s", result)
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
def _error(self, predicate: bool, command: str, code: int, command_verbose: Optional[bool]) -> None:
|
|
76
|
+
if predicate:
|
|
77
|
+
logger.error("Command '%s' failed with exit code %d", command, code)
|
|
78
|
+
verbose = self._merge_values(command_verbose, self._instance_verbose, AsyncLayeredCommand.class_verbose)
|
|
79
|
+
if verbose:
|
|
80
|
+
raise RuntimeError(f"command '{command}' failed with exit code {code}")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _merge_values(command: Optional[bool], instance: Optional[bool], class_: bool) -> bool:
|
|
85
|
+
return command if command is not None else (instance if instance is not None else class_)
|
|
86
|
+
|
|
87
|
+
async def execute(
|
|
88
|
+
self,
|
|
89
|
+
*commands: str,
|
|
90
|
+
command_capture_stdout: Optional[bool] = None,
|
|
91
|
+
command_capture_stderr: Optional[bool] = None,
|
|
92
|
+
command_raise_on_fail: Optional[bool] = None,
|
|
93
|
+
command_verbose: Optional[bool] = None
|
|
94
|
+
) -> Tuple[int, List[str], List[str]]:
|
|
95
|
+
logger.info("Executing AsyncLayeredCommand with %d commands", len(commands))
|
|
96
|
+
if not self._has_entered:
|
|
97
|
+
logger.error("LayeredCommand must be used with a context manager")
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
"LayeredCommand must be used with a context manager. Use as: `with LayeredCommand(...) as l1:`")
|
|
100
|
+
capture_stdout = self._merge_values(command_capture_stdout, self._instance_capture_stdout,
|
|
101
|
+
self.class_capture_stdout)
|
|
102
|
+
capture_stderr = self._merge_values(command_capture_stderr, self._instance_capture_stderr,
|
|
103
|
+
self.class_capture_stderr)
|
|
104
|
+
raise_on_fail = self._merge_values(command_raise_on_fail, self._instance_raise_on_fail,
|
|
105
|
+
self.class_raise_on_fail)
|
|
106
|
+
|
|
107
|
+
command = self._build_command(*commands)
|
|
108
|
+
logger.debug("Executing command with capture_stdout=%s, capture_stderr=%s", capture_stdout, capture_stderr)
|
|
109
|
+
if not capture_stdout and not capture_stderr:
|
|
110
|
+
code = (await async_cmd(command))[0]
|
|
111
|
+
logger.debug("Command executed with return code %d", code)
|
|
112
|
+
self._error(raise_on_fail and code != 0, command, code, command_verbose)
|
|
113
|
+
return code, [], []
|
|
114
|
+
|
|
115
|
+
code, stdout, stderr = await async_cmd(command, capture_stdout=True, capture_stderr=True)
|
|
116
|
+
logger.info("Command execution completed with return code %d", code)
|
|
117
|
+
return code, stdout.decode().splitlines() if stdout else [], stderr.decode().splitlines() if stderr else []
|
|
118
|
+
|
|
119
|
+
async def __call__(self, *args, **kwargs) -> Tuple[int, List[str], List[str]]:
|
|
120
|
+
return await self.execute(*args, **kwargs)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__all__ = [
|
|
124
|
+
'AsyncLayeredCommand'
|
|
125
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Literal, Optional, Any, Mapping, Iterable, Callable, Coroutine
|
|
6
|
+
|
|
7
|
+
from ..custom_types import Supplier
|
|
8
|
+
from ..decorators import normalize_decorator
|
|
9
|
+
from .time_strategy import LinearTimeStrategy, ConstantTimeStrategy
|
|
10
|
+
from ..versioned_imports import ParamSpec
|
|
11
|
+
from ..logging_.utils import get_logger
|
|
12
|
+
|
|
13
|
+
P = ParamSpec("P")
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AsyncRetryExecutor:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
timeout_strategy: Supplier[float] = LinearTimeStrategy(30, 5),
|
|
21
|
+
delay_strategy: Supplier[float] = ConstantTimeStrategy(0)
|
|
22
|
+
) -> None:
|
|
23
|
+
logger.info("Initializing AsyncRetryExecutor with timeout_strategy=%s, delay_strategy=%s", type(timeout_strategy).__name__, type(delay_strategy).__name__)
|
|
24
|
+
self.timeout_strategy = timeout_strategy
|
|
25
|
+
self.delay_strategy = delay_strategy
|
|
26
|
+
logger.debug("AsyncRetryExecutor initialized successfully")
|
|
27
|
+
|
|
28
|
+
def is_transient(self, e: Exception) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
This function will return true if the exception that was raised at a specific attempt should be ignored and we should try again with respet to the amount of retries left
|
|
31
|
+
Args:
|
|
32
|
+
e: exception caught
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
boolean
|
|
36
|
+
"""
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
async def execute(
|
|
40
|
+
self,
|
|
41
|
+
func: Callable[P, Coroutine],
|
|
42
|
+
*,
|
|
43
|
+
args: Optional[Iterable] = None,
|
|
44
|
+
kwargs: Optional[Mapping] = None,
|
|
45
|
+
max_tries: int = 5
|
|
46
|
+
) -> Optional[Any]:
|
|
47
|
+
args = list(args) if args else []
|
|
48
|
+
kwargs = dict(kwargs) if kwargs else {}
|
|
49
|
+
logger.info("Starting async retry execution for %s with max_tries=%d", func.__name__, max_tries)
|
|
50
|
+
|
|
51
|
+
for i in range(1, max_tries + 1):
|
|
52
|
+
timeout = self.timeout_strategy()
|
|
53
|
+
delay = self.delay_strategy()
|
|
54
|
+
logger.debug("Attempt %d/%d with timeout=%ss, delay=%ss", i, max_tries, timeout, delay)
|
|
55
|
+
try:
|
|
56
|
+
result = await asyncio.wait_for(func(*args, **kwargs), timeout=timeout)
|
|
57
|
+
logger.info("Async retry execution succeeded on attempt %d", i)
|
|
58
|
+
return result
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if self.is_transient(e):
|
|
61
|
+
logger.warning("Failed attempt %d/%d with transient exception: %s: %s", i, max_tries, type(e).__name__, e)
|
|
62
|
+
if i < max_tries - 1 and delay > 0:
|
|
63
|
+
logger.debug("Waiting %ss before next attempt", delay)
|
|
64
|
+
await asyncio.sleep(delay)
|
|
65
|
+
else:
|
|
66
|
+
logger.error("Non-transient exception on attempt %d: %s: %s", i, type(e).__name__, e)
|
|
67
|
+
raise e
|
|
68
|
+
logger.error("Failed all %d attempts for %s", max_tries, func.__name__)
|
|
69
|
+
raise RuntimeError(f"Failed all attempts")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@normalize_decorator
|
|
73
|
+
def with_async_retry(func, *retry_executor_args, max_tries: int = 5, **retry_executor_kwargs):
|
|
74
|
+
retry_executor = AsyncRetryExecutor(*retry_executor_args, **retry_executor_kwargs)
|
|
75
|
+
|
|
76
|
+
@functools.wraps(func)
|
|
77
|
+
async def wrapper(*args, **kwargs):
|
|
78
|
+
return await retry_executor.execute(func, args=args, kwargs=kwargs, max_tries=max_tries)
|
|
79
|
+
|
|
80
|
+
return wrapper
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
"AsyncRetryExecutor",
|
|
85
|
+
"with_async_retry"
|
|
86
|
+
]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from typing import Callable, Optional, Coroutine, List, Iterable, Any, Mapping, Tuple
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
except ImportError:
|
|
10
|
+
from ..mock_ import MockImportObject
|
|
11
|
+
|
|
12
|
+
tqdm = MockImportObject("'tqdm' is not installed. Please install 'tqdm' to use AsyncWorkerPool feature.") # type: ignore
|
|
13
|
+
|
|
14
|
+
from ..logging_.utils import get_logger
|
|
15
|
+
|
|
16
|
+
class AsyncWorkerPool:
|
|
17
|
+
DEFAULT_ORDER_IF_KEY_EXISTS = (
|
|
18
|
+
"pool", "timestamp", "worker_id", "task_id", "task_name", "num_tasks", "tasks", "level", "message", "exception"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Default logger instance
|
|
22
|
+
_logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def log(level: int, message: str, *args, **kwargs) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Static logging method that delegates to the default logger.
|
|
28
|
+
End users can override this method to customize logging behavior.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
level: Log level (DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50)
|
|
32
|
+
message: Log message
|
|
33
|
+
*args: Additional arguments for message formatting
|
|
34
|
+
**kwargs: Additional keyword arguments for logging
|
|
35
|
+
"""
|
|
36
|
+
AsyncWorkerPool._logger.log(level, message, *args, **kwargs)
|
|
37
|
+
|
|
38
|
+
def __init__(self, pool_name: str, num_workers: int = 5, show_pbar: bool = False) -> None:
|
|
39
|
+
self.log(logging.INFO, "Initializing AsyncWorkerPool '%s' with %d workers, show_pbar=%s", pool_name, num_workers, show_pbar)
|
|
40
|
+
self._num_workers: int = num_workers
|
|
41
|
+
self._pool_name: str = pool_name
|
|
42
|
+
self._show_pbar: bool = show_pbar
|
|
43
|
+
self._pbar: Optional[tqdm] = None
|
|
44
|
+
self._queue: asyncio.Queue[
|
|
45
|
+
Optional[Tuple[Callable, Iterable[Any], Mapping[Any, Any], Optional[str]]]] = asyncio.Queue()
|
|
46
|
+
self._workers: List = []
|
|
47
|
+
self.log(logging.DEBUG, "AsyncWorkerPool '%s' initialized successfully", pool_name)
|
|
48
|
+
|
|
49
|
+
async def worker(self, worker_id) -> None:
|
|
50
|
+
"""Worker coroutine that continuously fetches and executes tasks from the queue."""
|
|
51
|
+
self.log(logging.DEBUG, "Worker %d starting", worker_id)
|
|
52
|
+
task_index = 0
|
|
53
|
+
tasks = defaultdict(list)
|
|
54
|
+
while True:
|
|
55
|
+
task = await self._queue.get()
|
|
56
|
+
if task is None: # Sentinel value to shut down the worker
|
|
57
|
+
self.log(logging.DEBUG, "Worker %d received shutdown signal", worker_id)
|
|
58
|
+
break
|
|
59
|
+
func, args, kwargs, name = task
|
|
60
|
+
task_index += 1
|
|
61
|
+
self.log(logging.INFO, "Task %d '%s' started on worker %d", task_index, name, worker_id)
|
|
62
|
+
try:
|
|
63
|
+
await func(*args, **kwargs)
|
|
64
|
+
tasks["success"].append(name)
|
|
65
|
+
self.log(logging.INFO, "Task %d '%s' finished on worker %d", task_index, name, worker_id)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.log(logging.ERROR, "Task %d '%s' failed on worker %d: %s: %s", task_index, name, worker_id, type(e).__name__, e)
|
|
68
|
+
tasks["failure"].append(name)
|
|
69
|
+
|
|
70
|
+
if self._pbar:
|
|
71
|
+
self._pbar.update(1)
|
|
72
|
+
self._queue.task_done()
|
|
73
|
+
self.log(logging.INFO, "Worker %d completed %d tasks (success: %d, failure: %d)", worker_id, task_index, len(tasks['success']), len(tasks['failure']))
|
|
74
|
+
if tasks['success']:
|
|
75
|
+
self.log(logging.INFO, "Worker %d successful tasks: [%s]", worker_id, ', '.join(map(str, tasks['success'])))
|
|
76
|
+
if tasks['failure']:
|
|
77
|
+
self.log(logging.WARNING, "Worker %d failed tasks: [%s]", worker_id, ', '.join(map(str, tasks['failure'])))
|
|
78
|
+
|
|
79
|
+
async def start(self) -> None:
|
|
80
|
+
"""Starts the worker pool."""
|
|
81
|
+
self.log(logging.INFO, "Starting worker pool '%s' with %d workers", self._pool_name, self._num_workers)
|
|
82
|
+
if self._show_pbar:
|
|
83
|
+
self._pbar = tqdm(total=self._queue.qsize(), desc="#Tasks")
|
|
84
|
+
self._workers = [asyncio.create_task(self.worker(i + 1)) for i in range(self._num_workers)]
|
|
85
|
+
self.log(logging.INFO, "Worker pool '%s' started successfully", self._pool_name)
|
|
86
|
+
|
|
87
|
+
async def submit(
|
|
88
|
+
self,
|
|
89
|
+
func: Callable[..., Coroutine[None, None, None]],
|
|
90
|
+
args: Optional[Iterable[Any]] = None,
|
|
91
|
+
kwargs: Optional[Mapping[Any, Any]] = None,
|
|
92
|
+
name: Optional[str] = None
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Submit a new task to the queue."""
|
|
95
|
+
self.log(logging.DEBUG, "Adding new job '%s' to queue", name)
|
|
96
|
+
await self._queue.put((func, args or (), kwargs or {}, name))
|
|
97
|
+
|
|
98
|
+
async def join(self) -> None:
|
|
99
|
+
"""Stops the worker pool by waiting for all tasks to complete and shutting down workers."""
|
|
100
|
+
self.log(logging.INFO, "Starting join process for worker pool '%s'", self._pool_name)
|
|
101
|
+
await self._queue.join() # Wait until all tasks are processed
|
|
102
|
+
for _ in range(self._num_workers):
|
|
103
|
+
await self._queue.put(None) # Send sentinel values to stop workers
|
|
104
|
+
await asyncio.gather(*self._workers) # Wait for workers to finish
|
|
105
|
+
self.log(logging.INFO, "Join process completed for worker pool '%s'", self._pool_name)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"AsyncWorkerPool",
|
|
110
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import logging
|
|
3
|
+
from ..logging_.utils import get_logger
|
|
4
|
+
logger = get_logger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TimeStrategy(ABC):
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def next(self): ...
|
|
10
|
+
|
|
11
|
+
def __call__(self, *args, **kwargs):
|
|
12
|
+
return self.next()
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def reset(self): ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConstantTimeStrategy(TimeStrategy):
|
|
19
|
+
def __init__(self, timeout: float):
|
|
20
|
+
logger.debug("Initializing ConstantTimeStrategy with timeout=%s", timeout)
|
|
21
|
+
self.timeout = timeout
|
|
22
|
+
|
|
23
|
+
def next(self) -> float:
|
|
24
|
+
logger.debug("ConstantTimeStrategy returning timeout=%s", self.timeout)
|
|
25
|
+
return self.timeout
|
|
26
|
+
|
|
27
|
+
def reset(self) -> None:
|
|
28
|
+
logger.debug("ConstantTimeStrategy reset called (no-op)")
|
|
29
|
+
pass # No state to reset
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LinearTimeStrategy(TimeStrategy):
|
|
33
|
+
def __init__(self, base_timeout: float, step: float):
|
|
34
|
+
logger.debug("Initializing LinearTimeStrategy with base_timeout=%s, step=%s", base_timeout, step)
|
|
35
|
+
self.base_timeout = base_timeout
|
|
36
|
+
self.step = step
|
|
37
|
+
self.current_timeout = base_timeout
|
|
38
|
+
|
|
39
|
+
def next(self) -> float:
|
|
40
|
+
timeout = self.current_timeout
|
|
41
|
+
self.current_timeout += self.step
|
|
42
|
+
logger.debug("LinearTimeStrategy returning timeout=%s, next will be %s", timeout, self.current_timeout)
|
|
43
|
+
return timeout
|
|
44
|
+
|
|
45
|
+
def reset(self) -> None:
|
|
46
|
+
logger.debug("LinearTimeStrategy resetting to base_timeout=%s", self.base_timeout)
|
|
47
|
+
self.current_timeout = self.base_timeout
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class MultiplicativeTimeStrategy(TimeStrategy):
|
|
51
|
+
def __init__(self, base_timeout: float, factor: float):
|
|
52
|
+
logger.debug("Initializing MultiplicativeTimeStrategy with base_timeout=%s, factor=%s", base_timeout, factor)
|
|
53
|
+
self.base_timeout = base_timeout
|
|
54
|
+
self.factor = factor
|
|
55
|
+
self.current_timeout = base_timeout
|
|
56
|
+
|
|
57
|
+
def next(self) -> float:
|
|
58
|
+
timeout = self.current_timeout
|
|
59
|
+
self.current_timeout *= self.factor
|
|
60
|
+
logger.debug("MultiplicativeTimeStrategy returning timeout=%s, next will be %s", timeout, self.current_timeout)
|
|
61
|
+
return timeout
|
|
62
|
+
|
|
63
|
+
def reset(self) -> None:
|
|
64
|
+
logger.debug("MultiplicativeTimeStrategy resetting to base_timeout=%s", self.base_timeout)
|
|
65
|
+
self.current_timeout = self.base_timeout
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"TimeStrategy",
|
|
70
|
+
"ConstantTimeStrategy",
|
|
71
|
+
"LinearTimeStrategy",
|
|
72
|
+
"MultiplicativeTimeStrategy"
|
|
73
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from asyncio import Task
|
|
4
|
+
from typing import List, Coroutine, Any, Tuple, Optional, Set, AsyncIterator, Iterator, TypeVar
|
|
5
|
+
from ..logging_.utils import get_logger
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def return_first(coros: List[Coroutine], timeout: Optional[int] = None) -> List[Tuple[int, Any]]:
|
|
10
|
+
logger.debug("return_first called with %d coroutines, timeout=%s", len(coros), timeout)
|
|
11
|
+
tasks: List[Task] = [asyncio.create_task(coro) for coro in coros]
|
|
12
|
+
result: Tuple[Set[Task], Set[Task]] = await asyncio.wait(tasks, timeout=timeout,
|
|
13
|
+
return_when=asyncio.FIRST_COMPLETED)
|
|
14
|
+
done: Set[Task] = result[0]
|
|
15
|
+
# pending: Set[Task] = result[1]
|
|
16
|
+
|
|
17
|
+
res = []
|
|
18
|
+
for task in done:
|
|
19
|
+
res.append((tasks.index(task), task.result()))
|
|
20
|
+
|
|
21
|
+
logger.debug("return_first completed with %d results", len(res))
|
|
22
|
+
return res
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def return_all(coros: List[Coroutine], timeout: Optional[int] = None) -> List[Any]:
|
|
26
|
+
logger.debug("return_all called with %d coroutines, timeout=%s", len(coros), timeout)
|
|
27
|
+
tasks: List[Task] = [asyncio.create_task(coro) for coro in coros]
|
|
28
|
+
result: Tuple[Set[Task], Set[Task]] = await asyncio.wait(tasks, timeout=timeout,
|
|
29
|
+
return_when=asyncio.ALL_COMPLETED)
|
|
30
|
+
done: Set[Task] = result[0]
|
|
31
|
+
|
|
32
|
+
res = [task.result() for task in done]
|
|
33
|
+
logger.debug("return_all completed with %d results", len(res))
|
|
34
|
+
return res
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def cast_aiter(itr: Iterator) -> AsyncIterator:
|
|
38
|
+
logger.debug("cast_aiter called")
|
|
39
|
+
for x in itr:
|
|
40
|
+
yield x
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
T = TypeVar("T")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def async_enumerate(iterable: AsyncIterator[T], start: int = 0) -> AsyncIterator[Tuple[int, T]]:
|
|
47
|
+
index = start
|
|
48
|
+
async for item in iterable:
|
|
49
|
+
yield index, item
|
|
50
|
+
index += 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"return_first",
|
|
55
|
+
"return_all",
|
|
56
|
+
'cast_aiter'
|
|
57
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from ..metaclasses import AtomicClassMeta
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Counter:
|
|
6
|
+
"""A simple counter class
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, initial_value: Union[int, float] = 0, increment_amount: Union[int, float] = 1) -> None:
|
|
10
|
+
self.value = initial_value
|
|
11
|
+
self.increment_value = increment_amount
|
|
12
|
+
|
|
13
|
+
def increment(self) -> None:
|
|
14
|
+
"""increments the stored value by the increment amount
|
|
15
|
+
"""
|
|
16
|
+
self.value += self.increment_value
|
|
17
|
+
|
|
18
|
+
def decrement(self) -> None:
|
|
19
|
+
"""decrements the stored value by the increment amount
|
|
20
|
+
"""
|
|
21
|
+
self.value -= self.increment_value
|
|
22
|
+
|
|
23
|
+
def get(self) -> Union[int, float]:
|
|
24
|
+
"""returns the current value of the counter
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Union[int, float]: value
|
|
28
|
+
"""
|
|
29
|
+
return self.value
|
|
30
|
+
|
|
31
|
+
def set(self, value: Union[int, float]):
|
|
32
|
+
"""sets the values of the counter
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
value (Union[int, float]): value to set
|
|
36
|
+
"""
|
|
37
|
+
self.value = value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AtomicCounter(Counter, metaclass=AtomicClassMeta): # type:ignore
|
|
41
|
+
"""A Counter Class which is Atomic
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"Counter",
|
|
47
|
+
"AtomicCounter"
|
|
48
|
+
]
|