velocity-python 0.1.108__tar.gz → 0.1.110__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {velocity_python-0.1.108/src/velocity_python.egg-info → velocity_python-0.1.110}/PKG-INFO +2 -1
- {velocity_python-0.1.108 → velocity_python-0.1.110}/README.md +1 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/pyproject.toml +1 -1
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/security.py +11 -13
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/feature_flags.py +85 -55
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/context.py +91 -48
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/lambda_handler.py +26 -5
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/data_service.py +10 -2
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/session_control.py +4 -3
- velocity_python-0.1.110/src/velocity/aws/mirror.py +267 -0
- velocity_python-0.1.110/src/velocity/cache.py +182 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/table.py +131 -19
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/transaction.py +7 -1
- {velocity_python-0.1.108 → velocity_python-0.1.110/src/velocity_python.egg-info}/PKG-INFO +2 -1
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/SOURCES.txt +7 -0
- velocity_python-0.1.110/tests/test_cognito_user_cache.py +81 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_feature_flags.py +85 -0
- velocity_python-0.1.110/tests/test_lambda_handler_user_cache.py +87 -0
- velocity_python-0.1.110/tests/test_mirror.py +232 -0
- velocity_python-0.1.110/tests/test_read_cache.py +119 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_single_session_capability.py +4 -0
- velocity_python-0.1.110/tests/test_table_find_cache.py +172 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/LICENSE +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/setup.cfg +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_blob_store.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_capabilities.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_results.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_observability.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_response.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_security_capability.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_table_purge.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.110
|
|
4
4
|
Summary: A rapid application development library for interfacing with data storage
|
|
5
5
|
Author-email: Velocity Team <info@codeclubs.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -251,6 +251,7 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
251
251
|
| Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
|
|
252
252
|
| Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
|
|
253
253
|
| AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions, async jobs (JobHandler + job result stores) |
|
|
254
|
+
| Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
|
|
254
255
|
| NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
|
|
255
256
|
| Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
|
|
256
257
|
| Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
|
|
@@ -157,6 +157,7 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
157
157
|
| Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
|
|
158
158
|
| Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
|
|
159
159
|
| AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions, async jobs (JobHandler + job result stores) |
|
|
160
|
+
| Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
|
|
160
161
|
| NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
|
|
161
162
|
| Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
|
|
162
163
|
| Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
|
{velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/security.py
RENAMED
|
@@ -27,9 +27,9 @@ from __future__ import annotations
|
|
|
27
27
|
import datetime
|
|
28
28
|
import ipaddress
|
|
29
29
|
import logging
|
|
30
|
-
import time
|
|
31
30
|
|
|
32
31
|
from velocity.aws import feature_flags
|
|
32
|
+
from velocity.cache import memo, read_cache
|
|
33
33
|
|
|
34
34
|
from .base import (
|
|
35
35
|
CIDR_LIST,
|
|
@@ -62,11 +62,10 @@ KNOBS = {
|
|
|
62
62
|
"security_events_days": "90",
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
_cache: dict = {}
|
|
66
|
-
|
|
67
|
-
|
|
68
65
|
def clear_cache():
|
|
69
|
-
|
|
66
|
+
"""Forget the cached site row AND the shared flags row (tests)."""
|
|
67
|
+
read_cache.invalidate(SITE_TABLE)
|
|
68
|
+
feature_flags.clear_cache()
|
|
70
69
|
|
|
71
70
|
|
|
72
71
|
# ── request facts ───────────────────────────────────────────────────────
|
|
@@ -172,13 +171,9 @@ def check_rate(tx, bucket, *, limit, now, table=RATE_TABLE):
|
|
|
172
171
|
|
|
173
172
|
|
|
174
173
|
def _cached(key, loader, *, now=None):
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return hit[1]
|
|
179
|
-
value = loader()
|
|
180
|
-
_cache[key] = (now + CACHE_TTL_SECONDS, value)
|
|
181
|
-
return value
|
|
174
|
+
"""The process read cache (velocity.cache), tagged with the site table
|
|
175
|
+
so provisioning's own writes to it in this process drop the entry."""
|
|
176
|
+
return memo(("security",) + tuple(key), loader, CACHE_TTL_SECONDS, tables=(SITE_TABLE,), now=now)
|
|
182
177
|
|
|
183
178
|
|
|
184
179
|
def _site_row(tx):
|
|
@@ -201,7 +196,10 @@ def _site_row(tx):
|
|
|
201
196
|
|
|
202
197
|
|
|
203
198
|
def _flags(tx):
|
|
204
|
-
|
|
199
|
+
"""The stage's flags row from velocity's container cache — shared with
|
|
200
|
+
the handler context and session control, so the guards add no read of
|
|
201
|
+
their own to a request."""
|
|
202
|
+
return feature_flags.get_cached(tx)
|
|
205
203
|
|
|
206
204
|
|
|
207
205
|
def _row_config(row) -> dict:
|
|
@@ -23,19 +23,30 @@ carries one row per stage and each environment reads its own; the alternate
|
|
|
23
23
|
database name comes from config (:data:`ALTERNATE_DB_CONFIG_KEY`). (An app that
|
|
24
24
|
one day consolidates several systems into one database can widen the key then;
|
|
25
25
|
today the stage is the whole identity.)
|
|
26
|
+
|
|
27
|
+
The mirroring itself is the generic :mod:`velocity.aws.mirror` (any table,
|
|
28
|
+
any natural key, optional foreign-key translation); this module is that
|
|
29
|
+
mechanism applied to the flags table, keyed by ``applied_system``.
|
|
26
30
|
"""
|
|
27
31
|
|
|
28
32
|
from __future__ import annotations
|
|
29
33
|
|
|
30
34
|
import logging
|
|
31
35
|
|
|
36
|
+
from velocity.aws import mirror as _mirror
|
|
37
|
+
from velocity.cache import memo, read_cache
|
|
38
|
+
|
|
32
39
|
log = logging.getLogger(__name__)
|
|
33
40
|
|
|
34
41
|
SYSTEM_COLUMN = "applied_system"
|
|
35
42
|
DEFAULT_TABLE = "sys_feature_flags"
|
|
36
|
-
ALTERNATE_DB_CONFIG_KEY =
|
|
43
|
+
ALTERNATE_DB_CONFIG_KEY = _mirror.ALTERNATE_DB_CONFIG_KEY
|
|
44
|
+
# how long a warm container may reuse the flags row before re-reading it
|
|
45
|
+
CACHE_SECONDS_CONFIG_KEY = "FeatureFlagCacheSeconds"
|
|
46
|
+
DEFAULT_CACHE_SECONDS = 60
|
|
37
47
|
|
|
38
|
-
_alternate_engines
|
|
48
|
+
_alternate_engines = _mirror._alternate_engines
|
|
49
|
+
_cache_seconds: float | None = None
|
|
39
50
|
|
|
40
51
|
|
|
41
52
|
def current_system(default: str | None = None) -> str | None:
|
|
@@ -68,20 +79,74 @@ def get(
|
|
|
68
79
|
return dict(row)
|
|
69
80
|
|
|
70
81
|
|
|
82
|
+
def cache_seconds() -> float:
|
|
83
|
+
"""Seconds a warm container may reuse the flags row: the
|
|
84
|
+
``FeatureFlagCacheSeconds`` config key, default 60; ``0`` reads the row
|
|
85
|
+
on every call. Resolved once per container."""
|
|
86
|
+
global _cache_seconds
|
|
87
|
+
if _cache_seconds is None:
|
|
88
|
+
raw = None
|
|
89
|
+
try:
|
|
90
|
+
from velocity.aws import ssm_config
|
|
91
|
+
|
|
92
|
+
raw = ssm_config.getenv(CACHE_SECONDS_CONFIG_KEY, None)
|
|
93
|
+
except Exception:
|
|
94
|
+
raw = None
|
|
95
|
+
try:
|
|
96
|
+
_cache_seconds = (
|
|
97
|
+
float(raw) if raw not in (None, "") else float(DEFAULT_CACHE_SECONDS)
|
|
98
|
+
)
|
|
99
|
+
except (TypeError, ValueError):
|
|
100
|
+
_cache_seconds = float(DEFAULT_CACHE_SECONDS)
|
|
101
|
+
return _cache_seconds
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def clear_cache(table: str = DEFAULT_TABLE) -> None:
|
|
105
|
+
"""Forget cached rows (after a local flags write; tests)."""
|
|
106
|
+
global _cache_seconds
|
|
107
|
+
read_cache.invalidate(table)
|
|
108
|
+
_cache_seconds = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_cached(
|
|
112
|
+
tx, system: str | None = None, *, table: str = DEFAULT_TABLE, now: float | None = None
|
|
113
|
+
) -> dict:
|
|
114
|
+
""":func:`get` through the process read cache (:mod:`velocity.cache`)
|
|
115
|
+
— the hot-path reader.
|
|
116
|
+
|
|
117
|
+
The flags row is ONE row per stage, and every reader in a request
|
|
118
|
+
(the handler context, the security guards, session control, any
|
|
119
|
+
capability knob) wants the same one: this makes it at most one read per
|
|
120
|
+
:func:`cache_seconds` per container, however many readers there are.
|
|
121
|
+
Tagged with the table, so a flags write through the table API in this
|
|
122
|
+
process (the mirror hook, a save) drops it at once; the other
|
|
123
|
+
environment sees the change within the TTL.
|
|
124
|
+
"""
|
|
125
|
+
system = system if system is not None else current_system()
|
|
126
|
+
row = memo(
|
|
127
|
+
("feature_flags", table, system),
|
|
128
|
+
lambda: dict(get(tx, system, table=table)),
|
|
129
|
+
cache_seconds(),
|
|
130
|
+
tables=(table,),
|
|
131
|
+
now=now,
|
|
132
|
+
)
|
|
133
|
+
return dict(row)
|
|
134
|
+
|
|
135
|
+
|
|
71
136
|
def load(tx, loader=None) -> dict:
|
|
72
137
|
"""The request-time flag source used by handler contexts.
|
|
73
138
|
|
|
74
139
|
An injected ``loader(tx)`` wins (apps that resolve flags their own way);
|
|
75
|
-
otherwise :func:`
|
|
76
|
-
never raising into the request. The
|
|
77
|
-
``support.app.helpers`` loader remains only as a last
|
|
78
|
-
velocity reader finds no row (it raises on a missing
|
|
79
|
-
apps rely on); absent that package the answer is ``{}``.
|
|
140
|
+
otherwise :func:`get_cached` — velocity's stage-scoped reader behind the
|
|
141
|
+
container cache — is the source, never raising into the request. The
|
|
142
|
+
legacy CaringCent ``support.app.helpers`` loader remains only as a last
|
|
143
|
+
resort when the velocity reader finds no row (it raises on a missing
|
|
144
|
+
row, which those apps rely on); absent that package the answer is ``{}``.
|
|
80
145
|
"""
|
|
81
146
|
if callable(loader):
|
|
82
147
|
return loader(tx)
|
|
83
148
|
try:
|
|
84
|
-
flags =
|
|
149
|
+
flags = get_cached(tx)
|
|
85
150
|
except Exception:
|
|
86
151
|
flags = {}
|
|
87
152
|
if flags:
|
|
@@ -101,28 +166,13 @@ def _legacy_support_loader(tx):
|
|
|
101
166
|
return helpers.get_feature_flags(tx)
|
|
102
167
|
|
|
103
168
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return {k: v for k, v in dict(row).items() if not str(k).startswith("sys_")}
|
|
169
|
+
_without_sys = _mirror.business_columns
|
|
170
|
+
_toast = _mirror.toast
|
|
107
171
|
|
|
108
172
|
|
|
109
173
|
def alternate_engine(alternate_db: str | None = None):
|
|
110
|
-
"""Engine for the alternate database —
|
|
111
|
-
|
|
112
|
-
Host/port/user/password are inherited from the running config, so no
|
|
113
|
-
separate credentials are needed; only the database name changes. Cached per
|
|
114
|
-
database name so repeated mirrors in one process reuse the connection.
|
|
115
|
-
"""
|
|
116
|
-
from velocity.aws import ssm_config
|
|
117
|
-
from velocity.db.servers import postgres
|
|
118
|
-
|
|
119
|
-
if alternate_db is None:
|
|
120
|
-
alternate_db = ssm_config.getenv(ALTERNATE_DB_CONFIG_KEY, raise_on_missing=True)
|
|
121
|
-
if alternate_db not in _alternate_engines:
|
|
122
|
-
_alternate_engines[alternate_db] = postgres.initialize(
|
|
123
|
-
{"database": alternate_db}
|
|
124
|
-
)
|
|
125
|
-
return _alternate_engines[alternate_db]
|
|
174
|
+
"""Engine for the alternate database — see :func:`velocity.aws.mirror.alternate_engine`."""
|
|
175
|
+
return _mirror.alternate_engine(alternate_db)
|
|
126
176
|
|
|
127
177
|
|
|
128
178
|
def mirror_row(
|
|
@@ -133,14 +183,9 @@ def mirror_row(
|
|
|
133
183
|
Returns the mirrored key, or ``None`` when the row has no system key (a
|
|
134
184
|
keyless row is skipped, never mirrored to an arbitrary target).
|
|
135
185
|
"""
|
|
136
|
-
data = _without_sys(row)
|
|
137
|
-
key = data.get(SYSTEM_COLUMN)
|
|
138
|
-
if not key:
|
|
139
|
-
return None
|
|
140
186
|
engine = engine or alternate_engine(alternate_db)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return key
|
|
187
|
+
pk = _mirror.mirror_row(row, table=table, key=(SYSTEM_COLUMN,), engine=engine)
|
|
188
|
+
return pk[SYSTEM_COLUMN] if pk else None
|
|
144
189
|
|
|
145
190
|
|
|
146
191
|
def reconcile(
|
|
@@ -149,27 +194,11 @@ def reconcile(
|
|
|
149
194
|
"""Mirror every flags row into the alternate database. Catch-up companion of
|
|
150
195
|
:func:`mirror_row`. Returns ``{"synced": [...], "skipped": [...]}``."""
|
|
151
196
|
engine = engine or alternate_engine(alternate_db)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
data = _without_sys(row)
|
|
158
|
-
key = data.get(SYSTEM_COLUMN)
|
|
159
|
-
if not key:
|
|
160
|
-
skipped.append(dict(row).get("sys_id"))
|
|
161
|
-
continue
|
|
162
|
-
alt.table(table).merge(data, pk={SYSTEM_COLUMN: key})
|
|
163
|
-
synced.append(key)
|
|
164
|
-
return {"synced": synced, "skipped": skipped}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def _toast(context, message: str, level: str) -> None:
|
|
168
|
-
"""Best-effort operator toast; a context without one is fine (a queue run)."""
|
|
169
|
-
response = getattr(context, "response", None)
|
|
170
|
-
toast = getattr(response() if callable(response) else None, "toast", None)
|
|
171
|
-
if callable(toast):
|
|
172
|
-
toast(message, level)
|
|
197
|
+
result = _mirror.reconcile(tx, table=table, key=(SYSTEM_COLUMN,), engine=engine)
|
|
198
|
+
return {
|
|
199
|
+
"synced": [pk[SYSTEM_COLUMN] for pk in result["synced"]],
|
|
200
|
+
"skipped": result["skipped"],
|
|
201
|
+
}
|
|
173
202
|
|
|
174
203
|
|
|
175
204
|
def make_mirror_hook(
|
|
@@ -187,6 +216,7 @@ def make_mirror_hook(
|
|
|
187
216
|
|
|
188
217
|
def hook(tx, table_name, sys_id, row, context):
|
|
189
218
|
del tx, table_name, sys_id
|
|
219
|
+
clear_cache() # the row just changed here: this container re-reads it
|
|
190
220
|
try:
|
|
191
221
|
key = mirror_row(row, alternate_db=alternate_db, table=table)
|
|
192
222
|
if notify and key:
|
|
@@ -17,6 +17,7 @@ from velocity.aws.ssm_config import get_project_name as config_get_project_name
|
|
|
17
17
|
from velocity.aws.ssm_config import get_region as config_get_region
|
|
18
18
|
from velocity.aws.ssm_config import get_stage as config_get_stage
|
|
19
19
|
from velocity.logging import get_logger
|
|
20
|
+
from velocity.cache import memo
|
|
20
21
|
|
|
21
22
|
engine = velocity.db.postgres.initialize()
|
|
22
23
|
|
|
@@ -81,6 +82,38 @@ def _get_controls_vars() -> dict:
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
|
|
85
|
+
# ── the signed-in user, cached per container ─────────────────────────────
|
|
86
|
+
USER_CACHE_CONFIG_KEY = "UserCacheSeconds"
|
|
87
|
+
DEFAULT_USER_CACHE_SECONDS = 60
|
|
88
|
+
_user_cache_seconds = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def user_cache_seconds() -> float:
|
|
92
|
+
"""Seconds a warm container may reuse a signed-in user's Cognito record and
|
|
93
|
+
user-table row: the ``UserCacheSeconds`` config key, default 60. A disabled
|
|
94
|
+
or deleted user keeps that container's access until it expires — accepted
|
|
95
|
+
policy (Paul, 2026-09-13). ``0`` looks the user up on every request."""
|
|
96
|
+
global _user_cache_seconds
|
|
97
|
+
if _user_cache_seconds is None:
|
|
98
|
+
raw = None
|
|
99
|
+
try:
|
|
100
|
+
raw = config_getenv(USER_CACHE_CONFIG_KEY, None)
|
|
101
|
+
except Exception:
|
|
102
|
+
raw = None
|
|
103
|
+
try:
|
|
104
|
+
_user_cache_seconds = (
|
|
105
|
+
float(raw) if raw not in (None, "") else float(DEFAULT_USER_CACHE_SECONDS)
|
|
106
|
+
)
|
|
107
|
+
except (TypeError, ValueError):
|
|
108
|
+
_user_cache_seconds = float(DEFAULT_USER_CACHE_SECONDS)
|
|
109
|
+
return _user_cache_seconds
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def clear_user_cache_config() -> None:
|
|
113
|
+
global _user_cache_seconds
|
|
114
|
+
_user_cache_seconds = None
|
|
115
|
+
|
|
116
|
+
|
|
84
117
|
@engine.transaction
|
|
85
118
|
class Context:
|
|
86
119
|
|
|
@@ -807,63 +840,73 @@ class Context:
|
|
|
807
840
|
"cannot resolve a Cognito user"
|
|
808
841
|
) from None
|
|
809
842
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
UserPoolId=user_pool_id,
|
|
814
|
-
Username=user_sub,
|
|
815
|
-
)
|
|
816
|
-
self.perf.log("cognito admin_get_user")
|
|
817
|
-
resolved_username = response.get("Username")
|
|
818
|
-
except cognito_client.exceptions.UserNotFoundException:
|
|
843
|
+
# one Cognito round trip per user per UserCacheSeconds per container
|
|
844
|
+
# (velocity.cache); a lookup that fails is never cached
|
|
845
|
+
def resolve():
|
|
819
846
|
try:
|
|
820
|
-
self.perf.start("cognito
|
|
821
|
-
|
|
847
|
+
self.perf.start("cognito admin_get_user")
|
|
848
|
+
response = cognito_client.admin_get_user(
|
|
822
849
|
UserPoolId=user_pool_id,
|
|
823
|
-
|
|
824
|
-
Limit=1,
|
|
850
|
+
Username=user_sub,
|
|
825
851
|
)
|
|
826
|
-
self.perf.log("cognito
|
|
852
|
+
self.perf.log("cognito admin_get_user")
|
|
853
|
+
resolved_username = response.get("Username")
|
|
854
|
+
except cognito_client.exceptions.UserNotFoundException:
|
|
855
|
+
try:
|
|
856
|
+
self.perf.start("cognito list_users")
|
|
857
|
+
list_response = cognito_client.list_users(
|
|
858
|
+
UserPoolId=user_pool_id,
|
|
859
|
+
Filter=f'sub = "{user_sub}"',
|
|
860
|
+
Limit=1,
|
|
861
|
+
)
|
|
862
|
+
self.perf.log("cognito list_users")
|
|
863
|
+
except ClientError as exc:
|
|
864
|
+
message = exc.response.get("Error", {}).get("Message", "Unknown error")
|
|
865
|
+
raise AlertError(
|
|
866
|
+
f"Failed to search Cognito user by sub: {message}"
|
|
867
|
+
) from None
|
|
868
|
+
|
|
869
|
+
users = list_response.get("Users", [])
|
|
870
|
+
if not users:
|
|
871
|
+
raise AlertError("Cognito user not found") from None
|
|
872
|
+
|
|
873
|
+
user = users[0]
|
|
874
|
+
response = {
|
|
875
|
+
"Username": user.get("Username"),
|
|
876
|
+
"UserAttributes": user.get("Attributes", []),
|
|
877
|
+
"Enabled": user.get("Enabled"),
|
|
878
|
+
"UserStatus": user.get("UserStatus"),
|
|
879
|
+
"UserCreateDate": user.get("UserCreateDate"),
|
|
880
|
+
"UserLastModifiedDate": user.get("UserLastModifiedDate"),
|
|
881
|
+
}
|
|
882
|
+
resolved_username = response.get("Username")
|
|
827
883
|
except ClientError as exc:
|
|
828
884
|
message = exc.response.get("Error", {}).get("Message", "Unknown error")
|
|
829
|
-
raise AlertError(
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if not users:
|
|
835
|
-
raise AlertError("Cognito user not found") from None
|
|
836
|
-
|
|
837
|
-
user = users[0]
|
|
838
|
-
response = {
|
|
839
|
-
"Username": user.get("Username"),
|
|
840
|
-
"UserAttributes": user.get("Attributes", []),
|
|
841
|
-
"Enabled": user.get("Enabled"),
|
|
842
|
-
"UserStatus": user.get("UserStatus"),
|
|
843
|
-
"UserCreateDate": user.get("UserCreateDate"),
|
|
844
|
-
"UserLastModifiedDate": user.get("UserLastModifiedDate"),
|
|
885
|
+
raise AlertError(f"Failed to retrieve Cognito user: {message}") from None
|
|
886
|
+
|
|
887
|
+
attributes = {
|
|
888
|
+
attr["Name"]: attr.get("Value")
|
|
889
|
+
for attr in response.get("UserAttributes", [])
|
|
845
890
|
}
|
|
846
|
-
resolved_username = response.get("Username")
|
|
847
|
-
except ClientError as exc:
|
|
848
|
-
message = exc.response.get("Error", {}).get("Message", "Unknown error")
|
|
849
|
-
raise AlertError(f"Failed to retrieve Cognito user: {message}") from None
|
|
850
|
-
|
|
851
|
-
attributes = {
|
|
852
|
-
attr["Name"]: attr.get("Value")
|
|
853
|
-
for attr in response.get("UserAttributes", [])
|
|
854
|
-
}
|
|
855
891
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
892
|
+
user = {
|
|
893
|
+
"username": resolved_username or user_sub,
|
|
894
|
+
"email": attributes.get("email"),
|
|
895
|
+
"attributes": attributes,
|
|
896
|
+
"enabled": response.get("Enabled"),
|
|
897
|
+
"user_status": response.get("UserStatus"),
|
|
898
|
+
"sub": attributes.get("sub", user_sub),
|
|
899
|
+
}
|
|
864
900
|
|
|
865
|
-
|
|
901
|
+
return user
|
|
866
902
|
|
|
903
|
+
user = memo(
|
|
904
|
+
("cognito", user_pool_id, user_sub),
|
|
905
|
+
resolve,
|
|
906
|
+
user_cache_seconds(),
|
|
907
|
+
cache_none=False,
|
|
908
|
+
)
|
|
909
|
+
self.perf.log("get_cognito_user total")
|
|
867
910
|
return user
|
|
868
911
|
|
|
869
912
|
def get_cognito_user_optional(self, aws_event):
|
{velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
@@ -161,15 +161,13 @@ class LambdaHandler(BaseHandler):
|
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
context.perf.start("user lookup")
|
|
164
|
-
|
|
165
|
-
{"email_address": session.get("email_address")}
|
|
166
|
-
)
|
|
164
|
+
user = self._cached_user(tx, session.get("email_address"))
|
|
167
165
|
context.perf.log("user lookup")
|
|
168
|
-
if not
|
|
166
|
+
if not user:
|
|
169
167
|
raise Exception(
|
|
170
168
|
"A valid user with permission is required to access this function [DB]"
|
|
171
169
|
)
|
|
172
|
-
self.current_user =
|
|
170
|
+
self.current_user = user
|
|
173
171
|
|
|
174
172
|
temp = copy.deepcopy(context.postdata() or {})
|
|
175
173
|
payload = temp.get("payload")
|
|
@@ -181,6 +179,29 @@ class LambdaHandler(BaseHandler):
|
|
|
181
179
|
extra={"payload": pprint.pformat(temp)},
|
|
182
180
|
)
|
|
183
181
|
|
|
182
|
+
def _cached_user(self, tx, email_address):
|
|
183
|
+
"""The user-table row for the signed-in user as a dict, through the
|
|
184
|
+
process read cache (velocity.cache) for ``UserCacheSeconds``: one
|
|
185
|
+
query per TTL per container instead of one per request. A miss is
|
|
186
|
+
never cached, so a user created a moment ago signs in at once; a
|
|
187
|
+
write to the user table in this process drops the entry."""
|
|
188
|
+
from velocity.cache import memo
|
|
189
|
+
|
|
190
|
+
from velocity.aws.handlers.context import user_cache_seconds
|
|
191
|
+
|
|
192
|
+
def load():
|
|
193
|
+
row = tx.table(self.user_table).find({"email_address": email_address})
|
|
194
|
+
return row.to_dict() if row else None
|
|
195
|
+
|
|
196
|
+
user = memo(
|
|
197
|
+
("user", self.user_table, email_address),
|
|
198
|
+
load,
|
|
199
|
+
user_cache_seconds(),
|
|
200
|
+
tables=(self.user_table,),
|
|
201
|
+
cache_none=False,
|
|
202
|
+
)
|
|
203
|
+
return dict(user) if user else None
|
|
204
|
+
|
|
184
205
|
def afterAction(self, tx, context):
|
|
185
206
|
self._enhanced_after_action(tx, context)
|
|
186
207
|
logger.debug("starting LamdaHandler.afterAction")
|
{velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -20,6 +20,10 @@ from velocity.aws.handlers.exceptions import AlertError
|
|
|
20
20
|
from velocity.db.exceptions import DbDataIntegrityError
|
|
21
21
|
from velocity.misc import export
|
|
22
22
|
|
|
23
|
+
# a table's column list changes only on deploy (schema sync) or a lazy ALTER
|
|
24
|
+
# in this process (which drops the entry): reuse it across requests
|
|
25
|
+
SCHEMA_CACHE_SECONDS = 300
|
|
26
|
+
|
|
23
27
|
logger = logging.getLogger(__name__)
|
|
24
28
|
|
|
25
29
|
# Postgres FK-violation text, e.g.:
|
|
@@ -522,7 +526,9 @@ class DataServiceMixin:
|
|
|
522
526
|
order_dir = parts[1].lower()
|
|
523
527
|
|
|
524
528
|
if not order_col:
|
|
525
|
-
available = [
|
|
529
|
+
available = [
|
|
530
|
+
c for c in tx.table(obj).sys_columns(cache_ttl=SCHEMA_CACHE_SECONDS)
|
|
531
|
+
]
|
|
526
532
|
lowered = {c.lower(): c for c in available}
|
|
527
533
|
order_col = lowered.get("sys_id") or lowered.get("sys_created")
|
|
528
534
|
if not order_col and available:
|
|
@@ -551,7 +557,9 @@ class DataServiceMixin:
|
|
|
551
557
|
_inject_row_number_expression(params)
|
|
552
558
|
requested_cols = _extract_requested_column_names(params.get("columns"))
|
|
553
559
|
if requested_cols:
|
|
554
|
-
available_cols = {
|
|
560
|
+
available_cols = {
|
|
561
|
+
c.lower() for c in tx.table(obj).sys_columns(cache_ttl=SCHEMA_CACHE_SECONDS)
|
|
562
|
+
}
|
|
555
563
|
missing = sorted(
|
|
556
564
|
{c for c in requested_cols if c.lower() not in available_cols},
|
|
557
565
|
key=lambda v: v.lower(),
|
|
@@ -120,14 +120,15 @@ class SessionControlMixin:
|
|
|
120
120
|
# ── switch resolution ────────────────────────────────────────────────
|
|
121
121
|
def _session_flags(self, tx):
|
|
122
122
|
"""The stage's feature flags (the `single_session` capability's
|
|
123
|
-
knobs)
|
|
124
|
-
|
|
123
|
+
knobs) from velocity's container cache — no read of their own per
|
|
124
|
+
heartbeat; {} when there is no tx or no flags table, so sessions
|
|
125
|
+
never depend on the flag row existing."""
|
|
125
126
|
if tx is None:
|
|
126
127
|
return {}
|
|
127
128
|
try:
|
|
128
129
|
from velocity.aws import feature_flags
|
|
129
130
|
|
|
130
|
-
return feature_flags.
|
|
131
|
+
return feature_flags.get_cached(tx) or {}
|
|
131
132
|
except Exception:
|
|
132
133
|
return {}
|
|
133
134
|
|