velocity-python 0.0.240__tar.gz → 0.1.2__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.0.240 → velocity_python-0.1.2}/PKG-INFO +32 -27
- {velocity_python-0.0.240 → velocity_python-0.1.2}/pyproject.toml +39 -29
- velocity_python-0.1.2/src/velocity/__init__.py +17 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/formbuilder/reshaper.py +2 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/__init__.py +21 -11
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/base_handler.py +1 -1
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/mixins/web_handler.py +1 -1
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/sqs_handler.py +62 -30
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/database.py +6 -6
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/decorators.py +36 -3
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/engine.py +299 -13
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/row.py +102 -10
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/sequence.py +1 -1
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/table.py +94 -4
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/transaction.py +166 -11
- velocity_python-0.1.2/src/velocity/db/servers/postgres/__init__.py +147 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/postgres/sql.py +174 -5
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_engine.py +6 -6
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +1 -1
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_schema_locking_initializers.py +7 -7
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/export.py +11 -4
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity_python.egg-info/PKG-INFO +32 -27
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity_python.egg-info/SOURCES.txt +10 -0
- velocity_python-0.1.2/src/velocity_python.egg-info/requires.txt +47 -0
- velocity_python-0.1.2/tests/test_batch_operations.py +394 -0
- velocity_python-0.1.2/tests/test_concurrency_safety.py +374 -0
- velocity_python-0.1.2/tests/test_connection_pool.py +288 -0
- velocity_python-0.1.2/tests/test_connection_resilience.py +380 -0
- velocity_python-0.1.2/tests/test_prepared_statements.py +159 -0
- velocity_python-0.1.2/tests/test_psycopg3_upgrade.py +246 -0
- velocity_python-0.1.2/tests/test_query_cache.py +298 -0
- velocity_python-0.1.2/tests/test_row_batch_update.py +199 -0
- velocity_python-0.1.2/tests/test_row_cache_staleness.py +296 -0
- velocity_python-0.1.2/tests/test_sqs_per_record_transactions.py +593 -0
- velocity_python-0.0.240/src/velocity/__init__.py +0 -8
- velocity_python-0.0.240/src/velocity/db/servers/postgres/__init__.py +0 -85
- velocity_python-0.0.240/src/velocity_python.egg-info/requires.txt +0 -36
- {velocity_python-0.0.240 → velocity_python-0.1.2}/LICENSE +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/README.md +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/setup.cfg +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/formbuilder/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/validators/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/app/validators/formbuilder_template.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/logging.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/demo_profiles.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/profiles.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_decorators.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_formbuilder_reshaper.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_formbuilder_template_validator.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_payment_demo_profiles.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_payment_profiles.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_payment_router.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_table_alter.py +0 -0
- {velocity_python-0.0.240 → velocity_python-0.1.2}/tests/test_where_clause_validation.py +0 -0
|
@@ -1,54 +1,59 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://codeclubs.org/projects/velocity
|
|
8
8
|
Keywords: database,orm,sql,rapid-development,data-storage
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Topic :: Database
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Operating System :: OS Independent
|
|
20
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.9
|
|
21
20
|
Description-Content-Type: text/markdown
|
|
22
21
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
|
|
28
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: sqlparse>=0.5.0
|
|
23
|
+
Provides-Extra: aws
|
|
24
|
+
Requires-Dist: boto3>=1.35.0; extra == "aws"
|
|
25
|
+
Requires-Dist: requests>=2.32.0; extra == "aws"
|
|
26
|
+
Provides-Extra: excel
|
|
27
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "excel"
|
|
28
|
+
Provides-Extra: templates
|
|
29
|
+
Requires-Dist: jinja2>=3.1.0; extra == "templates"
|
|
30
|
+
Provides-Extra: http
|
|
31
|
+
Requires-Dist: requests>=2.32.0; extra == "http"
|
|
29
32
|
Provides-Extra: mysql
|
|
30
|
-
Requires-Dist: mysql-connector-python>=
|
|
33
|
+
Requires-Dist: mysql-connector-python>=9.0.0; extra == "mysql"
|
|
31
34
|
Provides-Extra: sqlserver
|
|
32
|
-
Requires-Dist: python-tds>=1.
|
|
35
|
+
Requires-Dist: python-tds>=1.15.0; extra == "sqlserver"
|
|
33
36
|
Provides-Extra: postgres
|
|
34
|
-
Requires-Dist:
|
|
37
|
+
Requires-Dist: psycopg[binary]>=3.2.0; extra == "postgres"
|
|
35
38
|
Provides-Extra: payment
|
|
36
|
-
Requires-Dist: stripe>=
|
|
37
|
-
Requires-Dist: braintree>=4.
|
|
39
|
+
Requires-Dist: stripe>=12.0.0; extra == "payment"
|
|
40
|
+
Requires-Dist: braintree>=4.30.0; extra == "payment"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: velocity-python[aws,excel,http,payment,postgres,templates]; extra == "all"
|
|
38
43
|
Provides-Extra: dev
|
|
39
|
-
Requires-Dist: pytest>=
|
|
40
|
-
Requires-Dist: pytest-cov>=
|
|
41
|
-
Requires-Dist: black>=
|
|
42
|
-
Requires-Dist: flake8>=
|
|
43
|
-
Requires-Dist: mypy>=1.
|
|
44
|
-
Requires-Dist: pre-commit>=
|
|
44
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
45
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
|
|
46
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
47
|
+
Requires-Dist: flake8>=7.0.0; extra == "dev"
|
|
48
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pre-commit>=4.0.0; extra == "dev"
|
|
45
50
|
Provides-Extra: test
|
|
46
|
-
Requires-Dist: pytest>=
|
|
47
|
-
Requires-Dist: pytest-cov>=
|
|
48
|
-
Requires-Dist: pytest-mock>=3.
|
|
51
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
52
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == "test"
|
|
53
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "test"
|
|
49
54
|
Provides-Extra: docs
|
|
50
|
-
Requires-Dist: sphinx>=
|
|
51
|
-
Requires-Dist: sphinx-rtd-theme>=
|
|
55
|
+
Requires-Dist: sphinx>=8.0.0; extra == "docs"
|
|
56
|
+
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
|
|
52
57
|
Dynamic: license-file
|
|
53
58
|
|
|
54
59
|
# Velocity
|
|
@@ -4,70 +4,80 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "velocity-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Velocity Team", email="info@codeclubs.org" },
|
|
10
10
|
]
|
|
11
11
|
description = "A rapid application development library for interfacing with data storage"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = "MIT"
|
|
14
|
-
requires-python = ">=3.
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
15
|
keywords = ["database", "orm", "sql", "rapid-development", "data-storage"]
|
|
16
16
|
classifiers = [
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Topic :: Database",
|
|
20
20
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.7",
|
|
23
|
-
"Programming Language :: Python :: 3.8",
|
|
24
22
|
"Programming Language :: Python :: 3.9",
|
|
25
23
|
"Programming Language :: Python :: 3.10",
|
|
26
24
|
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
27
26
|
"Operating System :: OS Independent",
|
|
28
27
|
]
|
|
29
28
|
dependencies = [
|
|
30
|
-
"
|
|
31
|
-
"requests>=2.25.0",
|
|
32
|
-
"jinja2>=3.0.0",
|
|
33
|
-
"xlrd>=2.0.0",
|
|
34
|
-
"openpyxl>=3.0.0",
|
|
35
|
-
"sqlparse>=0.4.0"
|
|
29
|
+
"sqlparse>=0.5.0"
|
|
36
30
|
]
|
|
37
31
|
|
|
38
32
|
[project.urls]
|
|
39
33
|
Homepage = "https://codeclubs.org/projects/velocity"
|
|
40
34
|
|
|
41
35
|
[project.optional-dependencies]
|
|
36
|
+
aws = [
|
|
37
|
+
"boto3>=1.35.0",
|
|
38
|
+
"requests>=2.32.0",
|
|
39
|
+
]
|
|
40
|
+
excel = [
|
|
41
|
+
"openpyxl>=3.1.0",
|
|
42
|
+
]
|
|
43
|
+
templates = [
|
|
44
|
+
"jinja2>=3.1.0",
|
|
45
|
+
]
|
|
46
|
+
http = [
|
|
47
|
+
"requests>=2.32.0",
|
|
48
|
+
]
|
|
42
49
|
mysql = [
|
|
43
|
-
"mysql-connector-python>=
|
|
50
|
+
"mysql-connector-python>=9.0.0",
|
|
44
51
|
]
|
|
45
52
|
sqlserver = [
|
|
46
|
-
"python-tds>=1.
|
|
53
|
+
"python-tds>=1.15.0",
|
|
47
54
|
]
|
|
48
55
|
postgres = [
|
|
49
|
-
"
|
|
56
|
+
"psycopg[binary]>=3.2.0",
|
|
50
57
|
]
|
|
51
58
|
payment = [
|
|
52
|
-
"stripe>=
|
|
53
|
-
"braintree>=4.
|
|
59
|
+
"stripe>=12.0.0",
|
|
60
|
+
"braintree>=4.30.0",
|
|
61
|
+
]
|
|
62
|
+
all = [
|
|
63
|
+
"velocity-python[postgres,aws,excel,templates,http,payment]",
|
|
54
64
|
]
|
|
55
65
|
dev = [
|
|
56
|
-
"pytest>=
|
|
57
|
-
"pytest-cov>=
|
|
58
|
-
"black>=
|
|
59
|
-
"flake8>=
|
|
60
|
-
"mypy>=1.
|
|
61
|
-
"pre-commit>=
|
|
66
|
+
"pytest>=8.0.0",
|
|
67
|
+
"pytest-cov>=6.0.0",
|
|
68
|
+
"black>=24.0.0",
|
|
69
|
+
"flake8>=7.0.0",
|
|
70
|
+
"mypy>=1.10.0",
|
|
71
|
+
"pre-commit>=4.0.0",
|
|
62
72
|
]
|
|
63
73
|
test = [
|
|
64
|
-
"pytest>=
|
|
65
|
-
"pytest-cov>=
|
|
66
|
-
"pytest-mock>=3.
|
|
74
|
+
"pytest>=8.0.0",
|
|
75
|
+
"pytest-cov>=6.0.0",
|
|
76
|
+
"pytest-mock>=3.14.0",
|
|
67
77
|
]
|
|
68
78
|
docs = [
|
|
69
|
-
"sphinx>=
|
|
70
|
-
"sphinx-rtd-theme>=
|
|
79
|
+
"sphinx>=8.0.0",
|
|
80
|
+
"sphinx-rtd-theme>=3.0.0",
|
|
71
81
|
]
|
|
72
82
|
|
|
73
83
|
[tool.setuptools.packages.find]
|
|
@@ -78,7 +88,7 @@ where = ["src"]
|
|
|
78
88
|
|
|
79
89
|
[tool.black]
|
|
80
90
|
line-length = 88
|
|
81
|
-
target-version = ['
|
|
91
|
+
target-version = ['py39', 'py310', 'py311', 'py312']
|
|
82
92
|
include = '\.pyi?$'
|
|
83
93
|
extend-exclude = '''
|
|
84
94
|
/(
|
|
@@ -122,7 +132,7 @@ module = [
|
|
|
122
132
|
"boto3.*",
|
|
123
133
|
"env",
|
|
124
134
|
"pytds.*",
|
|
125
|
-
"
|
|
135
|
+
"psycopg.*",
|
|
126
136
|
"requests.*",
|
|
127
137
|
"sqlparse.*",
|
|
128
138
|
"stripe.*",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__version__ = version = "0.1.2"
|
|
2
|
+
|
|
3
|
+
import importlib as _importlib
|
|
4
|
+
|
|
5
|
+
from . import db
|
|
6
|
+
from . import misc
|
|
7
|
+
from . import app
|
|
8
|
+
|
|
9
|
+
__all__ = ["aws", "db", "misc", "app"]
|
|
10
|
+
|
|
11
|
+
_LAZY_SUBMODULES = {"aws"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name: str):
|
|
15
|
+
if name in _LAZY_SUBMODULES:
|
|
16
|
+
return _importlib.import_module(f".{name}", __name__)
|
|
17
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -169,6 +169,8 @@ def reshape_formbuilder_template(raw_row: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
169
169
|
del fb[key]
|
|
170
170
|
|
|
171
171
|
for key in (
|
|
172
|
+
# UI-only computed column (row counter) — never a real DB column
|
|
173
|
+
"row_number",
|
|
172
174
|
"url_slug",
|
|
173
175
|
"url_slug_alternate",
|
|
174
176
|
# Administrative / validation metadata — not read by DonateMain frontend
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
+
import importlib as _importlib
|
|
1
2
|
import os
|
|
2
|
-
import requests
|
|
3
|
-
|
|
4
|
-
from velocity.aws.amplify import AmplifyProject
|
|
5
|
-
from velocity.aws.amplify_build import (
|
|
6
|
-
BackendDeploymentConfig,
|
|
7
|
-
build_environment_variables,
|
|
8
|
-
get_amplify_app_id_and_branch,
|
|
9
|
-
initialize_build_environment,
|
|
10
|
-
run_backend_deployment,
|
|
11
|
-
update_lambda_layers_to_latest,
|
|
12
|
-
)
|
|
13
3
|
|
|
14
4
|
DEBUG = (os.environ.get("ENV") != "production") or (os.environ.get("DEBUG") == "Y")
|
|
15
5
|
|
|
@@ -23,11 +13,31 @@ class AWS(object):
|
|
|
23
13
|
# Get AWS EC2 Instance ID. Must run this from the EC2 instance itself to get the ID
|
|
24
14
|
@staticmethod
|
|
25
15
|
def instance_id(cls):
|
|
16
|
+
import requests
|
|
26
17
|
response = requests.get("http://169.254.169.254/latest/meta-data/instance-id")
|
|
27
18
|
instance_id = response.text
|
|
28
19
|
return instance_id
|
|
29
20
|
|
|
30
21
|
|
|
22
|
+
# Lazy-load heavy modules (boto3-dependent) on first access
|
|
23
|
+
_LAZY_ATTRS = {
|
|
24
|
+
"AmplifyProject": "velocity.aws.amplify",
|
|
25
|
+
"BackendDeploymentConfig": "velocity.aws.amplify_build",
|
|
26
|
+
"build_environment_variables": "velocity.aws.amplify_build",
|
|
27
|
+
"get_amplify_app_id_and_branch": "velocity.aws.amplify_build",
|
|
28
|
+
"initialize_build_environment": "velocity.aws.amplify_build",
|
|
29
|
+
"run_backend_deployment": "velocity.aws.amplify_build",
|
|
30
|
+
"update_lambda_layers_to_latest": "velocity.aws.amplify_build",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str):
|
|
35
|
+
if name in _LAZY_ATTRS:
|
|
36
|
+
module = _importlib.import_module(_LAZY_ATTRS[name])
|
|
37
|
+
return getattr(module, name)
|
|
38
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
__all__ = [
|
|
32
42
|
"AmplifyProject",
|
|
33
43
|
"AWS",
|
|
@@ -335,7 +335,7 @@ class BaseHandler:
|
|
|
335
335
|
"""Return True if an exception looks like a transient DB disconnect.
|
|
336
336
|
|
|
337
337
|
This is intentionally message-based so it works even if the originating
|
|
338
|
-
exception type comes from
|
|
338
|
+
exception type comes from psycopg / SQLAlchemy / wrapped Velocity errors.
|
|
339
339
|
"""
|
|
340
340
|
if exc is None and args:
|
|
341
341
|
exc = args[0]
|
{velocity_python-0.0.240 → velocity_python-0.1.2}/src/velocity/aws/handlers/mixins/web_handler.py
RENAMED
|
@@ -217,7 +217,7 @@ class WebHandler(ABC):
|
|
|
217
217
|
return sanitized
|
|
218
218
|
|
|
219
219
|
def _normalize_activity_value(self, value: Any) -> Any:
|
|
220
|
-
"""Convert values into types acceptable by
|
|
220
|
+
"""Convert values into types acceptable by the database driver."""
|
|
221
221
|
if isinstance(value, (dict, list, tuple, set)):
|
|
222
222
|
try:
|
|
223
223
|
return json.dumps(value)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
SQS Handler Module
|
|
3
3
|
|
|
4
4
|
This module provides a base class for handling AWS SQS events in Lambda functions.
|
|
5
|
-
It includes
|
|
5
|
+
It includes per-record transaction isolation, partial batch failure reporting
|
|
6
|
+
via ``batchItemFailures``, and error handling hooks.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import json
|
|
@@ -22,8 +23,11 @@ class SqsHandler(BaseHandler):
|
|
|
22
23
|
"""
|
|
23
24
|
Base class for handling SQS events in AWS Lambda functions.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
Each SQS record is processed in its own database transaction. If a record
|
|
27
|
+
fails, the transaction is rolled back and the record's ``messageId`` is
|
|
28
|
+
included in the ``batchItemFailures`` response so that SQS retries only
|
|
29
|
+
the failed records. Successful records are deleted from the queue
|
|
30
|
+
automatically.
|
|
27
31
|
"""
|
|
28
32
|
|
|
29
33
|
def __init__(
|
|
@@ -33,14 +37,6 @@ class SqsHandler(BaseHandler):
|
|
|
33
37
|
context_factory: Optional[ContextFactory] = None,
|
|
34
38
|
context_class=VelocityContext.Context,
|
|
35
39
|
):
|
|
36
|
-
"""
|
|
37
|
-
Initialize the SQS handler.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
aws_event: The AWS Lambda event containing SQS records
|
|
41
|
-
aws_context: The AWS Lambda context object
|
|
42
|
-
context_class: The context class to use for processing
|
|
43
|
-
"""
|
|
44
40
|
super().__init__(
|
|
45
41
|
aws_event,
|
|
46
42
|
aws_context,
|
|
@@ -50,28 +46,40 @@ class SqsHandler(BaseHandler):
|
|
|
50
46
|
|
|
51
47
|
def serve(self, tx):
|
|
52
48
|
"""
|
|
53
|
-
Process all SQS records in
|
|
49
|
+
Process all SQS records, each in its own transaction.
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
Returns a ``batchItemFailures`` response so that only failed records
|
|
52
|
+
are retried by SQS. When all records succeed the return value is
|
|
53
|
+
``None`` (no failures to report).
|
|
57
54
|
"""
|
|
58
55
|
records = self.aws_event.get("Records", [])
|
|
56
|
+
if not records:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
engine = tx.engine
|
|
60
|
+
failures = []
|
|
59
61
|
|
|
60
62
|
for record in records:
|
|
61
|
-
|
|
63
|
+
message_id = record.get("messageId")
|
|
64
|
+
try:
|
|
65
|
+
with engine.transaction() as record_tx:
|
|
66
|
+
self._process_record(record_tx, record)
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
self.on_record_error(record, exc)
|
|
69
|
+
if message_id:
|
|
70
|
+
failures.append({"itemIdentifier": message_id})
|
|
71
|
+
|
|
72
|
+
if failures:
|
|
73
|
+
return {"batchItemFailures": failures}
|
|
74
|
+
return None
|
|
62
75
|
|
|
63
76
|
def _process_record(self, tx, record: Dict[str, Any]):
|
|
64
77
|
"""
|
|
65
78
|
Process a single SQS record.
|
|
66
|
-
|
|
67
|
-
Args:
|
|
68
|
-
tx: Database transaction object
|
|
69
|
-
record: Individual SQS record to process
|
|
70
79
|
"""
|
|
71
80
|
attrs = record.get("attributes", {})
|
|
72
81
|
postdata = {}
|
|
73
82
|
|
|
74
|
-
# Parse message body if present
|
|
75
83
|
body = record.get("body")
|
|
76
84
|
if body:
|
|
77
85
|
try:
|
|
@@ -80,7 +88,6 @@ class SqsHandler(BaseHandler):
|
|
|
80
88
|
logger.warning("Failed to parse SQS message body as JSON: %s", e)
|
|
81
89
|
postdata = {"raw_body": body}
|
|
82
90
|
|
|
83
|
-
# Create local context for this record
|
|
84
91
|
local_context = self.create_context(
|
|
85
92
|
args=attrs,
|
|
86
93
|
postdata=postdata,
|
|
@@ -90,26 +97,51 @@ class SqsHandler(BaseHandler):
|
|
|
90
97
|
if hasattr(local_context, "configure_perf"):
|
|
91
98
|
local_context.configure_perf(postdata=postdata)
|
|
92
99
|
|
|
93
|
-
# Determine action from postdata
|
|
94
100
|
action = postdata.get("action") if isinstance(postdata, dict) else None
|
|
95
|
-
|
|
96
|
-
# Get the list of actions to execute
|
|
97
101
|
actions = self.get_actions_to_execute(action)
|
|
98
102
|
|
|
99
|
-
# Use BaseHandler's execute_actions method
|
|
100
103
|
try:
|
|
101
104
|
self.execute_actions(tx, local_context, actions)
|
|
102
105
|
except Exception as e:
|
|
106
|
+
# Roll back failed DB state so handle_error/onError get a usable tx.
|
|
107
|
+
try:
|
|
108
|
+
tx.rollback()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
103
111
|
self.handle_error(tx, local_context, e)
|
|
104
112
|
|
|
105
|
-
def
|
|
113
|
+
def on_record_error(self, record, exception):
|
|
114
|
+
"""
|
|
115
|
+
Called when a record fails and will be reported in
|
|
116
|
+
``batchItemFailures``. Override in subclasses for custom behaviour
|
|
117
|
+
(e.g. alerting, metrics).
|
|
106
118
|
"""
|
|
107
|
-
|
|
119
|
+
message_id = record.get("messageId", "unknown")
|
|
120
|
+
receive_count = record.get("attributes", {}).get(
|
|
121
|
+
"ApproximateReceiveCount", "0"
|
|
122
|
+
)
|
|
123
|
+
logger.error(
|
|
124
|
+
"SQS record %s failed (receive count: %s): %s",
|
|
125
|
+
message_id,
|
|
126
|
+
receive_count,
|
|
127
|
+
exception,
|
|
128
|
+
)
|
|
108
129
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
context: The context object for this record
|
|
130
|
+
@staticmethod
|
|
131
|
+
def is_dlq_candidate(record, max_receives=5):
|
|
112
132
|
"""
|
|
133
|
+
Return ``True`` if the record has been received *max_receives* or more
|
|
134
|
+
times, indicating it is likely headed for the dead-letter queue.
|
|
135
|
+
|
|
136
|
+
The actual DLQ routing is controlled by the SQS redrive policy;
|
|
137
|
+
this helper is for logging and metrics.
|
|
138
|
+
"""
|
|
139
|
+
receive_count = int(
|
|
140
|
+
record.get("attributes", {}).get("ApproximateReceiveCount", "0")
|
|
141
|
+
)
|
|
142
|
+
return receive_count >= max_receives
|
|
143
|
+
|
|
144
|
+
def OnActionDefault(self, tx, context):
|
|
113
145
|
action = context.action() if hasattr(context, "action") else "unknown"
|
|
114
146
|
warning_message = (
|
|
115
147
|
f"[Warn] Action handler not found. Calling default action "
|
|
@@ -93,12 +93,12 @@ class Database:
|
|
|
93
93
|
Performs VACUUM on this database, optionally FULL, optionally ANALYZE,
|
|
94
94
|
optionally REINDEX.
|
|
95
95
|
"""
|
|
96
|
-
# Manually open a separate connection to run VACUUM
|
|
96
|
+
# Manually open a separate connection to run VACUUM outside a transaction.
|
|
97
97
|
conn = self.tx.engine.connect()
|
|
98
|
-
|
|
98
|
+
old_autocommit = conn.autocommit
|
|
99
99
|
try:
|
|
100
|
-
# Postgres requires VACUUM to run outside a normal transaction block
|
|
101
|
-
conn.
|
|
100
|
+
# Postgres requires VACUUM to run outside a normal transaction block.
|
|
101
|
+
conn.autocommit = True
|
|
102
102
|
|
|
103
103
|
# Build up the VACUUM command
|
|
104
104
|
parts = ["VACUUM"]
|
|
@@ -116,6 +116,6 @@ class Database:
|
|
|
116
116
|
cur.execute(f"REINDEX DATABASE {self.name}")
|
|
117
117
|
|
|
118
118
|
finally:
|
|
119
|
-
# Restore
|
|
120
|
-
conn.
|
|
119
|
+
# Restore autocommit state and close the connection
|
|
120
|
+
conn.autocommit = old_autocommit
|
|
121
121
|
conn.close()
|
|
@@ -60,6 +60,7 @@ def reset_id_on_dup_key(func):
|
|
|
60
60
|
|
|
61
61
|
@wraps(func)
|
|
62
62
|
def reset_decorator(self, *args, retries=0, **kwds):
|
|
63
|
+
max_retries = 10
|
|
63
64
|
sp = self.tx.create_savepoint(cursor=self.cursor())
|
|
64
65
|
try:
|
|
65
66
|
result = func(self, *args, **kwds)
|
|
@@ -71,7 +72,7 @@ def reset_id_on_dup_key(func):
|
|
|
71
72
|
raise
|
|
72
73
|
if not _is_primary_key_duplicate(error):
|
|
73
74
|
raise
|
|
74
|
-
if retries <
|
|
75
|
+
if retries < max_retries:
|
|
75
76
|
backoff_time = (2**retries) * 0.01 + random.uniform(0, 0.02)
|
|
76
77
|
time.sleep(backoff_time)
|
|
77
78
|
self.set_sequence(self.max("sys_id") + 1)
|
|
@@ -145,6 +146,11 @@ def create_missing(func):
|
|
|
145
146
|
"""
|
|
146
147
|
If the function call fails with DbColumnMissingError or DbTableMissingError,
|
|
147
148
|
tries to create them and re-run (only if schema is not locked).
|
|
149
|
+
|
|
150
|
+
DDL operations use IF NOT EXISTS at the SQL level, but concurrent Lambdas
|
|
151
|
+
may still race. If the CREATE/ALTER raises ``DbObjectExistsError`` we
|
|
152
|
+
silently ignore it (the object was created by another process) and retry
|
|
153
|
+
the original operation.
|
|
148
154
|
"""
|
|
149
155
|
|
|
150
156
|
@wraps(func)
|
|
@@ -172,7 +178,21 @@ def create_missing(func):
|
|
|
172
178
|
for i, arg in enumerate(args):
|
|
173
179
|
if isinstance(arg, dict):
|
|
174
180
|
data.update(arg)
|
|
175
|
-
|
|
181
|
+
|
|
182
|
+
# ALTER TABLE ADD COLUMN IF NOT EXISTS — acquire an advisory
|
|
183
|
+
# lock to serialize DDL across concurrent Lambda containers,
|
|
184
|
+
# then guard against any remaining race with a savepoint.
|
|
185
|
+
try:
|
|
186
|
+
self.tx.advisory_lock(f"velocity_ddl_{self.name}")
|
|
187
|
+
except Exception:
|
|
188
|
+
pass # advisory lock is best-effort (e.g. non-PostgreSQL)
|
|
189
|
+
sp2 = self.tx.create_savepoint(cursor=self.cursor())
|
|
190
|
+
try:
|
|
191
|
+
self.alter(data, mode="add")
|
|
192
|
+
self.tx.release_savepoint(sp2, cursor=self.cursor())
|
|
193
|
+
except exceptions.DbObjectExistsError:
|
|
194
|
+
self.tx.rollback_savepoint(sp2, cursor=self.cursor())
|
|
195
|
+
|
|
176
196
|
return func(self, *args, **kwds)
|
|
177
197
|
except exceptions.DbTableMissingError as e:
|
|
178
198
|
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
@@ -192,7 +212,20 @@ def create_missing(func):
|
|
|
192
212
|
for i, arg in enumerate(args):
|
|
193
213
|
if isinstance(arg, dict):
|
|
194
214
|
data.update(arg)
|
|
195
|
-
|
|
215
|
+
|
|
216
|
+
# CREATE TABLE IF NOT EXISTS — acquire advisory lock then
|
|
217
|
+
# guard against concurrent creation with a savepoint.
|
|
218
|
+
try:
|
|
219
|
+
self.tx.advisory_lock(f"velocity_ddl_{self.name}")
|
|
220
|
+
except Exception:
|
|
221
|
+
pass # advisory lock is best-effort
|
|
222
|
+
sp2 = self.tx.create_savepoint(cursor=self.cursor())
|
|
223
|
+
try:
|
|
224
|
+
self.create(data)
|
|
225
|
+
self.tx.release_savepoint(sp2, cursor=self.cursor())
|
|
226
|
+
except exceptions.DbObjectExistsError:
|
|
227
|
+
self.tx.rollback_savepoint(sp2, cursor=self.cursor())
|
|
228
|
+
|
|
196
229
|
return func(self, *args, **kwds)
|
|
197
230
|
|
|
198
231
|
return wrapper
|