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,261 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from typing import Dict, Optional, Type, Any, Sequence
4
+ from ...logging_.utils import get_logger
5
+
6
+ try:
7
+ from sqlalchemy import inspect, Column
8
+ from sqlalchemy.orm import DeclarativeBase
9
+ except ImportError:
10
+ from ...mock_ import MockImportObject
11
+
12
+ inspect = MockImportObject("'sqlalchemy' is not installed") # type:ignore
13
+ Column = MockImportObject("'sqlalchemy' is not installed") # type:ignore
14
+ DeclarativeBase = type("DeclarativeBase", (object,), {}) # type:ignore
15
+
16
+ from .database import Database
17
+ from .database_definitions import ColumnType, TableSchema, TableColumn as ColumnSchema, TableIndex as IndexSchema
18
+ logger = get_logger(__name__)
19
+
20
+ TYPE_MAPPING = {
21
+ 'INTEGER': ColumnType.INTEGER,
22
+ 'BIGINT': ColumnType.INTEGER,
23
+ 'FLOAT': ColumnType.FLOAT,
24
+ 'NUMERIC': ColumnType.FLOAT,
25
+ 'DECIMAL': ColumnType.FLOAT,
26
+ 'BOOLEAN': ColumnType.BOOLEAN,
27
+ 'TEXT': ColumnType.TEXT,
28
+ 'VARCHAR': ColumnType.TEXT,
29
+ 'CHAR': ColumnType.TEXT,
30
+ 'UUID': ColumnType.TEXT,
31
+ 'DATETIME': ColumnType.DATETIME,
32
+ 'DATE': ColumnType.DATE,
33
+ 'TIME': ColumnType.TIME,
34
+ 'JSON': ColumnType.JSON,
35
+ 'BLOB': ColumnType.BLOB,
36
+ }
37
+
38
+
39
+ async def validate_schema(db: Database, table_name: str, expected_schema: TableSchema) -> bool:
40
+ """
41
+ Validate that the existing table schema matches the expected schema.
42
+
43
+ Args:
44
+ db: Database instance
45
+ table_name: Name of the table to validate
46
+ expected_schema: Expected table schema
47
+
48
+ Returns:
49
+ bool: True if schema matches, False otherwise
50
+ """
51
+ try:
52
+ # Get existing schema
53
+ existing_schemas = await db.get_schemas()
54
+ if table_name not in existing_schemas:
55
+ logger.warning("Table '%s' does not exist", table_name)
56
+ return False
57
+
58
+ existing_schema = existing_schemas[table_name]
59
+
60
+ # Compare columns
61
+ if len(existing_schema.columns) != len(expected_schema.columns):
62
+ logger.warning(
63
+ "Column count mismatch in '%s': "
64
+ "expected %d, "
65
+ "got %d",
66
+ table_name,
67
+ len(expected_schema.columns),
68
+ len(existing_schema.columns)
69
+ )
70
+ return False
71
+
72
+ # Compare each column
73
+ for expected_col, existing_col in zip(sorted(expected_schema.columns, key=lambda c: c.name),
74
+ sorted(existing_schema.columns, key=lambda c: c.name)):
75
+ if (
76
+ expected_col.name != existing_col.name or
77
+ expected_col.type != existing_col.type or
78
+ expected_col.primary_key != existing_col.primary_key or
79
+ expected_col.nullable != existing_col.nullable or
80
+ expected_col.unique != existing_col.unique or
81
+ expected_col.foreign_key != existing_col.foreign_key
82
+ ):
83
+ logger.warning("Column mismatch in '%s': expected %s, got %s", table_name, expected_col, existing_col)
84
+ return False
85
+
86
+ # Compare indexes
87
+ if len(existing_schema.indexes) != len(expected_schema.indexes):
88
+ logger.warning(
89
+ "Index count mismatch in '%s': "
90
+ "expected %d, "
91
+ "got %d",
92
+ table_name,
93
+ len(expected_schema.indexes),
94
+ len(existing_schema.indexes)
95
+ )
96
+ return False
97
+
98
+ # Compare each index
99
+ for expected_idx, existing_idx in zip(sorted(expected_schema.indexes, key=lambda idx: idx.name),
100
+ sorted(existing_schema.indexes, key=lambda idx: idx.name)):
101
+ if (
102
+ expected_idx.name != existing_idx.name or
103
+ expected_idx.columns != existing_idx.columns or
104
+ expected_idx.unique != existing_idx.unique
105
+ ):
106
+ logger.warning("Index mismatch in '%s': expected %s, got %s", table_name, expected_idx, existing_idx)
107
+ return False
108
+
109
+ return True
110
+
111
+ except Exception as e:
112
+ logger.error("Error validating schema for '%s': %s", table_name, str(e))
113
+ return False
114
+
115
+
116
+ def get_column_type(column: Column) -> ColumnType:
117
+ """Convert SQLAlchemy column type to our ColumnType enum"""
118
+ # Ensure autoincrement for integer primary key named 'id'
119
+ if (
120
+ column.name == 'id'
121
+ and getattr(column, 'primary_key', False)
122
+ and ('INTEGER' in str(column.type).upper() or 'BIGINT' in str(column.type).upper())
123
+ ):
124
+ return ColumnType.AUTOINCREMENT
125
+ # Check for autoincrement attribute
126
+ if getattr(column, 'autoincrement', False) == True:
127
+ return ColumnType.AUTOINCREMENT
128
+ # Then check the column type
129
+ type_name = str(column.type).upper()
130
+ for sql_type, our_type in TYPE_MAPPING.items():
131
+ if sql_type in type_name:
132
+ return our_type
133
+ return ColumnType.TEXT # Default to TEXT if type not found
134
+
135
+
136
+ def get_foreign_key(column: Column) -> Optional[Dict[str, str]]:
137
+ """Extract foreign key information from a column"""
138
+ for fk in column.foreign_keys:
139
+ return {
140
+ "table": fk.column.table.name,
141
+ "column": fk.column.name
142
+ }
143
+ return None
144
+
145
+
146
+ def get_default_value(column: Column) -> Optional[Any]:
147
+ """Extract default value from a column"""
148
+ if column.default is not None:
149
+ if hasattr(column.default, 'arg'):
150
+ return column.default.arg
151
+ return column.default
152
+ return None
153
+
154
+
155
+ def model_to_schema(model_class: Type[DeclarativeBase]) -> TableSchema:
156
+ """Convert a SQLAlchemy model to our TableSchema"""
157
+ mapper = inspect(model_class)
158
+ table_name = mapper.local_table.name # type: ignore
159
+
160
+ # Convert columns
161
+ columns = []
162
+ for column in mapper.columns:
163
+ # Get unique constraint from column
164
+ is_unique = False
165
+ if column.unique:
166
+ is_unique = True
167
+ elif column.index and column.index.unique: # type: ignore
168
+ is_unique = True
169
+
170
+ col_schema = ColumnSchema(
171
+ name=column.name,
172
+ type=get_column_type(column),
173
+ primary_key=column.primary_key,
174
+ nullable=column.nullable, # type: ignore
175
+ unique=is_unique,
176
+ foreign_key=get_foreign_key(column),
177
+ default=get_default_value(column)
178
+ )
179
+ columns.append(col_schema)
180
+
181
+ # Convert indexes
182
+ indexes = []
183
+ for index in mapper.local_table.indexes: # type: ignore
184
+ # Skip indexes that are already handled by unique constraints
185
+ if len(index.columns) == 1 and index.columns[0].unique:
186
+ continue
187
+
188
+ idx_schema = IndexSchema(
189
+ name=index.name,
190
+ columns=[col.name for col in index.columns],
191
+ unique=index.unique
192
+ )
193
+ indexes.append(idx_schema)
194
+
195
+ return TableSchema(
196
+ name=table_name,
197
+ columns=columns,
198
+ indexes=indexes
199
+ )
200
+
201
+
202
+ class DatabaseInitializer(ABC):
203
+ # Map SQLAlchemy types to our ColumnType enum
204
+
205
+ @classmethod
206
+ @abstractmethod
207
+ def _get_models(cls) -> Sequence[Type[DeclarativeBase]]:
208
+ """Get all pydantic models from our models"""
209
+
210
+ @classmethod
211
+ def _get_table_schemas(cls) -> Dict[str, TableSchema]:
212
+ """Get all table schemas from our models"""
213
+ models = cls._get_models()
214
+ res = {}
215
+ for model in models:
216
+ try:
217
+ res[model.__tablename__] = model_to_schema(model)
218
+ except Exception as e:
219
+ raise Exception(f"Failed parsing '{model.__tablename__}'") from e
220
+ return res
221
+
222
+ @classmethod
223
+ async def init_db(cls, db: Database) -> None:
224
+ """
225
+ Initialize the database by creating or validating all required tables.
226
+
227
+ Args:
228
+ db: Database instance to initialize
229
+ """
230
+ try:
231
+ await db.connect()
232
+
233
+ # Get existing schemas
234
+ existing_schemas = await db.get_schemas()
235
+
236
+ # Get table schemas from models
237
+ table_schemas = cls._get_table_schemas()
238
+
239
+ # Create or validate each table
240
+ for table_name, expected_schema in table_schemas.items():
241
+ if table_name not in existing_schemas:
242
+ logger.info("Creating table '%s'", table_name)
243
+ await db.create_table(expected_schema)
244
+ else:
245
+ logger.info("Validating table '%s'", table_name)
246
+ if not await validate_schema(db, table_name, expected_schema):
247
+ raise ValueError(
248
+ f"Schema validation failed for table '{table_name}'. "
249
+ "Please check the logs for details."
250
+ )
251
+
252
+ logger.info("Database initialization completed successfully")
253
+
254
+ except Exception as e:
255
+ logger.error("Error initializing database: %s", str(e))
256
+ raise
257
+
258
+
259
+ __all__ = [
260
+ "DatabaseInitializer"
261
+ ]
@@ -0,0 +1,213 @@
1
+ from typing import Dict, Any, List, Optional, Literal, Union
2
+ from enum import Enum
3
+ from pydantic import BaseModel, Field, ConfigDict
4
+ import json
5
+
6
+
7
+ class ColumnType(str, Enum):
8
+ """SQL column types"""
9
+ INTEGER = "INTEGER"
10
+ AUTOINCREMENT = "AUTOINCREMENT"
11
+ SMALLINT = "SMALLINT"
12
+ MEDIUMINT = "MEDIUMINT"
13
+ TINYINT = "TINYINT"
14
+ BIGINT = "BIGINT"
15
+ FLOAT = "FLOAT"
16
+ DOUBLE = "DOUBLE"
17
+ DECIMAL = "DECIMAL"
18
+ BOOLEAN = "BOOLEAN"
19
+ TEXT = "TEXT"
20
+ VARCHAR = "VARCHAR"
21
+ CHAR = "CHAR"
22
+ DATE = "DATE"
23
+ TIME = "TIME"
24
+ DATETIME = "DATETIME"
25
+ TIMESTAMP = "TIMESTAMP"
26
+ BLOB = "BLOB"
27
+ JSON = "JSON"
28
+ UUID = "UUID"
29
+
30
+
31
+ class BaseDBModel(BaseModel):
32
+ """Base model with JSON serialization support"""
33
+ model_config = ConfigDict(
34
+ arbitrary_types_allowed=True
35
+ )
36
+
37
+ def to_dict(self) -> Dict[str, Any]:
38
+ """Convert model to dictionary"""
39
+ return self.model_dump(mode="json")
40
+
41
+ def to_json(self) -> str:
42
+ """Convert model to JSON string"""
43
+ return self.model_dump_json()
44
+
45
+ @classmethod
46
+ def from_json(cls, json_str: str) -> 'BaseDBModel':
47
+ """Create model instance from JSON string"""
48
+ data = json.loads(json_str)
49
+ return cls.model_validate(data)
50
+
51
+
52
+ class TableColumn(BaseDBModel):
53
+ """Represents a database column definition"""
54
+ name: str
55
+ type: ColumnType
56
+ nullable: bool = True
57
+ primary_key: bool = False
58
+ unique: bool = False
59
+ default: Any = None
60
+ foreign_key: Optional[Dict[str, str]] = None
61
+
62
+
63
+ class TableIndex(BaseDBModel):
64
+ """Represents a database index definition"""
65
+ name: str
66
+ columns: List[str]
67
+ unique: bool = False
68
+
69
+
70
+ class TableForeignKey(BaseDBModel):
71
+ """Represents a foreign key constraint"""
72
+ name: str
73
+ columns: List[str]
74
+ reference_table: str
75
+ reference_columns: List[str]
76
+ on_delete: Optional[str] = None
77
+ on_update: Optional[str] = None
78
+
79
+
80
+ class TableSchema(BaseDBModel):
81
+ """Represents a complete table schema"""
82
+ name: str
83
+ columns: List[TableColumn]
84
+ indexes: List[TableIndex] = Field(default_factory=list)
85
+ foreign_keys: List[TableForeignKey] = Field(default_factory=list)
86
+
87
+
88
+ class Operator(str, Enum):
89
+ """SQL comparison operators"""
90
+ EQ = "="
91
+ NEQ = "!="
92
+ GT = ">"
93
+ GTE = ">="
94
+ LT = "<"
95
+ LTE = "<="
96
+ LIKE = "LIKE" # Not supported in InMemoryDatabase
97
+ ILIKE = "ILIKE" # Not supported in InMemoryDatabase
98
+ CONTAINS = "CONTAINS" # Case-insensitive contains
99
+ CONTAINS_CS = "CONTAINS_CS" # Case-sensitive contains
100
+ IN = "IN"
101
+ NOT_IN = "NOT IN"
102
+ IS_NULL = "IS NULL"
103
+ IS_NOT_NULL = "IS NOT NULL"
104
+ AND = "AND"
105
+ OR = "OR"
106
+
107
+
108
+ class JoinType(str, Enum):
109
+ """SQL join types"""
110
+ INNER = "INNER JOIN"
111
+ LEFT = "LEFT JOIN"
112
+ RIGHT = "RIGHT JOIN"
113
+ FULL = "FULL JOIN"
114
+
115
+
116
+ class OrderDirection(str, Enum):
117
+ """SQL order directions"""
118
+ ASC = "ASC"
119
+ DESC = "DESC"
120
+
121
+
122
+ class Condition(BaseDBModel):
123
+ """A single SQL condition"""
124
+ column: str
125
+ operator: Operator
126
+ value: Optional[Any] = None
127
+ values: Optional[List[Any]] = None # For IN/NOT IN operators
128
+
129
+
130
+ class Join(BaseDBModel):
131
+ """SQL join definition"""
132
+ type: JoinType
133
+ table: str
134
+ conditions: List[Condition]
135
+
136
+
137
+ class OrderBy(BaseDBModel):
138
+ """SQL ORDER BY clause"""
139
+ column: str
140
+ direction: OrderDirection = OrderDirection.ASC
141
+
142
+
143
+ class WhereClause(BaseDBModel):
144
+ """SQL WHERE clause with conditions"""
145
+ conditions: List[Condition]
146
+ operator: Literal["AND", "OR"] = "AND"
147
+
148
+
149
+ class SelectQuery(BaseDBModel):
150
+ """Complete SELECT query definition"""
151
+ table: str
152
+ columns: Optional[List[str]] = None
153
+ where: Optional[WhereClause] = None
154
+ joins: Optional[List[Join]] = None
155
+ order_by: Optional[List[OrderBy]] = None
156
+ group_by: Optional[List[str]] = None
157
+ having: Optional[WhereClause] = None
158
+ limit: Optional[int] = None
159
+ offset: Optional[int] = None
160
+
161
+
162
+ class UpdateQuery(BaseDBModel):
163
+ """Complete UPDATE query definition"""
164
+ table: str
165
+ data: Dict[str, Any]
166
+ where: Optional[WhereClause] = None
167
+
168
+
169
+ class DeleteQuery(BaseDBModel):
170
+ """Complete DELETE query definition"""
171
+ table: str
172
+ where: Optional[WhereClause] = None
173
+
174
+
175
+ # Helper functions for JSON serialization
176
+ def serialize_to_json(obj: Union[BaseDBModel, List[BaseDBModel], Dict[str, Any]]) -> str:
177
+ """Serialize any supported object to JSON string"""
178
+ if isinstance(obj, BaseDBModel):
179
+ return obj.to_json()
180
+ elif isinstance(obj, list):
181
+ return json.dumps([item.to_dict() if isinstance(item, BaseDBModel) else item for item in obj])
182
+ elif isinstance(obj, dict):
183
+ return json.dumps({k: v.to_dict() if isinstance(v, BaseDBModel) else v for k, v in obj.items()})
184
+ return json.dumps(obj)
185
+
186
+
187
+ def deserialize_from_json(json_str: str, model_class: type[BaseDBModel]) -> Union[BaseDBModel, List[BaseDBModel]]:
188
+ """Deserialize JSON string to model instance(s)"""
189
+ data = json.loads(json_str)
190
+ if isinstance(data, list):
191
+ return [model_class.model_validate(item) for item in data]
192
+ return model_class.model_validate(data)
193
+
194
+
195
+ __all__ = [
196
+ "ColumnType",
197
+ "TableColumn",
198
+ "TableIndex",
199
+ "TableForeignKey",
200
+ "TableSchema",
201
+ "Operator",
202
+ "JoinType",
203
+ "OrderDirection",
204
+ "Condition",
205
+ "Join",
206
+ "OrderBy",
207
+ "WhereClause",
208
+ "SelectQuery",
209
+ "UpdateQuery",
210
+ "DeleteQuery",
211
+ "serialize_to_json",
212
+ "deserialize_from_json"
213
+ ]
@@ -0,0 +1,6 @@
1
+ from .database import Database
2
+ from .database_factory import DatabaseFactory
3
+
4
+
5
+ def get_db() -> Database:
6
+ return DatabaseFactory.get_database_from_settings()
@@ -0,0 +1,4 @@
1
+ from .in_memory_database import *
2
+ from .persistent_in_memory_database import *
3
+ from .redis_database import *
4
+ from .sqlite_database import *