fdc-shared-kernel 0.0.89__tar.gz → 0.0.91__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 (98) hide show
  1. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/PKG-INFO +1 -1
  2. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/fdc_shared_kernel.egg-info/PKG-INFO +1 -1
  3. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/fdc_shared_kernel.egg-info/SOURCES.txt +5 -1
  4. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/fdc_shared_kernel.egg-info/top_level.txt +1 -0
  5. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/pyproject.toml +1 -1
  6. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/event_executor/event_executor.py +2 -3
  7. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/aws_databus.py +2 -1
  8. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/utils/aws_utility.py +116 -3
  9. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/utils/event_messages.py +1 -0
  10. fdc_shared_kernel-0.0.91/tests/__init__.py +3 -0
  11. fdc_shared_kernel-0.0.91/tests/messaging/__init__.py +3 -0
  12. fdc_shared_kernel-0.0.91/tests/messaging/utils/__init__.py +3 -0
  13. fdc_shared_kernel-0.0.91/tests/messaging/utils/test_aws_utility.py +247 -0
  14. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/README.md +0 -0
  15. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/README_pypi.md +0 -0
  16. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
  17. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/fdc_shared_kernel.egg-info/requires.txt +0 -0
  18. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/requirements.txt +0 -0
  19. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/setup.cfg +0 -0
  20. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/__init__.py +0 -0
  21. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/async_task_executor/__init__.py +0 -0
  22. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/async_task_executor/async_task_executor.py +0 -0
  23. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/auth/__init__.py +0 -0
  24. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/auth/jwt_helper.py +0 -0
  25. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/auth/token_handler.py +0 -0
  26. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/auth/workbook_permission_handler.py +0 -0
  27. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/config/__init__.py +0 -0
  28. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/database/__init__.py +0 -0
  29. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/__init__.py +0 -0
  30. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/__init__.py +0 -0
  31. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/csv.py +0 -0
  32. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/db2.py +0 -0
  33. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/jira.py +0 -0
  34. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/mssql.py +0 -0
  35. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/mysql.py +0 -0
  36. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/oracle.py +0 -0
  37. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/postgres.py +0 -0
  38. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/redshift.py +0 -0
  39. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/connectors_to_system/salesforce.py +0 -0
  40. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/system_to_warehouse/__init__.py +0 -0
  41. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/system_to_warehouse/postgres.py +0 -0
  42. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/datatype_mappings/system_to_warehouse/redshift.py +0 -0
  43. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/enums/__init__.py +0 -0
  44. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/enums/async_task_executor.py +0 -0
  45. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/enums/status_tracker.py +0 -0
  46. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/event_executor/__init__.py +0 -0
  47. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/event_executor/utils.py +0 -0
  48. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/__init__.py +0 -0
  49. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/configuration_exceptions.py +0 -0
  50. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/custom_exceptions.py +0 -0
  51. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
  52. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/http_exceptions.py +0 -0
  53. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
  54. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/operational_exceptions.py +0 -0
  55. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/exceptions/security_exceptions.py +0 -0
  56. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/http/__init__.py +0 -0
  57. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/http/httpx_http_client.py +0 -0
  58. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/http/request_http_client.py +0 -0
  59. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/interfaces/__init__.py +0 -0
  60. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/interfaces/databus.py +0 -0
  61. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/interfaces/http.py +0 -0
  62. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/interfaces/keyvault.py +0 -0
  63. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/logger/__init__.py +0 -0
  64. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/__init__.py +0 -0
  65. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/http_databus.py +0 -0
  66. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/nats_databus.py +0 -0
  67. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/nats_publisher.py +0 -0
  68. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/messaging/nats_test.py +0 -0
  69. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/metrics/__init__.py +0 -0
  70. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/metrics/status_tracker.py +0 -0
  71. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/models/__init__.py +0 -0
  72. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/registries/__init__.py +0 -0
  73. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/registries/service_event_registry.py +0 -0
  74. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/security/__init__.py +0 -0
  75. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/security/key_vault/__init__.py +0 -0
  76. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/security/key_vault/aws_secret_manager.py +0 -0
  77. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/security/key_vault/azure_keyvault.py +0 -0
  78. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/status_tracker/__init__.py +0 -0
  79. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/status_tracker/status_tracker.py +0 -0
  80. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/__init__.py +0 -0
  81. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/config/__init__.py +0 -0
  82. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/config/test_config.py +0 -0
  83. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/logger/__init__.py +0 -0
  84. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/logger/test_logger.py +0 -0
  85. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/messaging/__init__.py +0 -0
  86. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/messaging/test_aws_databus.py +0 -0
  87. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/messaging/test_event_executor.py +0 -0
  88. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/messaging/test_nats_interface.py +0 -0
  89. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/utils/__init__.py +0 -0
  90. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/utils/test_data_validators.py +0 -0
  91. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
  92. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/tests/utils/test_string_utils.py +0 -0
  93. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/__init__.py +0 -0
  94. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/data_validators_utils.py +0 -0
  95. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/date_format_utils.py +0 -0
  96. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/string_utils.py +0 -0
  97. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/template_renderer.py +0 -0
  98. {fdc_shared_kernel-0.0.89 → fdc_shared_kernel-0.0.91}/shared_kernel/utils/thread_local_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fdc_shared_kernel
