amsdal-glue 0.0.1__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.
Files changed (57) hide show
  1. amsdal_glue-0.0.1/.gitignore +16 -0
  2. amsdal_glue-0.0.1/LICENSE.txt +3 -0
  3. amsdal_glue-0.0.1/PKG-INFO +43 -0
  4. amsdal_glue-0.0.1/README.md +18 -0
  5. amsdal_glue-0.0.1/pyproject.toml +202 -0
  6. amsdal_glue-0.0.1/src/amsdal_glue/__about__.py +1 -0
  7. amsdal_glue-0.0.1/src/amsdal_glue/__init__.py +0 -0
  8. amsdal_glue-0.0.1/src/amsdal_glue/commands/__init__.py +0 -0
  9. amsdal_glue-0.0.1/src/amsdal_glue/commands/planner/__init__.py +0 -0
  10. amsdal_glue-0.0.1/src/amsdal_glue/commands/planner/data_planner.py +33 -0
  11. amsdal_glue-0.0.1/src/amsdal_glue/commands/planner/lock_planner.py +25 -0
  12. amsdal_glue-0.0.1/src/amsdal_glue/commands/planner/schema_planner.py +50 -0
  13. amsdal_glue-0.0.1/src/amsdal_glue/commands/planner/transaction_planner.py +19 -0
  14. amsdal_glue-0.0.1/src/amsdal_glue/commands/tasks/__init__.py +0 -0
  15. amsdal_glue-0.0.1/src/amsdal_glue/commands/tasks/data_mutation_tasks.py +23 -0
  16. amsdal_glue-0.0.1/src/amsdal_glue/commands/tasks/lock_tasks.py +30 -0
  17. amsdal_glue-0.0.1/src/amsdal_glue/commands/tasks/schema_mutation_tasks.py +63 -0
  18. amsdal_glue-0.0.1/src/amsdal_glue/commands/tasks/transaction_tasks.py +30 -0
  19. amsdal_glue-0.0.1/src/amsdal_glue/connections/__init__.py +0 -0
  20. amsdal_glue-0.0.1/src/amsdal_glue/connections/connection_pool.py +66 -0
  21. amsdal_glue-0.0.1/src/amsdal_glue/initialize.py +61 -0
  22. amsdal_glue-0.0.1/src/amsdal_glue/queries/__init__.py +0 -0
  23. amsdal_glue-0.0.1/src/amsdal_glue/queries/executors/__init__.py +0 -0
  24. amsdal_glue-0.0.1/src/amsdal_glue/queries/executors/palars_final_query_executor.py +257 -0
  25. amsdal_glue-0.0.1/src/amsdal_glue/queries/planner/__init__.py +0 -0
  26. amsdal_glue-0.0.1/src/amsdal_glue/queries/planner/data_query_planner.py +121 -0
  27. amsdal_glue-0.0.1/src/amsdal_glue/queries/planner/schema_query_planner.py +37 -0
  28. amsdal_glue-0.0.1/src/amsdal_glue/queries/polars_operator_constructor.py +21 -0
  29. amsdal_glue-0.0.1/src/amsdal_glue/queries/tasks/__init__.py +0 -0
  30. amsdal_glue-0.0.1/src/amsdal_glue/queries/tasks/query_tasks.py +47 -0
  31. amsdal_glue-0.0.1/src/amsdal_glue/queries/tasks/schema_tasks.py +47 -0
  32. amsdal_glue-0.0.1/src/amsdal_glue/services/__init__.py +0 -0
  33. amsdal_glue-0.0.1/src/amsdal_glue/services/data_command.py +21 -0
  34. amsdal_glue-0.0.1/src/amsdal_glue/services/data_query.py +21 -0
  35. amsdal_glue-0.0.1/src/amsdal_glue/services/lock_command.py +19 -0
  36. amsdal_glue-0.0.1/src/amsdal_glue/services/schema_command.py +21 -0
  37. amsdal_glue-0.0.1/src/amsdal_glue/services/schema_query.py +21 -0
  38. amsdal_glue-0.0.1/src/amsdal_glue/services/transaction_command.py +19 -0
  39. amsdal_glue-0.0.1/src/amsdal_glue/task_executors/__init__.py +0 -0
  40. amsdal_glue-0.0.1/src/amsdal_glue/task_executors/parallel_thread_executor.py +35 -0
  41. amsdal_glue-0.0.1/src/amsdal_glue/task_executors/sequential_sync_executor.py +31 -0
  42. amsdal_glue-0.0.1/tests/__init__.py +0 -0
  43. amsdal_glue-0.0.1/tests/fixtures/__init__.py +0 -0
  44. amsdal_glue-0.0.1/tests/fixtures/customers.sqlite +0 -0
  45. amsdal_glue-0.0.1/tests/fixtures/shippings.sqlite +0 -0
  46. amsdal_glue-0.0.1/tests/test_data_command_execute.py +343 -0
  47. amsdal_glue-0.0.1/tests/test_data_command_services.py +102 -0
  48. amsdal_glue-0.0.1/tests/test_data_query_service.py +127 -0
  49. amsdal_glue-0.0.1/tests/test_lock_execute.py +138 -0
  50. amsdal_glue-0.0.1/tests/test_lock_service.py +66 -0
  51. amsdal_glue-0.0.1/tests/test_query_execute.py +440 -0
  52. amsdal_glue-0.0.1/tests/test_schema_command_execute.py +121 -0
  53. amsdal_glue-0.0.1/tests/test_schema_command_service.py +109 -0
  54. amsdal_glue-0.0.1/tests/test_schema_query_execute.py +73 -0
  55. amsdal_glue-0.0.1/tests/test_schema_query_service.py +67 -0
  56. amsdal_glue-0.0.1/tests/test_transaction_execute.py +200 -0
  57. amsdal_glue-0.0.1/tests/test_transaction_service.py +62 -0
