velocity-python 0.2.7__tar.gz → 0.2.9__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.2.7/src/velocity_python.egg-info → velocity_python-0.2.9}/PKG-INFO +1 -2
- {velocity_python-0.2.7 → velocity_python-0.2.9}/README.md +0 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/pyproject.toml +1 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/__init__.py +1 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/authz.py +48 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/security.py +105 -29
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/data_service.py +35 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/tiles_actions.py +8 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/schema.py +3 -1
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/braintree_adapter.py +29 -2
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/stripe_adapter.py +12 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9/src/velocity_python.egg-info}/PKG-INFO +1 -2
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_ensure_infrastructure.py +1 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_braintree_adapter.py +67 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_stripe_adapter.py +43 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_security_capability.py +77 -0
- velocity_python-0.2.9/tests/test_security_events_table_gate.py +210 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/LICENSE +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/setup.cfg +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/ci_gate.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/frontend_checks.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/facets.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/failures.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/identity.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/permissions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/queue_defaults.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/tiles.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/user_data.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/fragments.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/tunnel.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/logging.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/_mirror_core.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/money.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/plaid_api.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/plaid_webhook.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/redact.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/schema.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/testing/__init__.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/testing/fake_db.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_action_names.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_amplify_redirects.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_asset_references.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_assets_service.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_async_support.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_audit_mixin.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_authz.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_aws_package_hygiene.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_blob_store.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_business_objects.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_capabilities.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_check_changelog.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_ci_gate.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_client_errors_mixin.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_client_input_contract_export.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_client_sql_boundary.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_client_table_gate_case_and_pointers.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_cognito_client_lazy.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_context_queue_job.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_conv_combine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_owner_scope.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_row_number.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_data_service_security_gates.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_db_tunnel.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_declared_column_types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_decorators.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_deploy_config_fields.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_deploy_engine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_deploy_frontend_checks.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_deploy_operations_queue.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_dlq_constant.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_docs_snippets.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_email_processing.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_enqueue_metric_namespace.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_export_workbook.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_facets.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_facets_and_where.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_failures.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_identity.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_job_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_job_results.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_json_columns.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_merge_on_conflict.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_mirror_upsert_public.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_observability.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_on_action_default.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_on_commit.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_ops.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_audit_fixes.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_business_agnostic.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_charge_reference.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_extra_bounds.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_lazy_imports.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pdf.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_permissions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_permissions_actions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_permissions_notices.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_permissions_review.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_bulk_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_capability_types.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_ensure_infrastructure.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_get_tables.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_permissions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_pointer_joins.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_row_scope.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_search_export.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_tiles.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pgserver_user_data.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_plaid_api.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_plaid_webhook.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_pointer_join_sql.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_postgres_initializer_config.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_postgres_py_type.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_query_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_raw_sql_prefix_policy.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_read_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_readme_imports.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_response.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_result_returning.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_row_scoped_data_service.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_s3_bucket_config.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_s3_list_client_files.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_s3_upload.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_schema_lock_config_other_backends.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_search_paging_export.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_session_control.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_sms_twilio_signature.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_ssm_config_require.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_table_alter.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_table_claim.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_table_purge.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_testing_fake_db.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_tiles.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_tx_lifecycle_retry.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_tx_read_cache_and_bulk.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_user_data.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_user_not_provisioned.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/tests/test_write_hook_create_flow.py +0 -0
- {velocity_python-0.2.7 → velocity_python-0.2.9}/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.2.
|
|
3
|
+
Version: 0.2.9
|
|
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
|
|
@@ -267,7 +267,6 @@ What changed in each release, and what callers must do before upgrading, is in
|
|
|
267
267
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
268
268
|
| Security | `docs/SECURITY.md` | Pre-commit hooks, credential scanning |
|
|
269
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 |
|
|
271
270
|
|
|
272
271
|
---
|
|
273
272
|
|
|
@@ -174,7 +174,6 @@ What changed in each release, and what callers must do before upgrading, is in
|
|
|
174
174
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
175
175
|
| Security | `docs/SECURITY.md` | Pre-commit hooks, credential scanning |
|
|
176
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 |
|
|
178
177
|
|
|
179
178
|
---
|
|
180
179
|
|
|
@@ -935,6 +935,33 @@ def is_protected(table_name):
|
|
|
935
935
|
return table_name in _PROTECTED
|
|
936
936
|
|
|
937
937
|
|
|
938
|
+
# Tables whose rows are confidential (the security audit: everyone's sign-ins,
|
|
939
|
+
# addresses, refusals). The generic Data actions READ them only on a handler
|
|
940
|
+
# that lists the table in ``confidential_reads`` (an explicit opt-in, e.g. an
|
|
941
|
+
# operators-only console), in every mode: ``gate`` records nothing, so even a
|
|
942
|
+
# permitted read there would be unaudited.
|
|
943
|
+
_CONFIDENTIAL = set()
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def confidential_tables(*names):
|
|
947
|
+
"""Mark tables generic Data reads refuse unless the handler opts in (see
|
|
948
|
+
``DataServiceMixin.confidential_reads``), whatever ``authz_mode`` says.
|
|
949
|
+
Combine with :func:`protect_tables` to refuse writes as well."""
|
|
950
|
+
_CONFIDENTIAL.update(name for name in names if isinstance(name, str) and name)
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def is_confidential(table_name):
|
|
954
|
+
return table_name in _CONFIDENTIAL
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# velocity's own audit sink (capabilities.security.EVENTS_TABLE): append-only
|
|
958
|
+
# and confidential on every site, registered here so the rule never depends on
|
|
959
|
+
# which modules a handler happened to import (VelocityAudit SEC-12 follow-up).
|
|
960
|
+
SECURITY_EVENTS_TABLE = "sys_security_events"
|
|
961
|
+
protect_tables(SECURITY_EVENTS_TABLE)
|
|
962
|
+
confidential_tables(SECURITY_EVENTS_TABLE)
|
|
963
|
+
|
|
964
|
+
|
|
938
965
|
# ── the action index ───────────────────────────────────────────────────────
|
|
939
966
|
|
|
940
967
|
# id(rules tuple) -> (the tuple, {key: its rules in order}), where key is the
|
|
@@ -1319,6 +1346,15 @@ def _describe(resource):
|
|
|
1319
1346
|
return f"{resource.type}:{resource.id}"
|
|
1320
1347
|
|
|
1321
1348
|
|
|
1349
|
+
def record_confidential_read(handler, tx, context, table):
|
|
1350
|
+
"""Record one generic read of a confidential table by a handler that opted
|
|
1351
|
+
in (``confidential_reads``) as ``privileged_access``; False when it could
|
|
1352
|
+
not be recorded (the caller refuses the read). The opt-in serves an
|
|
1353
|
+
operators-only console; every use of it is still on the record."""
|
|
1354
|
+
decision = Decision(True, rule="confidential_reads", audit=True, action=DATA_READ)
|
|
1355
|
+
return bool(_record(handler, tx, context, decision, Resource(TABLE, table)))
|
|
1356
|
+
|
|
1357
|
+
|
|
1322
1358
|
def _record(handler, tx, context, decision, resource):
|
|
1323
1359
|
# imported here: the capabilities package pulls in more than a pure
|
|
1324
1360
|
# decision module should load at import
|
|
@@ -1330,6 +1366,16 @@ def _record(handler, tx, context, decision, resource):
|
|
|
1330
1366
|
if isinstance(session.get("masquerade"), dict)
|
|
1331
1367
|
else None
|
|
1332
1368
|
) or session.get("email_address")
|
|
1369
|
+
# the subject API Gateway's authorizer verified (the real caller, also
|
|
1370
|
+
# under masquerade), and its issuer, so the record names an identity and
|
|
1371
|
+
# not only an email
|
|
1372
|
+
sub = session.get("sub") or None
|
|
1373
|
+
provider = session.get("authentication_provider")
|
|
1374
|
+
issuer = session.get("iss") or (
|
|
1375
|
+
"https://" + provider.split(",", 1)[0].strip()
|
|
1376
|
+
if isinstance(provider, str) and provider.strip()
|
|
1377
|
+
else None
|
|
1378
|
+
)
|
|
1333
1379
|
return record_event(
|
|
1334
1380
|
tx,
|
|
1335
1381
|
PRIVILEGED_ACCESS,
|
|
@@ -1337,4 +1383,6 @@ def _record(handler, tx, context, decision, resource):
|
|
|
1337
1383
|
aws_event=getattr(handler, "aws_event", None),
|
|
1338
1384
|
context=context,
|
|
1339
1385
|
user_email=actor,
|
|
1386
|
+
user_sub=sub,
|
|
1387
|
+
detail={"iss": issuer} if issuer else None,
|
|
1340
1388
|
)
|
|
@@ -20,6 +20,15 @@ Attach :class:`SecurityMixin` FIRST in a handler's bases; it runs in
|
|
|
20
20
|
``beforeAction`` ahead of auth and every action, and is a no-op unless the
|
|
21
21
|
site's ``sys_capabilities`` row for ``security`` is enabled. A refusal raises
|
|
22
22
|
:class:`AccessError` (a 403) after committing a ``sys_security_events`` row.
|
|
23
|
+
|
|
24
|
+
``sys_security_events`` itself is a CORE table (``SCHEMA``, in
|
|
25
|
+
``velocity.aws.schema``'s ``core`` group), not the capability's: it is
|
|
26
|
+
velocity's one audit sink — refusals, masquerade grants, sign-ins — and every
|
|
27
|
+
site keeps it whether or not the guards are on (VelocityAudit SEC-12: with the
|
|
28
|
+
table capability-owned, a site without ``security`` silently dropped its
|
|
29
|
+
masquerade audit). Pruning stays here (``security_events_days``, default 365 to
|
|
30
|
+
match a one-year audit retention); a site without the capability lists the
|
|
31
|
+
table in the ``retention`` capability instead.
|
|
23
32
|
"""
|
|
24
33
|
|
|
25
34
|
from __future__ import annotations
|
|
@@ -46,6 +55,48 @@ log = logging.getLogger(__name__)
|
|
|
46
55
|
|
|
47
56
|
EVENTS_TABLE = "sys_security_events"
|
|
48
57
|
RATE_TABLE = "sys_rate_limit"
|
|
58
|
+
EVENT_COLUMNS = {
|
|
59
|
+
"guard": "TEXT",
|
|
60
|
+
"reason": "TEXT",
|
|
61
|
+
"ip": "TEXT",
|
|
62
|
+
"country": "TEXT",
|
|
63
|
+
"path": "TEXT",
|
|
64
|
+
"action": "TEXT",
|
|
65
|
+
"user_email": "TEXT",
|
|
66
|
+
"user_agent": "TEXT",
|
|
67
|
+
"user_sub": "TEXT",
|
|
68
|
+
"detail": "JSONB",
|
|
69
|
+
# when it happened (a sign-in trigger fired), which a queue can write
|
|
70
|
+
# later than sys_created; defaults to the time of writing
|
|
71
|
+
"occurred_at": "TIMESTAMPTZ",
|
|
72
|
+
}
|
|
73
|
+
# Append-only and confidential on every site: authz registers the table as
|
|
74
|
+
# protected (no generic write) and confidential (generic reads need an opt-in
|
|
75
|
+
# or enforce) itself, so the rule holds whether or not this module is imported.
|
|
76
|
+
# core infrastructure (velocity.aws.schema "core"): every site has the table
|
|
77
|
+
SCHEMA = {
|
|
78
|
+
EVENTS_TABLE: {
|
|
79
|
+
"columns": dict(EVENT_COLUMNS),
|
|
80
|
+
"indexes": [
|
|
81
|
+
{
|
|
82
|
+
"columns": ["sys_created"],
|
|
83
|
+
"name": "idx__sys_security_events__sys_created",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"columns": ["guard", "sys_created"],
|
|
87
|
+
"name": "idx__sys_security_events__guard",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"columns": ["user_sub", "sys_created"],
|
|
91
|
+
"name": "idx__sys_security_events__user_sub",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"columns": ["user_sub", "occurred_at"],
|
|
95
|
+
"name": "idx__sys_security_events__user_sub_occurred",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
}
|
|
49
100
|
CACHE_TTL_SECONDS = 60 # per-container cache of the switches + site row
|
|
50
101
|
|
|
51
102
|
SWITCHES = {
|
|
@@ -59,7 +110,7 @@ SWITCHES = {
|
|
|
59
110
|
KNOBS = {
|
|
60
111
|
"security_rate_limit_per_minute": "120",
|
|
61
112
|
"security_payload_max_bytes": "1000000",
|
|
62
|
-
"security_events_days": "
|
|
113
|
+
"security_events_days": "365",
|
|
63
114
|
}
|
|
64
115
|
|
|
65
116
|
|
|
@@ -275,30 +326,62 @@ def enforce(tx, aws_event, context, *, now=None):
|
|
|
275
326
|
return config
|
|
276
327
|
|
|
277
328
|
|
|
278
|
-
def record_event(
|
|
329
|
+
def record_event(
|
|
330
|
+
tx,
|
|
331
|
+
guard,
|
|
332
|
+
reason,
|
|
333
|
+
*,
|
|
334
|
+
aws_event=None,
|
|
335
|
+
context=None,
|
|
336
|
+
user_email=None,
|
|
337
|
+
user_sub=None,
|
|
338
|
+
detail=None,
|
|
339
|
+
occurred_at=None,
|
|
340
|
+
):
|
|
279
341
|
"""Append one ``sys_security_events`` row and commit it — an audit row
|
|
280
342
|
must survive whatever the caller does next (typically raise). The one
|
|
281
|
-
audit sink for the framework: the request guards, masquerade grants,
|
|
282
|
-
anything else security-relevant write here.
|
|
283
|
-
|
|
284
|
-
|
|
343
|
+
audit sink for the framework: the request guards, masquerade grants,
|
|
344
|
+
sign-ins, and anything else security-relevant write here. ``user_sub`` is
|
|
345
|
+
the identity provider's subject; ``detail`` a JSON-able dict of facts that
|
|
346
|
+
have no column; ``occurred_at`` when it happened, if not now (an event
|
|
347
|
+
relayed through a queue). Silent when the table is not provisioned in this site, and
|
|
348
|
+
an audit failure never breaks the request. Returns True when a row was
|
|
349
|
+
written.
|
|
350
|
+
|
|
351
|
+
A table created before ``user_sub``/``detail`` existed (a site whose schema
|
|
352
|
+
lane has not run since) still records the row without them: on a locked
|
|
353
|
+
schema an unknown column would otherwise lose the whole event."""
|
|
285
354
|
try:
|
|
286
355
|
table = tx.table(EVENTS_TABLE)
|
|
287
356
|
if not table.exists():
|
|
288
357
|
return False
|
|
289
358
|
facts = request_facts(aws_event)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
359
|
+
row = {
|
|
360
|
+
"guard": guard,
|
|
361
|
+
"reason": reason,
|
|
362
|
+
"ip": facts["ip"],
|
|
363
|
+
"country": facts["country"],
|
|
364
|
+
"path": facts["path"],
|
|
365
|
+
"action": _action(context) if context is not None else "",
|
|
366
|
+
"user_email": user_email,
|
|
367
|
+
"user_agent": facts["user_agent"][:512],
|
|
368
|
+
}
|
|
369
|
+
extra = {
|
|
370
|
+
"user_sub": user_sub,
|
|
371
|
+
"detail": detail,
|
|
372
|
+
"occurred_at": occurred_at or datetime.datetime.now(datetime.timezone.utc),
|
|
373
|
+
}
|
|
374
|
+
extra = {k: v for k, v in extra.items() if v is not None}
|
|
375
|
+
if extra:
|
|
376
|
+
try:
|
|
377
|
+
have = {c.lower() for c in table.sys_columns()}
|
|
378
|
+
except Exception:
|
|
379
|
+
# can't tell which newer columns exist: the event still lands
|
|
380
|
+
# with the columns every version of the table has
|
|
381
|
+
log.warning("security event %s: columns unknown", guard, exc_info=True)
|
|
382
|
+
have = set()
|
|
383
|
+
row.update({k: v for k, v in extra.items() if k in have})
|
|
384
|
+
table.insert(row)
|
|
302
385
|
tx.commit()
|
|
303
386
|
return True
|
|
304
387
|
except Exception:
|
|
@@ -386,16 +469,9 @@ class Security(Capability):
|
|
|
386
469
|
),
|
|
387
470
|
)
|
|
388
471
|
schema = {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"ip": "TEXT",
|
|
393
|
-
"country": "TEXT",
|
|
394
|
-
"path": "TEXT",
|
|
395
|
-
"action": "TEXT",
|
|
396
|
-
"user_email": "TEXT",
|
|
397
|
-
"user_agent": "TEXT",
|
|
398
|
-
},
|
|
472
|
+
# the events table is core (module SCHEMA); listed here too so turning
|
|
473
|
+
# the capability on in an older site still ensures it
|
|
474
|
+
EVENTS_TABLE: dict(EVENT_COLUMNS),
|
|
399
475
|
RATE_TABLE: {"bucket": "TEXT", "window_start": "TIMESTAMPTZ", "hits": "BIGINT"},
|
|
400
476
|
}
|
|
401
477
|
schedules = (("prune", HOURLY),)
|
|
@@ -407,7 +483,7 @@ class Security(Capability):
|
|
|
407
483
|
if action != "prune":
|
|
408
484
|
raise ValueError(f"security: unknown action {action!r}")
|
|
409
485
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
410
|
-
days = int(config.get("security_events_days") or
|
|
486
|
+
days = int(config.get("security_events_days") or 365)
|
|
411
487
|
return {
|
|
412
488
|
"rate_buckets": tx.table(RATE_TABLE).purge(
|
|
413
489
|
{"<sys_created": now - datetime.timedelta(hours=1)}
|
{velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -588,6 +588,7 @@ class ExportStream:
|
|
|
588
588
|
def __enter__(self):
|
|
589
589
|
handler, payload = self.handler, self.payload
|
|
590
590
|
handler._assert_client_table(self.table, "obj")
|
|
591
|
+
handler._audit_confidential_read(self.tx, self.table, self.context)
|
|
591
592
|
# ``table`` is the only source: it is what passed the table gate and
|
|
592
593
|
# whose hooks and scope run below, so a payload ``obj`` naming any
|
|
593
594
|
# other table is refused rather than read.
|
|
@@ -915,6 +916,11 @@ class DataServiceMixin:
|
|
|
915
916
|
# handler, not in the gate methods' signatures, so a site's override of
|
|
916
917
|
# those keeps working.
|
|
917
918
|
_table_operation = authz.DATA_READ
|
|
919
|
+
# Confidential tables (``authz.confidential_tables``) this handler serves
|
|
920
|
+
# to generic reads: an explicit opt-in for a console whose every user is
|
|
921
|
+
# an operator. Without it such a table is never read through generic Data
|
|
922
|
+
# (in any authz mode); it is read through its own audited action.
|
|
923
|
+
confidential_reads = frozenset()
|
|
918
924
|
|
|
919
925
|
def _client_table_allowed(self, table):
|
|
920
926
|
# lower case only: velocity lower-cases every Table name and
|
|
@@ -937,10 +943,30 @@ class DataServiceMixin:
|
|
|
937
943
|
# ``authz_mode`` say.
|
|
938
944
|
if self._table_operation != authz.DATA_READ and authz.is_protected(table):
|
|
939
945
|
return False
|
|
946
|
+
# Confidential tables (``authz.confidential_tables``: the security
|
|
947
|
+
# audit) are read through generic Data only where the handler opted
|
|
948
|
+
# in. Never by default, and not under ``enforce`` either: ``gate``
|
|
949
|
+
# answers allow/deny but records nothing, so a super user's read would
|
|
950
|
+
# go unaudited; the table's own action (``authz.require``) records it.
|
|
951
|
+
if authz.is_confidential(table) and table not in self.confidential_reads:
|
|
952
|
+
return False
|
|
940
953
|
return authz.gate(
|
|
941
954
|
self, self._table_operation, authz.Resource(authz.TABLE, table)
|
|
942
955
|
)
|
|
943
956
|
|
|
957
|
+
def _audit_confidential_read(self, tx, table, context):
|
|
958
|
+
"""A generic read of a confidential table (only reachable on a handler
|
|
959
|
+
that lists it in ``confidential_reads``) is recorded as privileged
|
|
960
|
+
access first, and refused when it can't be: the opt-in keeps an
|
|
961
|
+
operators' grid working without making its reads invisible. Called by
|
|
962
|
+
the read ACTIONS right after their table gate (query, read-object,
|
|
963
|
+
find-object, query-direct, export), never from an overridable seam,
|
|
964
|
+
so a site's hook override can't skip it."""
|
|
965
|
+
if not authz.is_confidential(table):
|
|
966
|
+
return
|
|
967
|
+
if not authz.record_confidential_read(self, tx, context, table):
|
|
968
|
+
raise AccessError("That access could not be recorded, so it was refused.")
|
|
969
|
+
|
|
944
970
|
def _assert_client_table(self, table, label="table"):
|
|
945
971
|
"""The table gate every client action passes (SEC 4.2)."""
|
|
946
972
|
ClientInputGuard.table_name(table, label)
|
|
@@ -1022,7 +1048,11 @@ class DataServiceMixin:
|
|
|
1022
1048
|
if not info:
|
|
1023
1049
|
continue
|
|
1024
1050
|
foreign = info["referenced_table_name"]
|
|
1025
|
-
|
|
1051
|
+
# never into a confidential table: its reads are audited by the
|
|
1052
|
+
# actions that open it by name, and a join would skip that
|
|
1053
|
+
if authz.is_confidential(foreign) or not self._client_table_allowed(
|
|
1054
|
+
foreign
|
|
1055
|
+
):
|
|
1026
1056
|
raise AlertError(f"Access to '{table}.{local}>' is not permitted.")
|
|
1027
1057
|
# A join reads the foreign table's rows without that table's row
|
|
1028
1058
|
# filter. So someone who reaches only part of it cannot read it
|
|
@@ -2053,6 +2083,7 @@ class DataServiceMixin:
|
|
|
2053
2083
|
if not obj:
|
|
2054
2084
|
raise ValueError("Parameter 'object' cannot be empty")
|
|
2055
2085
|
self._assert_client_table(table_name, "tableName")
|
|
2086
|
+
self._audit_confidential_read(tx, table_name, context)
|
|
2056
2087
|
if not isinstance(obj, dict):
|
|
2057
2088
|
raise BadRequestError("Parameter 'object' must be an object")
|
|
2058
2089
|
if obj.get("sys_id") != NEW_ROW:
|
|
@@ -2094,6 +2125,7 @@ class DataServiceMixin:
|
|
|
2094
2125
|
if not query or "where" not in query:
|
|
2095
2126
|
raise ValueError("Parameter 'query' must contain 'where' clause")
|
|
2096
2127
|
self._assert_client_table(table_name, "tableName")
|
|
2128
|
+
self._audit_confidential_read(tx, table_name, context)
|
|
2097
2129
|
query["where"] = self._client_where(tx, table_name, query["where"])
|
|
2098
2130
|
|
|
2099
2131
|
row = self.find_hook(
|
|
@@ -2254,6 +2286,7 @@ class DataServiceMixin:
|
|
|
2254
2286
|
if not table:
|
|
2255
2287
|
raise ValueError("Parameter 'obj' cannot be empty")
|
|
2256
2288
|
self._assert_client_table(table, "obj")
|
|
2289
|
+
self._audit_confidential_read(tx, table, context)
|
|
2257
2290
|
payload["params"] = self._client_query_params(tx, table, payload.get("params"))
|
|
2258
2291
|
|
|
2259
2292
|
result_format = payload.get("result_format")
|
|
@@ -2438,6 +2471,7 @@ class DataServiceMixin:
|
|
|
2438
2471
|
|
|
2439
2472
|
self._assert_direct_access_allowed(table_name)
|
|
2440
2473
|
self._assert_direct_unscoped(table_name, context)
|
|
2474
|
+
self._audit_confidential_read(tx, table_name, context)
|
|
2441
2475
|
|
|
2442
2476
|
params = self._client_query_params(tx, table_name, payload.get("params", {}))
|
|
2443
2477
|
|
{velocity_python-0.2.7 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/tiles_actions.py
RENAMED
|
@@ -112,6 +112,10 @@ class TilesMixin:
|
|
|
112
112
|
rule with the viewer filled in, and that where."""
|
|
113
113
|
table = tile["target"]
|
|
114
114
|
self._assert_client_table(table, "target")
|
|
115
|
+
# A confidential table (the security audit) is never a tile target:
|
|
116
|
+
# a saved, re-counted filter over it would be an unaudited oracle.
|
|
117
|
+
if authz.is_confidential(table):
|
|
118
|
+
raise AlertError(f"'{table}' can't be counted on a tile.")
|
|
115
119
|
rule = tiles.fill_rule(
|
|
116
120
|
tiles.clean_rule(_as_dict(tile.get("rule"))),
|
|
117
121
|
principal,
|
|
@@ -218,7 +222,10 @@ class TilesMixin:
|
|
|
218
222
|
return declared is not None and (
|
|
219
223
|
not declared.permission or principal.holds(declared.permission)
|
|
220
224
|
)
|
|
221
|
-
|
|
225
|
+
target = str(tile.get("target") or "")
|
|
226
|
+
if authz.is_confidential(target):
|
|
227
|
+
return False
|
|
228
|
+
return bool(self._client_table_allowed(target))
|
|
222
229
|
|
|
223
230
|
def _tile_public(self, tx, context, tile, principal, today, fresh=False, version=0):
|
|
224
231
|
"""One tile as the browser gets it, or None when it is not this
|
|
@@ -21,7 +21,8 @@ renames or retypes an existing column.
|
|
|
21
21
|
|
|
22
22
|
Groups:
|
|
23
23
|
|
|
24
|
-
* ``core`` — the tables every velocity handler app writes
|
|
24
|
+
* ``core`` — the tables every velocity handler app writes, including the
|
|
25
|
+
``sys_security_events`` audit sink (SEC-12).
|
|
25
26
|
* ``assets`` — ``images`` + ``asset_usage_index`` (:mod:`velocity.aws.assets`).
|
|
26
27
|
* ``payment`` — processor mirror tables (:mod:`velocity.payment.schema`).
|
|
27
28
|
* ``permissions`` — roles, grants and requests (:mod:`velocity.aws.permissions`).
|
|
@@ -50,6 +51,7 @@ SCHEMA_SOURCES: Dict[str, Tuple[str, ...]] = {
|
|
|
50
51
|
"velocity.aws.handlers.mixins.web_handler",
|
|
51
52
|
"velocity.aws.handlers.mixins.session_control",
|
|
52
53
|
"velocity.aws.user_data",
|
|
54
|
+
"velocity.aws.capabilities.security",
|
|
53
55
|
"velocity.db.migrations",
|
|
54
56
|
),
|
|
55
57
|
"assets": ("velocity.aws.assets",),
|
|
@@ -113,6 +113,24 @@ def parse_braintree_environment(value: Any = None) -> str:
|
|
|
113
113
|
return name
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def _decline_details(transaction: Any) -> Dict[str, Any]:
|
|
117
|
+
"""The processor's own verdict on a declined sale, for callers that decide
|
|
118
|
+
whether to try the card again: ``processor_response_code``,
|
|
119
|
+
``processor_response_text`` and ``processor_response_type``
|
|
120
|
+
(``approved`` / ``soft_declined`` / ``hard_declined`` -- Braintree's
|
|
121
|
+
classification; a hard decline will not be approved on retry). Only the
|
|
122
|
+
keys Braintree set; empty without a transaction (gateway rejection or
|
|
123
|
+
validation failure)."""
|
|
124
|
+
if transaction is None:
|
|
125
|
+
return {}
|
|
126
|
+
details = {
|
|
127
|
+
"processor_response_code": _attr(transaction, "processor_response_code"),
|
|
128
|
+
"processor_response_text": _attr(transaction, "processor_response_text"),
|
|
129
|
+
"processor_response_type": _attr(transaction, "processor_response_type"),
|
|
130
|
+
}
|
|
131
|
+
return {key: value for key, value in details.items() if value not in (None, "")}
|
|
132
|
+
|
|
133
|
+
|
|
116
134
|
def _deep_errors(result: Any) -> List[str]:
|
|
117
135
|
errors = getattr(getattr(result, "errors", None), "deep_errors", None) or []
|
|
118
136
|
return [scrub(f"{error.code}: {error.message}") for error in errors]
|
|
@@ -556,7 +574,11 @@ class BraintreeAdapter(PaymentProcessorAdapter):
|
|
|
556
574
|
"expires_at": None,
|
|
557
575
|
"error_message": error_message,
|
|
558
576
|
"metadata": _with_reference(
|
|
559
|
-
{
|
|
577
|
+
{
|
|
578
|
+
"braintree_errors": _deep_errors(result),
|
|
579
|
+
**_decline_details(result.transaction),
|
|
580
|
+
},
|
|
581
|
+
order_id,
|
|
560
582
|
),
|
|
561
583
|
}
|
|
562
584
|
|
|
@@ -674,7 +696,12 @@ class BraintreeAdapter(PaymentProcessorAdapter):
|
|
|
674
696
|
)
|
|
675
697
|
error_message = scrub(result.transaction.processor_response_text)
|
|
676
698
|
return failure(
|
|
677
|
-
"failed",
|
|
699
|
+
"failed",
|
|
700
|
+
error_message,
|
|
701
|
+
{
|
|
702
|
+
"braintree_errors": _deep_errors(result),
|
|
703
|
+
**_decline_details(result.transaction),
|
|
704
|
+
},
|
|
678
705
|
)
|
|
679
706
|
|
|
680
707
|
def find_charge_by_reference(
|
|
@@ -139,6 +139,16 @@ def _amount_captured(payment_intent: Any, charge: Any, requested: Optional[int])
|
|
|
139
139
|
return 0
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def _decline_details(error: Any) -> Dict[str, Any]:
|
|
143
|
+
"""``decline_code`` of a card decline (``CardError``): the issuer's reason
|
|
144
|
+
(``insufficient_funds``, ``lost_card``, ``expired_card`` ...), for callers
|
|
145
|
+
that decide whether to try the card again. Empty when Stripe gave none."""
|
|
146
|
+
decline_code = getattr(error, "decline_code", None) or getattr(
|
|
147
|
+
getattr(error, "error", None), "decline_code", None
|
|
148
|
+
)
|
|
149
|
+
return {"decline_code": decline_code} if decline_code else {}
|
|
150
|
+
|
|
151
|
+
|
|
142
152
|
class StripeAdapter(PaymentProcessorAdapter):
|
|
143
153
|
"""
|
|
144
154
|
Stripe Connect adapter for payment processing.
|
|
@@ -664,6 +674,7 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
664
674
|
"error_code": e.code,
|
|
665
675
|
"error_type": type(e).__name__,
|
|
666
676
|
"idempotency_key": idempotency_key,
|
|
677
|
+
**_decline_details(e),
|
|
667
678
|
},
|
|
668
679
|
}
|
|
669
680
|
if reference:
|
|
@@ -824,6 +835,7 @@ class StripeAdapter(PaymentProcessorAdapter):
|
|
|
824
835
|
"error_code": e.code,
|
|
825
836
|
"error_type": type(e).__name__,
|
|
826
837
|
"idempotency_key": idempotency_key,
|
|
838
|
+
**_decline_details(e),
|
|
827
839
|
},
|
|
828
840
|
}
|
|
829
841
|
if reference:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
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
|
|
@@ -267,7 +267,6 @@ What changed in each release, and what callers must do before upgrading, is in
|
|
|
267
267
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
268
268
|
| Security | `docs/SECURITY.md` | Pre-commit hooks, credential scanning |
|
|
269
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 |
|
|
271
270
|
|
|
272
271
|
---
|
|
273
272
|
|
|
@@ -377,6 +377,7 @@ tests/test_schema_lock_config_other_backends.py
|
|
|
377
377
|
tests/test_schema_migrations.py
|
|
378
378
|
tests/test_search_paging_export.py
|
|
379
379
|
tests/test_security_capability.py
|
|
380
|
+
tests/test_security_events_table_gate.py
|
|
380
381
|
tests/test_security_hardening.py
|
|
381
382
|
tests/test_server_cursor.py
|
|
382
383
|
tests/test_session_control.py
|
|
@@ -299,3 +299,70 @@ def test_generate_client_token_exhausted_raises_processor_error(monkeypatch):
|
|
|
299
299
|
with pytest.raises(ProcessorError) as err:
|
|
300
300
|
adapter.generate_client_token(attempts=2)
|
|
301
301
|
assert "after 2 attempts" in str(err.value)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _declined_sale(**transaction):
|
|
305
|
+
return lambda params: SimpleNamespace(
|
|
306
|
+
is_success=False,
|
|
307
|
+
message="generic error",
|
|
308
|
+
transaction=SimpleNamespace(**transaction) if transaction else None,
|
|
309
|
+
errors=SimpleNamespace(deep_errors=[]),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_charge_failure_carries_the_processor_verdict(monkeypatch):
|
|
314
|
+
"""A hard decline will not be approved on retry; callers (recurring
|
|
315
|
+
billing) read Braintree's own processor_response_type instead of
|
|
316
|
+
guessing from the response text."""
|
|
317
|
+
adapter = build_adapter()
|
|
318
|
+
monkeypatch.setattr(
|
|
319
|
+
adapter.gateway.transaction,
|
|
320
|
+
"sale",
|
|
321
|
+
_declined_sale(
|
|
322
|
+
processor_response_text="Closed Card",
|
|
323
|
+
processor_response_code="2004",
|
|
324
|
+
processor_response_type="hard_declined",
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
result = adapter.charge_stored_payment_method(
|
|
329
|
+
None, {"amount": 1050, "payment_method": "token_123"}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
assert result["error_message"] == "Closed Card"
|
|
333
|
+
assert result["metadata"]["processor_response_type"] == "hard_declined"
|
|
334
|
+
assert result["metadata"]["processor_response_code"] == "2004"
|
|
335
|
+
assert result["metadata"]["processor_response_text"] == "Closed Card"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_gateway_rejection_has_no_processor_verdict(monkeypatch):
|
|
339
|
+
adapter = build_adapter()
|
|
340
|
+
monkeypatch.setattr(adapter.gateway.transaction, "sale", _declined_sale())
|
|
341
|
+
|
|
342
|
+
result = adapter.charge_stored_payment_method(
|
|
343
|
+
None, {"amount": 1050, "payment_method": "token_123"}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
assert result["success"] is False
|
|
347
|
+
assert "processor_response_type" not in result["metadata"]
|
|
348
|
+
assert result["metadata"]["braintree_errors"] == []
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def test_authorize_failure_carries_the_processor_verdict(monkeypatch):
|
|
352
|
+
adapter = build_adapter()
|
|
353
|
+
monkeypatch.setattr(
|
|
354
|
+
adapter.gateway.transaction,
|
|
355
|
+
"sale",
|
|
356
|
+
_declined_sale(
|
|
357
|
+
processor_response_text="Insufficient Funds",
|
|
358
|
+
processor_response_type="soft_declined",
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
result = adapter.authorize_payment(
|
|
363
|
+
None, {"amount": 1050, "payment_method": "token_123"}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
assert result["status"] == "failed"
|
|
367
|
+
assert result["metadata"]["processor_response_type"] == "soft_declined"
|
|
368
|
+
assert "processor_response_code" not in result["metadata"]
|