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,491 @@
1
+ import logging
2
+ from typing import Dict, Any, List, Optional
3
+
4
+ try:
5
+ from sqlalchemy import create_engine, MetaData, Table, Column, inspect, text, select, update, delete, Engine
6
+ from sqlalchemy.orm import sessionmaker, Session
7
+ from sqlalchemy.sql import or_
8
+ from sqlalchemy.types import Integer, String, DateTime, Boolean, Float, JSON, LargeBinary
9
+ except ImportError:
10
+ from ....mock_ import MockImportObject
11
+
12
+ create_engine = MockImportObject("'sqlalchemy' is not installed") # type:ignore
13
+ MetaData = MockImportObject("'sqlalchemy' is not installed") # type:ignore
14
+ Table = MockImportObject("'sqlalchemy' is not installed") # type:ignore
15
+ Column = MockImportObject("'sqlalchemy' is not installed") # type:ignore
16
+ inspect = MockImportObject("'sqlalchemy' is not installed") # type:ignore
17
+ text = MockImportObject("'sqlalchemy' is not installed") # type:ignore
18
+ select = MockImportObject("'sqlalchemy' is not installed") # type:ignore
19
+ update = MockImportObject("'sqlalchemy' is not installed") # type:ignore
20
+ delete = MockImportObject("'sqlalchemy' is not installed") # type:ignore
21
+ Engine = MockImportObject("'sqlalchemy' is not installed") # type:ignore
22
+ sessionmaker = MockImportObject("'sqlalchemy' is not installed") # type:ignore
23
+ Session = MockImportObject("'sqlalchemy' is not installed") # type:ignore
24
+ or_ = MockImportObject("'sqlalchemy' is not installed") # type:ignore
25
+ Integer = MockImportObject("'sqlalchemy' is not installed") # type:ignore
26
+ String = MockImportObject("'sqlalchemy' is not installed") # type:ignore
27
+ DateTime = MockImportObject("'sqlalchemy' is not installed") # type:ignore
28
+ Boolean = MockImportObject("'sqlalchemy' is not installed") # type:ignore
29
+ Float = MockImportObject("'sqlalchemy' is not installed") # type:ignore
30
+ JSON = MockImportObject("'sqlalchemy' is not installed") # type:ignore
31
+ LargeBinary = MockImportObject("'sqlalchemy' is not installed") # type:ignore
32
+
33
+ import os
34
+ from ..database import Database
35
+ from ..database_definitions import (
36
+ TableColumn, TableSchema, TableIndex, TableForeignKey,
37
+ SelectQuery, UpdateQuery, DeleteQuery, Operator, OrderDirection,
38
+ ColumnType
39
+ )
40
+ from ..database_exceptions import DBException, DBValidationError, DBQueryError, DBConnectionError
41
+
42
+
43
+ class SQLiteDatabase(Database):
44
+ """SQLite implementation of the Database abstract class using SQLAlchemy"""
45
+
46
+ def is_connected(self) -> bool:
47
+ return self._connected
48
+
49
+ @classmethod
50
+ def _default_class_exception_conversion(cls, e: Exception) -> Exception:
51
+ """
52
+ Convert SQLite/SQLAlchemy specific exceptions to standard database exceptions.
53
+ """
54
+ if 'sqlalchemy' in str(type(e).__module__):
55
+ if 'OperationalError' in str(type(e)):
56
+ return DBConnectionError(f"Database connection error: {str(e)}")
57
+ elif 'IntegrityError' in str(type(e)):
58
+ return DBValidationError(f"Data validation error: {str(e)}")
59
+ elif 'ProgrammingError' in str(type(e)):
60
+ return DBQueryError(f"Query error: {str(e)}")
61
+ return DBException(f"Database error: {str(e)}")
62
+
63
+ def __init__(
64
+ self,
65
+ url: Optional[str] = None,
66
+ db_name: Optional[str] = None,
67
+ **engine_kwargs
68
+ ):
69
+ """
70
+ Initialize SQLite database connection
71
+ Args:
72
+ url (str, optional): SQLAlchemy database URL. If not provided, will use db_name or environment/config.
73
+ db_name (str, optional): Database file name. Used only if url is not provided.
74
+ **engine_kwargs: Additional keyword arguments for SQLAlchemy create_engine.
75
+ """
76
+ self.engine: Optional[Engine] = None
77
+ self.session_local = None
78
+ self.metadata = MetaData()
79
+ self._connected: bool = False
80
+ # Determine the connection URL
81
+ if url:
82
+ self.url = url
83
+ elif db_name:
84
+ self.url = f"sqlite:///{db_name}"
85
+ else:
86
+ # Try environment variable, then fallback
87
+ self.url = os.environ.get(
88
+ "SQLALCHEMY_DATABASE_URI", "sqlite:///:memory:")
89
+ self.engine_kwargs = engine_kwargs
90
+
91
+ async def connect(self) -> None:
92
+ """Establish connection to SQLite database"""
93
+ try:
94
+ self.engine = create_engine(self.url, pool_pre_ping=True, **self.engine_kwargs)
95
+ self.session_local = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
96
+ self.metadata.reflect(bind=self.engine)
97
+ self._connected = True
98
+ logging.info("Connected to SQLite database at '%s'", self.url)
99
+ except Exception as e:
100
+ logging.error(
101
+ "Error connecting to SQLite database at '%s': %s", self.url, e)
102
+ raise
103
+
104
+ async def disconnect(self) -> None:
105
+ """Close the SQLite database connection"""
106
+ if self.engine:
107
+ self.engine.dispose()
108
+ logging.info("Disconnected from SQLite database")
109
+ self._connected = False
110
+
111
+ def _get_column_type(self, sqlalchemy_type: Any) -> ColumnType:
112
+ """Convert SQLAlchemy type to ColumnType enum"""
113
+ type_str = str(sqlalchemy_type).upper()
114
+
115
+ # Map SQLAlchemy types to ColumnType
116
+ type_map = {
117
+ 'INTEGER': ColumnType.INTEGER,
118
+ 'BIGINT': ColumnType.BIGINT,
119
+ 'SMALLINT': ColumnType.SMALLINT,
120
+ 'FLOAT': ColumnType.FLOAT,
121
+ 'DOUBLE': ColumnType.DOUBLE,
122
+ 'DECIMAL': ColumnType.DECIMAL,
123
+ 'BOOLEAN': ColumnType.BOOLEAN,
124
+ 'TEXT': ColumnType.TEXT,
125
+ 'VARCHAR': ColumnType.VARCHAR,
126
+ 'CHAR': ColumnType.CHAR,
127
+ 'DATE': ColumnType.DATE,
128
+ 'TIME': ColumnType.TIME,
129
+ 'DATETIME': ColumnType.DATETIME,
130
+ 'TIMESTAMP': ColumnType.TIMESTAMP,
131
+ 'BLOB': ColumnType.BLOB,
132
+ 'JSON': ColumnType.JSON,
133
+ 'UUID': ColumnType.UUID
134
+ }
135
+
136
+ # Handle special cases
137
+ if 'AUTOINCREMENT' in type_str:
138
+ return ColumnType.AUTOINCREMENT
139
+ if 'VARCHAR' in type_str:
140
+ return ColumnType.VARCHAR
141
+ if 'CHAR' in type_str:
142
+ return ColumnType.CHAR
143
+
144
+ # Try to find a match in the type map
145
+ for sql_type, column_type in type_map.items():
146
+ if sql_type in type_str:
147
+ return column_type
148
+
149
+ raise ValueError(f"Unsupported SQLAlchemy type: {type_str}")
150
+
151
+ async def get_schemas(self) -> Dict[str, TableSchema]:
152
+ """
153
+ Get the complete database schema
154
+
155
+ Returns:
156
+ Dict[str, TableSchema]: Dictionary mapping table names to their schemas
157
+ """
158
+ if not self.engine:
159
+ raise RuntimeError("Database not connected")
160
+
161
+ inspector = inspect(self.engine)
162
+ schemas = {}
163
+
164
+ for table_name in inspector.get_table_names():
165
+ # Get all indexes first to check for unique constraints
166
+ indexes = inspector.get_indexes(table_name)
167
+ unique_columns = set()
168
+ for idx in indexes:
169
+ if idx['unique'] and len(idx['column_names']) == 1:
170
+ unique_columns.add(idx['column_names'][0])
171
+
172
+ columns = []
173
+ for col in self.metadata.tables[table_name].columns:
174
+ # Convert SQLAlchemy type to ColumnType
175
+ try:
176
+ column_type = self._get_column_type(col.type)
177
+ except ValueError as e:
178
+ logging.warning(
179
+ "Could not map type for column %s in table %s: %s", col['name'], table_name, e)
180
+ continue
181
+
182
+ # Handle default values
183
+ default = col.default
184
+ if default is not None and hasattr(default, 'arg'):
185
+ default = default.arg
186
+
187
+ # Check if column is unique (either by constraint or index)
188
+ is_unique = getattr(col, "unique", False) or col.name in unique_columns
189
+
190
+ column = TableColumn(
191
+ name=col.name,
192
+ type=column_type,
193
+ nullable=col.nullable, # type: ignore
194
+ primary_key=col.primary_key,
195
+ unique=is_unique,
196
+ default=default
197
+ )
198
+ columns.append(column)
199
+
200
+ # Get indexes (excluding single-column unique indexes as they're handled above)
201
+ table_indexes = []
202
+ for idx in indexes:
203
+ if not (idx['unique'] and len(idx['column_names']) == 1):
204
+ table_indexes.append(TableIndex(
205
+ name=idx['name'], # type: ignore
206
+ columns=idx['column_names'], # type: ignore
207
+ unique=idx['unique']
208
+ ))
209
+
210
+ # Get foreign keys
211
+ foreign_keys = []
212
+ for fk in inspector.get_foreign_keys(table_name):
213
+ foreign_keys.append(TableForeignKey(
214
+ name=fk.get('name', f"fk_{table_name}_{fk['constrained_columns'][0]}"), # type: ignore
215
+ columns=fk['constrained_columns'],
216
+ reference_table=fk['referred_table'],
217
+ reference_columns=fk['referred_columns']
218
+ ))
219
+
220
+ schemas[table_name] = TableSchema(
221
+ name=table_name,
222
+ columns=columns,
223
+ indexes=table_indexes,
224
+ foreign_keys=foreign_keys
225
+ )
226
+
227
+ return schemas
228
+
229
+ def _get_sqlalchemy_type(self, column_type: ColumnType) -> Any:
230
+ """Convert ColumnType to SQLAlchemy type"""
231
+ type_map = {
232
+ ColumnType.INTEGER: Integer,
233
+ ColumnType.AUTOINCREMENT: Integer,
234
+ ColumnType.SMALLINT: Integer,
235
+ ColumnType.MEDIUMINT: Integer,
236
+ ColumnType.TINYINT: Integer,
237
+ ColumnType.BIGINT: Integer,
238
+ ColumnType.FLOAT: Float,
239
+ ColumnType.DOUBLE: Float,
240
+ ColumnType.DECIMAL: Float,
241
+ ColumnType.BOOLEAN: Boolean,
242
+ ColumnType.TEXT: String,
243
+ ColumnType.VARCHAR: String,
244
+ ColumnType.CHAR: String,
245
+ ColumnType.DATE: DateTime,
246
+ ColumnType.TIME: DateTime,
247
+ ColumnType.DATETIME: DateTime,
248
+ ColumnType.TIMESTAMP: DateTime,
249
+ ColumnType.BLOB: LargeBinary,
250
+ ColumnType.JSON: JSON,
251
+ ColumnType.UUID: String
252
+ }
253
+ return type_map.get(column_type)
254
+
255
+ async def create_table(self, schema: TableSchema) -> None:
256
+ """
257
+ Create a new table in the SQLite database
258
+
259
+ Args:
260
+ schema (TableSchema): Schema definition for the table
261
+ """
262
+ try:
263
+ columns = []
264
+ for column in schema.columns:
265
+ # Convert ColumnType to SQLAlchemy type
266
+ sql_type = self._get_sqlalchemy_type(column.type)
267
+ if sql_type is None:
268
+ raise ValueError(f"Unsupported column type: {column.type}")
269
+
270
+ # Create column with all properties
271
+ col = Column( # type: ignore
272
+ column.name,
273
+ sql_type,
274
+ nullable=column.nullable,
275
+ primary_key=column.primary_key,
276
+ unique=column.unique,
277
+ default=column.default
278
+ )
279
+ columns.append(col)
280
+
281
+ # Create table with all columns
282
+ table = Table(schema.name, self.metadata, *columns)
283
+ table.create(self.engine) # type: ignore
284
+ logging.info("Created table: '%s'", schema.name)
285
+ except Exception as e:
286
+ logging.error("Error creating table '%s': %s", schema.name, e)
287
+ raise
288
+
289
+ def _get_session(self) -> Session:
290
+ """Get a new database session"""
291
+ return self.session_local() # type: ignore
292
+
293
+ async def insert(self, table: str, data: Dict[str, Any]) -> int:
294
+ """
295
+ Insert a record into the specified table
296
+
297
+ Args:
298
+ table (str): Name of the table
299
+ data (Dict[str, Any]): Dictionary containing column names and values
300
+
301
+ Returns:
302
+ int: ID of the inserted record
303
+ """
304
+ try:
305
+ with self._get_session() as session:
306
+ table_obj = self.metadata.tables[table]
307
+ stmt = table_obj.insert().values(**data)
308
+ result = session.execute(stmt)
309
+ session.commit()
310
+ return result.inserted_primary_key[0]
311
+ except Exception as e:
312
+ logging.error("Error inserting into '%s': %s", table, e)
313
+ raise
314
+
315
+ async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
316
+ """
317
+ Get records from the database
318
+
319
+ Args:
320
+ query (SelectQuery): Query definition containing table name, conditions, ordering, etc.
321
+
322
+ Returns:
323
+ List[Dict[str, Any]]: List of dictionaries containing the selected records
324
+ """
325
+ try:
326
+ with self._get_session() as session:
327
+ table_obj = self.metadata.tables[query.table]
328
+
329
+ # Start with base select
330
+ if query.columns:
331
+ stmt = select(*[table_obj.c[col] for col in query.columns])
332
+ else:
333
+ stmt = select(table_obj)
334
+
335
+ # Apply joins
336
+ if query.joins:
337
+ for join in query.joins:
338
+ join_table = self.metadata.tables[join.table]
339
+ join_conditions = []
340
+ for condition in join.conditions:
341
+ left_col = table_obj.c[condition.column]
342
+ right_col = join_table.c[condition.value] # type: ignore
343
+ join_conditions.append(left_col == right_col)
344
+ stmt = stmt.join(join_table, *join_conditions)
345
+
346
+ # Apply where clause
347
+ if query.where:
348
+ where_conditions = []
349
+ for condition in query.where.conditions:
350
+ col = table_obj.c[condition.column]
351
+ if condition.operator == Operator.EQ:
352
+ where_conditions.append(col == condition.value)
353
+ elif condition.operator == Operator.NEQ:
354
+ where_conditions.append(col != condition.value)
355
+ elif condition.operator == Operator.GT:
356
+ where_conditions.append(col > condition.value)
357
+ elif condition.operator == Operator.GTE:
358
+ where_conditions.append(col >= condition.value)
359
+ elif condition.operator == Operator.LT:
360
+ where_conditions.append(col < condition.value)
361
+ elif condition.operator == Operator.LTE:
362
+ where_conditions.append(col <= condition.value)
363
+ elif condition.operator == Operator.LIKE:
364
+ where_conditions.append(col.like(condition.value))
365
+ elif condition.operator == Operator.ILIKE:
366
+ where_conditions.append(col.ilike(condition.value))
367
+ elif condition.operator == Operator.IN:
368
+ where_conditions.append(col.in_(condition.values))
369
+ elif condition.operator == Operator.NOT_IN:
370
+ where_conditions.append(~col.in_(condition.values))
371
+ elif condition.operator == Operator.IS_NULL:
372
+ where_conditions.append(col.is_(None))
373
+ elif condition.operator == Operator.IS_NOT_NULL:
374
+ where_conditions.append(col.is_not(None))
375
+
376
+ if query.where.operator == "AND":
377
+ stmt = stmt.where(*where_conditions)
378
+ else: # OR
379
+ stmt = stmt.where(or_(*where_conditions))
380
+
381
+ # Apply order by
382
+ if query.order_by:
383
+ for order in query.order_by:
384
+ col = table_obj.c[order.column]
385
+ if order.direction == OrderDirection.DESC:
386
+ col = col.desc()
387
+ stmt = stmt.order_by(col)
388
+
389
+ # Apply group by
390
+ if query.group_by:
391
+ stmt = stmt.group_by(*[table_obj.c[col]
392
+ for col in query.group_by])
393
+
394
+ # Apply having
395
+ if query.having:
396
+ having_conditions = []
397
+ for condition in query.having.conditions:
398
+ col = table_obj.c[condition.column]
399
+ if condition.operator == Operator.EQ:
400
+ having_conditions.append(col == condition.value)
401
+ # ... (similar to where conditions)
402
+ if query.having.operator == "AND":
403
+ stmt = stmt.having(*having_conditions)
404
+ else: # OR
405
+ stmt = stmt.having(or_(*having_conditions))
406
+
407
+ # Apply limit and offset
408
+ if query.limit is not None:
409
+ stmt = stmt.limit(query.limit)
410
+ if query.offset is not None:
411
+ stmt = stmt.offset(query.offset)
412
+
413
+ result = session.execute(stmt)
414
+ # Convert result rows to dictionaries
415
+ return [dict(zip(result.keys(), row)) for row in result]
416
+ except Exception as e:
417
+ logging.error("Error selecting from '%s': %s", query.table, e)
418
+ raise
419
+
420
+ async def update(self, query: UpdateQuery) -> int:
421
+ """
422
+ Update records in the database
423
+
424
+ Args:
425
+ query (UpdateQuery): Query definition containing table name, conditions, and data to update
426
+
427
+ Returns:
428
+ int: Number of affected rows
429
+ """
430
+ try:
431
+ with self._get_session() as session:
432
+ table_obj = self.metadata.tables[query.table]
433
+ stmt = update(table_obj).values(**query.data)
434
+
435
+ if query.where:
436
+ where_conditions = []
437
+ for condition in query.where.conditions:
438
+ col = table_obj.c[condition.column]
439
+ if condition.operator == Operator.EQ:
440
+ where_conditions.append(col == condition.value)
441
+ # ... (similar to get method where conditions)
442
+ if query.where.operator == "AND":
443
+ stmt = stmt.where(*where_conditions)
444
+ else: # OR
445
+ stmt = stmt.where(or_(*where_conditions))
446
+
447
+ result = session.execute(stmt)
448
+ session.commit()
449
+ return result.rowcount
450
+ except Exception as e:
451
+ logging.error("Error updating '%s': %s", query.table, e)
452
+ raise
453
+
454
+ async def delete(self, query: DeleteQuery) -> int:
455
+ """
456
+ Delete records from the database
457
+
458
+ Args:
459
+ query (DeleteQuery): Query definition containing table name and conditions
460
+
461
+ Returns:
462
+ int: Number of affected rows
463
+ """
464
+ try:
465
+ with self._get_session() as session:
466
+ table_obj = self.metadata.tables[query.table]
467
+ stmt = delete(table_obj)
468
+
469
+ if query.where:
470
+ where_conditions = []
471
+ for condition in query.where.conditions:
472
+ col = table_obj.c[condition.column]
473
+ if condition.operator == Operator.EQ:
474
+ where_conditions.append(col == condition.value)
475
+ # ... (similar to get method where conditions)
476
+ if query.where.operator == "AND":
477
+ stmt = stmt.where(*where_conditions)
478
+ else: # OR
479
+ stmt = stmt.where(or_(*where_conditions))
480
+
481
+ result = session.execute(stmt)
482
+ session.commit()
483
+ return result.rowcount
484
+ except Exception as e:
485
+ logging.error("Error deleting from '%s': %s", query.table, e)
486
+ raise
487
+
488
+
489
+ __all__ = [
490
+ "SQLiteDatabase"
491
+ ]
@@ -0,0 +1,3 @@
1
+ from .worker import *
2
+ from .worker_pool import *
3
+ from .multi_id import *
@@ -0,0 +1,26 @@
1
+ import os
2
+ import threading
3
+
4
+
5
+ def process_id() -> int:
6
+ """
7
+ will return the current process' id
8
+ Returns:
9
+ int
10
+ """
11
+ return os.getpid()
12
+
13
+
14
+ def thread_id() -> int:
15
+ """
16
+ will return the current thread's id
17
+ Returns:
18
+ int
19
+ """
20
+ return threading.get_ident()
21
+
22
+
23
+ __all__ = [
24
+ "process_id",
25
+ "thread_id"
26
+ ]
@@ -0,0 +1,104 @@
1
+ from threading import Thread
2
+ from abc import ABC, abstractmethod
3
+ from typing import Optional, Any, Tuple as Tuple
4
+ import logging
5
+ import danielutils # this is explicitly this way to prevent circular import
6
+ from ...reflection import get_python_version
7
+ from ...logging_.utils import get_logger
8
+
9
+ if get_python_version() >= (3, 9):
10
+ from builtins import tuple as Tuple # type:ignore
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class Worker(ABC):
16
+ """A Worker Interface
17
+ """
18
+
19
+ def __init__(self, id: int,
20
+ pool: "danielutils.abstractions.multiprogramming.worker_pool.WorkerPool") -> None: # pylint: disable=redefined-builtin #noqa
21
+ logger.debug("Initializing Worker with id=%s", id)
22
+ self.id = id
23
+ self.pool = pool
24
+ self.thread: Thread = Thread(target=self._loop)
25
+ logger.debug("Worker %s initialized successfully", id)
26
+
27
+ @abstractmethod
28
+ def _work(self, obj: Any) -> None:
29
+ """execution of a single job
30
+ """
31
+
32
+ def _loop(self) -> None:
33
+ """main loop of the worker
34
+ """
35
+ logger.debug("Worker %s main loop started", self.id)
36
+ while True:
37
+ try:
38
+ obj = self.acquire()
39
+ if obj is not None:
40
+ logger.debug("Worker %s acquired job: %s", self.id, type(obj[0]).__name__)
41
+ self.work(obj[0])
42
+ else:
43
+ logger.debug("Worker %s received None job, exiting loop", self.id)
44
+ break
45
+ except Exception as e: # pylint: disable=broad-exception-caught
46
+ logger.error("Worker %s thread encountered an error: %s: %s", self.id, type(e).__name__, e)
47
+ break
48
+ logger.debug("Worker %s main loop ended", self.id)
49
+
50
+ def run(self) -> None:
51
+ """will start self._run() as a new thread with the argument given in __init__
52
+ """
53
+ logger.debug("Starting worker %s thread", self.id)
54
+ self.thread.start()
55
+ logger.debug("Worker %s thread started successfully", self.id)
56
+
57
+ def is_alive(self) -> bool:
58
+ """returns whether the worker is alive or not
59
+ """
60
+ is_alive = self.thread.is_alive()
61
+ logger.debug("Worker %s is_alive: %s", self.id, is_alive)
62
+ return is_alive
63
+
64
+ def work(self, obj: Any) -> None:
65
+ """performed the actual work that needs to happen
66
+ execution of a single job
67
+ """
68
+ logger.debug("Worker %s starting work on job: %s", self.id, type(obj).__name__)
69
+ try:
70
+ self._work(obj)
71
+ logger.debug("Worker %s completed work on job: %s", self.id, type(obj).__name__)
72
+ except Exception as e:
73
+ logger.error("Worker %s failed to process job %s: %s: %s", self.id, type(obj).__name__, type(e).__name__, e)
74
+ raise
75
+ finally:
76
+ self._notify()
77
+
78
+ def _notify(self) -> None:
79
+ """utility method to be called on the end of each iteration of work
80
+ to signal actions if needed
81
+ will call 'notification_function'
82
+ """
83
+ logger.debug("Worker %s notifying pool of job completion", self.id)
84
+ # TODO
85
+ self.pool._notify_subscribers() # type:ignore # pylint: disable=protected-access
86
+
87
+ def acquire(self) -> Optional[Tuple[Any]]:
88
+ """acquire a new job object to work on from the pool
89
+ will return a tuple of only one object (the job) or None if there are no more jobs
90
+ Returns:
91
+ Optional[tuple[Any]]: tuple of job object or None
92
+ """
93
+ logger.debug("Worker %s attempting to acquire job from pool", self.id)
94
+ job = self.pool._acquire() # pylint: disable=protected-access
95
+ if job is not None:
96
+ logger.debug("Worker %s acquired job: %s", self.id, type(job[0]).__name__)
97
+ else:
98
+ logger.debug("Worker %s no jobs available in pool", self.id)
99
+ return job
100
+
101
+
102
+ __all__ = [
103
+ "Worker"
104
+ ]