3
- Version: 0.0.89
3
+ Version: 0.0.91
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.4
2
2
  Name: fdc_shared_kernel
3
- Version: 0.0.89
3
+ Version: 0.0.91
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
@@ -89,4 +89,8 @@ shared_kernel/utils/data_validators_utils.py
89
89
  shared_kernel/utils/date_format_utils.py
90
90
  shared_kernel/utils/string_utils.py
91
91
  shared_kernel/utils/template_renderer.py
92
- shared_kernel/utils/thread_local_util.py
92
+ shared_kernel/utils/thread_local_util.py
93
+ tests/__init__.py
94
+ tests/messaging/__init__.py
95
+ tests/messaging/utils/__init__.py
96
+ tests/messaging/utils/test_aws_utility.py
@@ -3,3 +3,4 @@ dist
3
3
  docs
4
4
  release_notes
5
5
  shared_kernel
6
+ tests
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fdc_shared_kernel"
7
- version = "0.0.89"
7
+ version = "0.0.91"
8
8
  requires-python = ">=3.7"
9
9
  readme = "README_pypi.md"
10
10
  description = "Shared library for microservice"
@@ -206,7 +206,7 @@ class EventExecutor:
206
206
  else:
207
207
  self._stats[event_name].failed_events += 1
208
208
 
209
- def _callback_wrapper(self, callback: Callable[[Any], None], message: dict) -> None:
209
+ def _callback_wrapper(self, callback: Callable[[Any], None], event_msg: AWSEventMessage) -> None:
210
210
  """
211
211
  Wrapper around message processing to handle cleanup and status updates.
212
212
  """
@@ -214,8 +214,7 @@ class EventExecutor:
214
214
  event_name = None
215
215
 
216
216
  try:
217
- logger.info(f"Initiating callback for message: {message}")
218
- event_msg = AWSEventMessage(message)
217
+ logger.info(f"Initiating callback for message: {event_msg}")
219
218
  event_name = event_msg.event_name
220
219
 
221
220
  # Set thread-local context
@@ -120,7 +120,8 @@ class AWSDataBus(DataBus):
120
120
  def get_async_message(self, event_name):
121
121
  queue = self.event_queue_mapper[event_name]
122
122
  # get_message_from_queue should be blocking
123
- message = self.aws_utility.get_message_from_queue(queue)
123
+ message = self.aws_utility.recieve_event(queue)
124
+ # message = self.aws_utility.get_message_from_queue(queue)
124
125
  return message
125
126
 
126
127
  def delete_message(self, message: AWSEventMessage):
@@ -1,12 +1,13 @@
1
1
  import json
2
2
  from typing import Dict, Optional, Union
3
+ import uuid
3
4
 
4
5
  import boto3
5
6
  from botocore.exceptions import ClientError
6
7
 
7
8
  from shared_kernel.config import Config
8
9
  from shared_kernel.logger import Logger
9
- from shared_kernel.messaging.utils.event_messages import PublishEventMessage
10
+ from shared_kernel.messaging.utils.event_messages import AWSEventMessage, PublishEventMessage
10
11
 
11
12
  app_config = Config()
12
13
  logger = Logger(app_config.get("APP_NAME"))
@@ -29,11 +30,13 @@ class AWSMessagingUtility:
29
30
  def __init__(self):
30
31
  self.event_bridge = boto3.client("events")
31
32
  self.sqs = boto3.client("sqs")
33
+ self.s3 = boto3.client("s3")
32
34
  self.event_bus_name = app_config.get("EVENT_BUS_NAME")
33
35
  self.service_name = app_config.get("APP_NAME")
34
36
  self.account_id = app_config.get("ACCOUNT_ID")
