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,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lazy module loading functionality.
|
|
3
|
+
|
|
4
|
+
This module provides transparent lazy loading of optional modules,
|
|
5
|
+
allowing applications to start even when optional dependencies are missing.
|
|
6
|
+
The modules are only imported when actually used, providing clear error messages.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LazyModule:
|
|
14
|
+
"""
|
|
15
|
+
A proxy for modules that should only be imported when actually used.
|
|
16
|
+
|
|
17
|
+
This allows code to run even when optional dependencies are missing,
|
|
18
|
+
but provides clear error messages when the functionality is actually needed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, module_name: str, error_message: str, install_hint: str):
|
|
22
|
+
self._module_name = module_name
|
|
23
|
+
self._module: Optional[Any] = None
|
|
24
|
+
self._imported = False
|
|
25
|
+
|
|
26
|
+
if not error_message:
|
|
27
|
+
error_message = f"Module '{module_name}' is not available"
|
|
28
|
+
|
|
29
|
+
if install_hint and install_hint.strip():
|
|
30
|
+
error_message += f". {install_hint}"
|
|
31
|
+
|
|
32
|
+
self._error_message = error_message
|
|
33
|
+
|
|
34
|
+
# Set essential module attributes to make it look like a real module
|
|
35
|
+
self.__name__ = module_name
|
|
36
|
+
self.__file__ = None
|
|
37
|
+
self.__package__ = None
|
|
38
|
+
self.__path__ = None
|
|
39
|
+
self.__spec__ = None
|
|
40
|
+
|
|
41
|
+
def _ensure_imported(self):
|
|
42
|
+
"""Import the module if not already imported."""
|
|
43
|
+
if not self._imported:
|
|
44
|
+
# Check if there's already a real module in sys.modules
|
|
45
|
+
existing_module = sys.modules.get(self._module_name)
|
|
46
|
+
if existing_module is not self and existing_module is not None:
|
|
47
|
+
# There's already a real module, use it
|
|
48
|
+
self._module = existing_module
|
|
49
|
+
self._imported = True
|
|
50
|
+
else:
|
|
51
|
+
# Try to import the real module
|
|
52
|
+
try:
|
|
53
|
+
# Temporarily remove ourselves from sys.modules to avoid recursion
|
|
54
|
+
if existing_module is self:
|
|
55
|
+
del sys.modules[self._module_name]
|
|
56
|
+
|
|
57
|
+
self._module = __import__(self._module_name)
|
|
58
|
+
self._imported = True
|
|
59
|
+
|
|
60
|
+
# Restore ourselves to sys.modules
|
|
61
|
+
sys.modules[self._module_name] = self
|
|
62
|
+
except ImportError:
|
|
63
|
+
# If import fails, raise the custom error message
|
|
64
|
+
raise ImportError(self._error_message)
|
|
65
|
+
return self._module
|
|
66
|
+
|
|
67
|
+
def __getattr__(self, name):
|
|
68
|
+
"""Delegate attribute access to the actual module."""
|
|
69
|
+
# Try to import the module first
|
|
70
|
+
try:
|
|
71
|
+
module = self._ensure_imported()
|
|
72
|
+
return getattr(module, name)
|
|
73
|
+
except ImportError:
|
|
74
|
+
# If import fails, raise the custom error message
|
|
75
|
+
# This is runtime execution, not runtime loading
|
|
76
|
+
raise ImportError(self._error_message)
|
|
77
|
+
|
|
78
|
+
def __dir__(self):
|
|
79
|
+
"""Support dir() on the lazy module."""
|
|
80
|
+
if self._imported:
|
|
81
|
+
return dir(self._module)
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def module(self):
|
|
86
|
+
"""Access to the original module if it exists."""
|
|
87
|
+
if self._imported and self._module is not None:
|
|
88
|
+
return self._module
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def __repr__(self):
|
|
92
|
+
if self._imported:
|
|
93
|
+
return repr(self._module)
|
|
94
|
+
return f"<LazyModule '{self._module_name}' (not imported)>"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def lazy_import(module_name: str, error_message: str, install_hint: str):
|
|
98
|
+
"""
|
|
99
|
+
Set up lazy loading for a module.
|
|
100
|
+
The module will be replaced in sys.modules with a LazyModule instance.
|
|
101
|
+
It will only be truly imported when an attribute is accessed.
|
|
102
|
+
"""
|
|
103
|
+
sys.modules[module_name] = LazyModule(
|
|
104
|
+
module_name, error_message, install_hint)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
import importlib
|
|
4
|
+
from ...logging_.utils import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def dynamically_load(module_name: str, obj_name: str) -> Any:
|
|
10
|
+
"""dynamically loads the module and returns the object from this file
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
module_name (str): name of python module, (typically a file name without extension)
|
|
14
|
+
obj_name (str): the name of the wanted object
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Any: the object
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
module = importlib.import_module(module_name)
|
|
21
|
+
obj = getattr(module, obj_name)
|
|
22
|
+
logger.info("Successfully loaded object '%s' from module '%s'", obj_name, module_name)
|
|
23
|
+
return obj
|
|
24
|
+
except ImportError as e:
|
|
25
|
+
logger.error("Failed to import module '%s': %s", module_name, e)
|
|
26
|
+
raise
|
|
27
|
+
except AttributeError as e:
|
|
28
|
+
logger.error("Object '%s' not found in module '%s': %s", obj_name, module_name, e)
|
|
29
|
+
raise
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"dynamically_load"
|
|
34
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import pkgutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Set as Set, List as List, Dict as Dict
|
|
7
|
+
from ..interpreter import get_python_version
|
|
8
|
+
|
|
9
|
+
if get_python_version() >= (3, 9):
|
|
10
|
+
from builtins import list as List, set as Set, dict as Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_all_modules() -> Set[str]:
|
|
14
|
+
all_modules = set()
|
|
15
|
+
|
|
16
|
+
# Get built-in modules
|
|
17
|
+
builtin_modules = set(o.strip("_") for o in sys.builtin_module_names)
|
|
18
|
+
|
|
19
|
+
# Get modules from the Python Standard Library
|
|
20
|
+
stdlib_modules = set()
|
|
21
|
+
for module in pkgutil.iter_modules():
|
|
22
|
+
try:
|
|
23
|
+
# type:ignore
|
|
24
|
+
if not module.ispkg and hasattr(module.module_finder, 'path') and module.module_finder.path.startswith(sys.prefix):
|
|
25
|
+
stdlib_modules.add(module.name)
|
|
26
|
+
except AttributeError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# Combine built-in modules and modules from the Python Standard Library
|
|
30
|
+
all_modules.update(builtin_modules)
|
|
31
|
+
all_modules.update(stdlib_modules)
|
|
32
|
+
|
|
33
|
+
all_modules.update({
|
|
34
|
+
"platform", 'os', 'pathlib', 'subprocess', "inspect", "types", "dataclasses", "__future__", "ctypes", "pkgutil",
|
|
35
|
+
"logging", "importlib", "enum", "multiprocessing", "traceback", "re", "threading"
|
|
36
|
+
})
|
|
37
|
+
return all_modules
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
ALL_MODULES: set = get_all_modules()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_imports_helper(path: str):
|
|
44
|
+
from danielutils import file_exists
|
|
45
|
+
if not file_exists(path):
|
|
46
|
+
raise ValueError(f"Can't find file {path}")
|
|
47
|
+
is_in_multiline_comment = False
|
|
48
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
49
|
+
for l in f.readlines():
|
|
50
|
+
l = l.strip()
|
|
51
|
+
if l.startswith("#"):
|
|
52
|
+
continue
|
|
53
|
+
if '"""' in l:
|
|
54
|
+
if l.count('"""') % 2 == 1:
|
|
55
|
+
is_in_multiline_comment = not is_in_multiline_comment
|
|
56
|
+
|
|
57
|
+
if is_in_multiline_comment:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
if any([l.startswith("import "), l.startswith("from ")]):
|
|
61
|
+
yield l
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def resolve_relative(base_path: str, statement: str) -> str:
|
|
65
|
+
res = base_path
|
|
66
|
+
splits = statement.split(".")
|
|
67
|
+
for s in splits:
|
|
68
|
+
if s == "":
|
|
69
|
+
res = str(Path(res).parent)
|
|
70
|
+
else:
|
|
71
|
+
res = os.path.join(res, s)
|
|
72
|
+
break
|
|
73
|
+
from danielutils import is_directory
|
|
74
|
+
if not is_directory(res):
|
|
75
|
+
res = f"{res}.py"
|
|
76
|
+
return res
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def resolve_absolute(statement: str) -> str:
|
|
80
|
+
if statement in ALL_MODULES:
|
|
81
|
+
return statement
|
|
82
|
+
if "." in statement:
|
|
83
|
+
statement = statement.split(".")[0]
|
|
84
|
+
return resolve_absolute(statement)
|
|
85
|
+
return statement
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def resolve_path(base_path, statement: str) -> str:
|
|
89
|
+
og = statement
|
|
90
|
+
FROM = "from"
|
|
91
|
+
IMPORT = "import"
|
|
92
|
+
if FROM in statement:
|
|
93
|
+
statement = statement[statement.index(FROM) + len(FROM):].strip()
|
|
94
|
+
statement = statement[:statement.index(IMPORT)].strip()
|
|
95
|
+
if statement.startswith("."):
|
|
96
|
+
return resolve_relative(base_path, statement)
|
|
97
|
+
else:
|
|
98
|
+
statement = statement[statement.index(IMPORT) + len(IMPORT):].strip()
|
|
99
|
+
return resolve_absolute(statement)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def normalize_path(path: str) -> str:
|
|
103
|
+
from danielutils import is_directory
|
|
104
|
+
if is_directory(path):
|
|
105
|
+
path = os.path.join(path, '__init__.py')
|
|
106
|
+
return str(Path(path).absolute())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_imports(path: str) -> Dict[str, Set[str]]:
|
|
110
|
+
res: Dict[str, Set[str]] = defaultdict(set)
|
|
111
|
+
i = 0
|
|
112
|
+
path = normalize_path(path)
|
|
113
|
+
queue: List[str] = [path]
|
|
114
|
+
while i < len(queue):
|
|
115
|
+
cur = queue[i]
|
|
116
|
+
imports = list(get_imports_helper(cur))
|
|
117
|
+
paths = [resolve_path(cur, imp) for imp in imports]
|
|
118
|
+
for sub_path in paths:
|
|
119
|
+
if sub_path in res:
|
|
120
|
+
continue
|
|
121
|
+
from danielutils import file_exists, is_directory
|
|
122
|
+
if not (file_exists(sub_path) or is_directory(sub_path)):
|
|
123
|
+
res[cur].add(sub_path)
|
|
124
|
+
else:
|
|
125
|
+
if sub_path not in res:
|
|
126
|
+
sub_path = normalize_path(sub_path)
|
|
127
|
+
queue.append(sub_path)
|
|
128
|
+
res[cur].add(sub_path)
|
|
129
|
+
i += 1
|
|
130
|
+
return res
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def create_dependency_graph(path: str) -> 'Graph[str]':
|
|
134
|
+
from danielutils import Graph, MultiNode
|
|
135
|
+
res = dict(get_imports(path))
|
|
136
|
+
g: Graph[str] = Graph()
|
|
137
|
+
dct: Dict[str, MultiNode[str]] = {}
|
|
138
|
+
for k, v in res.items():
|
|
139
|
+
for o in v:
|
|
140
|
+
dct[o] = dct.get(o, MultiNode(o))
|
|
141
|
+
n = MultiNode(k, [dct[o] for o in v])
|
|
142
|
+
g.add_node(n)
|
|
143
|
+
|
|
144
|
+
return g
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_dependencies(path: str, topological_sort: bool = True) -> List[str]:
|
|
148
|
+
from danielutils import Graph
|
|
149
|
+
g: Graph[str] = create_dependency_graph(path)
|
|
150
|
+
|
|
151
|
+
if topological_sort:
|
|
152
|
+
return [n.data for n in g.topological_sort()]
|
|
153
|
+
|
|
154
|
+
return [n.data for n in g]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = [
|
|
158
|
+
"get_dependencies",
|
|
159
|
+
"create_dependency_graph",
|
|
160
|
+
'ALL_MODULES'
|
|
161
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..backoff_strategy import BackOffStrategy
|
|
3
|
+
from ...logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConstantBackOffStrategy(BackOffStrategy):
|
|
9
|
+
"""
|
|
10
|
+
will always back off exactly the same amount of time
|
|
11
|
+
|
|
12
|
+
:param delay: The amount of milliseconds to sleep
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, delay: float) -> None:
|
|
16
|
+
if not delay >= 0:
|
|
17
|
+
logger.error("Invalid delay value: %s - must be positive", delay)
|
|
18
|
+
raise ValueError("delay must be positive")
|
|
19
|
+
delay = float(delay)
|
|
20
|
+
super().__init__(lambda: delay)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ConstantBackOffStrategy"
|
|
25
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..backoff_strategy import BackOffStrategy
|
|
3
|
+
from ...logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ExponentialBackOffStrategy(BackOffStrategy):
|
|
9
|
+
def __init__(self, initial: float) -> None:
|
|
10
|
+
if not initial >= 0:
|
|
11
|
+
logger.error("Invalid initial value: %s - must be positive", initial)
|
|
12
|
+
raise ValueError("initial must be positive")
|
|
13
|
+
attempt: int = 1
|
|
14
|
+
|
|
15
|
+
def inner() -> float:
|
|
16
|
+
nonlocal attempt
|
|
17
|
+
attempt += 1
|
|
18
|
+
backoff_time = initial ** (attempt - 1)
|
|
19
|
+
return backoff_time
|
|
20
|
+
|
|
21
|
+
super().__init__(inner)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ExponentialBackOffStrategy"
|
|
26
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from ..backoff_strategy import BackOffStrategy
|
|
5
|
+
from ...logging_.utils import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FunctionalBackoffStrategy(BackOffStrategy):
|
|
11
|
+
def __init__(self, func: Callable[[int], float]) -> None:
|
|
12
|
+
logger.debug("Initializing FunctionalBackoffStrategy with function: %s", func.__name__ if hasattr(func, '__name__') else type(func).__name__)
|
|
13
|
+
attempt = 1
|
|
14
|
+
logger.debug("Setting up functional backoff with custom function")
|
|
15
|
+
|
|
16
|
+
def inner() -> float:
|
|
17
|
+
nonlocal attempt
|
|
18
|
+
attempt += 1
|
|
19
|
+
backoff_time = func(attempt - 1)
|
|
20
|
+
logger.debug("Functional backoff attempt %s: %sms (func(%s))", attempt, backoff_time, attempt - 1)
|
|
21
|
+
return backoff_time
|
|
22
|
+
|
|
23
|
+
super().__init__(inner)
|
|
24
|
+
logger.debug("FunctionalBackoffStrategy initialized successfully")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"FunctionalBackoffStrategy"
|
|
29
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..backoff_strategy import BackOffStrategy
|
|
3
|
+
from ...logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LinerBackoffStrategy(BackOffStrategy):
|
|
9
|
+
def __init__(self, initial: float, additive_term: float) -> None:
|
|
10
|
+
attempt = 1
|
|
11
|
+
|
|
12
|
+
def inner() -> float:
|
|
13
|
+
nonlocal attempt
|
|
14
|
+
attempt += 1
|
|
15
|
+
backoff_time = initial + additive_term * (attempt - 1)
|
|
16
|
+
return backoff_time
|
|
17
|
+
|
|
18
|
+
super().__init__(inner)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'LinerBackoffStrategy'
|
|
23
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..backoff_strategy import BackOffStrategy
|
|
3
|
+
from ...logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MultiplicativeBackoff(BackOffStrategy):
|
|
9
|
+
def __init__(self, initial_backoff: float) -> None:
|
|
10
|
+
attempt = 1
|
|
11
|
+
|
|
12
|
+
def inner() -> float:
|
|
13
|
+
nonlocal attempt
|
|
14
|
+
attempt += 1
|
|
15
|
+
backoff_time = initial_backoff * (attempt - 1)
|
|
16
|
+
return backoff_time
|
|
17
|
+
|
|
18
|
+
super().__init__(inner)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'MultiplicativeBackoff'
|
|
23
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..backoff_strategy import BackOffStrategy
|
|
3
|
+
from ...logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NoBackOffStrategy(BackOffStrategy):
|
|
9
|
+
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
logger.debug("Initializing NoBackOffStrategy (no delay)")
|
|
12
|
+
super().__init__(lambda: 0.0)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'NoBackOffStrategy',
|
|
17
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..aliases import Supplier
|
|
3
|
+
from ..logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BackOffStrategy:
|
|
9
|
+
"""
|
|
10
|
+
A class to create a common abstraction for backoff strategies
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, supp: Supplier[float]) -> None:
|
|
14
|
+
self._supp = supp
|
|
15
|
+
|
|
16
|
+
def get_backoff(self) -> float:
|
|
17
|
+
"""
|
|
18
|
+
Get the backoff time in milliseconds.
|
|
19
|
+
|
|
20
|
+
:return: amount of milliseconds to sleep
|
|
21
|
+
"""
|
|
22
|
+
backoff_time = self._supp()
|
|
23
|
+
return backoff_time
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BackOffStrategy"
|
|
28
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from typing import Generic, TypeVar, Optional
|
|
4
|
+
|
|
5
|
+
from ..aliases import Supplier, Consumer
|
|
6
|
+
from .backoff_strategies import ConstantBackOffStrategy
|
|
7
|
+
|
|
8
|
+
from .backoff_strategy import BackOffStrategy
|
|
9
|
+
from ..logging_.utils import get_logger
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RetryExecutor(Generic[T]):
|
|
17
|
+
def __init__(self, backoff_strategy: BackOffStrategy = ConstantBackOffStrategy(200)) -> None:
|
|
18
|
+
self._backoff_strategy = backoff_strategy
|
|
19
|
+
logger.info("RetryExecutor initialized with backoff strategy: %s", type(backoff_strategy).__name__)
|
|
20
|
+
|
|
21
|
+
def __enter__(self):
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
25
|
+
if exc_type is not None:
|
|
26
|
+
logger.warning("RetryExecutor context exited with exception: %s: %s", exc_type.__name__, exc_val)
|
|
27
|
+
|
|
28
|
+
def execute(self, supp: Supplier[T], max_retries: int = 5,
|
|
29
|
+
exception_callback: Optional[Consumer[Exception]] = None) -> Optional[T]:
|
|
30
|
+
logger.info("Starting retry execution with max_retries=%s", max_retries)
|
|
31
|
+
|
|
32
|
+
for i in range(max_retries):
|
|
33
|
+
try:
|
|
34
|
+
result = supp()
|
|
35
|
+
logger.info("Execution succeeded on attempt %s", i + 1)
|
|
36
|
+
return result
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.warning("Attempt %s failed with %s: %s", i + 1, type(e).__name__, e)
|
|
39
|
+
if exception_callback:
|
|
40
|
+
exception_callback(e)
|
|
41
|
+
|
|
42
|
+
if i != max_retries - 1:
|
|
43
|
+
self._sleep()
|
|
44
|
+
|
|
45
|
+
logger.error("All %s attempts failed", max_retries)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def _sleep(self) -> None:
|
|
49
|
+
backoff_time = self._backoff_strategy.get_backoff() / 1000
|
|
50
|
+
time.sleep(backoff_time)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"RetryExecutor",
|
|
55
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .try_get import *
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable, Any, Optional
|
|
3
|
+
from ..logging_.utils import get_logger
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def try_get(supplier: Callable[[], Any]) -> Optional[Any]:
|
|
9
|
+
"""try to get a value from a function and return the value or return None on fail
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
supplier (Callable[[], Any]): supplier function
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Optional[Any]: return value
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
result = supplier()
|
|
19
|
+
logger.debug("try_get succeeded for supplier: %s", supplier.__name__)
|
|
20
|
+
return result
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logger.debug("try_get failed for supplier: %s with %s: %s", supplier.__name__, type(e).__name__, e)
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"try_get"
|
|
28
|
+
]
|