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
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Dict, Any, List, Optional
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from sqlalchemy import create_engine, MetaData, Table, Column, inspect, text, select, update, delete, Engine
|
|
6
|
+
from sqlalchemy.orm import sessionmaker, Session
|
|
7
|
+
from sqlalchemy.sql import or_
|
|
8
|
+
from sqlalchemy.types import Integer, String, DateTime, Boolean, Float, JSON, LargeBinary
|
|
9
|
+
except ImportError:
|
|
10
|
+
from ....mock_ import MockImportObject
|
|
11
|
+
|
|
12
|
+
create_engine = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
13
|
+
MetaData = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
14
|
+
Table = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
15
|
+
Column = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
16
|
+
inspect = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
17
|
+
text = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
18
|
+
select = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
19
|
+
update = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
20
|
+
delete = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
21
|
+
Engine = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
22
|
+
sessionmaker = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
23
|
+
Session = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
24
|
+
or_ = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
25
|
+
Integer = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
26
|
+
String = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
27
|
+
DateTime = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
28
|
+
Boolean = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
29
|
+
Float = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
30
|
+
JSON = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
31
|
+
LargeBinary = MockImportObject("'sqlalchemy' is not installed") # type:ignore
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
from ..database import Database
|
|
35
|
+
from ..database_definitions import (
|
|
36
|
+
TableColumn, TableSchema, TableIndex, TableForeignKey,
|
|
37
|
+
SelectQuery, UpdateQuery, DeleteQuery, Operator, OrderDirection,
|
|
38
|
+
ColumnType
|
|
39
|
+
)
|
|
40
|
+
from ..database_exceptions import DBException, DBValidationError, DBQueryError, DBConnectionError
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SQLiteDatabase(Database):
|
|
44
|
+
"""SQLite implementation of the Database abstract class using SQLAlchemy"""
|
|
45
|
+
|
|
46
|
+
def is_connected(self) -> bool:
|
|
47
|
+
return self._connected
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def _default_class_exception_conversion(cls, e: Exception) -> Exception:
|
|
51
|
+
"""
|
|
52
|
+
Convert SQLite/SQLAlchemy specific exceptions to standard database exceptions.
|
|
53
|
+
"""
|
|
54
|
+
if 'sqlalchemy' in str(type(e).__module__):
|
|
55
|
+
if 'OperationalError' in str(type(e)):
|
|
56
|
+
return DBConnectionError(f"Database connection error: {str(e)}")
|
|
57
|
+
elif 'IntegrityError' in str(type(e)):
|
|
58
|
+
return DBValidationError(f"Data validation error: {str(e)}")
|
|
59
|
+
elif 'ProgrammingError' in str(type(e)):
|
|
60
|
+
return DBQueryError(f"Query error: {str(e)}")
|
|
61
|
+
return DBException(f"Database error: {str(e)}")
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
url: Optional[str] = None,
|
|
66
|
+
db_name: Optional[str] = None,
|
|
67
|
+
**engine_kwargs
|
|
68
|
+
):
|
|
69
|
+
"""
|
|
70
|
+
Initialize SQLite database connection
|
|
71
|
+
Args:
|
|
72
|
+
url (str, optional): SQLAlchemy database URL. If not provided, will use db_name or environment/config.
|
|
73
|
+
db_name (str, optional): Database file name. Used only if url is not provided.
|
|
74
|
+
**engine_kwargs: Additional keyword arguments for SQLAlchemy create_engine.
|
|
75
|
+
"""
|
|
76
|
+
self.engine: Optional[Engine] = None
|
|
77
|
+
self.session_local = None
|
|
78
|
+
self.metadata = MetaData()
|
|
79
|
+
self._connected: bool = False
|
|
80
|
+
# Determine the connection URL
|
|
81
|
+
if url:
|
|
82
|
+
self.url = url
|
|
83
|
+
elif db_name:
|
|
84
|
+
self.url = f"sqlite:///{db_name}"
|
|
85
|
+
else:
|
|
86
|
+
# Try environment variable, then fallback
|
|
87
|
+
self.url = os.environ.get(
|
|
88
|
+
"SQLALCHEMY_DATABASE_URI", "sqlite:///:memory:")
|
|
89
|
+
self.engine_kwargs = engine_kwargs
|
|
90
|
+
|
|
91
|
+
async def connect(self) -> None:
|
|
92
|
+
"""Establish connection to SQLite database"""
|
|
93
|
+
try:
|
|
94
|
+
self.engine = create_engine(self.url, pool_pre_ping=True, **self.engine_kwargs)
|
|
95
|
+
self.session_local = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
|
|
96
|
+
self.metadata.reflect(bind=self.engine)
|
|
97
|
+
self._connected = True
|
|
98
|
+
logging.info("Connected to SQLite database at '%s'", self.url)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logging.error(
|
|
101
|
+
"Error connecting to SQLite database at '%s': %s", self.url, e)
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
async def disconnect(self) -> None:
|
|
105
|
+
"""Close the SQLite database connection"""
|
|
106
|
+
if self.engine:
|
|
107
|
+
self.engine.dispose()
|
|
108
|
+
logging.info("Disconnected from SQLite database")
|
|
109
|
+
self._connected = False
|
|
110
|
+
|
|
111
|
+
def _get_column_type(self, sqlalchemy_type: Any) -> ColumnType:
|
|
112
|
+
"""Convert SQLAlchemy type to ColumnType enum"""
|
|
113
|
+
type_str = str(sqlalchemy_type).upper()
|
|
114
|
+
|
|
115
|
+
# Map SQLAlchemy types to ColumnType
|
|
116
|
+
type_map = {
|
|
117
|
+
'INTEGER': ColumnType.INTEGER,
|
|
118
|
+
'BIGINT': ColumnType.BIGINT,
|
|
119
|
+
'SMALLINT': ColumnType.SMALLINT,
|
|
120
|
+
'FLOAT': ColumnType.FLOAT,
|
|
121
|
+
'DOUBLE': ColumnType.DOUBLE,
|
|
122
|
+
'DECIMAL': ColumnType.DECIMAL,
|
|
123
|
+
'BOOLEAN': ColumnType.BOOLEAN,
|
|
124
|
+
'TEXT': ColumnType.TEXT,
|
|
125
|
+
'VARCHAR': ColumnType.VARCHAR,
|
|
126
|
+
'CHAR': ColumnType.CHAR,
|
|
127
|
+
'DATE': ColumnType.DATE,
|
|
128
|
+
'TIME': ColumnType.TIME,
|
|
129
|
+
'DATETIME': ColumnType.DATETIME,
|
|
130
|
+
'TIMESTAMP': ColumnType.TIMESTAMP,
|
|
131
|
+
'BLOB': ColumnType.BLOB,
|
|
132
|
+
'JSON': ColumnType.JSON,
|
|
133
|
+
'UUID': ColumnType.UUID
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Handle special cases
|
|
137
|
+
if 'AUTOINCREMENT' in type_str:
|
|
138
|
+
return ColumnType.AUTOINCREMENT
|
|
139
|
+
if 'VARCHAR' in type_str:
|
|
140
|
+
return ColumnType.VARCHAR
|
|
141
|
+
if 'CHAR' in type_str:
|
|
142
|
+
return ColumnType.CHAR
|
|
143
|
+
|
|
144
|
+
# Try to find a match in the type map
|
|
145
|
+
for sql_type, column_type in type_map.items():
|
|
146
|
+
if sql_type in type_str:
|
|
147
|
+
return column_type
|
|
148
|
+
|
|
149
|
+
raise ValueError(f"Unsupported SQLAlchemy type: {type_str}")
|
|
150
|
+
|
|
151
|
+
async def get_schemas(self) -> Dict[str, TableSchema]:
|
|
152
|
+
"""
|
|
153
|
+
Get the complete database schema
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dict[str, TableSchema]: Dictionary mapping table names to their schemas
|
|
157
|
+
"""
|
|
158
|
+
if not self.engine:
|
|
159
|
+
raise RuntimeError("Database not connected")
|
|
160
|
+
|
|
161
|
+
inspector = inspect(self.engine)
|
|
162
|
+
schemas = {}
|
|
163
|
+
|
|
164
|
+
for table_name in inspector.get_table_names():
|
|
165
|
+
# Get all indexes first to check for unique constraints
|
|
166
|
+
indexes = inspector.get_indexes(table_name)
|
|
167
|
+
unique_columns = set()
|
|
168
|
+
for idx in indexes:
|
|
169
|
+
if idx['unique'] and len(idx['column_names']) == 1:
|
|
170
|
+
unique_columns.add(idx['column_names'][0])
|
|
171
|
+
|
|
172
|
+
columns = []
|
|
173
|
+
for col in self.metadata.tables[table_name].columns:
|
|
174
|
+
# Convert SQLAlchemy type to ColumnType
|
|
175
|
+
try:
|
|
176
|
+
column_type = self._get_column_type(col.type)
|
|
177
|
+
except ValueError as e:
|
|
178
|
+
logging.warning(
|
|
179
|
+
"Could not map type for column %s in table %s: %s", col['name'], table_name, e)
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# Handle default values
|
|
183
|
+
default = col.default
|
|
184
|
+
if default is not None and hasattr(default, 'arg'):
|
|
185
|
+
default = default.arg
|
|
186
|
+
|
|
187
|
+
# Check if column is unique (either by constraint or index)
|
|
188
|
+
is_unique = getattr(col, "unique", False) or col.name in unique_columns
|
|
189
|
+
|
|
190
|
+
column = TableColumn(
|
|
191
|
+
name=col.name,
|
|
192
|
+
type=column_type,
|
|
193
|
+
nullable=col.nullable, # type: ignore
|
|
194
|
+
primary_key=col.primary_key,
|
|
195
|
+
unique=is_unique,
|
|
196
|
+
default=default
|
|
197
|
+
)
|
|
198
|
+
columns.append(column)
|
|
199
|
+
|
|
200
|
+
# Get indexes (excluding single-column unique indexes as they're handled above)
|
|
201
|
+
table_indexes = []
|
|
202
|
+
for idx in indexes:
|
|
203
|
+
if not (idx['unique'] and len(idx['column_names']) == 1):
|
|
204
|
+
table_indexes.append(TableIndex(
|
|
205
|
+
name=idx['name'], # type: ignore
|
|
206
|
+
columns=idx['column_names'], # type: ignore
|
|
207
|
+
unique=idx['unique']
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
# Get foreign keys
|
|
211
|
+
foreign_keys = []
|
|
212
|
+
for fk in inspector.get_foreign_keys(table_name):
|
|
213
|
+
foreign_keys.append(TableForeignKey(
|
|
214
|
+
name=fk.get('name', f"fk_{table_name}_{fk['constrained_columns'][0]}"), # type: ignore
|
|
215
|
+
columns=fk['constrained_columns'],
|
|
216
|
+
reference_table=fk['referred_table'],
|
|
217
|
+
reference_columns=fk['referred_columns']
|
|
218
|
+
))
|
|
219
|
+
|
|
220
|
+
schemas[table_name] = TableSchema(
|
|
221
|
+
name=table_name,
|
|
222
|
+
columns=columns,
|
|
223
|
+
indexes=table_indexes,
|
|
224
|
+
foreign_keys=foreign_keys
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return schemas
|
|
228
|
+
|
|
229
|
+
def _get_sqlalchemy_type(self, column_type: ColumnType) -> Any:
|
|
230
|
+
"""Convert ColumnType to SQLAlchemy type"""
|
|
231
|
+
type_map = {
|
|
232
|
+
ColumnType.INTEGER: Integer,
|
|
233
|
+
ColumnType.AUTOINCREMENT: Integer,
|
|
234
|
+
ColumnType.SMALLINT: Integer,
|
|
235
|
+
ColumnType.MEDIUMINT: Integer,
|
|
236
|
+
ColumnType.TINYINT: Integer,
|
|
237
|
+
ColumnType.BIGINT: Integer,
|
|
238
|
+
ColumnType.FLOAT: Float,
|
|
239
|
+
ColumnType.DOUBLE: Float,
|
|
240
|
+
ColumnType.DECIMAL: Float,
|
|
241
|
+
ColumnType.BOOLEAN: Boolean,
|
|
242
|
+
ColumnType.TEXT: String,
|
|
243
|
+
ColumnType.VARCHAR: String,
|
|
244
|
+
ColumnType.CHAR: String,
|
|
245
|
+
ColumnType.DATE: DateTime,
|
|
246
|
+
ColumnType.TIME: DateTime,
|
|
247
|
+
ColumnType.DATETIME: DateTime,
|
|
248
|
+
ColumnType.TIMESTAMP: DateTime,
|
|
249
|
+
ColumnType.BLOB: LargeBinary,
|
|
250
|
+
ColumnType.JSON: JSON,
|
|
251
|
+
ColumnType.UUID: String
|
|
252
|
+
}
|
|
253
|
+
return type_map.get(column_type)
|
|
254
|
+
|
|
255
|
+
async def create_table(self, schema: TableSchema) -> None:
|
|
256
|
+
"""
|
|
257
|
+
Create a new table in the SQLite database
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
schema (TableSchema): Schema definition for the table
|
|
261
|
+
"""
|
|
262
|
+
try:
|
|
263
|
+
columns = []
|
|
264
|
+
for column in schema.columns:
|
|
265
|
+
# Convert ColumnType to SQLAlchemy type
|
|
266
|
+
sql_type = self._get_sqlalchemy_type(column.type)
|
|
267
|
+
if sql_type is None:
|
|
268
|
+
raise ValueError(f"Unsupported column type: {column.type}")
|
|
269
|
+
|
|
270
|
+
# Create column with all properties
|
|
271
|
+
col = Column( # type: ignore
|
|
272
|
+
column.name,
|
|
273
|
+
sql_type,
|
|
274
|
+
nullable=column.nullable,
|
|
275
|
+
primary_key=column.primary_key,
|
|
276
|
+
unique=column.unique,
|
|
277
|
+
default=column.default
|
|
278
|
+
)
|
|
279
|
+
columns.append(col)
|
|
280
|
+
|
|
281
|
+
# Create table with all columns
|
|
282
|
+
table = Table(schema.name, self.metadata, *columns)
|
|
283
|
+
table.create(self.engine) # type: ignore
|
|
284
|
+
logging.info("Created table: '%s'", schema.name)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
logging.error("Error creating table '%s': %s", schema.name, e)
|
|
287
|
+
raise
|
|
288
|
+
|
|
289
|
+
def _get_session(self) -> Session:
|
|
290
|
+
"""Get a new database session"""
|
|
291
|
+
return self.session_local() # type: ignore
|
|
292
|
+
|
|
293
|
+
async def insert(self, table: str, data: Dict[str, Any]) -> int:
|
|
294
|
+
"""
|
|
295
|
+
Insert a record into the specified table
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
table (str): Name of the table
|
|
299
|
+
data (Dict[str, Any]): Dictionary containing column names and values
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
int: ID of the inserted record
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
with self._get_session() as session:
|
|
306
|
+
table_obj = self.metadata.tables[table]
|
|
307
|
+
stmt = table_obj.insert().values(**data)
|
|
308
|
+
result = session.execute(stmt)
|
|
309
|
+
session.commit()
|
|
310
|
+
return result.inserted_primary_key[0]
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logging.error("Error inserting into '%s': %s", table, e)
|
|
313
|
+
raise
|
|
314
|
+
|
|
315
|
+
async def get(self, query: SelectQuery) -> List[Dict[str, Any]]:
|
|
316
|
+
"""
|
|
317
|
+
Get records from the database
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
query (SelectQuery): Query definition containing table name, conditions, ordering, etc.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List[Dict[str, Any]]: List of dictionaries containing the selected records
|
|
324
|
+
"""
|
|
325
|
+
try:
|
|
326
|
+
with self._get_session() as session:
|
|
327
|
+
table_obj = self.metadata.tables[query.table]
|
|
328
|
+
|
|
329
|
+
# Start with base select
|
|
330
|
+
if query.columns:
|
|
331
|
+
stmt = select(*[table_obj.c[col] for col in query.columns])
|
|
332
|
+
else:
|
|
333
|
+
stmt = select(table_obj)
|
|
334
|
+
|
|
335
|
+
# Apply joins
|
|
336
|
+
if query.joins:
|
|
337
|
+
for join in query.joins:
|
|
338
|
+
join_table = self.metadata.tables[join.table]
|
|
339
|
+
join_conditions = []
|
|
340
|
+
for condition in join.conditions:
|
|
341
|
+
left_col = table_obj.c[condition.column]
|
|
342
|
+
right_col = join_table.c[condition.value] # type: ignore
|
|
343
|
+
join_conditions.append(left_col == right_col)
|
|
344
|
+
stmt = stmt.join(join_table, *join_conditions)
|
|
345
|
+
|
|
346
|
+
# Apply where clause
|
|
347
|
+
if query.where:
|
|
348
|
+
where_conditions = []
|
|
349
|
+
for condition in query.where.conditions:
|
|
350
|
+
col = table_obj.c[condition.column]
|
|
351
|
+
if condition.operator == Operator.EQ:
|
|
352
|
+
where_conditions.append(col == condition.value)
|
|
353
|
+
elif condition.operator == Operator.NEQ:
|
|
354
|
+
where_conditions.append(col != condition.value)
|
|
355
|
+
elif condition.operator == Operator.GT:
|
|
356
|
+
where_conditions.append(col > condition.value)
|
|
357
|
+
elif condition.operator == Operator.GTE:
|
|
358
|
+
where_conditions.append(col >= condition.value)
|
|
359
|
+
elif condition.operator == Operator.LT:
|
|
360
|
+
where_conditions.append(col < condition.value)
|
|
361
|
+
elif condition.operator == Operator.LTE:
|
|
362
|
+
where_conditions.append(col <= condition.value)
|
|
363
|
+
elif condition.operator == Operator.LIKE:
|
|
364
|
+
where_conditions.append(col.like(condition.value))
|
|
365
|
+
elif condition.operator == Operator.ILIKE:
|
|
366
|
+
where_conditions.append(col.ilike(condition.value))
|
|
367
|
+
elif condition.operator == Operator.IN:
|
|
368
|
+
where_conditions.append(col.in_(condition.values))
|
|
369
|
+
elif condition.operator == Operator.NOT_IN:
|
|
370
|
+
where_conditions.append(~col.in_(condition.values))
|
|
371
|
+
elif condition.operator == Operator.IS_NULL:
|
|
372
|
+
where_conditions.append(col.is_(None))
|
|
373
|
+
elif condition.operator == Operator.IS_NOT_NULL:
|
|
374
|
+
where_conditions.append(col.is_not(None))
|
|
375
|
+
|
|
376
|
+
if query.where.operator == "AND":
|
|
377
|
+
stmt = stmt.where(*where_conditions)
|
|
378
|
+
else: # OR
|
|
379
|
+
stmt = stmt.where(or_(*where_conditions))
|
|
380
|
+
|
|
381
|
+
# Apply order by
|
|
382
|
+
if query.order_by:
|
|
383
|
+
for order in query.order_by:
|
|
384
|
+
col = table_obj.c[order.column]
|
|
385
|
+
if order.direction == OrderDirection.DESC:
|
|
386
|
+
col = col.desc()
|
|
387
|
+
stmt = stmt.order_by(col)
|
|
388
|
+
|
|
389
|
+
# Apply group by
|
|
390
|
+
if query.group_by:
|
|
391
|
+
stmt = stmt.group_by(*[table_obj.c[col]
|
|
392
|
+
for col in query.group_by])
|
|
393
|
+
|
|
394
|
+
# Apply having
|
|
395
|
+
if query.having:
|
|
396
|
+
having_conditions = []
|
|
397
|
+
for condition in query.having.conditions:
|
|
398
|
+
col = table_obj.c[condition.column]
|
|
399
|
+
if condition.operator == Operator.EQ:
|
|
400
|
+
having_conditions.append(col == condition.value)
|
|
401
|
+
# ... (similar to where conditions)
|
|
402
|
+
if query.having.operator == "AND":
|
|
403
|
+
stmt = stmt.having(*having_conditions)
|
|
404
|
+
else: # OR
|
|
405
|
+
stmt = stmt.having(or_(*having_conditions))
|
|
406
|
+
|
|
407
|
+
# Apply limit and offset
|
|
408
|
+
if query.limit is not None:
|
|
409
|
+
stmt = stmt.limit(query.limit)
|
|
410
|
+
if query.offset is not None:
|
|
411
|
+
stmt = stmt.offset(query.offset)
|
|
412
|
+
|
|
413
|
+
result = session.execute(stmt)
|
|
414
|
+
# Convert result rows to dictionaries
|
|
415
|
+
return [dict(zip(result.keys(), row)) for row in result]
|
|
416
|
+
except Exception as e:
|
|
417
|
+
logging.error("Error selecting from '%s': %s", query.table, e)
|
|
418
|
+
raise
|
|
419
|
+
|
|
420
|
+
async def update(self, query: UpdateQuery) -> int:
|
|
421
|
+
"""
|
|
422
|
+
Update records in the database
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
query (UpdateQuery): Query definition containing table name, conditions, and data to update
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
int: Number of affected rows
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
with self._get_session() as session:
|
|
432
|
+
table_obj = self.metadata.tables[query.table]
|
|
433
|
+
stmt = update(table_obj).values(**query.data)
|
|
434
|
+
|
|
435
|
+
if query.where:
|
|
436
|
+
where_conditions = []
|
|
437
|
+
for condition in query.where.conditions:
|
|
438
|
+
col = table_obj.c[condition.column]
|
|
439
|
+
if condition.operator == Operator.EQ:
|
|
440
|
+
where_conditions.append(col == condition.value)
|
|
441
|
+
# ... (similar to get method where conditions)
|
|
442
|
+
if query.where.operator == "AND":
|
|
443
|
+
stmt = stmt.where(*where_conditions)
|
|
444
|
+
else: # OR
|
|
445
|
+
stmt = stmt.where(or_(*where_conditions))
|
|
446
|
+
|
|
447
|
+
result = session.execute(stmt)
|
|
448
|
+
session.commit()
|
|
449
|
+
return result.rowcount
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logging.error("Error updating '%s': %s", query.table, e)
|
|
452
|
+
raise
|
|
453
|
+
|
|
454
|
+
async def delete(self, query: DeleteQuery) -> int:
|
|
455
|
+
"""
|
|
456
|
+
Delete records from the database
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
query (DeleteQuery): Query definition containing table name and conditions
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
int: Number of affected rows
|
|
463
|
+
"""
|
|
464
|
+
try:
|
|
465
|
+
with self._get_session() as session:
|
|
466
|
+
table_obj = self.metadata.tables[query.table]
|
|
467
|
+
stmt = delete(table_obj)
|
|
468
|
+
|
|
469
|
+
if query.where:
|
|
470
|
+
where_conditions = []
|
|
471
|
+
for condition in query.where.conditions:
|
|
472
|
+
col = table_obj.c[condition.column]
|
|
473
|
+
if condition.operator == Operator.EQ:
|
|
474
|
+
where_conditions.append(col == condition.value)
|
|
475
|
+
# ... (similar to get method where conditions)
|
|
476
|
+
if query.where.operator == "AND":
|
|
477
|
+
stmt = stmt.where(*where_conditions)
|
|
478
|
+
else: # OR
|
|
479
|
+
stmt = stmt.where(or_(*where_conditions))
|
|
480
|
+
|
|
481
|
+
result = session.execute(stmt)
|
|
482
|
+
session.commit()
|
|
483
|
+
return result.rowcount
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logging.error("Error deleting from '%s': %s", query.table, e)
|
|
486
|
+
raise
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
__all__ = [
|
|
490
|
+
"SQLiteDatabase"
|
|
491
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def process_id() -> int:
|
|
6
|
+
"""
|
|
7
|
+
will return the current process' id
|
|
8
|
+
Returns:
|
|
9
|
+
int
|
|
10
|
+
"""
|
|
11
|
+
return os.getpid()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def thread_id() -> int:
|
|
15
|
+
"""
|
|
16
|
+
will return the current thread's id
|
|
17
|
+
Returns:
|
|
18
|
+
int
|
|
19
|
+
"""
|
|
20
|
+
return threading.get_ident()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"process_id",
|
|
25
|
+
"thread_id"
|
|
26
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional, Any, Tuple as Tuple
|
|
4
|
+
import logging
|
|
5
|
+
import danielutils # this is explicitly this way to prevent circular import
|
|
6
|
+
from ...reflection import get_python_version
|
|
7
|
+
from ...logging_.utils import get_logger
|
|
8
|
+
|
|
9
|
+
if get_python_version() >= (3, 9):
|
|
10
|
+
from builtins import tuple as Tuple # type:ignore
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Worker(ABC):
|
|
16
|
+
"""A Worker Interface
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, id: int,
|
|
20
|
+
pool: "danielutils.abstractions.multiprogramming.worker_pool.WorkerPool") -> None: # pylint: disable=redefined-builtin #noqa
|
|
21
|
+
logger.debug("Initializing Worker with id=%s", id)
|
|
22
|
+
self.id = id
|
|
23
|
+
self.pool = pool
|
|
24
|
+
self.thread: Thread = Thread(target=self._loop)
|
|
25
|
+
logger.debug("Worker %s initialized successfully", id)
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def _work(self, obj: Any) -> None:
|
|
29
|
+
"""execution of a single job
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def _loop(self) -> None:
|
|
33
|
+
"""main loop of the worker
|
|
34
|
+
"""
|
|
35
|
+
logger.debug("Worker %s main loop started", self.id)
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
obj = self.acquire()
|
|
39
|
+
if obj is not None:
|
|
40
|
+
logger.debug("Worker %s acquired job: %s", self.id, type(obj[0]).__name__)
|
|
41
|
+
self.work(obj[0])
|
|
42
|
+
else:
|
|
43
|
+
logger.debug("Worker %s received None job, exiting loop", self.id)
|
|
44
|
+
break
|
|
45
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
46
|
+
logger.error("Worker %s thread encountered an error: %s: %s", self.id, type(e).__name__, e)
|
|
47
|
+
break
|
|
48
|
+
logger.debug("Worker %s main loop ended", self.id)
|
|
49
|
+
|
|
50
|
+
def run(self) -> None:
|
|
51
|
+
"""will start self._run() as a new thread with the argument given in __init__
|
|
52
|
+
"""
|
|
53
|
+
logger.debug("Starting worker %s thread", self.id)
|
|
54
|
+
self.thread.start()
|
|
55
|
+
logger.debug("Worker %s thread started successfully", self.id)
|
|
56
|
+
|
|
57
|
+
def is_alive(self) -> bool:
|
|
58
|
+
"""returns whether the worker is alive or not
|
|
59
|
+
"""
|
|
60
|
+
is_alive = self.thread.is_alive()
|
|
61
|
+
logger.debug("Worker %s is_alive: %s", self.id, is_alive)
|
|
62
|
+
return is_alive
|
|
63
|
+
|
|
64
|
+
def work(self, obj: Any) -> None:
|
|
65
|
+
"""performed the actual work that needs to happen
|
|
66
|
+
execution of a single job
|
|
67
|
+
"""
|
|
68
|
+
logger.debug("Worker %s starting work on job: %s", self.id, type(obj).__name__)
|
|
69
|
+
try:
|
|
70
|
+
self._work(obj)
|
|
71
|
+
logger.debug("Worker %s completed work on job: %s", self.id, type(obj).__name__)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error("Worker %s failed to process job %s: %s: %s", self.id, type(obj).__name__, type(e).__name__, e)
|
|
74
|
+
raise
|
|
75
|
+
finally:
|
|
76
|
+
self._notify()
|
|
77
|
+
|
|
78
|
+
def _notify(self) -> None:
|
|
79
|
+
"""utility method to be called on the end of each iteration of work
|
|
80
|
+
to signal actions if needed
|
|
81
|
+
will call 'notification_function'
|
|
82
|
+
"""
|
|
83
|
+
logger.debug("Worker %s notifying pool of job completion", self.id)
|
|
84
|
+
# TODO
|
|
85
|
+
self.pool._notify_subscribers() # type:ignore # pylint: disable=protected-access
|
|
86
|
+
|
|
87
|
+
def acquire(self) -> Optional[Tuple[Any]]:
|
|
88
|
+
"""acquire a new job object to work on from the pool
|
|
89
|
+
will return a tuple of only one object (the job) or None if there are no more jobs
|
|
90
|
+
Returns:
|
|
91
|
+
Optional[tuple[Any]]: tuple of job object or None
|
|
92
|
+
"""
|
|
93
|
+
logger.debug("Worker %s attempting to acquire job from pool", self.id)
|
|
94
|
+
job = self.pool._acquire() # pylint: disable=protected-access
|
|
95
|
+
if job is not None:
|
|
96
|
+
logger.debug("Worker %s acquired job: %s", self.id, type(job[0]).__name__)
|
|
97
|
+
else:
|
|
98
|
+
logger.debug("Worker %s no jobs available in pool", self.id)
|
|
99
|
+
return job
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"Worker"
|
|
104
|
+
]
|