fdc-shared-kernel 0.0.40__tar.gz → 0.0.41__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.
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/pyproject.toml +1 -1
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/messaging/__init__.py +2 -2
- fdc_shared_kernel-0.0.41/shared_kernel/messaging/nats_databus.py +337 -0
- fdc_shared_kernel-0.0.40/shared_kernel/messaging/nats_databus.py +0 -177
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/README.md +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/README_pypi.md +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/SOURCES.txt +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/requires.txt +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/top_level.txt +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/requirements.txt +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/setup.cfg +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/async_task_executor/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/async_task_executor/async_task_executor.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/auth/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/auth/jwt_helper.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/auth/token_handler.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/database/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/enums/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/enums/async_task_status.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/configuration_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/custom_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/http_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/operational_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/security_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/http/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/http/httpx_http_client.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/http/request_http_client.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/interfaces/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/interfaces/databus.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/interfaces/http.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/interfaces/keyvault.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/messaging/aws_databus.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/messaging/http_databus.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/metrics/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/metrics/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/models/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/security/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/security/key_vault/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/security/key_vault/aws_secret_manager.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/security/key_vault/azure_keyvault.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/config/test_config.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/logger/test_logger.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/messaging/test_nats_interface.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/utils/test_data_validators.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/utils/test_string_utils.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/data_validators_utils.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/string_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fdc_shared_kernel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.41
|
|
4
4
|
Summary: Shared library for microservice
|
|
5
5
|
Author-email: Shikhil S <shikhil.s@dbizsolution.com>, Ahammed Akdham N <ahammedakdham.n@dbizsolution.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fdc_shared_kernel
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.41
|
|
4
4
|
Summary: Shared library for microservice
|
|
5
5
|
Author-email: Shikhil S <shikhil.s@dbizsolution.com>, Ahammed Akdham N <ahammedakdham.n@dbizsolution.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -4,8 +4,8 @@ from shared_kernel.messaging.aws_databus import AWSDataBus
|
|
|
4
4
|
from shared_kernel.interfaces import DataBus
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
# create an enum for the
|
|
8
|
-
class
|
|
7
|
+
# create an enum for the buses available
|
|
8
|
+
class DataBusFactory:
|
|
9
9
|
data_bus_classes = {
|
|
10
10
|
"NATS": NATSDataBus,
|
|
11
11
|
"HTTP": HTTPDataBus,
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from nats.aio.client import Client as NATS
|
|
3
|
+
from nats.js.api import StreamConfig, ConsumerConfig, DeliverPolicy, AckPolicy
|
|
4
|
+
from typing import Callable, Any, Dict, List, Optional, Union
|
|
5
|
+
from nats.js.errors import (
|
|
6
|
+
BadRequestError as JetstreamBadRequestError,
|
|
7
|
+
NotFoundError as JetstreamNotFoundError,
|
|
8
|
+
)
|
|
9
|
+
from nats.errors import TimeoutError as NATSTimeoutError
|
|
10
|
+
from nats.js.client import JetStreamContext
|
|
11
|
+
from nats.aio.msg import Msg
|
|
12
|
+
|
|
13
|
+
from shared_kernel.config import Config
|
|
14
|
+
from shared_kernel.interfaces.databus import DataBus
|
|
15
|
+
from shared_kernel.logger import Logger
|
|
16
|
+
|
|
17
|
+
app_config = Config()
|
|
18
|
+
logger = Logger(app_config.get("APP_NAME"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NATSDataBus(DataBus):
|
|
22
|
+
"""
|
|
23
|
+
A NATS interface class to handle both standard NATS and JetStream operations.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
_instance = None
|
|
27
|
+
|
|
28
|
+
def __new__(cls, *args, **kwargs):
|
|
29
|
+
if cls._instance is None:
|
|
30
|
+
cls._instance = super(NATSDataBus, cls).__new__(cls)
|
|
31
|
+
return cls._instance
|
|
32
|
+
|
|
33
|
+
def __init__(self, config: Dict = None):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the NATSDataBus.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config (Dict): A dictionary containing the NATS configuration.
|
|
39
|
+
"""
|
|
40
|
+
if not hasattr(self, "initialized"): # Prevent reinitialization
|
|
41
|
+
super().__init__()
|
|
42
|
+
self.nats_client = NATS()
|
|
43
|
+
self.servers = config.get("servers")
|
|
44
|
+
self.user = config.get("user")
|
|
45
|
+
self.password = config.get("password")
|
|
46
|
+
self.connected = False
|
|
47
|
+
self.nats_jet_stream_context = None # JetStream context
|
|
48
|
+
self.initialized = True
|
|
49
|
+
self.max_delivery_count = app_config.get("MAX_DELIVERY_COUNT")
|
|
50
|
+
|
|
51
|
+
async def _connection_error_callback(self, e: Exception) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Callback function for connection errors.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
e (Exception): The exception that occurred during connection.
|
|
57
|
+
"""
|
|
58
|
+
logger.error(f"Unable to connect to NATS server - {str(e)}")
|
|
59
|
+
|
|
60
|
+
async def _create_consumer(self, consumer_name: str, subject: str):
|
|
61
|
+
try:
|
|
62
|
+
# check if the consumer already exists
|
|
63
|
+
try:
|
|
64
|
+
await self.nats_jet_stream_context.consumer_info(
|
|
65
|
+
app_config.get("NATS_STREAM_NAME"), consumer_name
|
|
66
|
+
)
|
|
67
|
+
logger.info(f"Consumer {consumer_name} already exists")
|
|
68
|
+
return
|
|
69
|
+
except JetstreamNotFoundError as e:
|
|
70
|
+
# case where the consumer doesn't exists (error code 10014)
|
|
71
|
+
if e.err_code == 10014:
|
|
72
|
+
consumer_config = ConsumerConfig(
|
|
73
|
+
durable_name=consumer_name,
|
|
74
|
+
ack_policy=AckPolicy.EXPLICIT,
|
|
75
|
+
deliver_policy=DeliverPolicy.ALL,
|
|
76
|
+
filter_subject=subject,
|
|
77
|
+
max_ack_pending=5,
|
|
78
|
+
)
|
|
79
|
+
await self.nats_jet_stream_context.add_consumer(
|
|
80
|
+
stream=app_config.get("NATS_STREAM_NAME"),
|
|
81
|
+
config=consumer_config,
|
|
82
|
+
)
|
|
83
|
+
logger.info(f"Consumer {consumer_name} created")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Error creating consumer: {e}")
|
|
86
|
+
raise
|
|
87
|
+
|
|
88
|
+
async def _create_dlq_stream(self) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Create a Dead Letter Queue stream if it doesn't exist.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
Exception: If an error occurs while creating the DLQ stream.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
dlq_config = StreamConfig(
|
|
97
|
+
name=app_config.get("NATS_DLQ_STREAM_NAME"),
|
|
98
|
+
subjects=[f"{app_config.get('NATS_DLQ_STREAM_NAME')}.>"],
|
|
99
|
+
max_age=86400 * 7, # Keep messages for 7 days
|
|
100
|
+
)
|
|
101
|
+
await self.nats_jet_stream_context.add_stream(dlq_config)
|
|
102
|
+
logger.info(
|
|
103
|
+
f"DLQ Stream '{app_config.get('NATS_DLQ_STREAM_NAME')}' created"
|
|
104
|
+
)
|
|
105
|
+
except JetstreamBadRequestError as e:
|
|
106
|
+
if e.err_code == 10058: # Stream already exists
|
|
107
|
+
logger.info(
|
|
108
|
+
f"DLQ Stream '{app_config.get('NATS_DLQ_STREAM_NAME')}' already exists"
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
async def _create_update_stream(self, topic: Optional[str]):
|
|
114
|
+
"""
|
|
115
|
+
Create or update a JetStream stream.
|
|
116
|
+
The function attempts to create a JetStream stream with the provided topic.
|
|
117
|
+
If a stream with the same name already exists but does not include the topic,
|
|
118
|
+
the stream is updated by adding the new topic.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
topic (Optional[str]): The topic to add to the stream.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
RuntimeError: If unable to create or update the stream.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
# ensure that the topic is a list (empty if None)
|
|
128
|
+
topic = [] if topic is None else [topic]
|
|
129
|
+
stream_config = StreamConfig(
|
|
130
|
+
name=app_config.get("NATS_STREAM_NAME"),
|
|
131
|
+
subjects=topic,
|
|
132
|
+
max_age=600,
|
|
133
|
+
)
|
|
134
|
+
await self.nats_jet_stream_context.add_stream(stream_config)
|
|
135
|
+
logger.info(f"Stream {app_config.get("NATS_STREAM_NAME")} created")
|
|
136
|
+
except JetstreamBadRequestError as e:
|
|
137
|
+
# case where the stream already exists (error code 10058)
|
|
138
|
+
if e.err_code == 10058:
|
|
139
|
+
logger.info(f"Stream {app_config.get("NATS_STREAM_NAME")} already exists with other topics")
|
|
140
|
+
# retrieve the existing stream's information
|
|
141
|
+
stream_info = await self.nats_jet_stream_context.stream_info(
|
|
142
|
+
app_config.get("NATS_STREAM_NAME")
|
|
143
|
+
)
|
|
144
|
+
# get the current list of topics associated with the stream
|
|
145
|
+
topics: list = stream_info.config.subjects
|
|
146
|
+
# if the new topic is not already in the stream's subjects, add it
|
|
147
|
+
if topic[0] not in topics:
|
|
148
|
+
topics.append(topic[0])
|
|
149
|
+
# update the stream configuration to include the new topic
|
|
150
|
+
stream_config = StreamConfig(
|
|
151
|
+
name=app_config.get("NATS_STREAM_NAME"),
|
|
152
|
+
subjects=topics,
|
|
153
|
+
max_age=600,
|
|
154
|
+
)
|
|
155
|
+
# apply the updated configuration to the existing stream
|
|
156
|
+
await self.nats_jet_stream_context.update_stream(stream_config)
|
|
157
|
+
logger.info(f"Added topic: {topic[0]} to the stream {app_config.get("NATS_STREAM_NAME")}")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error creating/updating stream: {e}")
|
|
160
|
+
raise e
|
|
161
|
+
|
|
162
|
+
async def _move_to_dlq(
|
|
163
|
+
self, original_subject: str, message: Dict[str, Any]
|
|
164
|
+
) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Move a message to the Dead Letter Queue.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
original_subject (str): The original subject of the message.
|
|
170
|
+
message (Dict[str, Any]): The message to be moved to DLQ.
|
|
171
|
+
"""
|
|
172
|
+
dlq_subject = f"{app_config.get('NATS_DLQ_STREAM_NAME')}.{original_subject}"
|
|
173
|
+
await self.nats_jet_stream_context.publish(dlq_subject, json.dumps(message).encode())
|
|
174
|
+
logger.info(f"Moved message to DLQ: {dlq_subject}")
|
|
175
|
+
|
|
176
|
+
async def _register_topic_and_consumer(
|
|
177
|
+
self, consumer_name: str, event_name: str
|
|
178
|
+
) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Register a topic and create a consumer for it.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
consumer_name (str): The name of the consumer to create.
|
|
184
|
+
event_name (str): The event name (subject) to register.
|
|
185
|
+
"""
|
|
186
|
+
await self._create_update_stream(topic=event_name)
|
|
187
|
+
await self._create_consumer(consumer_name=consumer_name, subject=event_name)
|
|
188
|
+
|
|
189
|
+
async def _fetch_messages(
|
|
190
|
+
self, pull_subscription_object: JetStreamContext.PullSubscription
|
|
191
|
+
) -> Msg:
|
|
192
|
+
# fetch only one message
|
|
193
|
+
msgs: List[Msg] = await pull_subscription_object.fetch(batch=1, timeout=3600)
|
|
194
|
+
# get the message object from the list
|
|
195
|
+
message_object = msgs.pop()
|
|
196
|
+
# process the message
|
|
197
|
+
return message_object
|
|
198
|
+
|
|
199
|
+
async def make_connection(self):
|
|
200
|
+
"""
|
|
201
|
+
Connect to the NATS server.
|
|
202
|
+
"""
|
|
203
|
+
if not self.connected:
|
|
204
|
+
await self.nats_client.connect(
|
|
205
|
+
servers=self.servers,
|
|
206
|
+
user=self.user,
|
|
207
|
+
password=self.password,
|
|
208
|
+
error_cb=self._connection_error_callback,
|
|
209
|
+
)
|
|
210
|
+
self.nats_jet_stream_context: JetStreamContext = self.nats_client.jetstream(timeout=10)
|
|
211
|
+
self.connected = True
|
|
212
|
+
|
|
213
|
+
async def close_connection(self):
|
|
214
|
+
"""
|
|
215
|
+
Close the connection to the NATS server.
|
|
216
|
+
"""
|
|
217
|
+
if self.connected:
|
|
218
|
+
try:
|
|
219
|
+
await self.nats_client.close()
|
|
220
|
+
self.connected = False
|
|
221
|
+
except Exception as e:
|
|
222
|
+
raise e
|
|
223
|
+
|
|
224
|
+
async def publish_event(
|
|
225
|
+
self, event_name: str, event_payload: dict
|
|
226
|
+
) -> Union[bool, Exception]:
|
|
227
|
+
"""
|
|
228
|
+
Publish a message to a JetStream subject.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
event_name (str): The subject to publish the message to.
|
|
232
|
+
event_payload (dict): The message to be published.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
bool: True if the event was published successfully.
|
|
236
|
+
"""
|
|
237
|
+
# acknowledgement from the jetstream after successful publish
|
|
238
|
+
ack = await self.nats_jet_stream_context.publish(
|
|
239
|
+
event_name, json.dumps(event_payload).encode("utf-8")
|
|
240
|
+
)
|
|
241
|
+
logger.info(
|
|
242
|
+
f"Published event '{event_payload.get('event_name')}' to subject '{event_name}', ack: {ack}"
|
|
243
|
+
)
|
|
244
|
+
return ack
|
|
245
|
+
|
|
246
|
+
async def request_event(
|
|
247
|
+
self, event_name: str, event_payload: dict, timeout: float = 10.0
|
|
248
|
+
) -> Union[dict, Exception]:
|
|
249
|
+
"""
|
|
250
|
+
Send a request and wait for a response.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
event_name (str): The subject to publish the message to.
|
|
254
|
+
event_payload (dict): The message to be published.
|
|
255
|
+
timeout (float): The timeout for the request.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
dict: The response message.
|
|
259
|
+
"""
|
|
260
|
+
response = await self.nats_client.request(
|
|
261
|
+
event_name, json.dumps(event_payload).encode("utf-8"), timeout=timeout
|
|
262
|
+
)
|
|
263
|
+
return json.loads(response.data.decode("utf-8"))
|
|
264
|
+
|
|
265
|
+
async def subscribe_async_event(
|
|
266
|
+
self, event_name: str, callback: Callable[[Any], None]
|
|
267
|
+
):
|
|
268
|
+
"""
|
|
269
|
+
Subscribe to a JetStream subject with a durable consumer and process messages asynchronously.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
event_name (str): The subject to subscribe to.
|
|
273
|
+
callback (Callable[[Any], None]): A callback function to handle received messages.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
RuntimeError: If unable to subscribe to the event.
|
|
277
|
+
"""
|
|
278
|
+
# consumer_name is a persistent identifier for a consumer that ensures message
|
|
279
|
+
# consumption state is retained even if the client disconnects or the server restarts.
|
|
280
|
+
consumer_name = app_config.get("APP_NAME") + f"-{event_name}"
|
|
281
|
+
|
|
282
|
+
await self._register_topic_and_consumer(
|
|
283
|
+
consumer_name=consumer_name, event_name=event_name
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
pull_subscription_object: JetStreamContext.PullSubscription = (
|
|
287
|
+
await self.nats_jet_stream_context.pull_subscribe(subject=event_name, durable=consumer_name)
|
|
288
|
+
)
|
|
289
|
+
logger.info(f"Subscribed to async event on subject '{event_name}'")
|
|
290
|
+
while True:
|
|
291
|
+
try:
|
|
292
|
+
message_object: Msg = await self._fetch_messages(
|
|
293
|
+
pull_subscription_object
|
|
294
|
+
)
|
|
295
|
+
# process the message
|
|
296
|
+
event_data: dict = json.loads(message_object.data.decode())
|
|
297
|
+
has_job_failed = False
|
|
298
|
+
try:
|
|
299
|
+
callback(event_data)
|
|
300
|
+
# acknowledge the message after processing and finishing the job
|
|
301
|
+
await message_object.ack()
|
|
302
|
+
logger.info(f"Acknowledged job: {event_data}")
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(
|
|
305
|
+
f"Invoking callback during execution of {event_name} failed due to {str(e)}"
|
|
306
|
+
)
|
|
307
|
+
has_job_failed = True
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
has_job_failed
|
|
311
|
+
and message_object.metadata.num_delivered == self.max_delivery_count
|
|
312
|
+
):
|
|
313
|
+
logger.warning("Moving event to the dead letter topic after max retries")
|
|
314
|
+
await self._create_dlq_stream()
|
|
315
|
+
await self._move_to_dlq(event_name, event_data)
|
|
316
|
+
await message_object.ack() # acknowledge to remove from original queue
|
|
317
|
+
|
|
318
|
+
# TODO - What to do failed jobs ?
|
|
319
|
+
# failed jobs are moved to dead letter topic after max retries for now
|
|
320
|
+
except NATSTimeoutError:
|
|
321
|
+
logger.info("No more messages, will pull again...")
|
|
322
|
+
|
|
323
|
+
async def subscribe_sync_event(
|
|
324
|
+
self, event_name: str, callback: Callable[[Any], None]
|
|
325
|
+
):
|
|
326
|
+
"""
|
|
327
|
+
Subscribe to a NATS subject and process the message synchronously.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
event_name (str): The subject to subscribe to.
|
|
331
|
+
callback (Callable[[Any], None]): A callback function to handle received messages.
|
|
332
|
+
"""
|
|
333
|
+
await self.nats_client.subscribe(event_name, cb=callback)
|
|
334
|
+
logger.info(f"Subscribed to sync event on subject '{event_name}'")
|
|
335
|
+
|
|
336
|
+
def delete_message(self, receipt_handle: str):
|
|
337
|
+
pass
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
from nats.aio.client import Client as NATS
|
|
4
|
-
from nats.js.api import StreamConfig, ConsumerConfig, DeliverPolicy
|
|
5
|
-
from typing import Callable, Any, List, Union, Dict
|
|
6
|
-
from shared_kernel.interfaces import DataBus
|
|
7
|
-
|
|
8
|
-
logging.getLogger().setLevel(logging.INFO)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class NATSDataBus(DataBus):
|
|
12
|
-
"""
|
|
13
|
-
A NATS interface class to handle both standard NATS and JetStream operations.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
_instance = None
|
|
17
|
-
|
|
18
|
-
def __new__(cls, *args, **kwargs):
|
|
19
|
-
if cls._instance is None:
|
|
20
|
-
cls._instance = super(NATSDataBus, cls).__new__(cls)
|
|
21
|
-
return cls._instance
|
|
22
|
-
|
|
23
|
-
def __init__(self, config: Dict = None):
|
|
24
|
-
"""
|
|
25
|
-
Initialize the NATSDataBus.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
config (Dict): A dictionary containing the NATS configuration.
|
|
29
|
-
"""
|
|
30
|
-
if not hasattr(self, "initialized"): # Prevent reinitialization
|
|
31
|
-
super().__init__()
|
|
32
|
-
self.nc = NATS()
|
|
33
|
-
self.servers = config.get('servers')
|
|
34
|
-
self.user = config.get('user')
|
|
35
|
-
self.password = config.get('password')
|
|
36
|
-
self.connected = False
|
|
37
|
-
self.js = None # JetStream context
|
|
38
|
-
self.initialized = True
|
|
39
|
-
self.stream_name = config.get('stream_name', '')
|
|
40
|
-
self.event_names = config.get('event_names', [])
|
|
41
|
-
|
|
42
|
-
if self.stream_name and self.event_names:
|
|
43
|
-
self.create_stream(self.stream_name, self.event_names)
|
|
44
|
-
|
|
45
|
-
async def make_connection(self):
|
|
46
|
-
"""
|
|
47
|
-
Connect to the NATS server.
|
|
48
|
-
"""
|
|
49
|
-
if not self.connected:
|
|
50
|
-
await self.nc.connect(
|
|
51
|
-
servers=self.servers,
|
|
52
|
-
user=self.user,
|
|
53
|
-
password=self.password
|
|
54
|
-
)
|
|
55
|
-
self.js = self.nc.jetstream(timeout=10)
|
|
56
|
-
self.connected = True
|
|
57
|
-
|
|
58
|
-
async def close_connection(self):
|
|
59
|
-
"""
|
|
60
|
-
Close the connection to the NATS server.
|
|
61
|
-
"""
|
|
62
|
-
if self.connected:
|
|
63
|
-
try:
|
|
64
|
-
await self.nc.close()
|
|
65
|
-
self.connected = False
|
|
66
|
-
except Exception as e:
|
|
67
|
-
raise e
|
|
68
|
-
|
|
69
|
-
async def create_stream(self, stream_name: str, event_names: List[Any]):
|
|
70
|
-
"""
|
|
71
|
-
Create a stream for event names to persist the messages.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
stream_name (str): The name of the stream.
|
|
75
|
-
event_names (List[Any]): The subjects whose messages will be persisted.
|
|
76
|
-
"""
|
|
77
|
-
try:
|
|
78
|
-
self.stream_name = stream_name
|
|
79
|
-
# Check if the stream already exists
|
|
80
|
-
await self.js.stream_info(stream_name)
|
|
81
|
-
logging.info(f"Stream '{stream_name}' already exists.")
|
|
82
|
-
except Exception:
|
|
83
|
-
# Stream does not exist, so create it
|
|
84
|
-
stream_config = StreamConfig(
|
|
85
|
-
name=stream_name,
|
|
86
|
-
subjects=event_names,
|
|
87
|
-
max_age=600 # Retain messages for 10 minutes
|
|
88
|
-
)
|
|
89
|
-
await self.js.add_stream(stream_config)
|
|
90
|
-
logging.info(f"Stream '{stream_name}' created.")
|
|
91
|
-
|
|
92
|
-
async def publish_event(
|
|
93
|
-
self, event_name: str, event_payload: dict
|
|
94
|
-
) -> Union[bool, Exception]:
|
|
95
|
-
"""
|
|
96
|
-
Publish a message to a JetStream subject.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
event_name (str): The subject to publish the message to.
|
|
100
|
-
event_payload (dict): The message to be published.
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
bool: True if the event was published successfully.
|
|
104
|
-
"""
|
|
105
|
-
ack = await self.js.publish(
|
|
106
|
-
event_name, json.dumps(event_payload).encode("utf-8")
|
|
107
|
-
)
|
|
108
|
-
logging.info(
|
|
109
|
-
f"Published event '{event_payload.get('event_name')}' to subject '{event_name}', ack: {ack}"
|
|
110
|
-
)
|
|
111
|
-
return True
|
|
112
|
-
|
|
113
|
-
async def request_event(
|
|
114
|
-
self, event_name: str, event_payload: dict, timeout: float = 10.0
|
|
115
|
-
) -> Union[dict, Exception]:
|
|
116
|
-
"""
|
|
117
|
-
Send a request and wait for a response.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
event_name (str): The subject to publish the message to.
|
|
121
|
-
event_payload (dict): The message to be published.
|
|
122
|
-
timeout (float): The timeout for the request.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
dict: The response message.
|
|
126
|
-
"""
|
|
127
|
-
response = await self.nc.request(
|
|
128
|
-
event_name, json.dumps(event_payload).encode("utf-8"), timeout=timeout
|
|
129
|
-
)
|
|
130
|
-
return json.loads(response.data.decode("utf-8"))
|
|
131
|
-
|
|
132
|
-
async def subscribe_async_event(
|
|
133
|
-
self, event_name: str, callback: Callable[[Any], None], durable_name: str
|
|
134
|
-
):
|
|
135
|
-
"""
|
|
136
|
-
Subscribe to a JetStream subject with a durable consumer and process messages asynchronously.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
event_name (str): The subject to subscribe to.
|
|
140
|
-
callback (Callable[[Any], None]): A callback function to handle received messages.
|
|
141
|
-
durable_name (str): The name of the durable consumer.
|
|
142
|
-
"""
|
|
143
|
-
try:
|
|
144
|
-
# Check if the consumer already exists
|
|
145
|
-
await self.js.consumer_info(stream=self.stream_name, name=durable_name)
|
|
146
|
-
logging.info(f"Consumer '{durable_name}' already exists.")
|
|
147
|
-
except Exception:
|
|
148
|
-
# Consumer does not exist, so create it
|
|
149
|
-
self.consumer_config = ConsumerConfig(
|
|
150
|
-
name=durable_name,
|
|
151
|
-
durable_name=durable_name,
|
|
152
|
-
deliver_policy=DeliverPolicy.ALL,
|
|
153
|
-
deliver_subject=durable_name,
|
|
154
|
-
max_deliver=1
|
|
155
|
-
)
|
|
156
|
-
await self.js.add_consumer(stream=self.stream_name, config=self.consumer_config)
|
|
157
|
-
logging.info(f"Consumer '{durable_name}' created.")
|
|
158
|
-
|
|
159
|
-
await self.js.subscribe_bind(
|
|
160
|
-
stream=self.stream_name, cb=callback, config=self.consumer_config,
|
|
161
|
-
consumer=durable_name
|
|
162
|
-
)
|
|
163
|
-
logging.info(f"Subscribed to async event on subject '{event_name}'")
|
|
164
|
-
|
|
165
|
-
async def subscribe_sync_event(self, event_name: str, callback: Callable[[Any], None]):
|
|
166
|
-
"""
|
|
167
|
-
Subscribe to a NATS subject and process the message synchronously.
|
|
168
|
-
|
|
169
|
-
Args:
|
|
170
|
-
event_name (str): The subject to subscribe to.
|
|
171
|
-
callback (Callable[[Any], None]): A callback function to handle received messages.
|
|
172
|
-
"""
|
|
173
|
-
await self.nc.subscribe(event_name, cb=callback)
|
|
174
|
-
logging.info(f"Subscribed to sync event on subject '{event_name}'")
|
|
175
|
-
|
|
176
|
-
def delete_message(self, receipt_handle: str):
|
|
177
|
-
pass
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/requires.txt
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/fdc_shared_kernel.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/async_task_executor/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/enums/async_task_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/custom_exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/exceptions/http_exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/http/httpx_http_client.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/http/request_http_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/messaging/aws_databus.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/messaging/http_databus.py
RENAMED
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/metrics/status_tracker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/security/key_vault/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/config/__init__.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/config/test_config.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/logger/__init__.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/logger/test_logger.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/messaging/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/tests/utils/test_string_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/data_validators_utils.py
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.40 → fdc_shared_kernel-0.0.41}/shared_kernel/utils/date_format_utils.py
RENAMED
|
File without changes
|
|
File without changes
|