velocity-python 0.1.105__tar.gz → 0.1.106__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.105 → velocity_python-0.1.106}/PKG-INFO +1 -1
- {velocity_python-0.1.105 → velocity_python-0.1.106}/pyproject.toml +1 -1
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/__init__.py +1 -1
- velocity_python-0.1.106/src/velocity/aws/capabilities/__init__.py +43 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/base.py +79 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/dispatch.py +50 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/infra.py +43 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/jobs.py +53 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/provisioning.py +180 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/retention.py +65 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/security.py +351 -0
- velocity_python-0.1.106/src/velocity/aws/capabilities/single_session.py +60 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/feature_flags.py +64 -14
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/base_handler.py +22 -1
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/context.py +10 -7
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/lambda_handler.py +15 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/job_handler.py +13 -28
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/session_control.py +39 -11
- velocity_python-0.1.106/src/velocity/aws/job_lifecycle.py +46 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/table.py +33 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/SOURCES.txt +17 -0
- velocity_python-0.1.106/tests/test_access_error_403.py +68 -0
- velocity_python-0.1.106/tests/test_capabilities.py +363 -0
- velocity_python-0.1.106/tests/test_context_flags_loader.py +68 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_feature_flags.py +17 -4
- velocity_python-0.1.106/tests/test_job_lifecycle.py +102 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler_masquerade.py +35 -2
- velocity_python-0.1.106/tests/test_security_capability.py +293 -0
- velocity_python-0.1.106/tests/test_single_session_capability.py +84 -0
- velocity_python-0.1.106/tests/test_table_purge.py +75 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/LICENSE +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/README.md +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/setup.cfg +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_blob_store.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_job_results.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_observability.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_response.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""System-level capabilities a site turns on: declared once in code, toggled
|
|
2
|
+
per site/environment by the control plane, materialised by
|
|
3
|
+
:func:`provisioning.provision` at deploy time and run by
|
|
4
|
+
:class:`dispatch.CapabilityDispatcher` at runtime. See :mod:`base`.
|
|
5
|
+
|
|
6
|
+
Importing this package registers the built-in capabilities.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .base import ( # noqa: F401
|
|
10
|
+
EVERY_15_MINUTES,
|
|
11
|
+
HOURLY,
|
|
12
|
+
NIGHTLY,
|
|
13
|
+
REGISTRY,
|
|
14
|
+
Capability,
|
|
15
|
+
capability,
|
|
16
|
+
get,
|
|
17
|
+
resolve_config,
|
|
18
|
+
)
|
|
19
|
+
from .dispatch import CapabilityDispatcher, run_capability # noqa: F401
|
|
20
|
+
from .provisioning import provision, trigger_key # noqa: F401
|
|
21
|
+
|
|
22
|
+
# built-ins (importing registers them)
|
|
23
|
+
from . import retention # noqa: E402,F401
|
|
24
|
+
from . import security # noqa: E402,F401
|
|
25
|
+
from . import infra # noqa: E402,F401
|
|
26
|
+
from . import single_session # noqa: E402,F401
|
|
27
|
+
from . import jobs # noqa: E402,F401
|
|
28
|
+
from .security import SecurityMixin # noqa: E402,F401
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Capability",
|
|
32
|
+
"CapabilityDispatcher",
|
|
33
|
+
"REGISTRY",
|
|
34
|
+
"capability",
|
|
35
|
+
"get",
|
|
36
|
+
"provision",
|
|
37
|
+
"resolve_config",
|
|
38
|
+
"run_capability",
|
|
39
|
+
"trigger_key",
|
|
40
|
+
"NIGHTLY",
|
|
41
|
+
"HOURLY",
|
|
42
|
+
"EVERY_15_MINUTES",
|
|
43
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Capabilities: self-describing system-level features a site can turn on.
|
|
2
|
+
|
|
3
|
+
A whole class of features has the same shape — some schema, a recurring
|
|
4
|
+
schedule, a handler, a few config knobs, maybe a handler mixin: retention
|
|
5
|
+
pruning, session reaping, the feature-flag mirror, mail harvest, demo-safety
|
|
6
|
+
scrubs, ops metrics. A :class:`Capability` declares all of that in ONE object,
|
|
7
|
+
so the deploy pipeline can materialise it (see :mod:`provisioning`) and the
|
|
8
|
+
queue handler can run it (see :mod:`dispatch`) without a bespoke migration or
|
|
9
|
+
wiring per feature.
|
|
10
|
+
|
|
11
|
+
Division of labour:
|
|
12
|
+
|
|
13
|
+
* **code** (a subclass registered with :func:`capability`) says what the
|
|
14
|
+
feature *is* — its schema, default cadence, default knobs, and the work;
|
|
15
|
+
* the **control plane** (the site registry) says which sites/environments run
|
|
16
|
+
it and with what policy, delivered to the site as an entry that
|
|
17
|
+
:func:`provisioning.provision` materialises.
|
|
18
|
+
|
|
19
|
+
Config resolution (:func:`resolve_config`): the capability's ``defaults`` are
|
|
20
|
+
the floor; the environment's feature-flag row overrides the knobs the
|
|
21
|
+
capability declares (so they are edited on the Feature Flags page and mirrored
|
|
22
|
+
between databases); the site's registry ``config`` overrides everything (the
|
|
23
|
+
per-site policy — e.g. *which* tables retention may touch).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
# cron expressions the scheduler (croniter) accepts
|
|
29
|
+
NIGHTLY = "0 3 * * *"
|
|
30
|
+
HOURLY = "0 * * * *"
|
|
31
|
+
EVERY_15_MINUTES = "*/15 * * * *"
|
|
32
|
+
|
|
33
|
+
REGISTRY: dict = {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Capability:
|
|
37
|
+
name: str = ""
|
|
38
|
+
schema: dict = {} # {table: {column: sql type}} -> ensure_schema
|
|
39
|
+
defaults: dict = {} # flag name -> default; seeded as TEXT feature flags
|
|
40
|
+
schedules: tuple = () # ((action, cron), ...)
|
|
41
|
+
mixins: tuple = () # handler mixins the site attaches at runtime
|
|
42
|
+
applies_to: tuple = ("demo", "production") # default stages; registry can force
|
|
43
|
+
|
|
44
|
+
def handle(self, action, tx, context, config):
|
|
45
|
+
"""Do the work for one scheduled/queued ``action``. Return a JSON-able
|
|
46
|
+
summary (it lands in the queue response and the job log)."""
|
|
47
|
+
raise NotImplementedError(f"{self.name}: no handler for action {action!r}")
|
|
48
|
+
|
|
49
|
+
def on_refresh(self, tx, config):
|
|
50
|
+
"""Hook run after a demo refresh (optional) — scrub/neutralise here."""
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def capability(cls):
|
|
55
|
+
"""Class decorator: instantiate and register under ``cls.name``."""
|
|
56
|
+
if not getattr(cls, "name", ""):
|
|
57
|
+
raise ValueError(f"{cls.__name__} must set a non-empty `name`")
|
|
58
|
+
REGISTRY[cls.name] = cls()
|
|
59
|
+
return cls
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get(name):
|
|
63
|
+
try:
|
|
64
|
+
return REGISTRY[name]
|
|
65
|
+
except KeyError:
|
|
66
|
+
known = ", ".join(sorted(REGISTRY)) or "(none registered)"
|
|
67
|
+
raise KeyError(f"unknown capability {name!r}; registered: {known}") from None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_config(cap, flags=None, row_config=None) -> dict:
|
|
71
|
+
"""defaults <- feature flags (declared knobs only) <- site registry config."""
|
|
72
|
+
config = dict(cap.defaults)
|
|
73
|
+
flags = flags or {}
|
|
74
|
+
for key in cap.defaults:
|
|
75
|
+
value = flags.get(key)
|
|
76
|
+
if value not in (None, ""):
|
|
77
|
+
config[key] = value
|
|
78
|
+
config.update(row_config or {})
|
|
79
|
+
return config
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Runtime dispatch: route the ``run-capability`` queue action to a capability.
|
|
2
|
+
|
|
3
|
+
A site's queue handler mixes in :class:`CapabilityDispatcher`; the scheduler
|
|
4
|
+
stubs seeded by :mod:`provision` enqueue ``run-capability`` with
|
|
5
|
+
``{"capability": name, "action": action}`` and this routes it. Config is
|
|
6
|
+
resolved locally — the site's own ``sys_capabilities`` row plus its feature
|
|
7
|
+
flags — so the runtime never reaches into the control plane.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from velocity.aws import feature_flags
|
|
15
|
+
|
|
16
|
+
from .base import REGISTRY, resolve_config
|
|
17
|
+
from .provisioning import SITE_TABLE
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_capability(tx, context, name, action):
|
|
21
|
+
"""Resolve config and run one capability action. Returns the response body."""
|
|
22
|
+
cap = REGISTRY.get(name)
|
|
23
|
+
if cap is None:
|
|
24
|
+
return {"error": f"unknown capability {name!r}", "capability": name}
|
|
25
|
+
|
|
26
|
+
row = tx.table(SITE_TABLE).find({"name": name}) or {}
|
|
27
|
+
if row and row.get("enabled") is False:
|
|
28
|
+
return {
|
|
29
|
+
"capability": name,
|
|
30
|
+
"action": action,
|
|
31
|
+
"skipped": "disabled for this site",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
raw = row.get("config") if row else None
|
|
35
|
+
row_config = json.loads(raw) if isinstance(raw, str) and raw else (raw or {})
|
|
36
|
+
config = resolve_config(cap, flags=feature_flags.get(tx), row_config=row_config)
|
|
37
|
+
|
|
38
|
+
result = cap.handle(action, tx, context, config)
|
|
39
|
+
return {"capability": name, "action": action, "result": result}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CapabilityDispatcher:
|
|
43
|
+
"""Queue-handler mixin: ``run-capability`` -> the registered capability."""
|
|
44
|
+
|
|
45
|
+
def OnActionRunCapability(self, tx, context):
|
|
46
|
+
payload = context.payload() or {}
|
|
47
|
+
body = run_capability(
|
|
48
|
+
tx, context, payload.get("capability"), payload.get("action")
|
|
49
|
+
)
|
|
50
|
+
context.response().set_body(body)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Infra-tier capabilities: rendered into a site's CloudFormation stacks by
|
|
2
|
+
the sitefactory templates rather than run by a Lambda.
|
|
3
|
+
|
|
4
|
+
They are registered here so the registry recognises their names (the
|
|
5
|
+
provisioner would otherwise refuse an unknown capability) and so they sit
|
|
6
|
+
on the same Site Capabilities page as everything else; their ``config`` is
|
|
7
|
+
the template's policy. Nothing here executes at runtime.
|
|
8
|
+
|
|
9
|
+
* ``security_headers`` — the CloudFront response-headers policy on the web
|
|
10
|
+
distribution (HSTS, nosniff, frame options, referrer policy, XSS-off, and
|
|
11
|
+
an optional ``csp``). Free; ON by default for every factory site — a row
|
|
12
|
+
is only needed to turn it OFF or to set a Content-Security-Policy.
|
|
13
|
+
* ``api_throttling`` — API Gateway stage throttling (``rate`` requests/s,
|
|
14
|
+
``burst``). Free; the template defaults apply without a row.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .base import Capability, capability
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InfraCapability(Capability):
|
|
23
|
+
schedules = ()
|
|
24
|
+
schema = {}
|
|
25
|
+
|
|
26
|
+
def handle(self, action, tx, context, config):
|
|
27
|
+
raise RuntimeError(
|
|
28
|
+
f"{self.name} is an infra capability rendered into the site's stacks "
|
|
29
|
+
f"by sitefactory; it has no runtime action ({action!r})"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@capability
|
|
34
|
+
class SecurityHeaders(InfraCapability):
|
|
35
|
+
name = "security_headers"
|
|
36
|
+
# policy lives in the row's config: {"csp": "...", "frame_options": "DENY"}
|
|
37
|
+
defaults = {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@capability
|
|
41
|
+
class ApiThrottling(InfraCapability):
|
|
42
|
+
name = "api_throttling"
|
|
43
|
+
defaults = {} # {"rate": 100, "burst": 200}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""jobs: the scheduled half of asynchronous-job hygiene.
|
|
2
|
+
|
|
3
|
+
``JobHandler`` only discovers a stale job (worker died: Lambda timeout / OOM)
|
|
4
|
+
when someone polls its status — an abandoned job nobody polls stays "Running"
|
|
5
|
+
forever, and ``aws_job_activity`` is never pruned. This capability sweeps on
|
|
6
|
+
a schedule with the SAME predicate the web tier uses
|
|
7
|
+
(``velocity.aws.job_lifecycle.job_is_stale``): stale jobs are persisted as
|
|
8
|
+
Failed with the standard message, and old activity rows are purged.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import datetime
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
from velocity.aws.job_lifecycle import (
|
|
17
|
+
JOB_ACTIVITY_TABLE,
|
|
18
|
+
STALE_FAILURE,
|
|
19
|
+
STALE_JOB_STATUSES,
|
|
20
|
+
job_is_stale,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .base import EVERY_15_MINUTES, Capability, capability
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@capability
|
|
27
|
+
class Jobs(Capability):
|
|
28
|
+
name = "jobs"
|
|
29
|
+
defaults = {"job_stale_minutes": "16", "job_retention_days": "30"}
|
|
30
|
+
schedules = (("expire_stale", EVERY_15_MINUTES),)
|
|
31
|
+
|
|
32
|
+
def handle(self, action, tx, context, config):
|
|
33
|
+
if action != "expire_stale":
|
|
34
|
+
raise ValueError(f"jobs: unknown action {action!r}")
|
|
35
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
36
|
+
stale_after = datetime.timedelta(
|
|
37
|
+
minutes=int(config.get("job_stale_minutes") or 16)
|
|
38
|
+
)
|
|
39
|
+
jobs = tx.table(JOB_ACTIVITY_TABLE)
|
|
40
|
+
|
|
41
|
+
expired = 0
|
|
42
|
+
for status in sorted(STALE_JOB_STATUSES):
|
|
43
|
+
for job in jobs.select(where={"status": status}):
|
|
44
|
+
if job_is_stale(job, now=now, stale_after=stale_after):
|
|
45
|
+
jobs.update(
|
|
46
|
+
{"status": "Failed", "message": json.dumps(STALE_FAILURE)},
|
|
47
|
+
where={"sys_id": job["sys_id"]},
|
|
48
|
+
)
|
|
49
|
+
expired += 1
|
|
50
|
+
|
|
51
|
+
days = int(config.get("job_retention_days") or 30)
|
|
52
|
+
purged = jobs.purge({"<sys_created": now - datetime.timedelta(days=days)})
|
|
53
|
+
return {"expired": expired, "purged": purged}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Deploy-time provisioning: materialise a site's enabled capabilities.
|
|
2
|
+
|
|
3
|
+
Called by the sitefactory ``provision-capabilities`` step (part of
|
|
4
|
+
``deploy-backend``) with the ``capabilities`` block of ``site.json``, which is
|
|
5
|
+
the site registry rendered for one environment::
|
|
6
|
+
|
|
7
|
+
[{"name": "retention", "enabled": True, "config": {"tables": {...}}}, ...]
|
|
8
|
+
|
|
9
|
+
For each entry this (1) records it in the site's own ``sys_capabilities``
|
|
10
|
+
table — the runtime reads config from there, never from the control plane —
|
|
11
|
+
(2) ensures the capability's schema, (3) seeds its feature-flag knobs (filling
|
|
12
|
+
only what is unset, never clobbering an edit), and (4) upserts one
|
|
13
|
+
``sys_task_triggers`` row + one ``program_objects`` stub per schedule.
|
|
14
|
+
|
|
15
|
+
Everything is keyed deterministically (``cap:<name>:<action>``), so running it
|
|
16
|
+
on every deploy is idempotent — and that is the point: a demo refresh wipes
|
|
17
|
+
the triggers, the next deploy puts them back. A disabled entry disables its
|
|
18
|
+
triggers rather than deleting them, so the ledger of runs survives.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import datetime
|
|
24
|
+
import json
|
|
25
|
+
|
|
26
|
+
from velocity.aws import feature_flags
|
|
27
|
+
|
|
28
|
+
from .base import get
|
|
29
|
+
|
|
30
|
+
SITE_TABLE = "sys_capabilities" # per-site materialised registry
|
|
31
|
+
TRIGGERS_TABLE = "sys_task_triggers"
|
|
32
|
+
PROGRAMS_TABLE = "program_objects"
|
|
33
|
+
FLAGS_TABLE = "sys_feature_flags"
|
|
34
|
+
RUN_ACTION = "run-capability"
|
|
35
|
+
SEEDED_BY = "provision-capabilities"
|
|
36
|
+
EXECUTION_ENVIRONMENT = "SQS Event"
|
|
37
|
+
|
|
38
|
+
# The scheduler runs program source with exec() and only {tx, handler,
|
|
39
|
+
# context, attrs, postdata} in scope — so the stub is a single enqueue and the
|
|
40
|
+
# real logic stays in git-tracked capability code.
|
|
41
|
+
PROGRAM_SOURCE = (
|
|
42
|
+
"result = context.enqueue(action=%r, payload={%r: %r, %r: %r}, user=%r)\n"
|
|
43
|
+
'print("Enqueued", %r, result.get("batch_id"))\n'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def trigger_key(cap_name: str, action: str) -> str:
|
|
48
|
+
return f"cap:{cap_name}:{action}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def program_source(cap_name: str, action: str) -> str:
|
|
52
|
+
return PROGRAM_SOURCE % (
|
|
53
|
+
RUN_ACTION,
|
|
54
|
+
"capability",
|
|
55
|
+
cap_name,
|
|
56
|
+
"action",
|
|
57
|
+
action,
|
|
58
|
+
"Scheduler",
|
|
59
|
+
RUN_ACTION,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def provision(tx, stage: str, entries, *, now=None) -> list:
|
|
64
|
+
"""Materialise ``entries`` for ``stage``. Returns [(name, outcome), ...]."""
|
|
65
|
+
now = now or datetime.datetime.utcnow()
|
|
66
|
+
tx.table(SITE_TABLE).ensure_schema(
|
|
67
|
+
{"name": "TEXT", "enabled": "BOOLEAN", "config": "TEXT"}
|
|
68
|
+
)
|
|
69
|
+
report = []
|
|
70
|
+
for entry in entries or []:
|
|
71
|
+
name = entry["name"]
|
|
72
|
+
cap = get(name)
|
|
73
|
+
enabled = bool(entry.get("enabled", True))
|
|
74
|
+
applies = stage in cap.applies_to or bool(entry.get("force"))
|
|
75
|
+
active = enabled and applies
|
|
76
|
+
|
|
77
|
+
tx.table(SITE_TABLE).merge(
|
|
78
|
+
{
|
|
79
|
+
"name": name,
|
|
80
|
+
"enabled": active,
|
|
81
|
+
"config": json.dumps(entry.get("config") or {}),
|
|
82
|
+
"sys_modified_by": SEEDED_BY,
|
|
83
|
+
},
|
|
84
|
+
pk={"name": name},
|
|
85
|
+
)
|
|
86
|
+
if not active:
|
|
87
|
+
_set_schedules_enabled(tx, cap, False)
|
|
88
|
+
report.append((name, "disabled" if not enabled else f"not for {stage}"))
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
for table, columns in cap.schema.items():
|
|
92
|
+
tx.table(table).ensure_schema(columns)
|
|
93
|
+
|
|
94
|
+
if cap.defaults:
|
|
95
|
+
_seed_flag_defaults(tx, stage, cap)
|
|
96
|
+
|
|
97
|
+
for action, cron in cap.schedules:
|
|
98
|
+
_upsert_schedule(tx, cap, action, cron, now)
|
|
99
|
+
|
|
100
|
+
report.append((name, "provisioned"))
|
|
101
|
+
return report
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _seed_flag_defaults(tx, stage, cap):
|
|
105
|
+
"""Ensure the knob columns exist and fill only the ones that are unset."""
|
|
106
|
+
tx.table(FLAGS_TABLE).ensure_schema({key: "TEXT" for key in cap.defaults})
|
|
107
|
+
row = feature_flags.get(tx, stage)
|
|
108
|
+
if not row:
|
|
109
|
+
return # the app's own seed creates the stage row; nothing to fill yet
|
|
110
|
+
missing = {
|
|
111
|
+
key: str(value)
|
|
112
|
+
for key, value in cap.defaults.items()
|
|
113
|
+
if row.get(key) in (None, "")
|
|
114
|
+
}
|
|
115
|
+
if missing:
|
|
116
|
+
tx.table(FLAGS_TABLE).update(
|
|
117
|
+
missing, where={feature_flags.SYSTEM_COLUMN: stage}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _upsert_schedule(tx, cap, action, cron, now):
|
|
122
|
+
key = trigger_key(cap.name, action)
|
|
123
|
+
triggers = tx.table(TRIGGERS_TABLE)
|
|
124
|
+
if triggers.find({"trigger_name": key}):
|
|
125
|
+
# keep next_run / last_run / run_count — only the definition refreshes
|
|
126
|
+
triggers.update(
|
|
127
|
+
{"cron_expression": cron, "enabled": True, "sys_modified_by": SEEDED_BY},
|
|
128
|
+
where={"trigger_name": key},
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
triggers.insert(
|
|
132
|
+
{
|
|
133
|
+
"name": f"{cap.name}: {action}",
|
|
134
|
+
"description": f"Capability '{cap.name}' -> '{action}' ({cron}).",
|
|
135
|
+
"trigger_name": key,
|
|
136
|
+
"cron_expression": cron,
|
|
137
|
+
# due immediately: backdated a day so it fires on the next tick
|
|
138
|
+
# regardless of the session time zone (next_run is zone-less)
|
|
139
|
+
"next_run": now - datetime.timedelta(days=1),
|
|
140
|
+
"enabled": True,
|
|
141
|
+
"run_count": 0,
|
|
142
|
+
"sys_modified_by": SEEDED_BY,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
source = program_source(cap.name, action)
|
|
147
|
+
row = {
|
|
148
|
+
"program_name": f"{cap.name}: {action}",
|
|
149
|
+
"program_handle": key,
|
|
150
|
+
"program_description": (
|
|
151
|
+
f"Enqueues '{RUN_ACTION}' for capability '{cap.name}' action "
|
|
152
|
+
f"'{action}'. Provisioned by sitefactory; do not hand-edit."
|
|
153
|
+
),
|
|
154
|
+
"source_code": source,
|
|
155
|
+
"tags": f"capability,{cap.name}",
|
|
156
|
+
"execution_environment": EXECUTION_ENVIRONMENT,
|
|
157
|
+
"trigger": key,
|
|
158
|
+
"last_saved_date": now,
|
|
159
|
+
"num_lines": len(source.splitlines()),
|
|
160
|
+
"num_chars": len(source),
|
|
161
|
+
"version": 1,
|
|
162
|
+
"sys_modified_by": SEEDED_BY,
|
|
163
|
+
}
|
|
164
|
+
programs = tx.table(PROGRAMS_TABLE)
|
|
165
|
+
where = {"execution_environment": EXECUTION_ENVIRONMENT, "trigger": key}
|
|
166
|
+
if programs.find(where):
|
|
167
|
+
programs.update(row, where=where)
|
|
168
|
+
else:
|
|
169
|
+
programs.insert(row)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _set_schedules_enabled(tx, cap, enabled: bool):
|
|
173
|
+
triggers = tx.table(TRIGGERS_TABLE)
|
|
174
|
+
for action, _cron in cap.schedules:
|
|
175
|
+
key = trigger_key(cap.name, action)
|
|
176
|
+
if triggers.find({"trigger_name": key}):
|
|
177
|
+
triggers.update(
|
|
178
|
+
{"enabled": enabled, "sys_modified_by": SEEDED_BY},
|
|
179
|
+
where={"trigger_name": key},
|
|
180
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Retention: delete rows older than a per-table window.
|
|
2
|
+
|
|
3
|
+
The *policy* — which tables, how many days — is the site's registry config,
|
|
4
|
+
never guessed here; this capability only knows how to purge safely. A big
|
|
5
|
+
table is not necessarily a prunable one (an email archive is not a log), so
|
|
6
|
+
nothing is touched unless it is named in ``config["tables"]``::
|
|
7
|
+
|
|
8
|
+
{"tables": {"aws_api_activity": 90, "sys_log_email": 30, "filebuffer": 14}}
|
|
9
|
+
|
|
10
|
+
A window of ``null``/``0`` in that map falls back to the ``purge_default_days``
|
|
11
|
+
feature flag. The delete goes through velocity's :meth:`Table.purge` — batch-
|
|
12
|
+
bounded and committed per batch, so a run stays inside the Lambda budget and
|
|
13
|
+
never holds a long lock; an unfinished table just continues next run.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import datetime
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
from .base import NIGHTLY, Capability, capability
|
|
22
|
+
|
|
23
|
+
IDENTIFIER = re.compile(r"^[a-z_][a-z0-9_]*$")
|
|
24
|
+
TIMESTAMP_COLUMN = "sys_created"
|
|
25
|
+
BATCH_SIZE = 5000
|
|
26
|
+
MAX_BATCHES = 200 # 1M rows per run — well inside the Lambda budget
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@capability
|
|
30
|
+
class Retention(Capability):
|
|
31
|
+
name = "retention"
|
|
32
|
+
defaults = {"purge_default_days": "90"}
|
|
33
|
+
schedules = (("purge", NIGHTLY),)
|
|
34
|
+
|
|
35
|
+
def handle(self, action, tx, context, config):
|
|
36
|
+
if action != "purge":
|
|
37
|
+
raise ValueError(f"retention: unknown action {action!r}")
|
|
38
|
+
default_days = int(config.get("purge_default_days") or 90)
|
|
39
|
+
results = {}
|
|
40
|
+
for table, days in (config.get("tables") or {}).items():
|
|
41
|
+
results[table] = purge_table(tx, table, int(days or default_days))
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def purge_table(
|
|
46
|
+
tx, table, days, *, batch=BATCH_SIZE, max_batches=MAX_BATCHES, now=None
|
|
47
|
+
):
|
|
48
|
+
"""Delete rows of ``table`` whose ``sys_created`` is older than ``days``."""
|
|
49
|
+
if not IDENTIFIER.match(str(table)):
|
|
50
|
+
raise ValueError(f"retention: refusing unsafe table name {table!r}")
|
|
51
|
+
if days < 1:
|
|
52
|
+
raise ValueError(f"retention: window must be >= 1 day for {table!r}")
|
|
53
|
+
|
|
54
|
+
target = tx.table(table)
|
|
55
|
+
if not target.exists():
|
|
56
|
+
return {"skipped": "table does not exist"}
|
|
57
|
+
if TIMESTAMP_COLUMN not in target.sys_columns():
|
|
58
|
+
return {"skipped": f"no {TIMESTAMP_COLUMN} column"}
|
|
59
|
+
|
|
60
|
+
now = now or datetime.datetime.now(datetime.timezone.utc)
|
|
61
|
+
cutoff = now - datetime.timedelta(days=days)
|
|
62
|
+
deleted = target.purge(
|
|
63
|
+
{f"<{TIMESTAMP_COLUMN}": cutoff}, batch=batch, max_batches=max_batches
|
|
64
|
+
)
|
|
65
|
+
return {"deleted": deleted, "days": days}
|