fdc-shared-kernel 0.0.185__tar.gz → 0.0.186__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 (195) hide show
  1. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/PKG-INFO +1 -1
  2. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/PKG-INFO +1 -1
  3. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/pyproject.toml +1 -1
  4. fdc_shared_kernel-0.0.186/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +302 -0
  5. fdc_shared_kernel-0.0.185/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +0 -178
  6. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/README.md +0 -0
  7. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/README_pypi.md +0 -0
  8. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/SOURCES.txt +0 -0
  9. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
  10. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/requires.txt +0 -0
  11. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/top_level.txt +0 -0
  12. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/requirements.txt +0 -0
  13. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/setup.cfg +0 -0
  14. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/__init__.py +0 -0
  15. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/__init__.py +0 -0
  16. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/agent_channel.py +0 -0
  17. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/contexts.py +0 -0
  18. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/event_channel.py +0 -0
  19. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_audit_log/__init__.py +0 -0
  20. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_chat_persistence/__init__.py +0 -0
  21. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_chat_persistence/chat_persistence.py +0 -0
  22. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/async_task_executor/__init__.py +0 -0
  23. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/async_task_executor/async_task_executor.py +0 -0
  24. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/__init__.py +0 -0
  25. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/jwt_helper.py +0 -0
  26. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/token_handler.py +0 -0
  27. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/workbook_permission_handler.py +0 -0
  28. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/__init__.py +0 -0
  29. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/batch_job_status_tracker/container_job_status_tracker.py +0 -0
  30. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/__init__.py +0 -0
  31. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/aws_batch_job_handler.py +0 -0
  32. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/azure_apps_job_handler.py +0 -0
  33. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/job_handler_factory.py +0 -0
  34. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/job_status_mapper.py +0 -0
  35. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/__init__.py +0 -0
  36. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/azure_blob_storage_manager.py +0 -0
  37. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/job_storage_manager_factory.py +0 -0
  38. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/s3_job_storage_manager.py +0 -0
  39. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/config/__init__.py +0 -0
  40. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/constants/constants.py +0 -0
  41. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/__init__.py +0 -0
  42. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/databricks_warehouse_connection.py +0 -0
  43. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/jdbc_warehouse_connection.py +0 -0
  44. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/mssql_warehouse_connection.py +0 -0
  45. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/mysql_warehouse_connection.py +0 -0
  46. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/postgresql_warehouse_connection.py +0 -0
  47. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/snowflake_warehouse_connection.py +0 -0
  48. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/custom_query_builders/databricks_query.py +0 -0
  49. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/databricks_handler.py +0 -0
  50. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/factory.py +0 -0
  51. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/jdbc_driver_registry.py +0 -0
  52. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/jdbc_handler.py +0 -0
  53. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/mssql_handler.py +0 -0
  54. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/mysql_handler.py +0 -0
  55. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/odbc_handler.py +0 -0
  56. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/postgresql_handler.py +0 -0
  57. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/databricks_query_executor.py +0 -0
  58. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/jdbc_query_executor.py +0 -0
  59. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/mssql_query_executor.py +0 -0
  60. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/mysql_query_executor.py +0 -0
  61. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/postgresql_query_executor.py +0 -0
  62. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/redshift_query_executor.py +0 -0
  63. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/snowflake_query_executor.py +0 -0
  64. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/redshift_handler.py +0 -0
  65. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/snowflake_handler.py +0 -0
  66. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/utils.py +0 -0
  67. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/warehouse_handlers_map.py +0 -0
  68. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/database/__init__.py +0 -0
  69. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/__init__.py +0 -0
  70. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/aws_batch_job_context.py +0 -0
  71. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/azure_container_app_job_context.py +0 -0
  72. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/event_executor.py +0 -0
  73. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/job_context.py +0 -0
  74. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/job_record_dataclasses.py +0 -0
  75. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/warehouse_configs.py +0 -0
  76. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/__init__.py +0 -0
  77. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/__init__.py +0 -0
  78. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/csv.py +0 -0
  79. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/databricks.py +0 -0
  80. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/db2.py +0 -0
  81. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/jdbc.py +0 -0
  82. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/jira.py +0 -0
  83. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/mssql.py +0 -0
  84. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/mysql.py +0 -0
  85. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/oracle.py +0 -0
  86. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/postgres.py +0 -0
  87. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/redshift.py +0 -0
  88. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/salesforce.py +0 -0
  89. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/snowflake.py +0 -0
  90. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/sqlalchemy.py +0 -0
  91. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/__init__.py +0 -0
  92. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/postgres.py +0 -0
  93. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/redshift.py +0 -0
  94. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/__init__.py +0 -0
  95. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/async_task_executor.py +0 -0
  96. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/connection_engine.py +0 -0
  97. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/status_tracker.py +0 -0
  98. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/__init__.py +0 -0
  99. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/event_executor.py +0 -0
  100. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/job_executor.py +0 -0
  101. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/utils.py +0 -0
  102. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/__init__.py +0 -0
  103. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/batch_job_exceptions.py +0 -0
  104. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/configuration_exceptions.py +0 -0
  105. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/custom_exceptions.py +0 -0
  106. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
  107. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/http_exceptions.py +0 -0
  108. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
  109. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/operational_exceptions.py +0 -0
  110. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/security_exceptions.py +0 -0
  111. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/user_intervention_exception.py +0 -0
  112. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/http/__init__.py +0 -0
  113. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/http/httpx_http_client.py +0 -0
  114. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/http/request_http_client.py +0 -0
  115. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/__init__.py +0 -0
  116. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/base_job_handler.py +0 -0
  117. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/data_warehouse_handler.py +0 -0
  118. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/databus.py +0 -0
  119. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/encryption.py +0 -0
  120. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/http.py +0 -0
  121. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/keyvault.py +0 -0
  122. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/query_executor.py +0 -0
  123. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/storage_manager.py +0 -0
  124. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/warehouse_connection.py +0 -0
  125. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/logger/__init__.py +0 -0
  126. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/__init__.py +0 -0
  127. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/aws_databus.py +0 -0
  128. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/azure_databus.py +0 -0
  129. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/http_databus.py +0 -0
  130. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_databus.py +0 -0
  131. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_publisher.py +0 -0
  132. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_test.py +0 -0
  133. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/aws_utility.py +0 -0
  134. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/azure_utility.py +0 -0
  135. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/event_messages.py +0 -0
  136. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/metrics/__init__.py +0 -0
  137. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/metrics/status_tracker.py +0 -0
  138. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/models/__init__.py +0 -0
  139. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/__init__.py +0 -0
  140. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/schema_onboarding_event_registry.py +0 -0
  141. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/service_event_registry.py +0 -0
  142. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/__init__.py +0 -0
  143. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/base_tracker.py +0 -0
  144. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/enums.py +0 -0
  145. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/exceptions.py +0 -0
  146. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/run_manager.py +0 -0
  147. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/schema_onboarding_tracker.py +0 -0
  148. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/shared_helpers.py +0 -0
  149. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_determination.py +0 -0
  150. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_helpers.py +0 -0
  151. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_manager.py +0 -0
  152. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/template_manager.py +0 -0
  153. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/utility_manager.py +0 -0
  154. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/__init__.py +0 -0
  155. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/encryption/__init__.py +0 -0
  156. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/encryption/manager.py +0 -0
  157. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/encryption/providers.py +0 -0
  158. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/__init__.py +0 -0
  159. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/aws_secret_manager.py +0 -0
  160. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/azure_keyvault.py +0 -0
  161. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/status_tracker/__init__.py +0 -0
  162. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/status_tracker/status_tracker.py +0 -0
  163. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/__init__.py +0 -0
  164. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/blob_file_handler.py +0 -0
  165. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/s3_storage_handler.py +0 -0
  166. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/__init__.py +0 -0
  167. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/config/__init__.py +0 -0
  168. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/config/test_config.py +0 -0
  169. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/logger/__init__.py +0 -0
  170. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/logger/test_logger.py +0 -0
  171. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/__init__.py +0 -0
  172. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_aws_databus.py +0 -0
  173. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_event_executor.py +0 -0
  174. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_nats_interface.py +0 -0
  175. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/security/test_encryption.py +0 -0
  176. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/__init__.py +0 -0
  177. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_data_validators.py +0 -0
  178. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
  179. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_string_utils.py +0 -0
  180. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/traceability/__init__.py +0 -0
  181. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/traceability/traceability_handler.py +0 -0
  182. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/__init__.py +0 -0
  183. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/data_validators_utils.py +0 -0
  184. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/date_format_utils.py +0 -0
  185. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/date_utils.py +0 -0
  186. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/string_utils.py +0 -0
  187. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/template_renderer.py +0 -0
  188. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_debug_utils.py +0 -0
  189. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_local_storage.py +0 -0
  190. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_log_traker.py +0 -0
  191. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/tests/__init__.py +0 -0
  192. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/tests/messaging/__init__.py +0 -0
  193. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/tests/messaging/utils/__init__.py +0 -0
  194. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/tests/messaging/utils/test_aws_utility.py +0 -0
  195. {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.186}/tests/test_job_executor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fdc_shared_kernel
