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.
- danielutils/__init__.py +50 -0
- danielutils/abstractions/__init__.py +3 -0
- danielutils/abstractions/db/__init__.py +10 -0
- danielutils/abstractions/db/database.py +206 -0
- danielutils/abstractions/db/database_definitions.py +219 -0
- danielutils/abstractions/db/database_exceptions.py +34 -0
- danielutils/abstractions/db/database_factory.py +70 -0
- danielutils/abstractions/db/database_initializer.py +261 -0
- danielutils/abstractions/db/database_models.py +213 -0
- danielutils/abstractions/db/dependencies.py +6 -0
- danielutils/abstractions/db/implementations/__init__.py +4 -0
- danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
- danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
- danielutils/abstractions/db/implementations/redis_database.py +403 -0
- danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
- danielutils/abstractions/multiprogramming/__init__.py +3 -0
- danielutils/abstractions/multiprogramming/multi_id.py +26 -0
- danielutils/abstractions/multiprogramming/worker.py +104 -0
- danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
- danielutils/abstractions/repl.py +125 -0
- danielutils/aliases.py +174 -0
- danielutils/async_/__init__.py +7 -0
- danielutils/async_/async_cmd.py +28 -0
- danielutils/async_/async_command.py +879 -0
- danielutils/async_/async_layered_command.py +125 -0
- danielutils/async_/async_retry_executor.py +86 -0
- danielutils/async_/async_worker_pool.py +110 -0
- danielutils/async_/time_strategy.py +73 -0
- danielutils/async_/utils.py +57 -0
- danielutils/better_builtins/__init__.py +3 -0
- danielutils/better_builtins/counter.py +48 -0
- danielutils/better_builtins/frange.py +266 -0
- danielutils/better_builtins/typed_builtins/__init__.py +4 -0
- danielutils/better_builtins/typed_builtins/factory.py +126 -0
- danielutils/better_builtins/typed_builtins/tdict.py +36 -0
- danielutils/better_builtins/typed_builtins/tlist.py +223 -0
- danielutils/better_builtins/typed_builtins/tset.py +67 -0
- danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
- danielutils/colors.py +203 -0
- danielutils/context_managers/__init__.py +5 -0
- danielutils/context_managers/attr_context.py +40 -0
- danielutils/context_managers/multi_context.py +34 -0
- danielutils/context_managers/optional_context.py +33 -0
- danielutils/context_managers/state_context.py +25 -0
- danielutils/context_managers/temporary_file.py +108 -0
- danielutils/convenience.py +12 -0
- danielutils/conversions/__init__.py +2 -0
- danielutils/conversions/main_conversions.py +131 -0
- danielutils/conversions/specialized_conversions/__init__.py +2 -0
- danielutils/conversions/specialized_conversions/to_hex.py +25 -0
- danielutils/conversions/specialized_conversions/to_int.py +19 -0
- danielutils/custom_types.py +18 -0
- danielutils/data_structures/__init__.py +8 -0
- danielutils/data_structures/algorithms.py +52 -0
- danielutils/data_structures/comparer.py +47 -0
- danielutils/data_structures/default_dict.py +23 -0
- danielutils/data_structures/functions.py +35 -0
- danielutils/data_structures/graph/__init__.py +4 -0
- danielutils/data_structures/graph/binary_node.py +71 -0
- danielutils/data_structures/graph/graph.py +208 -0
- danielutils/data_structures/graph/multinode.py +101 -0
- danielutils/data_structures/graph/node.py +60 -0
- danielutils/data_structures/heap/__init__.py +3 -0
- danielutils/data_structures/heap/heap.py +119 -0
- danielutils/data_structures/heap/max_heap.py +18 -0
- danielutils/data_structures/heap/min_heap.py +18 -0
- danielutils/data_structures/queue/__init__.py +3 -0
- danielutils/data_structures/queue/atomic_queue.py +15 -0
- danielutils/data_structures/queue/priority_queue.py +110 -0
- danielutils/data_structures/queue/queue.py +92 -0
- danielutils/data_structures/stack.py +102 -0
- danielutils/data_structures/trees/__init__.py +2 -0
- danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
- danielutils/data_structures/trees/binary_tree.py +63 -0
- danielutils/date.py +43 -0
- danielutils/date_time.py +23 -0
- danielutils/decorators/__init__.py +18 -0
- danielutils/decorators/atomic.py +45 -0
- danielutils/decorators/attach.py +61 -0
- danielutils/decorators/chain_decorators.py +39 -0
- danielutils/decorators/decorate_conditionally.py +32 -0
- danielutils/decorators/delay_call.py +47 -0
- danielutils/decorators/deprecate.py +46 -0
- danielutils/decorators/final.py +39 -0
- danielutils/decorators/limit_recursion.py +63 -0
- danielutils/decorators/memo.py +68 -0
- danielutils/decorators/normalize_decorator.py +55 -0
- danielutils/decorators/overload.py +204 -0
- danielutils/decorators/partially_implemented.py +32 -0
- danielutils/decorators/processify.py +74 -0
- danielutils/decorators/property.py +42 -0
- danielutils/decorators/singleton.py +59 -0
- danielutils/decorators/threadify.py +40 -0
- danielutils/decorators/timeout.py +82 -0
- danielutils/decorators/total_ordering.py +82 -0
- danielutils/decorators/validate.py +227 -0
- danielutils/exceptions.py +90 -0
- danielutils/functions/__init__.py +13 -0
- danielutils/functions/areoneof.py +47 -0
- danielutils/functions/check_foreach.py +39 -0
- danielutils/functions/factorial.py +24 -0
- danielutils/functions/flatten.py +33 -0
- danielutils/functions/foreach.py +20 -0
- danielutils/functions/isoftype.py +475 -0
- danielutils/functions/isoneof.py +71 -0
- danielutils/functions/multiloop.py +51 -0
- danielutils/functions/parallel_for.py +47 -0
- danielutils/functions/partition.py +45 -0
- danielutils/functions/powerset.py +31 -0
- danielutils/functions/subseteq.py +25 -0
- danielutils/functions/types_subseteq.py +51 -0
- danielutils/generators/__init__.py +3 -0
- danielutils/generators/conditional_generator.py +59 -0
- danielutils/generators/generator_from_stream.py +31 -0
- danielutils/generators/join_generators.py +109 -0
- danielutils/internet.py +92 -0
- danielutils/io_.py +451 -0
- danielutils/java/__init__.py +18 -0
- danielutils/java/interfaces/__init__.py +1 -0
- danielutils/java/interfaces/comparable.py +31 -0
- danielutils/java/java_interface.py +59 -0
- danielutils/logging_/__init__.py +2 -0
- danielutils/logging_/_impl/__init__.py +4 -0
- danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
- danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
- danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
- danielutils/logging_/_impl/log_level.py +13 -0
- danielutils/logging_/_impl/logger.py +109 -0
- danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
- danielutils/logging_/utils.py +199 -0
- danielutils/lombok/__init__.py +1 -0
- danielutils/lombok/builder.py +68 -0
- danielutils/math_/__init__.py +2 -0
- danielutils/math_/constants.py +103 -0
- danielutils/math_/functions.py +34 -0
- danielutils/math_/math_print.py +52 -0
- danielutils/math_/math_symbols.py +125 -0
- danielutils/metaclasses/__init__.py +5 -0
- danielutils/metaclasses/atomic_class_meta.py +36 -0
- danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
- danielutils/metaclasses/instance_cache_meta.py +66 -0
- danielutils/metaclasses/interface.py +328 -0
- danielutils/metaclasses/overload_meta.py +48 -0
- danielutils/mock_/__init__.py +1 -0
- danielutils/mock_/mock_module.py +27 -0
- danielutils/path.py +55 -0
- danielutils/print_.py +123 -0
- danielutils/progress_bar/__init__.py +3 -0
- danielutils/progress_bar/ascii_progress_bar.py +161 -0
- danielutils/progress_bar/progress_bar.py +75 -0
- danielutils/progress_bar/progress_bar_pool.py +72 -0
- danielutils/protocols/__init__.py +3 -0
- danielutils/protocols/dictable.py +38 -0
- danielutils/protocols/evaluable.py +26 -0
- danielutils/protocols/serializable.py +51 -0
- danielutils/py.typed +0 -0
- danielutils/random_.py +47 -0
- danielutils/reflection/__init__.py +6 -0
- danielutils/reflection/declarations.py +288 -0
- danielutils/reflection/file/__init__.py +1 -0
- danielutils/reflection/file/file_reflection.py +85 -0
- danielutils/reflection/function/__init__.py +1 -0
- danielutils/reflection/function/function_reflections.py +152 -0
- danielutils/reflection/info_classes/__init__.py +6 -0
- danielutils/reflection/info_classes/argument_info.py +278 -0
- danielutils/reflection/info_classes/class_info.py +204 -0
- danielutils/reflection/info_classes/decorator_info.py +54 -0
- danielutils/reflection/info_classes/file_info.py +1180 -0
- danielutils/reflection/info_classes/folder_info.py +592 -0
- danielutils/reflection/info_classes/function_info.py +551 -0
- danielutils/reflection/info_classes/import_info.py +207 -0
- danielutils/reflection/interpreter/__init__.py +9 -0
- danielutils/reflection/interpreter/callstack.py +57 -0
- danielutils/reflection/interpreter/get_traceback.py +27 -0
- danielutils/reflection/interpreter/interpreter.py +41 -0
- danielutils/reflection/interpreter/is_debugging.py +13 -0
- danielutils/reflection/interpreter/os_.py +33 -0
- danielutils/reflection/interpreter/packages.py +33 -0
- danielutils/reflection/interpreter/python_version.py +33 -0
- danielutils/reflection/interpreter/signals.py +18 -0
- danielutils/reflection/interpreter/tracer.py +175 -0
- danielutils/reflection/module/__init__.py +3 -0
- danielutils/reflection/module/lazy_module.py +104 -0
- danielutils/reflection/module/module_reflections.py +34 -0
- danielutils/reflection/module/package_reflection.py +161 -0
- danielutils/retry_executor/__init__.py +3 -0
- danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
- danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
- danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
- danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
- danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
- danielutils/retry_executor/backoff_strategy.py +28 -0
- danielutils/retry_executor/retry_executor.py +55 -0
- danielutils/snippets/__init__.py +1 -0
- danielutils/snippets/try_get.py +28 -0
- danielutils/system/__init__.py +3 -0
- danielutils/system/independent.py +197 -0
- danielutils/system/layered_command.py +130 -0
- danielutils/system/windows/__init__.py +2 -0
- danielutils/system/windows/utils/__init__.py +1 -0
- danielutils/system/windows/utils/filetime.py +19 -0
- danielutils/system/windows/win32_ctime.py +183 -0
- danielutils/system/windows/windows.py +78 -0
- danielutils/testing/__init__.py +1 -0
- danielutils/testing/unittest_/__init__.py +2 -0
- danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
- danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
- danielutils/text.py +169 -0
- danielutils/time.py +64 -0
- danielutils/tools/__init__.py +1 -0
- danielutils/tools/unittest_test_runner/__init__.py +9 -0
- danielutils/tools/unittest_test_runner/cli.py +70 -0
- danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
- danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
- danielutils/tools/unittest_test_runner/core/execution.py +244 -0
- danielutils/tools/unittest_test_runner/core/parser.py +163 -0
- danielutils/tools/unittest_test_runner/core/runner.py +561 -0
- danielutils/tools/unittest_test_runner/models.py +82 -0
- danielutils/university/__init__.py +7 -0
- danielutils/university/computability_and_complexity/__init__.py +2 -0
- danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
- danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- danielutils/university/computability_and_complexity/languages/language.py +17 -0
- danielutils/university/computability_and_complexity/languages/sat.py +71 -0
- danielutils/university/computability_and_complexity/turing_machine.py +47 -0
- danielutils/university/databases/__init__.py +1 -0
- danielutils/university/databases/all.py +756 -0
- danielutils/university/image_proccesing/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/__init__.py +3 -0
- danielutils/university/image_proccesing/encoding/encoding.py +35 -0
- danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
- danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
- danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
- danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
- danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
- danielutils/university/linear_algebra/__init__.py +1 -0
- danielutils/university/linear_algebra/matrix.py +404 -0
- danielutils/university/machine_learning/__init__.py +0 -0
- danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
- danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
- danielutils/university/machine_learning/activation_functions/relu.py +11 -0
- danielutils/university/machine_learning/neuron.py +15 -0
- danielutils/university/oop/__init__.py +1 -0
- danielutils/university/oop/observer.py +31 -0
- danielutils/university/oop/strategy.py +9 -0
- danielutils/university/probability/__init__.py +7 -0
- danielutils/university/probability/conditional_variable/__init__.py +3 -0
- danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
- danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
- danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
- danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
- danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
- danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
- danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
- danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
- danielutils/university/probability/distributions.py +35 -0
- danielutils/university/probability/expressions/__init__.py +2 -0
- danielutils/university/probability/expressions/accumulation_expression.py +291 -0
- danielutils/university/probability/expressions/probability_expression.py +110 -0
- danielutils/university/probability/funcs/__init__.py +4 -0
- danielutils/university/probability/funcs/covariance.py +12 -0
- danielutils/university/probability/funcs/expected_value.py +41 -0
- danielutils/university/probability/funcs/probability_function.py +14 -0
- danielutils/university/probability/funcs/variance.py +17 -0
- danielutils/university/probability/operator.py +79 -0
- danielutils/university/probability/protocols.py +32 -0
- danielutils/university/probability/supp.py +101 -0
- danielutils/university/probability/transformation.py +44 -0
- danielutils/versioned_imports.py +31 -0
- danielutils-1.1.28.dist-info/METADATA +390 -0
- danielutils-1.1.28.dist-info/RECORD +287 -0
- danielutils-1.1.28.dist-info/WHEEL +5 -0
- danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
- danielutils-1.1.28.dist-info/top_level.txt +1 -0
danielutils/__init__.py
ADDED
|
@@ -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,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
|
+
]
|