velocity-python 0.1.61__tar.gz → 0.1.63__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.61 → velocity_python-0.1.63}/PKG-INFO +1 -1
- {velocity_python-0.1.61 → velocity_python-0.1.63}/pyproject.toml +1 -1
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/__init__.py +1 -1
- velocity_python-0.1.63/src/velocity/db/core/jsonproxy.py +175 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/row.py +19 -3
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/table.py +24 -7
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/base/sql.py +16 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/mysql/types.py +7 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/postgres/sql.py +14 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/postgres/types.py +10 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlite/sql.py +3 -3
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/stripe_adapter.py +15 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- velocity_python-0.1.63/tests/test_json_columns.py +305 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_payment_stripe_adapter.py +85 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/LICENSE +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/README.md +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/setup.cfg +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_observability.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_row_dirty_tracking.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.61 → velocity_python-0.1.63}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live list/dict proxies for JSON column values.
|
|
3
|
+
|
|
4
|
+
A ``list`` or ``dict`` stored in a row is just a JSON column (JSONB on
|
|
5
|
+
PostgreSQL). ``Row.__getitem__`` wraps such values in :class:`BoundList` /
|
|
6
|
+
:class:`BoundDict` so in-place mutation writes the whole value back through
|
|
7
|
+
the owning row::
|
|
8
|
+
|
|
9
|
+
row["tags"].append("vip") # UPDATE ... SET tags = '["a","vip"]'
|
|
10
|
+
row["meta"]["verified"] = True # whole "meta" value written back
|
|
11
|
+
|
|
12
|
+
There are no relation declarations and no diffing: every mutation persists
|
|
13
|
+
the complete top-level value. Nested containers share the root's flush, so
|
|
14
|
+
a deep mutation also writes the root value back.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _Controller:
|
|
19
|
+
"""Shared link between a root proxy and all of its nested proxies."""
|
|
20
|
+
|
|
21
|
+
__slots__ = ("flush", "root")
|
|
22
|
+
|
|
23
|
+
def __init__(self, flush):
|
|
24
|
+
self.flush = flush
|
|
25
|
+
self.root = None
|
|
26
|
+
|
|
27
|
+
def notify(self):
|
|
28
|
+
self.flush(unwrap(self.root))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def bind(value, flush):
|
|
32
|
+
"""
|
|
33
|
+
Wrap ``value`` (a dict or list) in a bound proxy tree.
|
|
34
|
+
|
|
35
|
+
``flush(plain_value)`` is called with the plain (unwrapped) top-level
|
|
36
|
+
value after every mutation anywhere in the tree. Non-container values
|
|
37
|
+
are returned unchanged.
|
|
38
|
+
"""
|
|
39
|
+
if not isinstance(value, (dict, list)):
|
|
40
|
+
return value
|
|
41
|
+
ctrl = _Controller(flush)
|
|
42
|
+
root = _wrap(value, ctrl)
|
|
43
|
+
ctrl.root = root
|
|
44
|
+
return root
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def unwrap(value):
|
|
48
|
+
"""Return a plain dict/list copy of a (possibly bound) value tree."""
|
|
49
|
+
if isinstance(value, dict):
|
|
50
|
+
return {k: unwrap(v) for k, v in value.items()}
|
|
51
|
+
if isinstance(value, list):
|
|
52
|
+
return [unwrap(v) for v in value]
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _wrap(value, ctrl):
|
|
57
|
+
if isinstance(value, dict):
|
|
58
|
+
return BoundDict(value, ctrl)
|
|
59
|
+
if isinstance(value, list):
|
|
60
|
+
return BoundList(value, ctrl)
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BoundList(list):
|
|
65
|
+
"""A list whose mutations flush the root value back to its column."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, iterable, ctrl):
|
|
68
|
+
super().__init__(_wrap(v, ctrl) for v in iterable)
|
|
69
|
+
self._ctrl = ctrl
|
|
70
|
+
|
|
71
|
+
# -- mutators ------------------------------------------------------
|
|
72
|
+
def __setitem__(self, index, value):
|
|
73
|
+
if isinstance(index, slice):
|
|
74
|
+
value = [_wrap(v, self._ctrl) for v in value]
|
|
75
|
+
else:
|
|
76
|
+
value = _wrap(value, self._ctrl)
|
|
77
|
+
super().__setitem__(index, value)
|
|
78
|
+
self._ctrl.notify()
|
|
79
|
+
|
|
80
|
+
def __delitem__(self, index):
|
|
81
|
+
super().__delitem__(index)
|
|
82
|
+
self._ctrl.notify()
|
|
83
|
+
|
|
84
|
+
def __iadd__(self, other):
|
|
85
|
+
self.extend(other)
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def __imul__(self, n):
|
|
89
|
+
result = super().__imul__(n)
|
|
90
|
+
self._ctrl.notify()
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
def append(self, value):
|
|
94
|
+
super().append(_wrap(value, self._ctrl))
|
|
95
|
+
self._ctrl.notify()
|
|
96
|
+
|
|
97
|
+
def extend(self, iterable):
|
|
98
|
+
super().extend(_wrap(v, self._ctrl) for v in iterable)
|
|
99
|
+
self._ctrl.notify()
|
|
100
|
+
|
|
101
|
+
def insert(self, index, value):
|
|
102
|
+
super().insert(index, _wrap(value, self._ctrl))
|
|
103
|
+
self._ctrl.notify()
|
|
104
|
+
|
|
105
|
+
def remove(self, value):
|
|
106
|
+
super().remove(value)
|
|
107
|
+
self._ctrl.notify()
|
|
108
|
+
|
|
109
|
+
def pop(self, index=-1):
|
|
110
|
+
result = super().pop(index)
|
|
111
|
+
self._ctrl.notify()
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def clear(self):
|
|
115
|
+
super().clear()
|
|
116
|
+
self._ctrl.notify()
|
|
117
|
+
|
|
118
|
+
def sort(self, **kwds):
|
|
119
|
+
super().sort(**kwds)
|
|
120
|
+
self._ctrl.notify()
|
|
121
|
+
|
|
122
|
+
def reverse(self):
|
|
123
|
+
super().reverse()
|
|
124
|
+
self._ctrl.notify()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BoundDict(dict):
|
|
128
|
+
"""A dict whose mutations flush the root value back to its column."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, mapping, ctrl):
|
|
131
|
+
super().__init__({k: _wrap(v, ctrl) for k, v in mapping.items()})
|
|
132
|
+
self._ctrl = ctrl
|
|
133
|
+
|
|
134
|
+
# -- mutators ------------------------------------------------------
|
|
135
|
+
def __setitem__(self, key, value):
|
|
136
|
+
super().__setitem__(key, _wrap(value, self._ctrl))
|
|
137
|
+
self._ctrl.notify()
|
|
138
|
+
|
|
139
|
+
def __delitem__(self, key):
|
|
140
|
+
super().__delitem__(key)
|
|
141
|
+
self._ctrl.notify()
|
|
142
|
+
|
|
143
|
+
def update(self, dict_=None, **kwds):
|
|
144
|
+
data = {}
|
|
145
|
+
if dict_:
|
|
146
|
+
data.update(dict_)
|
|
147
|
+
data.update(kwds)
|
|
148
|
+
for k, v in data.items():
|
|
149
|
+
super().__setitem__(k, _wrap(v, self._ctrl))
|
|
150
|
+
if data:
|
|
151
|
+
self._ctrl.notify()
|
|
152
|
+
|
|
153
|
+
def pop(self, key, *args):
|
|
154
|
+
existed = key in self
|
|
155
|
+
result = super().pop(key, *args)
|
|
156
|
+
if existed:
|
|
157
|
+
self._ctrl.notify()
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
def popitem(self):
|
|
161
|
+
result = super().popitem()
|
|
162
|
+
self._ctrl.notify()
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
def clear(self):
|
|
166
|
+
super().clear()
|
|
167
|
+
self._ctrl.notify()
|
|
168
|
+
|
|
169
|
+
def setdefault(self, key, default=None):
|
|
170
|
+
if key in self:
|
|
171
|
+
return self[key]
|
|
172
|
+
wrapped = _wrap(default, self._ctrl)
|
|
173
|
+
super().__setitem__(key, wrapped)
|
|
174
|
+
self._ctrl.notify()
|
|
175
|
+
return wrapped
|
|
@@ -2,6 +2,7 @@ import pprint
|
|
|
2
2
|
import time as _time
|
|
3
3
|
from collections.abc import MutableMapping
|
|
4
4
|
from velocity.db.exceptions import DbColumnMissingError
|
|
5
|
+
from velocity.db.core import jsonproxy
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
# Attributes that live on the Row instance itself and must never be
|
|
@@ -133,9 +134,24 @@ class Row(MutableMapping):
|
|
|
133
134
|
return self.pk[key]
|
|
134
135
|
cache = self._ensure_cache()
|
|
135
136
|
if key in cache:
|
|
136
|
-
|
|
137
|
+
val = cache[key]
|
|
138
|
+
# JSON columns come back as plain dict/list — wrap them in live
|
|
139
|
+
# proxies so in-place mutation writes the whole value back.
|
|
140
|
+
if isinstance(val, (dict, list)) and not isinstance(
|
|
141
|
+
val, (jsonproxy.BoundDict, jsonproxy.BoundList)
|
|
142
|
+
):
|
|
143
|
+
val = jsonproxy.bind(
|
|
144
|
+
val, lambda plain, _k=key: self.__setitem__(_k, plain)
|
|
145
|
+
)
|
|
146
|
+
cache[key] = val
|
|
147
|
+
return val
|
|
137
148
|
# Fall back to a direct DB fetch for columns not in the initial SELECT
|
|
138
|
-
|
|
149
|
+
val = self.table.get_value(key, self.pk)
|
|
150
|
+
if isinstance(val, (dict, list)):
|
|
151
|
+
val = jsonproxy.bind(
|
|
152
|
+
val, lambda plain, _k=key: self.__setitem__(_k, plain)
|
|
153
|
+
)
|
|
154
|
+
return val
|
|
139
155
|
|
|
140
156
|
def __setitem__(self, key, val):
|
|
141
157
|
if key in self.pk:
|
|
@@ -327,7 +343,7 @@ class Row(MutableMapping):
|
|
|
327
343
|
"""
|
|
328
344
|
Returns the row as a dictionary (from cache or via a SELECT on self.pk).
|
|
329
345
|
"""
|
|
330
|
-
return
|
|
346
|
+
return {k: jsonproxy.unwrap(v) for k, v in self._ensure_cache().items()}
|
|
331
347
|
|
|
332
348
|
def extract(self, *args):
|
|
333
349
|
"""
|
|
@@ -1984,12 +1984,21 @@ class Table:
|
|
|
1984
1984
|
_ddl_logger.warning("DDL ALTER COLUMN TYPE on %s column=%s", self.name, column)
|
|
1985
1985
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
1986
1986
|
|
|
1987
|
+
def _adapt_data(self, data):
|
|
1988
|
+
"""
|
|
1989
|
+
Apply the dialect's data-value adaptation (e.g. dict/list -> JSONB on
|
|
1990
|
+
PostgreSQL, JSON text elsewhere) to a column->value mapping. Used for
|
|
1991
|
+
INSERT/UPDATE data only — WHERE values keep native driver adaptation.
|
|
1992
|
+
"""
|
|
1993
|
+
adapt = self.sql.adapt_data_value
|
|
1994
|
+
return {k: adapt(v) for k, v in data.items()}
|
|
1995
|
+
|
|
1987
1996
|
@create_missing
|
|
1988
1997
|
def update(self, data, where=None, pk=None, **kwds):
|
|
1989
1998
|
"""
|
|
1990
1999
|
Performs an UPDATE of rows matching `where` or `pk` with `data`.
|
|
1991
2000
|
"""
|
|
1992
|
-
sql, vals = self.sql.update(self.tx, self.name, data, where, pk)
|
|
2001
|
+
sql, vals = self.sql.update(self.tx, self.name, self._adapt_data(data), where, pk)
|
|
1993
2002
|
if kwds.get("sql_only", False):
|
|
1994
2003
|
return sql, vals
|
|
1995
2004
|
self.tx.invalidate_cache(self.name)
|
|
@@ -2002,7 +2011,7 @@ class Table:
|
|
|
2002
2011
|
"""
|
|
2003
2012
|
Performs an INSERT of the given data into this table. Resets sys_id on duplicate keys if needed.
|
|
2004
2013
|
"""
|
|
2005
|
-
sql, vals = self.sql.insert(self.name, data)
|
|
2014
|
+
sql, vals = self.sql.insert(self.name, self._adapt_data(data))
|
|
2006
2015
|
if kwds.get("sql_only", False):
|
|
2007
2016
|
return sql, vals
|
|
2008
2017
|
self.tx.invalidate_cache(self.name)
|
|
@@ -2018,7 +2027,7 @@ class Table:
|
|
|
2018
2027
|
sql, vals = self.sql.merge(
|
|
2019
2028
|
self.tx,
|
|
2020
2029
|
self.name,
|
|
2021
|
-
data,
|
|
2030
|
+
self._adapt_data(data),
|
|
2022
2031
|
pk,
|
|
2023
2032
|
on_conflict_do_nothing=False,
|
|
2024
2033
|
on_conflict_update=True,
|
|
@@ -2047,7 +2056,9 @@ class Table:
|
|
|
2047
2056
|
"""
|
|
2048
2057
|
if not rows:
|
|
2049
2058
|
return 0
|
|
2050
|
-
sql, args_list, template = self.sql.insert_many(
|
|
2059
|
+
sql, args_list, template = self.sql.insert_many(
|
|
2060
|
+
self.name, [self._adapt_data(row) for row in rows]
|
|
2061
|
+
)
|
|
2051
2062
|
page_size = kwds.get("page_size", 1000)
|
|
2052
2063
|
self.tx.invalidate_cache(self.name)
|
|
2053
2064
|
return self.tx._execute_values(sql, args_list, template=template, page_size=page_size)
|
|
@@ -2068,7 +2079,9 @@ class Table:
|
|
|
2068
2079
|
"""
|
|
2069
2080
|
if not rows:
|
|
2070
2081
|
return 0
|
|
2071
|
-
sql, args_list, template = self.sql.merge_many(
|
|
2082
|
+
sql, args_list, template = self.sql.merge_many(
|
|
2083
|
+
self.tx, self.name, [self._adapt_data(row) for row in rows], pk=pk
|
|
2084
|
+
)
|
|
2072
2085
|
page_size = kwds.get("page_size", 1000)
|
|
2073
2086
|
self.tx.invalidate_cache(self.name)
|
|
2074
2087
|
return self.tx._execute_values(sql, args_list, template=template, page_size=page_size)
|
|
@@ -2172,7 +2185,9 @@ class Table:
|
|
|
2172
2185
|
"Current SQL dialect does not support insert-if-not-exists operations."
|
|
2173
2186
|
)
|
|
2174
2187
|
|
|
2175
|
-
sql, vals = ins_builder(
|
|
2188
|
+
sql, vals = ins_builder(
|
|
2189
|
+
self.tx, self.name, self._adapt_data(insert_payload), exists_where
|
|
2190
|
+
)
|
|
2176
2191
|
if sql_only:
|
|
2177
2192
|
return {"update": update_stmt, "insert": (sql, vals)}
|
|
2178
2193
|
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
@@ -2200,7 +2215,9 @@ class Table:
|
|
|
2200
2215
|
must be present in `data`.
|
|
2201
2216
|
:return: rowcount (0 or 1) or (sql, params) when sql_only=True
|
|
2202
2217
|
"""
|
|
2203
|
-
sql, vals = self.sql.insert_if_not_exists(
|
|
2218
|
+
sql, vals = self.sql.insert_if_not_exists(
|
|
2219
|
+
self.tx, self.name, self._adapt_data(data), where
|
|
2220
|
+
)
|
|
2204
2221
|
if kwds.get("sql_only", False):
|
|
2205
2222
|
return sql, vals
|
|
2206
2223
|
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Abstract base class for SQL dialect implementations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
6
7
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
8
|
|
|
@@ -38,6 +39,21 @@ class BaseSQLDialect(ABC):
|
|
|
38
39
|
DatabaseObjectExistsErrorCodes: List[str] = []
|
|
39
40
|
DataIntegrityErrorCodes: List[str] = []
|
|
40
41
|
|
|
42
|
+
@classmethod
|
|
43
|
+
def adapt_data_value(cls, val: Any) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Adapt a Python value being written to a column (INSERT/UPDATE data
|
|
46
|
+
values only — never WHERE parameters, which keep their native driver
|
|
47
|
+
adaptation, e.g. list -> ANY(...) on PostgreSQL).
|
|
48
|
+
|
|
49
|
+
Default: serialize dict/list to a JSON string for backends without a
|
|
50
|
+
native JSON parameter adapter (SQLite, SQL Server, MySQL). PostgreSQL
|
|
51
|
+
overrides this to bind JSONB natively.
|
|
52
|
+
"""
|
|
53
|
+
if isinstance(val, (dict, list)):
|
|
54
|
+
return json.dumps(val)
|
|
55
|
+
return val
|
|
56
|
+
|
|
41
57
|
@classmethod
|
|
42
58
|
def quote_identifier(cls, name: str) -> str:
|
|
43
59
|
"""Always-quote a single SQL identifier to prevent injection.
|
|
@@ -23,6 +23,7 @@ class TYPES(BaseTypes):
|
|
|
23
23
|
LONGTEXT = "LONGTEXT"
|
|
24
24
|
MEDIUMTEXT = "MEDIUMTEXT"
|
|
25
25
|
VARCHAR = "VARCHAR"
|
|
26
|
+
JSON = "JSON"
|
|
26
27
|
|
|
27
28
|
@classmethod
|
|
28
29
|
def get_type(cls, v):
|
|
@@ -51,6 +52,8 @@ class TYPES(BaseTypes):
|
|
|
51
52
|
return cls.TIME
|
|
52
53
|
if isinstance(v, bytes) or v is bytes:
|
|
53
54
|
return cls.BINARY
|
|
55
|
+
if isinstance(v, (dict, list)) or v is dict or v is list:
|
|
56
|
+
return cls.JSON
|
|
54
57
|
return cls.TEXT
|
|
55
58
|
|
|
56
59
|
@classmethod
|
|
@@ -80,6 +83,8 @@ class TYPES(BaseTypes):
|
|
|
80
83
|
return cls.TIME
|
|
81
84
|
if isinstance(v, bytes) or v is bytes:
|
|
82
85
|
return cls.BINARY
|
|
86
|
+
if isinstance(v, (dict, list)) or v is dict or v is list:
|
|
87
|
+
return cls.JSON
|
|
83
88
|
return cls.TEXT
|
|
84
89
|
|
|
85
90
|
@classmethod
|
|
@@ -108,4 +113,6 @@ class TYPES(BaseTypes):
|
|
|
108
113
|
return datetime.datetime
|
|
109
114
|
if v == cls.BINARY or "BLOB" in v:
|
|
110
115
|
return bytes
|
|
116
|
+
if v == cls.JSON:
|
|
117
|
+
return dict
|
|
111
118
|
raise Exception(f"Unmapped MySQL type {v}")
|
|
@@ -71,6 +71,20 @@ class SQL(BaseSQLDialect):
|
|
|
71
71
|
DatabaseObjectExistsErrorCodes = ["42710", "42P07", "42P04"]
|
|
72
72
|
DataIntegrityErrorCodes = ["23503", "23502", "23514", "23P01", "22003"]
|
|
73
73
|
|
|
74
|
+
@classmethod
|
|
75
|
+
def adapt_data_value(cls, val):
|
|
76
|
+
"""
|
|
77
|
+
Bind dict/list data values as JSONB. Lists must be wrapped here
|
|
78
|
+
because psycopg's native list adaptation targets PostgreSQL arrays
|
|
79
|
+
(kept for WHERE params, e.g. ``= ANY(%s)``); bare dicts also work via
|
|
80
|
+
the engine-registered dumper, but wrapping both keeps it uniform.
|
|
81
|
+
"""
|
|
82
|
+
if isinstance(val, (dict, list)):
|
|
83
|
+
from psycopg.types.json import Jsonb
|
|
84
|
+
|
|
85
|
+
return Jsonb(val)
|
|
86
|
+
return val
|
|
87
|
+
|
|
74
88
|
@classmethod
|
|
75
89
|
def get_error(cls, e):
|
|
76
90
|
# psycopg v3 uses 'sqlstate'; psycopg2 used 'pgcode'.
|
|
@@ -23,6 +23,8 @@ class TYPES(BaseTypes):
|
|
|
23
23
|
BOOLEAN = "BOOLEAN"
|
|
24
24
|
BINARY = "BYTEA"
|
|
25
25
|
INTERVAL = "INTERVAL"
|
|
26
|
+
JSONB = "JSONB"
|
|
27
|
+
JSON = "JSON"
|
|
26
28
|
|
|
27
29
|
@classmethod
|
|
28
30
|
def get_type(cls, v):
|
|
@@ -52,6 +54,8 @@ class TYPES(BaseTypes):
|
|
|
52
54
|
return cls.INTERVAL
|
|
53
55
|
if isinstance(v, bytes) or v is bytes:
|
|
54
56
|
return cls.BINARY
|
|
57
|
+
if isinstance(v, (dict, list)) or v is dict or v is list:
|
|
58
|
+
return cls.JSONB
|
|
55
59
|
return cls.TEXT
|
|
56
60
|
|
|
57
61
|
@classmethod
|
|
@@ -81,6 +85,8 @@ class TYPES(BaseTypes):
|
|
|
81
85
|
return cls.INTERVAL
|
|
82
86
|
if isinstance(v, bytes) or v is bytes:
|
|
83
87
|
return cls.BINARY
|
|
88
|
+
if isinstance(v, (dict, list)) or v is dict or v is list:
|
|
89
|
+
return cls.JSONB
|
|
84
90
|
return cls.TEXT
|
|
85
91
|
|
|
86
92
|
@classmethod
|
|
@@ -107,4 +113,8 @@ class TYPES(BaseTypes):
|
|
|
107
113
|
return datetime.timedelta
|
|
108
114
|
if v == cls.DATETIME_TZ or v == cls.TIMESTAMP_TZ:
|
|
109
115
|
return datetime.datetime
|
|
116
|
+
if v == cls.JSONB or v == cls.JSON:
|
|
117
|
+
# JSON columns hold either a dict or a list; dict is the
|
|
118
|
+
# canonical marker type for schema purposes.
|
|
119
|
+
return dict
|
|
110
120
|
raise Exception(f"Unmapped type {v}")
|
|
@@ -660,15 +660,15 @@ END;
|
|
|
660
660
|
|
|
661
661
|
@classmethod
|
|
662
662
|
def create_savepoint(cls, sp):
|
|
663
|
-
return f"SAVEPOINT {sp}"
|
|
663
|
+
return f"SAVEPOINT {sp}", tuple()
|
|
664
664
|
|
|
665
665
|
@classmethod
|
|
666
666
|
def release_savepoint(cls, sp):
|
|
667
|
-
return f"RELEASE SAVEPOINT {sp}"
|
|
667
|
+
return f"RELEASE SAVEPOINT {sp}", tuple()
|
|
668
668
|
|
|
669
669
|
@classmethod
|
|
670
670
|
def rollback_savepoint(cls, sp):
|
|
671
|
-
return f"ROLLBACK TO SAVEPOINT {sp}"
|
|
671
|
+
return f"ROLLBACK TO SAVEPOINT {sp}", tuple()
|
|
672
672
|
|
|
673
673
|
@classmethod
|
|
674
674
|
def create_view(cls, name, query, temp=False, silent=True):
|
|
@@ -469,6 +469,14 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
469
469
|
if self.platform:
|
|
470
470
|
payment_metadata["platform"] = str(self.platform)
|
|
471
471
|
|
|
472
|
+
# Optional idempotency key: when the caller supplies one, Stripe
|
|
473
|
+
# collapses retries of the *same* request (engine auto-retry, donor
|
|
474
|
+
# double-submit) into a single charge instead of charging again.
|
|
475
|
+
request_options = {}
|
|
476
|
+
idempotency_key = payment_data.get("idempotency_key")
|
|
477
|
+
if idempotency_key:
|
|
478
|
+
request_options["idempotency_key"] = str(idempotency_key)
|
|
479
|
+
|
|
472
480
|
payment_intent = stripe.PaymentIntent.create(
|
|
473
481
|
amount=amount_cents,
|
|
474
482
|
currency=currency,
|
|
@@ -482,6 +490,7 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
482
490
|
},
|
|
483
491
|
metadata=payment_metadata,
|
|
484
492
|
api_key=self._api_key,
|
|
493
|
+
**request_options,
|
|
485
494
|
)
|
|
486
495
|
charge = self._mirror_payment_intent_artifacts(
|
|
487
496
|
tx,
|
|
@@ -575,6 +584,11 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
575
584
|
if self.platform:
|
|
576
585
|
payment_metadata["platform"] = str(self.platform)
|
|
577
586
|
|
|
587
|
+
request_options = {}
|
|
588
|
+
idempotency_key = payment_data.get("idempotency_key")
|
|
589
|
+
if idempotency_key:
|
|
590
|
+
request_options["idempotency_key"] = str(idempotency_key)
|
|
591
|
+
|
|
578
592
|
payment_intent = stripe.PaymentIntent.create(
|
|
579
593
|
amount=amount_cents,
|
|
580
594
|
currency=currency,
|
|
@@ -587,6 +601,7 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
587
601
|
description=payment_data.get("description"),
|
|
588
602
|
metadata=payment_metadata,
|
|
589
603
|
api_key=self._api_key,
|
|
604
|
+
**request_options,
|
|
590
605
|
)
|
|
591
606
|
charge = self._mirror_payment_intent_artifacts(
|
|
592
607
|
tx,
|
|
@@ -41,6 +41,7 @@ src/velocity/db/core/column.py
|
|
|
41
41
|
src/velocity/db/core/database.py
|
|
42
42
|
src/velocity/db/core/decorators.py
|
|
43
43
|
src/velocity/db/core/engine.py
|
|
44
|
+
src/velocity/db/core/jsonproxy.py
|
|
44
45
|
src/velocity/db/core/result.py
|
|
45
46
|
src/velocity/db/core/row.py
|
|
46
47
|
src/velocity/db/core/sequence.py
|
|
@@ -165,6 +166,7 @@ tests/test_email_processing.py
|
|
|
165
166
|
tests/test_http_handler_rollback.py
|
|
166
167
|
tests/test_iconv_money_to_cents.py
|
|
167
168
|
tests/test_identifier_injection_guard.py
|
|
169
|
+
tests/test_json_columns.py
|
|
168
170
|
tests/test_jsonb_dict_adapter.py
|
|
169
171
|
tests/test_lambda_handler.py
|
|
170
172
|
tests/test_lambda_handler_auth.py
|