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.
Files changed (287) hide show
  1. danielutils/__init__.py +50 -0
  2. danielutils/abstractions/__init__.py +3 -0
  3. danielutils/abstractions/db/__init__.py +10 -0
  4. danielutils/abstractions/db/database.py +206 -0
  5. danielutils/abstractions/db/database_definitions.py +219 -0
  6. danielutils/abstractions/db/database_exceptions.py +34 -0
  7. danielutils/abstractions/db/database_factory.py +70 -0
  8. danielutils/abstractions/db/database_initializer.py +261 -0
  9. danielutils/abstractions/db/database_models.py +213 -0
  10. danielutils/abstractions/db/dependencies.py +6 -0
  11. danielutils/abstractions/db/implementations/__init__.py +4 -0
  12. danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
  13. danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
  14. danielutils/abstractions/db/implementations/redis_database.py +403 -0
  15. danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
  16. danielutils/abstractions/multiprogramming/__init__.py +3 -0
  17. danielutils/abstractions/multiprogramming/multi_id.py +26 -0
  18. danielutils/abstractions/multiprogramming/worker.py +104 -0
  19. danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
  20. danielutils/abstractions/repl.py +125 -0
  21. danielutils/aliases.py +174 -0
  22. danielutils/async_/__init__.py +7 -0
  23. danielutils/async_/async_cmd.py +28 -0
  24. danielutils/async_/async_command.py +879 -0
  25. danielutils/async_/async_layered_command.py +125 -0
  26. danielutils/async_/async_retry_executor.py +86 -0
  27. danielutils/async_/async_worker_pool.py +110 -0
  28. danielutils/async_/time_strategy.py +73 -0
  29. danielutils/async_/utils.py +57 -0
  30. danielutils/better_builtins/__init__.py +3 -0
  31. danielutils/better_builtins/counter.py +48 -0
  32. danielutils/better_builtins/frange.py +266 -0
  33. danielutils/better_builtins/typed_builtins/__init__.py +4 -0
  34. danielutils/better_builtins/typed_builtins/factory.py +126 -0
  35. danielutils/better_builtins/typed_builtins/tdict.py +36 -0
  36. danielutils/better_builtins/typed_builtins/tlist.py +223 -0
  37. danielutils/better_builtins/typed_builtins/tset.py +67 -0
  38. danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
  39. danielutils/colors.py +203 -0
  40. danielutils/context_managers/__init__.py +5 -0
  41. danielutils/context_managers/attr_context.py +40 -0
  42. danielutils/context_managers/multi_context.py +34 -0
  43. danielutils/context_managers/optional_context.py +33 -0
  44. danielutils/context_managers/state_context.py +25 -0
  45. danielutils/context_managers/temporary_file.py +108 -0
  46. danielutils/convenience.py +12 -0
  47. danielutils/conversions/__init__.py +2 -0
  48. danielutils/conversions/main_conversions.py +131 -0
  49. danielutils/conversions/specialized_conversions/__init__.py +2 -0
  50. danielutils/conversions/specialized_conversions/to_hex.py +25 -0
  51. danielutils/conversions/specialized_conversions/to_int.py +19 -0
  52. danielutils/custom_types.py +18 -0
  53. danielutils/data_structures/__init__.py +8 -0
  54. danielutils/data_structures/algorithms.py +52 -0
  55. danielutils/data_structures/comparer.py +47 -0
  56. danielutils/data_structures/default_dict.py +23 -0
  57. danielutils/data_structures/functions.py +35 -0
  58. danielutils/data_structures/graph/__init__.py +4 -0
  59. danielutils/data_structures/graph/binary_node.py +71 -0
  60. danielutils/data_structures/graph/graph.py +208 -0
  61. danielutils/data_structures/graph/multinode.py +101 -0
  62. danielutils/data_structures/graph/node.py +60 -0
  63. danielutils/data_structures/heap/__init__.py +3 -0
  64. danielutils/data_structures/heap/heap.py +119 -0
  65. danielutils/data_structures/heap/max_heap.py +18 -0
  66. danielutils/data_structures/heap/min_heap.py +18 -0
  67. danielutils/data_structures/queue/__init__.py +3 -0
  68. danielutils/data_structures/queue/atomic_queue.py +15 -0
  69. danielutils/data_structures/queue/priority_queue.py +110 -0
  70. danielutils/data_structures/queue/queue.py +92 -0
  71. danielutils/data_structures/stack.py +102 -0
  72. danielutils/data_structures/trees/__init__.py +2 -0
  73. danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
  74. danielutils/data_structures/trees/binary_tree.py +63 -0
  75. danielutils/date.py +43 -0
  76. danielutils/date_time.py +23 -0
  77. danielutils/decorators/__init__.py +18 -0
  78. danielutils/decorators/atomic.py +45 -0
  79. danielutils/decorators/attach.py +61 -0
  80. danielutils/decorators/chain_decorators.py +39 -0
  81. danielutils/decorators/decorate_conditionally.py +32 -0
  82. danielutils/decorators/delay_call.py +47 -0
  83. danielutils/decorators/deprecate.py +46 -0
  84. danielutils/decorators/final.py +39 -0
  85. danielutils/decorators/limit_recursion.py +63 -0
  86. danielutils/decorators/memo.py +68 -0
  87. danielutils/decorators/normalize_decorator.py +55 -0
  88. danielutils/decorators/overload.py +204 -0
  89. danielutils/decorators/partially_implemented.py +32 -0
  90. danielutils/decorators/processify.py +74 -0
  91. danielutils/decorators/property.py +42 -0
  92. danielutils/decorators/singleton.py +59 -0
  93. danielutils/decorators/threadify.py +40 -0
  94. danielutils/decorators/timeout.py +82 -0
  95. danielutils/decorators/total_ordering.py +82 -0
  96. danielutils/decorators/validate.py +227 -0
  97. danielutils/exceptions.py +90 -0
  98. danielutils/functions/__init__.py +13 -0
  99. danielutils/functions/areoneof.py +47 -0
  100. danielutils/functions/check_foreach.py +39 -0
  101. danielutils/functions/factorial.py +24 -0
  102. danielutils/functions/flatten.py +33 -0
  103. danielutils/functions/foreach.py +20 -0
  104. danielutils/functions/isoftype.py +475 -0
  105. danielutils/functions/isoneof.py +71 -0
  106. danielutils/functions/multiloop.py +51 -0
  107. danielutils/functions/parallel_for.py +47 -0
  108. danielutils/functions/partition.py +45 -0
  109. danielutils/functions/powerset.py +31 -0
  110. danielutils/functions/subseteq.py +25 -0
  111. danielutils/functions/types_subseteq.py +51 -0
  112. danielutils/generators/__init__.py +3 -0
  113. danielutils/generators/conditional_generator.py +59 -0
  114. danielutils/generators/generator_from_stream.py +31 -0
  115. danielutils/generators/join_generators.py +109 -0
  116. danielutils/internet.py +92 -0
  117. danielutils/io_.py +451 -0
  118. danielutils/java/__init__.py +18 -0
  119. danielutils/java/interfaces/__init__.py +1 -0
  120. danielutils/java/interfaces/comparable.py +31 -0
  121. danielutils/java/java_interface.py +59 -0
  122. danielutils/logging_/__init__.py +2 -0
  123. danielutils/logging_/_impl/__init__.py +4 -0
  124. danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
  125. danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
  126. danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
  127. danielutils/logging_/_impl/log_level.py +13 -0
  128. danielutils/logging_/_impl/logger.py +109 -0
  129. danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
  130. danielutils/logging_/utils.py +199 -0
  131. danielutils/lombok/__init__.py +1 -0
  132. danielutils/lombok/builder.py +68 -0
  133. danielutils/math_/__init__.py +2 -0
  134. danielutils/math_/constants.py +103 -0
  135. danielutils/math_/functions.py +34 -0
  136. danielutils/math_/math_print.py +52 -0
  137. danielutils/math_/math_symbols.py +125 -0
  138. danielutils/metaclasses/__init__.py +5 -0
  139. danielutils/metaclasses/atomic_class_meta.py +36 -0
  140. danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
  141. danielutils/metaclasses/instance_cache_meta.py +66 -0
  142. danielutils/metaclasses/interface.py +328 -0
  143. danielutils/metaclasses/overload_meta.py +48 -0
  144. danielutils/mock_/__init__.py +1 -0
  145. danielutils/mock_/mock_module.py +27 -0
  146. danielutils/path.py +55 -0
  147. danielutils/print_.py +123 -0
  148. danielutils/progress_bar/__init__.py +3 -0
  149. danielutils/progress_bar/ascii_progress_bar.py +161 -0
  150. danielutils/progress_bar/progress_bar.py +75 -0
  151. danielutils/progress_bar/progress_bar_pool.py +72 -0
  152. danielutils/protocols/__init__.py +3 -0
  153. danielutils/protocols/dictable.py +38 -0
  154. danielutils/protocols/evaluable.py +26 -0
  155. danielutils/protocols/serializable.py +51 -0
  156. danielutils/py.typed +0 -0
  157. danielutils/random_.py +47 -0
  158. danielutils/reflection/__init__.py +6 -0
  159. danielutils/reflection/declarations.py +288 -0
  160. danielutils/reflection/file/__init__.py +1 -0
  161. danielutils/reflection/file/file_reflection.py +85 -0
  162. danielutils/reflection/function/__init__.py +1 -0
  163. danielutils/reflection/function/function_reflections.py +152 -0
  164. danielutils/reflection/info_classes/__init__.py +6 -0
  165. danielutils/reflection/info_classes/argument_info.py +278 -0
  166. danielutils/reflection/info_classes/class_info.py +204 -0
  167. danielutils/reflection/info_classes/decorator_info.py +54 -0
  168. danielutils/reflection/info_classes/file_info.py +1180 -0
  169. danielutils/reflection/info_classes/folder_info.py +592 -0
  170. danielutils/reflection/info_classes/function_info.py +551 -0
  171. danielutils/reflection/info_classes/import_info.py +207 -0
  172. danielutils/reflection/interpreter/__init__.py +9 -0
  173. danielutils/reflection/interpreter/callstack.py +57 -0
  174. danielutils/reflection/interpreter/get_traceback.py +27 -0
  175. danielutils/reflection/interpreter/interpreter.py +41 -0
  176. danielutils/reflection/interpreter/is_debugging.py +13 -0
  177. danielutils/reflection/interpreter/os_.py +33 -0
  178. danielutils/reflection/interpreter/packages.py +33 -0
  179. danielutils/reflection/interpreter/python_version.py +33 -0
  180. danielutils/reflection/interpreter/signals.py +18 -0
  181. danielutils/reflection/interpreter/tracer.py +175 -0
  182. danielutils/reflection/module/__init__.py +3 -0
  183. danielutils/reflection/module/lazy_module.py +104 -0
  184. danielutils/reflection/module/module_reflections.py +34 -0
  185. danielutils/reflection/module/package_reflection.py +161 -0
  186. danielutils/retry_executor/__init__.py +3 -0
  187. danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
  188. danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
  189. danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
  190. danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
  191. danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
  192. danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
  193. danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
  194. danielutils/retry_executor/backoff_strategy.py +28 -0
  195. danielutils/retry_executor/retry_executor.py +55 -0
  196. danielutils/snippets/__init__.py +1 -0
  197. danielutils/snippets/try_get.py +28 -0
  198. danielutils/system/__init__.py +3 -0
  199. danielutils/system/independent.py +197 -0
  200. danielutils/system/layered_command.py +130 -0
  201. danielutils/system/windows/__init__.py +2 -0
  202. danielutils/system/windows/utils/__init__.py +1 -0
  203. danielutils/system/windows/utils/filetime.py +19 -0
  204. danielutils/system/windows/win32_ctime.py +183 -0
  205. danielutils/system/windows/windows.py +78 -0
  206. danielutils/testing/__init__.py +1 -0
  207. danielutils/testing/unittest_/__init__.py +2 -0
  208. danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
  209. danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
  210. danielutils/text.py +169 -0
  211. danielutils/time.py +64 -0
  212. danielutils/tools/__init__.py +1 -0
  213. danielutils/tools/unittest_test_runner/__init__.py +9 -0
  214. danielutils/tools/unittest_test_runner/cli.py +70 -0
  215. danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
  216. danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
  217. danielutils/tools/unittest_test_runner/core/execution.py +244 -0
  218. danielutils/tools/unittest_test_runner/core/parser.py +163 -0
  219. danielutils/tools/unittest_test_runner/core/runner.py +561 -0
  220. danielutils/tools/unittest_test_runner/models.py +82 -0
  221. danielutils/university/__init__.py +7 -0
  222. danielutils/university/computability_and_complexity/__init__.py +2 -0
  223. danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
  224. danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
  225. danielutils/university/computability_and_complexity/languages/language.py +17 -0
  226. danielutils/university/computability_and_complexity/languages/sat.py +71 -0
  227. danielutils/university/computability_and_complexity/turing_machine.py +47 -0
  228. danielutils/university/databases/__init__.py +1 -0
  229. danielutils/university/databases/all.py +756 -0
  230. danielutils/university/image_proccesing/__init__.py +1 -0
  231. danielutils/university/image_proccesing/encoding/__init__.py +3 -0
  232. danielutils/university/image_proccesing/encoding/encoding.py +35 -0
  233. danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
  234. danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
  235. danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
  236. danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
  237. danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
  238. danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
  239. danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
  240. danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
  241. danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
  242. danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
  243. danielutils/university/image_proccesing/tansformations/hough.py +0 -0
  244. danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
  245. danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
  246. danielutils/university/linear_algebra/__init__.py +1 -0
  247. danielutils/university/linear_algebra/matrix.py +404 -0
  248. danielutils/university/machine_learning/__init__.py +0 -0
  249. danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
  250. danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
  251. danielutils/university/machine_learning/activation_functions/relu.py +11 -0
  252. danielutils/university/machine_learning/neuron.py +15 -0
  253. danielutils/university/oop/__init__.py +1 -0
  254. danielutils/university/oop/observer.py +31 -0
  255. danielutils/university/oop/strategy.py +9 -0
  256. danielutils/university/probability/__init__.py +7 -0
  257. danielutils/university/probability/conditional_variable/__init__.py +3 -0
  258. danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
  259. danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
  260. danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
  261. danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
  262. danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
  263. danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
  264. danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
  265. danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
  266. danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
  267. danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
  268. danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
  269. danielutils/university/probability/distributions.py +35 -0
  270. danielutils/university/probability/expressions/__init__.py +2 -0
  271. danielutils/university/probability/expressions/accumulation_expression.py +291 -0
  272. danielutils/university/probability/expressions/probability_expression.py +110 -0
  273. danielutils/university/probability/funcs/__init__.py +4 -0
  274. danielutils/university/probability/funcs/covariance.py +12 -0
  275. danielutils/university/probability/funcs/expected_value.py +41 -0
  276. danielutils/university/probability/funcs/probability_function.py +14 -0
  277. danielutils/university/probability/funcs/variance.py +17 -0
  278. danielutils/university/probability/operator.py +79 -0
  279. danielutils/university/probability/protocols.py +32 -0
  280. danielutils/university/probability/supp.py +101 -0
  281. danielutils/university/probability/transformation.py +44 -0
  282. danielutils/versioned_imports.py +31 -0
  283. danielutils-1.1.28.dist-info/METADATA +390 -0
  284. danielutils-1.1.28.dist-info/RECORD +287 -0
  285. danielutils-1.1.28.dist-info/WHEEL +5 -0
  286. danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
  287. 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,2 @@
1
+ from .utils import *
2
+ from .win32_ctime import *
@@ -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,2 @@
1
+ from .always_teardown_testcase import *
2
+ from .auto_cwd_testcase 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
+ ]