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,403 @@
1
+ import json
2
+ import logging
3
+ from typing import List, Dict, Any, Optional, Type, Union, Sequence
4
+ from datetime import datetime
5
+ from ....logging_.utils import get_logger
6
+
7
+ try:
8
+ import redis.asyncio as redis
9
+ except ImportError:
10
+ from ....mock_ import MockImportObject
11
+
12
+ redis = MockImportObject("`redis` is not installed") # type:ignore
13
+
14
+ from ..database import Database
15
+ from ..database_definitions import (
16
+ DeleteQuery, UpdateQuery, SelectQuery, TableSchema,
17
+ Operator, WhereClause, Condition, ColumnType, TableColumn
18
+ )
19
+ from ..database_exceptions import DBException, DBValidationError, DBQueryError, DBConnectionError
20
+
21
+ class RedisDatabase(Database):
22
+ """Redis implementation of the Database abstract class"""
23
+
24
+ @classmethod
25
+ def _default_class_exception_conversion(cls, e: Exception) -> Exception:
26
+ """
27
+ Convert Redis-specific exceptions to standard database exceptions.
28
+ """
29
+ if isinstance(e, redis.ConnectionError):
30
+ return DBConnectionError(f"Redis connection error: {str(e)}")
31
+ elif isinstance(e, redis.RedisError):
32
+ return DBException(f"Redis error: {str(e)}")
33
+ elif isinstance(e, ValueError):
34
+ return DBValidationError(f"Validation error: {str(e)}")
35
+ elif isinstance(e, RuntimeError):
36
+ return DBQueryError(f"Query error: {str(e)}")
37
+ return DBException(f"Database error: {str(e)}")
38
+
39
+ def __init__(self, host='localhost', port=6379, db=0, password=None, decode_responses=True) -> None:
40
+ """
41
+ Initialize Redis database connection
42
+
43
+ Args:
44
+ host (str): Redis host
45
+ port (int): Redis port
46
+ db (int): Redis database number
47
+ password (str, optional): Redis password
48
+ decode_responses (bool): Whether to decode responses to strings
49
+ """
50
+ self.host = host
51
+ self.port = port
52
+ self.db = db
53
+ self.password = password
54
+ self.decode_responses = decode_responses
55
+ self._db: redis.Redis = None # type:ignore
56
+ self._connected = False
57
+ self.logger = get_logger(__name__)
58
+
59
+ # Redis key prefixes
60
+ self.SCHEMA_PREFIX = "schema:"
61
+ self.TABLE_PREFIX = "table:"
62
+ self.COUNTER_PREFIX = "counter:"
63
+
64
+ def is_connected(self) -> bool:
65
+ return self._connected
66
+
67
+ async def connect(self) -> None:
68
+ """Establish connection to Redis database"""
69
+ try:
70
+ self._db = redis.Redis(
71
+ host=self.host,
72
+ port=self.port,
73
+ db=self.db,
74
+ password=self.password,
75
+ decode_responses=self.decode_responses
76
+ )
77
+ # Test connection
78
+ await self._db.ping()
79
+ self._connected = True
80
+ self.logger.info("Connected to Redis database at %s:%s", self.host, self.port)
81
+ except Exception as e:
82
+ self.logger.error("Error connecting to Redis database: %s", e)
83
+ raise
84
+
85
+ async def disconnect(self) -> None:
86
+ """Close the Redis database connection"""
87
+ if self._db:
88
+ await self._db.aclose()
89
+ self._connected = False
90
+ self.logger.info("Disconnected from Redis database")
91
+
92
+ def _assert_connection(self) -> None:
93
+ """Check if connected to database"""
94
+ if not self._connected or not self._db:
95
+ raise DBConnectionError("Not connected to database")
96
+
97
+ async def get_schemas(self) -> Dict[str, TableSchema]:
98
+ """Get all table schemas"""
99
+ self._assert_connection()
100
+
101
+ schemas = {}
102
+ # Get all schema keys
103
+ schema_keys = await self._db.keys(f"{self.SCHEMA_PREFIX}*")
104
+
105
+ for key in schema_keys:
106
+ table_name = key[len(self.SCHEMA_PREFIX):]
107
+ schema_json = await self._db.get(key)
108
+ if schema_json:
109
+ schema_dict = json.loads(schema_json)
110
+ schemas[table_name] = TableSchema.model_validate(schema_dict)
111
+
112
+ return schemas
113
+
114
+ async def create_table(self, schema: TableSchema) -> None:
115
+ """Create a new table with the given schema"""
116
+ self._assert_connection()
117
+
118
+ schema_key = f"{self.SCHEMA_PREFIX}{schema.name}"
119
+ if await self._db.exists(schema_key):
120
+ raise ValueError(f"Table '{schema.name}' already exists")
121
+
122
+ # Store schema as JSON
123
+ schema_json = schema.to_json()
124
+ await self._db.set(schema_key, schema_json)
125
+
126
+ # Initialize auto-increment counters
127
+ for column in schema.columns:
128
+ if column.type == ColumnType.AUTOINCREMENT:
129
+ counter_key = f"{self.COUNTER_PREFIX}{schema.name}:{column.name}"
130
+ await self._db.set(counter_key, 0)
131
+
132
+ self.logger.info("Created table '%s'", schema.name)
133
+
134
+ async def _get_next_auto_increment_id(self, table: str, column: str) -> int:
135
+ """Get the next available ID for an auto-increment column"""
136
+ counter_key = f"{self.COUNTER_PREFIX}{table}:{column}"
137
+ return await self._db.incr(counter_key)
138
+
139
+ def _validate_column_type(self, column: TableColumn, value: Any) -> bool:
140
+ """Validate a value against a column's type"""
141
+ if value is None:
142
+ return column.nullable
143
+
144
+ type_map: Dict[str, Union[Type, Sequence[Type]]] = {
145
+ "INTEGER": int,
146
+ "BIGINT": int,
147
+ "FLOAT": float,
148
+ "DOUBLE": float,
149
+ "DECIMAL": float,
150
+ "BOOLEAN": bool,
151
+ "TEXT": str,
152
+ "VARCHAR": str,
153
+ "CHAR": str,
154
+ "DATE": (str, datetime),
155
+ "TIME": (str, datetime),
156
+ "DATETIME": (str, datetime),
157
+ "TIMESTAMP": (str, datetime),
158
+ "BLOB": bytes,
159
+ "JSON": (dict, list),
160
+ "UUID": str,
161
+ "AUTOINCREMENT": int
162
+ }
163
+
164
+ expected_type = type_map.get(column.type.value, None)
165
+ if not expected_type:
166
+ return False
167
+
168
+ if isinstance(expected_type, tuple):
169
+ return isinstance(value, expected_type)
170
+ return isinstance(value, expected_type) # type: ignore
171
+
172
+ async def insert(self, table: str, data: Dict[str, Any]) -> Any:
173
+ """Insert a new record into the specified table"""
174
+ self._assert_connection()
175
+
176
+ # Get schema
177
+ schema_key = f"{self.SCHEMA_PREFIX}{table}"
178
+ schema_json = await self._db.get(schema_key)
179
+ if not schema_json:
180
+ raise ValueError(f"Table '{table}' does not exist")
181
+
182
+ schema = TableSchema.model_validate(json.loads(schema_json))
183
+ row_data = data.copy()
184
+
185
+ # Handle auto-increment columns
186
+ for column in schema.columns:
187
+ if column.type == ColumnType.AUTOINCREMENT:
188
+ if column.name in data and data[column.name] is not None:
189
+ raise DBValidationError(f"Cannot specify value for auto-increment column '{column.name}'")
190
+ row_data[column.name] = await self._get_next_auto_increment_id(table, column.name)
191
+
192
+ # Validate data against schema
193
+ for column in schema.columns:
194
+ if column.name in row_data:
195
+ if not self._validate_column_type(column, row_data[column.name]):
196
+ raise DBValidationError(f"Invalid type for column '{column.name}'")
197
+ elif not column.nullable and column.default is None:
198
+ raise DBValidationError(f"Required column '{column.name}' has no value")
199
+
200
+ # Generate unique ID for the row
201
+ row_id = str(await self._get_next_auto_increment_id(table, "_id"))
202
+
203
+ # Store row data as hash
204
+ table_key = f"{self.TABLE_PREFIX}{table}"
205
+ row_key = f"{row_id}"
206
+
207
+ # Convert data to strings for Redis hash
208
+ hash_data = {}
209
+ for key, value in row_data.items():
210
+ if isinstance(value, (dict, list)):
211
+ hash_data[key] = json.dumps(value)
212
+ else:
213
+ hash_data[key] = str(value)
214
+
215
+ await self._db.hset(table_key, row_key, json.dumps(hash_data)) # type: ignore
216
+
217
+ self.logger.info("Inserted row %s into table '%s'", row_id, table)
218
+ return row_id
219
+
220
+ def _evaluate_condition(self, row: Dict[str, Any], condition: Condition) -> bool:
221
+ """Evaluate a single condition against a row"""
222
+ if condition.column not in row:
223
+ return False
224
+
225
+ value = row[condition.column]
226
+ if condition.operator == Operator.EQ:
227
+ return value == condition.value
228
+ elif condition.operator == Operator.NEQ:
229
+ return value != condition.value
230
+ elif condition.operator == Operator.GT:
231
+ return value > condition.value
232
+ elif condition.operator == Operator.GTE:
233
+ return value >= condition.value
234
+ elif condition.operator == Operator.LT:
235
+ return value < condition.value
236
+ elif condition.operator == Operator.LTE:
237
+ return value <= condition.value
238
+ elif condition.operator == Operator.CONTAINS:
239
+ return str(condition.value).lower() in str(value).lower()
240
+ elif condition.operator == Operator.CONTAINS_CS:
241
+ return str(condition.value) in str(value)
242
+ elif condition.operator == Operator.IN:
243
+ return value in condition.values # type: ignore
244
+ elif condition.operator == Operator.NOT_IN:
245
+ return value not in condition.values # type: ignore
246
+ elif condition.operator == Operator.IS_NULL:
247
+ return value is None
248
+ elif condition.operator == Operator.IS_NOT_NULL:
249
+ return value is not None
250
+ return False
251
+
252
+ def _evaluate_where_clause(self, row: Dict[str, Any], where_clause: WhereClause) -> bool:
253
+ """Evaluate a where clause against a row"""
254
+ results = [self._evaluate_condition(row, condition) for condition in where_clause.conditions]
255
+ if where_clause.operator == "AND":
256
+ return all(results)
257
+ return any(results)
258
+
259
+ async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
260
+ """Get records from the database"""
261
+ self._assert_connection()
262
+
263
+ # Get schema
264
+ schema_key = f"{self.SCHEMA_PREFIX}{query.table}"
265
+ schema_json = await self._db.get(schema_key)
266
+ if not schema_json:
267
+ raise ValueError(f"Table '{query.table}' does not exist")
268
+
269
+ schema = TableSchema.model_validate(json.loads(schema_json))
270
+
271
+ # Get all rows from the table
272
+ table_key = f"{self.TABLE_PREFIX}{query.table}"
273
+ all_rows = await self._db.hgetall(table_key) # type: ignore
274
+
275
+ rows = []
276
+ for row_id, row_json in all_rows.items():
277
+ row_data = json.loads(row_json)
278
+
279
+ # Convert string values back to appropriate types
280
+ processed_row = {}
281
+ for col in schema.columns:
282
+ if col.name in row_data:
283
+ value = row_data[col.name]
284
+ # Try to convert back to original type
285
+ if col.type in [ColumnType.INTEGER, ColumnType.BIGINT, ColumnType.AUTOINCREMENT]:
286
+ try:
287
+ processed_row[col.name] = int(value)
288
+ except (ValueError, TypeError):
289
+ processed_row[col.name] = value
290
+ elif col.type in [ColumnType.FLOAT, ColumnType.DOUBLE, ColumnType.DECIMAL]:
291
+ try:
292
+ processed_row[col.name] = float(value) # type: ignore
293
+ except (ValueError, TypeError):
294
+ processed_row[col.name] = value
295
+ elif col.type == ColumnType.BOOLEAN:
296
+ processed_row[col.name] = value.lower() in ('true', '1', 'yes')
297
+ elif col.type == ColumnType.JSON:
298
+ try:
299
+ processed_row[col.name] = json.loads(value)
300
+ except (ValueError, TypeError):
301
+ processed_row[col.name] = value
302
+ else:
303
+ processed_row[col.name] = value
304
+
305
+ rows.append(processed_row)
306
+
307
+ # Apply where clause if present
308
+ if query.where:
309
+ column_names = set(column.name for column in schema.columns)
310
+ for condition in query.where.conditions:
311
+ if condition.column not in column_names:
312
+ raise DBQueryError(f"Condition on invalid column '{condition.column}'")
313
+ rows = [row for row in rows if self._evaluate_where_clause(row, query.where)]
314
+
315
+ # Apply order by if present
316
+ if query.order_by:
317
+ for order in reversed(query.order_by):
318
+ rows.sort(key=lambda x: x.get(order.column, ''), reverse=order.direction == "DESC")
319
+
320
+ # Apply limit and offset if present
321
+ if query.offset:
322
+ rows = rows[query.offset:]
323
+ if query.limit:
324
+ rows = rows[:query.limit]
325
+
326
+ return rows
327
+
328
+ async def update(self, query: UpdateQuery) -> int:
329
+ """Update records in the database"""
330
+ self._assert_connection()
331
+
332
+ # Get schema
333
+ schema_key = f"{self.SCHEMA_PREFIX}{query.table}"
334
+ schema_json = await self._db.get(schema_key)
335
+ if not schema_json:
336
+ raise ValueError(f"Table '{query.table}' does not exist")
337
+
338
+ schema = TableSchema.model_validate(json.loads(schema_json))
339
+ updated_count = 0
340
+
341
+ # Get all rows from the table
342
+ table_key = f"{self.TABLE_PREFIX}{query.table}"
343
+ all_rows = await self._db.hgetall(table_key) # type: ignore
344
+
345
+ # Validate new values against schema
346
+ for column_name, value in query.data.items():
347
+ column = next((col for col in schema.columns if col.name == column_name), None)
348
+ if not column:
349
+ raise ValueError(f"Column '{column_name}' does not exist")
350
+ if not self._validate_column_type(column, value):
351
+ raise ValueError(f"Invalid type for column '{column_name}'")
352
+
353
+ # Update matching rows
354
+ for row_id, row_json in all_rows.items():
355
+ row_data = json.loads(row_json)
356
+
357
+ if not query.where or self._evaluate_where_clause(row_data, query.where):
358
+ # Update row data
359
+ for key, value in query.data.items():
360
+ if isinstance(value, (dict, list)):
361
+ row_data[key] = json.dumps(value)
362
+ else:
363
+ row_data[key] = str(value)
364
+
365
+ # Store updated row
366
+ await self._db.hset(table_key, row_id, json.dumps(row_data)) # type: ignore
367
+ updated_count += 1
368
+
369
+ self.logger.info("Updated %s rows in '%s'", updated_count, query.table)
370
+ return updated_count
371
+
372
+ async def delete(self, query: DeleteQuery) -> int:
373
+ """Delete records from the database"""
374
+ self._assert_connection()
375
+
376
+ # Get schema
377
+ schema_key = f"{self.SCHEMA_PREFIX}{query.table}"
378
+ schema_json = await self._db.get(schema_key)
379
+ if not schema_json:
380
+ raise ValueError(f"Table '{query.table}' does not exist")
381
+
382
+ # Get all rows from the table
383
+ table_key = f"{self.TABLE_PREFIX}{query.table}"
384
+ all_rows = await self._db.hgetall(table_key) # type: ignore
385
+
386
+ # Find rows to delete
387
+ rows_to_delete = []
388
+ for row_id, row_json in all_rows.items():
389
+ row_data = json.loads(row_json)
390
+ if not query.where or self._evaluate_where_clause(row_data, query.where):
391
+ rows_to_delete.append(row_id)
392
+
393
+ # Delete rows
394
+ for row_id in rows_to_delete:
395
+ await self._db.hdel(table_key, row_id) # type: ignore
396
+
397
+ self.logger.info("Deleted %s rows from '%s'", len(rows_to_delete), query.table)
398
+ return len(rows_to_delete)
399
+
400
+
401
+ __all__ = [
402
+ "RedisDatabase"
403
+ ]