3
- Version: 0.0.185
3
+ Version: 0.0.186
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.185
3
+ Version: 0.0.186
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,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fdc_shared_kernel"
7
- version = "0.0.185"
7
+ version = "0.0.186"
8
8
  requires-python = ">=3.7"
9
9
  readme = "README_pypi.md"
10
10
  description = "Shared library for microservice"
@@ -0,0 +1,302 @@
1
+ import errno
2
+ import hashlib
3
+ import time
4
+ import threading
5
+
6
+ from contextlib import contextmanager
7
+ from typing import Dict
8
+
9
+ from sqlalchemy import create_engine, text
10
+ from sqlalchemy.engine import Engine
11
+ from sqlalchemy.exc import InterfaceError, OperationalError, ProgrammingError
12
+
13
+ from shared_kernel.dataclasses.warehouse_configs import RedshiftConfig
14
+ from shared_kernel.exceptions.http_exceptions import BadRequest
15
+ from shared_kernel.interfaces.warehouse_connection import DataWarehouseConnection
16
+ from shared_kernel.logger import Logger
17
+ from shared_kernel.config import Config
18
+
19
+ config = Config()
20
+ logger = Logger(config.get("APP_NAME"))
21
+
22
+
23
+ class RedshiftWarehouseConnection(DataWarehouseConnection):
24
+
25
+ _engines: Dict[str, Engine] = {}
26
+ _lock = threading.Lock()
27
+ _last_dispose_time: Dict[str, float] = {}
28
+ _monkeypatched = False
29
+
30
+ @classmethod
31
+ def _apply_monkeypatch(cls):
32
+ """
33
+ Dynamically applies the redshift_connector integer OID deserialization patch.
34
+
35
+ WHY THIS IS CRITICAL FOR PRODUCTION:
36
+ - When Redshift executes constant simplification/folding (e.g. 'COUNT(x) * 0'),
37
+ its query optimizer decides to send the integer over the wire as a 4-byte payload.
38
+ - However, the schema definition (RowDescription) declares the column's OID as BIGINT (OID 20).
39
+ - By default, redshift-connector registers OID 20 to the `int8_recv` function, which performs
40
+ a rigid 8-byte struct unpack (`q_unpack`).
41
+ - Because the payload is only 4 bytes, `struct.unpack` fails with a `struct.error` stating
42
+ that the buffer is too small, crashing query executions.
43
+
44
+ HOW THIS MONKEYPATCH RESOLVES IT:
45
+ - We hijack `int8_recv`, `int4_recv`, and `int2_recv` within redshift-connector.
46
+ - Rather than relying on a fixed-width struct unpack, we check the actual length of the
47
+ bytes available in the network packet.
48
+ - We convert the bytes dynamically using `int.from_bytes(..., byteorder="big", signed=True)`
49
+ to safely parse any integer size (1, 2, 4, or 8 bytes) sent by the Redshift engine.
50
+ - We apply this patch at the module-level registry AND wrap the core Connection initialization
51
+ to force every active connection instance to use these patched deserializers, bypassing
52
+ driver version rigidity.
53
+ """
54
+ try:
55
+ # Lazy import driver modules so we only modify namespaces when the connection is used
56
+ import redshift_connector
57
+ import redshift_connector.utils.type_utils as type_utils
58
+ from redshift_connector.utils.type_utils import RedshiftOID, FC_BINARY
59
+
60
+ # Keep references to the original functions to allow safe fallbacks if unpack fails
61
+ original_int8_recv = type_utils.int8_recv
62
+ original_int4_recv = type_utils.int4_recv
63
+ original_int2_recv = type_utils.int2_recv
64
+
65
+ def safe_unpack_int(data: bytes, offset: int, length: int, original_recv_func) -> int:
66
+ """
67
+ A resilient integer parsing helper that reads integer payloads of any length dynamically.
68
+
69
+ Args:
70
+ data (bytes): The raw network packet buffer.
71
+ offset (int): The current offset index in the buffer.
72
+ length (int): The declared byte length of the value.
73
+ original_recv_func (callable): The original fallback driver function.
74
+ """
75
+ try:
76
+ if length > 0:
77
+ # Calculate exactly how many bytes remain in the packet from the current offset
78
+ available = len(data) - offset
79
+ # Determine the maximum safe byte-length we can extract without buffer overflow
80
+ actual_len = min(length, available)
81
+ if actual_len > 0:
82
+ # Redshift transmits integers in network byte order (big-endian).
83
+ # standard python int.from_bytes allows us to parse integers of any byte-length
84
+ # (1, 2, 4, or 8 bytes) safely and dynamically.
85
+ return int.from_bytes(data[offset : offset + actual_len], byteorder="big", signed=True)
86
+
87
+ # Fallback to original driver function if length parameters are missing or zero
88
+ return original_recv_func(data, offset, length)
89
+ except Exception:
90
+ # In case of any unexpected errors during dynamic parsing, attempt the original function
91
+ try:
92
+ return original_recv_func(data, offset, length)
93
+ except Exception:
94
+ # Final safe guard to prevent the service from crashing on malformed columns
95
+ return 0
96
+
97
+ # Wrap each receiver function to utilize our safe_unpack_int helper
98
+ def patched_int8_recv(data: bytes, offset: int, length: int) -> int:
99
+ return safe_unpack_int(data, offset, length, original_int8_recv)
100
+
101
+ def patched_int4_recv(data: bytes, offset: int, length: int) -> int:
102
+ return safe_unpack_int(data, offset, length, original_int4_recv)
103
+
104
+ def patched_int2_recv(data: bytes, offset: int, length: int) -> int:
105
+ return safe_unpack_int(data, offset, length, original_int2_recv)
106
+
107
+ # 1. Patch the global type utility receiver functions directly
108
+ type_utils.int8_recv = patched_int8_recv
109
+ type_utils.int4_recv = patched_int4_recv
110
+ type_utils.int2_recv = patched_int2_recv
111
+
112
+ # 2. Update the global module-level type mapping registry dictionary
113
+ type_utils.redshift_types[RedshiftOID.BIGINT] = (FC_BINARY, patched_int8_recv)
114
+ type_utils.redshift_types[RedshiftOID.INTEGER] = (FC_BINARY, patched_int4_recv)
115
+ type_utils.redshift_types[RedshiftOID.SMALLINT] = (FC_BINARY, patched_int2_recv)
116
+
117
+ # 3. Patch core Connection module configuration to handle connection pooling & instances
118
+ try:
119
+ import redshift_connector.core as core
120
+ # Overwrite REDSHIFT_TYPES map in core module
121
+ if hasattr(core, "REDSHIFT_TYPES"):
122
+ core.REDSHIFT_TYPES[RedshiftOID.BIGINT] = (FC_BINARY, patched_int8_recv)
123
+ core.REDSHIFT_TYPES[RedshiftOID.INTEGER] = (FC_BINARY, patched_int4_recv)
124
+ core.REDSHIFT_TYPES[RedshiftOID.SMALLINT] = (FC_BINARY, patched_int2_recv)
125
+
126
+ # Intercept Connection initialization to force connection instance registries
127
+ if hasattr(core, "Connection"):
128
+ original_conn_init = core.Connection.__init__
129
+ def patched_conn_init(self, *args, **kwargs):
130
+ # Execute the original constructor
131
+ original_conn_init(self, *args, **kwargs)
132
+ # Re-bind instance-specific deserialization mapping dictionaries
133
+ self.redshift_types[RedshiftOID.BIGINT] = (FC_BINARY, patched_int8_recv)
134
+ self.redshift_types[RedshiftOID.INTEGER] = (FC_BINARY, patched_int4_recv)
135
+ self.redshift_types[RedshiftOID.SMALLINT] = (FC_BINARY, patched_int2_recv)
136
+ core.Connection.__init__ = patched_conn_init
137
+ except Exception as core_err:
138
+ logger.warning(f"Could not patch core.REDSHIFT_TYPES or Connection.__init__: {core_err}")
139
+
140
+ logger.info("Successfully applied production-level redshift_connector integer OID deserialization patch")
141
+ except Exception as e:
142
+ logger.error(f"Failed to patch redshift_connector type deserialization: {e}")
143
+
144
+ def __init__(
145
+ self,
146
+ source_config: RedshiftConfig,
147
+ set_default_schema: bool = True,
148
+ ):
149
+ if source_config is None:
150
+ raise ValueError(
151
+ "Missing required parameter: source_config (RedshiftConfig)"
152
+ )
153
+
154
+ super().__init__(source_config)
155
+ self._redshift_config = source_config
156
+ self.org_schema = source_config.schema if source_config.schema else None
157
+ self.set_default_schema = set_default_schema
158
+
159
+ # Apply the connection protocol patch thread-safely on the very first class instantiation
160
+ with self.__class__._lock:
161
+ if not self.__class__._monkeypatched:
162
+ cls = self.__class__
163
+ cls._apply_monkeypatch()
164
+ cls._monkeypatched = True
165
+
166
+
167
+
168
+ def _engine_key(self, cfg: RedshiftConfig) -> str:
169
+ pwd_hash = hashlib.sha256(cfg.password.encode()).hexdigest()
170
+ return f"{cfg.host}|{cfg.port}|{cfg.database}|{cfg.username}|{pwd_hash}"
171
+
172
+ def invalidate_pool(self):
173
+ key = self._engine_key(self._redshift_config)
174
+ with self.__class__._lock:
175
+ now = time.time()
176
+ last_time = self._last_dispose_time.get(key, 0)
177
+ # Throttle (5 seconds)
178
+ if now - last_time < 5:
179
+ return
180
+ engine = self._engines.get(key)
181
+ if engine:
182
+ logger.warning(f"Disposing engine pool for {key} due to stale OID")
183
+ engine.dispose()
184
+ self._last_dispose_time[key] = now
185
+
186
+ def _get_engine(self) -> Engine:
187
+ cfg = self._redshift_config
188
+ key = self._engine_key(cfg)
189
+
190
+ if key not in self._engines:
191
+ with self.__class__._lock: # thread-safe
192
+ if key not in self._engines: # recheck
193
+ url = (
194
+ f"redshift+redshift_connector://{cfg.username}:{cfg.password}"
195
+ f"@{cfg.host}:{cfg.port}/{cfg.database}"
196
+ )
197
+ self._engines[key] = create_engine(
198
+ url,
199
+ pool_size=10,
200
+ max_overflow=5,
201
+ pool_timeout=30,
202
+ pool_recycle=1800,
203
+ pool_pre_ping=True,
204
+ isolation_level="AUTOCOMMIT",
205
+ )
206
+ logger.info(f"Created new engine for {cfg.host}:{cfg.port}/{cfg.database}")
207
+
208
+ return self._engines[key]
209
+
210
+ def _is_maintenance_error(self, exc: Exception) -> bool:
211
+ """Return True when the error looks like a Redshift cluster reboot or maintenance."""
212
+ err = exc
213
+ while hasattr(err, "__cause__") and err.__cause__:
214
+ err = err.__cause__
215
+
216
+ if isinstance(err, ConnectionRefusedError):
217
+ return err.errno in (errno.ECONNREFUSED, 10061)
218
+
219
+ if isinstance(err, InterfaceError) and "ConnectionRefusedError" in str(err):
220
+ return True
221
+
222
+ return False
223
+
224
+ @contextmanager
225
+ def get_connection(self, retries: int = 3, delay: int = 5, query_timeout: int = 30):
226
+ """Context manager for database connections with retry mechanism"""
227
+ engine = self._get_engine()
228
+ last_exception = None
229
+
230
+ for attempt in range(retries):
231
+ conn = None
232
+ try:
233
+ conn = engine.connect()
234
+ conn.execute(text("SET enable_case_sensitive_identifier TO on"))
235
+ conn.execute(text(f"SET statement_timeout = {query_timeout * 1000}"))
236
+ if self.org_schema and self.set_default_schema:
237
+ conn.execute(text(f"SET search_path TO {self.org_schema}"))
238
+ logger.info(f"Search path set to: {self.org_schema}")
239
+
240
+ yield conn
241
+ return
242
+
243
+ except (OperationalError, InterfaceError, ProgrammingError) as e:
244
+ last_exception = e
245
+ if conn is not None:
246
+ try:
247
+ conn.rollback()
248
+ except:
249
+ pass
250
+ conn.close()
251
+ conn = None
252
+
253
+ if self._is_maintenance_error(e):
254
+ logger.warning(f"Redshift maintenance detected (attempt {attempt + 1}/{retries}): {e}")
255
+ else:
256
+ logger.warning(f"Connection failed (attempt {attempt + 1}/{retries}): {e}")
257
+
258
+ if attempt < retries - 1:
259
+ time.sleep(delay)
260
+ delay += 5
261
+ continue
262
+
263
+ break
264
+
265
+ finally:
266
+ if conn is not None:
267
+ conn.close()
268
+
269
+ raise BadRequest(
270
+ f"Failed to connect to Redshift after {retries} attempts : {str(last_exception)}"
271
+ ) from last_exception
272
+
273
+ def close_connection(self):
274
+ """Dispose all pooled engines managed by this class."""
275
+ for engine in self._engines.values():
276
+ engine.dispose()
277
+ self._engines.clear()
278
+ logger.info("All Redshift engine pools disposed.")
279
+
280
+ def is_valid_connection(self) -> bool:
281
+ """Pool health is managed by pool_pre_ping; no manual check needed."""
282
+ return True
283
+
284
+ def test_connection(self) -> bool:
285
+ """Verify the connection is reachable. Raises ConnectionError on failure."""
286
+ try:
287
+ with self.get_connection():
288
+ return True
289
+ except Exception as e:
290
+ error_msg = str(e)
291
+ if "Name or service not known" in error_msg:
292
+ user_msg = "Host not found. Please check the host address."
293
+ elif "Connection refused" in error_msg:
294
+ user_msg = "Connection refused. Please check the host and port."
295
+ elif "database" in error_msg.lower() and "does not exist" in error_msg:
296
+ user_msg = "Database does not exist. Please check the database name."
297
+ elif "password authentication failed" in error_msg.lower():
298
+ user_msg = "Authentication failed. Please check your username and password."
299
+ else:
300
+ user_msg = f"Redshift connection test failed: {error_msg}"
301
+
302
+ raise ConnectionError(user_msg) from e
@@ -1,178 +0,0 @@
1
- import errno
2
- import hashlib
3
- import time
4
- import threading
5
-
6
- from contextlib import contextmanager
7
- from typing import Dict
8
-
9
- from sqlalchemy import create_engine, text
10
- from sqlalchemy.engine import Engine
11
- from sqlalchemy.exc import InterfaceError, OperationalError, ProgrammingError
12
-
13
- from shared_kernel.dataclasses.warehouse_configs import RedshiftConfig
14
- from shared_kernel.exceptions.http_exceptions import BadRequest
15
- from shared_kernel.interfaces.warehouse_connection import DataWarehouseConnection
16
- from shared_kernel.logger import Logger
17
- from shared_kernel.config import Config
18
-
19
- config = Config()
20
- logger = Logger(config.get("APP_NAME"))
21
-
22
-
23
- class RedshiftWarehouseConnection(DataWarehouseConnection):
24
-
25
- _engines: Dict[str, Engine] = {}
26
- _lock = threading.Lock()
27
- _last_dispose_time: Dict[str, float] = {}
28
-
29
- def __init__(
30
- self,
31
- source_config: RedshiftConfig,
32
- set_default_schema: bool = True,
33
- ):
34
- if source_config is None:
35
- raise ValueError(
36
- "Missing required parameter: source_config (RedshiftConfig)"
37
- )
38
-
39
- super().__init__(source_config)
40
- self._redshift_config = source_config
41
- self.org_schema = source_config.schema if source_config.schema else None
42
- self.set_default_schema = set_default_schema
43
-
44
- def _engine_key(self, cfg: RedshiftConfig) -> str:
45
- pwd_hash = hashlib.sha256(cfg.password.encode()).hexdigest()
46
- return f"{cfg.host}|{cfg.port}|{cfg.database}|{cfg.username}|{pwd_hash}"
47
-
48
- def invalidate_pool(self):
49
- key = self._engine_key(self._redshift_config)
50
- with self.__class__._lock:
51
- now = time.time()
52
- last_time = self._last_dispose_time.get(key, 0)
53
- # Throttle (5 seconds)
54
- if now - last_time < 5:
55
- return
56
- engine = self._engines.get(key)
57
- if engine:
58
- logger.warning(f"Disposing engine pool for {key} due to stale OID")
59
- engine.dispose()
60
- self._last_dispose_time[key] = now
61
-
62
- def _get_engine(self) -> Engine:
63
- cfg = self._redshift_config
64
- key = self._engine_key(cfg)
65
-
66
- if key not in self._engines:
67
- with self.__class__._lock: # thread-safe
68
- if key not in self._engines: # recheck
69
- url = (
70
- f"redshift+redshift_connector://{cfg.username}:{cfg.password}"
71
- f"@{cfg.host}:{cfg.port}/{cfg.database}"
72
- )
73
- self._engines[key] = create_engine(
74
- url,
75
- pool_size=10,
76
- max_overflow=5,
77
- pool_timeout=30,
78
- pool_recycle=1800,
79
- pool_pre_ping=True,
80
- isolation_level="AUTOCOMMIT",
81
- )
82
- logger.info(f"Created new engine for {cfg.host}:{cfg.port}/{cfg.database}")
83
-
84
- return self._engines[key]
85
-
86
- def _is_maintenance_error(self, exc: Exception) -> bool:
87
- """Return True when the error looks like a Redshift cluster reboot or maintenance."""
88
- err = exc
89
- while hasattr(err, "__cause__") and err.__cause__:
90
- err = err.__cause__
91
-
92
- if isinstance(err, ConnectionRefusedError):
93
- return err.errno in (errno.ECONNREFUSED, 10061)
94
-
95
- if isinstance(err, InterfaceError) and "ConnectionRefusedError" in str(err):
96
- return True
97
-
98
- return False
99
-
100
- @contextmanager
101
- def get_connection(self, retries: int = 3, delay: int = 5, query_timeout: int = 30):
102
- """Context manager for database connections with retry mechanism"""
103
- engine = self._get_engine()
104
- last_exception = None
105
-
106
- for attempt in range(retries):
107
- conn = None
108
- try:
109
- conn = engine.connect()
110
- conn.execute(text("SET enable_case_sensitive_identifier TO on"))
111
- conn.execute(text(f"SET statement_timeout = {query_timeout * 1000}"))
112
- if self.org_schema and self.set_default_schema:
113
- conn.execute(text(f"SET search_path TO {self.org_schema}"))
114
- logger.info(f"Search path set to: {self.org_schema}")
115
-
116
- yield conn
117
- return
118
-
119
- except (OperationalError, InterfaceError, ProgrammingError) as e:
120
- last_exception = e
121
- if conn is not None:
122
- try:
123
- conn.rollback()
124
- except:
125
- pass
126
- conn.close()
127
- conn = None
128
-
129
- if self._is_maintenance_error(e):
130
- logger.warning(f"Redshift maintenance detected (attempt {attempt + 1}/{retries}): {e}")
131
- else:
132
- logger.warning(f"Connection failed (attempt {attempt + 1}/{retries}): {e}")
133
-
134
- if attempt < retries - 1:
135
- time.sleep(delay)
136
- delay += 5
137
- continue
138
-
139
- break
140
-
141
- finally:
142
- if conn is not None:
143
- conn.close()
144
-
145
- raise BadRequest(
146
- f"Failed to connect to Redshift after {retries} attempts : {str(last_exception)}"
147
- ) from last_exception
148
-
149
- def close_connection(self):
150
- """Dispose all pooled engines managed by this class."""
151
- for engine in self._engines.values():
152
- engine.dispose()
153
- self._engines.clear()
154
- logger.info("All Redshift engine pools disposed.")
155
-
156
- def is_valid_connection(self) -> bool:
157
- """Pool health is managed by pool_pre_ping; no manual check needed."""
158
- return True
159
-
160
- def test_connection(self) -> bool:
161
- """Verify the connection is reachable. Raises ConnectionError on failure."""
162
- try:
163
- with self.get_connection():
164
- return True
165
- except Exception as e:
166
- error_msg = str(e)
167
- if "Name or service not known" in error_msg:
168
- user_msg = "Host not found. Please check the host address."
169
- elif "Connection refused" in error_msg:
170
- user_msg = "Connection refused. Please check the host and port."
171
- elif "database" in error_msg.lower() and "does not exist" in error_msg:
172
- user_msg = "Database does not exist. Please check the database name."
173
- elif "password authentication failed" in error_msg.lower():
174
- user_msg = "Authentication failed. Please check your username and password."
175
- else:
176
- user_msg = f"Redshift connection test failed: {error_msg}"
177
-
178
- raise ConnectionError(user_msg) from e