velocity-python 0.3.0__tar.gz → 0.3.1__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.3.0 → velocity_python-0.3.1}/PKG-INFO +1 -1
- {velocity_python-0.3.0 → velocity_python-0.3.1}/pyproject.toml +1 -1
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/__init__.py +1 -1
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/security.py +118 -43
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/lambda_handler.py +12 -4
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/audit.py +9 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/engine.py +35 -9
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/transaction.py +16 -2
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_connection_pool.py +75 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_masquerade.py +88 -1
- velocity_python-0.3.1/tests/test_record_event_postgres.py +245 -0
- velocity_python-0.3.1/tests/test_record_event_transaction.py +409 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_capability.py +20 -4
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_user_not_provisioned.py +2 -1
- {velocity_python-0.3.0 → velocity_python-0.3.1}/LICENSE +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/README.md +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/setup.cfg +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/authz.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/ci_gate.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/frontend_checks.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/facets.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/failures.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/identity.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/permissions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/queue_defaults.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/scheduler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/schema.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tiles.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/user_data.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/_pgddl.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/effective.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/fragments.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/integrity.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_integrity_triggers.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tunnel.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/hooks.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/logging.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/_mirror_core.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/money.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/plaid_api.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/plaid_webhook.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/redact.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/schema.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/engine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/expressions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/facts.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/storage.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/testing/__init__.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/testing/fake_db.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_action_names.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_amplify_redirects.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_asset_references.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_assets_service.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_async_support.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_log_confidential.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_mixin.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_registry_table_gate.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_authz.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_aws_package_hygiene.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_blob_store.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_business_objects.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capabilities.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capability_dependencies.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_check_changelog.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ci_gate.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_errors_mixin.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_input_contract_export.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_sql_boundary.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_table_gate_case_and_pointers.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_cognito_client_lazy.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_enqueue_idempotency.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_queue_job.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_conv_combine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_owner_scope.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_row_number.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_security_gates.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_audit_required.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_effective.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_integrity.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_tunnel.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_declared_column_types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_decorators.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_config_fields.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_engine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_frontend_checks.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_operations_queue.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dirty_pipeline_consumer.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dlq_constant.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_docs_snippets.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_email_processing.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_enqueue_metric_namespace.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ensure_infrastructure.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_export_workbook.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_facets.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_facets_and_where.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_failures.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_hooks.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_identity.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_record_guard.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_record_guard_postgres.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_results.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_json_columns.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_merge_on_conflict.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mirror_upsert_public.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_observability.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_on_action_default.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_on_commit.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ops.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_audit_fixes.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_business_agnostic.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_charge_reference.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_extra_bounds.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_lazy_imports.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pdf.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_actions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_notices.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_review.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_bulk_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_capability_types.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_ensure_infrastructure.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_get_tables.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_permissions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_pointer_joins.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_row_scope.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_search_export.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_tiles.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_user_data.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_plaid_api.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_plaid_webhook.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pointer_join_sql.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_postgres_initializer_config.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_postgres_py_type.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_query_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_raw_sql_prefix_policy.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_read_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_readme_imports.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_response.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_result_returning.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_scoped_data_service.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_rules.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_rules_storage.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_bucket_config.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_list_client_files.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_upload.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_scheduler.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_schema_lock_config_other_backends.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_search_paging_export.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_events_table_gate.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_session_control.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sms_twilio_signature.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ssm_config_require.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_alter.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_claim.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_purge.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_testing_fake_db.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tiles.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tx_lifecycle_retry.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tx_read_cache_and_bulk.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_user_data.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_write_hook_create_flow.py +0 -0
- {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_write_hook_empty_strings.py +0 -0
|
@@ -19,7 +19,8 @@ rate limits, payload caps, and an audit trail of what was refused and why.
|
|
|
19
19
|
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
|
-
:class:`AccessError` (a 403) after
|
|
22
|
+
:class:`AccessError` (a 403) after recording a ``sys_security_events`` row
|
|
23
|
+
(committed on its own transaction, so the 403's rollback keeps it).
|
|
23
24
|
|
|
24
25
|
``sys_security_events`` itself is a CORE table (``SCHEMA``, in
|
|
25
26
|
``velocity.aws.schema``'s ``core`` group), not the capability's: it is
|
|
@@ -39,6 +40,7 @@ import logging
|
|
|
39
40
|
|
|
40
41
|
from velocity.aws import feature_flags
|
|
41
42
|
from velocity.cache import memo, read_cache
|
|
43
|
+
from velocity.db import _pgddl, audit
|
|
42
44
|
|
|
43
45
|
from .base import (
|
|
44
46
|
CIDR_LIST,
|
|
@@ -314,7 +316,13 @@ def enforce(tx, aws_event, context, *, now=None):
|
|
|
314
316
|
guard, reason = refusal
|
|
315
317
|
if truthy(config.get("security_block_logging_enabled")):
|
|
316
318
|
record_event(
|
|
317
|
-
tx,
|
|
319
|
+
tx,
|
|
320
|
+
guard,
|
|
321
|
+
reason,
|
|
322
|
+
aws_event=aws_event,
|
|
323
|
+
context=context,
|
|
324
|
+
user_email=user,
|
|
325
|
+
actor=GUARD_AUDIT_ACTOR,
|
|
318
326
|
)
|
|
319
327
|
log.warning("security guard %s refused %s: %s", guard, facts["ip"], reason)
|
|
320
328
|
# imported here, not at module top: the capabilities package must not
|
|
@@ -326,6 +334,15 @@ def enforce(tx, aws_event, context, *, now=None):
|
|
|
326
334
|
return config
|
|
327
335
|
|
|
328
336
|
|
|
337
|
+
# A second connection waiting on a lock the caller's own transaction holds
|
|
338
|
+
# would wait forever (the database can't see it as a deadlock): bound it, so
|
|
339
|
+
# the event fails (and an audited decision is refused) instead of hanging.
|
|
340
|
+
EVENT_LOCK_TIMEOUT = "5s"
|
|
341
|
+
# The audit actor of a guard refusal: it is recorded in beforeAction, before
|
|
342
|
+
# any request actor is set.
|
|
343
|
+
GUARD_AUDIT_ACTOR = "system:security_guard"
|
|
344
|
+
|
|
345
|
+
|
|
329
346
|
def record_event(
|
|
330
347
|
tx,
|
|
331
348
|
guard,
|
|
@@ -337,58 +354,116 @@ def record_event(
|
|
|
337
354
|
user_sub=None,
|
|
338
355
|
detail=None,
|
|
339
356
|
occurred_at=None,
|
|
357
|
+
actor=None,
|
|
340
358
|
):
|
|
341
|
-
"""Append one ``sys_security_events`` row
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
359
|
+
"""Append one ``sys_security_events`` row on its own short transaction.
|
|
360
|
+
The one audit sink for the framework: the request guards, masquerade
|
|
361
|
+
grants, sign-ins, privileged decisions (``authz.require``), and anything
|
|
362
|
+
else security-relevant write here. ``user_sub`` is the identity provider's
|
|
363
|
+
subject; ``detail`` a JSON-able dict of facts that have no column;
|
|
364
|
+
``occurred_at`` when it happened, if not now (an event relayed through a
|
|
365
|
+
queue). Silent when the table is not provisioned in this site, and an
|
|
366
|
+
audit failure never breaks the request. Returns True only once the row is
|
|
367
|
+
committed.
|
|
368
|
+
|
|
369
|
+
``tx`` is the caller's transaction and is only used for its engine: the
|
|
370
|
+
row is written and committed through ``tx.engine.side_transaction()``
|
|
371
|
+
(another connection, which never waits for the pool), so it survives whatever the caller does next (raise,
|
|
372
|
+
roll back) and the caller's transaction is left exactly as it was: its
|
|
373
|
+
writes stay uncommitted, and its ``SET LOCAL`` settings (the audit actor),
|
|
374
|
+
row locks and transaction-level advisory locks are kept. A ``tx`` with no
|
|
375
|
+
engine (a test double) gets the row written on itself, uncommitted. On
|
|
376
|
+
PostgreSQL the event transaction's audit actor (``velocity.actor``, for a
|
|
377
|
+
``sys_security_events`` under the audit trigger's ``require_actor``) is
|
|
378
|
+
``actor`` when given, else the one the caller set with
|
|
379
|
+
:func:`velocity.db.audit.set_audit_actor`. Callers that record before any
|
|
380
|
+
actor is set (the request guards, the masquerade grant) pass their own.
|
|
350
381
|
|
|
351
382
|
A table created before ``user_sub``/``detail`` existed (a site whose schema
|
|
352
383
|
lane has not run since) still records the row without them: on a locked
|
|
353
384
|
schema an unknown column would otherwise lose the whole event."""
|
|
385
|
+
facts = dict(
|
|
386
|
+
aws_event=aws_event,
|
|
387
|
+
context=context,
|
|
388
|
+
user_email=user_email,
|
|
389
|
+
user_sub=user_sub,
|
|
390
|
+
detail=detail,
|
|
391
|
+
occurred_at=occurred_at,
|
|
392
|
+
)
|
|
354
393
|
try:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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)
|
|
385
|
-
tx.commit()
|
|
386
|
-
return True
|
|
394
|
+
open_transaction = getattr(
|
|
395
|
+
getattr(tx, "engine", None), "side_transaction", None
|
|
396
|
+
)
|
|
397
|
+
if not callable(open_transaction):
|
|
398
|
+
return _write_event(tx, guard, reason, **facts)
|
|
399
|
+
postgres = "postgres" in str(_pgddl.server_name(tx) or "").lower()
|
|
400
|
+
if postgres and not actor:
|
|
401
|
+
actor = _caller_actor(tx)
|
|
402
|
+
# the row is committed when the block exits; a commit failure raises
|
|
403
|
+
# out of it, so True is never returned for a row that isn't durable
|
|
404
|
+
with open_transaction() as event_tx:
|
|
405
|
+
if postgres:
|
|
406
|
+
# one round trip: the bounded lock wait, and the caller's audit
|
|
407
|
+
# actor (a sys_security_events under the audit trigger's
|
|
408
|
+
# require_actor still records)
|
|
409
|
+
event_tx.execute(
|
|
410
|
+
"SELECT set_config('lock_timeout', %s, true), "
|
|
411
|
+
"set_config(%s, %s, true)",
|
|
412
|
+
(EVENT_LOCK_TIMEOUT, audit.ACTOR_SETTING, actor or ""),
|
|
413
|
+
)
|
|
414
|
+
return _write_event(event_tx, guard, reason, **facts)
|
|
387
415
|
except Exception:
|
|
388
416
|
log.warning("security event %s not recorded", guard, exc_info=True)
|
|
389
417
|
return False
|
|
390
418
|
|
|
391
419
|
|
|
420
|
+
def _caller_actor(tx):
|
|
421
|
+
"""The audit actor :func:`velocity.db.audit.set_audit_actor` set on the
|
|
422
|
+
caller's transaction, or None. Read from the transaction object only:
|
|
423
|
+
running a statement on the caller's connection here could hit a dead
|
|
424
|
+
connection, and swallowing that error would lose the caller's
|
|
425
|
+
mid-transaction failure (R31) — its next write would land on a fresh
|
|
426
|
+
connection."""
|
|
427
|
+
value = getattr(tx, "_audit_actor", None)
|
|
428
|
+
return value if isinstance(value, str) and value else None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _write_event(
|
|
432
|
+
tx, guard, reason, *, aws_event, context, user_email, user_sub, detail, occurred_at
|
|
433
|
+
):
|
|
434
|
+
table = tx.table(EVENTS_TABLE)
|
|
435
|
+
if not table.exists():
|
|
436
|
+
return False
|
|
437
|
+
facts = request_facts(aws_event)
|
|
438
|
+
row = {
|
|
439
|
+
"guard": guard,
|
|
440
|
+
"reason": reason,
|
|
441
|
+
"ip": facts["ip"],
|
|
442
|
+
"country": facts["country"],
|
|
443
|
+
"path": facts["path"],
|
|
444
|
+
"action": _action(context) if context is not None else "",
|
|
445
|
+
"user_email": user_email,
|
|
446
|
+
"user_agent": facts["user_agent"][:512],
|
|
447
|
+
}
|
|
448
|
+
extra = {
|
|
449
|
+
"user_sub": user_sub,
|
|
450
|
+
"detail": detail,
|
|
451
|
+
"occurred_at": occurred_at or datetime.datetime.now(datetime.timezone.utc),
|
|
452
|
+
}
|
|
453
|
+
extra = {k: v for k, v in extra.items() if v is not None}
|
|
454
|
+
if extra:
|
|
455
|
+
try:
|
|
456
|
+
have = {c.lower() for c in table.sys_columns()}
|
|
457
|
+
except Exception:
|
|
458
|
+
# can't tell which newer columns exist: the event still lands
|
|
459
|
+
# with the columns every version of the table has
|
|
460
|
+
log.warning("security event %s: columns unknown", guard, exc_info=True)
|
|
461
|
+
have = set()
|
|
462
|
+
row.update({k: v for k, v in extra.items() if k in have})
|
|
463
|
+
table.insert(row)
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
|
|
392
467
|
def _user_email(context):
|
|
393
468
|
try:
|
|
394
469
|
session = context.session() or {}
|
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
from velocity.aws import ssm_config
|
|
10
10
|
from velocity.aws.handlers.base_handler import BaseHandler
|
|
11
|
-
from velocity.aws.handlers.exceptions import UserNotProvisionedError
|
|
11
|
+
from velocity.aws.handlers.exceptions import AccessError, UserNotProvisionedError
|
|
12
12
|
from velocity.aws.handlers.context_factory import ContextFactory
|
|
13
13
|
from velocity.aws.handlers.response import Response
|
|
14
14
|
from velocity.logging import configure_logging
|
|
@@ -116,11 +116,12 @@ class LambdaHandler(BaseHandler):
|
|
|
116
116
|
)
|
|
117
117
|
# Persist the audit the docstring promises: who acted as whom,
|
|
118
118
|
# from where. Goes to the framework's one security audit sink
|
|
119
|
-
# (sys_security_events, the Security Events page)
|
|
120
|
-
#
|
|
119
|
+
# (sys_security_events, the Security Events page), on its own
|
|
120
|
+
# transaction: this request's tx stays open. Fail-closed, as
|
|
121
|
+
# authz.require is: no audit row, no masquerade.
|
|
121
122
|
from velocity.aws.capabilities.security import record_event
|
|
122
123
|
|
|
123
|
-
record_event(
|
|
124
|
+
recorded = record_event(
|
|
124
125
|
tx,
|
|
125
126
|
"masquerade",
|
|
126
127
|
f"{grant['real_user']} acting as {grant['effective_user']} "
|
|
@@ -128,7 +129,14 @@ class LambdaHandler(BaseHandler):
|
|
|
128
129
|
aws_event=self.aws_event,
|
|
129
130
|
context=context,
|
|
130
131
|
user_email=grant["real_user"],
|
|
132
|
+
# recorded before any request actor is set; the actor
|
|
133
|
+
# format AuditTrailMixin uses under masquerade
|
|
134
|
+
actor=f"{grant['effective_user']} as {grant['real_user']}",
|
|
131
135
|
)
|
|
136
|
+
if not recorded:
|
|
137
|
+
raise AccessError(
|
|
138
|
+
"That masquerade could not be recorded, so it was refused."
|
|
139
|
+
)
|
|
132
140
|
|
|
133
141
|
if masquerade or auth_mode == "none":
|
|
134
142
|
# In masquerade mode the caller's own Cognito token is for a
|
|
@@ -172,11 +172,20 @@ def set_audit_actor(tx, actor: Optional[str]) -> None:
|
|
|
172
172
|
explicit ``tx.commit()``. ``None`` / ``""`` clears it (the trigger then
|
|
173
173
|
records ``db:<session_user>``). A no-op off PostgreSQL, where there is no
|
|
174
174
|
trigger to read it.
|
|
175
|
+
|
|
176
|
+
The value is also kept on the transaction (``tx._audit_actor``, cleared at
|
|
177
|
+
its commit, rollback or connection release; not undone by a savepoint
|
|
178
|
+
rollback) so a side transaction can carry it over without querying this
|
|
179
|
+
connection (:func:`velocity.aws.capabilities.security.record_event`).
|
|
175
180
|
"""
|
|
176
181
|
if not _pgddl.is_postgres(tx):
|
|
177
182
|
return
|
|
178
183
|
value = "" if actor is None else str(actor)
|
|
179
184
|
tx.execute("SELECT set_config(%s, %s, true)", (ACTOR_SETTING, value))
|
|
185
|
+
try:
|
|
186
|
+
tx._audit_actor = value or None
|
|
187
|
+
except AttributeError: # a transaction double that refuses attributes
|
|
188
|
+
pass
|
|
180
189
|
|
|
181
190
|
|
|
182
191
|
# ── trigger DDL ───────────────────────────────────────────────────────────
|
|
@@ -199,16 +199,27 @@ class ConnectionPool:
|
|
|
199
199
|
# Public API
|
|
200
200
|
# ------------------------------------------------------------------
|
|
201
201
|
|
|
202
|
-
def getconn(self):
|
|
203
|
-
"""Borrow a connection from the pool (blocking if exhausted).
|
|
202
|
+
def getconn(self, *, wait=True):
|
|
203
|
+
"""Borrow a connection from the pool (blocking if exhausted).
|
|
204
|
+
|
|
205
|
+
``wait=False`` never waits: with no free slot it raises
|
|
206
|
+
``DbConnectionError`` at once."""
|
|
204
207
|
if self._closed:
|
|
205
208
|
raise exceptions.DbConnectionError("Connection pool is closed")
|
|
206
209
|
|
|
207
210
|
t0 = time.perf_counter()
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
if not wait:
|
|
212
|
+
if not self._available.acquire(blocking=False):
|
|
213
|
+
raise exceptions.DbConnectionError(
|
|
214
|
+
f"Connection pool exhausted (maxconn={self._maxconn}); "
|
|
215
|
+
f"not waiting for a slot."
|
|
216
|
+
)
|
|
217
|
+
acquired = True
|
|
218
|
+
else:
|
|
219
|
+
# R24 — bounded wait for a free slot. Without a timeout an exhausted
|
|
220
|
+
# pool blocks until the surrounding Lambda/request itself times out,
|
|
221
|
+
# which masks the real problem and wastes the whole invocation window.
|
|
222
|
+
acquired = self._available.acquire(timeout=self._acquire_timeout)
|
|
212
223
|
if not acquired:
|
|
213
224
|
raise exceptions.DbConnectionError(
|
|
214
225
|
f"Connection pool exhausted: no connection available within "
|
|
@@ -478,16 +489,19 @@ class Engine:
|
|
|
478
489
|
self.__pool = None
|
|
479
490
|
self.__pool_enabled = False
|
|
480
491
|
|
|
481
|
-
def connect(self):
|
|
492
|
+
def connect(self, *, wait=True):
|
|
482
493
|
"""
|
|
483
494
|
Connects to the database and returns the connection object.
|
|
484
495
|
If pooling is enabled, borrows from the pool.
|
|
485
496
|
If the database is missing, tries to create it, then reconnect.
|
|
497
|
+
|
|
498
|
+
``wait=False`` never waits for a pool slot: with none free right now
|
|
499
|
+
it opens a direct (unpooled) connection instead.
|
|
486
500
|
"""
|
|
487
501
|
t0 = time.perf_counter()
|
|
488
502
|
if self.__pool:
|
|
489
503
|
try:
|
|
490
|
-
conn = self.__pool.getconn()
|
|
504
|
+
conn = self.__pool.getconn(wait=wait)
|
|
491
505
|
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
492
506
|
logger.debug(
|
|
493
507
|
"Engine.connect: obtained pooled connection (%.1f ms)", elapsed_ms
|
|
@@ -495,7 +509,7 @@ class Engine:
|
|
|
495
509
|
return conn
|
|
496
510
|
except exceptions.DbDatabaseMissingError:
|
|
497
511
|
self.create_database()
|
|
498
|
-
return self.__pool.getconn()
|
|
512
|
+
return self.__pool.getconn(wait=wait)
|
|
499
513
|
except Exception:
|
|
500
514
|
# Pool may have failed to connect — fall through to direct.
|
|
501
515
|
pass
|
|
@@ -596,6 +610,18 @@ class Engine:
|
|
|
596
610
|
# Never let adapter registration break connection establishment.
|
|
597
611
|
pass
|
|
598
612
|
|
|
613
|
+
def side_transaction(self):
|
|
614
|
+
"""A new :class:`Transaction` for a short write taken while another
|
|
615
|
+
transaction of this thread is open (an audit row that must commit on
|
|
616
|
+
its own, whatever the request's transaction does next). Use it as
|
|
617
|
+
``with engine.side_transaction() as tx:``.
|
|
618
|
+
|
|
619
|
+
It never waits for the pool: it leases a free slot if one is free right
|
|
620
|
+
now, else opens a direct connection. Waiting could only end when the
|
|
621
|
+
caller's own transaction, which holds a slot, finishes — a pool of one
|
|
622
|
+
would stall for the acquire timeout, or forever with none."""
|
|
623
|
+
return Transaction(self, wait_for_pool=False)
|
|
624
|
+
|
|
599
625
|
def transaction(self, func_or_cls=None):
|
|
600
626
|
"""
|
|
601
627
|
Decorator that provides a Transaction. If `tx` is passed in, uses it; otherwise, creates a new one.
|
|
@@ -343,9 +343,17 @@ class Transaction:
|
|
|
343
343
|
Encapsulates a single transaction in the database (connection + commit/rollback).
|
|
344
344
|
"""
|
|
345
345
|
|
|
346
|
-
def __init__(self, engine, connection=None):
|
|
346
|
+
def __init__(self, engine, connection=None, *, wait_for_pool=True):
|
|
347
347
|
self.engine = engine
|
|
348
348
|
self.connection = connection
|
|
349
|
+
# False (Engine.side_transaction): lease a pool slot only if one is
|
|
350
|
+
# free now, else connect directly — never wait on the pool.
|
|
351
|
+
self._wait_for_pool = wait_for_pool
|
|
352
|
+
# The audit actor velocity.db.audit.set_audit_actor set on this
|
|
353
|
+
# transaction (velocity.actor is transaction-local, so it is cleared
|
|
354
|
+
# at commit, rollback and connection release). Read by code that must
|
|
355
|
+
# carry it to another transaction without querying this connection.
|
|
356
|
+
self._audit_actor = None
|
|
349
357
|
self.__pg_types = {}
|
|
350
358
|
# Track whether this connection came from the pool and should be returned.
|
|
351
359
|
self.__pooled = False # set per leased connection (TX-7)
|
|
@@ -445,7 +453,10 @@ class Transaction:
|
|
|
445
453
|
"or Row used after the block must be consumed inside it."
|
|
446
454
|
)
|
|
447
455
|
try:
|
|
448
|
-
|
|
456
|
+
if getattr(self, "_wait_for_pool", True):
|
|
457
|
+
conn = self.engine.connect()
|
|
458
|
+
else:
|
|
459
|
+
conn = self.engine.connect(wait=False)
|
|
449
460
|
except exceptions.DbException as e:
|
|
450
461
|
if getattr(e, "tx", None) is None:
|
|
451
462
|
e.tx = self
|
|
@@ -472,6 +483,7 @@ class Transaction:
|
|
|
472
483
|
self.connection = None
|
|
473
484
|
self.__pooled = False
|
|
474
485
|
self._in_progress = False
|
|
486
|
+
self._audit_actor = None
|
|
475
487
|
if conn is None:
|
|
476
488
|
return
|
|
477
489
|
returned = False
|
|
@@ -844,6 +856,7 @@ class Transaction:
|
|
|
844
856
|
raise self._db_error(e)
|
|
845
857
|
# Work is durably committed; the transaction is clean again.
|
|
846
858
|
self._in_progress = False
|
|
859
|
+
self._audit_actor = None
|
|
847
860
|
if self._query_count:
|
|
848
861
|
_logger.debug(
|
|
849
862
|
"Transaction commit: %d queries in %.1f ms",
|
|
@@ -934,6 +947,7 @@ class Transaction:
|
|
|
934
947
|
self.connection.rollback()
|
|
935
948
|
# Transaction is clean again — no uncommitted work pending.
|
|
936
949
|
self._in_progress = False
|
|
950
|
+
self._audit_actor = None
|
|
937
951
|
|
|
938
952
|
def is_aborted(self):
|
|
939
953
|
"""Return True if the connection is in a failed/aborted transaction.
|
|
@@ -385,6 +385,8 @@ tests/test_query_missing_table_unlocked.py
|
|
|
385
385
|
tests/test_raw_sql_prefix_policy.py
|
|
386
386
|
tests/test_read_cache.py
|
|
387
387
|
tests/test_readme_imports.py
|
|
388
|
+
tests/test_record_event_postgres.py
|
|
389
|
+
tests/test_record_event_transaction.py
|
|
388
390
|
tests/test_response.py
|
|
389
391
|
tests/test_restricted_direct_tables.py
|
|
390
392
|
tests/test_result_returning.py
|
|
@@ -358,3 +358,78 @@ class TestAcquireTimeout:
|
|
|
358
358
|
monkeypatch.setenv("VELOCITY_POOL_ACQUIRE_TIMEOUT", "7")
|
|
359
359
|
eng = _make_engine()
|
|
360
360
|
assert eng.pool._acquire_timeout == 7
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class TestNoWaitAcquire:
|
|
364
|
+
"""``wait=False`` (Engine.side_transaction): never wait on the pool."""
|
|
365
|
+
|
|
366
|
+
def test_exhausted_pool_refuses_at_once_without_waiting(self):
|
|
367
|
+
pool = ConnectionPool(
|
|
368
|
+
_make_connect_fn(), minconn=0, maxconn=1, validate=False, acquire_timeout=0
|
|
369
|
+
)
|
|
370
|
+
held = pool.getconn()
|
|
371
|
+
t0 = time.time()
|
|
372
|
+
with pytest.raises(exceptions.DbConnectionError):
|
|
373
|
+
pool.getconn(wait=False)
|
|
374
|
+
assert time.time() - t0 < 0.5
|
|
375
|
+
pool.putconn(held)
|
|
376
|
+
assert pool.getconn(wait=False) is held # a free slot is still used
|
|
377
|
+
|
|
378
|
+
def test_engine_connects_directly_when_no_slot_is_free(self):
|
|
379
|
+
eng = _make_engine(pool_enabled=True, pool_min=0, pool_max=1)
|
|
380
|
+
held = eng.connect()
|
|
381
|
+
direct = eng.connect(wait=False)
|
|
382
|
+
assert direct is not held and not eng.is_pooled_connection(direct)
|
|
383
|
+
eng.return_pooled_connection(held)
|
|
384
|
+
|
|
385
|
+
def test_side_transaction_does_not_wait_and_closes_its_direct_connection(self):
|
|
386
|
+
eng = _make_engine(pool_enabled=True, pool_min=0, pool_max=1)
|
|
387
|
+
with Transaction(eng) as outer:
|
|
388
|
+
outer.cursor()
|
|
389
|
+
t0 = time.time()
|
|
390
|
+
with eng.side_transaction() as side:
|
|
391
|
+
side.cursor()
|
|
392
|
+
conn = side.connection
|
|
393
|
+
assert not eng.is_pooled_connection(conn)
|
|
394
|
+
assert time.time() - t0 < 0.5
|
|
395
|
+
conn.close.assert_called()
|
|
396
|
+
assert eng.is_pooled_connection(outer.connection)
|
|
397
|
+
|
|
398
|
+
def test_concurrent_no_wait_acquires_keep_the_slot_count_balanced(self):
|
|
399
|
+
pool = ConnectionPool(
|
|
400
|
+
_make_connect_fn(), minconn=0, maxconn=2, validate=False, acquire_timeout=0
|
|
401
|
+
)
|
|
402
|
+
lock = threading.Lock()
|
|
403
|
+
state = {"held": 0, "peak": 0, "refused": 0, "leased": 0}
|
|
404
|
+
|
|
405
|
+
def worker():
|
|
406
|
+
for _ in range(200):
|
|
407
|
+
try:
|
|
408
|
+
conn = pool.getconn(wait=False)
|
|
409
|
+
except exceptions.DbConnectionError:
|
|
410
|
+
with lock:
|
|
411
|
+
state["refused"] += 1
|
|
412
|
+
continue
|
|
413
|
+
with lock:
|
|
414
|
+
state["held"] += 1
|
|
415
|
+
state["leased"] += 1
|
|
416
|
+
state["peak"] = max(state["peak"], state["held"])
|
|
417
|
+
time.sleep(0)
|
|
418
|
+
with lock:
|
|
419
|
+
state["held"] -= 1
|
|
420
|
+
pool.putconn(conn)
|
|
421
|
+
|
|
422
|
+
threads = [threading.Thread(target=worker) for _ in range(8)]
|
|
423
|
+
for t in threads:
|
|
424
|
+
t.start()
|
|
425
|
+
for t in threads:
|
|
426
|
+
t.join(timeout=10)
|
|
427
|
+
assert not any(t.is_alive() for t in threads)
|
|
428
|
+
assert state["peak"] <= 2 and state["leased"] > 0
|
|
429
|
+
assert state["leased"] + state["refused"] == 8 * 200
|
|
430
|
+
# balanced: exactly maxconn slots are free again, no more
|
|
431
|
+
a, b = pool.getconn(wait=False), pool.getconn(wait=False)
|
|
432
|
+
with pytest.raises(exceptions.DbConnectionError):
|
|
433
|
+
pool.getconn(wait=False)
|
|
434
|
+
pool.putconn(a)
|
|
435
|
+
pool.putconn(b)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
from unittest.mock import patch
|
|
2
3
|
|
|
4
|
+
from velocity.aws.capabilities import security
|
|
5
|
+
from velocity.aws.handlers.exceptions import AccessError
|
|
3
6
|
from velocity.aws.handlers.lambda_handler import LambdaHandler
|
|
4
7
|
from velocity.aws.handlers.masquerade import mint_grant
|
|
5
8
|
|
|
@@ -188,7 +191,91 @@ class TestLambdaHandlerMasquerade(unittest.TestCase):
|
|
|
188
191
|
self.assertEqual(event["user_email"], ADMIN)
|
|
189
192
|
self.assertIn("acting as donor@example.com on donor_users", event["reason"])
|
|
190
193
|
self.assertEqual(event["action"], "do-thing")
|
|
191
|
-
|
|
194
|
+
# the audit never commits the request's transaction (a real engine
|
|
195
|
+
# writes it on its own; see test_record_event_transaction)
|
|
196
|
+
self.assertEqual(tx.commits, 0)
|
|
197
|
+
|
|
198
|
+
def test_a_masquerade_that_cannot_be_recorded_is_refused(self):
|
|
199
|
+
# fail-closed like authz.require: no audit row, no masquerade (the
|
|
200
|
+
# table not provisioned, no connection, a write failure)
|
|
201
|
+
token = mint_grant(
|
|
202
|
+
SECRET,
|
|
203
|
+
real_user=ADMIN,
|
|
204
|
+
effective_user=TARGET,
|
|
205
|
+
pool="donor_users",
|
|
206
|
+
)
|
|
207
|
+
ctx = _Context(grant_token=token)
|
|
208
|
+
tx = _Tx(
|
|
209
|
+
{"donor@example.com": {"email_address": "donor@example.com", "sys_id": 7}}
|
|
210
|
+
)
|
|
211
|
+
h = _make_handler()
|
|
212
|
+
with patch.object(security, "record_event", return_value=False) as rec:
|
|
213
|
+
with self.assertRaisesRegex(AccessError, "could not be recorded"):
|
|
214
|
+
h.beforeAction(tx=tx, context=ctx)
|
|
215
|
+
rec.assert_called_once()
|
|
216
|
+
self.assertEqual(h.current_user, {})
|
|
217
|
+
self.assertEqual(tx.events, [])
|
|
218
|
+
|
|
219
|
+
def test_the_masquerade_event_names_its_own_audit_actor(self):
|
|
220
|
+
# recorded before AuditTrailMixin sets the request's actor: under an
|
|
221
|
+
# events table with require_actor it would otherwise be refused
|
|
222
|
+
token = mint_grant(
|
|
223
|
+
SECRET, real_user=ADMIN, effective_user=TARGET, pool="donor_users"
|
|
224
|
+
)
|
|
225
|
+
ctx = _Context(grant_token=token)
|
|
226
|
+
tx = _Tx(
|
|
227
|
+
{"donor@example.com": {"email_address": "donor@example.com", "sys_id": 7}}
|
|
228
|
+
)
|
|
229
|
+
with patch.object(security, "record_event", return_value=True) as rec:
|
|
230
|
+
_make_handler().beforeAction(tx=tx, context=ctx)
|
|
231
|
+
self.assertEqual(rec.call_args.kwargs["actor"], f"{TARGET} as {ADMIN}")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class TestRefusedMasqueradeThroughServe(unittest.TestCase):
|
|
235
|
+
"""serve(): the refusal is a 403 with the reason, after a rollback."""
|
|
236
|
+
|
|
237
|
+
def test_a_masquerade_that_cannot_be_recorded_is_a_403(self):
|
|
238
|
+
import json
|
|
239
|
+
from unittest.mock import MagicMock
|
|
240
|
+
|
|
241
|
+
token = mint_grant(
|
|
242
|
+
SECRET, real_user=ADMIN, effective_user=TARGET, pool="donor_users"
|
|
243
|
+
)
|
|
244
|
+
aws_event = {
|
|
245
|
+
"headers": {"X-CC-Masquerade": token},
|
|
246
|
+
"queryStringParameters": {},
|
|
247
|
+
"body": json.dumps({"action": "do-thing", "payload": {}}),
|
|
248
|
+
}
|
|
249
|
+
h = _Handler(
|
|
250
|
+
aws_event=aws_event,
|
|
251
|
+
aws_context=type("C", (), {"aws_request_id": "rid"})(),
|
|
252
|
+
)
|
|
253
|
+
h.auth_mode = "required"
|
|
254
|
+
h.user_table = "donor_users"
|
|
255
|
+
ran = []
|
|
256
|
+
h.OnActionDoThing = lambda tx, context: ran.append(True)
|
|
257
|
+
tx = MagicMock()
|
|
258
|
+
with (
|
|
259
|
+
patch(
|
|
260
|
+
"velocity.aws.handlers.context.config_getenv",
|
|
261
|
+
side_effect=lambda name, default=None: (
|
|
262
|
+
SECRET if name == "MasqueradeSigningKey" else default
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
patch.object(security, "record_event", return_value=False),
|
|
266
|
+
):
|
|
267
|
+
rendered = h.serve(tx)
|
|
268
|
+
self.assertEqual(rendered["statusCode"], 403)
|
|
269
|
+
body = json.loads(rendered["body"])
|
|
270
|
+
self.assertEqual(
|
|
271
|
+
body,
|
|
272
|
+
{
|
|
273
|
+
"ok": False,
|
|
274
|
+
"message": "That masquerade could not be recorded, so it was refused.",
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
tx.rollback.assert_called_once()
|
|
278
|
+
self.assertEqual(ran, [])
|
|
192
279
|
|
|
193
280
|
|
|
194
281
|
if __name__ == "__main__":
|