35
37
  self.region = app_config.get("AWS_REGION")
36
38
  self.queue_visiblity_timeout = app_config.get("QUEUE_VISIBLITY_TIMEOUT")
39
+ self.s3_bucket = app_config.get("AWS_EVENT_PAYLOAD_BUCKET")
37
40
 
38
41
  def create_queue(self, event_name: str) -> AWSQueue:
39
42
  """
@@ -216,6 +219,49 @@ class AWSMessagingUtility:
216
219
  f"Message deleted from queue with receipt handle '{receipt_handle}'."
217
220
  )
218
221
 
222
+ def _check_payload_size(self, json_payload: str) -> bool:
223
+ """
224
+ Check if the payload size exceeds 256 KB.
225
+
226
+ Args:
227
+ json_payload (str): The JSON payload to check
228
+
229
+ Returns:
230
+ bool: True if payload size exceeds 256 KB, False otherwise
231
+ """
232
+ return len(json_payload.encode('utf-8')) > 262144 # 256 KB in bytes
233
+
234
+ def _upload_payload_to_s3(self, json_payload: str) -> str:
235
+ """
236
+ Upload large payload to S3 and return the S3 path.
237
+
238
+ Args:
239
+ json_payload (str): The JSON payload to upload
240
+
241
+ Returns:
242
+ str: The S3 path where the payload is stored
243
+ """
244
+ try:
245
+ # Generate a unique filename using UUID
246
+ file_name = f"event_payload/{str(uuid.uuid4())}.txt"
247
+
248
+ # Upload to S3
249
+ self.s3.put_object(
250
+ Bucket=self.s3_bucket,
251
+ Key=file_name,
252
+ Body=json_payload,
253
+ ContentType='application/json'
254
+ )
255
+
256
+ # Return the S3 path
257
+ s3_path = f"s3://{self.s3_bucket}/{file_name}"
258
+ logger.info(f"Uploaded large payload to S3: {s3_path}")
259
+ return s3_path
260
+
261
+ except Exception as e:
262
+ logger.error(f"Failed to upload payload to S3: {e}")
263
+ raise
264
+
219
265
  def publish_event(self, event_name: str, event_payload: dict) -> Union[bool, Optional[dict]]:
220
266
  """
221
267
  Publish an event to EventBridge.
