danielutils 1.0.45__tar.gz → 1.0.50__tar.gz
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-1.0.45/danielutils.egg-info → danielutils-1.0.50}/PKG-INFO +3 -3
- {danielutils-1.0.45 → danielutils-1.0.50}/README.md +2 -2
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/__init__.py +0 -1
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/__init__.py +1 -1
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database.py +41 -19
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database_exceptions.py +7 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database_factory.py +10 -10
- danielutils-1.0.50/danielutils/abstractions/db/database_initializer.py +256 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/dependencies.py +0 -1
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/implementations/in_memory_database.py +24 -25
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/implementations/persistent_in_memory_database.py +15 -17
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/implementations/redis_database.py +62 -72
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/implementations/sqlite_database.py +32 -31
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/async_layered_command.py +1 -1
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/logger.py +1 -1
- danielutils-1.0.50/danielutils/mock_/__init__.py +1 -0
- danielutils-1.0.50/danielutils/reflection/__init__.py +6 -0
- danielutils-1.0.50/danielutils/reflection/info_classes/__init__.py +6 -0
- {danielutils-1.0.45/danielutils/reflection → danielutils-1.0.50/danielutils/reflection/info_classes}/argument_info.py +8 -2
- {danielutils-1.0.45/danielutils/reflection → danielutils-1.0.50/danielutils/reflection/info_classes}/class_info.py +8 -4
- danielutils-1.0.50/danielutils/reflection/info_classes/file_info.py +267 -0
- {danielutils-1.0.45/danielutils/reflection → danielutils-1.0.50/danielutils/reflection/info_classes}/function_info.py +10 -3
- danielutils-1.0.50/danielutils/reflection/info_classes/import_info.py +100 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/testing/unittest_/always_teardown_testcase.py +4 -4
- {danielutils-1.0.45 → danielutils-1.0.50/danielutils.egg-info}/PKG-INFO +3 -3
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils.egg-info/SOURCES.txt +10 -10
- {danielutils-1.0.45 → danielutils-1.0.50}/pyproject.toml +1 -1
- danielutils-1.0.45/danielutils/abstractions/database/__init__.py +0 -2
- danielutils-1.0.45/danielutils/abstractions/database/cached_database.py +0 -75
- danielutils-1.0.45/danielutils/abstractions/database/database.py +0 -104
- danielutils-1.0.45/danielutils/abstractions/database/redis_database.py +0 -46
- danielutils-1.0.45/danielutils/abstractions/db/initializer.py +0 -260
- danielutils-1.0.45/danielutils/mock_/__init__.py +0 -2
- danielutils-1.0.45/danielutils/mock_/mock_database.py +0 -72
- danielutils-1.0.45/danielutils/reflection/__init__.py +0 -53
- {danielutils-1.0.45 → danielutils-1.0.50}/LICENSE +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/MANIFEST.in +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database_definitions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database_models.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/implementations/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/multiprogramming/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/multiprogramming/multi_id.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/multiprogramming/worker.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/multiprogramming/worker_pool.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/repl.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/aliases.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/async_cmd.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/async_retry_executor.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/async_worker_pool.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/time_strategy.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/async_/utils.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/counter.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/frange.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/factory.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/tdict.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/tlist.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/tset.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/better_builtins/typed_builtins/ttuple.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/colors.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/attr_context.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/multi_context.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/optional_context.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/state_context.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/context_managers/temporary_file.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/convenience.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/conversions/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/conversions/main_conversions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/conversions/specialized_conversions/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/conversions/specialized_conversions/to_hex.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/conversions/specialized_conversions/to_int.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/custom_types.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/algorithms.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/comparer.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/default_dict.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/functions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/graph/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/graph/binary_node.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/graph/graph.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/graph/multinode.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/graph/node.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/heap/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/heap/heap.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/heap/max_heap.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/heap/min_heap.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/queue/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/queue/atomic_queue.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/queue/priority_queue.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/queue/queue.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/stack.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/trees/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/trees/binary_syntax_tree.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/data_structures/trees/binary_tree.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/date.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/date_time.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/atomic.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/attach.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/chain_decorators.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/decorate_conditionally.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/delay_call.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/deprecate.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/final.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/limit_recursion.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/memo.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/normalize_decorator.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/overload.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/partially_implemented.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/processify.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/property.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/singleton.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/threadify.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/timeout.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/total_ordering.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/decorators/validate.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/exceptions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/file_specifications/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/areoneof.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/check_foreach.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/factorial.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/flatten.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/foreach.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/isoftype.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/isoneof.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/multiloop.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/parallel_for.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/partition.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/powerset.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/subseteq.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/functions/types_subseteq.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/generators/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/generators/conditional_generator.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/generators/generator_from_stream.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/generators/join_generators.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/internet.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/io_.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/java/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/java/interfaces/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/java/interfaces/comparable.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/java/java_interface.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/builtin_impls/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/builtin_impls/file_logger.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/builtin_impls/print_logger.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/log_level.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/logging_/logger_strategy_impl_base.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/lombok/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/lombok/builder.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/math_/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/math_/constants.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/math_/functions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/math_/math_print.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/math_/math_symbols.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/atomic_class_meta.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/implicit_data_deleter_meta.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/instance_cache_meta.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/interface.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/metaclasses/overload_meta.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/mock_/mock_module.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/path.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/print_.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/progress_bar/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/progress_bar/ascii_progress_bar.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/progress_bar/progress_bar.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/progress_bar/progress_bar_pool.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/protocols/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/protocols/dictable.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/protocols/evaluable.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/protocols/serializable.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/py.typed +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/random_.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/class_/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/class_/class_reflection.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/file/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/file/file_reflection.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/function/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/function/function_reflections.py +0 -0
- {danielutils-1.0.45/danielutils/reflection → danielutils-1.0.50/danielutils/reflection/info_classes}/decoration_info.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/callstack.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/get_traceback.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/interpreter.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/is_debugging.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/os_.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/packages.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/python_version.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/signals.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/interpreter/tracer.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/module/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/module/module_reflections.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/reflection/module/package_reflection.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/constant_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/exponential_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/functional_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/linear_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategies/no_backoff.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/backoff_strategy.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/retry_executor/retry_executor.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/snippets/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/snippets/try_get.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/independent.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/layered_command.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/windows/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/windows/utils/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/windows/utils/filetime.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/windows/win32_ctime.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/system/windows/windows.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/testing/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/testing/unittest_/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/testing/unittest_/auto_cwd_testcase.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/text.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/time.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/discreate_finite_automaton.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/languages/language.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/languages/sat.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/computability_and_complexity/turing_machine.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/databases/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/databases/all.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/encoding.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossless/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossless/run_length.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossy/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/image_proccesing/tansformations/transformation.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/linear_algebra/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/linear_algebra/matrix.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/machine_learning/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/machine_learning/activation_functions/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/machine_learning/activation_functions/activation_function.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/machine_learning/activation_functions/relu.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/machine_learning/neuron.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/oop/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/oop/observer.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/oop/strategy.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/conditional_variable.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/bernoulli.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/binomial.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/discrete.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/geometric.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/poisson.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/conditional_variable/discrete/uniform.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/distributions.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/expressions/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/expressions/accumulation_expression.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/expressions/probability_expression.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/funcs/__init__.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/funcs/covariance.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/funcs/expected_value.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/funcs/probability_function.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/funcs/variance.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/operator.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/protocols.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/supp.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/university/probability/transformation.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils/versioned_imports.py +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils.egg-info/dependency_links.txt +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/danielutils.egg-info/top_level.txt +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/setup.cfg +0 -0
- {danielutils-1.0.45 → danielutils-1.0.50}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: danielutils
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.50
|
|
4
4
|
Summary: A python utils library for things I find useful
|
|
5
5
|
Author-email: danielnachumdev <danielnachumdev@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -40,7 +40,7 @@ License-File: LICENSE
|
|
|
40
40
|
[](https://github.com/danielnachumdev/danielutils/actions/workflows/gitleaks.yml)
|
|
41
41
|
[](https://github.com/danielnachumdev/danielutils/actions/workflows/github-code-scanning/codeql)
|
|
42
42
|
|
|
43
|
-
# danielutils v1.0.
|
|
43
|
+
# danielutils v1.0.50
|
|
44
44
|
|
|
45
45
|
A comprehensive Python utilities library designed to enhance your development workflow with powerful tools for type safety, async programming, database operations, and much more.
|
|
46
46
|
|
|
@@ -53,7 +53,7 @@ A comprehensive Python utilities library designed to enhance your development wo
|
|
|
53
53
|
- 🎨 **Developer Experience**: Progress bars, logging, reflection, and debugging tools
|
|
54
54
|
- 🧮 **Academic Tools**: Probability theory and statistical functions
|
|
55
55
|
|
|
56
|
-
**Tested Python versions**: `3.8.0+`, `3.9.0`, `3.10.13
|
|
56
|
+
**Tested Python versions**: `3.8.0+`, `3.9.0`, `3.10.13`
|
|
57
57
|
|
|
58
58
|
> **Note**: This package is actively developed and subject to change. Use at your own risk!
|
|
59
59
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://github.com/danielnachumdev/danielutils/actions/workflows/gitleaks.yml)
|
|
5
5
|
[](https://github.com/danielnachumdev/danielutils/actions/workflows/github-code-scanning/codeql)
|
|
6
6
|
|
|
7
|
-
# danielutils v1.0.
|
|
7
|
+
# danielutils v1.0.50
|
|
8
8
|
|
|
9
9
|
A comprehensive Python utilities library designed to enhance your development workflow with powerful tools for type safety, async programming, database operations, and much more.
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ A comprehensive Python utilities library designed to enhance your development wo
|
|
|
17
17
|
- 🎨 **Developer Experience**: Progress bars, logging, reflection, and debugging tools
|
|
18
18
|
- 🧮 **Academic Tools**: Probability theory and statistical functions
|
|
19
19
|
|
|
20
|
-
**Tested Python versions**: `3.8.0+`, `3.9.0`, `3.10.13
|
|
20
|
+
**Tested Python versions**: `3.8.0+`, `3.9.0`, `3.10.13`
|
|
21
21
|
|
|
22
22
|
> **Note**: This package is actively developed and subject to change. Use at your own risk!
|
|
23
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import Dict, Any, List, Callable, TypeVar, cast, Optional
|
|
3
|
+
from typing import Dict, Any, List, Callable, TypeVar, cast, Optional, Set
|
|
4
4
|
from .database_exceptions import DBException
|
|
5
5
|
from .database_definitions import TableSchema, SelectQuery, UpdateQuery, DeleteQuery
|
|
6
6
|
|
|
@@ -17,23 +17,41 @@ class Database(ABC):
|
|
|
17
17
|
Private decorator to wrap database implementation methods and convert implementation-specific
|
|
18
18
|
exceptions to our standard database exceptions.
|
|
19
19
|
"""
|
|
20
|
+
|
|
20
21
|
@functools.wraps(db_method)
|
|
21
|
-
def wrapper(self: 'Database', *args: Any, **kwargs: Any) -> Any:
|
|
22
|
+
async def wrapper(self: 'Database', *args: Any, **kwargs: Any) -> Any:
|
|
22
23
|
try:
|
|
23
|
-
return db_method(self, *args, **kwargs)
|
|
24
|
+
return await db_method(self, *args, **kwargs)
|
|
24
25
|
except DBException:
|
|
25
26
|
raise
|
|
26
27
|
except Exception as e:
|
|
27
28
|
raise cls._default_class_exception_conversion(e)
|
|
29
|
+
|
|
28
30
|
return cast(F, wrapper)
|
|
29
31
|
|
|
32
|
+
@classmethod
|
|
33
|
+
def _get_functions_with_auto_converted_exceptions(cls) -> Set[str]:
|
|
34
|
+
return {
|
|
35
|
+
"connect",
|
|
36
|
+
"disconnect",
|
|
37
|
+
"get_schemas",
|
|
38
|
+
"create_table",
|
|
39
|
+
"insert",
|
|
40
|
+
"get",
|
|
41
|
+
"update",
|
|
42
|
+
"delete"
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
@classmethod
|
|
31
46
|
def __init_subclass__(cls) -> None:
|
|
32
47
|
"""Initialize subclass by wrapping all public methods with exception handling"""
|
|
33
48
|
for name, method in cls.__dict__.items():
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
not
|
|
49
|
+
if (
|
|
50
|
+
callable(method) and
|
|
51
|
+
not name.startswith('_') and
|
|
52
|
+
not isinstance(method, (classmethod, staticmethod))
|
|
53
|
+
and name in cls._get_functions_with_auto_converted_exceptions()
|
|
54
|
+
):
|
|
37
55
|
setattr(cls, name, cls._wrap_db_exceptions(method))
|
|
38
56
|
|
|
39
57
|
@classmethod
|
|
@@ -48,19 +66,19 @@ class Database(ABC):
|
|
|
48
66
|
Returns:
|
|
49
67
|
Exception: The converted exception
|
|
50
68
|
"""
|
|
51
|
-
return DBException(f"Database error: {str(e)}"
|
|
69
|
+
return DBException(f"Database error: {str(e)}")
|
|
52
70
|
|
|
53
|
-
def
|
|
71
|
+
async def __aenter__(self) -> 'Database':
|
|
54
72
|
"""
|
|
55
73
|
Context manager entry point. Connects to the database.
|
|
56
74
|
|
|
57
75
|
Returns:
|
|
58
76
|
Database: The database instance for use in the context
|
|
59
77
|
"""
|
|
60
|
-
self.connect()
|
|
78
|
+
await self.connect()
|
|
61
79
|
return self
|
|
62
80
|
|
|
63
|
-
def
|
|
81
|
+
async def __aexit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> None:
|
|
64
82
|
"""
|
|
65
83
|
Context manager exit point. Disconnects from the database.
|
|
66
84
|
|
|
@@ -69,18 +87,22 @@ class Database(ABC):
|
|
|
69
87
|
exc_val: The exception instance that was raised, if any
|
|
70
88
|
exc_tb: The traceback for the exception, if any
|
|
71
89
|
"""
|
|
72
|
-
self.disconnect()
|
|
90
|
+
await self.disconnect()
|
|
73
91
|
|
|
74
92
|
@abstractmethod
|
|
75
|
-
def connect(self) -> None:
|
|
93
|
+
async def connect(self) -> None:
|
|
76
94
|
"""Establish connection to the database"""
|
|
77
95
|
|
|
78
96
|
@abstractmethod
|
|
79
|
-
def disconnect(self) -> None:
|
|
97
|
+
async def disconnect(self) -> None:
|
|
80
98
|
"""Close the database connection"""
|
|
81
99
|
|
|
82
100
|
@abstractmethod
|
|
83
|
-
def
|
|
101
|
+
def is_connected(self) -> bool:
|
|
102
|
+
"""Check if the database connection is open"""
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
async def get_schemas(self) -> Dict[str, TableSchema]:
|
|
84
106
|
"""
|
|
85
107
|
Get the complete database schema
|
|
86
108
|
|
|
@@ -89,7 +111,7 @@ class Database(ABC):
|
|
|
89
111
|
"""
|
|
90
112
|
|
|
91
113
|
@abstractmethod
|
|
92
|
-
def create_table(self, schema: TableSchema) -> None:
|
|
114
|
+
async def create_table(self, schema: TableSchema) -> None:
|
|
93
115
|
"""
|
|
94
116
|
Create a new table in the database
|
|
95
117
|
|
|
@@ -98,7 +120,7 @@ class Database(ABC):
|
|
|
98
120
|
"""
|
|
99
121
|
|
|
100
122
|
@abstractmethod
|
|
101
|
-
def insert(self, table: str, data: Dict[str, Any]) -> Any:
|
|
123
|
+
async def insert(self, table: str, data: Dict[str, Any]) -> Any:
|
|
102
124
|
"""
|
|
103
125
|
Insert a record into the specified table
|
|
104
126
|
|
|
@@ -111,7 +133,7 @@ class Database(ABC):
|
|
|
111
133
|
"""
|
|
112
134
|
|
|
113
135
|
@abstractmethod
|
|
114
|
-
def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
|
|
136
|
+
async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
|
|
115
137
|
"""
|
|
116
138
|
Get records from the database
|
|
117
139
|
|
|
@@ -123,7 +145,7 @@ class Database(ABC):
|
|
|
123
145
|
"""
|
|
124
146
|
|
|
125
147
|
@abstractmethod
|
|
126
|
-
def update(self, query: UpdateQuery) -> int:
|
|
148
|
+
async def update(self, query: UpdateQuery) -> int:
|
|
127
149
|
"""
|
|
128
150
|
Update records in the database
|
|
129
151
|
|
|
@@ -135,7 +157,7 @@ class Database(ABC):
|
|
|
135
157
|
"""
|
|
136
158
|
|
|
137
159
|
@abstractmethod
|
|
138
|
-
def delete(self, query: DeleteQuery) -> int:
|
|
160
|
+
async def delete(self, query: DeleteQuery) -> int:
|
|
139
161
|
"""
|
|
140
162
|
Delete records from the database
|
|
141
163
|
|
{danielutils-1.0.45 → danielutils-1.0.50}/danielutils/abstractions/db/database_exceptions.py
RENAMED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class DBException(Exception):
|
|
2
5
|
"""Base class for all database exceptions"""
|
|
3
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
|
+
|
|
4
11
|
|
|
5
12
|
class DBConnectionError(DBException):
|
|
6
13
|
"""Raised when there is an error connecting to the database"""
|
|
@@ -3,6 +3,13 @@ from typing import Literal, Dict, Any, Optional, Tuple
|
|
|
3
3
|
from .database import Database
|
|
4
4
|
from .implementations import InMemoryDatabase, SQLiteDatabase, PersistentInMemoryDatabase, RedisDatabase
|
|
5
5
|
|
|
6
|
+
MAPPING = {
|
|
7
|
+
"sqlite": SQLiteDatabase,
|
|
8
|
+
"memory": InMemoryDatabase,
|
|
9
|
+
"persistent_memory": PersistentInMemoryDatabase,
|
|
10
|
+
"redis": RedisDatabase
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
|
|
7
14
|
class DatabaseFactory(ABC):
|
|
8
15
|
"""Factory class for creating database instances"""
|
|
@@ -12,8 +19,7 @@ class DatabaseFactory(ABC):
|
|
|
12
19
|
@classmethod
|
|
13
20
|
def get_database(
|
|
14
21
|
cls,
|
|
15
|
-
db_type: Literal["sqlite", "memory",
|
|
16
|
-
"persistent_memory", "redis"] = "persistent_memory",
|
|
22
|
+
db_type: Literal["sqlite", "memory", "persistent_memory", "redis"] = "persistent_memory",
|
|
17
23
|
db_args: Optional[Tuple[Any, ...]] = None,
|
|
18
24
|
db_kwargs: Optional[Dict[str, Any]] = None
|
|
19
25
|
) -> Database:
|
|
@@ -29,19 +35,13 @@ class DatabaseFactory(ABC):
|
|
|
29
35
|
Database: Database instance
|
|
30
36
|
"""
|
|
31
37
|
if db_type not in cls._instances:
|
|
32
|
-
|
|
33
|
-
"sqlite": SQLiteDatabase,
|
|
34
|
-
"memory": InMemoryDatabase,
|
|
35
|
-
"persistent_memory": PersistentInMemoryDatabase,
|
|
36
|
-
"redis": RedisDatabase
|
|
37
|
-
}
|
|
38
|
-
if db_type not in mapping:
|
|
38
|
+
if db_type not in MAPPING:
|
|
39
39
|
raise ValueError(f"Unsupported database type: '{db_type}'")
|
|
40
40
|
|
|
41
41
|
# Convert None to empty tuple/dict for database initialization
|
|
42
42
|
args = db_args or ()
|
|
43
43
|
kwargs = db_kwargs or {}
|
|
44
|
-
cls._instances[db_type] =
|
|
44
|
+
cls._instances[db_type] = MAPPING[db_type](*args, **kwargs)
|
|
45
45
|
return cls._instances[db_type]
|
|
46
46
|
|
|
47
47
|
@classmethod
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Dict, Optional, Type, Any, Sequence
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from sqlalchemy import inspect, Column
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
8
|
+
except ImportError:
|
|
9
|
+
from ...mock_ import MockImportObject
|
|
10
|
+
|
|
11
|
+
inspect = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
12
|
+
Column = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
13
|
+
DeclarativeBase = type("DeclarativeBase", (object,), {}) # type:ignore
|
|
14
|
+
|
|
15
|
+
from .database import Database
|
|
16
|
+
from .database_definitions import ColumnType, TableSchema, TableColumn as ColumnSchema, TableIndex as IndexSchema
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
TYPE_MAPPING = {
|
|
21
|
+
'INTEGER': ColumnType.INTEGER,
|
|
22
|
+
'BIGINT': ColumnType.INTEGER,
|
|
23
|
+
'FLOAT': ColumnType.FLOAT,
|
|
24
|
+
'NUMERIC': ColumnType.FLOAT,
|
|
25
|
+
'DECIMAL': ColumnType.FLOAT,
|
|
26
|
+
'BOOLEAN': ColumnType.BOOLEAN,
|
|
27
|
+
'TEXT': ColumnType.TEXT,
|
|
28
|
+
'VARCHAR': ColumnType.TEXT,
|
|
29
|
+
'CHAR': ColumnType.TEXT,
|
|
30
|
+
'UUID': ColumnType.TEXT,
|
|
31
|
+
'DATETIME': ColumnType.DATETIME,
|
|
32
|
+
'DATE': ColumnType.DATE,
|
|
33
|
+
'TIME': ColumnType.TIME,
|
|
34
|
+
'JSON': ColumnType.JSON,
|
|
35
|
+
'BLOB': ColumnType.BLOB,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def validate_schema(db: Database, table_name: str, expected_schema: TableSchema) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
Validate that the existing table schema matches the expected schema.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
db: Database instance
|
|
45
|
+
table_name: Name of the table to validate
|
|
46
|
+
expected_schema: Expected table schema
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
bool: True if schema matches, False otherwise
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
# Get existing schema
|
|
53
|
+
existing_schemas = await db.get_schemas()
|
|
54
|
+
if table_name not in existing_schemas:
|
|
55
|
+
logger.warning(f"Table '{table_name}' does not exist")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
existing_schema = existing_schemas[table_name]
|
|
59
|
+
|
|
60
|
+
# Compare columns
|
|
61
|
+
if len(existing_schema.columns) != len(expected_schema.columns):
|
|
62
|
+
logger.warning(
|
|
63
|
+
f"Column count mismatch in '{table_name}': "
|
|
64
|
+
f"expected {len(expected_schema.columns)}, "
|
|
65
|
+
f"got {len(existing_schema.columns)}"
|
|
66
|
+
)
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Compare each column
|
|
70
|
+
for expected_col, existing_col in zip(expected_schema.columns, existing_schema.columns
|
|
71
|
+
):
|
|
72
|
+
if (
|
|
73
|
+
expected_col.name != existing_col.name or
|
|
74
|
+
expected_col.type != existing_col.type or
|
|
75
|
+
expected_col.primary_key != existing_col.primary_key or
|
|
76
|
+
expected_col.nullable != existing_col.nullable or
|
|
77
|
+
expected_col.unique != existing_col.unique or
|
|
78
|
+
expected_col.foreign_key != existing_col.foreign_key
|
|
79
|
+
):
|
|
80
|
+
logger.warning(
|
|
81
|
+
f"Column mismatch in '{table_name}': expected {expected_col}, got {existing_col}"
|
|
82
|
+
)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# Compare indexes
|
|
86
|
+
if len(existing_schema.indexes) != len(expected_schema.indexes):
|
|
87
|
+
logger.warning(
|
|
88
|
+
f"Index count mismatch in '{table_name}': "
|
|
89
|
+
f"expected {len(expected_schema.indexes)}, "
|
|
90
|
+
f"got {len(existing_schema.indexes)}"
|
|
91
|
+
)
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Compare each index
|
|
95
|
+
for expected_idx, existing_idx in zip(expected_schema.indexes, existing_schema.indexes):
|
|
96
|
+
if (
|
|
97
|
+
expected_idx.name != existing_idx.name or
|
|
98
|
+
expected_idx.columns != existing_idx.columns or
|
|
99
|
+
expected_idx.unique != existing_idx.unique
|
|
100
|
+
):
|
|
101
|
+
logger.warning(f"Index mismatch in '{table_name}': expected {expected_idx}, got {existing_idx}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"Error validating schema for '{table_name}': {str(e)}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_column_type(column: Column) -> ColumnType:
|
|
112
|
+
"""Convert SQLAlchemy column type to our ColumnType enum"""
|
|
113
|
+
# Ensure autoincrement for integer primary key named 'id'
|
|
114
|
+
if (
|
|
115
|
+
column.name == 'id'
|
|
116
|
+
and getattr(column, 'primary_key', False)
|
|
117
|
+
and ('INTEGER' in str(column.type).upper() or 'BIGINT' in str(column.type).upper())
|
|
118
|
+
):
|
|
119
|
+
return ColumnType.AUTOINCREMENT
|
|
120
|
+
# Check for autoincrement attribute
|
|
121
|
+
if getattr(column, 'autoincrement', False) == True:
|
|
122
|
+
return ColumnType.AUTOINCREMENT
|
|
123
|
+
# Then check the column type
|
|
124
|
+
type_name = str(column.type).upper()
|
|
125
|
+
for sql_type, our_type in TYPE_MAPPING.items():
|
|
126
|
+
if sql_type in type_name:
|
|
127
|
+
return our_type
|
|
128
|
+
return ColumnType.TEXT # Default to TEXT if type not found
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_foreign_key(column: Column) -> Optional[Dict[str, str]]:
|
|
132
|
+
"""Extract foreign key information from a column"""
|
|
133
|
+
for fk in column.foreign_keys:
|
|
134
|
+
return {
|
|
135
|
+
"table": fk.column.table.name,
|
|
136
|
+
"column": fk.column.name
|
|
137
|
+
}
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_default_value(column: Column) -> Optional[Any]:
|
|
142
|
+
"""Extract default value from a column"""
|
|
143
|
+
if column.default is not None:
|
|
144
|
+
if hasattr(column.default, 'arg'):
|
|
145
|
+
return column.default.arg
|
|
146
|
+
return column.default
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def model_to_schema(model_class: Type[DeclarativeBase]) -> TableSchema:
|
|
151
|
+
"""Convert a SQLAlchemy model to our TableSchema"""
|
|
152
|
+
mapper = inspect(model_class)
|
|
153
|
+
table_name = mapper.local_table.name # type: ignore
|
|
154
|
+
|
|
155
|
+
# Convert columns
|
|
156
|
+
columns = []
|
|
157
|
+
for column in mapper.columns:
|
|
158
|
+
# Get unique constraint from column
|
|
159
|
+
is_unique = False
|
|
160
|
+
if column.unique:
|
|
161
|
+
is_unique = True
|
|
162
|
+
elif column.index and column.index.unique: # type: ignore
|
|
163
|
+
is_unique = True
|
|
164
|
+
|
|
165
|
+
col_schema = ColumnSchema(
|
|
166
|
+
name=column.name,
|
|
167
|
+
type=get_column_type(column),
|
|
168
|
+
primary_key=column.primary_key,
|
|
169
|
+
nullable=column.nullable, # type: ignore
|
|
170
|
+
unique=is_unique,
|
|
171
|
+
foreign_key=get_foreign_key(column),
|
|
172
|
+
default=get_default_value(column)
|
|
173
|
+
)
|
|
174
|
+
columns.append(col_schema)
|
|
175
|
+
|
|
176
|
+
# Convert indexes
|
|
177
|
+
indexes = []
|
|
178
|
+
for index in mapper.local_table.indexes: # type: ignore
|
|
179
|
+
# Skip indexes that are already handled by unique constraints
|
|
180
|
+
if len(index.columns) == 1 and index.columns[0].unique:
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
idx_schema = IndexSchema(
|
|
184
|
+
name=index.name,
|
|
185
|
+
columns=[col.name for col in index.columns],
|
|
186
|
+
unique=index.unique
|
|
187
|
+
)
|
|
188
|
+
indexes.append(idx_schema)
|
|
189
|
+
|
|
190
|
+
return TableSchema(
|
|
191
|
+
name=table_name,
|
|
192
|
+
columns=columns,
|
|
193
|
+
indexes=indexes
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class DatabaseInitializer(ABC):
|
|
198
|
+
# Map SQLAlchemy types to our ColumnType enum
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
@abstractmethod
|
|
202
|
+
def _get_models(cls) -> Sequence[Type[DeclarativeBase]]:
|
|
203
|
+
"""Get all pydantic models from our models"""
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def _get_table_schemas(cls) -> Dict[str, TableSchema]:
|
|
207
|
+
"""Get all table schemas from our models"""
|
|
208
|
+
models = cls._get_models()
|
|
209
|
+
res = {}
|
|
210
|
+
for model in models:
|
|
211
|
+
try:
|
|
212
|
+
res[model.__tablename__] = model_to_schema(model)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
raise Exception(f"Failed parsing '{model.__tablename__}'") from e
|
|
215
|
+
return res
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
async def init_db(cls, db: Database) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Initialize the database by creating or validating all required tables.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
db: Database instance to initialize
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
await db.connect()
|
|
227
|
+
|
|
228
|
+
# Get existing schemas
|
|
229
|
+
existing_schemas = await db.get_schemas()
|
|
230
|
+
|
|
231
|
+
# Get table schemas from models
|
|
232
|
+
table_schemas = cls._get_table_schemas()
|
|
233
|
+
|
|
234
|
+
# Create or validate each table
|
|
235
|
+
for table_name, expected_schema in table_schemas.items():
|
|
236
|
+
if table_name not in existing_schemas:
|
|
237
|
+
logger.info(f"Creating table '{table_name}'")
|
|
238
|
+
await db.create_table(expected_schema)
|
|
239
|
+
else:
|
|
240
|
+
logger.info(f"Validating table '{table_name}'")
|
|
241
|
+
if not await validate_schema(db, table_name, expected_schema):
|
|
242
|
+
raise ValueError(
|
|
243
|
+
f"Schema validation failed for table '{table_name}'. "
|
|
244
|
+
"Please check the logs for details."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
logger.info("Database initialization completed successfully")
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logger.error(f"Error initializing database: {str(e)}")
|
|
251
|
+
raise
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
__all__ = [
|
|
255
|
+
"DatabaseInitializer"
|
|
256
|
+
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Dict, Any, List
|
|
2
|
+
from typing import Dict, Any, List, Type, Union, Sequence
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
5
|
try:
|
|
@@ -18,16 +18,19 @@ from ..database_exceptions import DBValidationError, DBQueryError, DBConnectionE
|
|
|
18
18
|
class InMemoryDatabase(Database):
|
|
19
19
|
"""In-memory database implementation using dictionaries"""
|
|
20
20
|
|
|
21
|
+
def is_connected(self) -> bool:
|
|
22
|
+
return self._connected
|
|
23
|
+
|
|
21
24
|
@classmethod
|
|
22
25
|
def _default_class_exception_conversion(cls, e: Exception) -> Exception:
|
|
23
26
|
"""
|
|
24
27
|
Convert in-memory database specific exceptions to standard database exceptions.
|
|
25
28
|
"""
|
|
26
29
|
if isinstance(e, ValueError):
|
|
27
|
-
return DBValidationError(f"Validation error: {str(e)}"
|
|
30
|
+
return DBValidationError(f"Validation error: {str(e)}")
|
|
28
31
|
if isinstance(e, RuntimeError):
|
|
29
|
-
return DBQueryError(f"Query error: {str(e)}"
|
|
30
|
-
return DBException(f"Database error: {str(e)}"
|
|
32
|
+
return DBQueryError(f"Query error: {str(e)}")
|
|
33
|
+
return DBException(f"Database error: {str(e)}")
|
|
31
34
|
|
|
32
35
|
def __init__(self, *args, **kwargs) -> None:
|
|
33
36
|
try:
|
|
@@ -42,23 +45,19 @@ class InMemoryDatabase(Database):
|
|
|
42
45
|
self._connected = False
|
|
43
46
|
self.logger = logging.getLogger(__name__)
|
|
44
47
|
|
|
45
|
-
def connect(self) -> None:
|
|
48
|
+
async def connect(self) -> None:
|
|
46
49
|
"""Connect to the database (no-op for in-memory)"""
|
|
47
50
|
self._connected = True
|
|
48
51
|
self.logger.info("Connected to in-memory database")
|
|
49
52
|
|
|
50
|
-
def disconnect(self) -> None:
|
|
53
|
+
async def disconnect(self) -> None:
|
|
51
54
|
"""Disconnect from the database (no-op for in-memory)"""
|
|
52
55
|
self._connected = False
|
|
53
56
|
self.logger.info("Disconnected from in-memory database")
|
|
54
57
|
|
|
55
|
-
def
|
|
56
|
-
"""Check if connected to the database"""
|
|
57
|
-
return self._connected
|
|
58
|
-
|
|
59
|
-
def create_table(self, schema: TableSchema) -> None:
|
|
58
|
+
async def create_table(self, schema: TableSchema) -> None:
|
|
60
59
|
"""Create a new table with the given schema"""
|
|
61
|
-
if not self.
|
|
60
|
+
if not self.is_connected():
|
|
62
61
|
raise RuntimeError("Not connected to database")
|
|
63
62
|
|
|
64
63
|
if schema.name in self.tables:
|
|
@@ -98,16 +97,16 @@ class InMemoryDatabase(Database):
|
|
|
98
97
|
f"Column '{column}' is not an auto-increment column")
|
|
99
98
|
self.auto_increment_counters[table][column] -= 1
|
|
100
99
|
|
|
101
|
-
def get_schemas(self) -> Dict[str, TableSchema]:
|
|
100
|
+
async def get_schemas(self) -> Dict[str, TableSchema]:
|
|
102
101
|
"""Get all table schemas"""
|
|
103
|
-
if not self.
|
|
102
|
+
if not self.is_connected():
|
|
104
103
|
raise RuntimeError("Not connected to database")
|
|
105
104
|
|
|
106
105
|
return {schema.name: schema for schema in self.schemas.values()}
|
|
107
106
|
|
|
108
|
-
def insert(self, table: str, data: Dict[str, Any]) -> Any:
|
|
107
|
+
async def insert(self, table: str, data: Dict[str, Any]) -> Any:
|
|
109
108
|
"""Insert a new row into the table"""
|
|
110
|
-
if not self.
|
|
109
|
+
if not self.is_connected():
|
|
111
110
|
raise RuntimeError("Not connected to database")
|
|
112
111
|
|
|
113
112
|
if table not in self.tables:
|
|
@@ -165,9 +164,9 @@ class InMemoryDatabase(Database):
|
|
|
165
164
|
f"Inserted row into '{table}' with ID '{row_data['id']}'")
|
|
166
165
|
return row_data['id']
|
|
167
166
|
|
|
168
|
-
def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
|
|
167
|
+
async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
|
|
169
168
|
"""Get rows from the table matching the query"""
|
|
170
|
-
if not self.
|
|
169
|
+
if not self.is_connected():
|
|
171
170
|
raise DBConnectionError("Not connected to database")
|
|
172
171
|
|
|
173
172
|
if query.table not in self.tables:
|
|
@@ -203,9 +202,9 @@ class InMemoryDatabase(Database):
|
|
|
203
202
|
|
|
204
203
|
return rows
|
|
205
204
|
|
|
206
|
-
def update(self, query: UpdateQuery) -> int:
|
|
205
|
+
async def update(self, query: UpdateQuery) -> int:
|
|
207
206
|
"""Update rows in the table matching the query"""
|
|
208
|
-
if not self.
|
|
207
|
+
if not self.is_connected():
|
|
209
208
|
raise RuntimeError("Not connected to database")
|
|
210
209
|
|
|
211
210
|
if query.table not in self.tables:
|
|
@@ -236,9 +235,9 @@ class InMemoryDatabase(Database):
|
|
|
236
235
|
self.logger.info(f"Updated '{updated_count}' rows in '{query.table}'")
|
|
237
236
|
return updated_count
|
|
238
237
|
|
|
239
|
-
def delete(self, query: DeleteQuery) -> int:
|
|
238
|
+
async def delete(self, query: DeleteQuery) -> int:
|
|
240
239
|
"""Delete rows from the table matching the query"""
|
|
241
|
-
if not self.
|
|
240
|
+
if not self.is_connected():
|
|
242
241
|
raise RuntimeError("Not connected to database")
|
|
243
242
|
|
|
244
243
|
if query.table not in self.tables:
|
|
@@ -263,7 +262,7 @@ class InMemoryDatabase(Database):
|
|
|
263
262
|
if value is None:
|
|
264
263
|
return column.nullable
|
|
265
264
|
|
|
266
|
-
type_map = {
|
|
265
|
+
type_map: Dict[str, Union[Type, Sequence[Type]]] = {
|
|
267
266
|
"INTEGER": int,
|
|
268
267
|
"BIGINT": int,
|
|
269
268
|
"FLOAT": float,
|
|
@@ -351,9 +350,9 @@ class InMemoryDatabase(Database):
|
|
|
351
350
|
elif condition.operator == Operator.CONTAINS_CS:
|
|
352
351
|
return str(condition.value) in str(value)
|
|
353
352
|
elif condition.operator == Operator.IN:
|
|
354
|
-
return value in condition.values
|
|
353
|
+
return value in condition.values # type: ignore
|
|
355
354
|
elif condition.operator == Operator.NOT_IN:
|
|
356
|
-
return value not in condition.values
|
|
355
|
+
return value not in condition.values # type: ignore
|
|
357
356
|
elif condition.operator == Operator.IS_NULL:
|
|
358
357
|
return value is None
|
|
359
358
|
elif condition.operator == Operator.IS_NOT_NULL:
|