velocity-python 0.1.109__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.109/src/velocity_python.egg-info → velocity_python-0.1.110}/PKG-INFO +1 -1
- {velocity_python-0.1.109 → velocity_python-0.1.110}/pyproject.toml +1 -1
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/security.py +5 -12
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/feature_flags.py +16 -19
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/context.py +91 -48
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/lambda_handler.py +26 -5
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/data_service.py +10 -2
- velocity_python-0.1.110/src/velocity/cache.py +182 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/table.py +131 -19
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/transaction.py +7 -1
- {velocity_python-0.1.109 → velocity_python-0.1.110/src/velocity_python.egg-info}/PKG-INFO +1 -1
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity_python.egg-info/SOURCES.txt +5 -0
- velocity_python-0.1.110/tests/test_cognito_user_cache.py +81 -0
- velocity_python-0.1.110/tests/test_lambda_handler_user_cache.py +87 -0
- velocity_python-0.1.110/tests/test_read_cache.py +119 -0
- velocity_python-0.1.110/tests/test_table_find_cache.py +172 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/LICENSE +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/README.md +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/setup.cfg +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_blob_store.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_capabilities.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_job_results.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_observability.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_response.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_security_capability.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_table_purge.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.109 → velocity_python-0.1.110}/tests/test_write_hook_create_flow.py +0 -0
{velocity_python-0.1.109 → 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,12 +62,9 @@ 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)."""
|
|
70
|
-
|
|
67
|
+
read_cache.invalidate(SITE_TABLE)
|
|
71
68
|
feature_flags.clear_cache()
|
|
72
69
|
|
|
73
70
|
|
|
@@ -174,13 +171,9 @@ def check_rate(tx, bucket, *, limit, now, table=RATE_TABLE):
|
|
|
174
171
|
|
|
175
172
|
|
|
176
173
|
def _cached(key, loader, *, now=None):
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return hit[1]
|
|
181
|
-
value = loader()
|
|
182
|
-
_cache[key] = (now + CACHE_TTL_SECONDS, value)
|
|
183
|
-
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)
|
|
184
177
|
|
|
185
178
|
|
|
186
179
|
def _site_row(tx):
|
|
@@ -32,9 +32,9 @@ mechanism applied to the flags table, keyed by ``applied_system``.
|
|
|
32
32
|
from __future__ import annotations
|
|
33
33
|
|
|
34
34
|
import logging
|
|
35
|
-
import time
|
|
36
35
|
|
|
37
36
|
from velocity.aws import mirror as _mirror
|
|
37
|
+
from velocity.cache import memo, read_cache
|
|
38
38
|
|
|
39
39
|
log = logging.getLogger(__name__)
|
|
40
40
|
|
|
@@ -46,7 +46,6 @@ CACHE_SECONDS_CONFIG_KEY = "FeatureFlagCacheSeconds"
|
|
|
46
46
|
DEFAULT_CACHE_SECONDS = 60
|
|
47
47
|
|
|
48
48
|
_alternate_engines = _mirror._alternate_engines
|
|
49
|
-
_cache: dict = {} # (system, table) -> (expires_at, row)
|
|
50
49
|
_cache_seconds: float | None = None
|
|
51
50
|
|
|
52
51
|
|
|
@@ -102,37 +101,35 @@ def cache_seconds() -> float:
|
|
|
102
101
|
return _cache_seconds
|
|
103
102
|
|
|
104
103
|
|
|
105
|
-
def clear_cache() -> None:
|
|
104
|
+
def clear_cache(table: str = DEFAULT_TABLE) -> None:
|
|
106
105
|
"""Forget cached rows (after a local flags write; tests)."""
|
|
107
106
|
global _cache_seconds
|
|
108
|
-
|
|
107
|
+
read_cache.invalidate(table)
|
|
109
108
|
_cache_seconds = None
|
|
110
109
|
|
|
111
110
|
|
|
112
111
|
def get_cached(
|
|
113
112
|
tx, system: str | None = None, *, table: str = DEFAULT_TABLE, now: float | None = None
|
|
114
113
|
) -> dict:
|
|
115
|
-
""":func:`get`
|
|
114
|
+
""":func:`get` through the process read cache (:mod:`velocity.cache`)
|
|
115
|
+
— the hot-path reader.
|
|
116
116
|
|
|
117
117
|
The flags row is ONE row per stage, and every reader in a request
|
|
118
118
|
(the handler context, the security guards, session control, any
|
|
119
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
|
-
|
|
122
|
-
|
|
123
|
-
sees
|
|
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
124
|
"""
|
|
125
125
|
system = system if system is not None else current_system()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return dict(hit[1])
|
|
134
|
-
row = get(tx, system, table=table)
|
|
135
|
-
_cache[key] = (now + ttl, dict(row))
|
|
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
|
+
)
|
|
136
133
|
return dict(row)
|
|
137
134
|
|
|
138
135
|
|
|
@@ -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.109 → 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.109 → 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(),
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Process-level read cache: reads that may be reused across requests.
|
|
2
|
+
|
|
3
|
+
velocity's transaction cache (``select(..., cache=True)``) lives and dies
|
|
4
|
+
with one transaction. This store outlives requests on a warm Lambda
|
|
5
|
+
container (or any long-lived process), so a read that is the same for
|
|
6
|
+
every request — a feature-flag row, a capability row, the signed-in user,
|
|
7
|
+
a table's column list, a Cognito lookup — costs one round trip per TTL
|
|
8
|
+
instead of one per request. One store, one policy, no per-module dicts.
|
|
9
|
+
|
|
10
|
+
Three ways in:
|
|
11
|
+
|
|
12
|
+
* table reads — ``tx.table(t).find(where, cache_ttl=60)``,
|
|
13
|
+
``exists(cache_ttl=60)``, ``sys_columns(cache_ttl=300)``,
|
|
14
|
+
``select(..., cache_ttl=60)`` — keyed by database + SQL + params;
|
|
15
|
+
* :func:`memo` — any loader (an API call, a composite of reads), tagged
|
|
16
|
+
with the tables it depends on;
|
|
17
|
+
* :data:`read_cache` — explicit ``invalidate`` / ``clear`` / ``stats``.
|
|
18
|
+
|
|
19
|
+
**Invalidation.** A write through the table API (insert / update / delete /
|
|
20
|
+
merge / upsert / truncate) or DDL on table *T* in this process drops every
|
|
21
|
+
entry tagged *T* at once; a raw ``tx.execute`` write is classified and does
|
|
22
|
+
the same. Other processes see the change when their TTL expires — that
|
|
23
|
+
bounded staleness is the contract a caller accepts by naming a TTL.
|
|
24
|
+
``VELOCITY_READ_CACHE=0`` disables the store everywhere (tests, debugging).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import threading
|
|
31
|
+
import time
|
|
32
|
+
from collections import OrderedDict
|
|
33
|
+
|
|
34
|
+
ENV_KEY = "VELOCITY_READ_CACHE"
|
|
35
|
+
MAX_ENTRIES = 1024
|
|
36
|
+
# tag for entries that describe the schema (table list, columns): DDL drops
|
|
37
|
+
# them; ordinary writes do not
|
|
38
|
+
SCHEMA_TAG = "__schema__"
|
|
39
|
+
|
|
40
|
+
_MISS = object()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _norm(table) -> str:
|
|
44
|
+
return str(table).strip().lower()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ReadCache:
|
|
48
|
+
"""A TTL + LRU store of read results, indexed by the tables they depend on."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, max_entries: int = MAX_ENTRIES):
|
|
51
|
+
self._entries: OrderedDict = OrderedDict() # key -> (expires_at, value, tags)
|
|
52
|
+
self._by_table: dict = {} # tag -> set(keys)
|
|
53
|
+
self._lock = threading.RLock()
|
|
54
|
+
self._max = max_entries
|
|
55
|
+
self._enabled: bool | None = None
|
|
56
|
+
self.hits = 0
|
|
57
|
+
self.misses = 0
|
|
58
|
+
|
|
59
|
+
# ── switch ──────────────────────────────────────────────────────────
|
|
60
|
+
def enabled(self) -> bool:
|
|
61
|
+
if self._enabled is None:
|
|
62
|
+
raw = os.environ.get(ENV_KEY, "").strip().lower()
|
|
63
|
+
self._enabled = raw not in ("0", "false", "off", "no")
|
|
64
|
+
return self._enabled
|
|
65
|
+
|
|
66
|
+
def configure(self, *, enabled: bool | None) -> None:
|
|
67
|
+
"""Force the switch (``None`` re-reads the environment)."""
|
|
68
|
+
self._enabled = enabled
|
|
69
|
+
|
|
70
|
+
# ── core ────────────────────────────────────────────────────────────
|
|
71
|
+
def get(self, key, *, now: float | None = None):
|
|
72
|
+
"""``(hit, value)``; an expired entry is a miss and is dropped."""
|
|
73
|
+
if not self.enabled():
|
|
74
|
+
return False, None
|
|
75
|
+
now = time.monotonic() if now is None else now
|
|
76
|
+
with self._lock:
|
|
77
|
+
entry = self._entries.get(key)
|
|
78
|
+
if entry is None:
|
|
79
|
+
self.misses += 1
|
|
80
|
+
return False, None
|
|
81
|
+
expires_at, value, _tags = entry
|
|
82
|
+
if expires_at <= now:
|
|
83
|
+
self._drop(key)
|
|
84
|
+
self.misses += 1
|
|
85
|
+
return False, None
|
|
86
|
+
self._entries.move_to_end(key)
|
|
87
|
+
self.hits += 1
|
|
88
|
+
return True, value
|
|
89
|
+
|
|
90
|
+
def put(self, key, value, ttl: float, *, tables=(), now: float | None = None) -> None:
|
|
91
|
+
if not self.enabled() or ttl is None or ttl <= 0:
|
|
92
|
+
return
|
|
93
|
+
now = time.monotonic() if now is None else now
|
|
94
|
+
tags = frozenset(_norm(t) for t in tables if t)
|
|
95
|
+
with self._lock:
|
|
96
|
+
if key in self._entries:
|
|
97
|
+
self._drop(key)
|
|
98
|
+
self._entries[key] = (now + ttl, value, tags)
|
|
99
|
+
for tag in tags:
|
|
100
|
+
self._by_table.setdefault(tag, set()).add(key)
|
|
101
|
+
while len(self._entries) > self._max:
|
|
102
|
+
oldest, _ = self._entries.popitem(last=False)
|
|
103
|
+
self._untag(oldest)
|
|
104
|
+
|
|
105
|
+
def memo(self, key, loader, ttl: float, *, tables=(), cache_none: bool = True, now=None):
|
|
106
|
+
"""``loader()`` once per ``ttl`` for ``key``. ``cache_none=False`` never
|
|
107
|
+
stores a ``None`` result (a "not found" that may exist a moment later)."""
|
|
108
|
+
hit, value = self.get(key, now=now)
|
|
109
|
+
if hit:
|
|
110
|
+
return value
|
|
111
|
+
value = loader()
|
|
112
|
+
if value is not None or cache_none:
|
|
113
|
+
self.put(key, value, ttl, tables=tables, now=now)
|
|
114
|
+
return value
|
|
115
|
+
|
|
116
|
+
# ── invalidation ────────────────────────────────────────────────────
|
|
117
|
+
def invalidate(self, table=None) -> int:
|
|
118
|
+
"""Drop every entry tagged ``table`` (all entries when ``None``).
|
|
119
|
+
Returns how many were dropped."""
|
|
120
|
+
with self._lock:
|
|
121
|
+
if table is None:
|
|
122
|
+
count = len(self._entries)
|
|
123
|
+
self._entries.clear()
|
|
124
|
+
self._by_table.clear()
|
|
125
|
+
return count
|
|
126
|
+
keys = list(self._by_table.get(_norm(table), ()))
|
|
127
|
+
for key in keys:
|
|
128
|
+
self._drop(key)
|
|
129
|
+
return len(keys)
|
|
130
|
+
|
|
131
|
+
def invalidate_schema(self) -> int:
|
|
132
|
+
"""DDL happened: drop the table list / column entries."""
|
|
133
|
+
return self.invalidate(SCHEMA_TAG)
|
|
134
|
+
|
|
135
|
+
def clear(self) -> None:
|
|
136
|
+
with self._lock:
|
|
137
|
+
self.invalidate(None)
|
|
138
|
+
self.hits = 0
|
|
139
|
+
self.misses = 0
|
|
140
|
+
|
|
141
|
+
def _drop(self, key) -> None:
|
|
142
|
+
self._entries.pop(key, None)
|
|
143
|
+
self._untag(key)
|
|
144
|
+
|
|
145
|
+
def _untag(self, key) -> None:
|
|
146
|
+
for tag, keys in list(self._by_table.items()):
|
|
147
|
+
keys.discard(key)
|
|
148
|
+
if not keys:
|
|
149
|
+
del self._by_table[tag]
|
|
150
|
+
|
|
151
|
+
# ── observability ───────────────────────────────────────────────────
|
|
152
|
+
def __len__(self) -> int:
|
|
153
|
+
return len(self._entries)
|
|
154
|
+
|
|
155
|
+
def stats(self) -> dict:
|
|
156
|
+
return {
|
|
157
|
+
"enabled": self.enabled(),
|
|
158
|
+
"entries": len(self._entries),
|
|
159
|
+
"hits": self.hits,
|
|
160
|
+
"misses": self.misses,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
read_cache = ReadCache()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def memo(key, loader, ttl: float, *, tables=(), cache_none: bool = True, now=None):
|
|
168
|
+
"""Module-level shortcut for :meth:`ReadCache.memo` on the shared store."""
|
|
169
|
+
return read_cache.memo(key, loader, ttl, tables=tables, cache_none=cache_none, now=now)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def db_identity(engine) -> str:
|
|
173
|
+
"""A stable name for the database an engine talks to (part of every
|
|
174
|
+
table-read key, so two databases on one instance never share entries)."""
|
|
175
|
+
config = getattr(engine, "config", None)
|
|
176
|
+
if isinstance(config, dict):
|
|
177
|
+
host = config.get("host") or ""
|
|
178
|
+
name = config.get("dbname") or config.get("database") or ""
|
|
179
|
+
return f"{host}/{name}"
|
|
180
|
+
if isinstance(config, str):
|
|
181
|
+
return config
|
|
182
|
+
return f"engine:{id(engine)}"
|