aws-advanced-python-wrapper 1.0.0__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.
- CONTRIBUTING.md +63 -0
- aws_advanced_python_wrapper/__init__.py +28 -0
- aws_advanced_python_wrapper/aurora_connection_tracker_plugin.py +228 -0
- aws_advanced_python_wrapper/aurora_initial_connection_strategy_plugin.py +240 -0
- aws_advanced_python_wrapper/aws_secrets_manager_plugin.py +218 -0
- aws_advanced_python_wrapper/connect_time_plugin.py +69 -0
- aws_advanced_python_wrapper/connection_provider.py +232 -0
- aws_advanced_python_wrapper/database_dialect.py +708 -0
- aws_advanced_python_wrapper/default_plugin.py +144 -0
- aws_advanced_python_wrapper/developer_plugin.py +163 -0
- aws_advanced_python_wrapper/driver_configuration_profiles.py +44 -0
- aws_advanced_python_wrapper/driver_dialect.py +165 -0
- aws_advanced_python_wrapper/driver_dialect_codes.py +19 -0
- aws_advanced_python_wrapper/driver_dialect_manager.py +121 -0
- aws_advanced_python_wrapper/driver_info.py +18 -0
- aws_advanced_python_wrapper/errors.py +47 -0
- aws_advanced_python_wrapper/exception_handling.py +73 -0
- aws_advanced_python_wrapper/execute_time_plugin.py +58 -0
- aws_advanced_python_wrapper/failover_plugin.py +517 -0
- aws_advanced_python_wrapper/failover_result.py +42 -0
- aws_advanced_python_wrapper/fastest_response_strategy_plugin.py +345 -0
- aws_advanced_python_wrapper/federated_plugin.py +382 -0
- aws_advanced_python_wrapper/host_availability.py +86 -0
- aws_advanced_python_wrapper/host_list_provider.py +645 -0
- aws_advanced_python_wrapper/host_monitoring_plugin.py +728 -0
- aws_advanced_python_wrapper/host_selector.py +190 -0
- aws_advanced_python_wrapper/hostinfo.py +138 -0
- aws_advanced_python_wrapper/iam_plugin.py +195 -0
- aws_advanced_python_wrapper/mysql_driver_dialect.py +175 -0
- aws_advanced_python_wrapper/pep249.py +196 -0
- aws_advanced_python_wrapper/pg_driver_dialect.py +176 -0
- aws_advanced_python_wrapper/plugin.py +148 -0
- aws_advanced_python_wrapper/plugin_service.py +949 -0
- aws_advanced_python_wrapper/read_write_splitting_plugin.py +363 -0
- aws_advanced_python_wrapper/reader_failover_handler.py +252 -0
- aws_advanced_python_wrapper/resources/aws_advanced_python_wrapper_messages.properties +315 -0
- aws_advanced_python_wrapper/sql_alchemy_connection_provider.py +196 -0
- aws_advanced_python_wrapper/sqlalchemy_driver_dialect.py +127 -0
- aws_advanced_python_wrapper/stale_dns_plugin.py +209 -0
- aws_advanced_python_wrapper/states/__init__.py +13 -0
- aws_advanced_python_wrapper/states/session_state.py +94 -0
- aws_advanced_python_wrapper/states/session_state_service.py +221 -0
- aws_advanced_python_wrapper/utils/__init__.py +13 -0
- aws_advanced_python_wrapper/utils/atomic.py +51 -0
- aws_advanced_python_wrapper/utils/cache_map.py +99 -0
- aws_advanced_python_wrapper/utils/concurrent.py +100 -0
- aws_advanced_python_wrapper/utils/decorators.py +70 -0
- aws_advanced_python_wrapper/utils/failover_mode.py +39 -0
- aws_advanced_python_wrapper/utils/iamutils.py +75 -0
- aws_advanced_python_wrapper/utils/log.py +75 -0
- aws_advanced_python_wrapper/utils/messages.py +36 -0
- aws_advanced_python_wrapper/utils/mysql_exception_handler.py +73 -0
- aws_advanced_python_wrapper/utils/notifications.py +37 -0
- aws_advanced_python_wrapper/utils/pg_exception_handler.py +115 -0
- aws_advanced_python_wrapper/utils/properties.py +492 -0
- aws_advanced_python_wrapper/utils/rds_url_type.py +36 -0
- aws_advanced_python_wrapper/utils/rdsutils.py +226 -0
- aws_advanced_python_wrapper/utils/sliding_expiration_cache.py +146 -0
- aws_advanced_python_wrapper/utils/telemetry/default_telemetry_factory.py +82 -0
- aws_advanced_python_wrapper/utils/telemetry/null_telemetry.py +55 -0
- aws_advanced_python_wrapper/utils/telemetry/open_telemetry.py +189 -0
- aws_advanced_python_wrapper/utils/telemetry/telemetry.py +85 -0
- aws_advanced_python_wrapper/utils/telemetry/xray_telemetry.py +126 -0
- aws_advanced_python_wrapper/utils/utils.py +89 -0
- aws_advanced_python_wrapper/wrapper.py +322 -0
- aws_advanced_python_wrapper/writer_failover_handler.py +347 -0
- aws_advanced_python_wrapper-1.0.0.dist-info/LICENSE +201 -0
- aws_advanced_python_wrapper-1.0.0.dist-info/METADATA +261 -0
- aws_advanced_python_wrapper-1.0.0.dist-info/RECORD +70 -0
- aws_advanced_python_wrapper-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import socket
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Set
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from aws_advanced_python_wrapper.driver_dialect import DriverDialect
|
|
22
|
+
from aws_advanced_python_wrapper.host_list_provider import HostListProviderService
|
|
23
|
+
from aws_advanced_python_wrapper.hostinfo import HostInfo
|
|
24
|
+
from aws_advanced_python_wrapper.pep249 import Connection
|
|
25
|
+
from aws_advanced_python_wrapper.plugin_service import PluginService
|
|
26
|
+
from aws_advanced_python_wrapper.utils.properties import Properties
|
|
27
|
+
|
|
28
|
+
from aws_advanced_python_wrapper.hostinfo import HostRole
|
|
29
|
+
from aws_advanced_python_wrapper.plugin import Plugin, PluginFactory
|
|
30
|
+
from aws_advanced_python_wrapper.utils.log import Logger
|
|
31
|
+
from aws_advanced_python_wrapper.utils.messages import Messages
|
|
32
|
+
from aws_advanced_python_wrapper.utils.notifications import HostEvent
|
|
33
|
+
from aws_advanced_python_wrapper.utils.rdsutils import RdsUtils
|
|
34
|
+
from aws_advanced_python_wrapper.utils.utils import LogUtils
|
|
35
|
+
|
|
36
|
+
logger = Logger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StaleDnsHelper:
|
|
40
|
+
RETRIES: int = 3
|
|
41
|
+
|
|
42
|
+
def __init__(self, plugin_service: PluginService) -> None:
|
|
43
|
+
self._plugin_service = plugin_service
|
|
44
|
+
self._rds_helper = RdsUtils()
|
|
45
|
+
self._writer_host_info: Optional[HostInfo] = None
|
|
46
|
+
self._writer_host_address: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
def get_verified_connection(self, is_initial_connection: bool, host_list_provider_service: HostListProviderService, host_info: HostInfo,
|
|
49
|
+
props: Properties, connect_func: Callable) -> Connection:
|
|
50
|
+
"""
|
|
51
|
+
Ensure the connection created is not a stale writer connection that
|
|
52
|
+
:param is_initial_connection:
|
|
53
|
+
:param host_list_provider_service:
|
|
54
|
+
:param host_info:
|
|
55
|
+
:param props:
|
|
56
|
+
:param connect_func:
|
|
57
|
+
:return:
|
|
58
|
+
"""
|
|
59
|
+
if not self._rds_helper.is_writer_cluster_dns(host_info.host):
|
|
60
|
+
return connect_func()
|
|
61
|
+
|
|
62
|
+
conn: Connection = connect_func()
|
|
63
|
+
|
|
64
|
+
cluster_inet_address: Optional[str] = None
|
|
65
|
+
try:
|
|
66
|
+
cluster_inet_address = socket.gethostbyname(host_info.host)
|
|
67
|
+
except socket.gaierror:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
host_inet_address: Optional[str] = cluster_inet_address
|
|
71
|
+
|
|
72
|
+
logger.debug("StaleDnsHelper.ClusterEndpointDns", host_info.host, host_inet_address)
|
|
73
|
+
|
|
74
|
+
if cluster_inet_address is None:
|
|
75
|
+
return conn
|
|
76
|
+
|
|
77
|
+
if self._plugin_service.get_host_role(conn) == HostRole.READER:
|
|
78
|
+
# This if-statement is only reached if the connection url is a writer cluster endpoint.
|
|
79
|
+
# If the new connection resolves to a reader instance, this means the topology is outdated.
|
|
80
|
+
# Force refresh to update the topology.
|
|
81
|
+
self._plugin_service.force_refresh_host_list(conn)
|
|
82
|
+
else:
|
|
83
|
+
self._plugin_service.refresh_host_list(conn)
|
|
84
|
+
|
|
85
|
+
logger.debug("LogUtils.Topology", LogUtils.log_topology(self._plugin_service.hosts))
|
|
86
|
+
|
|
87
|
+
if self._writer_host_info is None:
|
|
88
|
+
writer_candidate: Optional[HostInfo] = self._get_writer()
|
|
89
|
+
if writer_candidate is not None and self._rds_helper.is_rds_cluster_dns(writer_candidate.host):
|
|
90
|
+
return conn
|
|
91
|
+
|
|
92
|
+
self._writer_host_info = writer_candidate
|
|
93
|
+
|
|
94
|
+
logger.debug("StaleDnsHelper.WriterHostSpec", self._writer_host_info)
|
|
95
|
+
|
|
96
|
+
if self._writer_host_info is None:
|
|
97
|
+
return conn
|
|
98
|
+
|
|
99
|
+
if self._writer_host_address is None:
|
|
100
|
+
try:
|
|
101
|
+
self._writer_host_address = socket.gethostbyname(self._writer_host_info.host)
|
|
102
|
+
except socket.gaierror:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
logger.debug("StaleDnsHelper.WriterInetAddress", self._writer_host_address)
|
|
106
|
+
|
|
107
|
+
if self._writer_host_address is None:
|
|
108
|
+
return conn
|
|
109
|
+
|
|
110
|
+
if self._writer_host_address != cluster_inet_address:
|
|
111
|
+
logger.debug("StaleDnsHelper.StaleDnsDetected", self._writer_host_info)
|
|
112
|
+
|
|
113
|
+
writer_conn: Connection = self._plugin_service.connect(self._writer_host_info, props)
|
|
114
|
+
if is_initial_connection:
|
|
115
|
+
host_list_provider_service.initial_connection_host_info = self._writer_host_info
|
|
116
|
+
|
|
117
|
+
if conn is not None:
|
|
118
|
+
try:
|
|
119
|
+
conn.close()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
return writer_conn
|
|
123
|
+
|
|
124
|
+
return conn
|
|
125
|
+
|
|
126
|
+
def notify_host_list_changed(self, changes: Dict[str, Set[HostEvent]]) -> None:
|
|
127
|
+
if self._writer_host_info is None:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
writer_changes = changes.get(self._writer_host_info.url, None)
|
|
131
|
+
if writer_changes is not None and HostEvent.CONVERTED_TO_READER in writer_changes:
|
|
132
|
+
logger.debug("StaleDnsHelper.Reset")
|
|
133
|
+
self._writer_host_info = None
|
|
134
|
+
self._writer_host_address = None
|
|
135
|
+
|
|
136
|
+
def _get_writer(self) -> Optional[HostInfo]:
|
|
137
|
+
for host in self._plugin_service.hosts:
|
|
138
|
+
if host.role == HostRole.WRITER:
|
|
139
|
+
return host
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class StaleDnsPlugin(Plugin):
|
|
144
|
+
|
|
145
|
+
_SUBSCRIBED_METHODS: Set[str] = {"init_host_provider",
|
|
146
|
+
"connect",
|
|
147
|
+
"force_connect",
|
|
148
|
+
"notify_host_list_changed"}
|
|
149
|
+
|
|
150
|
+
def __init__(self, plugin_service: PluginService) -> None:
|
|
151
|
+
self._plugin_service = plugin_service
|
|
152
|
+
self._stale_dns_helper = StaleDnsHelper(self._plugin_service)
|
|
153
|
+
|
|
154
|
+
StaleDnsPlugin._SUBSCRIBED_METHODS.update(self._plugin_service.network_bound_methods)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def subscribed_methods(self) -> Set[str]:
|
|
158
|
+
return self._SUBSCRIBED_METHODS
|
|
159
|
+
|
|
160
|
+
def connect(
|
|
161
|
+
self,
|
|
162
|
+
target_driver_func: Callable,
|
|
163
|
+
driver_dialect: DriverDialect,
|
|
164
|
+
host_info: HostInfo,
|
|
165
|
+
props: Properties,
|
|
166
|
+
is_initial_connection: bool,
|
|
167
|
+
connect_func: Callable) -> Connection:
|
|
168
|
+
return self._stale_dns_helper.get_verified_connection(
|
|
169
|
+
is_initial_connection, self._host_list_provider_service, host_info, props, connect_func)
|
|
170
|
+
|
|
171
|
+
def force_connect(
|
|
172
|
+
self,
|
|
173
|
+
target_driver_func: Callable,
|
|
174
|
+
driver_dialect: DriverDialect,
|
|
175
|
+
host_info: HostInfo,
|
|
176
|
+
props: Properties,
|
|
177
|
+
is_initial_connection: bool,
|
|
178
|
+
force_connect_func: Callable) -> Connection:
|
|
179
|
+
return self._stale_dns_helper.get_verified_connection(
|
|
180
|
+
is_initial_connection, self._host_list_provider_service, host_info, props, force_connect_func)
|
|
181
|
+
|
|
182
|
+
def execute(self, target: type, method_name: str, execute_func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
183
|
+
try:
|
|
184
|
+
self._plugin_service.refresh_host_list()
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
return execute_func()
|
|
189
|
+
|
|
190
|
+
def init_host_provider(
|
|
191
|
+
self,
|
|
192
|
+
properties: Properties,
|
|
193
|
+
host_list_provider_service: HostListProviderService,
|
|
194
|
+
init_host_provider_func: Callable):
|
|
195
|
+
|
|
196
|
+
self._host_list_provider_service = host_list_provider_service
|
|
197
|
+
|
|
198
|
+
init_host_provider_func()
|
|
199
|
+
|
|
200
|
+
if self._host_list_provider_service.is_static_host_list_provider():
|
|
201
|
+
raise Exception(Messages.get_formatted("StaleDnsPlugin.RequireDynamicProvider"))
|
|
202
|
+
|
|
203
|
+
def notify_host_list_changed(self, changes: Dict[str, Set[HostEvent]]):
|
|
204
|
+
self._stale_dns_helper.notify_host_list_changed(changes)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class StaleDnsPluginFactory(PluginFactory):
|
|
208
|
+
def get_instance(self, plugin_service: PluginService, props: Properties) -> Plugin:
|
|
209
|
+
return StaleDnsPlugin(plugin_service)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Generic, Optional, TypeVar
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SessionStateField(Generic[T]):
|
|
23
|
+
_value: Optional[T] = None
|
|
24
|
+
_pristine_value: Optional[T] = None
|
|
25
|
+
|
|
26
|
+
def copy(self):
|
|
27
|
+
new_field: SessionStateField[T] = SessionStateField()
|
|
28
|
+
if self._value is not None:
|
|
29
|
+
new_field._value = self._value
|
|
30
|
+
if self._pristine_value is not None:
|
|
31
|
+
new_field._pristine_value = self._pristine_value
|
|
32
|
+
return new_field
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def value(self):
|
|
36
|
+
return self._value
|
|
37
|
+
|
|
38
|
+
@value.setter
|
|
39
|
+
def value(self, new_value: Optional[T]):
|
|
40
|
+
self._value = new_value
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def pristine_value(self):
|
|
44
|
+
return self._pristine_value
|
|
45
|
+
|
|
46
|
+
@pristine_value.setter
|
|
47
|
+
def pristine_value(self, new_value: Optional[T]):
|
|
48
|
+
self._pristine_value = new_value
|
|
49
|
+
|
|
50
|
+
def reset_value(self):
|
|
51
|
+
self._value = None
|
|
52
|
+
|
|
53
|
+
def reset_pristine_value(self):
|
|
54
|
+
self.pristine_value = None
|
|
55
|
+
|
|
56
|
+
def reset(self):
|
|
57
|
+
self.reset_value()
|
|
58
|
+
self.reset_pristine_value()
|
|
59
|
+
|
|
60
|
+
def is_pristine(self) -> bool:
|
|
61
|
+
if self.value is None:
|
|
62
|
+
# the value has never been set up so the session state has pristine value
|
|
63
|
+
return True
|
|
64
|
+
return self._pristine_value is not None and self._pristine_value == self.value
|
|
65
|
+
|
|
66
|
+
def can_restore_pristine(self):
|
|
67
|
+
if self._pristine_value is None:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
if self._value is not None:
|
|
71
|
+
# it's necessary to restore pristine value only if current session value is not the same as pristine value.
|
|
72
|
+
return self.value != self.pristine_value
|
|
73
|
+
|
|
74
|
+
# it's inconclusive if the current value is the same as pristine value, so we need to take the safest path.
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
def __str__(self):
|
|
78
|
+
return f"{self.pristine_value if self.pristine_value is not None else '(blank)'} -> {self.value if self.value is not None else '(blank)'}"
|
|
79
|
+
|
|
80
|
+
def __repr__(self):
|
|
81
|
+
return f"{self.pristine_value if self.pristine_value is not None else '(blank)'} -> {self.value if self.value is not None else '(blank)'}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SessionState:
|
|
85
|
+
def __init__(self):
|
|
86
|
+
self.auto_commit: SessionStateField[bool] = SessionStateField()
|
|
87
|
+
self.readonly: SessionStateField[bool] = SessionStateField()
|
|
88
|
+
|
|
89
|
+
def copy(self):
|
|
90
|
+
new_session_state: SessionState = SessionState()
|
|
91
|
+
new_session_state.auto_commit = self.auto_commit.copy()
|
|
92
|
+
new_session_state.readonly = self.readonly.copy()
|
|
93
|
+
|
|
94
|
+
return new_session_state
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING, Callable, Optional, Protocol
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from aws_advanced_python_wrapper.plugin_service import PluginService
|
|
22
|
+
from aws_advanced_python_wrapper.pep249 import Connection
|
|
23
|
+
|
|
24
|
+
from aws_advanced_python_wrapper.errors import AwsWrapperError
|
|
25
|
+
from aws_advanced_python_wrapper.states.session_state import SessionState
|
|
26
|
+
from aws_advanced_python_wrapper.utils.log import Logger
|
|
27
|
+
from aws_advanced_python_wrapper.utils.properties import (Properties,
|
|
28
|
+
WrapperProperties)
|
|
29
|
+
|
|
30
|
+
logger = Logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SessionStateService(Protocol):
|
|
34
|
+
|
|
35
|
+
def get_autocommit(self) -> Optional[bool]:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def set_autocommit(self, autocommit: bool):
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def setup_pristine_autocommit(self, autocommit: Optional[bool] = None):
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
def get_readonly(self):
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def set_read_only(self, readonly: bool):
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
def setup_pristine_readonly(self, readonly: Optional[bool] = None):
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def reset(self):
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def begin(self):
|
|
57
|
+
"""
|
|
58
|
+
Begin session transfer process
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def complete(self):
|
|
63
|
+
"""
|
|
64
|
+
Complete session transfer process. This method should be called despite whether session transfer is successful or not.
|
|
65
|
+
"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
def apply_current_session_state(self, new_connection: Connection):
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
def apply_pristine_session_state(self, new_connection: Connection):
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SessionStateTransferHandlers:
|
|
76
|
+
reset_session_state_on_close_callable: Optional[Callable] = None
|
|
77
|
+
transfer_session_state_on_switch_callable: Optional[Callable] = None
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def set_reset_session_state_on_close_func(func: Callable):
|
|
81
|
+
SessionStateTransferHandlers.reset_session_state_on_close_callable = func
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def clear_reset_session_state_on_close_func():
|
|
85
|
+
SessionStateTransferHandlers.reset_session_state_on_close_callable = None
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def get_reset_session_state_on_close_func() -> Optional[Callable]:
|
|
89
|
+
return SessionStateTransferHandlers.reset_session_state_on_close_callable
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def get_transfer_session_state_on_switch_func() -> Optional[Callable]:
|
|
93
|
+
return SessionStateTransferHandlers.transfer_session_state_on_switch_callable
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def set_transfer_session_state_on_switch_func(func: Callable):
|
|
97
|
+
SessionStateTransferHandlers.transfer_session_state_on_switch_callable = func
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def reset_transfer_session_state_on_switch_func():
|
|
101
|
+
SessionStateTransferHandlers.reset_transfer_session_state_on_switch_callable = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SessionStateServiceImpl(SessionStateService):
|
|
105
|
+
def __init__(self, plugin_service: PluginService, props: Properties):
|
|
106
|
+
self._session_state: SessionState = SessionState()
|
|
107
|
+
self._copy_session_state: Optional[SessionState] = None
|
|
108
|
+
self._plugin_service: PluginService = plugin_service
|
|
109
|
+
self._props: Properties = props
|
|
110
|
+
|
|
111
|
+
def log_current_state(self):
|
|
112
|
+
logger.debug(f"Current session state: \n{self._session_state}")
|
|
113
|
+
|
|
114
|
+
def _transfer_state_enabled_setting(self) -> bool:
|
|
115
|
+
return WrapperProperties.TRANSFER_SESSION_STATE_ON_SWITCH.get_bool(self._props)
|
|
116
|
+
|
|
117
|
+
def _reset_state_enabled_setting(self) -> bool:
|
|
118
|
+
return WrapperProperties.RESET_SESSION_STATE_ON_CLOSE.get_bool(self._props)
|
|
119
|
+
|
|
120
|
+
def get_autocommit(self) -> Optional[bool]:
|
|
121
|
+
return self._session_state.auto_commit.value
|
|
122
|
+
|
|
123
|
+
def set_autocommit(self, autocommit: bool):
|
|
124
|
+
if not self._transfer_state_enabled_setting():
|
|
125
|
+
return
|
|
126
|
+
self._session_state.auto_commit.value = autocommit
|
|
127
|
+
|
|
128
|
+
def setup_pristine_autocommit(self, autocommit: Optional[bool] = None):
|
|
129
|
+
if not self._transfer_state_enabled_setting():
|
|
130
|
+
return
|
|
131
|
+
if self._session_state.auto_commit.pristine_value is not None:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
if autocommit is None and self._plugin_service.current_connection is not None:
|
|
135
|
+
autocommit = self._plugin_service.driver_dialect.get_autocommit(self._plugin_service.current_connection)
|
|
136
|
+
|
|
137
|
+
self._session_state.auto_commit.pristine_value = autocommit
|
|
138
|
+
self.log_current_state()
|
|
139
|
+
|
|
140
|
+
def get_readonly(self):
|
|
141
|
+
return self._session_state.readonly.value
|
|
142
|
+
|
|
143
|
+
def set_read_only(self, readonly: bool):
|
|
144
|
+
if not self._transfer_state_enabled_setting():
|
|
145
|
+
return
|
|
146
|
+
self._session_state.readonly.value = readonly
|
|
147
|
+
|
|
148
|
+
def setup_pristine_readonly(self, readonly: Optional[bool] = None):
|
|
149
|
+
if not self._transfer_state_enabled_setting():
|
|
150
|
+
return
|
|
151
|
+
if self._session_state.readonly.pristine_value is not None:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if readonly is None and self._plugin_service.current_connection is not None:
|
|
155
|
+
readonly = self._plugin_service.driver_dialect.is_read_only(self._plugin_service.current_connection)
|
|
156
|
+
|
|
157
|
+
self._session_state.readonly.pristine_value = readonly
|
|
158
|
+
self.log_current_state()
|
|
159
|
+
|
|
160
|
+
def reset(self):
|
|
161
|
+
self._session_state.auto_commit.reset()
|
|
162
|
+
self._session_state.readonly.reset()
|
|
163
|
+
|
|
164
|
+
def begin(self):
|
|
165
|
+
self.log_current_state()
|
|
166
|
+
if not self._transfer_state_enabled_setting() and not self._reset_state_enabled_setting():
|
|
167
|
+
return
|
|
168
|
+
if self._copy_session_state is not None:
|
|
169
|
+
raise AwsWrapperError("Previous session state transfer is not completed.")
|
|
170
|
+
self._copy_session_state = self._session_state.copy()
|
|
171
|
+
|
|
172
|
+
def complete(self):
|
|
173
|
+
self._copy_session_state = None
|
|
174
|
+
|
|
175
|
+
def apply_current_session_state(self, new_connection: Connection):
|
|
176
|
+
if not self._transfer_state_enabled_setting():
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
func: Optional[Callable] = SessionStateTransferHandlers.get_transfer_session_state_on_switch_func()
|
|
180
|
+
if func is not None:
|
|
181
|
+
is_handled: bool = func(self._session_state, new_connection)
|
|
182
|
+
if is_handled:
|
|
183
|
+
# Custom function has handled session transfer
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
if self._session_state.auto_commit.value is not None:
|
|
187
|
+
self._session_state.auto_commit.reset_pristine_value()
|
|
188
|
+
self.setup_pristine_autocommit()
|
|
189
|
+
self._plugin_service.driver_dialect.set_autocommit(new_connection, self._session_state.auto_commit.value)
|
|
190
|
+
|
|
191
|
+
if self._session_state.readonly.value is not None:
|
|
192
|
+
self._session_state.readonly.reset_pristine_value()
|
|
193
|
+
self.setup_pristine_readonly()
|
|
194
|
+
self._plugin_service.driver_dialect.set_read_only(new_connection, self._session_state.readonly.value)
|
|
195
|
+
|
|
196
|
+
def apply_pristine_session_state(self, new_connection: Connection):
|
|
197
|
+
if not self._transfer_state_enabled_setting():
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
func: Optional[Callable] = SessionStateTransferHandlers.get_transfer_session_state_on_switch_func()
|
|
201
|
+
if func is not None:
|
|
202
|
+
is_handled: bool = func(self._session_state, new_connection)
|
|
203
|
+
if is_handled:
|
|
204
|
+
# Custom function has handled session transfer
|
|
205
|
+
return
|
|
206
|
+
if self._copy_session_state is None:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
if self._copy_session_state.auto_commit.can_restore_pristine():
|
|
210
|
+
try:
|
|
211
|
+
self._plugin_service.driver_dialect.set_autocommit(new_connection, self._copy_session_state.auto_commit.pristine_value)
|
|
212
|
+
except Exception:
|
|
213
|
+
# Ignore any exception.
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
if self._copy_session_state.readonly.can_restore_pristine():
|
|
217
|
+
try:
|
|
218
|
+
self._plugin_service.driver_dialect.set_read_only(new_connection, self._copy_session_state.readonly.pristine_value)
|
|
219
|
+
except Exception:
|
|
220
|
+
# Ignore any exception.
|
|
221
|
+
pass
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from threading import Lock
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AtomicInt:
|
|
19
|
+
def __init__(self, initial_value: int = 0):
|
|
20
|
+
self._value = initial_value
|
|
21
|
+
self._lock: Lock = Lock()
|
|
22
|
+
|
|
23
|
+
def get(self):
|
|
24
|
+
with self._lock:
|
|
25
|
+
return self._value
|
|
26
|
+
|
|
27
|
+
def set(self, value: int):
|
|
28
|
+
with self._lock:
|
|
29
|
+
self._value = value
|
|
30
|
+
|
|
31
|
+
def get_and_increment(self):
|
|
32
|
+
with self._lock:
|
|
33
|
+
value = self._value
|
|
34
|
+
self._value += 1
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
def increment_and_get(self):
|
|
38
|
+
with self._lock:
|
|
39
|
+
self._value += 1
|
|
40
|
+
return self._value
|
|
41
|
+
|
|
42
|
+
def get_and_decrement(self):
|
|
43
|
+
with self._lock:
|
|
44
|
+
value = self._value
|
|
45
|
+
self._value -= 1
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
def decrement_and_get(self):
|
|
49
|
+
with self._lock:
|
|
50
|
+
self._value -= 1
|
|
51
|
+
return self._value
|