velocity-python 0.2.5__tar.gz → 0.2.7__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.5/src/velocity_python.egg-info → velocity_python-0.2.7}/PKG-INFO +1 -1
- {velocity_python-0.2.5 → velocity_python-0.2.7}/pyproject.toml +1 -1
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/__init__.py +1 -1
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/failures.py +198 -46
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/context.py +20 -4
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/async_support.py +3 -13
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/transaction.py +74 -18
- {velocity_python-0.2.5 → velocity_python-0.2.7/src/velocity_python.egg-info}/PKG-INFO +1 -1
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_async_support.py +4 -2
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_errors_mixin.py +5 -1
- velocity_python-0.2.7/tests/test_cognito_client_lazy.py +45 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_failures.py +117 -6
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_observability.py +37 -6
- {velocity_python-0.2.5 → velocity_python-0.2.7}/LICENSE +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/README.md +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/setup.cfg +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/authz.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/security.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/ci_gate.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/frontend_checks.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/facets.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/identity.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/permissions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/queue_defaults.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/schema.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tiles.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/user_data.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/fragments.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tunnel.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/logging.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/_mirror_core.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/money.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/plaid_api.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/plaid_webhook.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/redact.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/schema.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/testing/__init__.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/testing/fake_db.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_action_names.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_amplify_redirects.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_asset_references.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_assets_service.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_audit_mixin.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_authz.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_aws_package_hygiene.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_blob_store.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_business_objects.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_capabilities.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_check_changelog.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ci_gate.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_input_contract_export.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_sql_boundary.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_table_gate_case_and_pointers.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_queue_job.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_conv_combine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_owner_scope.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_row_number.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_security_gates.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_tunnel.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_declared_column_types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_decorators.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_config_fields.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_engine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_frontend_checks.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_operations_queue.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_dlq_constant.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_docs_snippets.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_email_processing.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_enqueue_metric_namespace.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ensure_infrastructure.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_export_workbook.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_facets.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_facets_and_where.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_identity.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_results.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_json_columns.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_merge_on_conflict.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mirror_upsert_public.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_on_action_default.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_on_commit.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ops.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_audit_fixes.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_business_agnostic.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_charge_reference.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_extra_bounds.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_lazy_imports.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pdf.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_actions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_notices.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_review.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_bulk_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_capability_types.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_ensure_infrastructure.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_get_tables.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_permissions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_pointer_joins.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_row_scope.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_search_export.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_tiles.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_user_data.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_plaid_api.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_plaid_webhook.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pointer_join_sql.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_postgres_initializer_config.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_postgres_py_type.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_query_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_raw_sql_prefix_policy.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_read_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_readme_imports.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_response.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_result_returning.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_scoped_data_service.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_bucket_config.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_list_client_files.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_upload.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_schema_lock_config_other_backends.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_search_paging_export.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_security_capability.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_session_control.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sms_twilio_signature.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ssm_config_require.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_alter.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_claim.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_purge.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_testing_fake_db.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tiles.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tx_lifecycle_retry.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tx_read_cache_and_bulk.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_user_data.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_user_not_provisioned.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_write_hook_create_flow.py +0 -0
- {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_write_hook_empty_strings.py +0 -0
|
@@ -61,6 +61,13 @@ logger = logging.getLogger(__name__)
|
|
|
61
61
|
EVENT_TYPE = "velocity.failure"
|
|
62
62
|
EVENT_VERSION = 1
|
|
63
63
|
TOPIC_CONFIG_KEY = "FailureTopicArn"
|
|
64
|
+
# Where a person reads failures (a failure inbox screen); the email links it.
|
|
65
|
+
INBOX_CONFIG_KEY = "FailureInboxUrl"
|
|
66
|
+
# The site's IANA zone; the email shows when it happened there.
|
|
67
|
+
TIMEZONE_CONFIG_KEY = "SiteTimezone"
|
|
68
|
+
# SNS caps an email subject at 100 characters of plain ASCII.
|
|
69
|
+
EMAIL_SUBJECT_LIMIT = 100
|
|
70
|
+
SUMMARY_LIMIT = 300
|
|
64
71
|
SOURCES = frozenset({"queue", "scheduler", "web", "webhook", "job"})
|
|
65
72
|
|
|
66
73
|
MESSAGE_LIMIT = 1000
|
|
@@ -541,10 +548,17 @@ def build_event(
|
|
|
541
548
|
"log_group": f"/aws/lambda/{function}" if function else None,
|
|
542
549
|
"log_stream": os.environ.get("AWS_LAMBDA_LOG_STREAM_NAME") or None,
|
|
543
550
|
"classification": None,
|
|
551
|
+
# for people (render_text / the alert email): where it failed, a
|
|
552
|
+
# one-line summary and what to do, from the exception's `summary` /
|
|
553
|
+
# `hint` attributes or `extra`
|
|
554
|
+
"where": frame or None,
|
|
555
|
+
"summary": _person_text(exc, extra, "summary", keys),
|
|
556
|
+
"hint": _person_text(exc, extra, "hint", keys),
|
|
544
557
|
}
|
|
545
558
|
if extra:
|
|
546
559
|
event["extra"] = payload_excerpt(
|
|
547
|
-
{k: v for k, v in extra.items() if k
|
|
560
|
+
{k: v for k, v in extra.items() if k not in ("message", "summary", "hint")},
|
|
561
|
+
keys,
|
|
548
562
|
)
|
|
549
563
|
event["classification"] = _run_classify(
|
|
550
564
|
classify if classify is not None else _classify, exc, event
|
|
@@ -552,55 +566,162 @@ def build_event(
|
|
|
552
566
|
return event
|
|
553
567
|
|
|
554
568
|
|
|
569
|
+
def _person_text(exc, extra, name, keys):
|
|
570
|
+
"""An exception's ``summary``/``hint`` attribute, else ``extra[name]``;
|
|
571
|
+
scrubbed and trimmed, or None."""
|
|
572
|
+
value = getattr(exc, name, None) if exc is not None else None
|
|
573
|
+
if not value and extra:
|
|
574
|
+
value = extra.get(name)
|
|
575
|
+
if not value:
|
|
576
|
+
return None
|
|
577
|
+
return _truncate(scrub_text(" ".join(str(value).split()), keys), SUMMARY_LIMIT)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _local_time(occurred_at: Optional[str]) -> str:
|
|
581
|
+
"""``Oct 20, 2026, 11:18 AM MDT`` in the site's zone (``SiteTimezone``),
|
|
582
|
+
else UTC; the raw value when it is not a timestamp."""
|
|
583
|
+
if not occurred_at:
|
|
584
|
+
return ""
|
|
585
|
+
try:
|
|
586
|
+
when = datetime.datetime.fromisoformat(str(occurred_at).replace("Z", "+00:00"))
|
|
587
|
+
except ValueError:
|
|
588
|
+
return str(occurred_at)
|
|
589
|
+
zone_name = None
|
|
590
|
+
try:
|
|
591
|
+
zone_name = _config(TIMEZONE_CONFIG_KEY)
|
|
592
|
+
except Exception:
|
|
593
|
+
zone_name = None
|
|
594
|
+
if zone_name:
|
|
595
|
+
try:
|
|
596
|
+
from zoneinfo import ZoneInfo
|
|
597
|
+
|
|
598
|
+
when = when.astimezone(ZoneInfo(str(zone_name)))
|
|
599
|
+
except Exception:
|
|
600
|
+
when = when.astimezone(datetime.timezone.utc)
|
|
601
|
+
hour = when.strftime("%I").lstrip("0") or "12"
|
|
602
|
+
return f"{when.strftime('%b')} {when.day}, {when.year}, {hour}:{when.strftime('%M %p %Z')}"
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _cloudwatch_escape(value: str) -> str:
|
|
606
|
+
from urllib.parse import quote
|
|
607
|
+
|
|
608
|
+
return quote(quote(value, safe=""), safe="").replace("%", "$")
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def log_link(event: Optional[Dict[str, Any]]) -> str:
|
|
612
|
+
"""The CloudWatch console page of the event's log stream, or ""."""
|
|
613
|
+
if not event or not event.get("log_group"):
|
|
614
|
+
return ""
|
|
615
|
+
region = os.environ.get("AWS_REGION") # set in every Lambda
|
|
616
|
+
if not region:
|
|
617
|
+
return ""
|
|
618
|
+
url = (
|
|
619
|
+
f"https://{region}.console.aws.amazon.com/cloudwatch/home?region={region}"
|
|
620
|
+
f"#logsV2:log-groups/log-group/{_cloudwatch_escape(event['log_group'])}"
|
|
621
|
+
)
|
|
622
|
+
if event.get("log_stream"):
|
|
623
|
+
url += f"/log-events/{_cloudwatch_escape(event['log_stream'])}"
|
|
624
|
+
return url
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _handled(event: Dict[str, Any]) -> Optional[bool]:
|
|
628
|
+
extra = event.get("extra")
|
|
629
|
+
if isinstance(extra, dict) and "handled" in extra:
|
|
630
|
+
return bool(extra["handled"])
|
|
631
|
+
return None
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
_SOURCE_WORDS = {
|
|
635
|
+
"job": "a background job",
|
|
636
|
+
"queue": "a queued message",
|
|
637
|
+
"scheduler": "a scheduled task",
|
|
638
|
+
"request": "a request",
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
555
642
|
def render_text(event: Optional[Dict[str, Any]]) -> str:
|
|
556
|
-
"""
|
|
557
|
-
|
|
643
|
+
"""The failure for a person (the alert email, fallbacks): what failed,
|
|
644
|
+
the error in words, what to do, when (site-local time), whether it was
|
|
645
|
+
handled, where to look. Nothing beyond the event, the configured inbox
|
|
646
|
+
link and a log link."""
|
|
558
647
|
if not event:
|
|
559
648
|
return ""
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
"
|
|
649
|
+
site = event.get("site") or "?"
|
|
650
|
+
env = event.get("env") or "?"
|
|
651
|
+
exc_name = (event.get("exception_type") or "").rsplit(".", 1)[-1]
|
|
652
|
+
source = _SOURCE_WORDS.get(event.get("source"), event.get("source") or "something")
|
|
653
|
+
lines = [f"{site} ({env}): {source} failed", ""]
|
|
654
|
+
|
|
655
|
+
what = event.get("action") or event.get("source") or "?"
|
|
656
|
+
extra = event.get("extra") if isinstance(event.get("extra"), dict) else {}
|
|
657
|
+
details = [str(extra[k]) for k in ("capability",) if extra.get(k)]
|
|
658
|
+
subject = extra.get("subject")
|
|
659
|
+
lines.append(
|
|
660
|
+
f"What: {what}"
|
|
661
|
+
+ (f" ({', '.join(details)})" if details else "")
|
|
662
|
+
+ (f", for {subject}" if subject else "")
|
|
663
|
+
)
|
|
664
|
+
error = event.get("summary") or event.get("message") or exc_name or "?"
|
|
665
|
+
lines.append(f"Error: {error}" + (f" ({exc_name})" if exc_name else ""))
|
|
666
|
+
if event.get("hint"):
|
|
667
|
+
lines.append(f"To do: {event['hint']}")
|
|
668
|
+
if event.get("occurred_at"):
|
|
669
|
+
lines.append(f"When: {_local_time(event['occurred_at'])}")
|
|
670
|
+
handled = _handled(event)
|
|
671
|
+
if handled is True:
|
|
672
|
+
lines.append(
|
|
673
|
+
"Handled: yes; it was recorded and the work is tried again on its next run"
|
|
674
|
+
)
|
|
675
|
+
elif handled is False:
|
|
676
|
+
lines.append("Handled: no; the function stopped on it")
|
|
677
|
+
if event.get("classification"):
|
|
678
|
+
lines.append(f"Class: {event['classification']}")
|
|
679
|
+
if event.get("receive_count"):
|
|
680
|
+
lines.append(f"Attempt: {event['receive_count']}")
|
|
681
|
+
lines.append("")
|
|
682
|
+
if event.get("function"):
|
|
683
|
+
lines.append(f"Function: {event['function']}")
|
|
684
|
+
link = log_link(event)
|
|
685
|
+
if link:
|
|
686
|
+
lines.append(f"Log: {link}")
|
|
687
|
+
inbox = None
|
|
688
|
+
try:
|
|
689
|
+
inbox = _config(INBOX_CONFIG_KEY)
|
|
690
|
+
except Exception:
|
|
691
|
+
inbox = None
|
|
692
|
+
if inbox:
|
|
693
|
+
lines.append(f"Inbox: {inbox}")
|
|
694
|
+
lines.append("")
|
|
695
|
+
if event.get("where"):
|
|
696
|
+
lines.append(f"Where it failed: {event['where']}")
|
|
697
|
+
if (
|
|
698
|
+
event.get("summary")
|
|
699
|
+
and event.get("message")
|
|
700
|
+
and event["message"] != event["summary"]
|
|
579
701
|
):
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
lines.append(f"{key}: {_render(event[key])}")
|
|
586
|
-
if event.get("traceback"):
|
|
587
|
-
lines.extend(["", "Traceback (scrubbed):", event["traceback"]])
|
|
702
|
+
lines.append(f"Raw message: {event['message']}")
|
|
703
|
+
if event.get("fingerprint"):
|
|
704
|
+
lines.append(f"Reference: {str(event['fingerprint'])[:12]}")
|
|
705
|
+
while lines and not lines[-1]:
|
|
706
|
+
lines.pop()
|
|
588
707
|
return "\n".join(lines)
|
|
589
708
|
|
|
590
709
|
|
|
591
710
|
def subject_line(event: Optional[Dict[str, Any]]) -> str:
|
|
592
|
-
"""
|
|
711
|
+
"""One line for an alert subject: site, env, what failed and the error in
|
|
712
|
+
words. Plain ASCII, at most 100 characters (SNS's email subject rules)."""
|
|
593
713
|
if not event:
|
|
594
714
|
return "Failure"
|
|
595
715
|
exc_name = (event.get("exception_type") or "Failure").rsplit(".", 1)[-1]
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
event.get(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
716
|
+
what = event.get("action") or event.get("source") or "something"
|
|
717
|
+
error = event.get("summary") or event.get("message") or exc_name
|
|
718
|
+
text = (
|
|
719
|
+
f"{event.get('site') or '?'} {event.get('env') or '?'}: {what} failed: {error}"
|
|
720
|
+
)
|
|
721
|
+
text = " ".join(text.split()).encode("ascii", "ignore").decode("ascii")
|
|
722
|
+
if len(text) > EMAIL_SUBJECT_LIMIT:
|
|
723
|
+
text = text[: EMAIL_SUBJECT_LIMIT - 3].rstrip() + "..."
|
|
724
|
+
return text
|
|
604
725
|
|
|
605
726
|
|
|
606
727
|
def _run_fallback(event: Dict[str, Any]) -> None:
|
|
@@ -701,13 +822,7 @@ def report(
|
|
|
701
822
|
return None
|
|
702
823
|
|
|
703
824
|
try:
|
|
704
|
-
_client().publish(
|
|
705
|
-
TopicArn=topic_arn,
|
|
706
|
-
Message=_dumps(event),
|
|
707
|
-
MessageAttributes={
|
|
708
|
-
"velocity_event": {"DataType": "String", "StringValue": "failure"}
|
|
709
|
-
},
|
|
710
|
-
)
|
|
825
|
+
_client().publish(**publish_request(topic_arn, event))
|
|
711
826
|
except Exception:
|
|
712
827
|
logger.exception(
|
|
713
828
|
"failures: publish to %s failed (fingerprint %s)",
|
|
@@ -718,6 +833,43 @@ def report(
|
|
|
718
833
|
return event
|
|
719
834
|
|
|
720
835
|
|
|
836
|
+
# SNS refuses a message over 256 KB; the email text is dropped to a short
|
|
837
|
+
# form well before that (the inbox keeps the whole event).
|
|
838
|
+
_SNS_MESSAGE_BUDGET = 240 * 1024
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def publish_request(topic_arn: str, event: Dict[str, Any]) -> Dict[str, Any]:
|
|
842
|
+
"""The ``sns.publish`` arguments for one event. Subscribers each get their
|
|
843
|
+
own form (``MessageStructure="json"``): a Lambda (the failure inbox) and
|
|
844
|
+
every other protocol the event's JSON, unchanged; an ``email``
|
|
845
|
+
subscription the text a person reads (:func:`render_text`) under a
|
|
846
|
+
readable subject. The ``handled`` attribute ("true"/"false"/"unknown")
|
|
847
|
+
lets a subscription filter: the person's backstop email takes only
|
|
848
|
+
failures that were not handled."""
|
|
849
|
+
machine = _dumps(event)
|
|
850
|
+
person = render_text(event)
|
|
851
|
+
message = json.dumps({"default": machine, "email": person})
|
|
852
|
+
if len(message.encode("utf-8")) > _SNS_MESSAGE_BUDGET:
|
|
853
|
+
person = person[:4000] + "\n\n(shortened; the failure inbox has the rest)"
|
|
854
|
+
message = json.dumps({"default": machine, "email": person})
|
|
855
|
+
handled = _handled(event)
|
|
856
|
+
return {
|
|
857
|
+
"TopicArn": topic_arn,
|
|
858
|
+
"Message": message,
|
|
859
|
+
"MessageStructure": "json",
|
|
860
|
+
"Subject": subject_line(event),
|
|
861
|
+
"MessageAttributes": {
|
|
862
|
+
"velocity_event": {"DataType": "String", "StringValue": "failure"},
|
|
863
|
+
"handled": {
|
|
864
|
+
"DataType": "String",
|
|
865
|
+
"StringValue": (
|
|
866
|
+
"unknown" if handled is None else ("true" if handled else "false")
|
|
867
|
+
),
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
|
|
721
873
|
def is_enabled() -> bool:
|
|
722
874
|
"""Whether ``FailureTopicArn`` resolves (events will be published)."""
|
|
723
875
|
return bool(_config(TOPIC_CONFIG_KEY))
|
|
@@ -27,7 +27,22 @@ from velocity.db.core.lazy_engine import LazyEngine
|
|
|
27
27
|
engine = LazyEngine(velocity.db.postgres.initialize)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# The Cognito client is built on first use, not at import: creating it loads
|
|
31
|
+
# Cognito's whole API model and resolves its endpoint (~13 MB and ~2 s on a
|
|
32
|
+
# 128 MB Lambda), which every function importing a handler paid — a
|
|
33
|
+
# Scheduler or queue worker that never signs anyone in included. Tests may
|
|
34
|
+
# replace this attribute; whatever it holds is what `_cognito()` returns.
|
|
35
|
+
cognito_client = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _cognito():
|
|
39
|
+
"""The shared Cognito client, created on first use."""
|
|
40
|
+
global cognito_client
|
|
41
|
+
if cognito_client is None:
|
|
42
|
+
cognito_client = boto3.client("cognito-idp")
|
|
43
|
+
return cognito_client
|
|
44
|
+
|
|
45
|
+
|
|
31
46
|
logger = get_logger("velocity.aws.handlers.context")
|
|
32
47
|
|
|
33
48
|
# Cognito User Pool id shape, e.g. "us-east-1_aBcD1234". Used to distinguish a
|
|
@@ -1036,18 +1051,19 @@ class Context:
|
|
|
1036
1051
|
# one Cognito round trip per user per UserCacheSeconds per container
|
|
1037
1052
|
# (velocity.cache); a lookup that fails is never cached
|
|
1038
1053
|
def resolve():
|
|
1054
|
+
client = _cognito()
|
|
1039
1055
|
try:
|
|
1040
1056
|
self.perf.start("cognito admin_get_user")
|
|
1041
|
-
response =
|
|
1057
|
+
response = client.admin_get_user(
|
|
1042
1058
|
UserPoolId=user_pool_id,
|
|
1043
1059
|
Username=user_sub,
|
|
1044
1060
|
)
|
|
1045
1061
|
self.perf.log("cognito admin_get_user")
|
|
1046
1062
|
resolved_username = response.get("Username")
|
|
1047
|
-
except
|
|
1063
|
+
except client.exceptions.UserNotFoundException:
|
|
1048
1064
|
try:
|
|
1049
1065
|
self.perf.start("cognito list_users")
|
|
1050
|
-
list_response =
|
|
1066
|
+
list_response = client.list_users(
|
|
1051
1067
|
UserPoolId=user_pool_id,
|
|
1052
1068
|
Filter=f'sub = "{user_sub}"',
|
|
1053
1069
|
Limit=1,
|
|
@@ -528,19 +528,9 @@ class AsyncTransaction:
|
|
|
528
528
|
|
|
529
529
|
# R12 — Slow query logging
|
|
530
530
|
if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
|
|
531
|
-
from velocity.db.core.transaction import
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
_logger.warning(
|
|
535
|
-
"Slow async query: %.1f ms sql=%s",
|
|
536
|
-
elapsed_ms,
|
|
537
|
-
sql_preview,
|
|
538
|
-
extra={
|
|
539
|
-
"query_duration_ms": round(elapsed_ms, 1),
|
|
540
|
-
"sql_preview": sql_preview,
|
|
541
|
-
},
|
|
542
|
-
stack_info=True,
|
|
543
|
-
)
|
|
531
|
+
from velocity.db.core.transaction import _slow_query_log
|
|
532
|
+
|
|
533
|
+
_slow_query_log(_logger, sql, elapsed_ms)
|
|
544
534
|
|
|
545
535
|
# R14 — N+1 detection / R33 — raw-write cache invalidation
|
|
546
536
|
from velocity.db.core.transaction import _classify_sql, _extract_table_name
|
|
@@ -30,6 +30,15 @@ _DEFAULT_QUERY_CACHE_SIZE = int(os.environ.get("VELOCITY_QUERY_CACHE_SIZE", "100
|
|
|
30
30
|
# Slow-query threshold in milliseconds (0 = disabled).
|
|
31
31
|
_SLOW_QUERY_MS = int(os.environ.get("VELOCITY_SLOW_QUERY_MS", "500"))
|
|
32
32
|
_SLOW_QUERY_SQL_CHARS = int(os.environ.get("VELOCITY_SLOW_QUERY_SQL_CHARS", "4000"))
|
|
33
|
+
# The slow-query line is for people: this much SQL, the caller's file:line,
|
|
34
|
+
# no traceback unless VELOCITY_SLOW_QUERY_STACK=1 asks for one. The full
|
|
35
|
+
# preview (VELOCITY_SLOW_QUERY_SQL_CHARS) still rides on the record's `extra`.
|
|
36
|
+
_SLOW_QUERY_LINE_CHARS = 200
|
|
37
|
+
_SLOW_QUERY_STACK = os.environ.get("VELOCITY_SLOW_QUERY_STACK", "").strip().lower() in (
|
|
38
|
+
"1",
|
|
39
|
+
"true",
|
|
40
|
+
"yes",
|
|
41
|
+
)
|
|
33
42
|
|
|
34
43
|
# N+1 detection: warn when the same table is SELECTed more than this many
|
|
35
44
|
# times within a single transaction. 0 = disabled. Only active when the
|
|
@@ -174,16 +183,79 @@ def _written_tables_from_sql(sql):
|
|
|
174
183
|
return tags or None
|
|
175
184
|
|
|
176
185
|
|
|
186
|
+
# a run of three or more bind placeholders: "IN (%s, %s, %s, …)"
|
|
187
|
+
_PLACEHOLDER_RUN_RE = re.compile(r"(%s|\?)(?:\s*,\s*\1){2,}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _collapse_placeholders(text):
|
|
191
|
+
"""``%s, %s, … (812 of them)`` -> ``%s × 812``."""
|
|
192
|
+
|
|
193
|
+
def collapse(match):
|
|
194
|
+
token = match.group(1)
|
|
195
|
+
return f"{token} × {match.group(0).count(token)}"
|
|
196
|
+
|
|
197
|
+
return _PLACEHOLDER_RUN_RE.sub(collapse, text)
|
|
198
|
+
|
|
199
|
+
|
|
177
200
|
def _summarize_sql(sql, limit=None):
|
|
178
201
|
if not sql:
|
|
179
202
|
return None
|
|
180
|
-
compact = " ".join(str(sql).split())
|
|
203
|
+
compact = _collapse_placeholders(" ".join(str(sql).split()))
|
|
181
204
|
max_chars = _SLOW_QUERY_SQL_CHARS if limit is None else limit
|
|
182
205
|
if max_chars and len(compact) > max_chars:
|
|
183
206
|
return compact[:max_chars] + "... [truncated]"
|
|
184
207
|
return compact
|
|
185
208
|
|
|
186
209
|
|
|
210
|
+
_VELOCITY_DIR = os.path.dirname(
|
|
211
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _caller_location():
|
|
216
|
+
"""``file.py:line`` of the first stack frame outside velocity (the code
|
|
217
|
+
that ran the query), or ``None``."""
|
|
218
|
+
import traceback
|
|
219
|
+
|
|
220
|
+
for frame in reversed(traceback.extract_stack()[:-1]):
|
|
221
|
+
filename = os.path.abspath(frame.filename)
|
|
222
|
+
if filename.startswith(_VELOCITY_DIR + os.sep) or "<frozen" in frame.filename:
|
|
223
|
+
continue
|
|
224
|
+
return f"{os.path.basename(frame.filename)}:{frame.lineno}"
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _slow_query_log(logger, sql, elapsed_ms, *, op=None, table=None):
|
|
229
|
+
"""One readable line for a slow query: what, on which table, how long,
|
|
230
|
+
from where, and the start of the SQL (placeholders collapsed). The fields
|
|
231
|
+
for log tooling go in ``extra``; a traceback only with
|
|
232
|
+
VELOCITY_SLOW_QUERY_STACK=1."""
|
|
233
|
+
op = op or _classify_sql(sql)
|
|
234
|
+
table = table if table is not None else _extract_table_name(sql)
|
|
235
|
+
preview = _summarize_sql(sql)
|
|
236
|
+
line = _summarize_sql(sql, limit=_SLOW_QUERY_LINE_CHARS)
|
|
237
|
+
caller = _caller_location()
|
|
238
|
+
took = (
|
|
239
|
+
f"{elapsed_ms / 1000:.1f} s" if elapsed_ms >= 1000 else f"{elapsed_ms:.0f} ms"
|
|
240
|
+
)
|
|
241
|
+
logger.warning(
|
|
242
|
+
"Slow %s%s: %s%s — %s",
|
|
243
|
+
op or "query",
|
|
244
|
+
f" on {table}" if table else "",
|
|
245
|
+
took,
|
|
246
|
+
f" at {caller}" if caller else "",
|
|
247
|
+
line,
|
|
248
|
+
extra={
|
|
249
|
+
"query_duration_ms": round(elapsed_ms, 1),
|
|
250
|
+
"table_name": table,
|
|
251
|
+
"operation": op,
|
|
252
|
+
"sql_preview": preview,
|
|
253
|
+
"caller": caller,
|
|
254
|
+
},
|
|
255
|
+
stack_info=_SLOW_QUERY_STACK,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
187
259
|
def _is_prepare_kw_type_error(error):
|
|
188
260
|
"""True for the TypeError a DB-API driver raises when ``execute()`` has
|
|
189
261
|
no ``prepare`` keyword (``unexpected keyword argument 'prepare'``)."""
|
|
@@ -650,23 +722,7 @@ class Transaction:
|
|
|
650
722
|
|
|
651
723
|
# R12 — Slow query logging.
|
|
652
724
|
if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
|
|
653
|
-
|
|
654
|
-
tbl = _extract_table_name(sql)
|
|
655
|
-
sql_preview = _summarize_sql(sql)
|
|
656
|
-
_logger.warning(
|
|
657
|
-
"Slow query (%s): %.1f ms table=%s sql=%s",
|
|
658
|
-
op,
|
|
659
|
-
elapsed_ms,
|
|
660
|
-
tbl,
|
|
661
|
-
sql_preview,
|
|
662
|
-
extra={
|
|
663
|
-
"query_duration_ms": round(elapsed_ms, 1),
|
|
664
|
-
"table_name": tbl,
|
|
665
|
-
"operation": op,
|
|
666
|
-
"sql_preview": sql_preview,
|
|
667
|
-
},
|
|
668
|
-
stack_info=True,
|
|
669
|
-
)
|
|
725
|
+
_slow_query_log(_logger, sql, elapsed_ms)
|
|
670
726
|
|
|
671
727
|
# R14 — N+1 detection (only when debug=True).
|
|
672
728
|
if debug and _N_PLUS_1_THRESHOLD:
|
|
@@ -254,6 +254,7 @@ tests/test_client_errors_mixin.py
|
|
|
254
254
|
tests/test_client_input_contract_export.py
|
|
255
255
|
tests/test_client_sql_boundary.py
|
|
256
256
|
tests/test_client_table_gate_case_and_pointers.py
|
|
257
|
+
tests/test_cognito_client_lazy.py
|
|
257
258
|
tests/test_cognito_user_cache.py
|
|
258
259
|
tests/test_column_tx_arg.py
|
|
259
260
|
tests/test_concurrency_safety.py
|
|
@@ -728,6 +728,8 @@ class TestAsyncSlowQueryLogging:
|
|
|
728
728
|
assert len(slow_calls) >= 1
|
|
729
729
|
args = slow_calls[0].args
|
|
730
730
|
kwargs = slow_calls[0].kwargs
|
|
731
|
-
|
|
731
|
+
# the same one-line form as the sync path: op, table, SQL last
|
|
732
|
+
assert args[1:3] == ("SELECT", " on large_table")
|
|
733
|
+
assert args[-1] == "SELECT * FROM large_table"
|
|
732
734
|
assert kwargs["extra"]["sql_preview"] == "SELECT * FROM large_table"
|
|
733
|
-
assert kwargs["stack_info"] is
|
|
735
|
+
assert kwargs["stack_info"] is False
|
|
@@ -696,7 +696,11 @@ def test_real_failures_report_builds_a_publishable_event():
|
|
|
696
696
|
):
|
|
697
697
|
run(Handler(), {"errors": [error(message="token=abc123secret failed")]})
|
|
698
698
|
client.publish.assert_called_once()
|
|
699
|
-
|
|
699
|
+
# one body per subscriber: the inbox reads "default" (the event), a
|
|
700
|
+
# person's email reads "email"; neither may carry the secret
|
|
701
|
+
bodies = json.loads(client.publish.call_args.kwargs["Message"])
|
|
702
|
+
assert "abc123secret" not in bodies["email"]
|
|
703
|
+
event = json.loads(bodies["default"])
|
|
700
704
|
assert event["source"] == client_errors.FAILURE_SOURCE
|
|
701
705
|
assert event["action"] == "report-client-error"
|
|
702
706
|
assert event["exception_type"].endswith("client_errors.ClientError")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""The Cognito client is created on first use, not when velocity's handler
|
|
2
|
+
context is imported: building it loads Cognito's API model (~13 MB, ~2 s on a
|
|
3
|
+
128 MB Lambda) that a Scheduler or queue worker never needs."""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_importing_the_context_creates_no_cognito_client():
|
|
11
|
+
name = "velocity.aws.handlers.context"
|
|
12
|
+
original = sys.modules.pop(name, None)
|
|
13
|
+
try:
|
|
14
|
+
with mock.patch("boto3.client") as client:
|
|
15
|
+
module = importlib.import_module(name)
|
|
16
|
+
cognito_calls = [
|
|
17
|
+
c for c in client.call_args_list if c.args[:1] == ("cognito-idp",)
|
|
18
|
+
]
|
|
19
|
+
assert cognito_calls == []
|
|
20
|
+
assert module.cognito_client is None
|
|
21
|
+
finally:
|
|
22
|
+
# every other test keeps the module object it imported
|
|
23
|
+
if original is not None:
|
|
24
|
+
sys.modules[name] = original
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_the_client_is_created_once_on_first_use():
|
|
28
|
+
from velocity.aws.handlers import context
|
|
29
|
+
|
|
30
|
+
with (
|
|
31
|
+
mock.patch.object(context, "cognito_client", None),
|
|
32
|
+
mock.patch("boto3.client") as client,
|
|
33
|
+
):
|
|
34
|
+
first = context._cognito()
|
|
35
|
+
second = context._cognito()
|
|
36
|
+
client.assert_called_once_with("cognito-idp")
|
|
37
|
+
assert first is second
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_a_client_put_in_place_is_the_one_used():
|
|
41
|
+
from velocity.aws.handlers import context
|
|
42
|
+
|
|
43
|
+
fake = object()
|
|
44
|
+
with mock.patch.object(context, "cognito_client", fake):
|
|
45
|
+
assert context._cognito() is fake
|