fdc-shared-kernel 0.0.184__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.
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/PKG-INFO +1 -1
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/SOURCES.txt +5 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/pyproject.toml +1 -1
- fdc_shared_kernel-0.0.186/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +302 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/__init__.py +1 -0
- fdc_shared_kernel-0.0.186/shared_kernel/interfaces/encryption.py +33 -0
- fdc_shared_kernel-0.0.186/shared_kernel/security/__init__.py +2 -0
- fdc_shared_kernel-0.0.186/shared_kernel/security/encryption/__init__.py +4 -0
- fdc_shared_kernel-0.0.186/shared_kernel/security/encryption/manager.py +84 -0
- fdc_shared_kernel-0.0.186/shared_kernel/security/encryption/providers.py +96 -0
- fdc_shared_kernel-0.0.186/shared_kernel/tests/security/test_encryption.py +105 -0
- fdc_shared_kernel-0.0.184/shared_kernel/data_warehouse_handlers/connections/redshift_warehouse_connection.py +0 -178
- fdc_shared_kernel-0.0.184/shared_kernel/security/__init__.py +0 -1
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/README.md +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/README_pypi.md +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/dependency_links.txt +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/requires.txt +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/top_level.txt +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/requirements.txt +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/setup.cfg +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/agent_channel.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/contexts.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/agent_communication_channel/event_channel.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_audit_log/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_chat_persistence/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/ai_chat_persistence/chat_persistence.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/async_task_executor/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/async_task_executor/async_task_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/jwt_helper.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/token_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/auth/workbook_permission_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/batch_job_status_tracker/container_job_status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/aws_batch_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/azure_apps_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/job_handler_factory.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_handlers/job_status_mapper.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/azure_blob_storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/job_storage_manager_factory.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/batch_job/job_storage_managers/s3_job_storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/constants/constants.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/databricks_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/jdbc_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/mssql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/mysql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/postgresql_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/connections/snowflake_warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/custom_query_builders/databricks_query.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/databricks_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/factory.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/jdbc_driver_registry.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/jdbc_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/mssql_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/mysql_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/odbc_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/postgresql_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/databricks_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/jdbc_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/mssql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/mysql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/postgresql_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/redshift_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/query_executors/snowflake_query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/redshift_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/snowflake_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/data_warehouse_handlers/warehouse_handlers_map.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/database/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/aws_batch_job_context.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/azure_container_app_job_context.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/event_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/job_context.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/job_record_dataclasses.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/dataclasses/warehouse_configs.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/csv.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/databricks.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/db2.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/jdbc.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/jira.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/mssql.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/mysql.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/oracle.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/postgres.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/redshift.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/salesforce.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/snowflake.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/connectors_to_system/sqlalchemy.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/postgres.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/datatype_mappings/system_to_warehouse/redshift.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/async_task_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/connection_engine.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/enums/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/event_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/job_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/event_executor/utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/batch_job_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/configuration_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/custom_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/data_validation_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/http_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/infrastructure_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/operational_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/security_exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/exceptions/user_intervention_exception.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/http/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/http/httpx_http_client.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/http/request_http_client.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/base_job_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/data_warehouse_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/http.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/keyvault.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/query_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/storage_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/interfaces/warehouse_connection.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/aws_databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/azure_databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/http_databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_publisher.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/nats_test.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/aws_utility.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/azure_utility.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/messaging/utils/event_messages.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/metrics/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/metrics/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/models/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/schema_onboarding_event_registry.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/registries/service_event_registry.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/base_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/enums.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/exceptions.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/run_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/schema_onboarding_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/shared_helpers.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_determination.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_helpers.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/task_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/template_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/schema_onboarding_tracker/utility_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/aws_secret_manager.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/security/key_vault/azure_keyvault.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/status_tracker/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/status_tracker/status_tracker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/blob_file_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/storage_handlers/s3_storage_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/config/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/config/test_config.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/logger/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/logger/test_logger.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_aws_databus.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_event_executor.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/messaging/test_nats_interface.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_data_validators.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/tests/utils/test_string_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/traceability/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/traceability/traceability_handler.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/data_validators_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/date_format_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/date_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/string_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/template_renderer.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_debug_utils.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_local_storage.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/shared_kernel/utils/thread_log_traker.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/tests/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/tests/messaging/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/tests/messaging/utils/__init__.py +0 -0
- {fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/tests/messaging/utils/test_aws_utility.py +0 -0
- {fdc_shared_kernel-0.0.184 → 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.
|
|
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.
|
|
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
|
{fdc_shared_kernel-0.0.184 → fdc_shared_kernel-0.0.186}/fdc_shared_kernel.egg-info/SOURCES.txt
RENAMED
|
@@ -113,6 +113,7 @@ shared_kernel/interfaces/__init__.py
|
|
|
113
113
|
shared_kernel/interfaces/base_job_handler.py
|
|
114
114
|
shared_kernel/interfaces/data_warehouse_handler.py
|
|
115
115
|
shared_kernel/interfaces/databus.py
|
|
116
|
+
shared_kernel/interfaces/encryption.py
|
|
116
117
|
shared_kernel/interfaces/http.py
|
|
117
118
|
shared_kernel/interfaces/keyvault.py
|
|
118
119
|
shared_kernel/interfaces/query_executor.py
|
|
@@ -148,6 +149,9 @@ shared_kernel/schema_onboarding_tracker/task_manager.py
|
|
|
148
149
|
shared_kernel/schema_onboarding_tracker/template_manager.py
|
|
149
150
|
shared_kernel/schema_onboarding_tracker/utility_manager.py
|
|
150
151
|
shared_kernel/security/__init__.py
|
|
152
|
+
shared_kernel/security/encryption/__init__.py
|
|
153
|
+
shared_kernel/security/encryption/manager.py
|
|
154
|
+
shared_kernel/security/encryption/providers.py
|
|
151
155
|
shared_kernel/security/key_vault/__init__.py
|
|
152
156
|
shared_kernel/security/key_vault/aws_secret_manager.py
|
|
153
157
|
shared_kernel/security/key_vault/azure_keyvault.py
|
|
@@ -165,6 +169,7 @@ shared_kernel/tests/messaging/__init__.py
|
|
|
165
169
|
shared_kernel/tests/messaging/test_aws_databus.py
|
|
166
170
|
shared_kernel/tests/messaging/test_event_executor.py
|
|
167
171
|
shared_kernel/tests/messaging/test_nats_interface.py
|
|
172
|
+
shared_kernel/tests/security/test_encryption.py
|
|
168
173
|
shared_kernel/tests/utils/__init__.py
|
|
169
174
|
shared_kernel/tests/utils/test_data_validators.py
|
|
170
175
|
shared_kernel/tests/utils/test_date_format_utils.py
|
|
@@ -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
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EncryptionProviderInterface(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Abstract interface for encryption/decryption providers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def encrypt(self, plaintext: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Encrypts the plaintext string and returns the base64-encoded ciphertext.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
plaintext (str): The plaintext string to encrypt.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The base64-encoded ciphertext string.
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Decrypts the base64-encoded ciphertext and returns the plaintext string.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
ciphertext (str): The base64-encoded ciphertext string.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: The decrypted plaintext string.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from shared_kernel.interfaces.encryption import EncryptionProviderInterface
|
|
3
|
+
from shared_kernel.exceptions import EncryptionError
|
|
4
|
+
from shared_kernel.security.encryption.providers import (
|
|
5
|
+
BasicEncryptionProvider,
|
|
6
|
+
AwsKmsProvider,
|
|
7
|
+
AzureKeyVaultCryptoProvider,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EncryptionManager:
|
|
12
|
+
"""
|
|
13
|
+
Factory manager for creating encryption providers and orchestrating
|
|
14
|
+
encryption and decryption operations.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
method: str,
|
|
20
|
+
key: Optional[str] = None,
|
|
21
|
+
config: Optional[Dict[str, Any]] = None,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Initializes the EncryptionManager with the specified method, key, and config.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
method (str): The encryption method (e.g., 'BASIC', 'AWS_KMS', 'AZURE_KEYVAULT').
|
|
28
|
+
key (str, optional): The encryption key or identifier.
|
|
29
|
+
config (dict, optional): Additional configuration for cloud provider clients.
|
|
30
|
+
"""
|
|
31
|
+
self._provider = self._resolve_provider(method, key, config or {})
|
|
32
|
+
|
|
33
|
+
def _resolve_provider(
|
|
34
|
+
self, method: str, key: Optional[str], config: Dict[str, Any]
|
|
35
|
+
) -> EncryptionProviderInterface:
|
|
36
|
+
"""
|
|
37
|
+
Resolves and instantiates the proper encryption provider.
|
|
38
|
+
"""
|
|
39
|
+
method_upper = method.upper()
|
|
40
|
+
|
|
41
|
+
if method_upper in ["BASIC", "SYMMETRIC", "FERNET"]:
|
|
42
|
+
return BasicEncryptionProvider(key, config)
|
|
43
|
+
# elif method_upper == "AWS_KMS":
|
|
44
|
+
# return AwsKmsProvider(key, config)
|
|
45
|
+
# elif method_upper in ["AZURE_KEYVAULT", "AZURE_KMS"]:
|
|
46
|
+
# return AzureKeyVaultCryptoProvider(key, config)
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError(f"Unsupported encryption method: {method}")
|
|
49
|
+
|
|
50
|
+
def encrypt(self, plaintext: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Encrypts the plaintext string using the configured provider.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
plaintext (str): The string to encrypt.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: Base64-encoded encrypted ciphertext.
|
|
59
|
+
"""
|
|
60
|
+
if not isinstance(plaintext, str):
|
|
61
|
+
raise TypeError("Plaintext must be a string")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
return self._provider.encrypt(plaintext)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise EncryptionError("Encryption failed", detail=str(e))
|
|
67
|
+
|
|
68
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Decrypts the ciphertext string using the configured provider.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
ciphertext (str): The base64-encoded ciphertext to decrypt.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
str: Decrypted plaintext string.
|
|
77
|
+
"""
|
|
78
|
+
if not isinstance(ciphertext, str):
|
|
79
|
+
raise TypeError("Ciphertext must be a string")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
return self._provider.decrypt(ciphertext)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise EncryptionError("Decryption failed", detail=str(e))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import os
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
from shared_kernel.interfaces.encryption import EncryptionProviderInterface
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasicEncryptionProvider(EncryptionProviderInterface):
|
|
10
|
+
"""
|
|
11
|
+
Symmetric encryption provider using cryptography Fernet.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, key: Optional[str] = None, config: Optional[Dict[str, Any]] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initializes the BasicEncryptionProvider with a symmetric key.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
key (str, optional): The encryption key. If not provided,
|
|
20
|
+
it will attempt to read config or environment.
|
|
21
|
+
config (dict, optional): Configuration dictionary.
|
|
22
|
+
"""
|
|
23
|
+
cfg = config or {}
|
|
24
|
+
raw_key = key or cfg.get("encryption_key") or os.getenv("FDC_ENCRYPTION_KEY")
|
|
25
|
+
|
|
26
|
+
if not raw_key:
|
|
27
|
+
raise ValueError("Encryption key must be provided for basic encryption.")
|
|
28
|
+
|
|
29
|
+
self._fernet = self._setup_fernet(raw_key)
|
|
30
|
+
|
|
31
|
+
def _setup_fernet(self, raw_key: str) -> Fernet:
|
|
32
|
+
"""
|
|
33
|
+
Sets up the Fernet instance, deriving a key if the raw key is not valid.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
key_bytes = raw_key.encode("utf-8")
|
|
37
|
+
decoded = base64.urlsafe_b64decode(key_bytes)
|
|
38
|
+
if len(decoded) == 32:
|
|
39
|
+
return Fernet(key_bytes)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
# Derive a 32-byte key using SHA-256 for non-Fernet formatted keys
|
|
44
|
+
derived = hashlib.sha256(raw_key.encode("utf-8")).digest()
|
|
45
|
+
fernet_key = base64.urlsafe_b64encode(derived)
|
|
46
|
+
return Fernet(fernet_key)
|
|
47
|
+
|
|
48
|
+
def encrypt(self, plaintext: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Encrypts the plaintext string.
|
|
51
|
+
"""
|
|
52
|
+
if not plaintext:
|
|
53
|
+
return ""
|
|
54
|
+
data_bytes = plaintext.encode("utf-8")
|
|
55
|
+
token = self._fernet.encrypt(data_bytes)
|
|
56
|
+
return token.decode("utf-8")
|
|
57
|
+
|
|
58
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Decrypts the ciphertext string.
|
|
61
|
+
"""
|
|
62
|
+
if not ciphertext:
|
|
63
|
+
return ""
|
|
64
|
+
token_bytes = ciphertext.encode("utf-8")
|
|
65
|
+
decrypted = self._fernet.decrypt(token_bytes)
|
|
66
|
+
return decrypted.decode("utf-8")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AwsKmsProvider(EncryptionProviderInterface):
|
|
70
|
+
"""
|
|
71
|
+
AWS KMS encryption provider stub.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, key: Optional[str] = None, config: Optional[Dict[str, Any]] = None):
|
|
75
|
+
raise NotImplementedError("AwsKmsProvider is not implemented yet.")
|
|
76
|
+
|
|
77
|
+
def encrypt(self, plaintext: str) -> str:
|
|
78
|
+
raise NotImplementedError("AwsKmsProvider is not implemented yet.")
|
|
79
|
+
|
|
80
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
81
|
+
raise NotImplementedError("AwsKmsProvider is not implemented yet.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AzureKeyVaultCryptoProvider(EncryptionProviderInterface):
|
|
85
|
+
"""
|
|
86
|
+
Azure Key Vault Keys Cryptography provider stub.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, key: Optional[str] = None, config: Optional[Dict[str, Any]] = None):
|
|
90
|
+
raise NotImplementedError("AzureKeyVaultCryptoProvider is not implemented yet.")
|
|
91
|
+
|
|
92
|
+
def encrypt(self, plaintext: str) -> str:
|
|
93
|
+
raise NotImplementedError("AzureKeyVaultCryptoProvider is not implemented yet.")
|
|
94
|
+
|
|
95
|
+
def decrypt(self, ciphertext: str) -> str:
|
|
96
|
+
raise NotImplementedError("AzureKeyVaultCryptoProvider is not implemented yet.")
|