velocity-python 0.1.107__tar.gz → 0.1.109__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.107 → velocity_python-0.1.109}/PKG-INFO +2 -1
- {velocity_python-0.1.107 → velocity_python-0.1.109}/README.md +1 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/pyproject.toml +1 -1
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/__init__.py +2 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/base.py +48 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/security.py +6 -1
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/feature_flags.py +88 -55
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/mixins/session_control.py +4 -3
- velocity_python-0.1.109/src/velocity/aws/mirror.py +267 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/PKG-INFO +2 -1
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_capability_catalog.py +32 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_feature_flags.py +85 -0
- velocity_python-0.1.109/tests/test_mirror.py +232 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_single_session_capability.py +4 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/LICENSE +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/setup.cfg +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_blob_store.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_capabilities.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_job_results.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_observability.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_response.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_security_capability.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_table_purge.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.107 → velocity_python-0.1.109}/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.109
|
|
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.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/__init__.py
RENAMED
|
@@ -20,6 +20,7 @@ from .base import ( # noqa: F401
|
|
|
20
20
|
load_config,
|
|
21
21
|
policy_field,
|
|
22
22
|
resolve_config,
|
|
23
|
+
summarize_policy,
|
|
23
24
|
validate_policy,
|
|
24
25
|
)
|
|
25
26
|
from .dispatch import ( # noqa: F401
|
|
@@ -52,6 +53,7 @@ __all__ = [
|
|
|
52
53
|
"provision",
|
|
53
54
|
"resolve_config",
|
|
54
55
|
"run_capability",
|
|
56
|
+
"summarize_policy",
|
|
55
57
|
"trigger_key",
|
|
56
58
|
"validate_policy",
|
|
57
59
|
"NIGHTLY",
|
|
@@ -161,6 +161,54 @@ def load_config(raw) -> dict:
|
|
|
161
161
|
return value
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def summarize_policy(name, config, *, max_items=3) -> str:
|
|
165
|
+
"""One line a list can show for a row's policy — "Tables: aws_api_activity
|
|
166
|
+
7d, filebuffer 14d · IP deny list: 2" — built from the declared fields so
|
|
167
|
+
it reads the same for every capability. Empty string for an empty policy;
|
|
168
|
+
undeclared keys are counted, not shown."""
|
|
169
|
+
cap = REGISTRY.get(name)
|
|
170
|
+
try:
|
|
171
|
+
config = load_config(config)
|
|
172
|
+
except ValueError:
|
|
173
|
+
return "invalid JSON"
|
|
174
|
+
if cap is None or not config:
|
|
175
|
+
return ""
|
|
176
|
+
parts = []
|
|
177
|
+
declared = set()
|
|
178
|
+
for field in cap.policy:
|
|
179
|
+
declared.add(field["name"])
|
|
180
|
+
value = config.get(field["name"])
|
|
181
|
+
if value in (None, "", [], {}):
|
|
182
|
+
continue
|
|
183
|
+
parts.append(f"{field['label']}: {_brief(field, value, max_items)}")
|
|
184
|
+
extra = len([key for key in config if key not in declared])
|
|
185
|
+
if extra:
|
|
186
|
+
parts.append(f"+{extra} other key{'s' if extra > 1 else ''}")
|
|
187
|
+
return " · ".join(parts)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _brief(field, value, max_items):
|
|
191
|
+
kind = field["kind"]
|
|
192
|
+
if kind == TABLE_DAYS and isinstance(value, dict):
|
|
193
|
+
items = [
|
|
194
|
+
f"{table} {days}d" if days not in (None, "", 0) else f"{table} (default)"
|
|
195
|
+
for table, days in value.items()
|
|
196
|
+
]
|
|
197
|
+
return _clip(items, max_items)
|
|
198
|
+
if kind in (CIDR_LIST, COUNTRY_LIST, LIST) and isinstance(value, (list, tuple)):
|
|
199
|
+
return _clip([str(v) for v in value], max_items)
|
|
200
|
+
if kind == BOOLEAN:
|
|
201
|
+
return "on" if str(value).lower() in ("true", "1", "yes", "on") else "off"
|
|
202
|
+
text = str(value)
|
|
203
|
+
return text if len(text) <= 40 else text[:37] + "..."
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _clip(items, max_items):
|
|
207
|
+
if len(items) <= max_items:
|
|
208
|
+
return ", ".join(items)
|
|
209
|
+
return ", ".join(items[:max_items]) + f" (+{len(items) - max_items} more)"
|
|
210
|
+
|
|
211
|
+
|
|
164
212
|
def validate_policy(name, config) -> list:
|
|
165
213
|
"""Check a site's ``config`` against the capability's declared policy.
|
|
166
214
|
|
{velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity/aws/capabilities/security.py
RENAMED
|
@@ -66,7 +66,9 @@ _cache: dict = {}
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def clear_cache():
|
|
69
|
+
"""Forget the cached site row AND the shared flags row (tests)."""
|
|
69
70
|
_cache.clear()
|
|
71
|
+
feature_flags.clear_cache()
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
# ── request facts ───────────────────────────────────────────────────────
|
|
@@ -201,7 +203,10 @@ def _site_row(tx):
|
|
|
201
203
|
|
|
202
204
|
|
|
203
205
|
def _flags(tx):
|
|
204
|
-
|
|
206
|
+
"""The stage's flags row from velocity's container cache — shared with
|
|
207
|
+
the handler context and session control, so the guards add no read of
|
|
208
|
+
their own to a request."""
|
|
209
|
+
return feature_flags.get_cached(tx)
|
|
205
210
|
|
|
206
211
|
|
|
207
212
|
def _row_config(row) -> dict:
|
|
@@ -23,19 +23,31 @@ 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
|
|
35
|
+
import time
|
|
36
|
+
|
|
37
|
+
from velocity.aws import mirror as _mirror
|
|
31
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: dict = {} # (system, table) -> (expires_at, row)
|
|
50
|
+
_cache_seconds: float | None = None
|
|
39
51
|
|
|
40
52
|
|
|
41
53
|
def current_system(default: str | None = None) -> str | None:
|
|
@@ -68,20 +80,76 @@ def get(
|
|
|
68
80
|
return dict(row)
|
|
69
81
|
|
|
70
82
|
|
|
83
|
+
def cache_seconds() -> float:
|
|
84
|
+
"""Seconds a warm container may reuse the flags row: the
|
|
85
|
+
``FeatureFlagCacheSeconds`` config key, default 60; ``0`` reads the row
|
|
86
|
+
on every call. Resolved once per container."""
|
|
87
|
+
global _cache_seconds
|
|
88
|
+
if _cache_seconds is None:
|
|
89
|
+
raw = None
|
|
90
|
+
try:
|
|
91
|
+
from velocity.aws import ssm_config
|
|
92
|
+
|
|
93
|
+
raw = ssm_config.getenv(CACHE_SECONDS_CONFIG_KEY, None)
|
|
94
|
+
except Exception:
|
|
95
|
+
raw = None
|
|
96
|
+
try:
|
|
97
|
+
_cache_seconds = (
|
|
98
|
+
float(raw) if raw not in (None, "") else float(DEFAULT_CACHE_SECONDS)
|
|
99
|
+
)
|
|
100
|
+
except (TypeError, ValueError):
|
|
101
|
+
_cache_seconds = float(DEFAULT_CACHE_SECONDS)
|
|
102
|
+
return _cache_seconds
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def clear_cache() -> None:
|
|
106
|
+
"""Forget cached rows (after a local flags write; tests)."""
|
|
107
|
+
global _cache_seconds
|
|
108
|
+
_cache.clear()
|
|
109
|
+
_cache_seconds = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_cached(
|
|
113
|
+
tx, system: str | None = None, *, table: str = DEFAULT_TABLE, now: float | None = None
|
|
114
|
+
) -> dict:
|
|
115
|
+
""":func:`get` behind a per-container TTL cache — 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. A
|
|
121
|
+
flag saved through the mirror hook clears the local cache, so the
|
|
122
|
+
environment that made the change sees it at once; the other environment
|
|
123
|
+
sees it within the TTL.
|
|
124
|
+
"""
|
|
125
|
+
system = system if system is not None else current_system()
|
|
126
|
+
ttl = cache_seconds()
|
|
127
|
+
if ttl <= 0:
|
|
128
|
+
return get(tx, system, table=table)
|
|
129
|
+
now = time.monotonic() if now is None else now
|
|
130
|
+
key = (system, table)
|
|
131
|
+
hit = _cache.get(key)
|
|
132
|
+
if hit and hit[0] > now:
|
|
133
|
+
return dict(hit[1])
|
|
134
|
+
row = get(tx, system, table=table)
|
|
135
|
+
_cache[key] = (now + ttl, dict(row))
|
|
136
|
+
return dict(row)
|
|
137
|
+
|
|
138
|
+
|
|
71
139
|
def load(tx, loader=None) -> dict:
|
|
72
140
|
"""The request-time flag source used by handler contexts.
|
|
73
141
|
|
|
74
142
|
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 ``{}``.
|
|
143
|
+
otherwise :func:`get_cached` — velocity's stage-scoped reader behind the
|
|
144
|
+
container cache — is the source, never raising into the request. The
|
|
145
|
+
legacy CaringCent ``support.app.helpers`` loader remains only as a last
|
|
146
|
+
resort when the velocity reader finds no row (it raises on a missing
|
|
147
|
+
row, which those apps rely on); absent that package the answer is ``{}``.
|
|
80
148
|
"""
|
|
81
149
|
if callable(loader):
|
|
82
150
|
return loader(tx)
|
|
83
151
|
try:
|
|
84
|
-
flags =
|
|
152
|
+
flags = get_cached(tx)
|
|
85
153
|
except Exception:
|
|
86
154
|
flags = {}
|
|
87
155
|
if flags:
|
|
@@ -101,28 +169,13 @@ def _legacy_support_loader(tx):
|
|
|
101
169
|
return helpers.get_feature_flags(tx)
|
|
102
170
|
|
|
103
171
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return {k: v for k, v in dict(row).items() if not str(k).startswith("sys_")}
|
|
172
|
+
_without_sys = _mirror.business_columns
|
|
173
|
+
_toast = _mirror.toast
|
|
107
174
|
|
|
108
175
|
|
|
109
176
|
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]
|
|
177
|
+
"""Engine for the alternate database — see :func:`velocity.aws.mirror.alternate_engine`."""
|
|
178
|
+
return _mirror.alternate_engine(alternate_db)
|
|
126
179
|
|
|
127
180
|
|
|
128
181
|
def mirror_row(
|
|
@@ -133,14 +186,9 @@ def mirror_row(
|
|
|
133
186
|
Returns the mirrored key, or ``None`` when the row has no system key (a
|
|
134
187
|
keyless row is skipped, never mirrored to an arbitrary target).
|
|
135
188
|
"""
|
|
136
|
-
data = _without_sys(row)
|
|
137
|
-
key = data.get(SYSTEM_COLUMN)
|
|
138
|
-
if not key:
|
|
139
|
-
return None
|
|
140
189
|
engine = engine or alternate_engine(alternate_db)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return key
|
|
190
|
+
pk = _mirror.mirror_row(row, table=table, key=(SYSTEM_COLUMN,), engine=engine)
|
|
191
|
+
return pk[SYSTEM_COLUMN] if pk else None
|
|
144
192
|
|
|
145
193
|
|
|
146
194
|
def reconcile(
|
|
@@ -149,27 +197,11 @@ def reconcile(
|
|
|
149
197
|
"""Mirror every flags row into the alternate database. Catch-up companion of
|
|
150
198
|
:func:`mirror_row`. Returns ``{"synced": [...], "skipped": [...]}``."""
|
|
151
199
|
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)
|
|
200
|
+
result = _mirror.reconcile(tx, table=table, key=(SYSTEM_COLUMN,), engine=engine)
|
|
201
|
+
return {
|
|
202
|
+
"synced": [pk[SYSTEM_COLUMN] for pk in result["synced"]],
|
|
203
|
+
"skipped": result["skipped"],
|
|
204
|
+
}
|
|
173
205
|
|
|
174
206
|
|
|
175
207
|
def make_mirror_hook(
|
|
@@ -187,6 +219,7 @@ def make_mirror_hook(
|
|
|
187
219
|
|
|
188
220
|
def hook(tx, table_name, sys_id, row, context):
|
|
189
221
|
del tx, table_name, sys_id
|
|
222
|
+
clear_cache() # the row just changed here: this container re-reads it
|
|
190
223
|
try:
|
|
191
224
|
key = mirror_row(row, alternate_db=alternate_db, table=table)
|
|
192
225
|
if notify and key:
|
|
@@ -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
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Cross-database row mirroring: one table, two databases on the same
|
|
2
|
+
instance (demo <-> production), rows kept identical no matter where the
|
|
3
|
+
edit happened.
|
|
4
|
+
|
|
5
|
+
Generalised from the feature-flag mirror (:mod:`velocity.aws.feature_flags`
|
|
6
|
+
now delegates here). A row crosses over by its **natural key** — the columns
|
|
7
|
+
that identify it in either database (``applied_system`` for flags;
|
|
8
|
+
``site_sys_id, env_name, capability`` for site capabilities) — never by
|
|
9
|
+
``sys_id``, which differs per database; ``sys_*`` bookkeeping never crosses.
|
|
10
|
+
|
|
11
|
+
Foreign keys whose ids differ between databases are the caller's to
|
|
12
|
+
translate: ``translate(tx, alt, data)`` rewrites the outgoing row for the
|
|
13
|
+
alternate database (map a site by its slug, say) or raises
|
|
14
|
+
:class:`MirrorSkip` with the reason the row cannot cross — surfaced to the
|
|
15
|
+
operator as a warning, never an error: the local save always stands.
|
|
16
|
+
|
|
17
|
+
Writes into the alternate database go straight through the table API (no
|
|
18
|
+
handler, no rwx hooks), so a mirror never triggers a mirror back. The hooks
|
|
19
|
+
are warn-and-continue; :func:`reconcile` is the catch-up once an unreachable
|
|
20
|
+
alternate is back.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
|
|
27
|
+
log = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
ALTERNATE_DB_CONFIG_KEY = "DBAlternate"
|
|
30
|
+
SYS_PREFIX = "sys_"
|
|
31
|
+
|
|
32
|
+
_alternate_engines: dict = {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MirrorSkip(Exception):
|
|
36
|
+
"""Raised by a ``translate`` to say this row cannot cross, and why."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def alternate_engine(alternate_db: str | None = None):
|
|
40
|
+
"""Engine for the alternate database — same instance, other ``database``.
|
|
41
|
+
|
|
42
|
+
Host/port/user/password are inherited from the running config, so no
|
|
43
|
+
separate credentials are needed; only the database name changes (the
|
|
44
|
+
``DBAlternate`` config key by default). Cached per database name so
|
|
45
|
+
repeated mirrors in one process reuse the connection.
|
|
46
|
+
"""
|
|
47
|
+
from velocity.aws import ssm_config
|
|
48
|
+
from velocity.db.servers import postgres
|
|
49
|
+
|
|
50
|
+
if alternate_db is None:
|
|
51
|
+
alternate_db = ssm_config.getenv(ALTERNATE_DB_CONFIG_KEY, raise_on_missing=True)
|
|
52
|
+
if alternate_db not in _alternate_engines:
|
|
53
|
+
_alternate_engines[alternate_db] = postgres.initialize(
|
|
54
|
+
{"database": alternate_db}
|
|
55
|
+
)
|
|
56
|
+
return _alternate_engines[alternate_db]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def business_columns(row) -> dict:
|
|
60
|
+
"""A row's business columns — the ``sys_*`` bookkeeping never crosses over."""
|
|
61
|
+
return {k: v for k, v in dict(row).items() if not str(k).startswith(SYS_PREFIX)}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def natural_key(data, key) -> dict | None:
|
|
65
|
+
"""``{column: value}`` for the key columns, or None when any is unset — a
|
|
66
|
+
keyless row is skipped, never mirrored onto an arbitrary target."""
|
|
67
|
+
pk = {}
|
|
68
|
+
for column in key:
|
|
69
|
+
value = data.get(column)
|
|
70
|
+
if value in (None, ""):
|
|
71
|
+
return None
|
|
72
|
+
pk[column] = value
|
|
73
|
+
return pk
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _outgoing(tx, alt, row, key, translate):
|
|
77
|
+
data = business_columns(row)
|
|
78
|
+
if translate is not None:
|
|
79
|
+
data = translate(tx, alt, data)
|
|
80
|
+
if data is None:
|
|
81
|
+
return None, None
|
|
82
|
+
return data, natural_key(data, key)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def mirror_row(
|
|
86
|
+
row,
|
|
87
|
+
*,
|
|
88
|
+
table: str,
|
|
89
|
+
key,
|
|
90
|
+
alternate_db: str | None = None,
|
|
91
|
+
engine=None,
|
|
92
|
+
translate=None,
|
|
93
|
+
tx=None,
|
|
94
|
+
) -> dict | None:
|
|
95
|
+
"""Upsert one row into the alternate database by its natural key.
|
|
96
|
+
|
|
97
|
+
Returns the key used, or None when the row was skipped (keyless, or
|
|
98
|
+
``translate`` returned None). A ``MirrorSkip`` from ``translate``
|
|
99
|
+
propagates — the hooks turn it into a warning for the operator.
|
|
100
|
+
"""
|
|
101
|
+
engine = engine or alternate_engine(alternate_db)
|
|
102
|
+
with engine.transaction() as alt:
|
|
103
|
+
data, pk = _outgoing(tx, alt, row, key, translate)
|
|
104
|
+
if pk is None:
|
|
105
|
+
return None
|
|
106
|
+
alt.table(table).merge(data, pk=pk)
|
|
107
|
+
return pk
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def delete_mirror(
|
|
111
|
+
row,
|
|
112
|
+
*,
|
|
113
|
+
table: str,
|
|
114
|
+
key,
|
|
115
|
+
alternate_db: str | None = None,
|
|
116
|
+
engine=None,
|
|
117
|
+
translate=None,
|
|
118
|
+
tx=None,
|
|
119
|
+
) -> dict | None:
|
|
120
|
+
"""Delete the row's counterpart in the alternate database (by natural
|
|
121
|
+
key). Same skip/return contract as :func:`mirror_row`."""
|
|
122
|
+
engine = engine or alternate_engine(alternate_db)
|
|
123
|
+
with engine.transaction() as alt:
|
|
124
|
+
data, pk = _outgoing(tx, alt, row, key, translate)
|
|
125
|
+
if pk is None:
|
|
126
|
+
return None
|
|
127
|
+
alt.table(table).delete(pk)
|
|
128
|
+
return pk
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def reconcile(
|
|
132
|
+
tx,
|
|
133
|
+
*,
|
|
134
|
+
table: str,
|
|
135
|
+
key,
|
|
136
|
+
alternate_db: str | None = None,
|
|
137
|
+
engine=None,
|
|
138
|
+
translate=None,
|
|
139
|
+
) -> dict:
|
|
140
|
+
"""Mirror every row of ``table`` into the alternate database — the
|
|
141
|
+
catch-up companion of :func:`mirror_row` (after the alternate was
|
|
142
|
+
unreachable, or for a fresh environment). One-way: rows that exist only
|
|
143
|
+
in the alternate are left alone. Returns ``{"synced": [keys],
|
|
144
|
+
"skipped": [sys_ids]}``."""
|
|
145
|
+
engine = engine or alternate_engine(alternate_db)
|
|
146
|
+
synced: list = []
|
|
147
|
+
skipped: list = []
|
|
148
|
+
rows = list(tx.table(table).select())
|
|
149
|
+
with engine.transaction() as alt:
|
|
150
|
+
for row in rows:
|
|
151
|
+
try:
|
|
152
|
+
data, pk = _outgoing(tx, alt, row, key, translate)
|
|
153
|
+
except MirrorSkip:
|
|
154
|
+
pk = None
|
|
155
|
+
if pk is None:
|
|
156
|
+
skipped.append(dict(row).get("sys_id"))
|
|
157
|
+
continue
|
|
158
|
+
alt.table(table).merge(data, pk=pk)
|
|
159
|
+
synced.append(pk)
|
|
160
|
+
return {"synced": synced, "skipped": skipped}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def toast(context, message: str, level: str) -> None:
|
|
164
|
+
"""Best-effort operator toast; a context without one is fine (a queue run)."""
|
|
165
|
+
response = getattr(context, "response", None)
|
|
166
|
+
toaster = getattr(response() if callable(response) else None, "toast", None)
|
|
167
|
+
if callable(toaster):
|
|
168
|
+
toaster(message, level)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _run(operation, context, what, verbs, notify):
|
|
172
|
+
"""Run one mirror operation warn-and-continue; tell the operator."""
|
|
173
|
+
done, undone = verbs
|
|
174
|
+
try:
|
|
175
|
+
pk = operation()
|
|
176
|
+
except MirrorSkip as skip:
|
|
177
|
+
log.warning("%s not %s the alternate database: %s", what, done, skip)
|
|
178
|
+
if notify:
|
|
179
|
+
toast(context, f"{what} {undone} the alternate database: {skip}.", "warning")
|
|
180
|
+
return None
|
|
181
|
+
except Exception as exc: # never block the local write
|
|
182
|
+
log.warning("%s: mirror to the alternate database failed", what, exc_info=True)
|
|
183
|
+
if notify:
|
|
184
|
+
toast(
|
|
185
|
+
context,
|
|
186
|
+
f"{what} {undone} the alternate database ({exc}). Run the "
|
|
187
|
+
"reconcile once it is reachable.",
|
|
188
|
+
"warning",
|
|
189
|
+
)
|
|
190
|
+
return None
|
|
191
|
+
if pk is None:
|
|
192
|
+
return None # keyless (an after_new stub, say): nothing to mirror
|
|
193
|
+
if notify:
|
|
194
|
+
toast(context, f"{what} {done} the alternate database.", "success")
|
|
195
|
+
return pk
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def make_mirror_hook(
|
|
199
|
+
*,
|
|
200
|
+
table: str,
|
|
201
|
+
key,
|
|
202
|
+
alternate_db: str | None = None,
|
|
203
|
+
translate=None,
|
|
204
|
+
notify: bool = True,
|
|
205
|
+
label: str | None = None,
|
|
206
|
+
):
|
|
207
|
+
"""An RWX ``after_write`` (or ``after_new``) callable that mirrors the
|
|
208
|
+
saved row. The app's ``rwx/<table>.py`` is a registration line; the
|
|
209
|
+
logic stays here."""
|
|
210
|
+
what = label or table
|
|
211
|
+
|
|
212
|
+
def hook(tx, table_name, sys_id, row, context):
|
|
213
|
+
del table_name, sys_id
|
|
214
|
+
return _run(
|
|
215
|
+
lambda: mirror_row(
|
|
216
|
+
row,
|
|
217
|
+
table=table,
|
|
218
|
+
key=key,
|
|
219
|
+
alternate_db=alternate_db,
|
|
220
|
+
translate=translate,
|
|
221
|
+
tx=tx,
|
|
222
|
+
),
|
|
223
|
+
context,
|
|
224
|
+
what,
|
|
225
|
+
("mirrored to", "saved here but not mirrored to"),
|
|
226
|
+
notify,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return hook
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def make_delete_hook(
|
|
233
|
+
*,
|
|
234
|
+
table: str,
|
|
235
|
+
key,
|
|
236
|
+
alternate_db: str | None = None,
|
|
237
|
+
translate=None,
|
|
238
|
+
notify: bool = True,
|
|
239
|
+
label: str | None = None,
|
|
240
|
+
):
|
|
241
|
+
"""An RWX ``before_delete`` callable that removes the row's counterpart.
|
|
242
|
+
``before`` because the row must still exist here to read its natural
|
|
243
|
+
key; the local delete proceeds whatever happens on the other side."""
|
|
244
|
+
what = label or table
|
|
245
|
+
|
|
246
|
+
def hook(tx, table_name, sys_id, context):
|
|
247
|
+
if tx is None:
|
|
248
|
+
return None
|
|
249
|
+
row = tx.table(table_name).find({"sys_id": sys_id})
|
|
250
|
+
if not row:
|
|
251
|
+
return None
|
|
252
|
+
return _run(
|
|
253
|
+
lambda: delete_mirror(
|
|
254
|
+
row,
|
|
255
|
+
table=table,
|
|
256
|
+
key=key,
|
|
257
|
+
alternate_db=alternate_db,
|
|
258
|
+
translate=translate,
|
|
259
|
+
tx=tx,
|
|
260
|
+
),
|
|
261
|
+
context,
|
|
262
|
+
what,
|
|
263
|
+
("removed from", "deleted here but not removed from"),
|
|
264
|
+
notify,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return hook
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.109
|
|
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 |
|
{velocity_python-0.1.107 → velocity_python-0.1.109}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,7 @@ src/velocity/aws/email.py
|
|
|
12
12
|
src/velocity/aws/feature_flags.py
|
|
13
13
|
src/velocity/aws/job_lifecycle.py
|
|
14
14
|
src/velocity/aws/job_results.py
|
|
15
|
+
src/velocity/aws/mirror.py
|
|
15
16
|
src/velocity/aws/s3.py
|
|
16
17
|
src/velocity/aws/sms.py
|
|
17
18
|
src/velocity/aws/ssm_config.py
|
|
@@ -242,6 +243,7 @@ tests/test_lambda_handler.py
|
|
|
242
243
|
tests/test_lambda_handler_auth.py
|
|
243
244
|
tests/test_lambda_handler_masquerade.py
|
|
244
245
|
tests/test_masquerade_grant.py
|
|
246
|
+
tests/test_mirror.py
|
|
245
247
|
tests/test_mixins_import.py
|
|
246
248
|
tests/test_n_plus_one.py
|
|
247
249
|
tests/test_nosql_dynamodb.py
|