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.
Files changed (70) hide show
  1. CONTRIBUTING.md +63 -0
  2. aws_advanced_python_wrapper/__init__.py +28 -0
  3. aws_advanced_python_wrapper/aurora_connection_tracker_plugin.py +228 -0
  4. aws_advanced_python_wrapper/aurora_initial_connection_strategy_plugin.py +240 -0
  5. aws_advanced_python_wrapper/aws_secrets_manager_plugin.py +218 -0
  6. aws_advanced_python_wrapper/connect_time_plugin.py +69 -0
  7. aws_advanced_python_wrapper/connection_provider.py +232 -0
  8. aws_advanced_python_wrapper/database_dialect.py +708 -0
  9. aws_advanced_python_wrapper/default_plugin.py +144 -0
  10. aws_advanced_python_wrapper/developer_plugin.py +163 -0
  11. aws_advanced_python_wrapper/driver_configuration_profiles.py +44 -0
  12. aws_advanced_python_wrapper/driver_dialect.py +165 -0
  13. aws_advanced_python_wrapper/driver_dialect_codes.py +19 -0
  14. aws_advanced_python_wrapper/driver_dialect_manager.py +121 -0
  15. aws_advanced_python_wrapper/driver_info.py +18 -0
  16. aws_advanced_python_wrapper/errors.py +47 -0
  17. aws_advanced_python_wrapper/exception_handling.py +73 -0
  18. aws_advanced_python_wrapper/execute_time_plugin.py +58 -0
  19. aws_advanced_python_wrapper/failover_plugin.py +517 -0
  20. aws_advanced_python_wrapper/failover_result.py +42 -0
  21. aws_advanced_python_wrapper/fastest_response_strategy_plugin.py +345 -0
  22. aws_advanced_python_wrapper/federated_plugin.py +382 -0
  23. aws_advanced_python_wrapper/host_availability.py +86 -0
  24. aws_advanced_python_wrapper/host_list_provider.py +645 -0
  25. aws_advanced_python_wrapper/host_monitoring_plugin.py +728 -0
  26. aws_advanced_python_wrapper/host_selector.py +190 -0
  27. aws_advanced_python_wrapper/hostinfo.py +138 -0
  28. aws_advanced_python_wrapper/iam_plugin.py +195 -0
  29. aws_advanced_python_wrapper/mysql_driver_dialect.py +175 -0
  30. aws_advanced_python_wrapper/pep249.py +196 -0
  31. aws_advanced_python_wrapper/pg_driver_dialect.py +176 -0
  32. aws_advanced_python_wrapper/plugin.py +148 -0
  33. aws_advanced_python_wrapper/plugin_service.py +949 -0
  34. aws_advanced_python_wrapper/read_write_splitting_plugin.py +363 -0
  35. aws_advanced_python_wrapper/reader_failover_handler.py +252 -0
  36. aws_advanced_python_wrapper/resources/aws_advanced_python_wrapper_messages.properties +315 -0
  37. aws_advanced_python_wrapper/sql_alchemy_connection_provider.py +196 -0
  38. aws_advanced_python_wrapper/sqlalchemy_driver_dialect.py +127 -0
  39. aws_advanced_python_wrapper/stale_dns_plugin.py +209 -0
  40. aws_advanced_python_wrapper/states/__init__.py +13 -0
  41. aws_advanced_python_wrapper/states/session_state.py +94 -0
  42. aws_advanced_python_wrapper/states/session_state_service.py +221 -0
  43. aws_advanced_python_wrapper/utils/__init__.py +13 -0
  44. aws_advanced_python_wrapper/utils/atomic.py +51 -0
  45. aws_advanced_python_wrapper/utils/cache_map.py +99 -0
  46. aws_advanced_python_wrapper/utils/concurrent.py +100 -0
  47. aws_advanced_python_wrapper/utils/decorators.py +70 -0
  48. aws_advanced_python_wrapper/utils/failover_mode.py +39 -0
  49. aws_advanced_python_wrapper/utils/iamutils.py +75 -0
  50. aws_advanced_python_wrapper/utils/log.py +75 -0
  51. aws_advanced_python_wrapper/utils/messages.py +36 -0
  52. aws_advanced_python_wrapper/utils/mysql_exception_handler.py +73 -0
  53. aws_advanced_python_wrapper/utils/notifications.py +37 -0
  54. aws_advanced_python_wrapper/utils/pg_exception_handler.py +115 -0
  55. aws_advanced_python_wrapper/utils/properties.py +492 -0
  56. aws_advanced_python_wrapper/utils/rds_url_type.py +36 -0
  57. aws_advanced_python_wrapper/utils/rdsutils.py +226 -0
  58. aws_advanced_python_wrapper/utils/sliding_expiration_cache.py +146 -0
  59. aws_advanced_python_wrapper/utils/telemetry/default_telemetry_factory.py +82 -0
  60. aws_advanced_python_wrapper/utils/telemetry/null_telemetry.py +55 -0
  61. aws_advanced_python_wrapper/utils/telemetry/open_telemetry.py +189 -0
  62. aws_advanced_python_wrapper/utils/telemetry/telemetry.py +85 -0
  63. aws_advanced_python_wrapper/utils/telemetry/xray_telemetry.py +126 -0
  64. aws_advanced_python_wrapper/utils/utils.py +89 -0
  65. aws_advanced_python_wrapper/wrapper.py +322 -0
  66. aws_advanced_python_wrapper/writer_failover_handler.py +347 -0
  67. aws_advanced_python_wrapper-1.0.0.dist-info/LICENSE +201 -0
  68. aws_advanced_python_wrapper-1.0.0.dist-info/METADATA +261 -0
  69. aws_advanced_python_wrapper-1.0.0.dist-info/RECORD +70 -0
  70. aws_advanced_python_wrapper-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,189 @@
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 TYPE_CHECKING, Callable, Optional, Sequence, Union
18
+
19
+ if TYPE_CHECKING:
20
+ from opentelemetry.util.types import AttributeValue
21
+
22
+ from opentelemetry import context as context_api
23
+ from opentelemetry import trace
24
+ from opentelemetry import trace as trace_api
25
+ from opentelemetry.metrics import (CallbackOptions, Meter, Observation,
26
+ get_meter)
27
+ from opentelemetry.sdk.trace import ReadableSpan, Span, StatusCode, Tracer
28
+
29
+ from aws_advanced_python_wrapper.utils.log import Logger
30
+ from aws_advanced_python_wrapper.utils.messages import Messages
31
+ from aws_advanced_python_wrapper.utils.telemetry.telemetry import (
32
+ TelemetryConst, TelemetryContext, TelemetryCounter, TelemetryFactory,
33
+ TelemetryGauge, TelemetryTraceLevel)
34
+
35
+ logger = Logger(__name__)
36
+
37
+ INSTRUMENTATION_NAME = "aws-advanced-python-wrapper"
38
+
39
+
40
+ class OpenTelemetryContext(TelemetryContext):
41
+ def __init__(self, tracer: Tracer, name: str, trace_level: TelemetryTraceLevel,
42
+ start_time: Optional[int] = None, link_span: Optional[Span] = None):
43
+ self._name = name
44
+ self._tracer = tracer
45
+ self._span: Optional[Span]
46
+ self._meter: Meter
47
+ self._token: Optional[object] = None
48
+
49
+ current_span: Span = trace.get_current_span() # type: ignore
50
+ is_root = (current_span is None or current_span == trace.INVALID_SPAN)
51
+
52
+ if is_root and trace_level == TelemetryTraceLevel.NESTED:
53
+ trace_level = TelemetryTraceLevel.TOP_LEVEL
54
+
55
+ links: Sequence[trace_api.Link] = ()
56
+ if trace_level in [TelemetryTraceLevel.FORCE_TOP_LEVEL, TelemetryTraceLevel.TOP_LEVEL]:
57
+ if link_span is not None:
58
+ links = [trace.Link(link_span.get_span_context())]
59
+ else:
60
+ if not is_root:
61
+ links = [trace.Link(current_span.get_span_context())]
62
+
63
+ self._span = self._tracer.start_span(self._name, context=context_api.Context(),
64
+ links=links, start_time=start_time) # type: ignore
65
+ if not is_root:
66
+ self.set_attribute(TelemetryConst.TRACE_NAME_ANNOTATION, self._name)
67
+
68
+ ctx = trace.set_span_in_context(self._span) # type: ignore
69
+ self._token = context_api.attach(ctx)
70
+ logger.debug("OpenTelemetryContext.TelemetryTraceID", self._name,
71
+ self._span.get_span_context().trace_id) # type: ignore
72
+
73
+ elif trace_level == TelemetryTraceLevel.NESTED:
74
+ if link_span is not None:
75
+ links = [trace.Link(link_span.get_span_context())]
76
+
77
+ self._span = self._tracer.start_span(self._name, links=links, start_time=start_time) # type: ignore
78
+ ctx = trace.set_span_in_context(self._span) # type: ignore
79
+ self._token = context_api.attach(ctx)
80
+ self.set_attribute(TelemetryConst.TRACE_NAME_ANNOTATION, self._name)
81
+
82
+ elif trace_level == TelemetryTraceLevel.NO_TRACE:
83
+ self._span = None
84
+
85
+ def set_success(self, success: bool):
86
+ if self._span is not None:
87
+ self._span.set_status(StatusCode.OK if success else StatusCode.ERROR)
88
+
89
+ def set_attribute(self, key: str, value: AttributeValue):
90
+ if self._span is not None:
91
+ self._span.set_attribute(key, value)
92
+
93
+ def set_exception(self, exception: Exception):
94
+ if self._span is not None and exception is not None:
95
+ self._span.set_attribute(TelemetryConst.EXCEPTION_TYPE_ANNOTATION, exception.__class__.__name__)
96
+ self._span.set_attribute(TelemetryConst.EXCEPTION_MESSAGE_ANNOTATION, str(exception))
97
+ self._span.record_exception(exception)
98
+
99
+ def get_name(self) -> str:
100
+ return self._name
101
+
102
+ def close_context(self):
103
+ if self._token is not None:
104
+ context_api.detach(self._token)
105
+ if self._span is not None:
106
+ self._span.end()
107
+
108
+ @property
109
+ def tracer(self) -> Tracer:
110
+ return self._tracer
111
+
112
+ @property
113
+ def span(self) -> Optional[Span]:
114
+ return self._span
115
+
116
+
117
+ def post_copy(context: OpenTelemetryContext, trace_level: TelemetryTraceLevel):
118
+ if trace_level == TelemetryTraceLevel.NO_TRACE:
119
+ return
120
+
121
+ _clone_and_close_context(context, trace_level)
122
+
123
+
124
+ def _clone_and_close_context(context: OpenTelemetryContext, trace_level: TelemetryTraceLevel) -> OpenTelemetryContext:
125
+ if not isinstance(context.span, ReadableSpan):
126
+ raise RuntimeError(Messages.get("OpenTelemetry.InvalidContext"))
127
+
128
+ clone = OpenTelemetryContext(
129
+ context.tracer, TelemetryConst.COPY_TRACE_NAME_PREFIX + context.get_name(),
130
+ trace_level, context.span.start_time, context.span)
131
+
132
+ for key in context.span.attributes: # type: ignore
133
+ value = context.span.attributes[key] # type: ignore
134
+ clone.set_attribute(key, value)
135
+
136
+ clone.span.set_status(context.span.status) # type: ignore
137
+ clone.set_attribute(TelemetryConst.SOURCE_TRACE_ANNOTATION, str(context.span.get_span_context().trace_id))
138
+ clone.span.end(context.span.end_time) # type: ignore
139
+
140
+ return clone
141
+
142
+
143
+ class OpenTelemetryCounter(TelemetryCounter):
144
+ def __init__(self, meter: Meter, name: str):
145
+ self._meter: Meter = meter
146
+ self._name: str = name
147
+ self._counter = meter.create_up_down_counter(self._name, unit="1")
148
+
149
+ def add(self, value):
150
+ self._counter.add(value)
151
+
152
+ def inc(self):
153
+ self._counter.add(1)
154
+
155
+ def get_name(self):
156
+ return self._name
157
+
158
+
159
+ class OpenTelemetryGauge(TelemetryGauge):
160
+ def __init__(self, meter: Meter, name: str, callback: Callable[[], Union[float, int]]):
161
+ self._meter: Meter = meter
162
+ self.name: str = name
163
+ self._counter = meter.create_observable_up_down_counter(name, callbacks=[self._callback_observation], unit="1")
164
+ self._callback: Callable[[], float] = callback
165
+
166
+ def get_name(self):
167
+ return self.name
168
+
169
+ def _callback_observation(self, options: CallbackOptions):
170
+ value: Union[int, float] = self._callback()
171
+ observation = Observation(value)
172
+ yield observation
173
+
174
+
175
+ class OpenTelemetryFactory(TelemetryFactory):
176
+ def open_telemetry_context(self, name: str, trace_level: TelemetryTraceLevel) -> TelemetryContext:
177
+ return OpenTelemetryContext(trace.get_tracer(INSTRUMENTATION_NAME), name, trace_level) # type: ignore
178
+
179
+ def post_copy(self, context: TelemetryContext, trace_level: TelemetryTraceLevel):
180
+ if isinstance(context, OpenTelemetryContext):
181
+ post_copy(context, trace_level)
182
+ else:
183
+ raise RuntimeError(Messages.get_formatted("OpenTelemetryFactory.WrongParameterType", type(context)))
184
+
185
+ def create_counter(self, name: str) -> TelemetryCounter:
186
+ return OpenTelemetryCounter(get_meter(INSTRUMENTATION_NAME), name)
187
+
188
+ def create_gauge(self, name: str, callback: Callable[[], Union[float, int]]):
189
+ return OpenTelemetryGauge(get_meter(INSTRUMENTATION_NAME), name, callback)
@@ -0,0 +1,85 @@
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 abc import ABC, abstractmethod
18
+ from enum import Enum, auto
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from opentelemetry.util.types import AttributeValue
23
+
24
+
25
+ class TelemetryConst:
26
+ TRACE_NAME_ANNOTATION = "trace_name"
27
+ SOURCE_TRACE_ANNOTATION = "source_trace_id"
28
+ PARENT_TRACE_ANNOTATION = "parent_trace_id"
29
+ EXCEPTION_TYPE_ANNOTATION = "exception_type"
30
+ EXCEPTION_MESSAGE_ANNOTATION = "exception_message"
31
+ COPY_TRACE_NAME_PREFIX = "copy: "
32
+
33
+
34
+ class TelemetryTraceLevel(Enum):
35
+ FORCE_TOP_LEVEL = auto()
36
+ TOP_LEVEL = auto()
37
+ NESTED = auto()
38
+ NO_TRACE = auto()
39
+
40
+
41
+ class TelemetryContext(ABC):
42
+ def set_success(self, success: bool):
43
+ pass
44
+
45
+ def set_attribute(self, key: str, value: AttributeValue):
46
+ pass
47
+
48
+ def set_exception(self, exception: Exception):
49
+ pass
50
+
51
+ def get_name(self):
52
+ pass
53
+
54
+ def close_context(self):
55
+ pass
56
+
57
+
58
+ class TelemetryCounter(ABC):
59
+ def add(self, value):
60
+ pass
61
+
62
+ def inc(self):
63
+ pass
64
+
65
+
66
+ class TelemetryGauge(ABC):
67
+ pass
68
+
69
+
70
+ class TelemetryFactory(ABC):
71
+ @abstractmethod
72
+ def open_telemetry_context(self, name: str, trace_level: TelemetryTraceLevel) -> TelemetryContext:
73
+ pass
74
+
75
+ @abstractmethod
76
+ def post_copy(self, context: TelemetryContext, trace_level: TelemetryTraceLevel):
77
+ pass
78
+
79
+ @abstractmethod
80
+ def create_counter(self, name: str) -> TelemetryCounter:
81
+ pass
82
+
83
+ @abstractmethod
84
+ def create_gauge(self, name: str, callback):
85
+ pass
@@ -0,0 +1,126 @@
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 concurrent.futures import ThreadPoolExecutor
18
+ from typing import TYPE_CHECKING
19
+
20
+ from aws_xray_sdk.core import xray_recorder
21
+
22
+ if TYPE_CHECKING:
23
+ from opentelemetry.util.types import AttributeValue
24
+ from aws_xray_sdk.core.models.segment import Segment
25
+ from aws_xray_sdk.core.models.subsegment import Subsegment
26
+
27
+ from aws_advanced_python_wrapper.utils.log import Logger
28
+ from aws_advanced_python_wrapper.utils.messages import Messages
29
+ from aws_advanced_python_wrapper.utils.telemetry.telemetry import (
30
+ TelemetryConst, TelemetryContext, TelemetryCounter, TelemetryFactory,
31
+ TelemetryTraceLevel)
32
+
33
+ logger = Logger(__name__)
34
+
35
+
36
+ class XRayTelemetryContext(TelemetryContext):
37
+ def __init__(self, name: str, trace_level: TelemetryTraceLevel):
38
+ self._name: str = name
39
+ self._trace_entity: Segment | Subsegment
40
+
41
+ if trace_level in [TelemetryTraceLevel.FORCE_TOP_LEVEL, TelemetryTraceLevel.TOP_LEVEL]:
42
+ self._trace_entity = xray_recorder.begin_segment(self._name)
43
+ self.is_segment = True
44
+ logger.debug("XRayTelemetryContext.TraceID", self._name, self._trace_entity.trace_id)
45
+ elif trace_level == TelemetryTraceLevel.NESTED:
46
+ self._trace_entity = xray_recorder.begin_subsegment(self._name) # type: ignore
47
+ self.set_attribute(TelemetryConst.TRACE_NAME_ANNOTATION, self._name)
48
+ self.is_segment = False
49
+ elif trace_level == TelemetryTraceLevel.NO_TRACE:
50
+ pass
51
+
52
+ def set_success(self, success: bool):
53
+ if self._trace_entity is not None:
54
+ self._trace_entity.error = not success
55
+
56
+ def set_attribute(self, key: str, value: AttributeValue):
57
+ if self._trace_entity is not None:
58
+ self._trace_entity.put_annotation(key, value)
59
+
60
+ def set_exception(self, exception: Exception):
61
+ if self._trace_entity is not None and exception is not None:
62
+ self._trace_entity.put_annotation("exception_type", exception.__class__.__name__)
63
+ self._trace_entity.put_annotation("exception_message", str(exception))
64
+
65
+ def get_name(self):
66
+ return self._name
67
+
68
+ def close_context(self, end_time=None):
69
+ if self._trace_entity is not None:
70
+ if self.is_segment:
71
+ xray_recorder.end_segment(end_time)
72
+ else:
73
+ xray_recorder.end_subsegment(end_time)
74
+
75
+
76
+ def post_copy(context: XRayTelemetryContext, trace_level: TelemetryTraceLevel):
77
+ if trace_level == TelemetryTraceLevel.NO_TRACE:
78
+ return
79
+
80
+ if trace_level in [TelemetryTraceLevel.FORCE_TOP_LEVEL, TelemetryTraceLevel.TOP_LEVEL]:
81
+ with ThreadPoolExecutor() as executor:
82
+ future = executor.submit(_clone_and_close_context, context, trace_level)
83
+ future.result()
84
+ else:
85
+ _clone_and_close_context(context, trace_level)
86
+
87
+
88
+ def _clone_and_close_context(context: XRayTelemetryContext, trace_level: TelemetryTraceLevel) -> XRayTelemetryContext:
89
+ clone = XRayTelemetryContext(TelemetryConst.COPY_TRACE_NAME_PREFIX + context.get_name(), trace_level)
90
+
91
+ clone._trace_entity.start_time = context._trace_entity.start_time
92
+
93
+ for key in context._trace_entity.annotations.items():
94
+ value = context._trace_entity.annotations[key]
95
+ if key != TelemetryConst.TRACE_NAME_ANNOTATION and value is not None:
96
+ clone.set_attribute(key, value)
97
+
98
+ if context.is_segment and context._trace_entity.error:
99
+ clone._trace_entity.add_error_flag()
100
+
101
+ clone.set_attribute(TelemetryConst.SOURCE_TRACE_ANNOTATION, str(context._trace_entity.trace_id))
102
+
103
+ if context._trace_entity.parent_id is not None:
104
+ if trace_level == TelemetryTraceLevel.NESTED:
105
+ clone._trace_entity.parent_id = context._trace_entity.parent_id
106
+
107
+ clone.close_context(context._trace_entity.end_time)
108
+
109
+ return clone
110
+
111
+
112
+ class XRayTelemetryFactory(TelemetryFactory):
113
+ def open_telemetry_context(self, name: str, trace_level: TelemetryTraceLevel) -> TelemetryContext:
114
+ return XRayTelemetryContext(name, trace_level)
115
+
116
+ def post_copy(self, context: TelemetryContext, trace_level: TelemetryTraceLevel):
117
+ if isinstance(context, XRayTelemetryContext):
118
+ post_copy(context, trace_level)
119
+ else:
120
+ raise RuntimeError(Messages.get_formatted("XRayTelemetryFactory.WrongParameterType", type(context)))
121
+
122
+ def create_counter(self, name: str) -> TelemetryCounter:
123
+ raise RuntimeError(Messages.get_formatted("XRayTelemetryFactory.MetricsNotSupported"))
124
+
125
+ def create_gauge(self, name: str, callback):
126
+ raise RuntimeError(Messages.get_formatted("XRayTelemetryFactory.MetricsNotSupported"))
@@ -0,0 +1,89 @@
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 sys
18
+ from importlib import import_module
19
+ from logging import DEBUG, Formatter, Logger, StreamHandler
20
+ from queue import Empty, Queue
21
+ from typing import TYPE_CHECKING, Optional, Tuple
22
+
23
+ if TYPE_CHECKING:
24
+ from aws_advanced_python_wrapper.hostinfo import HostInfo
25
+
26
+
27
+ class LogUtils:
28
+ @staticmethod
29
+ def setup_logger(logger: Logger, level: int = DEBUG, format_string: Optional[str] = None):
30
+ for handler in logger.handlers:
31
+ if isinstance(handler, StreamHandler):
32
+ return
33
+
34
+ if format_string is None:
35
+ format_string = \
36
+ "%(asctime)s.%(msecs)03d %(name)-12s:%(funcName)s [%(levelname)-8s] - %(threadName)s - %(message)s"
37
+
38
+ handler = StreamHandler(stream=sys.stdout)
39
+ handler.setFormatter(Formatter(format_string))
40
+ handler.setLevel(level)
41
+
42
+ logger.setLevel(level)
43
+ logger.addHandler(handler)
44
+
45
+ @staticmethod
46
+ def log_topology(hosts: Tuple[HostInfo, ...], message_prefix: Optional[str] = None):
47
+ """Returns a formatted string of the topology that looks like the following
48
+ Topology: {
49
+ HostInfo[host=url, port=123]
50
+ <null>
51
+ HostInfo[host=url1, port=234]}
52
+
53
+ Args:
54
+ hosts (list): a list of hosts representing the cluster topology
55
+ message_prefix (str): messages to prefix to the topology information
56
+
57
+ Returns:
58
+ _type_: a formatted string of the topology
59
+ """
60
+ msg = "\n\t".join(["<null>" if not host else str(host) for host in hosts])
61
+ prefix = "" if not message_prefix else message_prefix + " "
62
+ return prefix + f": {{\n\t{msg}}}"
63
+
64
+
65
+ class QueueUtils:
66
+ @staticmethod
67
+ def get(q: Queue):
68
+ try:
69
+ return q.get_nowait()
70
+ except Empty:
71
+ return None
72
+
73
+ @staticmethod
74
+ def clear(q: Queue):
75
+ with q.mutex:
76
+ q.queue.clear()
77
+ q.all_tasks_done.notify_all()
78
+ q.unfinished_tasks = 0
79
+
80
+
81
+ class Utils:
82
+ @staticmethod
83
+ def initialize_class(full_class_name: str, *args):
84
+ try:
85
+ parts = full_class_name.split('.')
86
+ m = import_module(".".join(parts[:-1]))
87
+ return getattr(m, parts[-1])(*args)
88
+ except ModuleNotFoundError:
89
+ return None