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,50 @@
1
+ __version__ = "1.1.28"
2
+ """danielutils is a convenience library of functions decorators
3
+ data-structures and more that make my development workflow faster
4
+ """
5
+ # =================================================================
6
+ # ============================= LEAFS =============================
7
+ # =================================================================
8
+ from .path import *
9
+ from .date_time import *
10
+ from .aliases import *
11
+ from .exceptions import PrintCatchOne
12
+ from .snippets import *
13
+ from .abstractions import *
14
+ from .protocols import *
15
+ # =================================================================
16
+ # ========================= ORDER MATTERS =========================
17
+ # =================================================================
18
+
19
+ from .reflection import *
20
+ from .decorators import *
21
+ # ========== NEEDS REFLECTION ==========
22
+ # ========== NEEDS DECORATORS ==========
23
+ from .colors import *
24
+ # ========== NEEDS BOTH ==========
25
+
26
+ from .progress_bar import *
27
+ from .functions import *
28
+ from .io_ import *
29
+ from .system import *
30
+ from .text import *
31
+ from .conversions import *
32
+ from .better_builtins import *
33
+ from .time import *
34
+ from .date import *
35
+ from .data_structures import *
36
+ from .math_ import *
37
+ from .system import *
38
+ from .print_ import *
39
+ from .metaclasses import *
40
+ from .generators import *
41
+ from .university import *
42
+ from .mock_ import *
43
+ from .context_managers import *
44
+ from .testing import *
45
+ from .retry_executor import *
46
+ from .java import *
47
+ from .random_ import *
48
+ from .lombok import *
49
+ from .logging_ import *
50
+ from .async_ import *
@@ -0,0 +1,3 @@
1
+ from .db import *
2
+ from .multiprogramming import *
3
+ from .repl import *
@@ -0,0 +1,10 @@
1
+ """
2
+ Database package for Stick With Me
3
+ """
4
+ from .implementations import *
5
+ from .database import *
6
+ from .database_definitions import *
7
+ from .database_exceptions import *
8
+ from .database_factory import *
9
+ from .dependencies import *
10
+ from .database_initializer import *
@@ -0,0 +1,206 @@
1
+ import functools
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import Dict, Any, List, Callable, TypeVar, cast, Optional, Set
5
+ from .database_exceptions import DBException
6
+ from .database_definitions import TableSchema, SelectQuery, UpdateQuery, DeleteQuery
7
+ from ...logging_.utils import get_logger
8
+ logger = get_logger(__name__)
9
+
10
+ # Type variable for the decorator
11
+ F = TypeVar('F', bound=Callable[..., Any])
12
+
13
+
14
+ class Database(ABC):
15
+ """Abstract base class for database operations"""
16
+
17
+ @classmethod
18
+ def _wrap_db_exceptions(cls, db_method: F) -> F:
19
+ """
20
+ Private decorator to wrap database implementation methods and convert implementation-specific
21
+ exceptions to our standard database exceptions.
22
+ """
23
+
24
+ @functools.wraps(db_method)
25
+ async def wrapper(self: 'Database', *args: Any, **kwargs: Any) -> Any:
26
+ method_name = db_method.__name__
27
+ logger.debug("Executing database method: %s", method_name)
28
+ try:
29
+ result = await db_method(self, *args, **kwargs)
30
+ logger.debug("Database method '%s' completed successfully", method_name)
31
+ return result
32
+ except DBException as e:
33
+ logger.error("Database method '%s' failed with DBException: %s", method_name, e)
34
+ raise
35
+ except Exception as e:
36
+ logger.error("Database method '%s' failed with %s: %s", method_name, type(e).__name__, e)
37
+ raise cls._default_class_exception_conversion(e)
38
+
39
+ return cast(F, wrapper)
40
+
41
+ @classmethod
42
+ def _get_functions_with_auto_converted_exceptions(cls) -> Set[str]:
43
+ return {
44
+ "connect",
45
+ "disconnect",
46
+ "get_schemas",
47
+ "create_table",
48
+ "insert",
49
+ "get",
50
+ "update",
51
+ "delete"
52
+ }
53
+
54
+ @classmethod
55
+ def __init_subclass__(cls) -> None:
56
+ """Initialize subclass by wrapping all public methods with exception handling"""
57
+ logger.debug("Initializing Database subclass: %s", cls.__name__)
58
+ wrapped_methods = []
59
+ for name, method in cls.__dict__.items():
60
+ if (
61
+ callable(method) and
62
+ not name.startswith('_') and
63
+ not isinstance(method, (classmethod, staticmethod))
64
+ and name in cls._get_functions_with_auto_converted_exceptions()
65
+ ):
66
+ setattr(cls, name, cls._wrap_db_exceptions(method))
67
+ wrapped_methods.append(name)
68
+ logger.debug("Database subclass '%s' initialized with %s wrapped methods: %s", cls.__name__, len(wrapped_methods), wrapped_methods)
69
+
70
+ @classmethod
71
+ def _default_class_exception_conversion(cls, e: Exception) -> Exception:
72
+ """
73
+ Handle implementation-specific exceptions. Override this method in implementations
74
+ to provide custom exception handling.
75
+
76
+ Args:
77
+ e (Exception): The original exception
78
+
79
+ Returns:
80
+ Exception: The converted exception
81
+ """
82
+ logger.warning("Converting exception %s to DBException: %s", type(e).__name__, e)
83
+ return DBException(f"Database error: {str(e)}")
84
+
85
+ async def __aenter__(self) -> 'Database':
86
+ """
87
+ Context manager entry point. Connects to the database.
88
+
89
+ Returns:
90
+ Database: The database instance for use in the context
91
+ """
92
+ logger.debug("Entering database context for %s", self.__class__.__name__)
93
+ await self.connect()
94
+ logger.debug("Database context entered successfully for %s", self.__class__.__name__)
95
+ return self
96
+
97
+ async def __aexit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> None:
98
+ """
99
+ Context manager exit point. Disconnects from the database.
100
+
101
+ Args:
102
+ exc_type: The type of exception that was raised, if any
103
+ exc_val: The exception instance that was raised, if any
104
+ exc_tb: The traceback for the exception, if any
105
+ """
106
+ logger.debug("Exiting database context for %s", self.__class__.__name__)
107
+ if exc_type is not None:
108
+ logger.warning("Database context exited with exception: %s: %s", exc_type.__name__, exc_val)
109
+ await self.disconnect()
110
+ logger.debug("Database context exited successfully for %s", self.__class__.__name__)
111
+
112
+ @abstractmethod
113
+ async def connect(self) -> None:
114
+ """Establish connection to the database
115
+ Note: Implementations should log connection attempts and results
116
+ """
117
+
118
+ @abstractmethod
119
+ async def disconnect(self) -> None:
120
+ """Close the database connection
121
+ Note: Implementations should log disconnection attempts and results
122
+ """
123
+
124
+ @abstractmethod
125
+ def is_connected(self) -> bool:
126
+ """Check if the database connection is open
127
+ Note: Implementations should log connection status checks
128
+ """
129
+
130
+ @abstractmethod
131
+ async def get_schemas(self) -> Dict[str, TableSchema]:
132
+ """
133
+ Get the complete database schema
134
+ Note: Implementations should log schema retrieval operations
135
+
136
+ Returns:
137
+ Dict[str, TableSchema]: Dictionary mapping table names to their schemas
138
+ """
139
+
140
+ @abstractmethod
141
+ async def create_table(self, schema: TableSchema) -> None:
142
+ """
143
+ Create a new table in the database
144
+ Note: Implementations should log table creation operations
145
+
146
+ Args:
147
+ schema (TableSchema): Schema definition for the table
148
+ """
149
+
150
+ @abstractmethod
151
+ async def insert(self, table: str, data: Dict[str, Any]) -> Any:
152
+ """
153
+ Insert a record into the specified table
154
+ Note: Implementations should log insert operations with table name and data size
155
+
156
+ Args:
157
+ table (str): Name of the table to insert into
158
+ data (Dict[str, Any]): Dictionary containing column names and values
159
+
160
+ Returns:
161
+ Any: ID of the inserted record
162
+ """
163
+
164
+ @abstractmethod
165
+ async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
166
+ """
167
+ Get records from the database
168
+ Note: Implementations should log query operations with table name and result count
169
+
170
+ Args:
171
+ query (SelectQuery): Query definition containing table name, conditions, ordering, etc.
172
+
173
+ Returns:
174
+ List[Dict[str, Any]]: List of dictionaries containing the selected records
175
+ """
176
+
177
+ @abstractmethod
178
+ async def update(self, query: UpdateQuery) -> int:
179
+ """
180
+ Update records in the database
181
+ Note: Implementations should log update operations with table name and affected row count
182
+
183
+ Args:
184
+ query (UpdateQuery): Query definition containing table name, conditions, and data to update
185
+
186
+ Returns:
187
+ int: Number of affected rows
188
+ """
189
+
190
+ @abstractmethod
191
+ async def delete(self, query: DeleteQuery) -> int:
192
+ """
193
+ Delete records from the database
194
+ Note: Implementations should log delete operations with table name and affected row count
195
+
196
+ Args:
197
+ query (DeleteQuery): Query definition containing table name and conditions
198
+
199
+ Returns:
200
+ int: Number of affected rows
201
+ """
202
+
203
+
204
+ __all__ = [
205
+ "Database"
206
+ ]
@@ -0,0 +1,219 @@
1
+ import json
2
+ from typing import Dict, Any, List, Optional, Literal, Union, Type
3
+ from enum import Enum
4
+
5
+ try:
6
+ from pydantic import BaseModel, Field, ConfigDict
7
+ except ImportError:
8
+ BaseModel = type("BaseModel", (object,), {}) # type:ignore
9
+ Field = lambda **kwargs: kwargs # type:ignore
10
+ ConfigDict = lambda **kwargs: kwargs # type:ignore
11
+
12
+
13
+ class ColumnType(str, Enum):
14
+ """SQL column types"""
15
+ INTEGER = "INTEGER"
16
+ AUTOINCREMENT = "AUTOINCREMENT"
17
+ SMALLINT = "SMALLINT"
18
+ MEDIUMINT = "MEDIUMINT"
19
+ TINYINT = "TINYINT"
20
+ BIGINT = "BIGINT"
21
+ FLOAT = "FLOAT"
22
+ DOUBLE = "DOUBLE"
23
+ DECIMAL = "DECIMAL"
24
+ BOOLEAN = "BOOLEAN"
25
+ TEXT = "TEXT"
26
+ VARCHAR = "VARCHAR"
27
+ CHAR = "CHAR"
28
+ DATE = "DATE"
29
+ TIME = "TIME"
30
+ DATETIME = "DATETIME"
31
+ TIMESTAMP = "TIMESTAMP"
32
+ BLOB = "BLOB"
33
+ JSON = "JSON"
34
+ UUID = "UUID"
35
+
36
+
37
+ class BaseDBModel(BaseModel):
38
+ """Base model with JSON serialization support"""
39
+ model_config = ConfigDict(
40
+ arbitrary_types_allowed=True
41
+ )
42
+
43
+ def to_dict(self) -> Dict[str, Any]:
44
+ """Convert model to dictionary"""
45
+ return self.model_dump(mode="json")
46
+
47
+ def to_json(self) -> str:
48
+ """Convert model to JSON string"""
49
+ return self.model_dump_json()
50
+
51
+ @classmethod
52
+ def from_json(cls, json_str: str) -> 'BaseDBModel':
53
+ """Create model instance from JSON string"""
54
+ data = json.loads(json_str)
55
+ return cls.model_validate(data)
56
+
57
+
58
+ class TableColumn(BaseDBModel):
59
+ """Represents a database column definition"""
60
+ name: str
61
+ type: ColumnType
62
+ nullable: bool = True
63
+ primary_key: bool = False
64
+ unique: bool = False
65
+ default: Any = None
66
+ foreign_key: Optional[Dict[str, str]] = None
67
+
68
+
69
+ class TableIndex(BaseDBModel):
70
+ """Represents a database index definition"""
71
+ name: str
72
+ columns: List[str]
73
+ unique: bool = False
74
+
75
+
76
+ class TableForeignKey(BaseDBModel):
77
+ """Represents a foreign key constraint"""
78
+ name: str
79
+ columns: List[str]
80
+ reference_table: str
81
+ reference_columns: List[str]
82
+ on_delete: Optional[str] = None
83
+ on_update: Optional[str] = None
84
+
85
+
86
+ class TableSchema(BaseDBModel):
87
+ """Represents a complete table schema"""
88
+ name: str
89
+ columns: List[TableColumn]
90
+ indexes: List[TableIndex] = Field(default_factory=list)
91
+ foreign_keys: List[TableForeignKey] = Field(default_factory=list)
92
+
93
+
94
+ class Operator(str, Enum):
95
+ """SQL comparison operators"""
96
+ EQ = "="
97
+ NEQ = "!="
98
+ GT = ">"
99
+ GTE = ">="
100
+ LT = "<"
101
+ LTE = "<="
102
+ LIKE = "LIKE" # Not supported in InMemoryDatabase
103
+ ILIKE = "ILIKE" # Not supported in InMemoryDatabase
104
+ CONTAINS = "CONTAINS" # Case-insensitive contains
105
+ CONTAINS_CS = "CONTAINS_CS" # Case-sensitive contains
106
+ IN = "IN"
107
+ NOT_IN = "NOT IN"
108
+ IS_NULL = "IS NULL"
109
+ IS_NOT_NULL = "IS NOT NULL"
110
+ AND = "AND"
111
+ OR = "OR"
112
+
113
+
114
+ class JoinType(str, Enum):
115
+ """SQL join types"""
116
+ INNER = "INNER JOIN"
117
+ LEFT = "LEFT JOIN"
118
+ RIGHT = "RIGHT JOIN"
119
+ FULL = "FULL JOIN"
120
+
121
+
122
+ class OrderDirection(str, Enum):
123
+ """SQL order directions"""
124
+ ASC = "ASC"
125
+ DESC = "DESC"
126
+
127
+
128
+ class Condition(BaseDBModel):
129
+ """A single SQL condition"""
130
+ column: str
131
+ operator: Operator
132
+ value: Optional[Any] = None
133
+ values: Optional[List[Any]] = None # For IN/NOT IN operators
134
+
135
+
136
+ class Join(BaseDBModel):
137
+ """SQL join definition"""
138
+ type: JoinType
139
+ table: str
140
+ conditions: List[Condition]
141
+
142
+
143
+ class OrderBy(BaseDBModel):
144
+ """SQL ORDER BY clause"""
145
+ column: str
146
+ direction: OrderDirection = OrderDirection.ASC
147
+
148
+
149
+ class WhereClause(BaseDBModel):
150
+ """SQL WHERE clause with conditions"""
151
+ conditions: List[Condition]
152
+ operator: Literal["AND", "OR"] = "AND"
153
+
154
+
155
+ class SelectQuery(BaseDBModel):
156
+ """Complete SELECT query definition"""
157
+ table: str
158
+ columns: Optional[List[str]] = None
159
+ where: Optional[WhereClause] = None
160
+ joins: Optional[List[Join]] = None
161
+ order_by: Optional[List[OrderBy]] = None
162
+ group_by: Optional[List[str]] = None
163
+ having: Optional[WhereClause] = None
164
+ limit: Optional[int] = None
165
+ offset: Optional[int] = None
166
+
167
+
168
+ class UpdateQuery(BaseDBModel):
169
+ """Complete UPDATE query definition"""
170
+ table: str
171
+ data: Dict[str, Any]
172
+ where: Optional[WhereClause] = None
173
+
174
+
175
+ class DeleteQuery(BaseDBModel):
176
+ """Complete DELETE query definition"""
177
+ table: str
178
+ where: Optional[WhereClause] = None
179
+
180
+
181
+ # Helper functions for JSON serialization
182
+ def serialize_to_json(obj: Union[BaseDBModel, List[BaseDBModel], Dict[str, Any]]) -> str:
183
+ """Serialize any supported object to JSON string"""
184
+ if isinstance(obj, BaseDBModel):
185
+ return obj.to_json()
186
+ elif isinstance(obj, list):
187
+ return json.dumps([item.to_dict() if isinstance(item, BaseDBModel) else item for item in obj])
188
+ elif isinstance(obj, dict):
189
+ return json.dumps({k: v.to_dict() if isinstance(v, BaseDBModel) else v for k, v in obj.items()})
190
+ return json.dumps(obj)
191
+
192
+
193
+ def deserialize_from_json(json_str: str, model_class: Type[BaseDBModel]) -> Union[BaseDBModel, List[BaseDBModel]]:
194
+ """Deserialize JSON string to model instance(s)"""
195
+ data = json.loads(json_str)
196
+ if isinstance(data, list):
197
+ return [model_class.model_validate(item) for item in data]
198
+ return model_class.model_validate(data)
199
+
200
+
201
+ __all__ = [
202
+ "ColumnType",
203
+ "TableColumn",
204
+ "TableIndex",
205
+ "TableForeignKey",
206
+ "TableSchema",
207
+ "Operator",
208
+ "JoinType",
209
+ "OrderDirection",
210
+ "Condition",
211
+ "Join",
212
+ "OrderBy",
213
+ "WhereClause",
214
+ "SelectQuery",
215
+ "UpdateQuery",
216
+ "DeleteQuery",
217
+ "serialize_to_json",
218
+ "deserialize_from_json"
219
+ ]
@@ -0,0 +1,34 @@
1
+ from typing import Optional
2
+
3
+
4
+ class DBException(Exception):
5
+ """Base class for all database exceptions"""
6
+
7
+ def __init__(self, message: Optional[str] = None, status_code: Optional[int] = None) -> None:
8
+ super().__init__(message)
9
+ self.status_code = status_code
10
+
11
+
12
+ class DBConnectionError(DBException):
13
+ """Raised when there is an error connecting to the database"""
14
+
15
+
16
+ class DBQueryError(DBException):
17
+ """Raised when there is an error executing a query"""
18
+
19
+
20
+ class DBSchemaError(DBException):
21
+ """Raised when there is an error with database schema operations"""
22
+
23
+
24
+ class DBValidationError(DBException):
25
+ """Raised when there is a validation error"""
26
+
27
+
28
+ __all__ = [
29
+ "DBException",
30
+ "DBConnectionError",
31
+ "DBQueryError",
32
+ "DBSchemaError",
33
+ "DBValidationError"
34
+ ]
@@ -0,0 +1,70 @@
1
+ from abc import ABC, abstractmethod
2
+ import logging
3
+ from typing import Literal, Dict, Any, Optional, Tuple
4
+ from .database import Database
5
+ from .implementations import InMemoryDatabase, SQLiteDatabase, PersistentInMemoryDatabase, RedisDatabase
6
+ from ...logging_.utils import get_logger
7
+ logger = get_logger(__name__)
8
+
9
+ MAPPING = {
10
+ "sqlite": SQLiteDatabase,
11
+ "memory": InMemoryDatabase,
12
+ "persistent_memory": PersistentInMemoryDatabase,
13
+ "redis": RedisDatabase
14
+ }
15
+
16
+
17
+ class DatabaseFactory(ABC):
18
+ """Factory class for creating database instances"""
19
+
20
+ _instances: Dict[str, Database] = {}
21
+
22
+ @classmethod
23
+ def get_database(
24
+ cls,
25
+ db_type: Literal["sqlite", "memory", "persistent_memory", "redis"] = "persistent_memory",
26
+ db_args: Optional[Tuple[Any, ...]] = None,
27
+ db_kwargs: Optional[Dict[str, Any]] = None
28
+ ) -> Database:
29
+ """
30
+ Get a database instance based on the specified type. Returns existing instance if available.
31
+
32
+ Args:
33
+ db_args (Optional[Tuple[Any, ...]]): Positional arguments for database initialization
34
+ db_type (str): Type of database to create (default: "persistent_memory")
35
+ Supported types: "sqlite", "memory", "persistent_memory"
36
+ db_kwargs (Optional[Dict[str, Any]]): Keyword arguments for database initialization
37
+ Returns:
38
+ Database: Database instance
39
+ """
40
+ logger.debug("Requesting database instance: type=%s, args_count=%s, kwargs_count=%s", db_type, len(db_args) if db_args else 0, len(db_kwargs) if db_kwargs else 0)
41
+
42
+ if db_type not in cls._instances:
43
+ logger.info("Creating new database instance: %s", db_type)
44
+ if db_type not in MAPPING:
45
+ logger.error("Unsupported database type requested: %s", db_type)
46
+ raise ValueError(f"Unsupported database type: '{db_type}'")
47
+
48
+ # Convert None to empty tuple/dict for database initialization
49
+ args = db_args or ()
50
+ kwargs = db_kwargs or {}
51
+ logger.debug("Initializing %s database with args=%s, kwargs=%s", db_type, args, kwargs)
52
+ cls._instances[db_type] = MAPPING[db_type](*args, **kwargs)
53
+ logger.info("Database instance created successfully: %s", db_type)
54
+ else:
55
+ logger.debug("Returning existing database instance: %s", db_type)
56
+
57
+ return cls._instances[db_type]
58
+
59
+ @classmethod
60
+ @abstractmethod
61
+ def get_database_from_settings(cls) -> Database:
62
+ """
63
+ Get a database instance using the application settings.
64
+ This is the preferred method for getting a database instance in the application.
65
+ """
66
+
67
+
68
+ __all__ = [
69
+ "DatabaseFactory"
70
+ ]