velocity-python 0.3.1__tar.gz → 0.4.0__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.3.1 → velocity_python-0.4.0}/PKG-INFO +1 -1
- {velocity_python-0.3.1 → velocity_python-0.4.0}/pyproject.toml +1 -1
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/__init__.py +1 -1
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/authz.py +30 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/__init__.py +1 -0
- velocity_python-0.4.0/src/velocity/aws/capabilities/workflow.py +48 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/__init__.py +13 -0
- velocity_python-0.4.0/src/velocity/aws/handlers/mixins/workflow_actions.py +491 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/schema.py +5 -0
- velocity_python-0.4.0/src/velocity/aws/settings.py +1104 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/rules/__init__.py +2 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/rules/engine.py +8 -1
- velocity_python-0.4.0/src/velocity/workflow/__init__.py +163 -0
- velocity_python-0.4.0/src/velocity/workflow/access.py +342 -0
- velocity_python-0.4.0/src/velocity/workflow/contract.py +1056 -0
- velocity_python-0.4.0/src/velocity/workflow/definitions.py +824 -0
- velocity_python-0.4.0/src/velocity/workflow/engine.py +3422 -0
- velocity_python-0.4.0/src/velocity/workflow/hooks.py +434 -0
- velocity_python-0.4.0/src/velocity/workflow/notices.py +410 -0
- velocity_python-0.4.0/src/velocity/workflow/outbox.py +266 -0
- velocity_python-0.4.0/src/velocity/workflow/points.py +300 -0
- velocity_python-0.4.0/src/velocity/workflow/schema.py +581 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/SOURCES.txt +22 -0
- velocity_python-0.4.0/tests/test_pgserver_settings.py +146 -0
- velocity_python-0.4.0/tests/test_settings.py +664 -0
- velocity_python-0.4.0/tests/test_workflow_actions.py +789 -0
- velocity_python-0.4.0/tests/test_workflow_contract.py +342 -0
- velocity_python-0.4.0/tests/test_workflow_definitions.py +385 -0
- velocity_python-0.4.0/tests/test_workflow_engine.py +1189 -0
- velocity_python-0.4.0/tests/test_workflow_features.py +799 -0
- velocity_python-0.4.0/tests/test_workflow_postgres.py +432 -0
- velocity_python-0.4.0/tests/test_workflow_review.py +910 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/LICENSE +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/README.md +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/setup.cfg +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/security.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/ci_gate.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/frontend_checks.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/facets.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/failures.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/identity.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/permissions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/queue_defaults.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/scheduler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/tiles.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/user_data.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/_pgddl.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/audit.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/effective.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/fragments.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/integrity.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_integrity_triggers.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/tunnel.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/hooks.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/logging.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/_mirror_core.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/money.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/plaid_api.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/plaid_webhook.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/redact.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/schema.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/rules/expressions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/rules/facts.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/rules/storage.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/testing/__init__.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/testing/fake_db.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_action_names.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_amplify_redirects.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_asset_references.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_assets_service.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_async_support.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_audit_log_confidential.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_audit_mixin.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_audit_registry_table_gate.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_authz.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_aws_package_hygiene.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_blob_store.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_business_objects.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_capabilities.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_capability_dependencies.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_check_changelog.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_ci_gate.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_client_errors_mixin.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_client_input_contract_export.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_client_sql_boundary.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_client_table_gate_case_and_pointers.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_cognito_client_lazy.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_context_enqueue_idempotency.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_context_queue_job.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_conv_combine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_owner_scope.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_row_number.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_data_service_security_gates.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_audit_required.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_effective.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_integrity.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_db_tunnel.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_declared_column_types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_decorators.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_deploy_config_fields.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_deploy_engine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_deploy_frontend_checks.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_deploy_operations_queue.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_dirty_pipeline_consumer.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_dlq_constant.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_docs_snippets.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_email_processing.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_enqueue_metric_namespace.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_ensure_infrastructure.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_export_workbook.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_facets.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_facets_and_where.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_failures.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_hooks.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_identity.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_job_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_job_record_guard.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_job_record_guard_postgres.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_job_results.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_json_columns.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_merge_on_conflict.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_mirror_upsert_public.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_observability.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_on_action_default.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_on_commit.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_ops.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_audit_fixes.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_business_agnostic.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_charge_reference.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_extra_bounds.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_lazy_imports.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pdf.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_permissions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_permissions_actions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_permissions_notices.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_permissions_review.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_bulk_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_capability_types.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_ensure_infrastructure.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_get_tables.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_permissions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_pointer_joins.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_row_scope.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_search_export.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_tiles.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pgserver_user_data.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_plaid_api.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_plaid_webhook.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_pointer_join_sql.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_postgres_initializer_config.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_postgres_py_type.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_query_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_raw_sql_prefix_policy.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_read_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_readme_imports.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_record_event_postgres.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_record_event_transaction.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_response.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_result_returning.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_row_scoped_data_service.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_rules.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_rules_storage.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_s3_bucket_config.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_s3_list_client_files.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_s3_upload.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_scheduler.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_schema_lock_config_other_backends.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_search_paging_export.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_security_capability.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_security_events_table_gate.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_session_control.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_sms_twilio_signature.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_ssm_config_require.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_table_alter.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_table_claim.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_table_purge.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_testing_fake_db.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_tiles.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_tx_lifecycle_retry.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_tx_read_cache_and_bulk.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_user_data.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_user_not_provisioned.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_write_hook_create_flow.py +0 -0
- {velocity_python-0.3.1 → velocity_python-0.4.0}/tests/test_write_hook_empty_strings.py +0 -0
|
@@ -961,6 +961,36 @@ SECURITY_EVENTS_TABLE = "sys_security_events"
|
|
|
961
961
|
protect_tables(SECURITY_EVENTS_TABLE)
|
|
962
962
|
confidential_tables(SECURITY_EVENTS_TABLE)
|
|
963
963
|
|
|
964
|
+
# velocity.workflow's tables, registered here for the same reason: the generic
|
|
965
|
+
# Data actions must refuse them whether or not anything imported
|
|
966
|
+
# velocity.workflow first. Route definitions change only through the
|
|
967
|
+
# workflow's own functions; dockets are read only through its contract
|
|
968
|
+
# builder (a generic read would bypass the visibility rule and expose, for
|
|
969
|
+
# one, every wait's correlation token). velocity.workflow.schema checks that
|
|
970
|
+
# it declares exactly these.
|
|
971
|
+
WORKFLOW_DEFINITION_TABLES = (
|
|
972
|
+
"sys_route",
|
|
973
|
+
"sys_route_station",
|
|
974
|
+
"sys_route_pass",
|
|
975
|
+
"sys_route_test",
|
|
976
|
+
)
|
|
977
|
+
WORKFLOW_DOCKET_TABLES = (
|
|
978
|
+
"sys_docket",
|
|
979
|
+
"sys_docket_position",
|
|
980
|
+
"sys_docket_document",
|
|
981
|
+
"sys_docket_mark",
|
|
982
|
+
"sys_docket_hold",
|
|
983
|
+
"sys_docket_flag",
|
|
984
|
+
"sys_docket_wait",
|
|
985
|
+
"sys_docket_link",
|
|
986
|
+
"sys_docket_outbox",
|
|
987
|
+
"sys_docket_seen",
|
|
988
|
+
"sys_notice",
|
|
989
|
+
"sys_tray_handover",
|
|
990
|
+
)
|
|
991
|
+
protect_tables(*WORKFLOW_DEFINITION_TABLES, *WORKFLOW_DOCKET_TABLES)
|
|
992
|
+
confidential_tables(*WORKFLOW_DOCKET_TABLES)
|
|
993
|
+
|
|
964
994
|
|
|
965
995
|
# ── the action index ───────────────────────────────────────────────────────
|
|
966
996
|
|
|
@@ -46,6 +46,7 @@ from . import security # noqa: E402,F401
|
|
|
46
46
|
from . import infra # noqa: E402,F401
|
|
47
47
|
from . import single_session # noqa: E402,F401
|
|
48
48
|
from . import jobs # noqa: E402,F401
|
|
49
|
+
from . import workflow # noqa: E402,F401
|
|
49
50
|
from .security import SecurityMixin # noqa: E402,F401
|
|
50
51
|
|
|
51
52
|
__all__ = [
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""workflow: the timers and the outbox of :mod:`velocity.workflow`, on a schedule.
|
|
2
|
+
|
|
3
|
+
Dockets keep time through this capability: once a minute ``sweep`` marks SLA
|
|
4
|
+
breaches (they stay), missed waits, expired claims and due hand-overs
|
|
5
|
+
(:func:`velocity.workflow.sweep`), then drains the outbox (program jobs,
|
|
6
|
+
notice emails; :func:`velocity.workflow.outbox.drain`). ``drain`` alone is
|
|
7
|
+
also schedulable.
|
|
8
|
+
|
|
9
|
+
Schema: the tables are the ``workflow`` infrastructure group
|
|
10
|
+
(``ensure_infrastructure(tx, include=("workflow",))``); the parts a column
|
|
11
|
+
list cannot declare — append-only, audit-required marks and the possession
|
|
12
|
+
constraint — come from :func:`velocity.workflow.ensure_schema`, which the
|
|
13
|
+
app's schema lane runs (it does both).
|
|
14
|
+
|
|
15
|
+
Handler mixins: ``WorkflowMixin`` on the Data handler (client actions),
|
|
16
|
+
``WorkflowQueueMixin`` on the queue handler.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from .base import Capability, capability
|
|
22
|
+
|
|
23
|
+
EVERY_MINUTE = "* * * * *"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@capability
|
|
27
|
+
class Workflow(Capability):
|
|
28
|
+
name = "workflow"
|
|
29
|
+
label = "Workflow"
|
|
30
|
+
description = (
|
|
31
|
+
"Dockets moving along routes of stations: SLA breaches, missed waits, "
|
|
32
|
+
"expired claims and hand-overs once a minute, and the outbox (program "
|
|
33
|
+
"jobs and notice emails) drained. No per-site policy."
|
|
34
|
+
)
|
|
35
|
+
schedules = (("sweep", EVERY_MINUTE),)
|
|
36
|
+
mixins = (
|
|
37
|
+
"velocity.aws.handlers.mixins.workflow_actions:WorkflowMixin",
|
|
38
|
+
"velocity.aws.handlers.mixins.workflow_actions:WorkflowQueueMixin",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def handle(self, action, tx, context, config):
|
|
42
|
+
from velocity.workflow import engine, outbox
|
|
43
|
+
|
|
44
|
+
if action == "sweep":
|
|
45
|
+
return {"swept": engine.sweep(tx), "drained": outbox.drain(tx, context)}
|
|
46
|
+
if action == "drain":
|
|
47
|
+
return {"drained": outbox.drain(tx, context)}
|
|
48
|
+
raise ValueError(f"workflow: unknown action {action!r}")
|
{velocity_python-0.3.1 → velocity_python-0.4.0}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
@@ -17,6 +17,9 @@ This package provides mixins for common Lambda handler patterns:
|
|
|
17
17
|
reportClientError / ErrorBoundary) to velocity.aws.failures
|
|
18
18
|
- DirtyPipelineMixin: the process-dirty-object queue consumer and the
|
|
19
19
|
sweep-dirty-rows action (velocity.aws.dirty_pipeline)
|
|
20
|
+
- WorkflowMixin / WorkflowQueueMixin: the workflow-* actions over dockets
|
|
21
|
+
(velocity.workflow): trays, the docket contract, acts, claims, flags,
|
|
22
|
+
notices; and the queue side (program jobs, outbox drain, timers)
|
|
20
23
|
- SchedulerMixin: run-schedules, the once-a-minute tick over sys_schedules
|
|
21
24
|
(velocity.aws.scheduler)
|
|
22
25
|
"""
|
|
@@ -30,6 +33,12 @@ from .session_control import SessionControlMixin
|
|
|
30
33
|
from .action_names import action_names
|
|
31
34
|
from .permissions_actions import PERMISSION_ACTIONS, PermissionsMixin
|
|
32
35
|
from .tiles_actions import TILE_ACTIONS, TilesMixin
|
|
36
|
+
from .workflow_actions import (
|
|
37
|
+
WORKFLOW_ACTIONS,
|
|
38
|
+
WORKFLOW_QUEUE_ACTIONS,
|
|
39
|
+
WorkflowMixin,
|
|
40
|
+
WorkflowQueueMixin,
|
|
41
|
+
)
|
|
33
42
|
from .client_errors import ClientErrorReporting, AnonymousClientErrorReporting
|
|
34
43
|
from velocity.aws.dirty_pipeline import DirtyPipelineMixin
|
|
35
44
|
from velocity.aws.scheduler import SchedulerMixin
|
|
@@ -47,6 +56,10 @@ __all__ = [
|
|
|
47
56
|
"PERMISSION_ACTIONS",
|
|
48
57
|
"TilesMixin",
|
|
49
58
|
"TILE_ACTIONS",
|
|
59
|
+
"WorkflowMixin",
|
|
60
|
+
"WorkflowQueueMixin",
|
|
61
|
+
"WORKFLOW_ACTIONS",
|
|
62
|
+
"WORKFLOW_QUEUE_ACTIONS",
|
|
50
63
|
"action_names",
|
|
51
64
|
"ClientErrorReporting",
|
|
52
65
|
"AnonymousClientErrorReporting",
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""The actions behind the workflow screens (``velocity.workflow``).
|
|
2
|
+
|
|
3
|
+
A site adds :class:`WorkflowMixin` to its Data handler and lists the actions
|
|
4
|
+
in ``client_actions`` (``client_actions = MINE | WORKFLOW_ACTIONS``)::
|
|
5
|
+
|
|
6
|
+
workflow-tray {view, station?, cursor?, limit?} -> {counts, rows, cursor?, as_of}
|
|
7
|
+
workflow-docket {docket} -> Docket
|
|
8
|
+
workflow-act {docket, action, inputs?, comment?, request_key}
|
|
9
|
+
-> {ok, docket} | 4xx {ok: false, reasons}
|
|
10
|
+
workflow-flag {docket, note?} | {docket, clear} -> {ok, docket}
|
|
11
|
+
workflow-claim {docket, position, release?} -> {ok, docket}
|
|
12
|
+
workflow-notices {unread_only?, cursor?, limit?} -> {unread, rows, cursor?}
|
|
13
|
+
workflow-notice-ack {notice} | {all: true} -> {ok, unread}
|
|
14
|
+
|
|
15
|
+
and :class:`WorkflowQueueMixin` to its queue handler (never reachable over
|
|
16
|
+
HTTP): ``workflow-program`` (a program station's job), ``workflow-drain``
|
|
17
|
+
(the outbox) and ``workflow-sweep`` (the timers).
|
|
18
|
+
|
|
19
|
+
**Authorization.** Every action is :func:`velocity.aws.authz.require` on a
|
|
20
|
+
named action (:mod:`velocity.workflow.access`) with the docket as a resource
|
|
21
|
+
whose relations come from the person's seats — the site's
|
|
22
|
+
``workflow_seats`` (default: :func:`velocity.workflow.register_seats`'s
|
|
23
|
+
hook), never from the request. An act or claim allowed only by
|
|
24
|
+
``workflow.oversee`` or ``workflow.act_any`` is a privileged use: authz
|
|
25
|
+
records it before it happens, and the engine then acts with ``override``.
|
|
26
|
+
|
|
27
|
+
**Refusals** are ``{ok: false, reasons: [Reason]}`` with HTTP 403 (not
|
|
28
|
+
yours) or 409 (the docket's state or a guard said no), or 400 for a
|
|
29
|
+
malformed request. A docket the person may not see is refused exactly like
|
|
30
|
+
one that does not exist.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import dataclasses
|
|
36
|
+
import logging
|
|
37
|
+
|
|
38
|
+
from velocity.aws import authz
|
|
39
|
+
from velocity.aws.handlers.exceptions import AccessError
|
|
40
|
+
from .action_names import action_names
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
FORBIDDEN = 403
|
|
45
|
+
CONFLICT = 409
|
|
46
|
+
BAD_REQUEST = 400
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _workflow():
|
|
50
|
+
# imported lazily: velocity.workflow pulls in the rules package
|
|
51
|
+
from velocity import workflow
|
|
52
|
+
|
|
53
|
+
return workflow
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _Refusal(Exception):
|
|
57
|
+
def __init__(self, status, reasons):
|
|
58
|
+
super().__init__(status)
|
|
59
|
+
self.status = status
|
|
60
|
+
self.reasons = reasons
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _reasons_status(reasons) -> int:
|
|
64
|
+
codes = {r.get("code") for r in reasons or ()}
|
|
65
|
+
return (
|
|
66
|
+
FORBIDDEN if codes & {"not_available", "not_yours", "forbidden"} else CONFLICT
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class WorkflowMixin:
|
|
71
|
+
"""See the module docstring."""
|
|
72
|
+
|
|
73
|
+
workflow_tray_limit = 50
|
|
74
|
+
|
|
75
|
+
# ── seams ──────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
def workflow_seats(self, tx, context, principal):
|
|
78
|
+
"""Where the signed-in person sits (override, or register a seats
|
|
79
|
+
hook). Seats without a ``person`` get the user row id."""
|
|
80
|
+
return _workflow().hooks.seats_for(tx, principal)
|
|
81
|
+
|
|
82
|
+
# ── helpers ────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
def _wf_principal(self, context):
|
|
85
|
+
principal = authz.principal_for(self, context)
|
|
86
|
+
if principal.user_sys_id is None:
|
|
87
|
+
raise AccessError(authz.REFUSED)
|
|
88
|
+
return principal
|
|
89
|
+
|
|
90
|
+
def _wf_seats(self, tx, context, principal):
|
|
91
|
+
seats = self.workflow_seats(tx, context, principal)
|
|
92
|
+
wf = _workflow()
|
|
93
|
+
if seats is None:
|
|
94
|
+
seats = wf.NO_SEATS
|
|
95
|
+
if not isinstance(seats, wf.Seats):
|
|
96
|
+
raise AccessError(authz.REFUSED)
|
|
97
|
+
if not seats.person:
|
|
98
|
+
seats = dataclasses.replace(seats, person=str(principal.user_sys_id))
|
|
99
|
+
return seats
|
|
100
|
+
|
|
101
|
+
def _wf_docket(self, tx, payload, key="docket"):
|
|
102
|
+
wf = _workflow()
|
|
103
|
+
try:
|
|
104
|
+
docket = wf.engine.docket_row(tx, (payload or {}).get(key))
|
|
105
|
+
except wf.WorkflowError:
|
|
106
|
+
# never say whether a docket the person can't see exists
|
|
107
|
+
raise AccessError(authz.REFUSED) from None
|
|
108
|
+
route = wf.engine._Ctx(tx).route(docket["route_id"])
|
|
109
|
+
return docket, route
|
|
110
|
+
|
|
111
|
+
def _wf_require(self, tx, context, action, docket, relations):
|
|
112
|
+
"""``authz.require`` on a docket-scoped action. Every refusal says the
|
|
113
|
+
same sentence (``authz.REFUSED``): not which permission was missing,
|
|
114
|
+
and not whether the docket exists."""
|
|
115
|
+
wf = _workflow()
|
|
116
|
+
try:
|
|
117
|
+
return authz.require(
|
|
118
|
+
self, tx, context, action, wf.access.resource(docket, relations)
|
|
119
|
+
)
|
|
120
|
+
except AccessError:
|
|
121
|
+
raise AccessError(authz.REFUSED) from None
|
|
122
|
+
|
|
123
|
+
def _wf_masquerading(self, principal) -> bool:
|
|
124
|
+
return principal.real_user is not None
|
|
125
|
+
|
|
126
|
+
def _wf_decider(self, context):
|
|
127
|
+
"""The viewer's ``may_view``: the site's authz decision on
|
|
128
|
+
``workflow.docket.view`` for one docket (a forbid rule hides it from
|
|
129
|
+
trays and links too). A privileged permit (``view_any``) is not
|
|
130
|
+
used here: listing is never a recorded use."""
|
|
131
|
+
wf = _workflow()
|
|
132
|
+
|
|
133
|
+
def decide(docket, relations):
|
|
134
|
+
decision = authz.decide(
|
|
135
|
+
self, wf.access.VIEW, wf.access.resource(docket, relations), context
|
|
136
|
+
)
|
|
137
|
+
return decision.allowed and not decision.audit
|
|
138
|
+
|
|
139
|
+
return decide
|
|
140
|
+
|
|
141
|
+
def _wf_allows(self, tx, context, *, record=True):
|
|
142
|
+
"""The actor's write decisions: ``authz.require`` on the named action
|
|
143
|
+
(``workflow.sheet.write`` / ``workflow.hold.release``) with the
|
|
144
|
+
docket's entity scope and the permission it needs, so scoped grants,
|
|
145
|
+
denials, forbids (and masquerade) apply. A refusal answers False."""
|
|
146
|
+
wf = _workflow()
|
|
147
|
+
|
|
148
|
+
def allows(action, permission, docket):
|
|
149
|
+
if not record:
|
|
150
|
+
# for display only (the action list): never a recorded use
|
|
151
|
+
decision = authz.decide(
|
|
152
|
+
self,
|
|
153
|
+
action,
|
|
154
|
+
wf.access.resource(docket, (), permission=permission),
|
|
155
|
+
context,
|
|
156
|
+
)
|
|
157
|
+
return decision.allowed and not decision.audit
|
|
158
|
+
try:
|
|
159
|
+
authz.require(
|
|
160
|
+
self,
|
|
161
|
+
tx,
|
|
162
|
+
context,
|
|
163
|
+
action,
|
|
164
|
+
wf.access.resource(docket, (), permission=permission),
|
|
165
|
+
)
|
|
166
|
+
except AccessError:
|
|
167
|
+
return False
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
return allows
|
|
171
|
+
|
|
172
|
+
def _wf_actor(self, seats, decision, principal, tx=None, context=None):
|
|
173
|
+
"""The actor a decision allows: ``override`` for a route owner or
|
|
174
|
+
``workflow.act_any`` (both recorded by authz), ``cross_entity`` only
|
|
175
|
+
for ``workflow.act_any``."""
|
|
176
|
+
wf = _workflow()
|
|
177
|
+
return wf.engine.Actor.person(
|
|
178
|
+
seats,
|
|
179
|
+
override=decision.rule in wf.access.OVERRIDE_RULES,
|
|
180
|
+
cross_entity=decision.rule == "workflow.act_any",
|
|
181
|
+
allows=self._wf_allows(tx, context) if tx is not None else None,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _wf_viewer(self, tx, context, docket, route, principal, seats):
|
|
185
|
+
"""Require ``workflow.docket.view`` and build the viewer; whether an
|
|
186
|
+
override would let them act is asked (not recorded) for the action
|
|
187
|
+
list."""
|
|
188
|
+
wf = _workflow()
|
|
189
|
+
relations = wf.access.view_relations(tx, docket, route, seats)
|
|
190
|
+
self._wf_require(tx, context, wf.access.VIEW, docket, relations)
|
|
191
|
+
probe = authz.decide(
|
|
192
|
+
self,
|
|
193
|
+
wf.access.ACT,
|
|
194
|
+
wf.access.resource(docket, relations & {wf.access.OVERSEES}),
|
|
195
|
+
context,
|
|
196
|
+
)
|
|
197
|
+
override = probe.allowed and probe.rule in wf.access.OVERRIDE_RULES
|
|
198
|
+
return wf.contract.Viewer(
|
|
199
|
+
seats=seats,
|
|
200
|
+
principal=principal,
|
|
201
|
+
act_override=override,
|
|
202
|
+
cross_entity=override and probe.rule == "workflow.act_any",
|
|
203
|
+
view_decider=self._wf_decider(context),
|
|
204
|
+
allows=self._wf_allows(tx, context, record=False),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def _wf_refuse(self, context, status, reasons):
|
|
208
|
+
context.response().set_status(status)
|
|
209
|
+
context.response().set_body({"ok": False, "reasons": list(reasons)})
|
|
210
|
+
|
|
211
|
+
def _wf_after_change(self, tx, context, docket_id):
|
|
212
|
+
"""Ask for the outbox to be drained once this transaction commits."""
|
|
213
|
+
wf = _workflow()
|
|
214
|
+
if not wf.outbox.has_pending(tx, docket_id):
|
|
215
|
+
return
|
|
216
|
+
try:
|
|
217
|
+
context.enqueue(wf.outbox.DRAIN_ACTION, {}, suppress_job_activity=True)
|
|
218
|
+
except Exception:
|
|
219
|
+
# the scheduled drain picks it up within a minute
|
|
220
|
+
logger.warning("workflow drain enqueue failed", exc_info=True)
|
|
221
|
+
|
|
222
|
+
def _wf_guarded(self, tx, context, work):
|
|
223
|
+
"""Run ``work`` in a savepoint; a refusal rolls back everything it
|
|
224
|
+
wrote and answers ``{ok: false, reasons}`` with its status."""
|
|
225
|
+
wf = _workflow()
|
|
226
|
+
try:
|
|
227
|
+
with tx.savepoint():
|
|
228
|
+
return work()
|
|
229
|
+
except AccessError:
|
|
230
|
+
self._wf_refuse(
|
|
231
|
+
context, FORBIDDEN, [{"code": "forbidden", "text": authz.REFUSED}]
|
|
232
|
+
)
|
|
233
|
+
except _Refusal as exc:
|
|
234
|
+
self._wf_refuse(context, exc.status, exc.reasons)
|
|
235
|
+
except wf.WorkflowRefused as exc:
|
|
236
|
+
status = exc.status if exc.status in (400, 403, 409) else CONFLICT
|
|
237
|
+
self._wf_refuse(context, status, exc.reasons)
|
|
238
|
+
except wf.WorkflowError as exc:
|
|
239
|
+
self._wf_refuse(
|
|
240
|
+
context, BAD_REQUEST, [{"code": "invalid", "text": str(exc)}]
|
|
241
|
+
)
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
def _wf_docket_body(self, tx, context, docket_id, principal, seats):
|
|
245
|
+
wf = _workflow()
|
|
246
|
+
docket, route = self._wf_docket(tx, {"docket": docket_id})
|
|
247
|
+
viewer = self._wf_viewer(tx, context, docket, route, principal, seats)
|
|
248
|
+
# under masquerade nothing is written, not even "seen"
|
|
249
|
+
return wf.contract.build_docket(
|
|
250
|
+
tx,
|
|
251
|
+
docket["sys_id"],
|
|
252
|
+
viewer,
|
|
253
|
+
record_seen=not self._wf_masquerading(principal),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# ── reading ────────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
def OnActionWorkflowTray(self, tx, context):
|
|
259
|
+
wf = _workflow()
|
|
260
|
+
payload = context.payload() or {}
|
|
261
|
+
principal = self._wf_principal(context)
|
|
262
|
+
authz.require(self, tx, context, wf.access.TRAY)
|
|
263
|
+
seats = self._wf_seats(tx, context, principal)
|
|
264
|
+
viewer = wf.contract.Viewer(
|
|
265
|
+
seats=seats, principal=principal, view_decider=self._wf_decider(context)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def work():
|
|
269
|
+
body = wf.contract.tray(
|
|
270
|
+
tx,
|
|
271
|
+
viewer,
|
|
272
|
+
payload.get("view") or "mine",
|
|
273
|
+
station=payload.get("station"),
|
|
274
|
+
cursor=payload.get("cursor"),
|
|
275
|
+
limit=payload.get("limit") or self.workflow_tray_limit,
|
|
276
|
+
counts=payload.get("counts") is not False,
|
|
277
|
+
)
|
|
278
|
+
context.response().set_body(body)
|
|
279
|
+
|
|
280
|
+
self._wf_guarded(tx, context, work)
|
|
281
|
+
|
|
282
|
+
def OnActionWorkflowDocket(self, tx, context):
|
|
283
|
+
payload = context.payload() or {}
|
|
284
|
+
principal = self._wf_principal(context)
|
|
285
|
+
seats = self._wf_seats(tx, context, principal)
|
|
286
|
+
body = self._wf_docket_body(
|
|
287
|
+
tx, context, payload.get("docket"), principal, seats
|
|
288
|
+
)
|
|
289
|
+
context.response().set_body(body)
|
|
290
|
+
|
|
291
|
+
# ── acting ─────────────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
def OnActionWorkflowAct(self, tx, context):
|
|
294
|
+
wf = _workflow()
|
|
295
|
+
payload = context.payload() or {}
|
|
296
|
+
principal = self._wf_principal(context)
|
|
297
|
+
seats = self._wf_seats(tx, context, principal)
|
|
298
|
+
|
|
299
|
+
def work():
|
|
300
|
+
action = payload.get("action")
|
|
301
|
+
# a malformed action id is a bad request, before anything is
|
|
302
|
+
# looked up (it names no target to authorize)
|
|
303
|
+
wf.engine.parse_action(action)
|
|
304
|
+
docket, route = self._wf_docket(tx, payload)
|
|
305
|
+
request_key = payload.get("request_key")
|
|
306
|
+
if isinstance(request_key, str) and wf.engine.already_acted(
|
|
307
|
+
tx, docket["sys_id"], wf.engine.Actor.person(seats), request_key
|
|
308
|
+
):
|
|
309
|
+
# a retry of this person's own request that succeeded: answer
|
|
310
|
+
# with the docket as it is (seeing it is still checked)
|
|
311
|
+
body = self._wf_docket_body(
|
|
312
|
+
tx, context, docket["sys_id"], principal, seats
|
|
313
|
+
)
|
|
314
|
+
context.response().set_body({"ok": True, "docket": body})
|
|
315
|
+
return
|
|
316
|
+
relations = wf.access.act_relations(tx, docket, route, seats, action)
|
|
317
|
+
decision = self._wf_require(tx, context, wf.access.ACT, docket, relations)
|
|
318
|
+
# the rest of the payload is checked only once the act is allowed
|
|
319
|
+
if not isinstance(request_key, str) or not request_key.strip():
|
|
320
|
+
raise _Refusal(
|
|
321
|
+
BAD_REQUEST,
|
|
322
|
+
[{"code": "request_key", "text": "A request key is required."}],
|
|
323
|
+
)
|
|
324
|
+
inputs = payload.get("inputs")
|
|
325
|
+
if inputs is not None and not isinstance(inputs, dict):
|
|
326
|
+
raise _Refusal(
|
|
327
|
+
BAD_REQUEST,
|
|
328
|
+
[{"code": "inputs", "text": "inputs must be an object."}],
|
|
329
|
+
)
|
|
330
|
+
comment = payload.get("comment")
|
|
331
|
+
if comment is not None and not isinstance(comment, str):
|
|
332
|
+
raise _Refusal(
|
|
333
|
+
BAD_REQUEST, [{"code": "comment", "text": "comment must be text."}]
|
|
334
|
+
)
|
|
335
|
+
result = wf.engine.act(
|
|
336
|
+
tx,
|
|
337
|
+
docket["sys_id"],
|
|
338
|
+
action,
|
|
339
|
+
inputs,
|
|
340
|
+
self._wf_actor(seats, decision, principal, tx, context),
|
|
341
|
+
request_key,
|
|
342
|
+
comment=comment,
|
|
343
|
+
)
|
|
344
|
+
if not result.ok:
|
|
345
|
+
raise _Refusal(_reasons_status(result.reasons), result.reasons)
|
|
346
|
+
self._wf_after_change(tx, context, docket["sys_id"])
|
|
347
|
+
body = self._wf_docket_body(tx, context, docket["sys_id"], principal, seats)
|
|
348
|
+
context.response().set_body({"ok": True, "docket": body})
|
|
349
|
+
|
|
350
|
+
self._wf_guarded(tx, context, work)
|
|
351
|
+
|
|
352
|
+
def OnActionWorkflowClaim(self, tx, context):
|
|
353
|
+
wf = _workflow()
|
|
354
|
+
payload = context.payload() or {}
|
|
355
|
+
principal = self._wf_principal(context)
|
|
356
|
+
seats = self._wf_seats(tx, context, principal)
|
|
357
|
+
|
|
358
|
+
def work():
|
|
359
|
+
docket, route = self._wf_docket(tx, payload)
|
|
360
|
+
try:
|
|
361
|
+
position = wf.engine._position(tx, payload.get("position"))
|
|
362
|
+
except wf.WorkflowError:
|
|
363
|
+
raise AccessError(authz.REFUSED) from None
|
|
364
|
+
if position["docket_id"] != docket["sys_id"]:
|
|
365
|
+
raise AccessError(authz.REFUSED)
|
|
366
|
+
relations = wf.access.act_relations(
|
|
367
|
+
tx, docket, route, seats, position=position
|
|
368
|
+
)
|
|
369
|
+
decision = self._wf_require(tx, context, wf.access.CLAIM, docket, relations)
|
|
370
|
+
wf.engine.claim(
|
|
371
|
+
tx,
|
|
372
|
+
docket["sys_id"],
|
|
373
|
+
position["sys_id"],
|
|
374
|
+
self._wf_actor(seats, decision, principal, tx, context),
|
|
375
|
+
release=payload.get("release") is True,
|
|
376
|
+
)
|
|
377
|
+
self._wf_after_change(tx, context, docket["sys_id"])
|
|
378
|
+
body = self._wf_docket_body(tx, context, docket["sys_id"], principal, seats)
|
|
379
|
+
context.response().set_body({"ok": True, "docket": body})
|
|
380
|
+
|
|
381
|
+
self._wf_guarded(tx, context, work)
|
|
382
|
+
|
|
383
|
+
def OnActionWorkflowFlag(self, tx, context):
|
|
384
|
+
wf = _workflow()
|
|
385
|
+
payload = context.payload() or {}
|
|
386
|
+
principal = self._wf_principal(context)
|
|
387
|
+
seats = self._wf_seats(tx, context, principal)
|
|
388
|
+
|
|
389
|
+
def work():
|
|
390
|
+
docket, route = self._wf_docket(tx, payload)
|
|
391
|
+
relations = wf.access.view_relations(tx, docket, route, seats)
|
|
392
|
+
self._wf_require(tx, context, wf.access.FLAG, docket, relations)
|
|
393
|
+
actor = wf.engine.Actor.person(seats)
|
|
394
|
+
if payload.get("clear") is not None:
|
|
395
|
+
wf.engine.clear_flag(tx, docket["sys_id"], payload.get("clear"), actor)
|
|
396
|
+
else:
|
|
397
|
+
note = payload.get("note")
|
|
398
|
+
if note is not None and not isinstance(note, str):
|
|
399
|
+
raise _Refusal(
|
|
400
|
+
BAD_REQUEST, [{"code": "note", "text": "note must be text."}]
|
|
401
|
+
)
|
|
402
|
+
wf.engine.flag(tx, docket["sys_id"], actor, note)
|
|
403
|
+
body = self._wf_docket_body(tx, context, docket["sys_id"], principal, seats)
|
|
404
|
+
context.response().set_body({"ok": True, "docket": body})
|
|
405
|
+
|
|
406
|
+
self._wf_guarded(tx, context, work)
|
|
407
|
+
|
|
408
|
+
# ── notices ────────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
def OnActionWorkflowNotices(self, tx, context):
|
|
411
|
+
wf = _workflow()
|
|
412
|
+
payload = context.payload() or {}
|
|
413
|
+
principal = self._wf_principal(context)
|
|
414
|
+
authz.require(self, tx, context, wf.access.NOTICES)
|
|
415
|
+
seats = self._wf_seats(tx, context, principal)
|
|
416
|
+
|
|
417
|
+
def work():
|
|
418
|
+
body = wf.notices.list_for(
|
|
419
|
+
tx,
|
|
420
|
+
seats.person,
|
|
421
|
+
unread_only=bool(payload.get("unread_only")),
|
|
422
|
+
cursor=payload.get("cursor"),
|
|
423
|
+
limit=payload.get("limit") or 50,
|
|
424
|
+
)
|
|
425
|
+
context.response().set_body(body)
|
|
426
|
+
|
|
427
|
+
self._wf_guarded(tx, context, work)
|
|
428
|
+
|
|
429
|
+
def OnActionWorkflowNoticeAck(self, tx, context):
|
|
430
|
+
wf = _workflow()
|
|
431
|
+
payload = context.payload() or {}
|
|
432
|
+
principal = self._wf_principal(context)
|
|
433
|
+
authz.require(self, tx, context, wf.access.NOTICE_ACK)
|
|
434
|
+
seats = self._wf_seats(tx, context, principal)
|
|
435
|
+
|
|
436
|
+
def work():
|
|
437
|
+
unread = wf.notices.ack(
|
|
438
|
+
tx,
|
|
439
|
+
seats.person,
|
|
440
|
+
notice_id=payload.get("notice"),
|
|
441
|
+
all=payload.get("all") is True,
|
|
442
|
+
)
|
|
443
|
+
context.response().set_body({"ok": True, "unread": unread})
|
|
444
|
+
|
|
445
|
+
self._wf_guarded(tx, context, work)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class WorkflowQueueMixin:
|
|
449
|
+
"""Queue-handler actions: program jobs, the outbox drain, the timers.
|
|
450
|
+
Refused over HTTP (queue records and scheduled ticks only)."""
|
|
451
|
+
|
|
452
|
+
def OnActionWorkflowProgram(self, tx, context):
|
|
453
|
+
from velocity.aws.dirty_pipeline import refuse_http
|
|
454
|
+
|
|
455
|
+
refuse_http(self, "workflow-program")
|
|
456
|
+
wf = _workflow()
|
|
457
|
+
payload = context.payload() or {}
|
|
458
|
+
result = wf.engine.run_program(
|
|
459
|
+
tx, payload.get("position"), payload.get("attempt")
|
|
460
|
+
)
|
|
461
|
+
drained = wf.outbox.drain(tx, context)
|
|
462
|
+
context.response().set_body({"result": result, "drained": drained})
|
|
463
|
+
|
|
464
|
+
def OnActionWorkflowDrain(self, tx, context):
|
|
465
|
+
from velocity.aws.dirty_pipeline import refuse_http
|
|
466
|
+
|
|
467
|
+
refuse_http(self, "workflow-drain")
|
|
468
|
+
context.response().set_body({"drained": _workflow().outbox.drain(tx, context)})
|
|
469
|
+
|
|
470
|
+
def OnActionWorkflowSweep(self, tx, context):
|
|
471
|
+
from velocity.aws.dirty_pipeline import refuse_http
|
|
472
|
+
|
|
473
|
+
refuse_http(self, "workflow-sweep")
|
|
474
|
+
wf = _workflow()
|
|
475
|
+
swept = wf.engine.sweep(tx)
|
|
476
|
+
context.response().set_body(
|
|
477
|
+
{"swept": swept, "drained": wf.outbox.drain(tx, context)}
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# every client action WorkflowMixin serves: ``client_actions = MINE | WORKFLOW_ACTIONS``
|
|
482
|
+
WORKFLOW_ACTIONS = action_names(WorkflowMixin)
|
|
483
|
+
# the queue handler's (never list these in client_actions)
|
|
484
|
+
WORKFLOW_QUEUE_ACTIONS = action_names(WorkflowQueueMixin)
|
|
485
|
+
|
|
486
|
+
__all__ = [
|
|
487
|
+
"WORKFLOW_ACTIONS",
|
|
488
|
+
"WORKFLOW_QUEUE_ACTIONS",
|
|
489
|
+
"WorkflowMixin",
|
|
490
|
+
"WorkflowQueueMixin",
|
|
491
|
+
]
|
|
@@ -29,6 +29,9 @@ Groups:
|
|
|
29
29
|
* ``tiles`` — tiles and who sees them (:mod:`velocity.aws.tiles`).
|
|
30
30
|
* ``scheduler`` — ``sys_schedules`` (:mod:`velocity.aws.scheduler`).
|
|
31
31
|
* ``rules`` — rules, rule tests and the decision log (:mod:`velocity.rules.storage`).
|
|
32
|
+
* ``workflow`` — routes, dockets, marks, notices (:mod:`velocity.workflow.schema`);
|
|
33
|
+
run :func:`velocity.workflow.ensure_schema` too for the append-only marks
|
|
34
|
+
and the possession constraint.
|
|
32
35
|
|
|
33
36
|
Each module's ``SCHEMA`` maps a table name to ``Table.ensure_schema`` keyword
|
|
34
37
|
arguments (``columns`` plus optional ``indexes`` / ``unique_indexes``). Two
|
|
@@ -67,6 +70,8 @@ SCHEMA_SOURCES: Dict[str, Tuple[str, ...]] = {
|
|
|
67
70
|
"scheduler": ("velocity.aws.scheduler",),
|
|
68
71
|
# data-driven rules (velocity.rules); opt-in, for apps that evaluate them
|
|
69
72
|
"rules": ("velocity.rules.storage",),
|
|
73
|
+
# dockets on routes (velocity.workflow); opt-in, for apps that use them
|
|
74
|
+
"workflow": ("velocity.workflow.schema",),
|
|
70
75
|
"payment": ("velocity.payment.schema",),
|
|
71
76
|
}
|
|
72
77
|
GROUPS = tuple(SCHEMA_SOURCES)
|