fdc-shared-kernel 0.0.185__tar.gz → 0.0.187__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.185 → fdc_shared_kernel-0.0.187}/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/pyproject.toml +1 -1
- fdc_shared_kernel-0.0.187/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +302 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/snowflake_handler.py +2 -1
- fdc_shared_kernel-0.0.185/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +0 -178
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/README.md +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/README_pypi.md +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/SOURCES.txt +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/requires.txt +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/top_level.txt +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/requirements.txt +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/setup.cfg +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/agent_communication_channel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/agent_communication_channel/agent_channel.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/agent_communication_channel/contexts.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/agent_communication_channel/event_channel.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/ai_audit_log/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/ai_chat_persistence/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/ai_chat_persistence/chat_persistence.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/async_task_executor/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/async_task_executor/async_task_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/auth/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/auth/jwt_helper.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/auth/token_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/auth/workbook_permission_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/batch_job_status_tracker/container_job_status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_handlers/aws_batch_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_handlers/azure_apps_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_handlers/job_handler_factory.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_handlers/job_status_mapper.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_storage_managers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_storage_managers/azure_blob_storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_storage_managers/job_storage_manager_factory.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/batch_job/job_storage_managers/s3_job_storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/constants/constants.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/databricks_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/jdbc_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/mssql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/mysql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/postgresql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/connections/snowflake_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/custom_query_builders/databricks_query.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/databricks_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/factory.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/jdbc_driver_registry.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/jdbc_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/mssql_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/mysql_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/odbc_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/postgresql_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/databricks_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/jdbc_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/mssql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/mysql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/postgresql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/redshift_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/query_executors/snowflake_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/redshift_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/data_warehouse_handlers/warehouse_handlers_map.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/database/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/aws_batch_job_context.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/azure_container_app_job_context.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/event_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/job_context.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/job_record_dataclasses.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/dataclasses/warehouse_configs.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/csv.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/databricks.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/db2.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/jdbc.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/jira.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/mssql.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/mysql.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/oracle.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/postgres.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/redshift.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/salesforce.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/snowflake.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/connectors_to_system/sqlalchemy.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/system_to_warehouse/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/system_to_warehouse/postgres.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/datatype_mappings/system_to_warehouse/redshift.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/enums/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/enums/async_task_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/enums/connection_engine.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/enums/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/event_executor/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/event_executor/event_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/event_executor/job_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/event_executor/utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/batch_job_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/configuration_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/custom_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/http_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/operational_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/security_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/exceptions/user_intervention_exception.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/http/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/http/httpx_http_client.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/http/request_http_client.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/base_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/data_warehouse_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/encryption.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/http.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/keyvault.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/query_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/interfaces/warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/aws_databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/azure_databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/http_databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/nats_databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/nats_publisher.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/nats_test.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/utils/aws_utility.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/utils/azure_utility.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/messaging/utils/event_messages.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/metrics/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/metrics/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/models/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/registries/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/registries/schema_onboarding_event_registry.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/registries/service_event_registry.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/base_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/enums.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/exceptions.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/run_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/schema_onboarding_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/shared_helpers.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/task_determination.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/task_helpers.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/task_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/template_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/schema_onboarding_tracker/utility_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/encryption/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/encryption/manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/encryption/providers.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/key_vault/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/key_vault/aws_secret_manager.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/security/key_vault/azure_keyvault.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/status_tracker/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/status_tracker/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/storage_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/storage_handlers/blob_file_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/storage_handlers/s3_storage_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/config/test_config.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/logger/test_logger.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/messaging/test_aws_databus.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/messaging/test_event_executor.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/messaging/test_nats_interface.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/security/test_encryption.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/utils/test_data_validators.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/tests/utils/test_string_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/traceability/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/traceability/traceability_handler.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/data_validators_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/date_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/string_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/template_renderer.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/thread_debug_utils.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/thread_local_storage.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/shared_kernel/utils/thread_log_traker.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/tests/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/tests/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/tests/messaging/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/tests/messaging/utils/test_aws_utility.py +0 -0
- {fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/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.
|
|
3
|
+
Version: 0.0.187
|
|
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.
|
|
3
|
+
Version: 0.0.187
|
|
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
|
|
@@ -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
|
|
@@ -30,7 +30,8 @@ class SnowflakeWarehouseHandler(WarehouseHandler):
|
|
|
30
30
|
account=str(self.payload.get("account")).strip(),
|
|
31
31
|
database=str(self.payload.get("database")).strip(),
|
|
32
32
|
schema=str(self.payload.get("schema")).strip() if self.payload.get("schema") else None,
|
|
33
|
-
schema_list=self.payload.get("schema_list")
|
|
33
|
+
schema_list=self.payload.get("schema_list"),
|
|
34
|
+
pagination=self.pagination,
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
def _build_connection(self) -> SnowflakeWarehouseConnection:
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/requires.txt
RENAMED
|
File without changes
|
{fdc_shared_kernel-0.0.185 → fdc_shared_kernel-0.0.187}/fdc_shared_kernel.egg-info/top_level.txt
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.185 → fdc_shared_kernel-0.0.187}/shared_kernel/ai_audit_log/__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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.185 → fdc_shared_kernel-0.0.187}/shared_kernel/constants/constants.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|