velocity-python 0.1.133__tar.gz → 0.2.0__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.133/src/velocity_python.egg-info → velocity_python-0.2.0}/PKG-INFO +11 -8
- {velocity_python-0.1.133 → velocity_python-0.2.0}/README.md +4 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/pyproject.toml +15 -11
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/__init__.py +1 -1
- velocity_python-0.2.0/src/velocity/aws/blobs.py +256 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/__init__.py +10 -1
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/provisioning.py +109 -2
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/__init__.py +31 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/config.py +94 -10
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/operations.py +29 -6
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/policies.py +196 -42
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/reconcile.py +1 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/facets.py +132 -55
- velocity_python-0.2.0/src/velocity/aws/handlers/__init__.py +25 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/context.py +119 -4
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/lambda_handler.py +19 -2
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/__init__.py +6 -1
- velocity_python-0.2.0/src/velocity/aws/handlers/mixins/audit_trail.py +421 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/client_errors.py +73 -21
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/client_input.py +45 -12
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/data_service.py +772 -281
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/row_scoped.py +42 -43
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/response.py +17 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/job_results.py +146 -12
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/s3.py +16 -3
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/sms.py +209 -3
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/column.py +5 -2
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/table.py +125 -4
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/transaction.py +7 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/base/initializer.py +9 -3
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/base/sql.py +8 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/mysql/__init__.py +6 -5
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/postgres/__init__.py +7 -6
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/postgres/sql.py +247 -2
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/postgres/types.py +56 -9
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlite/__init__.py +6 -4
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlserver/__init__.py +6 -5
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/tablehelper.py +42 -24
- velocity_python-0.2.0/src/velocity/misc/conv/__init__.py +5 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/conv/iconv.py +59 -0
- velocity_python-0.2.0/src/velocity/misc/export.py +563 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/core/store.py +6 -2
- velocity_python-0.2.0/src/velocity/payment/__init__.py +89 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/_mirror_core.py +20 -2
- velocity_python-0.2.0/src/velocity/payment/plaid_api.py +240 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/plaid_webhook.py +22 -36
- velocity_python-0.2.0/src/velocity/testing/__init__.py +27 -0
- velocity_python-0.2.0/src/velocity/testing/fake_db.py +1468 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0/src/velocity_python.egg-info}/PKG-INFO +11 -8
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity_python.egg-info/SOURCES.txt +26 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity_python.egg-info/requires.txt +6 -7
- velocity_python-0.2.0/tests/test_audit_mixin.py +496 -0
- velocity_python-0.2.0/tests/test_blob_store.py +217 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_capabilities.py +144 -1
- velocity_python-0.2.0/tests/test_check_changelog.py +298 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_client_errors_mixin.py +121 -5
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_client_sql_boundary.py +29 -3
- velocity_python-0.2.0/tests/test_client_table_gate_case_and_pointers.py +234 -0
- velocity_python-0.2.0/tests/test_context_queue_job.py +248 -0
- velocity_python-0.2.0/tests/test_conv_combine.py +83 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_data_service_query_cap.py +5 -1
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_data_service_save_as.py +2 -0
- velocity_python-0.2.0/tests/test_data_service_security_gates.py +568 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_declared_column_types.py +45 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_deploy_config_fields.py +100 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_deploy_operations_queue.py +66 -0
- velocity_python-0.2.0/tests/test_deploy_policies.py +443 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_docs_snippets.py +1 -1
- velocity_python-0.2.0/tests/test_export_workbook.py +164 -0
- velocity_python-0.2.0/tests/test_facets_and_where.py +238 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_job_results.py +188 -6
- velocity_python-0.2.0/tests/test_merge_on_conflict.py +214 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_mirror_upsert_public.py +98 -0
- velocity_python-0.2.0/tests/test_payment_extra_bounds.py +63 -0
- velocity_python-0.2.0/tests/test_payment_lazy_imports.py +107 -0
- velocity_python-0.2.0/tests/test_pgserver_capability_types.py +149 -0
- velocity_python-0.2.0/tests/test_pgserver_get_tables.py +73 -0
- velocity_python-0.2.0/tests/test_pgserver_search_export.py +109 -0
- velocity_python-0.2.0/tests/test_plaid_api.py +293 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_postgres_initializer_config.py +53 -4
- velocity_python-0.2.0/tests/test_postgres_py_type.py +120 -0
- velocity_python-0.2.0/tests/test_restricted_direct_tables.py +212 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_row_scoped_data_service.py +28 -1
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_s3_upload.py +46 -0
- velocity_python-0.2.0/tests/test_schema_lock_config_other_backends.py +94 -0
- velocity_python-0.2.0/tests/test_search_paging_export.py +661 -0
- velocity_python-0.2.0/tests/test_sms_twilio_signature.py +194 -0
- velocity_python-0.2.0/tests/test_table_claim.py +199 -0
- velocity_python-0.2.0/tests/test_testing_fake_db.py +652 -0
- velocity_python-0.1.133/src/velocity/aws/blobs.py +0 -91
- velocity_python-0.1.133/src/velocity/aws/handlers/__init__.py +0 -5
- velocity_python-0.1.133/src/velocity/misc/conv/__init__.py +0 -4
- velocity_python-0.1.133/src/velocity/misc/export.py +0 -268
- velocity_python-0.1.133/src/velocity/payment/__init__.py +0 -50
- velocity_python-0.1.133/tests/test_blob_store.py +0 -64
- velocity_python-0.1.133/tests/test_restricted_direct_tables.py +0 -59
- {velocity_python-0.1.133 → velocity_python-0.2.0}/LICENSE +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/setup.cfg +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/security.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/ci_gate.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/frontend_checks.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/failures.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/queue_defaults.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/schema.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/fragments.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/tunnel.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/money.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/redact.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/schema.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_amplify_redirects.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_aws_package_hygiene.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_ci_gate.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_client_input_contract_export.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_data_service_row_number.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_db_tunnel.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_deploy_engine.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_deploy_frontend_checks.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_dlq_constant.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_enqueue_metric_namespace.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_ensure_infrastructure.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_facets.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_failures.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_observability.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_on_action_default.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_ops.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_audit_fixes.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_business_agnostic.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_pgserver_bulk_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_pgserver_ensure_infrastructure.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_pgserver_pointer_joins.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_plaid_webhook.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_pointer_join_sql.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_raw_sql_prefix_policy.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_read_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_readme_imports.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_response.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_result_returning.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_s3_bucket_config.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_s3_list_client_files.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_security_capability.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_ssm_config_require.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_table_purge.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_tx_lifecycle_retry.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_tx_read_cache_and_bulk.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_user_not_provisioned.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_write_hook_create_flow.py +0 -0
- {velocity_python-0.1.133 → velocity_python-0.2.0}/tests/test_write_hook_empty_strings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A rapid application development library for interfacing with data storage
|
|
5
5
|
Author-email: Velocity Team <info@codeclubs.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -52,11 +52,10 @@ Requires-Dist: python-tds>=1.15.0; extra == "sqlserver"
|
|
|
52
52
|
Provides-Extra: postgres
|
|
53
53
|
Requires-Dist: psycopg[binary]>=3.2.0; extra == "postgres"
|
|
54
54
|
Provides-Extra: payment
|
|
55
|
-
Requires-Dist: stripe
|
|
56
|
-
Requires-Dist: braintree
|
|
57
|
-
Requires-Dist: xmltodict
|
|
55
|
+
Requires-Dist: stripe<17,>=15.0.0; extra == "payment"
|
|
56
|
+
Requires-Dist: braintree<5,>=4.30.0; extra == "payment"
|
|
57
|
+
Requires-Dist: xmltodict<2,>=0.13.0; extra == "payment"
|
|
58
58
|
Provides-Extra: plaid
|
|
59
|
-
Requires-Dist: velocity-python[payment]; extra == "plaid"
|
|
60
59
|
Requires-Dist: ecdsa>=0.19.0; extra == "plaid"
|
|
61
60
|
Provides-Extra: all
|
|
62
61
|
Requires-Dist: velocity-python[aws,excel,http,payment,pdf,plaid,postgres,templates]; extra == "all"
|
|
@@ -76,9 +75,9 @@ Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
|
|
|
76
75
|
Requires-Dist: psycopg[binary]>=3.2.0; extra == "test"
|
|
77
76
|
Requires-Dist: boto3>=1.35.0; extra == "test"
|
|
78
77
|
Requires-Dist: requests>=2.32.0; extra == "test"
|
|
79
|
-
Requires-Dist: stripe
|
|
80
|
-
Requires-Dist: braintree
|
|
81
|
-
Requires-Dist: xmltodict
|
|
78
|
+
Requires-Dist: stripe<17,>=15.0.0; extra == "test"
|
|
79
|
+
Requires-Dist: braintree<5,>=4.30.0; extra == "test"
|
|
80
|
+
Requires-Dist: xmltodict<2,>=0.13.0; extra == "test"
|
|
82
81
|
Requires-Dist: authorizenet>=1.1.3; extra == "test"
|
|
83
82
|
Requires-Dist: ecdsa>=0.19.0; extra == "test"
|
|
84
83
|
Requires-Dist: openpyxl>=3.1.0; extra == "test"
|
|
@@ -247,6 +246,8 @@ def import_customers(tx, records):
|
|
|
247
246
|
## Documentation
|
|
248
247
|
|
|
249
248
|
Full documentation is included in the `docs/` directory of the source distribution.
|
|
249
|
+
What changed in each release, and what callers must do before upgrading, is in
|
|
250
|
+
[`CHANGELOG.md`](CHANGELOG.md) (Breaking / Deploy ordering / Added / Fixed per version).
|
|
250
251
|
|
|
251
252
|
| Guide | File | Description |
|
|
252
253
|
|-------|------|-------------|
|
|
@@ -265,6 +266,8 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
265
266
|
| Utilities | `docs/utilities.md` | Excel/PDF export, data conversion, formatting, timers, email parsing and SMTP sending |
|
|
266
267
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
267
268
|
| Security | `docs/SECURITY.md` | Pre-commit hooks, credential scanning |
|
|
269
|
+
| Changelog | `CHANGELOG.md` | Per-release breaking changes, deploy ordering, additions and fixes |
|
|
270
|
+
| Improvement Backlog | `docs/IMPROVEMENT_BACKLOG.md` | Proposed and deferred library improvements |
|
|
268
271
|
|
|
269
272
|
---
|
|
270
273
|
|
|
@@ -153,6 +153,8 @@ def import_customers(tx, records):
|
|
|
153
153
|
## Documentation
|
|
154
154
|
|
|
155
155
|
Full documentation is included in the `docs/` directory of the source distribution.
|
|
156
|
+
What changed in each release, and what callers must do before upgrading, is in
|
|
157
|
+
[`CHANGELOG.md`](CHANGELOG.md) (Breaking / Deploy ordering / Added / Fixed per version).
|
|
156
158
|
|
|
157
159
|
| Guide | File | Description |
|
|
158
160
|
|-------|------|-------------|
|
|
@@ -171,6 +173,8 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
171
173
|
| Utilities | `docs/utilities.md` | Excel/PDF export, data conversion, formatting, timers, email parsing and SMTP sending |
|
|
172
174
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
173
175
|
| Security | `docs/SECURITY.md` | Pre-commit hooks, credential scanning |
|
|
176
|
+
| Changelog | `CHANGELOG.md` | Per-release breaking changes, deploy ordering, additions and fixes |
|
|
177
|
+
| Improvement Backlog | `docs/IMPROVEMENT_BACKLOG.md` | Proposed and deferred library improvements |
|
|
174
178
|
|
|
175
179
|
---
|
|
176
180
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "velocity-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Velocity Team", email="info@codeclubs.org" },
|
|
10
10
|
]
|
|
@@ -83,18 +83,22 @@ sqlserver = [
|
|
|
83
83
|
postgres = [
|
|
84
84
|
"psycopg[binary]>=3.2.0",
|
|
85
85
|
]
|
|
86
|
+
# Tested bounds: the unit suite passes on stripe 15.0.x and 16.0.0 and on
|
|
87
|
+
# braintree 4.42 and 4.47. Raise an upper bound only after `make test-unit`
|
|
88
|
+
# passes on the new major (stripe objects stopped being dicts in 13; each
|
|
89
|
+
# stripe major also moves the default API version, so callers should pin
|
|
90
|
+
# stripe.api_version). Apps pin exact versions inside these bounds (see
|
|
91
|
+
# docs/payment.md "Installation").
|
|
86
92
|
payment = [
|
|
87
|
-
"stripe>=
|
|
88
|
-
"braintree>=4.30.0",
|
|
89
|
-
"xmltodict>=0.13.0",
|
|
93
|
+
"stripe>=15.0.0,<17",
|
|
94
|
+
"braintree>=4.30.0,<5",
|
|
95
|
+
"xmltodict>=0.13.0,<2",
|
|
90
96
|
]
|
|
91
97
|
# Plaid webhook ES256 verification (velocity.payment.plaid_webhook). ecdsa is
|
|
92
98
|
# pure Python, so it fits a Lambda layer where `cryptography` does not.
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# drop it once those package-level imports are lazy or guarded.
|
|
99
|
+
# velocity/payment/__init__.py imports the processor adapters lazily, so
|
|
100
|
+
# importing plaid_webhook needs no stripe/braintree (tests/test_payment_lazy_imports.py).
|
|
96
101
|
plaid = [
|
|
97
|
-
"velocity-python[payment]",
|
|
98
102
|
"ecdsa>=0.19.0",
|
|
99
103
|
]
|
|
100
104
|
all = [
|
|
@@ -120,9 +124,9 @@ test = [
|
|
|
120
124
|
"psycopg[binary]>=3.2.0",
|
|
121
125
|
"boto3>=1.35.0",
|
|
122
126
|
"requests>=2.32.0",
|
|
123
|
-
"stripe>=
|
|
124
|
-
"braintree>=4.30.0",
|
|
125
|
-
"xmltodict>=0.13.0",
|
|
127
|
+
"stripe>=15.0.0,<17",
|
|
128
|
+
"braintree>=4.30.0,<5",
|
|
129
|
+
"xmltodict>=0.13.0,<2",
|
|
126
130
|
"authorizenet>=1.1.3",
|
|
127
131
|
"ecdsa>=0.19.0",
|
|
128
132
|
"openpyxl>=3.1.0",
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Content-addressed S3 blob store.
|
|
2
|
+
|
|
3
|
+
For offloading large row payloads (raw emails, documents, exports) out of
|
|
4
|
+
Postgres while the row keeps only the content hash. Keys are derived from
|
|
5
|
+
the sha256 of the content, so storage is idempotent and deduplicating by
|
|
6
|
+
construction: the same bytes always land on the same key, and a re-upload
|
|
7
|
+
of an existing object is skipped.
|
|
8
|
+
|
|
9
|
+
store = BlobStore("my-bucket", prefix="demo/gmail-raw", suffix=".eml")
|
|
10
|
+
sha, key = store.put_bytes(raw) # no-op if already stored
|
|
11
|
+
raw = store.get_bytes(sha) # or get_bytes(key)
|
|
12
|
+
url = store.presigned_get(sha) # hand-off to a browser
|
|
13
|
+
|
|
14
|
+
A site's per-stage store (keys under ``{stage}/{family}``, the bucket named by
|
|
15
|
+
a config key) comes from :meth:`BlobStore.for_stage`; browser uploads go
|
|
16
|
+
straight to S3 through :meth:`BlobStore.presigned_put`:
|
|
17
|
+
|
|
18
|
+
store = BlobStore.for_stage("BlobBucket", "forms/assets")
|
|
19
|
+
upload = store.presigned_put(key, content_type="image/png")
|
|
20
|
+
# -> {"url": ..., "key": key, "bucket": ...}; the browser PUTs the body
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import unicodedata
|
|
29
|
+
from typing import Optional, Tuple
|
|
30
|
+
from urllib.parse import quote
|
|
31
|
+
|
|
32
|
+
#: The stage segment used when neither the caller nor the environment names one.
|
|
33
|
+
UNKNOWN_STAGE = "unknown"
|
|
34
|
+
|
|
35
|
+
_UNSAFE_HEADER_CHARS = re.compile(r'["\\\x00-\x1f\x7f]')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BlobStoreNotConfigured(LookupError):
|
|
39
|
+
"""No bucket is configured for a :meth:`BlobStore.for_stage` store.
|
|
40
|
+
|
|
41
|
+
``bucket_key`` is the config key that was looked up; the message names it
|
|
42
|
+
and is safe to show an operator.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, bucket_key: str):
|
|
46
|
+
self.bucket_key = bucket_key
|
|
47
|
+
super().__init__(f"no {bucket_key} is configured for this environment")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def resolve_bucket(bucket_key: str, bucket: Optional[str] = None) -> Optional[str]:
|
|
51
|
+
"""``bucket`` when given, else the ``bucket_key`` config value
|
|
52
|
+
(:func:`velocity.aws.ssm_config.getenv`), else None. Whitespace is stripped."""
|
|
53
|
+
value = str(bucket or "").strip()
|
|
54
|
+
if value:
|
|
55
|
+
return value
|
|
56
|
+
from velocity.aws import ssm_config
|
|
57
|
+
|
|
58
|
+
return str(ssm_config.getenv(bucket_key) or "").strip() or None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_stage(stage: Optional[str] = None, default: str = UNKNOWN_STAGE) -> str:
|
|
62
|
+
"""``stage`` when given, else :func:`velocity.aws.ssm_config.get_stage`
|
|
63
|
+
(``AppStage``, then its compatibility aliases), else ``default``."""
|
|
64
|
+
value = str(stage or "").strip()
|
|
65
|
+
if value:
|
|
66
|
+
return value
|
|
67
|
+
from velocity.aws import ssm_config
|
|
68
|
+
|
|
69
|
+
return str(ssm_config.get_stage() or "").strip() or default
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def stage_prefix(family: str, stage: Optional[str] = None) -> str:
|
|
73
|
+
"""``{stage}/{family}``: the per-environment key prefix of a store family."""
|
|
74
|
+
family = str(family or "").strip("/")
|
|
75
|
+
if not family:
|
|
76
|
+
raise ValueError("stage_prefix requires a family")
|
|
77
|
+
return f"{resolve_stage(stage)}/{family}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def attachment_disposition(filename: str) -> str:
|
|
81
|
+
"""A ``Content-Disposition: attachment`` value naming ``filename``.
|
|
82
|
+
|
|
83
|
+
S3 refuses a response header it cannot represent in ISO-8859-1, and a
|
|
84
|
+
quote would end the value early, so ``filename=`` carries an ASCII
|
|
85
|
+
rendering (accents folded, quotes / backslashes / control characters
|
|
86
|
+
replaced, ``download`` when nothing ASCII is left of the stem); a name that is not plain ASCII also gets the RFC 5987
|
|
87
|
+
``filename*=UTF-8''...`` form, which browsers prefer.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def fold(text):
|
|
91
|
+
folded = unicodedata.normalize("NFKD", text).encode("ascii", "ignore")
|
|
92
|
+
return _UNSAFE_HEADER_CHARS.sub("_", folded.decode("ascii"))
|
|
93
|
+
|
|
94
|
+
name = str(filename or "").strip()
|
|
95
|
+
stem, extension = os.path.splitext(name)
|
|
96
|
+
stem = fold(stem).strip()
|
|
97
|
+
ascii_name = (stem if stem.strip(" ._") else "download") + fold(extension)
|
|
98
|
+
value = f'attachment; filename="{ascii_name}"'
|
|
99
|
+
if name and ascii_name != name:
|
|
100
|
+
value += f"; filename*=UTF-8''{quote(name, safe='')}"
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class BlobStore:
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
bucket: str,
|
|
108
|
+
prefix: str = "",
|
|
109
|
+
suffix: str = "",
|
|
110
|
+
region: Optional[str] = None,
|
|
111
|
+
client=None,
|
|
112
|
+
):
|
|
113
|
+
if not bucket:
|
|
114
|
+
raise ValueError("BlobStore requires a bucket name")
|
|
115
|
+
self.bucket = bucket
|
|
116
|
+
self.prefix = prefix.strip("/")
|
|
117
|
+
self.suffix = suffix
|
|
118
|
+
self.region = region
|
|
119
|
+
self._client = client
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def for_stage(
|
|
123
|
+
cls,
|
|
124
|
+
bucket_key: str,
|
|
125
|
+
family: str,
|
|
126
|
+
*,
|
|
127
|
+
suffix: str = "",
|
|
128
|
+
bucket: Optional[str] = None,
|
|
129
|
+
stage: Optional[str] = None,
|
|
130
|
+
region: Optional[str] = None,
|
|
131
|
+
client=None,
|
|
132
|
+
) -> "BlobStore":
|
|
133
|
+
"""The store for one key family of this environment.
|
|
134
|
+
|
|
135
|
+
The bucket is ``bucket`` or the ``bucket_key`` config value
|
|
136
|
+
(:func:`resolve_bucket`); keys live under ``{stage}/{family}``, the
|
|
137
|
+
stage being ``stage`` or :func:`velocity.aws.ssm_config.get_stage`
|
|
138
|
+
(:func:`resolve_stage`). Raises :class:`BlobStoreNotConfigured` when no
|
|
139
|
+
bucket resolves. Explicit ``bucket`` / ``stage`` serve local tools,
|
|
140
|
+
where nothing sets the Lambda environment.
|
|
141
|
+
"""
|
|
142
|
+
name = resolve_bucket(bucket_key, bucket)
|
|
143
|
+
if name is None:
|
|
144
|
+
raise BlobStoreNotConfigured(bucket_key)
|
|
145
|
+
return cls(
|
|
146
|
+
name,
|
|
147
|
+
prefix=stage_prefix(family, stage),
|
|
148
|
+
suffix=suffix,
|
|
149
|
+
region=region,
|
|
150
|
+
client=client,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def client(self):
|
|
155
|
+
if self._client is None:
|
|
156
|
+
import boto3
|
|
157
|
+
|
|
158
|
+
kwargs = {"region_name": self.region} if self.region else {}
|
|
159
|
+
self._client = boto3.client("s3", **kwargs)
|
|
160
|
+
return self._client
|
|
161
|
+
|
|
162
|
+
def key_for(self, sha256_hex: str) -> str:
|
|
163
|
+
name = f"{sha256_hex}{self.suffix}"
|
|
164
|
+
return f"{self.prefix}/{name}" if self.prefix else name
|
|
165
|
+
|
|
166
|
+
def _resolve_key(self, sha_or_key: str) -> str:
|
|
167
|
+
# A bare 64-hex-char sha resolves through key_for; anything else is
|
|
168
|
+
# treated as a full key (lets callers store either form).
|
|
169
|
+
value = str(sha_or_key)
|
|
170
|
+
if len(value) == 64 and all(c in "0123456789abcdef" for c in value.lower()):
|
|
171
|
+
return self.key_for(value.lower())
|
|
172
|
+
return value
|
|
173
|
+
|
|
174
|
+
def exists(self, sha_or_key: str) -> bool:
|
|
175
|
+
try:
|
|
176
|
+
self.client.head_object(
|
|
177
|
+
Bucket=self.bucket, Key=self._resolve_key(sha_or_key)
|
|
178
|
+
)
|
|
179
|
+
return True
|
|
180
|
+
except Exception:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def put_bytes(
|
|
184
|
+
self, data: bytes, content_type: Optional[str] = None
|
|
185
|
+
) -> Tuple[str, str]:
|
|
186
|
+
"""Store ``data`` (idempotent); returns (sha256_hex, key)."""
|
|
187
|
+
payload = bytes(data or b"")
|
|
188
|
+
sha = hashlib.sha256(payload).hexdigest()
|
|
189
|
+
key = self.key_for(sha)
|
|
190
|
+
if not self.exists(key):
|
|
191
|
+
params = {"Bucket": self.bucket, "Key": key, "Body": payload}
|
|
192
|
+
if content_type:
|
|
193
|
+
params["ContentType"] = content_type
|
|
194
|
+
self.client.put_object(**params)
|
|
195
|
+
return sha, key
|
|
196
|
+
|
|
197
|
+
def get_bytes(self, sha_or_key: str) -> bytes:
|
|
198
|
+
response = self.client.get_object(
|
|
199
|
+
Bucket=self.bucket, Key=self._resolve_key(sha_or_key)
|
|
200
|
+
)
|
|
201
|
+
return response["Body"].read()
|
|
202
|
+
|
|
203
|
+
def presigned_get(
|
|
204
|
+
self,
|
|
205
|
+
sha_or_key: str,
|
|
206
|
+
expires_seconds: int = 900,
|
|
207
|
+
*,
|
|
208
|
+
filename: Optional[str] = None,
|
|
209
|
+
content_type: Optional[str] = None,
|
|
210
|
+
) -> str:
|
|
211
|
+
"""A presigned GET URL. ``filename`` makes the browser save the object
|
|
212
|
+
under that name (:func:`attachment_disposition`) rather than its hash
|
|
213
|
+
key; ``content_type`` overrides the served ``Content-Type``."""
|
|
214
|
+
params = {"Bucket": self.bucket, "Key": self._resolve_key(sha_or_key)}
|
|
215
|
+
if filename:
|
|
216
|
+
params["ResponseContentDisposition"] = attachment_disposition(filename)
|
|
217
|
+
if content_type:
|
|
218
|
+
params["ResponseContentType"] = content_type
|
|
219
|
+
return self.client.generate_presigned_url(
|
|
220
|
+
"get_object", Params=params, ExpiresIn=int(expires_seconds)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def presigned_put(
|
|
224
|
+
self,
|
|
225
|
+
sha_or_key: str,
|
|
226
|
+
*,
|
|
227
|
+
content_type: Optional[str] = None,
|
|
228
|
+
expires_seconds: int = 900,
|
|
229
|
+
content_length: Optional[int] = None,
|
|
230
|
+
) -> dict:
|
|
231
|
+
"""A presigned PUT for a direct browser-to-S3 upload.
|
|
232
|
+
|
|
233
|
+
Returns ``{"url", "key", "bucket"}``; the client sends
|
|
234
|
+
``fetch(url, {method: "PUT", body})``. ``content_type``, when given, is
|
|
235
|
+
signed, so the PUT must send that exact ``Content-Type`` (S3 answers
|
|
236
|
+
403 otherwise). ``content_length`` signs ``Content-Length`` too, so a
|
|
237
|
+
SigV4 URL accepts only a body of exactly that many bytes — the way to
|
|
238
|
+
make a size limit the caller validated binding (a presigned PUT cannot
|
|
239
|
+
carry a range; that needs a presigned POST policy).
|
|
240
|
+
|
|
241
|
+
Mint the key server-side (``key_for(sha)``, or a key derived from a
|
|
242
|
+
record id) and never sign a key the client names.
|
|
243
|
+
"""
|
|
244
|
+
key = self._resolve_key(sha_or_key)
|
|
245
|
+
params = {"Bucket": self.bucket, "Key": key}
|
|
246
|
+
if content_type:
|
|
247
|
+
params["ContentType"] = content_type
|
|
248
|
+
if content_length is not None:
|
|
249
|
+
length = int(content_length)
|
|
250
|
+
if length < 0:
|
|
251
|
+
raise ValueError("content_length must not be negative")
|
|
252
|
+
params["ContentLength"] = length
|
|
253
|
+
url = self.client.generate_presigned_url(
|
|
254
|
+
"put_object", Params=params, ExpiresIn=int(expires_seconds)
|
|
255
|
+
)
|
|
256
|
+
return {"url": url, "key": key, "bucket": self.bucket}
|
|
@@ -28,7 +28,13 @@ from .dispatch import ( # noqa: F401
|
|
|
28
28
|
CapabilityDispatcher,
|
|
29
29
|
run_capability,
|
|
30
30
|
)
|
|
31
|
-
from .provisioning import
|
|
31
|
+
from .provisioning import ( # noqa: F401
|
|
32
|
+
as_bool,
|
|
33
|
+
provision,
|
|
34
|
+
row_enabled,
|
|
35
|
+
trigger_key,
|
|
36
|
+
upgrade_column_types,
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
# built-ins (importing registers them)
|
|
34
40
|
from . import retention # noqa: E402,F401
|
|
@@ -44,6 +50,7 @@ __all__ = [
|
|
|
44
50
|
"CapabilityDispatcher",
|
|
45
51
|
"POLICY_KINDS",
|
|
46
52
|
"REGISTRY",
|
|
53
|
+
"as_bool",
|
|
47
54
|
"capability",
|
|
48
55
|
"catalog",
|
|
49
56
|
"describe",
|
|
@@ -52,9 +59,11 @@ __all__ = [
|
|
|
52
59
|
"policy_field",
|
|
53
60
|
"provision",
|
|
54
61
|
"resolve_config",
|
|
62
|
+
"row_enabled",
|
|
55
63
|
"run_capability",
|
|
56
64
|
"summarize_policy",
|
|
57
65
|
"trigger_key",
|
|
66
|
+
"upgrade_column_types",
|
|
58
67
|
"validate_policy",
|
|
59
68
|
"NIGHTLY",
|
|
60
69
|
"HOURLY",
|
{velocity_python-0.1.133 → velocity_python-0.2.0}/src/velocity/aws/capabilities/provisioning.py
RENAMED
|
@@ -44,13 +44,21 @@ PROGRAM_SOURCE = (
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
CAPABILITIES_NAME_INDEX = f"idx__{SITE_TABLE}__name"
|
|
48
|
+
|
|
47
49
|
# The table velocity owns for this module; ensured by provision() and by
|
|
48
50
|
# velocity.aws.schema.ensure_infrastructure() (the pre-lock schema step).
|
|
49
51
|
SCHEMA = {
|
|
50
52
|
SITE_TABLE: {
|
|
51
|
-
|
|
53
|
+
# Declared SQL types. Sites provisioned before velocity honoured them
|
|
54
|
+
# hold TEXT here; upgrade_column_types() casts those in place.
|
|
55
|
+
"columns": {"name": "TEXT", "enabled": "BOOLEAN", "config": "JSONB"},
|
|
52
56
|
# provision() merges on name: ON CONFLICT needs this unique index.
|
|
53
|
-
|
|
57
|
+
# Named as velocity's merge self-heal names it (idx__<table>__<cols>),
|
|
58
|
+
# which is also the name app schema lanes declare with
|
|
59
|
+
# CREATE UNIQUE INDEX IF NOT EXISTS, so an environment provisioned
|
|
60
|
+
# first and one synced first end up with one identical index.
|
|
61
|
+
"unique_indexes": [{"columns": ["name"], "name": CAPABILITIES_NAME_INDEX}],
|
|
54
62
|
},
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -79,6 +87,105 @@ def row_enabled(row) -> bool:
|
|
|
79
87
|
return as_bool((row or {}).get("enabled"), default=True)
|
|
80
88
|
|
|
81
89
|
|
|
90
|
+
def _enabled_using(column: str) -> str:
|
|
91
|
+
"""``USING`` expression casting a TEXT ``enabled`` exactly as
|
|
92
|
+
:func:`as_bool` reads it: NULL stays NULL (on), the false spellings are
|
|
93
|
+
FALSE, anything else TRUE — never a cast error on an odd value."""
|
|
94
|
+
falses = ", ".join(f"'{text}'" for text in sorted(_FALSE_TEXT))
|
|
95
|
+
return (
|
|
96
|
+
f"CASE WHEN {column} IS NULL THEN NULL "
|
|
97
|
+
f"WHEN lower(btrim({column})) IN ({falses}) THEN FALSE ELSE TRUE END"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _config_using(column: str) -> str:
|
|
102
|
+
"""``USING`` expression for a TEXT ``config``: blank becomes NULL (read as
|
|
103
|
+
``{}``), everything else is cast as JSON — pre-checked by
|
|
104
|
+
:func:`_invalid_configs`, so the cast cannot fail half-way."""
|
|
105
|
+
return f"NULLIF(btrim({column}), '')::jsonb"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# column -> (information_schema data_type wanted, declared DDL, USING builder)
|
|
109
|
+
COLUMN_UPGRADES = {
|
|
110
|
+
"enabled": ("boolean", "BOOLEAN", _enabled_using),
|
|
111
|
+
"config": ("jsonb", "JSONB", _config_using),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _invalid_configs(table) -> list:
|
|
116
|
+
"""Names of rows whose TEXT ``config`` is not blank and not a JSON
|
|
117
|
+
object — the rows that would fail the JSONB cast or fail
|
|
118
|
+
:func:`velocity.aws.capabilities.base.load_config` afterwards."""
|
|
119
|
+
bad = []
|
|
120
|
+
for row in table.select(columns=["name", "config"]):
|
|
121
|
+
raw = row.get("config")
|
|
122
|
+
if raw is None or not str(raw).strip():
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
value = json.loads(raw)
|
|
126
|
+
except ValueError:
|
|
127
|
+
value = None
|
|
128
|
+
if not isinstance(value, dict):
|
|
129
|
+
bad.append(row.get("name"))
|
|
130
|
+
return bad
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def upgrade_column_types(tx, *, dry_run: bool = False) -> list:
|
|
134
|
+
"""Cast a TEXT ``sys_capabilities.enabled`` / ``config`` to the declared
|
|
135
|
+
BOOLEAN / JSONB, in place. Idempotent: an absent table, a column already
|
|
136
|
+
typed, or a non-PostgreSQL server is a no-op (``[]``).
|
|
137
|
+
|
|
138
|
+
For an app's migration lane: provisioning and ``ensure_infrastructure``
|
|
139
|
+
never rewrite an existing column's type, so a site whose table was created
|
|
140
|
+
while velocity read declared types as sample values keeps TEXT until this
|
|
141
|
+
runs. Returns ``[{"column", "from", "to", "sql"}, ...]`` for each column
|
|
142
|
+
changed (or, with ``dry_run=True``, that would change — nothing runs).
|
|
143
|
+
|
|
144
|
+
Raises ``ValueError`` before any DDL when a column has a type other than
|
|
145
|
+
TEXT or the declared one, or when a ``config`` value is not a JSON object
|
|
146
|
+
(named, so an operator can fix those rows first).
|
|
147
|
+
"""
|
|
148
|
+
table = tx.table(SITE_TABLE)
|
|
149
|
+
if not str(getattr(table.sql, "server", "")).lower().startswith("postgre"):
|
|
150
|
+
return []
|
|
151
|
+
if not table.exists():
|
|
152
|
+
return []
|
|
153
|
+
types = {str(k).lower(): str(v).lower() for k, v in table.column_types().items()}
|
|
154
|
+
|
|
155
|
+
pending = {}
|
|
156
|
+
for column, (wanted, ddl, using) in COLUMN_UPGRADES.items():
|
|
157
|
+
current = types.get(column)
|
|
158
|
+
if current is None or current == wanted:
|
|
159
|
+
continue
|
|
160
|
+
if current != "text":
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"{SITE_TABLE}.{column} is {current!r}; expected text or "
|
|
163
|
+
f"{wanted} — inspect it by hand"
|
|
164
|
+
)
|
|
165
|
+
pending[column] = (current, wanted, ddl, using)
|
|
166
|
+
if not pending:
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
if "config" in pending:
|
|
170
|
+
bad = _invalid_configs(table)
|
|
171
|
+
if bad:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"{SITE_TABLE}.config is not a JSON object for: "
|
|
174
|
+
f"{', '.join(map(str, bad))} — fix those rows, then rerun"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
changes = []
|
|
178
|
+
columns = {}
|
|
179
|
+
for column, (current, wanted, ddl, using) in pending.items():
|
|
180
|
+
spec = {"type": ddl, "using": using(table.sql.quote_identifier(column))}
|
|
181
|
+
sql, _vals = table.alter({column: spec}, sql_only=True, declared=True)
|
|
182
|
+
changes.append({"column": column, "from": current, "to": wanted, "sql": sql})
|
|
183
|
+
columns[column] = spec
|
|
184
|
+
if not dry_run:
|
|
185
|
+
table.migrate_schema(columns=columns)
|
|
186
|
+
return changes
|
|
187
|
+
|
|
188
|
+
|
|
82
189
|
def trigger_key(cap_name: str, action: str) -> str:
|
|
83
190
|
return f"cap:{cap_name}:{action}"
|
|
84
191
|
|
|
@@ -25,6 +25,15 @@ Frontend build check (``python build.py validate-frontend``):
|
|
|
25
25
|
from velocity.aws.deploy import validate_yarn_frontend
|
|
26
26
|
validate_yarn_frontend(PROJECT_ROOT, extra_amplify_yml_steps=(...))
|
|
27
27
|
|
|
28
|
+
IAM statement builders (pure; resources may be CloudFormation intrinsics),
|
|
29
|
+
for templates that should grant exactly what velocity's deploys grant:
|
|
30
|
+
|
|
31
|
+
from velocity.aws.deploy import (
|
|
32
|
+
ssm_parameter_arns, ssm_read_statement, queue_consumer_statement,
|
|
33
|
+
queue_producer_statement, failure_topic_publish_statement,
|
|
34
|
+
dlq_redrive_policy,
|
|
35
|
+
)
|
|
36
|
+
|
|
28
37
|
See docs/deploy.md in the velocity-python repo for deployment, and the
|
|
29
38
|
"CI regression gate" section of docs/aws-handlers.md for ci_gate.
|
|
30
39
|
"""
|
|
@@ -40,13 +49,24 @@ from velocity.aws.deploy.frontend_checks import (
|
|
|
40
49
|
from velocity.aws.deploy.policies import (
|
|
41
50
|
AWS_LAMBDA_VPC_ACCESS_ARN,
|
|
42
51
|
IAM_MANAGED_POLICY_MAX_CHARS,
|
|
52
|
+
QUEUE_CONSUME_ACTIONS,
|
|
53
|
+
SEND_QUEUE_ACTIONS,
|
|
54
|
+
SSM_READ_ACTIONS,
|
|
43
55
|
ManagedPolicySizeError,
|
|
44
56
|
app_policy_name,
|
|
45
57
|
app_runtime_statements,
|
|
46
58
|
baseline_lambda_policy_document,
|
|
59
|
+
dlq_redrive_policy,
|
|
47
60
|
ensure_app_policy,
|
|
61
|
+
failure_topic_publish_statement,
|
|
62
|
+
queue_consumer_statement,
|
|
63
|
+
queue_producer_statement,
|
|
64
|
+
queue_worker_statement,
|
|
48
65
|
reconcile_role_policies,
|
|
49
66
|
retire_policy,
|
|
67
|
+
ssm_decrypt_statement,
|
|
68
|
+
ssm_parameter_arns,
|
|
69
|
+
ssm_read_statement,
|
|
50
70
|
sync_managed_policy_document,
|
|
51
71
|
)
|
|
52
72
|
from velocity.aws.deploy.operations import (
|
|
@@ -93,10 +113,14 @@ __all__ = [
|
|
|
93
113
|
"ManagedPolicySizeError",
|
|
94
114
|
"MasqueradeReconcile",
|
|
95
115
|
"PlainTarget",
|
|
116
|
+
"QUEUE_CONSUME_ACTIONS",
|
|
117
|
+
"SEND_QUEUE_ACTIONS",
|
|
118
|
+
"SSM_READ_ACTIONS",
|
|
96
119
|
"app_policy_name",
|
|
97
120
|
"app_runtime_statements",
|
|
98
121
|
"apply_reserved_concurrency",
|
|
99
122
|
"baseline_lambda_policy_document",
|
|
123
|
+
"dlq_redrive_policy",
|
|
100
124
|
"ensure_alarm_topic",
|
|
101
125
|
"ensure_api_cors_header",
|
|
102
126
|
"ensure_app_policy",
|
|
@@ -105,11 +129,15 @@ __all__ = [
|
|
|
105
129
|
"ensure_identity_pool_developer_provider",
|
|
106
130
|
"ensure_queue_alarms",
|
|
107
131
|
"ensure_queue_event_source_mapping",
|
|
132
|
+
"failure_topic_publish_statement",
|
|
108
133
|
"get_amplify_app_id_and_branch",
|
|
109
134
|
"initialize_build_environment",
|
|
110
135
|
"list_functions_by_tag",
|
|
111
136
|
"queue_alarm_definitions",
|
|
137
|
+
"queue_consumer_statement",
|
|
138
|
+
"queue_producer_statement",
|
|
112
139
|
"queue_visibility_timeout",
|
|
140
|
+
"queue_worker_statement",
|
|
113
141
|
"reconcile_role_policies",
|
|
114
142
|
"resolve_deploy_config_value",
|
|
115
143
|
"resolve_latest_layer_arns",
|
|
@@ -121,6 +149,9 @@ __all__ = [
|
|
|
121
149
|
"run_post_deploy_reconcilers",
|
|
122
150
|
"set_cloudwatch_logging",
|
|
123
151
|
"setup_sqs_queue_and_permissions",
|
|
152
|
+
"ssm_decrypt_statement",
|
|
153
|
+
"ssm_parameter_arns",
|
|
154
|
+
"ssm_read_statement",
|
|
124
155
|
"sync_managed_policy_document",
|
|
125
156
|
"update_lambda_function_config",
|
|
126
157
|
"update_lambda_layers_to_latest",
|