@@ -227,12 +273,26 @@ class AWSMessagingUtility:
227
273
  """
228
274
  try:
229
275
  publish_message = PublishEventMessage(event_payload)
276
+ json_payload = publish_message.to_json()
277
+
278
+ # Check if payload size exceeds 256 KB
279
+ if self._check_payload_size(json_payload):
280
+ # Upload to S3 and get the path
281
+ s3_path = self._upload_payload_to_s3(json_payload)
282
+
283
+ # Replace the payload with the S3 path and remove event_payload
284
+ event_payload['event_payload_path'] = s3_path
285
+ del event_payload['event_payload']
286
+ publish_message = PublishEventMessage(event_payload)
287
+ json_payload = publish_message.to_json()
288
+ logger.warning(f"Event payload size exceeded 256 KB limit. Using S3 path: {s3_path}")
289
+
230
290
  response: dict = self.event_bridge.put_events(
231
291
  Entries=[
232
292
  {
233
293
  "Source": self.service_name,
234
294
  "DetailType": event_name,
235
- "Detail": publish_message.to_json(),
295
+ "Detail": json_payload,
236
296
  "EventBusName": self.event_bus_name,
237
297
  }
238
298
  ]
@@ -256,7 +316,60 @@ class AWSMessagingUtility:
256
316
  except Exception as e:
257
317
  logger.error(f"Failed to publish event: {e}")
258
318
  return False, None
259
-
319
+
320
+ def _get_payload_from_s3(self, s3_path: str) -> str:
321
+ """
322
+ Retrieve and decode payload from S3 bucket.
323
+
324
+ Args:
325
+ s3_path (str): The S3 path where the payload is stored
326
+
327
+ Returns:
328
+ str: The decoded payload content
329
+
330
+ Raises:
331
+ ClientError: If there's an error accessing the S3 bucket
332
+ ValueError: If the S3 path is invalid
333
+ """
334
+ try:
335
+ # Extract bucket and key from s3:// path
336
+ if not s3_path.startswith('s3://'):
337
+ raise ValueError(f"Invalid S3 path format: {s3_path}")
338
+
339
+ path_parts = s3_path.replace('s3://', '').split('/', 1)
340
+ if len(path_parts) != 2:
341
+ raise ValueError(f"Invalid S3 path format: {s3_path}")
342
+
343
+ bucket, key = path_parts
344
+
345
+ s3_object = self.s3.get_object(Bucket=bucket, Key=key)
346
+ return s3_object["Body"].read().decode("utf-8")
347
+
348
+ except ClientError as e:
349
+ logger.error(f"Failed to retrieve payload from S3: {e}")
350
+ raise
351
+ except Exception as e:
352
+ logger.error(f"Unexpected error while retrieving payload from S3: {e}")
353
+ raise
354
+
355
+ def recieve_event(self, queue):
356
+ """
357
+ Receive and process an event from the queue, handling both direct payloads and S3-stored payloads.
358
+
359
+ Args:
360
+ queue (AWSQueue): The queue to receive the message from
361
+
362
+ Returns:
363
+ AWSEventMessage: The processed event message
364
+ """
365
+ deserialized_message = self.get_message_from_queue(queue)
366
+ event_msg = AWSEventMessage(deserialized_message)
367
+
368
+ if event_msg.event_payload_path:
369
+ event_msg.event_payload = json.loads(self._get_payload_from_s3(event_msg.event_payload_path))["event_payload"]
370
+
371
+ return event_msg
372
+
260
373
  def get_queue_url_arn(self, event_name: str) -> dict:
261
374
  queue_name = f"{self.service_name}-{event_name}"
262
375
  queue_arn = f"arn:aws:sqs:{self.region}:{self.account_id}:{queue_name}"
@@ -158,6 +158,7 @@ class EventMessage:
158
158
  # event payload will contain entity_id, connector_id and other info
159
159
  # needed for specific events
160
160
  self.event_payload: dict = raw_message.get("event_payload", {})
161
+ self.event_payload_path = raw_message.get("event_payload_path", False)
161
162
 
162
163
  # initialize EventMeta object
163
164
  self.event_meta = EventMeta(raw_message.get("event_meta", {}))
@@ -0,0 +1,3 @@
1
+ """
2
+ Test package for shared_kernel.
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Test package for messaging module.
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Test package for messaging utils module.
3
+ """
@@ -0,0 +1,247 @@
1
+ import json
2
+ import pytest
3
+ from unittest.mock import patch, MagicMock
4
+ from moto.sqs import mock_sqs
5
+ from moto.events import mock_events
6
+ from moto.s3 import mock_s3
7
+ import boto3
8
+
9
+ from shared_kernel.messaging.utils.aws_utility import AWSMessagingUtility, AWSQueue
10
+ from shared_kernel.messaging.utils.event_messages import AWSEventMessage, PublishEventMessage
11
+
12
+ @pytest.fixture
13
+ def aws_utility():
14
+ mock_config = MagicMock()
15
+ mock_config.get.side_effect = lambda key: {
16
+ "APP_NAME": "test-app",
17
+ "EVENT_BUS_NAME": "test-bus",
18
+ "ACCOUNT_ID": "123456789012",
19
+ "AWS_REGION": "us-east-1",
20
+ "QUEUE_VISIBLITY_TIMEOUT": "30",
21
+ "AWS_EVENT_PAYLOAD_BUCKET": "test-bucket"
22
+ }[key]
23
+
24
+ with patch('shared_kernel.messaging.utils.aws_utility.app_config', mock_config):
25
+ utility = AWSMessagingUtility()
26
+ return utility
27
+
28
+ @pytest.fixture
29
+ def sample_event_payload():
30
+ return {
31
+ "event_name": "test-event",
32
+ "event_id": "test-event-id",
33
+ "event_payload": {"key": "value"},
34
+ "event_meta": {
35
+ "trace_id": "test-trace-id",
36
+ "span_id": "test-span-id",
37
+ "timestamp": "2024-04-17T13:54:14.479Z"
38
+ }
39
+ }
40
+
41
+ @pytest.fixture
42
+ def setup_s3(aws_utility):
43
+ with mock_s3():
44
+ s3 = boto3.client('s3', region_name='us-east-1')
45
+ s3.create_bucket(Bucket='test-bucket')
46
+ yield s3
47
+
48
+ @pytest.fixture
49
+ def setup_sqs(aws_utility):
50
+ with mock_sqs():
51
+ sqs = boto3.client('sqs', region_name='us-east-1')
52
+ yield sqs
53
+
54
+ @pytest.fixture
55
+ def setup_events(aws_utility):
56
+ with mock_events():
57
+ events = boto3.client('events', region_name='us-east-1')
58
+ events.create_event_bus(Name='test-bus')
59
+ yield events
60
+
61
+ class TestAWSQueue:
62
+ def test_aws_queue_initialization(self):
63
+ queue = AWSQueue("test-arn", "test-url", "test-event")
64
+ assert queue.arn == "test-arn"
65
+ assert queue.url == "test-url"
66
+ assert queue.event_name == "test-event"
67
+
68
+ def test_aws_queue_is_valid(self):
69
+ valid_queue = AWSQueue("test-arn", "test-url", "test-event")
70
+ assert valid_queue.is_valid()
71
+
72
+ invalid_queue = AWSQueue(None, "test-url", "test-event")
73
+ assert not invalid_queue.is_valid()
74
+
75
+ invalid_queue = AWSQueue("test-arn", None, "test-event")
76
+ assert not invalid_queue.is_valid()
77
+
78
+ class TestQueueOperations:
79
+ def test_create_queue(self, aws_utility, setup_sqs):
80
+ queue = aws_utility.create_queue("test-event")
81
+ assert isinstance(queue, AWSQueue)
82
+ assert queue.event_name == "test-event"
83
+ assert "test-app-test-event" in queue.url
84
+
85
+ def test_get_queue(self, aws_utility, setup_sqs):
86
+ # First create a queue
87
+ created_queue = aws_utility.create_queue("test-event")
88
+ # Then get it
89
+ queue = aws_utility.get_queue("test-event")
90
+ assert queue.url == created_queue.url
91
+ assert queue.arn == created_queue.arn
92
+
93
+ def test_check_if_queue_exist(self, aws_utility, setup_sqs):
94
+ # Queue doesn't exist initially
95
+ assert aws_utility.check_if_queue_exist("test-event") is False
96
+
97
+ # Create queue
98
+ aws_utility.create_queue("test-event")
99
+ assert aws_utility.check_if_queue_exist("test-event") is True
100
+
101
+ def test_check_and_update_queue_config(self, aws_utility, setup_sqs):
102
+ queue = aws_utility.create_queue("test-event")
103
+ aws_utility.check_and_update_queue_config(queue)
104
+ # No assertion needed as we're just ensuring no exceptions are raised
105
+
106
+ class TestEventBridgeOperations:
107
+ def test_add_event_bridge_rule(self, aws_utility, setup_sqs, setup_events):
108
+ queue = aws_utility.create_queue("test-event")
109
+ aws_utility.add_event_bridge_rule(queue)
110
+ # Verify rule was created by checking if we can get it
111
+ rules = aws_utility.event_bridge.list_rules(EventBusName='test-bus', NamePrefix="test-event_rule")
112
+ assert len(rules['Rules']) == 1
113
+ rule = rules['Rules'][0]
114
+ assert rule['Name'] == "test-event_rule"
115
+ assert rule['State'] == "ENABLED"
116
+
117
+ def test_publish_event_success(self, aws_utility, setup_events, sample_event_payload):
118
+ success, result = aws_utility.publish_event("test-event", sample_event_payload)
119
+ assert success is True
120
+ assert result is not None
121
+
122
+ def test_publish_event_failure(self, aws_utility):
123
+ with patch('boto3.client') as mock_client:
124
+ mock_client.return_value.put_events.return_value = {
125
+ "FailedEntryCount": 1,
126
+ "Entries": [{"ErrorMessage": "Test error"}]
127
+ }
128
+ success, result = aws_utility.publish_event("test-event", {})
129
+ assert success is False
130
+ assert result is None
131
+
132
+ class TestS3Operations:
133
+ def test_check_payload_size(self, aws_utility):
134
+ small_payload = json.dumps({"key": "value"})
135
+ large_payload = json.dumps({"key": "x" * 300000}) # > 256KB
136
+
137
+ assert aws_utility._check_payload_size(small_payload) is False
138
+ assert aws_utility._check_payload_size(large_payload) is True
139
+
140
+ def test_upload_payload_to_s3(self, aws_utility, setup_s3):
141
+ payload = json.dumps({"key": "value"})
142
+ s3_path = aws_utility._upload_payload_to_s3(payload)
143
+
144
+ assert s3_path.startswith("s3://")
145
+ assert aws_utility.s3_bucket in s3_path
146
+
147
+ def test_get_payload_from_s3(self, aws_utility, setup_s3):
148
+ # First upload a payload
149
+ original_payload = json.dumps({"key": "value"})
150
+ s3_path = aws_utility._upload_payload_to_s3(original_payload)
151
+
152
+ # Then retrieve it
153
+ retrieved_payload = aws_utility._get_payload_from_s3(s3_path)
154
+ assert retrieved_payload == original_payload
155
+
156
+ def test_get_payload_from_s3_invalid_path(self, aws_utility):
157
+ with pytest.raises(ValueError):
158
+ aws_utility._get_payload_from_s3("invalid-path")
159
+
160
+ class TestMessageOperations:
161
+ def test_get_message_from_queue(self, aws_utility, setup_sqs):
162
+ queue = aws_utility.create_queue("test-event")
163
+ # Send a message to the queue
164
+ aws_utility.sqs.send_message(
165
+ QueueUrl=queue.url,
166
+ MessageBody=json.dumps({"test": "message"})
167
+ )
168
+ message = aws_utility.get_message_from_queue(queue)
169
+ assert message is not None
170
+ assert "test" in json.loads(message["Body"])
171
+
172
+ def test_delete_message_from_queue(self, aws_utility, setup_sqs):
173
+ queue = aws_utility.create_queue("test-event")
174
+ # Send and receive a message
175
+ aws_utility.sqs.send_message(
176
+ QueueUrl=queue.url,
177
+ MessageBody=json.dumps({"test": "message"})
178
+ )
179
+ message = aws_utility.get_message_from_queue(queue)
180
+ # Delete the message
181
+ aws_utility.delete_message_from_queue(queue, message["ReceiptHandle"])
182
+ # Try to receive messages again, should be empty
183
+ response = aws_utility.sqs.receive_message(QueueUrl=queue.url)
184
+ assert "Messages" not in response
185
+
186
+ def test_recieve_event_with_direct_payload(self, aws_utility, setup_sqs):
187
+ queue = aws_utility.create_queue("test-event")
188
+ # Send a message with the correct event structure
189
+ aws_utility.sqs.send_message(
190
+ QueueUrl=queue.url,
191
+ MessageBody=json.dumps({
192
+ "detail": {
193
+ "event_name": "test-event",
194
+ "event_payload": "test",
195
+ "metadata": {
196
+ "trace_id": "test-trace-id",
197
+ "timestamp": "2024-04-17T13:54:14.479Z"
198
+ }
199
+ }
200
+ })
201
+ )
202
+ event_msg = aws_utility.recieve_event(queue)
203
+ assert event_msg is not None
204
+ assert event_msg.event_name == "test-event"
205
+ assert event_msg.event_payload == "test"
206
+
207
+ def test_recieve_event_with_s3_payload(self, aws_utility, setup_sqs, setup_s3):
208
+ queue = aws_utility.create_queue("test-event")
209
+ # Upload payload to S3 with proper JSON format
210
+ payload = json.dumps({"event_payload": "test payload"})
211
+ s3_path = aws_utility._upload_payload_to_s3(payload)
212
+ # Send a message with S3 path
213
+ aws_utility.sqs.send_message(
214
+ QueueUrl=queue.url,
215
+ MessageBody=json.dumps({
216
+ "detail": {
217
+ "event_name": "test-event",
218
+ "event_payload_path": s3_path,
219
+ "event_meta": {
220
+ "trace_id": "test-trace-id",
221
+ "span_id": "test-span-id",
222
+ "timestamp": "2024-04-17T13:54:14.479Z"
223
+ }
224
+ }
225
+ })
226
+ )
227
+ event_msg = aws_utility.recieve_event(queue)
228
+ assert event_msg.event_payload == "test payload"
229
+
230
+ def test_update_visibility_timeout(self, aws_utility, setup_sqs):
231
+ queue = aws_utility.create_queue("test-event")
232
+ # Send and receive a message
233
+ aws_utility.sqs.send_message(
234
+ QueueUrl=queue.url,
235
+ MessageBody=json.dumps({"test": "message"})
236
+ )
237
+ message = aws_utility.get_message_from_queue(queue)
238
+ # Update visibility timeout
239
+ aws_utility.update_visibility_timeout(queue.url, message["ReceiptHandle"], 30)
240
+ # No assertion needed as we're just ensuring no exceptions are raised
241
+
242
+ def test_get_queue_url_arn(self, aws_utility):
243
+ result = aws_utility.get_queue_url_arn("test-event")
244
+ assert "queue_arn" in result
245
+ assert "queue_url" in result
246
+ assert "test-event" in result["queue_arn"]
247
+ assert "test-event" in result["queue_url"]