velocity-python 0.1.83__tar.gz → 0.1.86__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.83/src/velocity_python.egg-info → velocity_python-0.1.86}/PKG-INFO +17 -1
- {velocity_python-0.1.83 → velocity_python-0.1.86}/README.md +1 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/pyproject.toml +24 -1
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/mixins/data_service.py +36 -1
- velocity_python-0.1.86/src/velocity/nosql/__init__.py +20 -0
- velocity_python-0.1.86/src/velocity/nosql/core/__init__.py +0 -0
- velocity_python-0.1.86/src/velocity/nosql/core/capabilities.py +45 -0
- velocity_python-0.1.86/src/velocity/nosql/core/store.py +328 -0
- velocity_python-0.1.86/src/velocity/nosql/core/where.py +244 -0
- velocity_python-0.1.86/src/velocity/nosql/exceptions.py +63 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/__init__.py +2 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/base/__init__.py +31 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/cassandra/__init__.py +216 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/cassandra/translation.py +77 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/couchbase/__init__.py +267 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/couchbase/translation.py +93 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/dynamodb/__init__.py +307 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/dynamodb/translation.py +107 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/elasticsearch/__init__.py +281 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/elasticsearch/translation.py +101 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/memory/__init__.py +157 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/mongodb/__init__.py +236 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/mongodb/translation.py +64 -0
- velocity_python-0.1.86/src/velocity/nosql/servers/redis/__init__.py +230 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86/src/velocity_python.egg-info}/PKG-INFO +17 -1
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity_python.egg-info/SOURCES.txt +27 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity_python.egg-info/requires.txt +20 -0
- velocity_python-0.1.86/tests/test_delete_hook_fk_blocked.py +154 -0
- velocity_python-0.1.86/tests/test_nosql_dynamodb.py +103 -0
- velocity_python-0.1.86/tests/test_nosql_memory.py +57 -0
- velocity_python-0.1.86/tests/test_nosql_mongodb.py +69 -0
- velocity_python-0.1.86/tests/test_nosql_redis.py +72 -0
- velocity_python-0.1.86/tests/test_nosql_translation.py +249 -0
- velocity_python-0.1.86/tests/test_nosql_where.py +115 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/LICENSE +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/setup.cfg +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/engine.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/maintenance.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_activity_tracking_aborted_tx.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_column_tx_arg.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_db_maintenance.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_enqueue_send_failures.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_json_columns.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_observability.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_mirror_empty_table.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_sqlite_backend.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_store_user_data.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_transaction_class_wrapping.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_transaction_commit_and_ownership.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_transaction_edge_cases.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.83 → velocity_python-0.1.86}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.86
|
|
4
4
|
Summary: A rapid application development library for interfacing with data storage
|
|
5
5
|
Author-email: Velocity Team <info@codeclubs.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,6 +39,16 @@ Provides-Extra: http
|
|
|
39
39
|
Requires-Dist: requests>=2.32.0; extra == "http"
|
|
40
40
|
Provides-Extra: mysql
|
|
41
41
|
Requires-Dist: mysql-connector-python>=9.0.0; extra == "mysql"
|
|
42
|
+
Provides-Extra: redis
|
|
43
|
+
Requires-Dist: redis>=5.0.0; extra == "redis"
|
|
44
|
+
Provides-Extra: mongodb
|
|
45
|
+
Requires-Dist: pymongo>=4.6.0; extra == "mongodb"
|
|
46
|
+
Provides-Extra: elasticsearch
|
|
47
|
+
Requires-Dist: elasticsearch>=8.13.0; extra == "elasticsearch"
|
|
48
|
+
Provides-Extra: cassandra
|
|
49
|
+
Requires-Dist: cassandra-driver>=3.29.0; extra == "cassandra"
|
|
50
|
+
Provides-Extra: couchbase
|
|
51
|
+
Requires-Dist: couchbase>=4.2.0; extra == "couchbase"
|
|
42
52
|
Provides-Extra: sqlserver
|
|
43
53
|
Requires-Dist: python-tds>=1.15.0; extra == "sqlserver"
|
|
44
54
|
Provides-Extra: postgres
|
|
@@ -70,6 +80,11 @@ Requires-Dist: braintree>=4.30.0; extra == "test"
|
|
|
70
80
|
Requires-Dist: xmltodict>=0.13.0; extra == "test"
|
|
71
81
|
Requires-Dist: authorizenet>=1.1.3; extra == "test"
|
|
72
82
|
Requires-Dist: openpyxl>=3.1.0; extra == "test"
|
|
83
|
+
Requires-Dist: redis>=5.0.0; extra == "test"
|
|
84
|
+
Requires-Dist: fakeredis>=2.23.0; extra == "test"
|
|
85
|
+
Requires-Dist: pymongo>=4.6.0; extra == "test"
|
|
86
|
+
Requires-Dist: mongomock>=4.1.0; extra == "test"
|
|
87
|
+
Requires-Dist: moto[dynamodb]>=5.0.0; extra == "test"
|
|
73
88
|
Provides-Extra: docs
|
|
74
89
|
Requires-Dist: sphinx>=8.0.0; extra == "docs"
|
|
75
90
|
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
|
|
@@ -234,6 +249,7 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
234
249
|
| Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
|
|
235
250
|
| Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
|
|
236
251
|
| AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions |
|
|
252
|
+
| NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
|
|
237
253
|
| Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
|
|
238
254
|
| Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing |
|
|
239
255
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
@@ -157,6 +157,7 @@ Full documentation is included in the `docs/` directory of the source distributi
|
|
|
157
157
|
| Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
|
|
158
158
|
| Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
|
|
159
159
|
| AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions |
|
|
160
|
+
| NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
|
|
160
161
|
| Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
|
|
161
162
|
| Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing |
|
|
162
163
|
| Testing Guide | `docs/TESTING.md` | Running tests, markers, coverage |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "velocity-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.86"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Velocity Team", email="info@codeclubs.org" },
|
|
10
10
|
]
|
|
@@ -63,6 +63,21 @@ http = [
|
|
|
63
63
|
mysql = [
|
|
64
64
|
"mysql-connector-python>=9.0.0",
|
|
65
65
|
]
|
|
66
|
+
redis = [
|
|
67
|
+
"redis>=5.0.0",
|
|
68
|
+
]
|
|
69
|
+
mongodb = [
|
|
70
|
+
"pymongo>=4.6.0",
|
|
71
|
+
]
|
|
72
|
+
elasticsearch = [
|
|
73
|
+
"elasticsearch>=8.13.0",
|
|
74
|
+
]
|
|
75
|
+
cassandra = [
|
|
76
|
+
"cassandra-driver>=3.29.0",
|
|
77
|
+
]
|
|
78
|
+
couchbase = [
|
|
79
|
+
"couchbase>=4.2.0",
|
|
80
|
+
]
|
|
66
81
|
sqlserver = [
|
|
67
82
|
"python-tds>=1.15.0",
|
|
68
83
|
]
|
|
@@ -102,6 +117,14 @@ test = [
|
|
|
102
117
|
"xmltodict>=0.13.0",
|
|
103
118
|
"authorizenet>=1.1.3",
|
|
104
119
|
"openpyxl>=3.1.0",
|
|
120
|
+
# NoSQL adapter unit tests run against in-process fakes (real clients for
|
|
121
|
+
# the wire formats, fakes for the servers). ES/Cassandra/Couchbase have
|
|
122
|
+
# translation-only unit tests and need no driver here.
|
|
123
|
+
"redis>=5.0.0",
|
|
124
|
+
"fakeredis>=2.23.0",
|
|
125
|
+
"pymongo>=4.6.0",
|
|
126
|
+
"mongomock>=4.1.0",
|
|
127
|
+
"moto[dynamodb]>=5.0.0",
|
|
105
128
|
]
|
|
106
129
|
docs = [
|
|
107
130
|
"sphinx>=8.0.0",
|
{velocity_python-0.1.83 → velocity_python-0.1.86}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
@@ -17,10 +17,36 @@ from io import BytesIO, StringIO
|
|
|
17
17
|
|
|
18
18
|
from velocity.aws import dirty_pipeline
|
|
19
19
|
from velocity.aws.handlers.exceptions import AlertError
|
|
20
|
+
from velocity.db.exceptions import DbDataIntegrityError
|
|
20
21
|
from velocity.misc import export
|
|
21
22
|
|
|
22
23
|
logger = logging.getLogger(__name__)
|
|
23
24
|
|
|
25
|
+
# Postgres FK-violation text, e.g.:
|
|
26
|
+
# ... violates foreign key constraint "billing_formula_client_sys_id_fkey"
|
|
27
|
+
# on table "billing_formula"
|
|
28
|
+
# DETAIL: Key (sys_id)=(1364) is still referenced from table "billing_formula".
|
|
29
|
+
_REFERENCED_FROM_RE = re.compile(r'still referenced from table "([^"]+)"')
|
|
30
|
+
_FK_ON_TABLE_RE = re.compile(r'foreign key constraint "[^"]+" on table "([^"]+)"')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _fk_blocked_delete_message(table, exc):
|
|
34
|
+
"""User-facing message for a delete blocked by a foreign key reference."""
|
|
35
|
+
text = str(exc)
|
|
36
|
+
match = _REFERENCED_FROM_RE.search(text) or _FK_ON_TABLE_RE.search(text)
|
|
37
|
+
label = table.replace("_", " ")
|
|
38
|
+
if match:
|
|
39
|
+
referencing = match.group(1).replace("_", " ")
|
|
40
|
+
return (
|
|
41
|
+
f"This {label} record can't be deleted because {referencing} "
|
|
42
|
+
f"records still reference it. Remove or reassign those related "
|
|
43
|
+
f"records first."
|
|
44
|
+
)
|
|
45
|
+
return (
|
|
46
|
+
f"This {label} record can't be deleted because related records "
|
|
47
|
+
f"still reference it."
|
|
48
|
+
)
|
|
49
|
+
|
|
24
50
|
|
|
25
51
|
class DataServiceMixin:
|
|
26
52
|
"""
|
|
@@ -463,7 +489,16 @@ class DataServiceMixin:
|
|
|
463
489
|
self._call_rwx_hook("before_delete", table, tx, table, sys_id, context)
|
|
464
490
|
row = tx.table(table).find(sys_id)
|
|
465
491
|
if row:
|
|
466
|
-
|
|
492
|
+
# A failed DELETE aborts the whole postgres transaction; the
|
|
493
|
+
# savepoint confines the abort so the outer tx (activity
|
|
494
|
+
# tracking, response writes) stays usable.
|
|
495
|
+
try:
|
|
496
|
+
with tx.savepoint():
|
|
497
|
+
row.clear()
|
|
498
|
+
except DbDataIntegrityError as exc:
|
|
499
|
+
raise AlertError(
|
|
500
|
+
_fk_blocked_delete_message(table, exc)
|
|
501
|
+
) from exc
|
|
467
502
|
self._call_rwx_hook("after_delete", "common", tx, table, sys_id, context)
|
|
468
503
|
self._call_rwx_hook("after_delete", table, tx, table, sys_id, context)
|
|
469
504
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
velocity.nosql — a thin, capability-honest layer over NoSQL backends.
|
|
3
|
+
|
|
4
|
+
Same idiom as velocity.db: collections are just names, documents are just
|
|
5
|
+
dicts, where-dicts use the familiar operator-prefix convention. Unlike
|
|
6
|
+
velocity.db there is no transaction contract — see docs/nosql.md.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from velocity.nosql.servers import redis
|
|
10
|
+
store = redis.initialize()
|
|
11
|
+
col = store.collection("sessions")
|
|
12
|
+
col.put("abc", {"user": "paul"}, ttl=3600)
|
|
13
|
+
col.get("abc")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from velocity.nosql.core.capabilities import Capabilities
|
|
17
|
+
from velocity.nosql.core.store import KEY_FIELD, Collection, Store
|
|
18
|
+
from velocity.nosql import exceptions
|
|
19
|
+
|
|
20
|
+
__all__ = ["Capabilities", "Collection", "Store", "KEY_FIELD", "exceptions"]
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Capability declaration for NoSQL adapters.
|
|
3
|
+
|
|
4
|
+
Every adapter declares what it can do; shared code checks the flags and the
|
|
5
|
+
Collection base class enforces them, so unsupported operations fail loudly
|
|
6
|
+
with NoSqlUnsupportedOperationError instead of degrading silently.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Capabilities:
|
|
14
|
+
"""What a NoSQL adapter supports.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
name: Adapter identifier ("memory", "redis", "dynamodb", ...).
|
|
18
|
+
query: ``find()`` with a where-dict is supported at all.
|
|
19
|
+
indexed_query: ad-hoc where-dicts are served efficiently without an
|
|
20
|
+
explicit index (Mongo/ES/Couchbase style). When False, ``find()``
|
|
21
|
+
beyond a key lookup requires ``allow_scan=True``.
|
|
22
|
+
scan: full-collection scans are possible (with ``allow_scan=True``).
|
|
23
|
+
ttl: per-document time-to-live on ``put()``.
|
|
24
|
+
lazy_ttl: expired documents may still exist physically and are
|
|
25
|
+
filtered out client-side on read (DynamoDB-style lazy expiry).
|
|
26
|
+
conditional_put: ``put_if_absent()`` supported.
|
|
27
|
+
cas: compare-and-swap via ``get_versioned()`` / ``cas()``.
|
|
28
|
+
transactions: backend has some atomic multi-write construct exposed
|
|
29
|
+
through the adapter's native handle (never through the shared
|
|
30
|
+
contract).
|
|
31
|
+
search: full-text relevance search via an adapter extension.
|
|
32
|
+
sort: server-side sort in ``find()``.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
query: bool = False
|
|
37
|
+
indexed_query: bool = False
|
|
38
|
+
scan: bool = False
|
|
39
|
+
ttl: bool = False
|
|
40
|
+
lazy_ttl: bool = False
|
|
41
|
+
conditional_put: bool = False
|
|
42
|
+
cas: bool = False
|
|
43
|
+
transactions: bool = False
|
|
44
|
+
search: bool = False
|
|
45
|
+
sort: bool = False
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Store / Collection — the shared NoSQL contract.
|
|
3
|
+
|
|
4
|
+
Deliberately *not* part of velocity.db's transactional world: there is no
|
|
5
|
+
``tx`` here, and the shared contract makes no atomicity promises beyond the
|
|
6
|
+
single operation. Backends with atomic multi-write constructs expose them
|
|
7
|
+
via ``store.native`` only.
|
|
8
|
+
|
|
9
|
+
Tiered contract:
|
|
10
|
+
Tier 1 (every adapter): get / put / delete / exists / put_if_absent,
|
|
11
|
+
plus get_versioned / cas where Capabilities.cas is set.
|
|
12
|
+
Tier 2 (Capabilities.query): find(where) using velocity's where-dict
|
|
13
|
+
convention (operator prefixes: {">=age": 21}, {"%name": "Smi%"}).
|
|
14
|
+
Tier 3: store/collection ``.native`` and adapter-specific extensions.
|
|
15
|
+
|
|
16
|
+
Honesty rules enforced here, not left to adapters:
|
|
17
|
+
- Operations a backend can't support raise NoSqlUnsupportedOperationError.
|
|
18
|
+
- On backends without efficient ad-hoc queries (Capabilities.indexed_query
|
|
19
|
+
False), a find() beyond key lookups raises NoSqlUnindexedQueryError
|
|
20
|
+
unless the caller passes allow_scan=True.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
from abc import ABC, abstractmethod
|
|
25
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
26
|
+
|
|
27
|
+
from velocity.nosql.core.capabilities import Capabilities
|
|
28
|
+
from velocity.nosql.core.where import EQ, IN, Condition, parse_where
|
|
29
|
+
from velocity.nosql.exceptions import (
|
|
30
|
+
NoSqlInvalidKeyError,
|
|
31
|
+
NoSqlSchemaLockedError,
|
|
32
|
+
NoSqlUnindexedQueryError,
|
|
33
|
+
NoSqlUnsupportedOperationError,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Reserved field carrying the document key in find() results.
|
|
37
|
+
KEY_FIELD = "_key"
|
|
38
|
+
# Internal version field used by adapters that emulate CAS.
|
|
39
|
+
VERSION_FIELD = "_vel_version"
|
|
40
|
+
# Internal expiry marker used by adapters with lazy TTL (DynamoDB, Mongo).
|
|
41
|
+
EXPIRES_FIELD = "_expires_at"
|
|
42
|
+
|
|
43
|
+
MAX_KEY_LENGTH = 1024
|
|
44
|
+
|
|
45
|
+
SortSpec = Union[str, List[str], None]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _schema_locked_from_env() -> bool:
|
|
49
|
+
return os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ("true", "1", "yes")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def apply_sort(
|
|
53
|
+
results: List[Dict[str, Any]], sort: List[Tuple[str, bool]]
|
|
54
|
+
) -> List[Dict[str, Any]]:
|
|
55
|
+
"""Client-side sort used by adapters that filter in Python (memory,
|
|
56
|
+
Redis/Cassandra scans). None sorts last in both directions; stable sorts
|
|
57
|
+
applied in reverse order keep earlier sort keys dominant."""
|
|
58
|
+
for field, ascending in reversed(sort):
|
|
59
|
+
nulls = [d for d in results if d.get(field) is None]
|
|
60
|
+
rest = [d for d in results if d.get(field) is not None]
|
|
61
|
+
rest.sort(key=lambda d, f=field: d.get(f), reverse=not ascending)
|
|
62
|
+
results = rest + nulls
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_sort(sort: SortSpec) -> List[Tuple[str, bool]]:
|
|
67
|
+
"""Normalize a sort spec into [(field, ascending), ...].
|
|
68
|
+
|
|
69
|
+
Accepts "field", "-field" (descending), or a list of those.
|
|
70
|
+
"""
|
|
71
|
+
if not sort:
|
|
72
|
+
return []
|
|
73
|
+
if isinstance(sort, str):
|
|
74
|
+
sort = [sort]
|
|
75
|
+
result = []
|
|
76
|
+
for item in sort:
|
|
77
|
+
item = item.strip()
|
|
78
|
+
if item.startswith("-"):
|
|
79
|
+
result.append((item[1:].strip(), False))
|
|
80
|
+
else:
|
|
81
|
+
result.append((item, True))
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Store(ABC):
|
|
86
|
+
"""A connection to one NoSQL backend; hands out Collections."""
|
|
87
|
+
|
|
88
|
+
capabilities: Capabilities
|
|
89
|
+
|
|
90
|
+
def __init__(self, schema_locked: bool = False):
|
|
91
|
+
self.schema_locked = schema_locked or _schema_locked_from_env()
|
|
92
|
+
self._collections: Dict[str, "Collection"] = {}
|
|
93
|
+
|
|
94
|
+
def collection(self, name: str) -> "Collection":
|
|
95
|
+
if not isinstance(name, str) or not name.strip():
|
|
96
|
+
raise NoSqlInvalidKeyError(
|
|
97
|
+
f"Collection name must be a non-empty string: {name!r}"
|
|
98
|
+
)
|
|
99
|
+
name = name.strip()
|
|
100
|
+
if name not in self._collections:
|
|
101
|
+
self._collections[name] = self._make_collection(name)
|
|
102
|
+
return self._collections[name]
|
|
103
|
+
|
|
104
|
+
def ensure_collection(self, name: str, **options) -> None:
|
|
105
|
+
"""Create the backing object (table/index/...) if missing. Idempotent.
|
|
106
|
+
|
|
107
|
+
Honors VELOCITY_SCHEMA_LOCKED, mirroring velocity.db schema locking.
|
|
108
|
+
"""
|
|
109
|
+
if self.schema_locked:
|
|
110
|
+
raise NoSqlSchemaLockedError(
|
|
111
|
+
f"Cannot ensure collection {name!r}: schema is locked"
|
|
112
|
+
)
|
|
113
|
+
self._ensure_collection(name, **options)
|
|
114
|
+
|
|
115
|
+
def drop_collection(self, name: str) -> None:
|
|
116
|
+
if self.schema_locked:
|
|
117
|
+
raise NoSqlSchemaLockedError(
|
|
118
|
+
f"Cannot drop collection {name!r}: schema is locked"
|
|
119
|
+
)
|
|
120
|
+
self._drop_collection(name)
|
|
121
|
+
self._collections.pop(name, None)
|
|
122
|
+
|
|
123
|
+
def close(self) -> None:
|
|
124
|
+
"""Release backend resources. Default: no-op."""
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def native(self) -> Any:
|
|
128
|
+
"""The raw backend client, for Tier-3 use. Never required for Tier 1/2."""
|
|
129
|
+
raise NoSqlUnsupportedOperationError(
|
|
130
|
+
f"{self.capabilities.name} store does not expose a native client"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def _make_collection(self, name: str) -> "Collection": ...
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def _ensure_collection(self, name: str, **options) -> None: ...
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def _drop_collection(self, name: str) -> None: ...
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Collection(ABC):
|
|
144
|
+
"""One named document collection. Documents are dicts; keys are strings."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, store: Store, name: str):
|
|
147
|
+
self.store = store
|
|
148
|
+
self.name = name
|
|
149
|
+
|
|
150
|
+
# ------------------------------------------------------------------ Tier 1
|
|
151
|
+
|
|
152
|
+
def get(self, key: str) -> Optional[Dict[str, Any]]:
|
|
153
|
+
"""Return the document, or None if absent (or expired)."""
|
|
154
|
+
return self._get(self._check_key(key))
|
|
155
|
+
|
|
156
|
+
def put(self, key: str, doc: Dict[str, Any], ttl: Optional[int] = None) -> None:
|
|
157
|
+
"""Write (create or replace) the document. ``ttl`` is seconds."""
|
|
158
|
+
self._put(self._check_key(key), self._check_doc(doc), self._check_ttl(ttl))
|
|
159
|
+
|
|
160
|
+
def put_if_absent(
|
|
161
|
+
self, key: str, doc: Dict[str, Any], ttl: Optional[int] = None
|
|
162
|
+
) -> bool:
|
|
163
|
+
"""Atomically create the document only if the key is absent.
|
|
164
|
+
|
|
165
|
+
Returns True if this call created it. This is the locking primitive.
|
|
166
|
+
"""
|
|
167
|
+
self._require(self.store.capabilities.conditional_put, "put_if_absent")
|
|
168
|
+
return self._put_if_absent(
|
|
169
|
+
self._check_key(key), self._check_doc(doc), self._check_ttl(ttl)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def get_versioned(self, key: str) -> Tuple[Optional[Dict[str, Any]], Optional[Any]]:
|
|
173
|
+
"""Return (document, version-token) for use with cas(). (None, None) if absent."""
|
|
174
|
+
self._require(self.store.capabilities.cas, "get_versioned")
|
|
175
|
+
return self._get_versioned(self._check_key(key))
|
|
176
|
+
|
|
177
|
+
def cas(
|
|
178
|
+
self, key: str, doc: Dict[str, Any], version: Any, ttl: Optional[int] = None
|
|
179
|
+
) -> bool:
|
|
180
|
+
"""Replace the document only if it still carries ``version``.
|
|
181
|
+
|
|
182
|
+
Returns True on success, False if the document changed (or vanished)
|
|
183
|
+
since ``get_versioned``. The version token is adapter-opaque.
|
|
184
|
+
"""
|
|
185
|
+
self._require(self.store.capabilities.cas, "cas")
|
|
186
|
+
if version is None:
|
|
187
|
+
return False
|
|
188
|
+
return self._cas(
|
|
189
|
+
self._check_key(key), self._check_doc(doc), version, self._check_ttl(ttl)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def delete(self, key: str) -> bool:
|
|
193
|
+
"""Delete the document. Returns True if it existed."""
|
|
194
|
+
return self._delete(self._check_key(key))
|
|
195
|
+
|
|
196
|
+
def exists(self, key: str) -> bool:
|
|
197
|
+
return self._exists(self._check_key(key))
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------ Tier 2
|
|
200
|
+
|
|
201
|
+
def find(
|
|
202
|
+
self,
|
|
203
|
+
where: Optional[Dict[str, Any]] = None,
|
|
204
|
+
limit: Optional[int] = None,
|
|
205
|
+
sort: SortSpec = None,
|
|
206
|
+
allow_scan: bool = False,
|
|
207
|
+
) -> List[Dict[str, Any]]:
|
|
208
|
+
"""Query the collection with a velocity where-dict.
|
|
209
|
+
|
|
210
|
+
Returned documents include their key under ``_key``. Conditions are
|
|
211
|
+
AND-combined. ``sort`` is "field" / "-field" / a list of those.
|
|
212
|
+
"""
|
|
213
|
+
conditions = self._gate_query(where, allow_scan)
|
|
214
|
+
sort_spec = parse_sort(sort)
|
|
215
|
+
if sort_spec:
|
|
216
|
+
self._require(self.store.capabilities.sort, "sort")
|
|
217
|
+
return self._find(conditions, limit, sort_spec)
|
|
218
|
+
|
|
219
|
+
def count(
|
|
220
|
+
self, where: Optional[Dict[str, Any]] = None, allow_scan: bool = False
|
|
221
|
+
) -> int:
|
|
222
|
+
conditions = self._gate_query(where, allow_scan)
|
|
223
|
+
return self._count(conditions)
|
|
224
|
+
|
|
225
|
+
# ------------------------------------------------------------------ Tier 3
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def native(self) -> Any:
|
|
229
|
+
"""The adapter's native handle for this collection (falls back to the store's)."""
|
|
230
|
+
return self.store.native
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------- plumbing
|
|
233
|
+
|
|
234
|
+
def _gate_query(
|
|
235
|
+
self, where: Optional[Dict[str, Any]], allow_scan: bool
|
|
236
|
+
) -> List[Condition]:
|
|
237
|
+
caps = self.store.capabilities
|
|
238
|
+
if not caps.query and not caps.scan:
|
|
239
|
+
raise NoSqlUnsupportedOperationError(
|
|
240
|
+
f"{caps.name} does not support find()/count()"
|
|
241
|
+
)
|
|
242
|
+
conditions = parse_where(where)
|
|
243
|
+
key_only = all(
|
|
244
|
+
c.field == KEY_FIELD and c.op in (EQ, IN) for c in conditions
|
|
245
|
+
) and bool(conditions)
|
|
246
|
+
needs_scan = not caps.indexed_query and not key_only
|
|
247
|
+
if needs_scan and not caps.scan:
|
|
248
|
+
raise NoSqlUnsupportedOperationError(
|
|
249
|
+
f"{caps.name} can only query by {KEY_FIELD}"
|
|
250
|
+
)
|
|
251
|
+
if needs_scan and not allow_scan:
|
|
252
|
+
raise NoSqlUnindexedQueryError(
|
|
253
|
+
f"This query is a full scan on {caps.name}. Pass allow_scan=True "
|
|
254
|
+
f"to accept the cost, or restructure the query around {KEY_FIELD}."
|
|
255
|
+
)
|
|
256
|
+
return conditions
|
|
257
|
+
|
|
258
|
+
def _require(self, flag: bool, operation: str) -> None:
|
|
259
|
+
if not flag:
|
|
260
|
+
raise NoSqlUnsupportedOperationError(
|
|
261
|
+
f"{self.store.capabilities.name} does not support {operation}"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def _check_key(key: str) -> str:
|
|
266
|
+
if not isinstance(key, str) or not key or len(key) > MAX_KEY_LENGTH:
|
|
267
|
+
raise NoSqlInvalidKeyError(
|
|
268
|
+
f"Keys must be non-empty strings up to {MAX_KEY_LENGTH} chars, got: {key!r}"
|
|
269
|
+
)
|
|
270
|
+
return key
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def _check_doc(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
274
|
+
if not isinstance(doc, dict):
|
|
275
|
+
raise TypeError(f"Documents must be dicts, got {type(doc)}")
|
|
276
|
+
# Reserved fields never round-trip into storage.
|
|
277
|
+
return {
|
|
278
|
+
k: v
|
|
279
|
+
for k, v in doc.items()
|
|
280
|
+
if k not in (KEY_FIELD, VERSION_FIELD, EXPIRES_FIELD)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
def _check_ttl(self, ttl: Optional[int]) -> Optional[int]:
|
|
284
|
+
if ttl is None:
|
|
285
|
+
return None
|
|
286
|
+
self._require(self.store.capabilities.ttl, "ttl")
|
|
287
|
+
ttl = int(ttl)
|
|
288
|
+
if ttl <= 0:
|
|
289
|
+
raise ValueError(f"ttl must be a positive number of seconds, got {ttl}")
|
|
290
|
+
return ttl
|
|
291
|
+
|
|
292
|
+
# ------------------------------------------------------- adapter interface
|
|
293
|
+
|
|
294
|
+
@abstractmethod
|
|
295
|
+
def _get(self, key: str) -> Optional[Dict[str, Any]]: ...
|
|
296
|
+
|
|
297
|
+
@abstractmethod
|
|
298
|
+
def _put(self, key: str, doc: Dict[str, Any], ttl: Optional[int]) -> None: ...
|
|
299
|
+
|
|
300
|
+
def _put_if_absent(self, key: str, doc: Dict[str, Any], ttl: Optional[int]) -> bool:
|
|
301
|
+
raise NoSqlUnsupportedOperationError("put_if_absent not implemented")
|
|
302
|
+
|
|
303
|
+
def _get_versioned(
|
|
304
|
+
self, key: str
|
|
305
|
+
) -> Tuple[Optional[Dict[str, Any]], Optional[Any]]:
|
|
306
|
+
raise NoSqlUnsupportedOperationError("get_versioned not implemented")
|
|
307
|
+
|
|
308
|
+
def _cas(
|
|
309
|
+
self, key: str, doc: Dict[str, Any], version: Any, ttl: Optional[int]
|
|
310
|
+
) -> bool:
|
|
311
|
+
raise NoSqlUnsupportedOperationError("cas not implemented")
|
|
312
|
+
|
|
313
|
+
@abstractmethod
|
|
314
|
+
def _delete(self, key: str) -> bool: ...
|
|
315
|
+
|
|
316
|
+
def _exists(self, key: str) -> bool:
|
|
317
|
+
return self._get(key) is not None
|
|
318
|
+
|
|
319
|
+
def _find(
|
|
320
|
+
self,
|
|
321
|
+
conditions: List[Condition],
|
|
322
|
+
limit: Optional[int],
|
|
323
|
+
sort: List[Tuple[str, bool]],
|
|
324
|
+
) -> List[Dict[str, Any]]:
|
|
325
|
+
raise NoSqlUnsupportedOperationError("find not implemented")
|
|
326
|
+
|
|
327
|
+
def _count(self, conditions: List[Condition]) -> int:
|
|
328
|
+
return len(self._find(conditions, None, []))
|