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,204 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable, cast, Any, TypeVar, Dict, List
|
|
3
|
+
import inspect
|
|
4
|
+
import functools
|
|
5
|
+
from ..reflection import is_function_annotated_properly
|
|
6
|
+
from ..functions import isoftype, isoneof, isoneof_strict
|
|
7
|
+
from ..exceptions import OverloadDuplication, OverloadNotFound
|
|
8
|
+
from .deprecate import deprecate
|
|
9
|
+
from ..versioned_imports import ParamSpec
|
|
10
|
+
from ..logging_.utils import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
P = ParamSpec("P")
|
|
16
|
+
FuncT = Callable[P, T] # type:ignore
|
|
17
|
+
T2 = TypeVar("T2")
|
|
18
|
+
P2 = ParamSpec("P2")
|
|
19
|
+
FuncT2 = Callable[P2, T2] # type:ignore
|
|
20
|
+
|
|
21
|
+
__overload_dict: Dict[str, Dict[tuple, Callable]] = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@deprecate("'explicit_global_overload' is a legacy decorator please use 'overload' instead")
|
|
25
|
+
def explicit_global_overload(*types) -> Callable:
|
|
26
|
+
"""decorator for overloading functions\n
|
|
27
|
+
Usage\n-------\n
|
|
28
|
+
@overload(str,str)\n
|
|
29
|
+
def print_info(name,color):
|
|
30
|
+
...\n\n
|
|
31
|
+
@overload(str,[int,float]))\n
|
|
32
|
+
def print_info(name,age):
|
|
33
|
+
...\n\n
|
|
34
|
+
|
|
35
|
+
* use None to skip argument
|
|
36
|
+
* use no arguments to mark as default function
|
|
37
|
+
* you should overload in decreasing order of specificity! e.g
|
|
38
|
+
@overload(int) should appear in the code before @overload(Any)
|
|
39
|
+
|
|
40
|
+
\n\n\n
|
|
41
|
+
\nRaises:
|
|
42
|
+
OverloadDuplication: if a functions is overloaded twice (or more)
|
|
43
|
+
with same argument types
|
|
44
|
+
OverloadNotFound: if an overloaded function is called with
|
|
45
|
+
types that has no variant of the function
|
|
46
|
+
|
|
47
|
+
\nNotice:
|
|
48
|
+
The function's __doc__ will hold the value of the last variant only
|
|
49
|
+
"""
|
|
50
|
+
# make sure to use unique global dictionary
|
|
51
|
+
if len(types) == 1 and type(types[0]).__name__ == "function":
|
|
52
|
+
raise ValueError("can't create an overload without defining types")
|
|
53
|
+
global __overload_dict
|
|
54
|
+
types = cast(tuple, types)
|
|
55
|
+
# allow input of both tuples and lists for flexibly
|
|
56
|
+
if len(types) > 0:
|
|
57
|
+
types_as_list = list(types)
|
|
58
|
+
for i, maybe_list_of_types in enumerate(types):
|
|
59
|
+
if isoneof(maybe_list_of_types, [list, tuple]):
|
|
60
|
+
types_as_list[i] = tuple(sorted(list(maybe_list_of_types),
|
|
61
|
+
key=lambda sub_type: sub_type.__name__))
|
|
62
|
+
types = tuple(types_as_list)
|
|
63
|
+
|
|
64
|
+
def deco(func: Callable) -> Callable:
|
|
65
|
+
if not callable(func):
|
|
66
|
+
raise TypeError("overload decorator must be used on a callable")
|
|
67
|
+
|
|
68
|
+
# assign current overload to overload dictionary
|
|
69
|
+
name = f"{func.__module__}.{func.__qualname__}"
|
|
70
|
+
|
|
71
|
+
if name not in __overload_dict:
|
|
72
|
+
__overload_dict[name] = {}
|
|
73
|
+
|
|
74
|
+
if types in __overload_dict[name]:
|
|
75
|
+
# raise if current overload already exists for current function
|
|
76
|
+
raise OverloadDuplication(
|
|
77
|
+
f"{name} has duplicate overloading for type(s): {types}")
|
|
78
|
+
|
|
79
|
+
__overload_dict[name][types] = func
|
|
80
|
+
|
|
81
|
+
@functools.wraps(func)
|
|
82
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
83
|
+
default_func = None
|
|
84
|
+
# select correct overload
|
|
85
|
+
for variable_types, curr_func in __overload_dict[f"{func.__module__}.{func.__qualname__}"].items():
|
|
86
|
+
if len(variable_types) == 0:
|
|
87
|
+
if default_func is None:
|
|
88
|
+
default_func = curr_func
|
|
89
|
+
continue
|
|
90
|
+
# will not reach here because of duplicate overloading so this is redundant
|
|
91
|
+
raise ValueError("Can't have two default functions")
|
|
92
|
+
|
|
93
|
+
if len(variable_types) != len(args):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
for i, variable_type in enumerate(variable_types):
|
|
97
|
+
if variable_type is not None:
|
|
98
|
+
if isoneof(variable_type, [list, tuple]):
|
|
99
|
+
if not isoneof_strict(args[i], variable_type):
|
|
100
|
+
break
|
|
101
|
+
else:
|
|
102
|
+
if not isoftype(args[i], variable_type):
|
|
103
|
+
break
|
|
104
|
+
else:
|
|
105
|
+
return curr_func(*args, **kwargs)
|
|
106
|
+
|
|
107
|
+
if default_func is not None:
|
|
108
|
+
return default_func(*args, **kwargs)
|
|
109
|
+
# or raise exception if no overload exists for current arguments
|
|
110
|
+
raise OverloadNotFound(
|
|
111
|
+
f"function {func.__module__}.{func.__qualname__} is not overloaded with {[type(v) for v in args]}")
|
|
112
|
+
|
|
113
|
+
return wrapper
|
|
114
|
+
|
|
115
|
+
return deco
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class overload:
|
|
119
|
+
"""this class create an object to manage the overloads for a given function.\n
|
|
120
|
+
will only match a specific resolution and won't infer best guess for types
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
__SKIP_SET = {"self", "cls", "args", "kwargs"}
|
|
124
|
+
|
|
125
|
+
def __init__(self, func: FuncT):
|
|
126
|
+
overload._validate(func)
|
|
127
|
+
self._qualname = func.__qualname__
|
|
128
|
+
self._moudle = func.__module__
|
|
129
|
+
self._functions: Dict[int, List[Callable]] = {}
|
|
130
|
+
self._functions[overload._get_key(func)] = [func]
|
|
131
|
+
functools.wraps(func)(self)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _get_key(func: Callable):
|
|
135
|
+
return len(inspect.signature(func).parameters)
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _validate(func: Callable):
|
|
139
|
+
if not callable(func):
|
|
140
|
+
raise ValueError("Can only overload functions")
|
|
141
|
+
if not is_function_annotated_properly(func):
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"{func.__module__}.{func.__qualname__} is not properly annotated."
|
|
144
|
+
"\nFunction must be fully annotated to be overloaded")
|
|
145
|
+
|
|
146
|
+
def overload(self, func: FuncT2) -> "overload":
|
|
147
|
+
"""will add another function to the list of available options
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
func (Callable): a new alternative function
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
overload2: returns the overload object
|
|
154
|
+
"""
|
|
155
|
+
self._validate(func)
|
|
156
|
+
k = overload._get_key(func)
|
|
157
|
+
if k not in self._functions:
|
|
158
|
+
self._functions[k] = []
|
|
159
|
+
self._functions[k].append(func)
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def __call__(self, *args, **kwargs):
|
|
163
|
+
num_args = len(args) + len(kwargs.keys())
|
|
164
|
+
if num_args not in self._functions:
|
|
165
|
+
raise AttributeError(
|
|
166
|
+
f"No overload with {num_args} argument found for {self._moudle}.{self._qualname}")
|
|
167
|
+
|
|
168
|
+
if num_args == 0:
|
|
169
|
+
return self._functions[num_args][0](*args, **kwargs)
|
|
170
|
+
|
|
171
|
+
max_score = 0
|
|
172
|
+
winner = self._functions[num_args][0]
|
|
173
|
+
EXACT_MATCH = 1 / num_args
|
|
174
|
+
SUBCLASS = 1 / num_args
|
|
175
|
+
for func in self._functions[num_args]:
|
|
176
|
+
score = 0
|
|
177
|
+
signature = inspect.signature(func)
|
|
178
|
+
for i, tup in enumerate(signature.parameters.items()):
|
|
179
|
+
param_name, param_type = tup
|
|
180
|
+
if param_name in overload.__SKIP_SET:
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if type(args[i]) == param_type.annotation: # pylint :disable=unidiomatic-typecheck
|
|
184
|
+
score += EXACT_MATCH # type:ignore
|
|
185
|
+
|
|
186
|
+
elif isoftype(args[i], param_type.annotation):
|
|
187
|
+
score += SUBCLASS # type:ignore
|
|
188
|
+
else:
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
# reaching here means current function matches perfectly the annotation
|
|
193
|
+
if score > max_score:
|
|
194
|
+
max_score = score
|
|
195
|
+
winner = func
|
|
196
|
+
# raise AttributeError("No overload found")
|
|
197
|
+
|
|
198
|
+
return winner(*args, **kwargs)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
"explicit_global_overload",
|
|
203
|
+
"overload"
|
|
204
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Callable, Any, TypeVar
|
|
2
|
+
import functools
|
|
3
|
+
from .validate import validate
|
|
4
|
+
from ..colors import warning
|
|
5
|
+
from ..versioned_imports import ParamSpec
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
P = ParamSpec("P")
|
|
9
|
+
FuncT = Callable[P, T] # type:ignore
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@validate # type:ignore
|
|
13
|
+
def PartiallyImplemented(func: FuncT) -> FuncT:
|
|
14
|
+
"""decorator to mark function as not fully implemented for development purposes
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
func (Callable): the function to decorate
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@functools.wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
22
|
+
warning(
|
|
23
|
+
f"As marked by the developer, {func.__module__}.{func.__qualname__} "
|
|
24
|
+
"may not be fully implemented and might not work properly.")
|
|
25
|
+
return func(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
return wrapper
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"PartiallyImplemented"
|
|
32
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import functools
|
|
3
|
+
import multiprocessing
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from ..reflection import get_prev_frame
|
|
8
|
+
from ..abstractions.multiprogramming import process_id
|
|
9
|
+
from ..logging_.utils import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def processify(func):
|
|
15
|
+
"""Modifies the function so that when calling it, a new process
|
|
16
|
+
will start to run it with provided arguments. Note that no return
|
|
17
|
+
value will be given.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
func (Callable): the function to make a process
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Callable: the modified function
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Creating processify decorator for function %s", func.__name__)
|
|
26
|
+
multiprocessing.freeze_support()
|
|
27
|
+
|
|
28
|
+
@functools.wraps(func)
|
|
29
|
+
def wrapper(*args, **kwargs):
|
|
30
|
+
main_pid = kwargs.get("__main_pid", process_id())
|
|
31
|
+
current_pid = process_id()
|
|
32
|
+
logger.debug("Processify wrapper called for %s, main_pid=%s, current_pid=%s", func.__name__, main_pid, current_pid)
|
|
33
|
+
|
|
34
|
+
if current_pid == main_pid:
|
|
35
|
+
logger.info("Starting new process for function %s", func.__name__)
|
|
36
|
+
frame = get_prev_frame(2) # type:ignore
|
|
37
|
+
dct = {k: v for k, v in frame.f_globals.items() if type(v) != type(inspect)} # type:ignore
|
|
38
|
+
logger.debug("Process context prepared with %s global variables", len(dct))
|
|
39
|
+
p = multiprocessing.Process(target=_run_func, args=(main_pid, dct, func.__name__, args, kwargs))
|
|
40
|
+
p.start()
|
|
41
|
+
logger.debug("Process started for %s, PID: %s", func.__name__, p.pid)
|
|
42
|
+
p.join() # Optionally wait for the process to finish
|
|
43
|
+
logger.info("Process completed for %s", func.__name__)
|
|
44
|
+
else:
|
|
45
|
+
logger.debug("Executing %s in child process", func.__name__)
|
|
46
|
+
del kwargs["__main_pid"]
|
|
47
|
+
return func(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
logger.debug("Processify decorator applied to %s", func.__name__)
|
|
50
|
+
return wrapper
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _run_func(main_pid: int, dct: dict, func_name: str, args, kwargs) -> None:
|
|
54
|
+
logger.debug("Running function %s in child process", func_name)
|
|
55
|
+
return dct[func_name](*args, __main_pid=main_pid, **kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def debug_info(include_builtins: bool = False) -> Dict[str, Any]:
|
|
59
|
+
f = get_prev_frame(2)
|
|
60
|
+
if f is None:
|
|
61
|
+
raise RuntimeError("Failed to get frame")
|
|
62
|
+
g = {k: v for k, v in f.f_globals.items() if k != "__builtins__"} if not include_builtins else dict(f.f_globals)
|
|
63
|
+
return {
|
|
64
|
+
"file": f.f_code.co_filename,
|
|
65
|
+
"function": f.f_code.co_qualname, # type:ignore
|
|
66
|
+
"line": f.f_lineno,
|
|
67
|
+
"globals": g,
|
|
68
|
+
"locals": dict(f.f_locals)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"processify"
|
|
74
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Any, Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class property2:
|
|
5
|
+
"""my partial implementation to @property
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, func):
|
|
9
|
+
self.gfunc = func
|
|
10
|
+
self.sfunc = None
|
|
11
|
+
self.dfunc = None
|
|
12
|
+
|
|
13
|
+
def setter(self, func):
|
|
14
|
+
"""setter function
|
|
15
|
+
"""
|
|
16
|
+
self.sfunc = func
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def deleter(self, func):
|
|
20
|
+
"""deleter function"""
|
|
21
|
+
self.dfunc = func
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __get__(self, instance: Any, owner: Union[type, None] = None) -> Any:
|
|
25
|
+
if callable(self.gfunc):
|
|
26
|
+
return self.gfunc(instance)
|
|
27
|
+
raise ValueError("Can't use unset getter function")
|
|
28
|
+
|
|
29
|
+
def __set__(self, instance: Any, value: Any) -> None:
|
|
30
|
+
if callable(self.sfunc):
|
|
31
|
+
return self.sfunc(instance)
|
|
32
|
+
raise ValueError("Can't use unset getter function")
|
|
33
|
+
|
|
34
|
+
def __delete__(self, instance: Any) -> None:
|
|
35
|
+
if callable(self.dfunc):
|
|
36
|
+
return self.dfunc(instance)
|
|
37
|
+
raise ValueError("Can't use unset getter function")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"property2"
|
|
42
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from ..logging_.utils import get_logger
|
|
3
|
+
|
|
4
|
+
logger = get_logger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def singleton(og_class):
|
|
8
|
+
"""Decorator that ensures a class has only one instance (Singleton pattern)."""
|
|
9
|
+
instance = None
|
|
10
|
+
original_new = getattr(og_class, '__new__')
|
|
11
|
+
original_init = getattr(og_class, '__init__')
|
|
12
|
+
|
|
13
|
+
def __new__(cls, *args, **kwargs):
|
|
14
|
+
nonlocal instance
|
|
15
|
+
if instance is None:
|
|
16
|
+
logger.debug("Creating singleton instance for %s", og_class.__name__)
|
|
17
|
+
# index 0 is the current class.
|
|
18
|
+
# in the minimal case index 1 has 'object' class
|
|
19
|
+
# otherwise the immediate parent of current class
|
|
20
|
+
cls_index, og_index = 0, list(cls.__mro__).index(og_class)
|
|
21
|
+
blacklist = {*cls.__mro__[:og_index + 1]}
|
|
22
|
+
for candidate in cls.__mro__[og_index + 1:]:
|
|
23
|
+
if candidate not in blacklist:
|
|
24
|
+
try:
|
|
25
|
+
instance = candidate.__new__(cls, *args, **kwargs)
|
|
26
|
+
logger.debug("Successfully created singleton instance using %s", candidate.__name__)
|
|
27
|
+
break
|
|
28
|
+
except Exception as e:
|
|
29
|
+
logger.debug("Failed to create instance using %s: %s", candidate.__name__, e)
|
|
30
|
+
pass
|
|
31
|
+
else:
|
|
32
|
+
instance = object.__new__(cls)
|
|
33
|
+
logger.debug("Created singleton instance using object.__new__")
|
|
34
|
+
else:
|
|
35
|
+
logger.debug("Returning existing singleton instance for %s", og_class.__name__)
|
|
36
|
+
return instance
|
|
37
|
+
|
|
38
|
+
is_init: bool = False
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
41
|
+
nonlocal is_init
|
|
42
|
+
if not is_init:
|
|
43
|
+
logger.debug("Initializing singleton instance for %s", og_class.__name__)
|
|
44
|
+
original_init(self, *args, **kwargs)
|
|
45
|
+
is_init = True
|
|
46
|
+
logger.info("Singleton instance initialized for %s", og_class.__name__)
|
|
47
|
+
else:
|
|
48
|
+
logger.debug("Singleton instance already initialized for %s", og_class.__name__)
|
|
49
|
+
|
|
50
|
+
setattr(og_class, "__new__", __new__)
|
|
51
|
+
setattr(og_class, "__init__", __init__)
|
|
52
|
+
setattr(og_class, "instance", lambda: instance)
|
|
53
|
+
logger.debug("Applied singleton decorator to %s", og_class.__name__)
|
|
54
|
+
return og_class
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"singleton"
|
|
59
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable, TypeVar
|
|
3
|
+
import functools
|
|
4
|
+
import threading
|
|
5
|
+
from ..versioned_imports import ParamSpec
|
|
6
|
+
from ..logging_.utils import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
P = ParamSpec("P")
|
|
12
|
+
FuncT = Callable[P, T] # type:ignore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def threadify(func: FuncT) -> FuncT:
|
|
16
|
+
"""will modify the function that when calling it a new thread
|
|
17
|
+
will start to run it with provided arguments.\nnote that no return value will be given
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
func (Callable): the function to make a thread
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Callable: the modified function
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Creating threadify decorator for function %s", func.__name__)
|
|
26
|
+
|
|
27
|
+
@functools.wraps(func)
|
|
28
|
+
def wrapper(*args, **kwargs):
|
|
29
|
+
logger.debug("Starting thread for function %s", func.__name__)
|
|
30
|
+
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
|
31
|
+
thread.start()
|
|
32
|
+
logger.debug("Thread started for %s, thread_id=%s", func.__name__, thread.ident)
|
|
33
|
+
|
|
34
|
+
logger.debug("Threadify decorator applied to %s", func.__name__)
|
|
35
|
+
return wrapper
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"threadify"
|
|
40
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import functools
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Callable, TypeVar, Union
|
|
5
|
+
from .validate import validate
|
|
6
|
+
from ..versioned_imports import ParamSpec
|
|
7
|
+
from ..logging_.utils import get_logger
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
P = ParamSpec("P")
|
|
13
|
+
FuncT = Callable[P, T] # type:ignore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@validate # type:ignore
|
|
17
|
+
def timeout(duration: Union[int, float], silent: bool = False) -> Callable[[FuncT], FuncT]:
|
|
18
|
+
"""A decorator to limit runtime for a function
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
duration (Union[int, float]): allowed runtime duration
|
|
22
|
+
silent (bool, optional): keyword only argument whether
|
|
23
|
+
to pass the exception up the call stack. Defaults to False.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: if a function is not provided to be decorated
|
|
27
|
+
Exception: any exception from within the function
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Callable: the result decorated function
|
|
31
|
+
"""
|
|
32
|
+
logger.debug("Creating timeout decorator with duration=%ss, silent=%s", duration, silent)
|
|
33
|
+
|
|
34
|
+
# https://stackoverflow.com/a/21861599/6416556
|
|
35
|
+
def timeout_deco(func: FuncT) -> FuncT:
|
|
36
|
+
logger.debug("Applying timeout decorator to function %s", func.__name__)
|
|
37
|
+
if not callable(func):
|
|
38
|
+
logger.error("Object %s is not callable", func)
|
|
39
|
+
raise ValueError("timeout must decorate a function")
|
|
40
|
+
|
|
41
|
+
@functools.wraps(func)
|
|
42
|
+
def wrapper(*args, **kwargs):
|
|
43
|
+
logger.debug("Executing function %s with timeout %ss", func.__name__, duration)
|
|
44
|
+
res: list = [
|
|
45
|
+
TimeoutError(f'{func.__module__}.{func.__qualname__} timed out after {duration} seconds!')]
|
|
46
|
+
|
|
47
|
+
def timeout_wrapper() -> None:
|
|
48
|
+
try:
|
|
49
|
+
logger.debug("Starting timeout thread for %s", func.__name__)
|
|
50
|
+
res[0] = func(*args, **kwargs)
|
|
51
|
+
logger.debug("Function %s completed successfully in timeout thread", func.__name__)
|
|
52
|
+
except Exception as function_error: # pylint : disable=broad-exception-caught
|
|
53
|
+
logger.warning("Function %s raised exception in timeout thread: %s: %s", func.__name__, type(function_error).__name__, function_error)
|
|
54
|
+
res[0] = function_error
|
|
55
|
+
|
|
56
|
+
t = threading.Thread(target=timeout_wrapper, daemon=True)
|
|
57
|
+
try:
|
|
58
|
+
logger.debug("Starting timeout thread for %s", func.__name__)
|
|
59
|
+
t.start()
|
|
60
|
+
t.join(duration)
|
|
61
|
+
logger.debug("Timeout thread for %s completed or timed out", func.__name__)
|
|
62
|
+
except Exception as thread_error:
|
|
63
|
+
logger.error("Thread error for %s: %s: %s", func.__name__, type(thread_error).__name__, thread_error)
|
|
64
|
+
raise thread_error
|
|
65
|
+
if isinstance(res[0], BaseException):
|
|
66
|
+
if not silent:
|
|
67
|
+
logger.warning("Function %s timed out or raised exception: %s", func.__name__, type(res[0]).__name__)
|
|
68
|
+
raise res[0]
|
|
69
|
+
logger.debug("Function %s timed out but silent mode enabled", func.__name__)
|
|
70
|
+
return None
|
|
71
|
+
logger.debug("Function %s completed successfully within timeout", func.__name__)
|
|
72
|
+
return res[0]
|
|
73
|
+
|
|
74
|
+
logger.debug("Timeout decorator applied to %s", func.__name__)
|
|
75
|
+
return wrapper
|
|
76
|
+
|
|
77
|
+
return timeout_deco
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"timeout"
|
|
82
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Callable, Any, Dict, List, Tuple
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_default_implementation(func: Callable) -> bool:
|
|
5
|
+
return type(func).__qualname__ == "wrapper_descriptor"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def usable(func: Callable) -> bool:
|
|
9
|
+
return func is not None and not is_default_implementation(func)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def total_ordering(cls: type) -> type:
|
|
13
|
+
funcs: Dict[str, Callable[[Any, Any], Any]] = {
|
|
14
|
+
'__eq__': getattr(cls, '__eq__'),
|
|
15
|
+
'__ne__': getattr(cls, '__ne__'),
|
|
16
|
+
'__gt__': getattr(cls, '__gt__'),
|
|
17
|
+
'__ge__': getattr(cls, '__ge__'),
|
|
18
|
+
'__lt__': getattr(cls, '__lt__'),
|
|
19
|
+
'__le__': getattr(cls, '__le__'),
|
|
20
|
+
}
|
|
21
|
+
g1: List[Callable[[Any, Any], Any]] = [funcs["__eq__"], funcs["__ne__"]]
|
|
22
|
+
u1 = any(map(usable, g1))
|
|
23
|
+
g2: List[Callable[[Any, Any], Any]] = [funcs["__ge__"], funcs["__gt__"]]
|
|
24
|
+
u2 = any(map(usable, g2))
|
|
25
|
+
g3: List[Callable[[Any, Any], Any]] = [funcs["__lt__"], funcs["__le__"]]
|
|
26
|
+
u3 = any(map(usable, g3))
|
|
27
|
+
if sum(map(int, [u1, u2, u3])) < 2:
|
|
28
|
+
raise ValueError("There are not enough functions from different groups implemented")
|
|
29
|
+
mapping: Dict[str, Dict[Tuple, Callable]] = {
|
|
30
|
+
'__eq__': {
|
|
31
|
+
# a==b <=> not a!=b
|
|
32
|
+
('__ne__',): lambda self, other: not funcs['__eq__'](self, other),
|
|
33
|
+
# a==b <=> a<=b and b<=a
|
|
34
|
+
('__le__',): lambda self, other: funcs['__le__'](self, other) and funcs['__le__'](other, self),
|
|
35
|
+
# a==b <=> a>=b and b>=a
|
|
36
|
+
# a==b <=> not a<b and not b<a
|
|
37
|
+
# a==b <=> not a>b and not b>a
|
|
38
|
+
# a==b <=> a<=b and not a<b
|
|
39
|
+
('__le__', '__lt__'): lambda self, other: funcs['__le__'](self, other) and not funcs['__lt__'](self, other),
|
|
40
|
+
# a==b <=> a>=b and not a>b
|
|
41
|
+
('__ge__', '__ge__'): lambda self, other: funcs['__ge__'](self, other) and not funcs['__gt__'](self, other),
|
|
42
|
+
# a==b <=> a<=b and not a>=b
|
|
43
|
+
('__le__', '__ge__'): lambda self, other: funcs['__ge__'](self, other) and funcs['__le__'](self, other),
|
|
44
|
+
# not a<b and not b>a
|
|
45
|
+
('__lt__', '__gt__'): lambda self, other: not funcs['__lt__'](self, other) and not funcs['__gt__'](other,
|
|
46
|
+
self),
|
|
47
|
+
},
|
|
48
|
+
'__ne__': {
|
|
49
|
+
('__eq__',): lambda self, other: not funcs['__eq__'](self, other),
|
|
50
|
+
},
|
|
51
|
+
'__lt__': {
|
|
52
|
+
('__ge__',): lambda self, other: not funcs['__ge__'](self, other),
|
|
53
|
+
('__eq__', '__le__'): lambda self, other: funcs['__le__'](self, other) and not funcs['__eq__'](self, other),
|
|
54
|
+
},
|
|
55
|
+
'__le__': {
|
|
56
|
+
('__gt__',): lambda self, other: not funcs['__gt__'](self, other),
|
|
57
|
+
('__eq__', '__lt__'): lambda self, other: funcs['__lt__'](self, other) or funcs['__eq__'](self, other),
|
|
58
|
+
},
|
|
59
|
+
'__gt__': {
|
|
60
|
+
('__le__',): lambda self, other: not funcs['__le__'](self, other),
|
|
61
|
+
('__eq__', '__ge__'): lambda self, other: funcs['__ge__'](self, other) and not funcs['__eq__'](self, other),
|
|
62
|
+
},
|
|
63
|
+
'__ge__': {
|
|
64
|
+
('__lt__',): lambda self, other: not funcs['__lt__'](self, other),
|
|
65
|
+
('__eq__', '__gt__'): lambda self, other: funcs['__gt__'](self, other) or funcs['__eq__'](self, other),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
for name, f in funcs.items():
|
|
69
|
+
if usable(f):
|
|
70
|
+
continue
|
|
71
|
+
for option, implementation in mapping[name].items():
|
|
72
|
+
if all(map(usable, (funcs[dep] for dep in option))):
|
|
73
|
+
funcs[name] = implementation
|
|
74
|
+
break
|
|
75
|
+
for name, f in funcs.items():
|
|
76
|
+
setattr(cls, name, f)
|
|
77
|
+
return cls
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"total_ordering"
|
|
82
|
+
]
|