velocity-python 0.1.116__tar.gz → 0.1.118__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.116/src/velocity_python.egg-info → velocity_python-0.1.118}/PKG-INFO +1 -1
- {velocity_python-0.1.116 → velocity_python-0.1.118}/pyproject.toml +1 -1
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/__init__.py +1 -1
- velocity_python-0.1.118/src/velocity/aws/facets.py +342 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/data_service.py +5 -1
- {velocity_python-0.1.116 → velocity_python-0.1.118/src/velocity_python.egg-info}/PKG-INFO +1 -1
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- velocity_python-0.1.118/tests/test_facets.py +394 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/LICENSE +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/README.md +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/setup.cfg +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/blobs.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/base.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/dispatch.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/infra.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/jobs.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/provisioning.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/retention.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/security.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/capabilities/single_session.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/config.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/operations.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/policies.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/reconcile.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/deploy/targets.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/email.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/feature_flags.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/job_lifecycle.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/job_results.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/ops.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/sms.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_email.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/tests/test_sms.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/lazy_engine.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_mail_send.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/core/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/core/capabilities.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/core/store.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/core/where.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/exceptions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/base/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/memory/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/nosql/servers/redis/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_access_error_403.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_blob_store.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_capabilities.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_capability_catalog.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_cognito_user_cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_context_flags_loader.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_data_service_query_cap.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_data_service_renumber.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_data_service_save_as.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_delete_hook_fk_blocked.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_feature_flags.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_job_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_job_lifecycle.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_job_results.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_lambda_handler_user_cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_lazy_engine.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_dynamodb.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_memory.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_mongodb.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_redis.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_translation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_nosql_where.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_observability.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_on_commit.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_ops.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_query_missing_table_unlocked.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_read_cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_response.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_result_returning.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_security_capability.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_session_control.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_single_session_capability.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_table_find_cache.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_table_purge.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.116 → velocity_python-0.1.118}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Structured grid facets: the client says WHAT it selected, this builds the SQL.
|
|
2
|
+
|
|
3
|
+
A server-side grid used to filter by sending SQL it had built in the browser
|
|
4
|
+
(``params.where``). Facets — the pick-lists and write-in boxes on a grid's
|
|
5
|
+
toolbar — are the first filter to move off that path: the client sends the
|
|
6
|
+
selection as data (``params.facets``) and :func:`build_where` turns it into a
|
|
7
|
+
parameterized predicate here, with the field names whitelisted as plain
|
|
8
|
+
identifiers and every value bound as a parameter. ``params.where`` keeps
|
|
9
|
+
working unchanged alongside it (the toolbar search and the column filters
|
|
10
|
+
still arrive that way); the two are AND'ed by :func:`apply_to_params`.
|
|
11
|
+
|
|
12
|
+
The wire shape is one list under ``params.facets``::
|
|
13
|
+
|
|
14
|
+
[
|
|
15
|
+
{"field": "has_attachments", "kind": "values", "values": ["true"]},
|
|
16
|
+
{"field": "label_ids", "kind": "contains", "values": ["INBOX", "SENT"]},
|
|
17
|
+
{"field": "deleted_at", "kind": "presence", "value": "present"},
|
|
18
|
+
{"field": "internal_date", "kind": "since", "value": "30d"},
|
|
19
|
+
{"field": "from_domain", "kind": "text", "value": "example.com",
|
|
20
|
+
"match": "contains"},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
Facets AND together; the values of one facet OR together. Kinds:
|
|
24
|
+
|
|
25
|
+
``values`` ``field IN (…)`` — a pick-list over a column's own values (or a
|
|
26
|
+
lookup table's ids). The default when ``kind`` is missing.
|
|
27
|
+
``contains`` membership in a JSON array column: ``field @> '["x"]'::jsonb``,
|
|
28
|
+
OR'd per value. For ``jsonb`` columns only.
|
|
29
|
+
``presence`` ``value`` is ``present`` or ``absent``: NULL *or empty string*
|
|
30
|
+
counts as absent, whatever the column type
|
|
31
|
+
(``NULLIF(field::text, '')``).
|
|
32
|
+
``since`` a period ending now: ``today``, ``Nd`` (``7d``, ``30d`` …, up to
|
|
33
|
+
3660 days), ``year`` (this calendar year). Evaluated in the
|
|
34
|
+
database's session time zone.
|
|
35
|
+
``text`` a write-in, case-insensitive, against ``field::text``:
|
|
36
|
+
``match`` is ``contains`` (default), ``equals`` or ``starts``.
|
|
37
|
+
The user's ``%`` and ``_`` are literal. With ``fields`` (a list
|
|
38
|
+
of columns) the term is matched against each, OR'd — one box
|
|
39
|
+
that searches subject *or* snippet.
|
|
40
|
+
|
|
41
|
+
Anything malformed — an unknown kind, a field that is not a bare identifier,
|
|
42
|
+
too many or too long values — raises :class:`FacetError` rather than being
|
|
43
|
+
dropped: a silently ignored facet would show rows as filtered when they are
|
|
44
|
+
not. Strings are bound as parameters and PostgreSQL infers their type from the
|
|
45
|
+
column (a ``'3'`` parameter compares to a bigint, ``'true'`` to a boolean), so
|
|
46
|
+
no column types are needed here.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import re
|
|
52
|
+
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
53
|
+
|
|
54
|
+
# The SQL emitted here is PostgreSQL's (jsonb ``@>``, ``make_interval``,
|
|
55
|
+
# ``ILIKE``), and importing the postgres SQL module is what fills in
|
|
56
|
+
# TableHelper's reserved-word list, so a column called ``order`` is quoted.
|
|
57
|
+
import velocity.db.servers.postgres.sql # noqa: F401
|
|
58
|
+
from velocity.db.servers.tablehelper import TableHelper
|
|
59
|
+
|
|
60
|
+
PARAM_KEY = "facets"
|
|
61
|
+
|
|
62
|
+
KINDS = ("values", "contains", "presence", "since", "text")
|
|
63
|
+
MATCHES = ("contains", "equals", "starts")
|
|
64
|
+
PRESENCE = ("present", "absent")
|
|
65
|
+
|
|
66
|
+
MAX_FACETS = 40
|
|
67
|
+
MAX_FIELDS = 10
|
|
68
|
+
MAX_VALUES = 200
|
|
69
|
+
MAX_VALUE_CHARS = 500
|
|
70
|
+
MAX_DAYS = 3660
|
|
71
|
+
|
|
72
|
+
_IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
73
|
+
_DAYS = re.compile(r"^(\d{1,4})d$")
|
|
74
|
+
_LIKE_SPECIAL = re.compile(r"([\\%_])")
|
|
75
|
+
|
|
76
|
+
Predicate = Tuple[str, Tuple[Any, ...]]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class FacetError(ValueError):
|
|
80
|
+
"""A facet the client sent cannot be turned into SQL."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── validation ────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _field(entry: Dict[str, Any]) -> str:
|
|
87
|
+
field = entry.get("field")
|
|
88
|
+
if not isinstance(field, str) or not _IDENTIFIER.match(field):
|
|
89
|
+
raise FacetError(f"facet field must be a column name, got {field!r}")
|
|
90
|
+
return field
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _scalar(value: Any, field: str) -> str:
|
|
94
|
+
if isinstance(value, bool):
|
|
95
|
+
return "true" if value else "false"
|
|
96
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
97
|
+
return str(value)
|
|
98
|
+
if not isinstance(value, str):
|
|
99
|
+
raise FacetError(f"facet {field}: values must be strings, got {value!r}")
|
|
100
|
+
if len(value) > MAX_VALUE_CHARS:
|
|
101
|
+
raise FacetError(
|
|
102
|
+
f"facet {field}: a value is longer than {MAX_VALUE_CHARS} characters"
|
|
103
|
+
)
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _values(entry: Dict[str, Any], field: str) -> List[str]:
|
|
108
|
+
raw = entry.get("values")
|
|
109
|
+
if raw is None and "value" in entry:
|
|
110
|
+
raw = [entry["value"]]
|
|
111
|
+
if not isinstance(raw, (list, tuple)):
|
|
112
|
+
raise FacetError(f"facet {field}: 'values' must be a list")
|
|
113
|
+
if len(raw) > MAX_VALUES:
|
|
114
|
+
raise FacetError(f"facet {field}: more than {MAX_VALUES} values")
|
|
115
|
+
seen, out = set(), []
|
|
116
|
+
for item in raw:
|
|
117
|
+
value = _scalar(item, field)
|
|
118
|
+
if value == "" or value in seen:
|
|
119
|
+
continue
|
|
120
|
+
seen.add(value)
|
|
121
|
+
out.append(value)
|
|
122
|
+
return out
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _one(entry: Dict[str, Any], field: str) -> str:
|
|
126
|
+
raw = entry.get("value")
|
|
127
|
+
if raw is None:
|
|
128
|
+
values = entry.get("values")
|
|
129
|
+
raw = values[0] if isinstance(values, (list, tuple)) and values else None
|
|
130
|
+
if raw is None:
|
|
131
|
+
return ""
|
|
132
|
+
return _scalar(raw, field).strip()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ── one facet -> one predicate ────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _kind_values(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
|
|
139
|
+
values = _values(entry, field)
|
|
140
|
+
if not values:
|
|
141
|
+
return None
|
|
142
|
+
if len(values) == 1:
|
|
143
|
+
return f"{column} = %s", (values[0],)
|
|
144
|
+
return f"{column} IN ({', '.join(['%s'] * len(values))})", tuple(values)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _kind_contains(
|
|
148
|
+
column: str, entry: Dict[str, Any], field: str
|
|
149
|
+
) -> Optional[Predicate]:
|
|
150
|
+
import json
|
|
151
|
+
|
|
152
|
+
values = _values(entry, field)
|
|
153
|
+
if not values:
|
|
154
|
+
return None
|
|
155
|
+
parts = [f"{column} @> %s::jsonb" for _ in values]
|
|
156
|
+
params = tuple(json.dumps([value]) for value in values)
|
|
157
|
+
return f"({' OR '.join(parts)})", params
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _kind_presence(
|
|
161
|
+
column: str, entry: Dict[str, Any], field: str
|
|
162
|
+
) -> Optional[Predicate]:
|
|
163
|
+
value = _one(entry, field).lower()
|
|
164
|
+
if not value:
|
|
165
|
+
return None
|
|
166
|
+
if value not in PRESENCE:
|
|
167
|
+
raise FacetError(f"facet {field}: presence must be one of {PRESENCE}")
|
|
168
|
+
test = "IS NOT NULL" if value == "present" else "IS NULL"
|
|
169
|
+
return f"NULLIF({column}::text, '') {test}", ()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _kind_since(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
|
|
173
|
+
value = _one(entry, field).lower()
|
|
174
|
+
if not value:
|
|
175
|
+
return None
|
|
176
|
+
if value == "today":
|
|
177
|
+
return f"{column} >= date_trunc('day', now())", ()
|
|
178
|
+
if value == "year":
|
|
179
|
+
return f"{column} >= date_trunc('year', now())", ()
|
|
180
|
+
match = _DAYS.match(value)
|
|
181
|
+
if not match:
|
|
182
|
+
raise FacetError(f"facet {field}: period must be today, <N>d or year")
|
|
183
|
+
days = int(match.group(1))
|
|
184
|
+
if not 1 <= days <= MAX_DAYS:
|
|
185
|
+
raise FacetError(f"facet {field}: period must be 1 to {MAX_DAYS} days")
|
|
186
|
+
return f"{column} >= now() - make_interval(days => %s)", (days,)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _columns(entry: Dict[str, Any], column: str, field: str) -> List[str]:
|
|
190
|
+
"""The column(s) a text facet matches: ``fields`` when given, else the field."""
|
|
191
|
+
fields = entry.get("fields")
|
|
192
|
+
if fields in (None, "", []):
|
|
193
|
+
return [column]
|
|
194
|
+
if not isinstance(fields, (list, tuple)):
|
|
195
|
+
raise FacetError(f"facet {field}: 'fields' must be a list of column names")
|
|
196
|
+
if len(fields) > MAX_FIELDS:
|
|
197
|
+
raise FacetError(f"facet {field}: more than {MAX_FIELDS} fields")
|
|
198
|
+
out = []
|
|
199
|
+
for name in fields:
|
|
200
|
+
if not isinstance(name, str) or not _IDENTIFIER.match(name):
|
|
201
|
+
raise FacetError(
|
|
202
|
+
f"facet {field}: fields must be column names, got {name!r}"
|
|
203
|
+
)
|
|
204
|
+
quoted = TableHelper.quote(name)
|
|
205
|
+
if quoted not in out:
|
|
206
|
+
out.append(quoted)
|
|
207
|
+
return out
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _kind_text(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
|
|
211
|
+
value = _one(entry, field)
|
|
212
|
+
if not value:
|
|
213
|
+
return None
|
|
214
|
+
match = str(entry.get("match") or "contains").lower()
|
|
215
|
+
if match not in MATCHES:
|
|
216
|
+
raise FacetError(f"facet {field}: match must be one of {MATCHES}")
|
|
217
|
+
escaped = _LIKE_SPECIAL.sub(r"\\\1", value)
|
|
218
|
+
pattern = {
|
|
219
|
+
"contains": f"%{escaped}%",
|
|
220
|
+
"starts": f"{escaped}%",
|
|
221
|
+
"equals": escaped,
|
|
222
|
+
}[match]
|
|
223
|
+
columns = _columns(entry, column, field)
|
|
224
|
+
if len(columns) == 1:
|
|
225
|
+
return f"{columns[0]}::text ILIKE %s", (pattern,)
|
|
226
|
+
parts = " OR ".join(f"{name}::text ILIKE %s" for name in columns)
|
|
227
|
+
return f"({parts})", tuple(pattern for _ in columns)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
_BUILDERS = {
|
|
231
|
+
"values": _kind_values,
|
|
232
|
+
"contains": _kind_contains,
|
|
233
|
+
"presence": _kind_presence,
|
|
234
|
+
"since": _kind_since,
|
|
235
|
+
"text": _kind_text,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def predicate(entry: Any) -> Optional[Predicate]:
|
|
240
|
+
"""``(sql, params)`` for one facet entry, or None when it selects nothing."""
|
|
241
|
+
if not isinstance(entry, dict):
|
|
242
|
+
raise FacetError(f"a facet must be an object, got {type(entry).__name__}")
|
|
243
|
+
field = _field(entry)
|
|
244
|
+
kind = str(entry.get("kind") or "values").lower()
|
|
245
|
+
if kind not in _BUILDERS:
|
|
246
|
+
raise FacetError(f"facet {field}: unknown kind {kind!r}")
|
|
247
|
+
column = TableHelper.quote(field)
|
|
248
|
+
return _BUILDERS[kind](column, entry, field)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def build_where(facets: Any) -> Optional[Predicate]:
|
|
252
|
+
"""``(sql, params)`` AND-ing every facet, or None when none selects anything."""
|
|
253
|
+
if facets in (None, "", [], ()):
|
|
254
|
+
return None
|
|
255
|
+
if not isinstance(facets, (list, tuple)):
|
|
256
|
+
raise FacetError("'facets' must be a list")
|
|
257
|
+
if len(facets) > MAX_FACETS:
|
|
258
|
+
raise FacetError(f"more than {MAX_FACETS} facets")
|
|
259
|
+
parts: List[str] = []
|
|
260
|
+
params: List[Any] = []
|
|
261
|
+
for entry in facets:
|
|
262
|
+
built = predicate(entry)
|
|
263
|
+
if built is None:
|
|
264
|
+
continue
|
|
265
|
+
sql, values = built
|
|
266
|
+
parts.append(sql)
|
|
267
|
+
params.extend(values)
|
|
268
|
+
if not parts:
|
|
269
|
+
return None
|
|
270
|
+
return " AND ".join(parts), tuple(params)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ── folding into a query's params ─────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _existing_predicates(tx, table: str, where: Any) -> List[Predicate]:
|
|
277
|
+
"""The caller's ``where`` as ``(sql, params)`` predicates, whatever its shape."""
|
|
278
|
+
if where in (None, "", {}, [], ()):
|
|
279
|
+
return []
|
|
280
|
+
if isinstance(where, str):
|
|
281
|
+
clause = where.strip()
|
|
282
|
+
if clause.upper().startswith("WHERE "):
|
|
283
|
+
clause = clause[6:].strip()
|
|
284
|
+
if not clause:
|
|
285
|
+
return []
|
|
286
|
+
# The string travelled without parameters, so its literal ``%`` (an
|
|
287
|
+
# ``ilike '%x%'`` from the toolbar search) never met the driver's
|
|
288
|
+
# placeholder parser. Joined to a parameterized predicate it does:
|
|
289
|
+
# every literal ``%`` must become ``%%`` (found on demo, 2026-09-21).
|
|
290
|
+
return [(f"({clause.replace('%', '%%')})", ())]
|
|
291
|
+
if isinstance(where, dict):
|
|
292
|
+
helper = TableHelper(tx, table)
|
|
293
|
+
return [
|
|
294
|
+
(
|
|
295
|
+
sql,
|
|
296
|
+
(
|
|
297
|
+
()
|
|
298
|
+
if value is None
|
|
299
|
+
else (value if isinstance(value, tuple) else (value,))
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
for sql, value in (
|
|
303
|
+
helper.make_predicate(key, val) for key, val in where.items()
|
|
304
|
+
)
|
|
305
|
+
]
|
|
306
|
+
if isinstance(where, (list, tuple)):
|
|
307
|
+
out: List[Predicate] = []
|
|
308
|
+
for item in where:
|
|
309
|
+
if not isinstance(item, (list, tuple)) or len(item) != 2:
|
|
310
|
+
raise FacetError(f"where predicate must be (sql, params), got {item!r}")
|
|
311
|
+
sql, value = item
|
|
312
|
+
out.append(
|
|
313
|
+
(
|
|
314
|
+
sql,
|
|
315
|
+
(
|
|
316
|
+
()
|
|
317
|
+
if value is None
|
|
318
|
+
else (value if isinstance(value, tuple) else (value,))
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
return out
|
|
323
|
+
raise FacetError(f"unsupported where shape: {type(where).__name__}")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def apply_to_params(tx, table: str, params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
327
|
+
"""Fold ``params['facets']`` into ``params['where']``; drop the key.
|
|
328
|
+
|
|
329
|
+
Returns a new dict. With no facets the params come back unchanged (the
|
|
330
|
+
same ``where`` object), so callers without facets see no difference.
|
|
331
|
+
"""
|
|
332
|
+
if not isinstance(params, dict) or PARAM_KEY not in params:
|
|
333
|
+
return params if isinstance(params, dict) else {}
|
|
334
|
+
out = dict(params)
|
|
335
|
+
facets = out.pop(PARAM_KEY)
|
|
336
|
+
built = build_where(facets)
|
|
337
|
+
if built is None:
|
|
338
|
+
return out
|
|
339
|
+
predicates = _existing_predicates(tx, table, out.get("where"))
|
|
340
|
+
predicates.append(built)
|
|
341
|
+
out["where"] = predicates
|
|
342
|
+
return out
|
{velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -16,6 +16,7 @@ import xml.etree.ElementTree as ET
|
|
|
16
16
|
from io import BytesIO, StringIO
|
|
17
17
|
|
|
18
18
|
from velocity.aws import dirty_pipeline, ssm_config
|
|
19
|
+
from velocity.aws import facets
|
|
19
20
|
from velocity.aws.handlers.exceptions import AlertError
|
|
20
21
|
from velocity.db.exceptions import DbDataIntegrityError
|
|
21
22
|
from velocity.misc import export
|
|
@@ -409,7 +410,10 @@ class DataServiceMixin:
|
|
|
409
410
|
def query_hook(self, tx, table, payload, context):
|
|
410
411
|
self._call_rwx_hook("before_query", "common", tx, table, payload, context)
|
|
411
412
|
self._call_rwx_hook("before_query", table, tx, table, payload, context)
|
|
412
|
-
|
|
413
|
+
# Structured facet selections (params.facets) become a parameterized
|
|
414
|
+
# predicate AND'ed onto params.where — here, once, so the rows, the
|
|
415
|
+
# count and every later use of ``params`` see the same filter.
|
|
416
|
+
params = facets.apply_to_params(tx, table, payload.get("params", {}))
|
|
413
417
|
result_format = payload.get("result_format")
|
|
414
418
|
|
|
415
419
|
# Clear any previous swallowed-error details so we only report errors
|
{velocity_python-0.1.116 → velocity_python-0.1.118}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -10,6 +10,7 @@ src/velocity/aws/amplify_build.py
|
|
|
10
10
|
src/velocity/aws/blobs.py
|
|
11
11
|
src/velocity/aws/dirty_pipeline.py
|
|
12
12
|
src/velocity/aws/email.py
|
|
13
|
+
src/velocity/aws/facets.py
|
|
13
14
|
src/velocity/aws/feature_flags.py
|
|
14
15
|
src/velocity/aws/job_lifecycle.py
|
|
15
16
|
src/velocity/aws/job_results.py
|
|
@@ -233,6 +234,7 @@ tests/test_delete_hook_fk_blocked.py
|
|
|
233
234
|
tests/test_dirty_pipeline_fast_path.py
|
|
234
235
|
tests/test_email_processing.py
|
|
235
236
|
tests/test_enqueue_send_failures.py
|
|
237
|
+
tests/test_facets.py
|
|
236
238
|
tests/test_feature_flags.py
|
|
237
239
|
tests/test_get_cognito_user_provider.py
|
|
238
240
|
tests/test_http_handler_rollback.py
|