velocity-python 0.1.39__tar.gz → 0.1.41__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {velocity_python-0.1.39/src/velocity_python.egg-info → velocity_python-0.1.41}/PKG-INFO +1 -1
- {velocity_python-0.1.39 → velocity_python-0.1.41}/pyproject.toml +1 -1
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/table.py +488 -3
- {velocity_python-0.1.39 → velocity_python-0.1.41/src/velocity_python.egg-info}/PKG-INFO +1 -1
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- velocity_python-0.1.41/tests/test_context_job_descriptions.py +92 -0
- velocity_python-0.1.41/tests/test_table_alter.py +546 -0
- velocity_python-0.1.39/tests/test_table_alter.py +0 -175
- {velocity_python-0.1.39 → velocity_python-0.1.41}/LICENSE +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/README.md +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/setup.cfg +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_observability.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_row_dirty_tracking.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.39 → velocity_python-0.1.41}/tests/test_where_clause_validation.py +0 -0
|
@@ -358,6 +358,99 @@ def _normalize_foreign_key_specs(foreign_keys):
|
|
|
358
358
|
return normalized
|
|
359
359
|
|
|
360
360
|
|
|
361
|
+
def _normalize_drop_index_specs(indexes):
|
|
362
|
+
if indexes is None:
|
|
363
|
+
return []
|
|
364
|
+
|
|
365
|
+
if isinstance(indexes, (str, bytes, Mapping)):
|
|
366
|
+
indexes = [indexes]
|
|
367
|
+
|
|
368
|
+
normalized = []
|
|
369
|
+
for definition in indexes:
|
|
370
|
+
if isinstance(definition, Mapping):
|
|
371
|
+
name = definition.get("name")
|
|
372
|
+
columns = definition.get("columns")
|
|
373
|
+
else:
|
|
374
|
+
name = definition
|
|
375
|
+
columns = None
|
|
376
|
+
|
|
377
|
+
if isinstance(columns, str):
|
|
378
|
+
columns = [column.strip() for column in columns.split(",") if column.strip()]
|
|
379
|
+
elif isinstance(columns, Sequence):
|
|
380
|
+
columns = list(columns)
|
|
381
|
+
elif columns is not None:
|
|
382
|
+
columns = [columns]
|
|
383
|
+
|
|
384
|
+
if not name and not columns:
|
|
385
|
+
raise ValueError("drop_indexes entries require a name or columns")
|
|
386
|
+
|
|
387
|
+
normalized.append(
|
|
388
|
+
{
|
|
389
|
+
"name": _normalize_identifier(name) if name else None,
|
|
390
|
+
"columns": tuple(_normalize_identifier(column) for column in columns)
|
|
391
|
+
if columns
|
|
392
|
+
else None,
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return normalized
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _normalize_drop_foreign_key_specs(foreign_keys):
|
|
400
|
+
if foreign_keys is None:
|
|
401
|
+
return []
|
|
402
|
+
|
|
403
|
+
if isinstance(foreign_keys, (str, bytes, Mapping)):
|
|
404
|
+
foreign_keys = [foreign_keys]
|
|
405
|
+
|
|
406
|
+
normalized = []
|
|
407
|
+
for definition in foreign_keys:
|
|
408
|
+
if isinstance(definition, Mapping):
|
|
409
|
+
name = definition.get("name")
|
|
410
|
+
columns = definition.get("columns") or definition.get("column")
|
|
411
|
+
ref_table = definition.get("ref_table") or definition.get("key_to_table")
|
|
412
|
+
ref_columns = definition.get("ref_columns") or definition.get("key_to_columns")
|
|
413
|
+
else:
|
|
414
|
+
name = definition
|
|
415
|
+
columns = None
|
|
416
|
+
ref_table = None
|
|
417
|
+
ref_columns = None
|
|
418
|
+
|
|
419
|
+
if isinstance(columns, str):
|
|
420
|
+
columns = [column.strip() for column in columns.split(",") if column.strip()]
|
|
421
|
+
elif isinstance(columns, Sequence):
|
|
422
|
+
columns = list(columns)
|
|
423
|
+
elif columns is not None:
|
|
424
|
+
columns = [columns]
|
|
425
|
+
|
|
426
|
+
if isinstance(ref_columns, str):
|
|
427
|
+
ref_columns = [column.strip() for column in ref_columns.split(",") if column.strip()]
|
|
428
|
+
elif isinstance(ref_columns, Sequence):
|
|
429
|
+
ref_columns = list(ref_columns)
|
|
430
|
+
elif ref_columns is not None:
|
|
431
|
+
ref_columns = [ref_columns]
|
|
432
|
+
|
|
433
|
+
if not name and not columns:
|
|
434
|
+
raise ValueError("drop_foreign_keys entries require a name or columns")
|
|
435
|
+
|
|
436
|
+
normalized.append(
|
|
437
|
+
{
|
|
438
|
+
"name": _normalize_identifier(name) if name else None,
|
|
439
|
+
"columns": tuple(_normalize_identifier(column) for column in columns)
|
|
440
|
+
if columns
|
|
441
|
+
else None,
|
|
442
|
+
"ref_table": _normalize_identifier(ref_table) if ref_table else None,
|
|
443
|
+
"ref_columns": tuple(
|
|
444
|
+
_normalize_identifier(column) for column in ref_columns
|
|
445
|
+
)
|
|
446
|
+
if ref_columns
|
|
447
|
+
else None,
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
return normalized
|
|
452
|
+
|
|
453
|
+
|
|
361
454
|
class Table:
|
|
362
455
|
SYSTEM_COLUMNS = SYSTEM_COLUMN_NAMES
|
|
363
456
|
|
|
@@ -532,14 +625,16 @@ class Table:
|
|
|
532
625
|
return statements if sql_only else None
|
|
533
626
|
|
|
534
627
|
@return_default(None)
|
|
535
|
-
def drop_index(self, columns, **kwds):
|
|
628
|
+
def drop_index(self, columns=None, name=None, **kwds):
|
|
536
629
|
"""
|
|
537
630
|
Drops an index for the specified columns.
|
|
538
631
|
"""
|
|
539
|
-
sql, vals = self.sql.drop_index(self.name, columns)
|
|
632
|
+
sql, vals = self.sql.drop_index(self.name, columns, name=name)
|
|
540
633
|
if kwds.get("sql_only", False):
|
|
541
634
|
return sql, vals
|
|
542
|
-
_ddl_logger.warning(
|
|
635
|
+
_ddl_logger.warning(
|
|
636
|
+
"DDL DROP INDEX on %s columns=%s name=%s", self.name, columns, name
|
|
637
|
+
)
|
|
543
638
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
544
639
|
|
|
545
640
|
@return_default(None)
|
|
@@ -887,6 +982,396 @@ class Table:
|
|
|
887
982
|
|
|
888
983
|
return summary
|
|
889
984
|
|
|
985
|
+
def migrate_schema(
|
|
986
|
+
self,
|
|
987
|
+
columns=None,
|
|
988
|
+
rename_columns=None,
|
|
989
|
+
drop_columns=None,
|
|
990
|
+
unique_indexes=None,
|
|
991
|
+
indexes=None,
|
|
992
|
+
foreign_keys=None,
|
|
993
|
+
drop_indexes=None,
|
|
994
|
+
drop_foreign_keys=None,
|
|
995
|
+
replace_indexes=False,
|
|
996
|
+
replace_foreign_keys=False,
|
|
997
|
+
create_missing=True,
|
|
998
|
+
ensure_system_columns=False,
|
|
999
|
+
):
|
|
1000
|
+
"""Apply explicit schema migrations needed to evolve this table.
|
|
1001
|
+
|
|
1002
|
+
Unlike ``ensure_schema()``, this helper may rename columns, alter
|
|
1003
|
+
existing column definitions, drop obsolete columns, and explicitly
|
|
1004
|
+
replace indexes or foreign keys. It is intended for deliberate
|
|
1005
|
+
deploy-time migration code rather than passive schema alignment.
|
|
1006
|
+
|
|
1007
|
+
Operations run in this order:
|
|
1008
|
+
|
|
1009
|
+
1. Create the table when missing and ``create_missing=True``.
|
|
1010
|
+
2. Rename columns.
|
|
1011
|
+
3. Drop explicit foreign keys and indexes.
|
|
1012
|
+
4. Add or alter columns using :meth:`alter` in ``smart`` mode.
|
|
1013
|
+
5. Ensure system columns when requested.
|
|
1014
|
+
6. Create or replace declared indexes and foreign keys.
|
|
1015
|
+
7. Drop columns.
|
|
1016
|
+
"""
|
|
1017
|
+
|
|
1018
|
+
if columns is not None and not isinstance(columns, Mapping):
|
|
1019
|
+
raise TypeError("columns must be a mapping when provided.")
|
|
1020
|
+
if rename_columns is not None and not isinstance(rename_columns, Mapping):
|
|
1021
|
+
raise TypeError("rename_columns must be a mapping when provided.")
|
|
1022
|
+
|
|
1023
|
+
normalized_columns = self.lower_keys(columns or {})
|
|
1024
|
+
normalized_renames = {}
|
|
1025
|
+
for orig, new in (rename_columns or {}).items():
|
|
1026
|
+
orig_name = _normalize_identifier(orig)
|
|
1027
|
+
new_name = _normalize_identifier(new)
|
|
1028
|
+
if not orig_name or not new_name:
|
|
1029
|
+
raise ValueError("rename_columns entries require non-empty source and target names")
|
|
1030
|
+
normalized_renames[orig_name] = new_name
|
|
1031
|
+
|
|
1032
|
+
if drop_columns is None:
|
|
1033
|
+
normalized_drops = []
|
|
1034
|
+
elif isinstance(drop_columns, str):
|
|
1035
|
+
normalized_drops = [
|
|
1036
|
+
_normalize_identifier(column)
|
|
1037
|
+
for column in drop_columns.split(",")
|
|
1038
|
+
if _normalize_identifier(column)
|
|
1039
|
+
]
|
|
1040
|
+
elif isinstance(drop_columns, Sequence):
|
|
1041
|
+
normalized_drops = [
|
|
1042
|
+
_normalize_identifier(column)
|
|
1043
|
+
for column in drop_columns
|
|
1044
|
+
if _normalize_identifier(column)
|
|
1045
|
+
]
|
|
1046
|
+
else:
|
|
1047
|
+
normalized_drops = [_normalize_identifier(drop_columns)]
|
|
1048
|
+
|
|
1049
|
+
conflicting_columns = set(normalized_columns).intersection(normalized_drops)
|
|
1050
|
+
if conflicting_columns:
|
|
1051
|
+
raise ValueError(
|
|
1052
|
+
"columns and drop_columns cannot reference the same column(s): "
|
|
1053
|
+
+ ", ".join(sorted(conflicting_columns))
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
normalized_indexes = _normalize_index_specs(indexes)
|
|
1057
|
+
normalized_unique_indexes = _normalize_index_specs(
|
|
1058
|
+
unique_indexes, unique_default=True
|
|
1059
|
+
)
|
|
1060
|
+
normalized_foreign_keys = _normalize_foreign_key_specs(foreign_keys)
|
|
1061
|
+
normalized_drop_indexes = _normalize_drop_index_specs(drop_indexes)
|
|
1062
|
+
normalized_drop_foreign_keys = _normalize_drop_foreign_key_specs(
|
|
1063
|
+
drop_foreign_keys
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
summary = {
|
|
1067
|
+
"created_table": False,
|
|
1068
|
+
"renamed_columns": [],
|
|
1069
|
+
"altered_columns": [],
|
|
1070
|
+
"dropped_columns": [],
|
|
1071
|
+
"created_indexes": [],
|
|
1072
|
+
"dropped_indexes": [],
|
|
1073
|
+
"created_foreign_keys": [],
|
|
1074
|
+
"dropped_foreign_keys": [],
|
|
1075
|
+
"ensured_system_columns": False,
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
table_exists = self.exists()
|
|
1079
|
+
if not table_exists:
|
|
1080
|
+
if not create_missing:
|
|
1081
|
+
raise exceptions.DbTableMissingError(
|
|
1082
|
+
f"Table '{self.name}' does not exist and create_missing=False."
|
|
1083
|
+
)
|
|
1084
|
+
self._ensure_schema_unlocked("create missing table")
|
|
1085
|
+
self.create(normalized_columns)
|
|
1086
|
+
summary["created_table"] = True
|
|
1087
|
+
table_exists = True
|
|
1088
|
+
|
|
1089
|
+
existing_columns = {column.lower() for column in self.sys_columns()}
|
|
1090
|
+
|
|
1091
|
+
if not summary["created_table"]:
|
|
1092
|
+
for orig_name, new_name in normalized_renames.items():
|
|
1093
|
+
if orig_name == new_name:
|
|
1094
|
+
continue
|
|
1095
|
+
|
|
1096
|
+
orig_exists = orig_name in existing_columns
|
|
1097
|
+
new_exists = new_name in existing_columns
|
|
1098
|
+
|
|
1099
|
+
if orig_exists and new_exists:
|
|
1100
|
+
raise exceptions.DbSchemaConflictError(
|
|
1101
|
+
f"Cannot rename column '{orig_name}' to '{new_name}' on table '{self.name}': "
|
|
1102
|
+
"both columns already exist."
|
|
1103
|
+
)
|
|
1104
|
+
if not orig_exists and new_exists:
|
|
1105
|
+
continue
|
|
1106
|
+
if not orig_exists:
|
|
1107
|
+
raise exceptions.DbSchemaConflictError(
|
|
1108
|
+
f"Cannot rename column '{orig_name}' to '{new_name}' on table '{self.name}': "
|
|
1109
|
+
f"column '{orig_name}' does not exist."
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
self._ensure_schema_unlocked("rename columns")
|
|
1113
|
+
self.rename_column(orig_name, new_name)
|
|
1114
|
+
summary["renamed_columns"].append(
|
|
1115
|
+
{"from": orig_name, "to": new_name}
|
|
1116
|
+
)
|
|
1117
|
+
existing_columns.remove(orig_name)
|
|
1118
|
+
existing_columns.add(new_name)
|
|
1119
|
+
|
|
1120
|
+
existing_foreign_keys = self._existing_foreign_key_signatures()
|
|
1121
|
+
for spec in normalized_drop_foreign_keys:
|
|
1122
|
+
match = next(
|
|
1123
|
+
(
|
|
1124
|
+
existing
|
|
1125
|
+
for existing in existing_foreign_keys
|
|
1126
|
+
if (
|
|
1127
|
+
spec["name"]
|
|
1128
|
+
and existing["constraint_name"] == spec["name"]
|
|
1129
|
+
)
|
|
1130
|
+
or (
|
|
1131
|
+
spec["columns"]
|
|
1132
|
+
and existing["columns"] == spec["columns"]
|
|
1133
|
+
and (
|
|
1134
|
+
spec["ref_table"] is None
|
|
1135
|
+
or existing["ref_table"] == spec["ref_table"]
|
|
1136
|
+
)
|
|
1137
|
+
and (
|
|
1138
|
+
spec["ref_columns"] is None
|
|
1139
|
+
or existing["ref_columns"] == spec["ref_columns"]
|
|
1140
|
+
)
|
|
1141
|
+
)
|
|
1142
|
+
),
|
|
1143
|
+
None,
|
|
1144
|
+
)
|
|
1145
|
+
if not match:
|
|
1146
|
+
continue
|
|
1147
|
+
|
|
1148
|
+
self._ensure_schema_unlocked("drop foreign keys")
|
|
1149
|
+
self.drop_foreign_key(
|
|
1150
|
+
list(match["columns"]),
|
|
1151
|
+
match["ref_table"],
|
|
1152
|
+
list(match["ref_columns"]),
|
|
1153
|
+
name=match["constraint_name"],
|
|
1154
|
+
)
|
|
1155
|
+
summary["dropped_foreign_keys"].append(
|
|
1156
|
+
{
|
|
1157
|
+
"name": match["constraint_name"],
|
|
1158
|
+
"columns": list(match["columns"]),
|
|
1159
|
+
}
|
|
1160
|
+
)
|
|
1161
|
+
existing_foreign_keys = self._existing_foreign_key_signatures()
|
|
1162
|
+
|
|
1163
|
+
existing_indexes = self._existing_index_signatures()
|
|
1164
|
+
for spec in normalized_drop_indexes:
|
|
1165
|
+
match = next(
|
|
1166
|
+
(
|
|
1167
|
+
existing
|
|
1168
|
+
for existing in existing_indexes
|
|
1169
|
+
if (
|
|
1170
|
+
spec["name"] and existing["name"] == spec["name"]
|
|
1171
|
+
)
|
|
1172
|
+
or (
|
|
1173
|
+
spec["columns"]
|
|
1174
|
+
and existing["columns"] == spec["columns"]
|
|
1175
|
+
)
|
|
1176
|
+
),
|
|
1177
|
+
None,
|
|
1178
|
+
)
|
|
1179
|
+
if not match:
|
|
1180
|
+
continue
|
|
1181
|
+
|
|
1182
|
+
self._ensure_schema_unlocked("drop indexes")
|
|
1183
|
+
self.drop_index(list(match["columns"]), name=match["name"])
|
|
1184
|
+
summary["dropped_indexes"].append(
|
|
1185
|
+
{
|
|
1186
|
+
"name": match["name"],
|
|
1187
|
+
"columns": list(match["columns"]),
|
|
1188
|
+
}
|
|
1189
|
+
)
|
|
1190
|
+
existing_indexes = self._existing_index_signatures()
|
|
1191
|
+
|
|
1192
|
+
if normalized_columns and not summary["created_table"]:
|
|
1193
|
+
planned = self.alter(normalized_columns, sql_only=True)
|
|
1194
|
+
if planned:
|
|
1195
|
+
self._ensure_schema_unlocked("alter columns")
|
|
1196
|
+
self.alter(normalized_columns)
|
|
1197
|
+
summary["altered_columns"] = list(normalized_columns.keys())
|
|
1198
|
+
existing_columns.update(normalized_columns.keys())
|
|
1199
|
+
|
|
1200
|
+
if ensure_system_columns:
|
|
1201
|
+
required_system_columns = {name.lower() for name in self.SYSTEM_COLUMNS}
|
|
1202
|
+
if not required_system_columns.issubset(existing_columns):
|
|
1203
|
+
self._ensure_schema_unlocked("ensure system columns")
|
|
1204
|
+
self.ensure_system_columns()
|
|
1205
|
+
summary["ensured_system_columns"] = True
|
|
1206
|
+
existing_columns.update(required_system_columns)
|
|
1207
|
+
|
|
1208
|
+
existing_indexes = self._existing_index_signatures()
|
|
1209
|
+
for spec in normalized_unique_indexes + normalized_indexes:
|
|
1210
|
+
desired_signature = self._desired_index_signature(spec)
|
|
1211
|
+
exact_match = next(
|
|
1212
|
+
(
|
|
1213
|
+
existing
|
|
1214
|
+
for existing in existing_indexes
|
|
1215
|
+
if existing["unique"] == desired_signature["unique"]
|
|
1216
|
+
and existing["columns"] == desired_signature["columns"]
|
|
1217
|
+
and existing["where"] == desired_signature["where"]
|
|
1218
|
+
),
|
|
1219
|
+
None,
|
|
1220
|
+
)
|
|
1221
|
+
if exact_match and (
|
|
1222
|
+
not spec["name"] or exact_match["name"] == desired_signature["name"]
|
|
1223
|
+
):
|
|
1224
|
+
continue
|
|
1225
|
+
|
|
1226
|
+
conflict = None
|
|
1227
|
+
if exact_match and spec["name"]:
|
|
1228
|
+
conflict = exact_match
|
|
1229
|
+
if conflict is None and spec["name"]:
|
|
1230
|
+
conflict = next(
|
|
1231
|
+
(
|
|
1232
|
+
existing
|
|
1233
|
+
for existing in existing_indexes
|
|
1234
|
+
if existing["name"] == desired_signature["name"]
|
|
1235
|
+
),
|
|
1236
|
+
None,
|
|
1237
|
+
)
|
|
1238
|
+
if conflict is None:
|
|
1239
|
+
conflict = next(
|
|
1240
|
+
(
|
|
1241
|
+
existing
|
|
1242
|
+
for existing in existing_indexes
|
|
1243
|
+
if existing["columns"] == desired_signature["columns"]
|
|
1244
|
+
and existing["where"] == desired_signature["where"]
|
|
1245
|
+
),
|
|
1246
|
+
None,
|
|
1247
|
+
)
|
|
1248
|
+
if conflict is not None:
|
|
1249
|
+
if not replace_indexes:
|
|
1250
|
+
raise exceptions.DbSchemaConflictError(
|
|
1251
|
+
f"Index migration on table '{self.name}' conflicts with existing index '{conflict['name']}'. "
|
|
1252
|
+
"Pass replace_indexes=True to replace it explicitly."
|
|
1253
|
+
)
|
|
1254
|
+
self._ensure_schema_unlocked("replace indexes")
|
|
1255
|
+
self.drop_index(list(conflict["columns"]), name=conflict["name"])
|
|
1256
|
+
summary["dropped_indexes"].append(
|
|
1257
|
+
{
|
|
1258
|
+
"name": conflict["name"],
|
|
1259
|
+
"columns": list(conflict["columns"]),
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
existing_indexes = self._existing_index_signatures()
|
|
1263
|
+
|
|
1264
|
+
self._ensure_schema_unlocked("create indexes")
|
|
1265
|
+
self.create_index(
|
|
1266
|
+
spec["columns"],
|
|
1267
|
+
unique=spec["unique"],
|
|
1268
|
+
direction=spec["direction"],
|
|
1269
|
+
where=spec["where"],
|
|
1270
|
+
lower=spec["lower"],
|
|
1271
|
+
name=spec["name"],
|
|
1272
|
+
)
|
|
1273
|
+
summary["created_indexes"].append(
|
|
1274
|
+
{
|
|
1275
|
+
"name": spec["name"] or desired_signature["name"],
|
|
1276
|
+
"columns": list(spec["columns"]),
|
|
1277
|
+
"unique": spec["unique"],
|
|
1278
|
+
}
|
|
1279
|
+
)
|
|
1280
|
+
existing_indexes = self._existing_index_signatures()
|
|
1281
|
+
|
|
1282
|
+
existing_foreign_keys = self._existing_foreign_key_signatures()
|
|
1283
|
+
for spec in normalized_foreign_keys:
|
|
1284
|
+
exact_match = next(
|
|
1285
|
+
(
|
|
1286
|
+
existing
|
|
1287
|
+
for existing in existing_foreign_keys
|
|
1288
|
+
if existing["columns"] == spec["columns"]
|
|
1289
|
+
and existing["ref_table"] == spec["ref_table"]
|
|
1290
|
+
and existing["ref_columns"] == spec["ref_columns"]
|
|
1291
|
+
and existing["on_delete"] == spec["on_delete"]
|
|
1292
|
+
and existing["on_update"] == spec["on_update"]
|
|
1293
|
+
),
|
|
1294
|
+
None,
|
|
1295
|
+
)
|
|
1296
|
+
if exact_match and (
|
|
1297
|
+
not spec["name"] or exact_match["constraint_name"] == spec["name"]
|
|
1298
|
+
):
|
|
1299
|
+
continue
|
|
1300
|
+
|
|
1301
|
+
conflict = None
|
|
1302
|
+
if exact_match and spec["name"]:
|
|
1303
|
+
conflict = exact_match
|
|
1304
|
+
if conflict is None and spec["name"]:
|
|
1305
|
+
conflict = next(
|
|
1306
|
+
(
|
|
1307
|
+
existing
|
|
1308
|
+
for existing in existing_foreign_keys
|
|
1309
|
+
if existing["constraint_name"] == spec["name"]
|
|
1310
|
+
),
|
|
1311
|
+
None,
|
|
1312
|
+
)
|
|
1313
|
+
if conflict is None:
|
|
1314
|
+
conflict = next(
|
|
1315
|
+
(
|
|
1316
|
+
existing
|
|
1317
|
+
for existing in existing_foreign_keys
|
|
1318
|
+
if existing["columns"] == spec["columns"]
|
|
1319
|
+
),
|
|
1320
|
+
None,
|
|
1321
|
+
)
|
|
1322
|
+
if conflict is not None:
|
|
1323
|
+
if not replace_foreign_keys:
|
|
1324
|
+
raise exceptions.DbSchemaConflictError(
|
|
1325
|
+
f"Foreign key migration on table '{self.name}' conflicts with existing constraint '{conflict['constraint_name']}'. "
|
|
1326
|
+
"Pass replace_foreign_keys=True to replace it explicitly."
|
|
1327
|
+
)
|
|
1328
|
+
self._ensure_schema_unlocked("replace foreign keys")
|
|
1329
|
+
self.drop_foreign_key(
|
|
1330
|
+
list(conflict["columns"]),
|
|
1331
|
+
conflict["ref_table"],
|
|
1332
|
+
list(conflict["ref_columns"]),
|
|
1333
|
+
name=conflict["constraint_name"],
|
|
1334
|
+
)
|
|
1335
|
+
summary["dropped_foreign_keys"].append(
|
|
1336
|
+
{
|
|
1337
|
+
"name": conflict["constraint_name"],
|
|
1338
|
+
"columns": list(conflict["columns"]),
|
|
1339
|
+
}
|
|
1340
|
+
)
|
|
1341
|
+
existing_foreign_keys = self._existing_foreign_key_signatures()
|
|
1342
|
+
|
|
1343
|
+
self._ensure_schema_unlocked("create foreign keys")
|
|
1344
|
+
self.create_foreign_key(
|
|
1345
|
+
list(spec["columns"]),
|
|
1346
|
+
spec["ref_table"],
|
|
1347
|
+
list(spec["ref_columns"]),
|
|
1348
|
+
name=spec["name"],
|
|
1349
|
+
on_delete=spec["on_delete"],
|
|
1350
|
+
on_update=spec["on_update"],
|
|
1351
|
+
)
|
|
1352
|
+
summary["created_foreign_keys"].append(
|
|
1353
|
+
{
|
|
1354
|
+
"name": spec["name"],
|
|
1355
|
+
"columns": list(spec["columns"]),
|
|
1356
|
+
"ref_table": spec["ref_table"],
|
|
1357
|
+
"ref_columns": list(spec["ref_columns"]),
|
|
1358
|
+
"on_delete": spec["on_delete"],
|
|
1359
|
+
"on_update": spec["on_update"],
|
|
1360
|
+
}
|
|
1361
|
+
)
|
|
1362
|
+
existing_foreign_keys = self._existing_foreign_key_signatures()
|
|
1363
|
+
|
|
1364
|
+
if not summary["created_table"]:
|
|
1365
|
+
for column_name in normalized_drops:
|
|
1366
|
+
if column_name not in existing_columns:
|
|
1367
|
+
continue
|
|
1368
|
+
self._ensure_schema_unlocked("drop columns")
|
|
1369
|
+
self.drop_column(column_name)
|
|
1370
|
+
summary["dropped_columns"].append(column_name)
|
|
1371
|
+
existing_columns.remove(column_name)
|
|
1372
|
+
|
|
1373
|
+
return summary
|
|
1374
|
+
|
|
890
1375
|
def drop(self):
|
|
891
1376
|
"""
|
|
892
1377
|
Drops this table if it exists.
|
|
@@ -156,6 +156,7 @@ tests/test_batch_operations.py
|
|
|
156
156
|
tests/test_concurrency_safety.py
|
|
157
157
|
tests/test_connection_pool.py
|
|
158
158
|
tests/test_connection_resilience.py
|
|
159
|
+
tests/test_context_job_descriptions.py
|
|
159
160
|
tests/test_decorators.py
|
|
160
161
|
tests/test_dirty_pipeline_fast_path.py
|
|
161
162
|
tests/test_email_processing.py
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
from velocity.aws.handlers import context as handler_context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _FakeQueue:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.entries = []
|
|
10
|
+
|
|
11
|
+
def send_messages(self, Entries):
|
|
12
|
+
self.entries.extend(Entries)
|
|
13
|
+
return {"Successful": [{"Id": item["Id"]} for item in Entries], "Failed": []}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _FakeSqsResource:
|
|
17
|
+
def __init__(self, queue):
|
|
18
|
+
self.queue = queue
|
|
19
|
+
|
|
20
|
+
def get_queue_by_name(self, QueueName):
|
|
21
|
+
return self.queue
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _FakeContext:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.jobs = []
|
|
27
|
+
|
|
28
|
+
def session(self):
|
|
29
|
+
return {"email_address": "owner@example.com"}
|
|
30
|
+
|
|
31
|
+
def create_job(self, job_data=None):
|
|
32
|
+
self.jobs.append(job_data)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestQueueJobDescriptions(unittest.TestCase):
|
|
36
|
+
def test_enqueue_uses_caller_supplied_description(self):
|
|
37
|
+
fake_queue = _FakeQueue()
|
|
38
|
+
fake_context = _FakeContext()
|
|
39
|
+
|
|
40
|
+
with patch.dict(
|
|
41
|
+
handler_context.os.environ,
|
|
42
|
+
{"SqsWorkQueue": "example-queue"},
|
|
43
|
+
clear=False,
|
|
44
|
+
), patch.object(
|
|
45
|
+
handler_context.boto3,
|
|
46
|
+
"resource",
|
|
47
|
+
return_value=_FakeSqsResource(fake_queue),
|
|
48
|
+
):
|
|
49
|
+
handler_context.Context.enqueue(
|
|
50
|
+
fake_context,
|
|
51
|
+
"run-program-object",
|
|
52
|
+
payload={
|
|
53
|
+
"sys_id": 1021,
|
|
54
|
+
"program_handle": "daily_update_summary_table",
|
|
55
|
+
"description": "Program Object: daily_update_summary_table",
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.assertEqual(1, len(fake_context.jobs))
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
"Program Object: daily_update_summary_table",
|
|
62
|
+
fake_context.jobs[0]["description"],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def test_enqueue_leaves_description_empty_when_caller_omits_it(self):
|
|
66
|
+
fake_queue = _FakeQueue()
|
|
67
|
+
fake_context = _FakeContext()
|
|
68
|
+
|
|
69
|
+
with patch.dict(
|
|
70
|
+
handler_context.os.environ,
|
|
71
|
+
{"SqsWorkQueue": "example-queue"},
|
|
72
|
+
clear=False,
|
|
73
|
+
), patch.object(
|
|
74
|
+
handler_context.boto3,
|
|
75
|
+
"resource",
|
|
76
|
+
return_value=_FakeSqsResource(fake_queue),
|
|
77
|
+
):
|
|
78
|
+
handler_context.Context.enqueue(
|
|
79
|
+
fake_context,
|
|
80
|
+
"run-program-object",
|
|
81
|
+
payload={
|
|
82
|
+
"sys_id": 1021,
|
|
83
|
+
"program_handle": "daily_update_summary_table",
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self.assertEqual(1, len(fake_context.jobs))
|
|
88
|
+
self.assertIsNone(fake_context.jobs[0]["description"])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
unittest.main()
|