@@ -0,0 +1,16 @@
1
+ /.idea
2
+ /.vscode
3
+ /.env*
4
+ /.venv
5
+ coverage.*
6
+ .coverage*
7
+ .mypy_cache
8
+ .pytest_cache
9
+ .ruff_cache
10
+ __pycache__
11
+ pyrightconfig.json
12
+ .DS_Store
13
+ /dist
14
+ /build
15
+ /cython_build
16
+ *.egg-info
@@ -0,0 +1,3 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
2
+
3
+ The software contains third-party components that are distributed under the following license: https://github.com/pola-rs/polars/blob/main/LICENSE.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.3
2
+ Name: amsdal-glue
3
+ Version: 0.0.1
4
+ Summary: AMSDAL Glue is a Python interface providing high-level abstraction for interacting with multiple databases simultaneously, simplifying the development and maintenance process.
5
+ Project-URL: Documentation, https://pypi.org/project/amsdal_glue/#readme
6
+ Project-URL: Issues, https://pypi.org/project/amsdal_glue/
7
+ Project-URL: Source, https://pypi.org/project/amsdal_glue/
8
+ License: Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ The software contains third-party components that are distributed under the following license: https://github.com/pola-rs/polars/blob/main/LICENSE.
11
+ License-File: LICENSE.txt
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: amsdal-glue-core==0.0.*
21
+ Requires-Dist: polars>=0.20
22
+ Provides-Extra: connections
23
+ Requires-Dist: amsdal-glue-connections==0.0.*; extra == 'connections'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # amsdal-glue
27
+
28
+ AMSDAL Glue is a Python interface providing high-level abstraction for interacting with multiple databases simultaneously, simplifying the development and maintenance process.
29
+
30
+ [![PyPI - Version](https://img.shields.io/pypi/v/amsdal-glue.svg)](https://pypi.org/project/amsdal-glue)
31
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/amsdal-glue.svg)](https://pypi.org/project/amsdal-glue)
32
+
33
+ -----
34
+
35
+ ## Table of Contents
36
+
37
+ - [Installation](#installation)
38
+
39
+ ## Installation
40
+
41
+ ```console
42
+ pip install amsdal-glue
43
+ ```
@@ -0,0 +1,18 @@
1
+ # amsdal-glue
2
+
3
+ AMSDAL Glue is a Python interface providing high-level abstraction for interacting with multiple databases simultaneously, simplifying the development and maintenance process.
4
+
5
+ [![PyPI - Version](https://img.shields.io/pypi/v/amsdal-glue.svg)](https://pypi.org/project/amsdal-glue)
6
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/amsdal-glue.svg)](https://pypi.org/project/amsdal-glue)
7
+
8
+ -----
9
+
10
+ ## Table of Contents
11
+
12
+ - [Installation](#installation)
13
+
14
+ ## Installation
15
+
16
+ ```console
17
+ pip install amsdal-glue
18
+ ```
@@ -0,0 +1,202 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = ["src/amsdal_glue"]
7
+
8
+ [project]
9
+ name = "amsdal-glue"
10
+ dynamic = ["version"]
11
+ description = 'AMSDAL Glue is a Python interface providing high-level abstraction for interacting with multiple databases simultaneously, simplifying the development and maintenance process.'
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ license = { file = "LICENSE.txt" }
15
+ keywords = []
16
+ authors = []
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: Implementation :: CPython",
24
+ "Programming Language :: Python :: Implementation :: PyPy",
25
+ ]
26
+ dependencies = [
27
+ "amsdal_glue_core==0.0.*",
28
+ "polars>=0.20"
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ connections = [
33
+ "amsdal-glue-connections==0.0.*",
34
+ ]
35
+
36
+ [project.urls]
37
+ Documentation = "https://pypi.org/project/amsdal_glue/#readme"
38
+ Issues = "https://pypi.org/project/amsdal_glue/"
39
+ Source = "https://pypi.org/project/amsdal_glue/"
40
+
41
+ [tool.hatch.version]
42
+ path = "src/amsdal_glue/__about__.py"
43
+
44
+ [tool.hatch.envs.default]
45
+ path = "../../.venv/glue"
46
+ extra-dependencies = [
47
+ "mypy>=1.0.0",
48
+ "coverage[toml]>=7.4",
49
+ "pytest>=8.2.1",
50
+ "pytest-mock>=3.14.0",
51
+ "ruff>=0.4.5",
52
+ ]
53
+ pre-install-commands = [
54
+ "pip install -e ../core/",
55
+ "pip install -e ../connections/",
56
+ ]
57
+
58
+ [tool.hatch.envs.default.scripts]
59
+ test = "pytest {args:tests}"
60
+ test-cov = "coverage run -m pytest {args:tests}"
61
+ cov-report = [
62
+ "- coverage combine",
63
+ "coverage report",
64
+ ]
65
+ cov = [
66
+ "test-cov",
67
+ "cov-report",
68
+ ]
69
+ typing = "mypy --install-types --non-interactive {args:src/amsdal_glue tests}"
70
+ style = [
71
+ "ruff check {args:.}",
72
+ "ruff format --preview {args:.}",
73
+ ]
74
+ fmt = [
75
+ "ruff format {args:.}",
76
+ "ruff check --fix {args:.}",
77
+ "style",
78
+ ]
79
+ all = [
80
+ "style",
81
+ "typing",
82
+ ]
83
+
84
+ [tool.ruff]
85
+ target-version = "py310"
86
+ line-length = 120
87
+
88
+ [tool.ruff.format]
89
+ quote-style = "single"
90
+ indent-style = "space"
91
+ docstring-code-format = true
92
+
93
+ [tool.ruff.lint]
94
+ select = [
95
+ "A",
96
+ "ARG",
97
+ "ASYNC",
98
+ "B",
99
+ "BLE",
100
+ "C",
101
+ "C4",
102
+ "C90",
103
+ "COM",
104
+ "DJ",
105
+ "DTZ",
106
+ "E",
107
+ "EM",
108
+ "ERA",
109
+ "F",
110
+ "FBT",
111
+ "FIX",
112
+ "FLY",
113
+ "FURB",
114
+ "G",
115
+ "I",
116
+ "ICN",
117
+ "INT",
118
+ "ISC",
119
+ "LOG",
120
+ "N",
121
+ "PERF",
122
+ "PIE",
123
+ "PL",
124
+ "PTH",
125
+ "PYI",
126
+ "Q",
127
+ "RET",
128
+ "RSE",
129
+ "RUF",
130
+ "S",
131
+ "SIM",
132
+ "SLF",
133
+ "SLOT",
134
+ "T",
135
+ "T10",
136
+ "T20",
137
+ "TCH",
138
+ "TD",
139
+ "TID",
140
+ "TRY",
141
+ "UP",
142
+ "W",
143
+ "YTT",
144
+ ]
145
+ ignore = [
146
+ "COM812",
147
+ "ISC001",
148
+ ]
149
+ unfixable = [
150
+ # Don't touch unused imports
151
+ "F401",
152
+ ]
153
+
154
+ [tool.ruff.lint.isort]
155
+ force-single-line = true
156
+ order-by-type = false
157
+
158
+ [tool.ruff.lint.flake8-tidy-imports]
159
+ ban-relative-imports = "all"
160
+
161
+ [tool.ruff.lint.per-file-ignores]
162
+ # Tests can use magic values, assertions, and relative imports
163
+ "tests/**.py" = ["PLR2004", "S101", "TID252"]
164
+
165
+ [tool.ruff.lint.flake8-bandit]
166
+ check-typed-exception = true
167
+
168
+ [tool.ruff.lint.flake8-quotes]
169
+ inline-quotes = "single"
170
+
171
+ [tool.ruff.lint.mccabe]
172
+ # Flag errors (`C901`) whenever the complexity level exceeds 10.
173
+ max-complexity = 10
174
+
175
+ [tool.ruff.lint.pylint]
176
+ max-args = 6
177
+
178
+ [tool.ruff.lint.pydocstyle]
179
+ convention = "google"
180
+
181
+ [tool.mypy]
182
+ ignore_missing_imports = true
183
+ check_untyped_defs = true
184
+
185
+ [tool.coverage.run]
186
+ source_pkgs = ["amsdal_glue", "tests"]
187
+ branch = true
188
+ parallel = true
189
+ omit = [
190
+ "src/amsdal_glue/__about__.py",
191
+ ]
192
+
193
+ [tool.coverage.paths]
194
+ amsdal_glue = ["src/amsdal_glue"]
195
+ tests = ["tests"]
196
+
197
+ [tool.coverage.report]
198
+ exclude_lines = [
199
+ "no cov",
200
+ "if __name__ == .__main__.:",
201
+ "if TYPE_CHECKING:",
202
+ ]
@@ -0,0 +1 @@
1
+ __version__ = '0.0.1'
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from amsdal_glue_core.commands.mutation_nodes import DataMutationNode
4
+ from amsdal_glue_core.commands.planner.data_command_planner import DataCommandPlanner
5
+ from amsdal_glue_core.common.helpers.resolve_connection import resolve_connection
6
+ from amsdal_glue_core.common.operations.commands import DataCommand
7
+ from amsdal_glue_core.common.workflows.chain import ChainTask
8
+
9
+ from amsdal_glue.commands.tasks.data_mutation_tasks import DataMutationTask
10
+
11
+ if TYPE_CHECKING:
12
+ from amsdal_glue_core.common.workflows.task import Task
13
+
14
+
15
+ class DefaultDataCommandPlanner(DataCommandPlanner):
16
+ def plan_data_command(self, command: DataCommand) -> ChainTask:
17
+ task_batches: list[Task] = []
18
+ prev_connection = None
19
+
20
+ for mutation in command.mutations:
21
+ connection = resolve_connection(mutation.schema, command.transaction_id)
22
+
23
+ if connection != prev_connection:
24
+ task_batches.append(
25
+ DataMutationTask(
26
+ data_mutation_node=DataMutationNode(mutations=[]),
27
+ )
28
+ )
29
+ prev_connection = connection
30
+
31
+ task_batches[-1].item.mutations.append(mutation)
32
+
33
+ return ChainTask(tasks=task_batches)
@@ -0,0 +1,25 @@
1
+ from amsdal_glue_core.commands.lock_command_node import ExecutionLockCommand
2
+ from amsdal_glue_core.commands.planner.lock_command_planner import LockCommandPlanner
3
+ from amsdal_glue_core.common.operations.commands import LockCommand
4
+ from amsdal_glue_core.common.workflows.chain import ChainTask
5
+
6
+ from amsdal_glue.commands.tasks.lock_tasks import LockCommandTask
7
+
8
+
9
+ class DefaultLockCommandPlanner(LockCommandPlanner):
10
+ def plan_lock(self, command: LockCommand) -> ChainTask:
11
+ return ChainTask(
12
+ tasks=[
13
+ LockCommandTask(
14
+ lock_command=ExecutionLockCommand(
15
+ lock_id=command.lock_id,
16
+ transaction_id=command.transaction_id,
17
+ action=command.action,
18
+ mode=command.mode,
19
+ parameter=command.parameter,
20
+ locked_object=lock_object,
21
+ )
22
+ )
23
+ for lock_object in command.locked_objects
24
+ ]
25
+ )
@@ -0,0 +1,50 @@
1
+ from collections import defaultdict
2
+
3
+ from amsdal_glue_core.commands.mutation_nodes import SchemaCommandNode
4
+ from amsdal_glue_core.commands.planner.schema_command_planner import SchemaCommandPlanner
5
+ from amsdal_glue_core.common.operations.commands import SchemaCommand
6
+ from amsdal_glue_core.common.services.managers.connection import ConnectionManager
7
+ from amsdal_glue_core.common.workflows.chain import ChainTask
8
+
9
+ from amsdal_glue.commands.tasks.schema_mutation_tasks import FinalSchemaCommandTask
10
+ from amsdal_glue.commands.tasks.schema_mutation_tasks import SchemaCommandTask
11
+
12
+
13
+ class DefaultSchemaCommandPlanner(SchemaCommandPlanner):
14
+ def plan_schema_command(self, command: SchemaCommand) -> ChainTask:
15
+ from amsdal_glue_core.containers import Container
16
+
17
+ mutations_per_connection = defaultdict(list)
18
+ connection_manager = Container.managers.get(ConnectionManager)
19
+
20
+ for mutation in command.mutations:
21
+ _schema_name = mutation.get_schema_name()
22
+ _connection = connection_manager.get_connection_pool(_schema_name)
23
+ mutations_per_connection[_connection].append(mutation)
24
+
25
+ if len(mutations_per_connection) > 1:
26
+ return ChainTask(
27
+ tasks=[
28
+ SchemaCommandTask(
29
+ command_node=SchemaCommandNode(command=command),
30
+ ),
31
+ ],
32
+ )
33
+
34
+ _tasks = [
35
+ SchemaCommandTask(
36
+ command_node=SchemaCommandNode(
37
+ command=SchemaCommand(
38
+ lock_id=command.lock_id,
39
+ transaction_id=command.transaction_id,
40
+ mutations=mutations,
41
+ ),
42
+ ),
43
+ )
44
+ for mutations in mutations_per_connection.values()
45
+ ]
46
+
47
+ return ChainTask(
48
+ tasks=_tasks, # type: ignore[arg-type]
49
+ final_task=FinalSchemaCommandTask(tasks=_tasks),
50
+ )
@@ -0,0 +1,19 @@
1
+ from amsdal_glue_core.commands.planner.transaction_command_planner import TransactionCommandPlanner
2
+ from amsdal_glue_core.commands.transaction_node import ExecutionTransactionCommandNode
3
+ from amsdal_glue_core.common.operations.commands import TransactionCommand
4
+ from amsdal_glue_core.common.workflows.chain import ChainTask
5
+
6
+ from amsdal_glue.commands.tasks.transaction_tasks import TransactionCommandTask
7
+
8
+
9
+ class DefaultTransactionCommandPlanner(TransactionCommandPlanner):
10
+ def plan_transaction(self, command: TransactionCommand) -> ChainTask:
11
+ return ChainTask(
12
+ tasks=[
13
+ TransactionCommandTask(
14
+ transaction_command=ExecutionTransactionCommandNode(
15
+ command=command,
16
+ ),
17
+ ),
18
+ ],
19
+ )
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from amsdal_glue_core.commands.executors.data_command_executor import DataCommandNodeExecutor
5
+ from amsdal_glue_core.commands.mutation_nodes import DataMutationNode
6
+ from amsdal_glue_core.common.workflows.task import Task
7
+
8
+
9
+ @dataclass(kw_only=True)
10
+ class DataMutationTask(Task):
11
+ data_mutation_node: DataMutationNode
12
+
13
+ def execute(self, transaction_id: str | None, lock_id: str | None) -> None:
14
+ _query_executor = DataCommandNodeExecutor()
15
+ _query_executor.execute(self.data_mutation_node, transaction_id=transaction_id, lock_id=lock_id)
16
+
17
+ @property
18
+ def item(self) -> Any:
19
+ return self.data_mutation_node
20
+
21
+ @property
22
+ def result(self) -> Any:
23
+ return self.data_mutation_node.result
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from amsdal_glue_core.commands.lock_command_node import ExecutionLockCommand
5
+ from amsdal_glue_core.common.workflows.task import Task
6
+
7
+
8
+ @dataclass(kw_only=True)
9
+ class LockCommandTask(Task):
10
+ lock_command: ExecutionLockCommand
11
+
12
+ def execute(self, transaction_id: str | None, lock_id: str | None) -> None:
13
+ from amsdal_glue_core.commands.executors.lock_command_executor import LockCommandNodeExecutor
14
+
15
+ _command_executor = LockCommandNodeExecutor()
16
+ _command_executor.execute(self.lock_command, transaction_id=transaction_id, lock_id=lock_id)
17
+
18
+ @property
19
+ def item(self) -> Any:
20
+ return self.lock_command
21
+
22
+ @property
23
+ def result(self) -> Any:
24
+ return self.lock_command.result
25
+
26
+ def __repr__(self) -> str:
27
+ return f'LockCommandTask<{self.lock_command}>'
28
+
29
+ def __hash__(self) -> int:
30
+ return hash(id(self))
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from amsdal_glue_core.commands.executors.schema_command_executor import SchemaCommandNodeExecutor
5
+ from amsdal_glue_core.commands.mutation_nodes import SchemaCommandNode
6
+ from amsdal_glue_core.common.operations.commands import SchemaCommand
7
+ from amsdal_glue_core.common.workflows.task import Task
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class SchemaCommandTask(Task):
12
+ command_node: SchemaCommandNode
13
+
14
+ def __post_init__(self) -> None:
15
+ self._executor = SchemaCommandNodeExecutor()
16
+
17
+ def execute(self, transaction_id: str | None, lock_id: str | None) -> None:
18
+ self._executor.execute(self.command_node, transaction_id=transaction_id, lock_id=lock_id)
19
+
20
+ @property
21
+ def item(self) -> Any:
22
+ return self.command_node.command
23
+
24
+ @property
25
+ def result(self) -> list[Any] | None:
26
+ return self.command_node.result
27
+
28
+
29
+ @dataclass(kw_only=True)
30
+ class FinalSchemaCommandTask(Task):
31
+ tasks: list[SchemaCommandTask]
32
+
33
+ def __post_init__(self) -> None:
34
+ self._result: SchemaCommandNode | None = None
35
+
36
+ def execute(self, transaction_id: str | None, lock_id: str | None) -> None: # noqa: ARG002
37
+ if not self.tasks:
38
+ return
39
+
40
+ _command = SchemaCommand(
41
+ lock_id=self.tasks[0].item.lock_id,
42
+ transaction_id=self.tasks[0].item.transaction_id,
43
+ mutations=[],
44
+ )
45
+ _result = []
46
+
47
+ for task in self.tasks:
48
+ _command.mutations.extend(task.item.mutations)
49
+ _results = [task.result[i] if task.result else None for i in range(len(task.item.mutations))]
50
+ _result.extend(_results)
51
+
52
+ self._result = SchemaCommandNode(
53
+ command=_command,
54
+ result=_result,
55
+ )
56
+
57
+ @property
58
+ def item(self) -> Any:
59
+ return [task.item for task in self.tasks]
60
+
61
+ @property
62
+ def result(self) -> list[Any] | None:
63
+ return self._result.result if self._result else None
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from amsdal_glue_core.commands.transaction_node import ExecutionTransactionCommandNode
5
+ from amsdal_glue_core.common.workflows.task import Task
6
+
7
+
8
+ @dataclass(kw_only=True)
9
+ class TransactionCommandTask(Task):
10
+ transaction_command: ExecutionTransactionCommandNode
11
+
12
+ def execute(self, transaction_id: str | None, lock_id: str | None) -> None:
13
+ from amsdal_glue_core.commands.executors.transaction_command_executor import TransactionNodeExecutor
14
+
15
+ _command_executor = TransactionNodeExecutor()
16
+ _command_executor.execute(self.transaction_command, transaction_id=transaction_id, lock_id=lock_id)
17
+
18
+ @property
19
+ def item(self) -> Any:
20
+ return self.transaction_command
21
+
22
+ @property
23
+ def result(self) -> Any:
24
+ return self.transaction_command.result
25
+
26
+ def __repr__(self) -> str:
27
+ return f'TransactionCommandTask<{self.transaction_command}>'
28
+
29
+ def __hash__(self) -> int:
30
+ return hash(id(self))
@@ -0,0 +1,66 @@
1
+ import contextlib
2
+ from datetime import datetime
3
+ from datetime import timedelta
4
+ from datetime import timezone
5
+ from typing import Any
6
+
7
+ from amsdal_glue_core.common.interfaces.connection import ConnectionBase
8
+ from amsdal_glue_core.common.services.managers.connection import ConnectionPoolBase
9
+
10
+
11
+ class DefaultConnectionPool(ConnectionPoolBase):
12
+ def __init__(
13
+ self,
14
+ connection_class: type[ConnectionBase],
15
+ *args: Any,
16
+ max_connections: int = 10,
17
+ expiration_time: int = 60,
18
+ **kwargs: Any,
19
+ ) -> None:
20
+ super().__init__(connection_class, *args, **kwargs)
21
+ self.connections: dict[str | None, tuple[ConnectionBase, datetime]] = {}
22
+ self._max_connections = max_connections
23
+ self._expiration_time = expiration_time
24
+
25
+ def _now(self) -> datetime:
26
+ return datetime.now(tz=timezone.utc)
27
+
28
+ def _get_available_connection(self) -> ConnectionBase:
29
+ for transaction_id, (connection, last_used) in list(self.connections.items()):
30
+ if self._now() - last_used > timedelta(seconds=self._expiration_time):
31
+ with contextlib.suppress(Exception):
32
+ connection.rollback_transaction(transaction_id)
33
+
34
+ self.connections.pop(transaction_id)
35
+
36
+ return connection
37
+
38
+ if len(self.connections) >= self._max_connections:
39
+ msg = 'Max connections reached'
40
+ raise RuntimeError(msg)
41
+
42
+ connection = self._connection_class()
43
+ connection.connect(*self._connection_args, **self._connection_kwargs)
44
+
45
+ return connection
46
+
47
+ def get_connection(self, transaction_id: str | None = None) -> ConnectionBase:
48
+ if transaction_id in self.connections:
49
+ connection, _ = self.connections[transaction_id]
50
+
51
+ self.connections[transaction_id] = (connection, self._now())
52
+
53
+ return connection
54
+
55
+ connection = self._get_available_connection()
56
+
57
+ self.connections[transaction_id] = (connection, self._now())
58
+
59
+ return connection
60
+
61
+ def disconnect(self) -> None:
62
+ for connection, _ in self.connections.values():
63
+ if connection.is_connected:
64
+ connection.disconnect()
65
+
66
+ self.connections.clear()