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,197 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import IO, Optional, cast, Union, Generator, Tuple as Tuple, List as List
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import time
|
|
6
|
+
from ..decorators import timeout, validate
|
|
7
|
+
from ..conversions import str_to_bytes
|
|
8
|
+
from ..generators import join_generators, generator_from_stream
|
|
9
|
+
from ..reflection import get_python_version
|
|
10
|
+
from ..logging_.utils import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
if get_python_version() >= (3, 9):
|
|
15
|
+
from builtins import tuple as Tuple, list as List # type:ignore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cm(*args: str, shell: bool = True) -> Tuple[int, bytes, bytes]:
|
|
19
|
+
"""Execute windows shell command and return output
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
command or args:\n
|
|
23
|
+
command (str): A string representation of the command to execute.
|
|
24
|
+
args (list[str]): A list of all the command parts
|
|
25
|
+
shell (bool, optional): whether to execute in shell. Defaults to True.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
TypeError: will raise if 'shell' is not boolean
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple[int, bytes, bytes]: return code, stdout, stderr
|
|
32
|
+
"""
|
|
33
|
+
logger.info("Executing command: %s (shell=%s)", ' '.join(args), shell)
|
|
34
|
+
if not isinstance(shell, bool):
|
|
35
|
+
logger.error("'shell' parameter must be of type bool")
|
|
36
|
+
raise TypeError(
|
|
37
|
+
"In function 'cm' param 'shell' must be of type bool")
|
|
38
|
+
|
|
39
|
+
for i, arg in enumerate(args):
|
|
40
|
+
path_obj = Path(args[i])
|
|
41
|
+
if path_obj.is_file() or path_obj.is_dir():
|
|
42
|
+
args = (*args[:i], f"\"{arg}\"", *args[i + 1:])
|
|
43
|
+
|
|
44
|
+
command_str = " ".join(args)
|
|
45
|
+
res = subprocess.run(command_str, shell=shell,
|
|
46
|
+
capture_output=True, check=False)
|
|
47
|
+
logger.info("Command completed with return code: %s", res.returncode)
|
|
48
|
+
return res.returncode, res.stdout, res.stderr
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@validate # type:ignore
|
|
52
|
+
def sleep(seconds: Union[int, float]) -> None:
|
|
53
|
+
"""make current thread sleep
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
seconds (float): number of seconds to sleep
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
None
|
|
60
|
+
"""
|
|
61
|
+
time.sleep(seconds)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def __acm_write(*args, p: subprocess.Popen, sep=" ", end="\n") -> None:
|
|
65
|
+
p.stdin = cast(IO[bytes], p.stdin)
|
|
66
|
+
b_args = str_to_bytes(sep).join(str_to_bytes(v) for v in args)
|
|
67
|
+
b_end = str_to_bytes(end)
|
|
68
|
+
p.stdin.write(b_args + b_end)
|
|
69
|
+
p.stdin.flush()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@validate # type:ignore
|
|
73
|
+
def acm(command: str, inputs: Optional[List[str]] = None, i_timeout: float = 0.01,
|
|
74
|
+
shell: bool = False, use_write_helper: bool = True, cwd: Optional[str] = None) \
|
|
75
|
+
-> Tuple[int, Optional[List[bytes]], Optional[List[bytes]]]:
|
|
76
|
+
"""Advanced command
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
command (str): The command to execute\n
|
|
80
|
+
inputs (list[str]): the inputs to give to the program from the command. Defaults to None.\n
|
|
81
|
+
i_timeout (float, optional): An individual timeout for every step of the execution. Defaults to 0.01.\n
|
|
82
|
+
cwd (?, optional): Current working directory. Defaults to None.\n
|
|
83
|
+
env (?, optional): Environment variables. Defaults to None.\n
|
|
84
|
+
shell (bool, optional): whether to execute the command through shell. Defaults to False.\n
|
|
85
|
+
use_write_helper (bool, optional): whether to parse each input as it
|
|
86
|
+
would have been parse with builtin print() or to use raw text. Defaults to True.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
If @timeout will raise something other than TimeoutError.\n
|
|
90
|
+
If the subprocess input and output handling will raise an exception.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
tuple[int, Optional[list[bytes]], Optional[list[bytes]]]: return code, stdout, stderr
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
if inputs is None:
|
|
97
|
+
inputs = []
|
|
98
|
+
|
|
99
|
+
p = None
|
|
100
|
+
try:
|
|
101
|
+
# with subprocess.Popen(command, stdout=subprocess.PIPE,
|
|
102
|
+
# stdin=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, shell=shell) as p:
|
|
103
|
+
# TODO with ... as p:
|
|
104
|
+
p = subprocess.Popen(command, stdout=subprocess.PIPE,
|
|
105
|
+
stdin=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd, shell=shell)
|
|
106
|
+
p.stdin = cast(IO[bytes], p.stdin)
|
|
107
|
+
p.stdout = cast(IO[bytes], p.stdout)
|
|
108
|
+
p.stderr = cast(IO[bytes], p.stderr)
|
|
109
|
+
|
|
110
|
+
@timeout(i_timeout) # type:ignore
|
|
111
|
+
def readlines(s: IO, l: list):
|
|
112
|
+
l.extend(s.readlines())
|
|
113
|
+
|
|
114
|
+
def extend_from_stream(stream: IO[bytes], list_to_extend_to: list):
|
|
115
|
+
if stream is not None and stream.readable():
|
|
116
|
+
try:
|
|
117
|
+
readlines(stream, list_to_extend_to)
|
|
118
|
+
# new_len = len(l)
|
|
119
|
+
except TimeoutError:
|
|
120
|
+
# break
|
|
121
|
+
pass
|
|
122
|
+
except BaseException as e1:
|
|
123
|
+
raise e1
|
|
124
|
+
|
|
125
|
+
stdout: List[bytes] = []
|
|
126
|
+
stderr: List[bytes] = []
|
|
127
|
+
for curr_input in inputs:
|
|
128
|
+
if p.stdin.writable():
|
|
129
|
+
if use_write_helper:
|
|
130
|
+
__acm_write(curr_input, p=p)
|
|
131
|
+
else:
|
|
132
|
+
__acm_write(curr_input, p=p, sep="", end="")
|
|
133
|
+
extend_from_stream(p.stdout, stdout)
|
|
134
|
+
extend_from_stream(p.stderr, stderr)
|
|
135
|
+
# else:
|
|
136
|
+
# extend_from_stream(p.stdout, stdout)
|
|
137
|
+
# extend_from_stream(p.stderr, stderr)
|
|
138
|
+
p.stdin.close()
|
|
139
|
+
p.stdout.close()
|
|
140
|
+
if p.stderr is not None:
|
|
141
|
+
p.stderr.close()
|
|
142
|
+
returncode = p.wait()
|
|
143
|
+
return returncode, stdout, stderr
|
|
144
|
+
except BaseException as e2:
|
|
145
|
+
raise type(e2)(f"Maybe use shell=True? original error:\n{e2.args}")
|
|
146
|
+
finally:
|
|
147
|
+
if p is not None:
|
|
148
|
+
if p.stdin is not None:
|
|
149
|
+
p.stdin.close()
|
|
150
|
+
if p.stderr is not None:
|
|
151
|
+
p.stderr.close()
|
|
152
|
+
if p.stdout is not None:
|
|
153
|
+
p.stdout.close()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cmrt(*args, shell: bool = True) -> Generator[Tuple[int, bytes], None, None]:
|
|
157
|
+
"""Executes a command and yields stdout and stderr in real-time.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
shell (bool, optional): If True, the command is executed through the shell. Defaults to True.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
TypeError: if 'shell' is not boolean
|
|
164
|
+
|
|
165
|
+
Yields:
|
|
166
|
+
Generator[tuple[int, bytes], None, None]: the tuple yielded will contain the 'stream identifier'
|
|
167
|
+
0 - stdout,
|
|
168
|
+
1 - stderr
|
|
169
|
+
and the actual value from the stream
|
|
170
|
+
"""
|
|
171
|
+
if not isinstance(shell, bool):
|
|
172
|
+
raise TypeError("The 'shell' parameter must be of type bool.")
|
|
173
|
+
|
|
174
|
+
# Quote the arguments that represent file or directory paths.
|
|
175
|
+
for i, arg in enumerate(args):
|
|
176
|
+
path_obj = Path(args[i])
|
|
177
|
+
if path_obj.is_file() or path_obj.is_dir():
|
|
178
|
+
args = (*args[:i], f"\"{arg}\"", *args[i + 1:])
|
|
179
|
+
|
|
180
|
+
# Join the arguments into a command string and execute the command.
|
|
181
|
+
cmd = " ".join(args)
|
|
182
|
+
|
|
183
|
+
with subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process:
|
|
184
|
+
combined = join_generators( # type:ignore
|
|
185
|
+
generator_from_stream(process.stdout), # type:ignore
|
|
186
|
+
generator_from_stream(process.stderr) # type:ignore
|
|
187
|
+
) # type:ignore
|
|
188
|
+
for tup in combined:
|
|
189
|
+
yield tup
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
__all__ = [
|
|
193
|
+
"cm",
|
|
194
|
+
"acm",
|
|
195
|
+
"sleep",
|
|
196
|
+
"cmrt"
|
|
197
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys, os
|
|
3
|
+
from typing import Optional, Tuple, List
|
|
4
|
+
from ..context_managers import TemporaryFile
|
|
5
|
+
import random
|
|
6
|
+
from ..logging_.utils import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LayeredCommand:
|
|
12
|
+
class_capture_stdout: bool = True
|
|
13
|
+
class_capture_stderr: bool = True
|
|
14
|
+
class_raise_on_fail: bool = False
|
|
15
|
+
class_verbose: bool = False
|
|
16
|
+
_class_prev_instance: Optional['LayeredCommand'] = None
|
|
17
|
+
_id: int = 0
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def prev(self):
|
|
21
|
+
return self._prev_instance
|
|
22
|
+
|
|
23
|
+
@prev.setter
|
|
24
|
+
def prev(self, value: Optional['LayeredCommand'] = None):
|
|
25
|
+
self._prev_instance = value
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
command: Optional[str] = None,
|
|
30
|
+
*,
|
|
31
|
+
prev_instance: Optional['LayeredCommand'] = None,
|
|
32
|
+
instance_capture_stdout: Optional[bool] = None,
|
|
33
|
+
instance_capture_stderr: Optional[bool] = None,
|
|
34
|
+
instance_raise_on_fail: Optional[bool] = None,
|
|
35
|
+
instance_verbose: Optional[bool] = None
|
|
36
|
+
):
|
|
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 LayeredCommand._class_prev_instance
|
|
43
|
+
self._cur_class_prev_instance = LayeredCommand._class_prev_instance
|
|
44
|
+
LayeredCommand._class_prev_instance = self
|
|
45
|
+
self._executor = os.system
|
|
46
|
+
self._has_entered: bool = False
|
|
47
|
+
logger.info("LayeredCommand initialized with command: '%s', prev_instance: %s", self._command, prev_instance is not None)
|
|
48
|
+
|
|
49
|
+
def __enter__(self):
|
|
50
|
+
self._open()
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
54
|
+
if exc_type is not None:
|
|
55
|
+
logger.warning("LayeredCommand context exited with exception: %s: %s", exc_type.__name__, exc_val)
|
|
56
|
+
if self.prev is self._cur_class_prev_instance:
|
|
57
|
+
LayeredCommand._class_prev_instance = self.prev
|
|
58
|
+
|
|
59
|
+
def _open(self) -> None:
|
|
60
|
+
self._has_entered = True
|
|
61
|
+
|
|
62
|
+
def _build_command(self, *commands: str) -> str:
|
|
63
|
+
res = ""
|
|
64
|
+
if self.prev is not None:
|
|
65
|
+
prev = self.prev._build_command()
|
|
66
|
+
res += f"{prev} & " if prev != "" else ""
|
|
67
|
+
if self._command != "":
|
|
68
|
+
return res + " & ".join([self._command, *commands])
|
|
69
|
+
return res + " & ".join(commands)
|
|
70
|
+
|
|
71
|
+
def _error(self, predicate: bool, command: str, code: int, command_verbose: Optional[bool]) -> None:
|
|
72
|
+
if predicate:
|
|
73
|
+
verbose = self._merge_values(command_verbose, self._instance_verbose, LayeredCommand.class_verbose)
|
|
74
|
+
if verbose:
|
|
75
|
+
raise RuntimeError(f"command '{command}' failed with exit code {code}")
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _merge_values(command: Optional[bool], instance: Optional[bool], class_: bool) -> bool:
|
|
80
|
+
return command if command is not None else (instance if instance is not None else class_)
|
|
81
|
+
|
|
82
|
+
def execute(
|
|
83
|
+
self,
|
|
84
|
+
*commands: str,
|
|
85
|
+
command_capture_stdout: Optional[bool] = None,
|
|
86
|
+
command_capture_stderr: Optional[bool] = None,
|
|
87
|
+
command_raise_on_fail: Optional[bool] = None,
|
|
88
|
+
command_verbose: Optional[bool] = None
|
|
89
|
+
) -> Tuple[int, List[str], List[str]]:
|
|
90
|
+
if not self._has_entered:
|
|
91
|
+
logger.error("LayeredCommand must be used with a context manager")
|
|
92
|
+
raise RuntimeError(
|
|
93
|
+
"LayeredCommand must be used with a context manager. Use as: `with LayeredCommand(...) as l1:`")
|
|
94
|
+
|
|
95
|
+
capture_stdout = self._merge_values(command_capture_stdout, self._instance_capture_stdout,
|
|
96
|
+
self.class_capture_stdout)
|
|
97
|
+
capture_stderr = self._merge_values(command_capture_stderr, self._instance_capture_stderr,
|
|
98
|
+
self.class_capture_stderr)
|
|
99
|
+
raise_on_fail = self._merge_values(command_raise_on_fail, self._instance_raise_on_fail,
|
|
100
|
+
self.class_raise_on_fail)
|
|
101
|
+
|
|
102
|
+
command = self._build_command(*commands)
|
|
103
|
+
logger.info("Executing command: %s", command)
|
|
104
|
+
|
|
105
|
+
if not capture_stdout and not capture_stderr:
|
|
106
|
+
code = self._executor(command)
|
|
107
|
+
self._error(raise_on_fail and code != 0, command, code, command_verbose)
|
|
108
|
+
return code, [], []
|
|
109
|
+
|
|
110
|
+
letters = "abcdefghijklmnopqrstuvwxyz"
|
|
111
|
+
prefix = "".join(random.choices(letters, k=10)) + f"_{LayeredCommand._id}"
|
|
112
|
+
LayeredCommand._id += 1
|
|
113
|
+
|
|
114
|
+
with TemporaryFile.random(prefix=prefix, suffix="stdout.log") as stdout:
|
|
115
|
+
with TemporaryFile.random(prefix=prefix, suffix="stderr.log") as stderr:
|
|
116
|
+
if capture_stdout:
|
|
117
|
+
command += f" > {stdout}"
|
|
118
|
+
if capture_stderr:
|
|
119
|
+
command += f" 2> {stderr}"
|
|
120
|
+
code = self._executor(command)
|
|
121
|
+
self._error(raise_on_fail and code != 0, command, code, command_verbose)
|
|
122
|
+
return code, stdout.readlines(), stderr.readlines()
|
|
123
|
+
|
|
124
|
+
def __call__(self, *args, **kwargs) -> Tuple[int, List[str], List[str]]:
|
|
125
|
+
return self.execute(*args, **kwargs)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = [
|
|
129
|
+
'LayeredCommand'
|
|
130
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .filetime import *
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class FileTime:
|
|
8
|
+
creation: Optional[datetime] = None
|
|
9
|
+
modification: Optional[datetime] = None
|
|
10
|
+
access: Optional[datetime] = None
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
return f"{self.__class__.__name__}(\n\tcreation={str(self.creation)},\n" \
|
|
14
|
+
f"\tmodification={str(self.modification)},\n\taceess={str(self.access)}\n)"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'FileTime'
|
|
19
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
credit to https://github.com/Delgan/win32-setctime/blob/master/win32_setctime.py
|
|
3
|
+
and modifications by me
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
import os
|
|
9
|
+
from .utils import FileTime
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from ctypes import byref, get_last_error, wintypes, WinDLL, WinError
|
|
13
|
+
|
|
14
|
+
kernel32 = WinDLL("kernel32", use_last_error=True)
|
|
15
|
+
|
|
16
|
+
CreateFileW = kernel32.CreateFileW
|
|
17
|
+
SetFileTime = kernel32.SetFileTime
|
|
18
|
+
# Modification
|
|
19
|
+
GetFileTime = kernel32.GetFileTime
|
|
20
|
+
CloseHandle = kernel32.CloseHandle
|
|
21
|
+
|
|
22
|
+
CreateFileW.argtypes = (
|
|
23
|
+
wintypes.LPWSTR,
|
|
24
|
+
wintypes.DWORD,
|
|
25
|
+
wintypes.DWORD,
|
|
26
|
+
wintypes.LPVOID,
|
|
27
|
+
wintypes.DWORD,
|
|
28
|
+
wintypes.DWORD,
|
|
29
|
+
wintypes.HANDLE,
|
|
30
|
+
)
|
|
31
|
+
CreateFileW.restype = wintypes.HANDLE
|
|
32
|
+
|
|
33
|
+
SetFileTime.argtypes = (
|
|
34
|
+
wintypes.HANDLE,
|
|
35
|
+
wintypes.PFILETIME,
|
|
36
|
+
wintypes.PFILETIME,
|
|
37
|
+
wintypes.PFILETIME,
|
|
38
|
+
)
|
|
39
|
+
SetFileTime.restype = wintypes.BOOL
|
|
40
|
+
|
|
41
|
+
# modification
|
|
42
|
+
# https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletime
|
|
43
|
+
GetFileTime.argtypes = (
|
|
44
|
+
wintypes.HANDLE,
|
|
45
|
+
wintypes.LPFILETIME,
|
|
46
|
+
wintypes.LPFILETIME,
|
|
47
|
+
wintypes.LPFILETIME,
|
|
48
|
+
)
|
|
49
|
+
GetFileTime.restype = wintypes.BOOL
|
|
50
|
+
|
|
51
|
+
CloseHandle.argtypes = (wintypes.HANDLE,)
|
|
52
|
+
CloseHandle.restype = wintypes.BOOL
|
|
53
|
+
except (ImportError, AttributeError, OSError, ValueError):
|
|
54
|
+
SUPPORTED = False
|
|
55
|
+
else:
|
|
56
|
+
SUPPORTED = os.name == "nt"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HELPERS:
|
|
60
|
+
|
|
61
|
+
class CreationDisposition(IntEnum):
|
|
62
|
+
# https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
|
63
|
+
CREATE_NEW = 1
|
|
64
|
+
CREATE_ALWAYS = 2
|
|
65
|
+
OPEN_EXISTING = 3
|
|
66
|
+
OPEN_ALWAYS = 4
|
|
67
|
+
TRUNCATE_EXISTING = 5
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def epoch_time_to_windows_time(timestamp: float) -> int:
|
|
71
|
+
# https://stackoverflow.com/questions/1566645/filetime-to-int64
|
|
72
|
+
# https://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux
|
|
73
|
+
return int(timestamp * 10000000) + 116444736000000000
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def windows_time_to_epoch(timestamp: int) -> int:
|
|
77
|
+
# https://stackoverflow.com/questions/1566645/filetime-to-int64
|
|
78
|
+
# https://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux
|
|
79
|
+
return (timestamp-116444736000000000)//10000000
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def close_handle(handle) -> None:
|
|
83
|
+
if not wintypes.BOOL(CloseHandle(handle)):
|
|
84
|
+
raise WinError(get_last_error())
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def datetime_to_wintypes_FILETIME(dt: Optional[datetime]) -> 'wintypes.FILETIME':
|
|
88
|
+
if dt is None:
|
|
89
|
+
return wintypes.FILETIME(0xFFFFFFFF, 0xFFFFFFFF)
|
|
90
|
+
|
|
91
|
+
timestamp = HELPERS.epoch_time_to_windows_time(dt.timestamp())
|
|
92
|
+
if not 0 < timestamp < (1 << 64):
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"The system value of the timestamp exceeds u64 size: %d" % timestamp)
|
|
95
|
+
|
|
96
|
+
return wintypes.FILETIME(timestamp & 0xFFFFFFFF, timestamp >> 32)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def setctime(filepath: str, filetime: FileTime, *, follow_symlinks: bool = True) -> None:
|
|
100
|
+
"""Set the "ctime" (creation time) attribute of a file given an unix timestamp (Windows only)."""
|
|
101
|
+
if not SUPPORTED:
|
|
102
|
+
raise OSError(
|
|
103
|
+
"This function is only available for the Windows platform.")
|
|
104
|
+
|
|
105
|
+
if filetime.access is None and filetime.modification is None and filetime.creation is None:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"This function has no meaning if all values in 'filetime' argument are None. This is probably a mistake")
|
|
108
|
+
|
|
109
|
+
filepath = os.path.normpath(os.path.abspath(str(filepath)))
|
|
110
|
+
|
|
111
|
+
atime: wintypes.FILETIME = HELPERS.datetime_to_wintypes_FILETIME(
|
|
112
|
+
filetime.access)
|
|
113
|
+
mtime: wintypes.FILETIME = HELPERS.datetime_to_wintypes_FILETIME(
|
|
114
|
+
filetime.modification)
|
|
115
|
+
ctime: wintypes.FILETIME = HELPERS.datetime_to_wintypes_FILETIME(
|
|
116
|
+
filetime.creation)
|
|
117
|
+
|
|
118
|
+
flags = 128 | 0x02000000
|
|
119
|
+
|
|
120
|
+
if not follow_symlinks:
|
|
121
|
+
flags |= 0x00200000
|
|
122
|
+
|
|
123
|
+
handle = wintypes.HANDLE(
|
|
124
|
+
CreateFileW(
|
|
125
|
+
filepath,
|
|
126
|
+
256,
|
|
127
|
+
0,
|
|
128
|
+
None,
|
|
129
|
+
HELPERS.CreationDisposition.OPEN_EXISTING.value,
|
|
130
|
+
flags,
|
|
131
|
+
None
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
if handle.value == wintypes.HANDLE(-1).value:
|
|
135
|
+
raise WinError(get_last_error())
|
|
136
|
+
|
|
137
|
+
if not wintypes.BOOL(SetFileTime(handle, byref(ctime), byref(atime), byref(mtime))):
|
|
138
|
+
raise WinError(get_last_error())
|
|
139
|
+
HELPERS.close_handle(handle)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def getctime(filepath: str, follow_symlinks: bool = True) -> FileTime:
|
|
143
|
+
if not SUPPORTED:
|
|
144
|
+
raise OSError(
|
|
145
|
+
"This function is only available for the Windows platform.")
|
|
146
|
+
|
|
147
|
+
filepath = os.path.normpath(os.path.abspath(str(filepath)))
|
|
148
|
+
|
|
149
|
+
# placeholder variables to hold values for
|
|
150
|
+
atime = wintypes.FILETIME() # last access time
|
|
151
|
+
mtime = wintypes.FILETIME() # last modification time
|
|
152
|
+
ctime = wintypes.FILETIME() # creation time
|
|
153
|
+
|
|
154
|
+
flags = 128 | 0x02000000
|
|
155
|
+
|
|
156
|
+
if not follow_symlinks:
|
|
157
|
+
flags |= 0x00200000
|
|
158
|
+
f = CreateFileW(filepath, 256, 0, None,
|
|
159
|
+
HELPERS.CreationDisposition.OPEN_EXISTING.value, flags, None)
|
|
160
|
+
handle = wintypes.HANDLE(f)
|
|
161
|
+
if handle.value == wintypes.HANDLE(-1).value:
|
|
162
|
+
raise WinError(get_last_error())
|
|
163
|
+
|
|
164
|
+
if not wintypes.BOOL(GetFileTime(handle, byref(ctime), byref(atime), byref(mtime))):
|
|
165
|
+
raise WinError(get_last_error())
|
|
166
|
+
|
|
167
|
+
HELPERS.close_handle(handle)
|
|
168
|
+
|
|
169
|
+
# reverse of calculation in setctime function above.
|
|
170
|
+
stage1 = map(
|
|
171
|
+
lambda time: time.dwHighDateTime << 32 | time.dwLowDateTime, # type:ignore
|
|
172
|
+
[ctime, mtime, atime]
|
|
173
|
+
)
|
|
174
|
+
stage2 = map(HELPERS.windows_time_to_epoch, stage1)
|
|
175
|
+
stage3 = map(datetime.fromtimestamp, stage2)
|
|
176
|
+
return FileTime(*list(stage3))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
__all__ = [
|
|
180
|
+
"setctime",
|
|
181
|
+
"getctime",
|
|
182
|
+
'FileTime'
|
|
183
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# from functools import partial
|
|
2
|
+
# from screeninfo import get_monitors
|
|
3
|
+
# import pyautogui
|
|
4
|
+
# from PIL import ImageGrab, Image
|
|
5
|
+
# ImageGrab.grab = partial(ImageGrab.grab, all_screens=True)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# def screenshot(xy: tuple[int, int] = (0, 0), wh: tuple[int, int] = None) -> Image:
|
|
9
|
+
# """creates a screenshot of the screen in an arbitrary location and size
|
|
10
|
+
|
|
11
|
+
# Args:
|
|
12
|
+
# xy (tuple[int, int], optional): the top left location of the screenshot. Defaults to (0, 0).
|
|
13
|
+
# wh (tuple[int, int], optional): the size of the screenshot in pixels. Defaults to None. if None will
|
|
14
|
+
# create a screen shot of all monitors
|
|
15
|
+
# Returns:
|
|
16
|
+
# Image: _description_
|
|
17
|
+
# """
|
|
18
|
+
# if wh is None:
|
|
19
|
+
# wh = (0, 0)
|
|
20
|
+
# for monitor_index in range(get_monitor_count()):
|
|
21
|
+
# size = get_monitor_size(monitor_index)
|
|
22
|
+
# wh = (wh[0]+size[0], wh[1]+size[1])
|
|
23
|
+
# return pyautogui.screenshot(None, (xy[0], xy[1], wh[0], wh[1]))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# def screenshot_monitor(index: int) -> Image:
|
|
27
|
+
# """creates a screenshot of the monitor
|
|
28
|
+
|
|
29
|
+
# Args:
|
|
30
|
+
# index (int): the monitor's index
|
|
31
|
+
|
|
32
|
+
# Returns:
|
|
33
|
+
# Image: the screenshot
|
|
34
|
+
# """
|
|
35
|
+
# return screenshot(get_monitor_location(index), get_monitor_size(index))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# def get_monitor_size(index: int) -> tuple[int, int]:
|
|
39
|
+
# """returns the size of the monitor
|
|
40
|
+
|
|
41
|
+
# Args:
|
|
42
|
+
# index (int): the monitor's index
|
|
43
|
+
|
|
44
|
+
# Returns:
|
|
45
|
+
# tuple[int, int]: the size
|
|
46
|
+
# """
|
|
47
|
+
# monitor = get_monitors()[index]
|
|
48
|
+
# return monitor.width, monitor.height
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# def get_monitor_location(index: int) -> tuple[int, int]:
|
|
52
|
+
# """returns the coordinates of the location of the monitor
|
|
53
|
+
|
|
54
|
+
# Args:
|
|
55
|
+
# index (int): the monitor's index
|
|
56
|
+
|
|
57
|
+
# Returns:
|
|
58
|
+
# tuple[int, int]: the coordinates
|
|
59
|
+
# """
|
|
60
|
+
# monitor = get_monitors()[index]
|
|
61
|
+
# return monitor.x, monitor.y
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# def get_monitor_count() -> int:
|
|
65
|
+
# """return the number of displays connected to this computer
|
|
66
|
+
|
|
67
|
+
# Returns:
|
|
68
|
+
# int: amount of displays
|
|
69
|
+
# """
|
|
70
|
+
# return len(get_monitors())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# __all__ = [
|
|
74
|
+
# "screenshot",
|
|
75
|
+
# "get_monitor_size",
|
|
76
|
+
# "get_monitor_count",
|
|
77
|
+
# "screenshot_monitor"
|
|
78
|
+
# ]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .unittest_ import *
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import unittest
|
|
4
|
+
from _collections_abc import Coroutine
|
|
5
|
+
from typing import Optional, Callable, Type
|
|
6
|
+
from unittest import TestResult
|
|
7
|
+
from ...logging_.utils import get_logger
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AlwaysTeardownTestCase(unittest.TestCase):
|
|
13
|
+
"""
|
|
14
|
+
SafeTestCase makes sure that tearDown / cleanup methods are always run when
|
|
15
|
+
They should be.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def run(self, result=None) -> Optional[TestResult]:
|
|
19
|
+
logger.debug("Setting up AlwaysTeardownTestCase for test: %s", self._testMethodName)
|
|
20
|
+
test_method = getattr(self, self._testMethodName)
|
|
21
|
+
wrapped_test = self._cleanup_wrapper(test_method, KeyboardInterrupt)
|
|
22
|
+
setattr(self, self._testMethodName, wrapped_test)
|
|
23
|
+
|
|
24
|
+
self.setUp = self._cleanup_wrapper(self.setUp, BaseException) # type: ignore
|
|
25
|
+
|
|
26
|
+
return super().run(result)
|
|
27
|
+
|
|
28
|
+
def _cleanup_wrapper(self, method: Callable, exception: Type[BaseException]) -> Callable:
|
|
29
|
+
@functools.wraps(method)
|
|
30
|
+
def wrapper(*args, **kwargs):
|
|
31
|
+
try:
|
|
32
|
+
return method(*args, **kwargs)
|
|
33
|
+
except exception:
|
|
34
|
+
logger.debug("Exception %s caught in test, running cleanup for: %s", exception.__name__, method.__name__)
|
|
35
|
+
self.tearDown()
|
|
36
|
+
self.doCleanups()
|
|
37
|
+
raise
|
|
38
|
+
|
|
39
|
+
return wrapper
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AsyncAlwaysTeardownTestCase(unittest.IsolatedAsyncioTestCase):
|
|
43
|
+
"""
|
|
44
|
+
SafeTestCase makes sure that tearDown / cleanup methods are always run when
|
|
45
|
+
They should be.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def run(self, result=None) -> Optional[TestResult]:
|
|
49
|
+
logger.debug("Setting up AsyncAlwaysTeardownTestCase for test: %s", self._testMethodName)
|
|
50
|
+
test_method = getattr(self, self._testMethodName)
|
|
51
|
+
wrapped_test = self._cleanup_wrapper(test_method, KeyboardInterrupt)
|
|
52
|
+
setattr(self, self._testMethodName, wrapped_test)
|
|
53
|
+
|
|
54
|
+
self.asyncSetUp = self._cleanup_wrapper(self.asyncSetUp, BaseException) # type: ignore
|
|
55
|
+
|
|
56
|
+
return super().run(result)
|
|
57
|
+
|
|
58
|
+
def _cleanup_wrapper(self, method: Coroutine, exception: Type[BaseException]) -> Callable:
|
|
59
|
+
@functools.wraps(method) # type: ignore
|
|
60
|
+
async def wrapper(*args, **kwargs):
|
|
61
|
+
try:
|
|
62
|
+
return await method(*args, **kwargs) # type: ignore
|
|
63
|
+
except exception:
|
|
64
|
+
logger.debug("Exception %s caught in async test, running cleanup for: %s", exception.__name__, method.__name__)
|
|
65
|
+
self.tearDown()
|
|
66
|
+
self.doCleanups()
|
|
67
|
+
raise
|
|
68
|
+
|
|
69
|
+
return wrapper
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"AlwaysTeardownTestCase",
|
|
74
|
+
"AsyncAlwaysTeardownTestCase",
|
|
75
|
+
]
|