velocity-python 0.1.1__tar.gz → 0.1.3__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.1 → velocity_python-0.1.3}/PKG-INFO +12 -6
- {velocity_python-0.1.1 → velocity_python-0.1.3}/pyproject.toml +17 -6
- velocity_python-0.1.3/src/velocity/__init__.py +17 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/__init__.py +21 -11
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/column.py +2 -1
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/engine.py +5 -1
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/row.py +43 -5
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/table.py +28 -7
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/base/sql.py +10 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/sql.py +5 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/sql.py +5 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/export.py +11 -4
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity_python.egg-info/PKG-INFO +12 -6
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity_python.egg-info/requires.txt +15 -4
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_row_batch_update.py +3 -0
- velocity_python-0.1.3/tests/test_row_cache_staleness.py +296 -0
- velocity_python-0.1.3/tests/test_security_hardening.py +256 -0
- velocity_python-0.1.1/src/velocity/__init__.py +0 -8
- {velocity_python-0.1.1 → velocity_python-0.1.3}/LICENSE +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/README.md +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/setup.cfg +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/formbuilder/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/formbuilder/reshaper.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/validators/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/app/validators/formbuilder_template.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/demo_profiles.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/profiles.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_formbuilder_reshaper.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_formbuilder_template_validator.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_payment_demo_profiles.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_payment_profiles.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_payment_router.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.1 → velocity_python-0.1.3}/tests/test_where_clause_validation.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.3
|
|
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
|
|
@@ -19,12 +19,16 @@ Classifier: Operating System :: OS Independent
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: boto3>=1.35.0
|
|
23
|
-
Requires-Dist: requests>=2.32.0
|
|
24
|
-
Requires-Dist: jinja2>=3.1.0
|
|
25
|
-
Requires-Dist: xlrd>=2.0.1
|
|
26
|
-
Requires-Dist: openpyxl>=3.1.0
|
|
27
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"
|
|
28
32
|
Provides-Extra: mysql
|
|
29
33
|
Requires-Dist: mysql-connector-python>=9.0.0; extra == "mysql"
|
|
30
34
|
Provides-Extra: sqlserver
|
|
@@ -34,6 +38,8 @@ Requires-Dist: psycopg[binary]>=3.2.0; extra == "postgres"
|
|
|
34
38
|
Provides-Extra: payment
|
|
35
39
|
Requires-Dist: stripe>=12.0.0; extra == "payment"
|
|
36
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"
|
|
37
43
|
Provides-Extra: dev
|
|
38
44
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
39
45
|
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
|
|
@@ -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.3"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Velocity Team", email="info@codeclubs.org" },
|
|
10
10
|
]
|
|
@@ -26,11 +26,6 @@ classifiers = [
|
|
|
26
26
|
"Operating System :: OS Independent",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
-
"boto3>=1.35.0",
|
|
30
|
-
"requests>=2.32.0",
|
|
31
|
-
"jinja2>=3.1.0",
|
|
32
|
-
"xlrd>=2.0.1",
|
|
33
|
-
"openpyxl>=3.1.0",
|
|
34
29
|
"sqlparse>=0.5.0"
|
|
35
30
|
]
|
|
36
31
|
|
|
@@ -38,6 +33,19 @@ dependencies = [
|
|
|
38
33
|
Homepage = "https://codeclubs.org/projects/velocity"
|
|
39
34
|
|
|
40
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
|
+
]
|
|
41
49
|
mysql = [
|
|
42
50
|
"mysql-connector-python>=9.0.0",
|
|
43
51
|
]
|
|
@@ -51,6 +59,9 @@ payment = [
|
|
|
51
59
|
"stripe>=12.0.0",
|
|
52
60
|
"braintree>=4.30.0",
|
|
53
61
|
]
|
|
62
|
+
all = [
|
|
63
|
+
"velocity-python[postgres,aws,excel,templates,http,payment]",
|
|
64
|
+
]
|
|
54
65
|
dev = [
|
|
55
66
|
"pytest>=8.0.0",
|
|
56
67
|
"pytest-cov>=6.0.0",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__version__ = version = "0.1.3"
|
|
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}")
|
|
@@ -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",
|
|
@@ -125,8 +125,9 @@ class Column:
|
|
|
125
125
|
Returns the MAX() of this column, or 0 if table/column is missing.
|
|
126
126
|
"""
|
|
127
127
|
try:
|
|
128
|
+
qcol = self.sql.quote_identifier(self.name)
|
|
128
129
|
sql, vals = self.sql.select(
|
|
129
|
-
columns=f"max({
|
|
130
|
+
columns=f"max({qcol})", table=self.table.name, where=where
|
|
130
131
|
)
|
|
131
132
|
return self.tx.execute(sql, vals).scalar()
|
|
132
133
|
except (exceptions.DbTableMissingError, exceptions.DbColumnMissingError):
|
|
@@ -7,7 +7,7 @@ from contextlib import contextmanager
|
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from velocity.db import exceptions
|
|
9
9
|
from velocity.db.core.transaction import Transaction
|
|
10
|
-
from velocity.db.utils import mask_config_for_display
|
|
10
|
+
from velocity.db.utils import mask_config_for_display, mask_sensitive_in_string
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
|
|
@@ -714,6 +714,10 @@ class Engine:
|
|
|
714
714
|
if relevant_frames:
|
|
715
715
|
enhanced_message += "\n\nCall Context:\n" + "".join(relevant_frames)
|
|
716
716
|
|
|
717
|
+
# Mask any credentials that may have leaked into driver error messages
|
|
718
|
+
# (e.g. connection strings containing password=...).
|
|
719
|
+
enhanced_message = mask_sensitive_in_string(enhanced_message)
|
|
720
|
+
|
|
717
721
|
# Note: SQL formatting for logging is available via _format_sql_with_params,
|
|
718
722
|
# but we intentionally avoid eager logging here.
|
|
719
723
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pprint
|
|
2
|
+
import time as _time
|
|
2
3
|
import warnings
|
|
3
4
|
from collections.abc import MutableMapping
|
|
4
5
|
from velocity.db.exceptions import DbColumnMissingError
|
|
@@ -8,6 +9,7 @@ from velocity.db.exceptions import DbColumnMissingError
|
|
|
8
9
|
# intercepted by __getattr__ / __setattr__.
|
|
9
10
|
_INTERNAL_ATTRS = frozenset({
|
|
10
11
|
"table", "pk", "_cache", "_column_set", "_batching", "_pending",
|
|
12
|
+
"_cache_ttl", "_cache_time", "_no_cache",
|
|
11
13
|
})
|
|
12
14
|
|
|
13
15
|
|
|
@@ -23,7 +25,7 @@ class Row(MutableMapping):
|
|
|
23
25
|
write-through (immediate UPDATE) and also update the local cache.
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
|
-
def __init__(self, table, key, lock=None):
|
|
28
|
+
def __init__(self, table, key, lock=None, cache_ttl=None, no_cache=False):
|
|
27
29
|
if isinstance(table, str):
|
|
28
30
|
raise Exception("Table parameter must be a `table` instance.")
|
|
29
31
|
object.__setattr__(self, "table", table)
|
|
@@ -43,6 +45,9 @@ class Row(MutableMapping):
|
|
|
43
45
|
object.__setattr__(self, "_column_set", None)
|
|
44
46
|
object.__setattr__(self, "_batching", False)
|
|
45
47
|
object.__setattr__(self, "_pending", {})
|
|
48
|
+
object.__setattr__(self, "_cache_ttl", cache_ttl)
|
|
49
|
+
object.__setattr__(self, "_cache_time", None)
|
|
50
|
+
object.__setattr__(self, "_no_cache", no_cache)
|
|
46
51
|
if lock:
|
|
47
52
|
self.lock()
|
|
48
53
|
|
|
@@ -51,10 +56,20 @@ class Row(MutableMapping):
|
|
|
51
56
|
# ------------------------------------------------------------------
|
|
52
57
|
|
|
53
58
|
def _ensure_cache(self):
|
|
54
|
-
"""Populate the local cache from the database if not yet loaded."""
|
|
59
|
+
"""Populate the local cache from the database if not yet loaded or expired."""
|
|
60
|
+
if self._no_cache:
|
|
61
|
+
# Always fetch fresh — never store in cache
|
|
62
|
+
data = self.table.select(where=self.pk).as_dict().one()
|
|
63
|
+
return data or {}
|
|
64
|
+
if self._cache is not None and self._cache_ttl is not None:
|
|
65
|
+
elapsed = _time.monotonic() - (self._cache_time or 0)
|
|
66
|
+
if elapsed > self._cache_ttl:
|
|
67
|
+
object.__setattr__(self, "_cache", None)
|
|
68
|
+
object.__setattr__(self, "_column_set", None)
|
|
55
69
|
if self._cache is None:
|
|
56
70
|
data = self.table.select(where=self.pk).as_dict().one()
|
|
57
71
|
object.__setattr__(self, "_cache", data or {})
|
|
72
|
+
object.__setattr__(self, "_cache_time", _time.monotonic())
|
|
58
73
|
return self._cache
|
|
59
74
|
|
|
60
75
|
def _column_names_lower(self):
|
|
@@ -232,9 +247,9 @@ class Row(MutableMapping):
|
|
|
232
247
|
data.update(kwds)
|
|
233
248
|
if data:
|
|
234
249
|
self.table.update_or_insert(data, pk=self.pk)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
# Invalidate cache so trigger-computed columns are re-fetched
|
|
251
|
+
object.__setattr__(self, "_cache", None)
|
|
252
|
+
object.__setattr__(self, "_column_set", None)
|
|
238
253
|
return self
|
|
239
254
|
|
|
240
255
|
def copy(self, lock=None):
|
|
@@ -366,6 +381,29 @@ class Row(MutableMapping):
|
|
|
366
381
|
self.table.select(where=self.pk, lock=True)
|
|
367
382
|
return self
|
|
368
383
|
|
|
384
|
+
def with_lock(self):
|
|
385
|
+
"""SELECT ... FOR UPDATE and clear the cache so the next read is fresh.
|
|
386
|
+
|
|
387
|
+
Returns self for chaining::
|
|
388
|
+
|
|
389
|
+
row = row.with_lock()
|
|
390
|
+
balance = row["balance"] # guaranteed fresh, locked
|
|
391
|
+
"""
|
|
392
|
+
self.table.select(where=self.pk, lock=True)
|
|
393
|
+
object.__setattr__(self, "_cache", None)
|
|
394
|
+
object.__setattr__(self, "_column_set", None)
|
|
395
|
+
return self
|
|
396
|
+
|
|
397
|
+
def no_cache(self):
|
|
398
|
+
"""Return a new Row that never caches — every read hits the database.
|
|
399
|
+
|
|
400
|
+
Useful for critical-path reads where staleness is unacceptable::
|
|
401
|
+
|
|
402
|
+
fresh_row = row.no_cache()
|
|
403
|
+
balance = fresh_row["balance"] # always from DB
|
|
404
|
+
"""
|
|
405
|
+
return Row(self.table, self.pk, no_cache=True)
|
|
406
|
+
|
|
369
407
|
def notBlank(self, key, failobj=None):
|
|
370
408
|
"""
|
|
371
409
|
Returns the value if it is not blank, else failobj.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
3
4
|
import sqlparse
|
|
@@ -12,6 +13,8 @@ from velocity.db.core.decorators import (
|
|
|
12
13
|
reset_id_on_dup_key,
|
|
13
14
|
)
|
|
14
15
|
|
|
16
|
+
_ddl_logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
class Query:
|
|
17
20
|
"""
|
|
@@ -252,6 +255,7 @@ class Table:
|
|
|
252
255
|
)
|
|
253
256
|
if kwds.get("sql_only", False):
|
|
254
257
|
return sql, vals
|
|
258
|
+
_ddl_logger.warning("DDL CREATE INDEX on %s columns=%s unique=%s", self.name, columns, unique)
|
|
255
259
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
256
260
|
|
|
257
261
|
def create_indexes(self, indexes, **kwds):
|
|
@@ -317,6 +321,7 @@ class Table:
|
|
|
317
321
|
sql, vals = self.sql.drop_index(self.name, columns)
|
|
318
322
|
if kwds.get("sql_only", False):
|
|
319
323
|
return sql, vals
|
|
324
|
+
_ddl_logger.warning("DDL DROP INDEX on %s columns=%s", self.name, columns)
|
|
320
325
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
321
326
|
|
|
322
327
|
@return_default(None)
|
|
@@ -325,6 +330,7 @@ class Table:
|
|
|
325
330
|
Drops a column from this table.
|
|
326
331
|
"""
|
|
327
332
|
sql, vals = self.sql.drop_column(self.name, column)
|
|
333
|
+
_ddl_logger.warning("DDL DROP COLUMN %s on %s", column, self.name)
|
|
328
334
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
329
335
|
|
|
330
336
|
def create(self, columns=None, drop=False):
|
|
@@ -334,6 +340,7 @@ class Table:
|
|
|
334
340
|
"""
|
|
335
341
|
columns = columns or {}
|
|
336
342
|
sql, vals = self.sql.create_table(self.name, columns, drop)
|
|
343
|
+
_ddl_logger.warning("DDL CREATE TABLE %s columns=%s drop=%s", self.name, list(columns.keys()), drop)
|
|
337
344
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
338
345
|
|
|
339
346
|
def drop(self):
|
|
@@ -341,6 +348,7 @@ class Table:
|
|
|
341
348
|
Drops this table if it exists.
|
|
342
349
|
"""
|
|
343
350
|
sql, vals = self.sql.drop_table(self.name)
|
|
351
|
+
_ddl_logger.warning("DDL DROP TABLE %s", self.name)
|
|
344
352
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
345
353
|
|
|
346
354
|
def exists(self):
|
|
@@ -400,13 +408,13 @@ class Table:
|
|
|
400
408
|
"""
|
|
401
409
|
return Column(self, name)
|
|
402
410
|
|
|
403
|
-
def row(self, key=None, lock=None):
|
|
411
|
+
def row(self, key=None, lock=None, cache_ttl=None, no_cache=False):
|
|
404
412
|
"""
|
|
405
413
|
Retrieves a Row instance for the given primary key or conditions dict. If `key` is None, returns a new row.
|
|
406
414
|
"""
|
|
407
415
|
if key is None:
|
|
408
416
|
return self.new(lock=lock)
|
|
409
|
-
return Row(self, key, lock=lock)
|
|
417
|
+
return Row(self, key, lock=lock, cache_ttl=cache_ttl, no_cache=no_cache)
|
|
410
418
|
|
|
411
419
|
def dict(self, key):
|
|
412
420
|
"""
|
|
@@ -415,14 +423,14 @@ class Table:
|
|
|
415
423
|
r = self.find(key)
|
|
416
424
|
return r.to_dict() if r else {}
|
|
417
425
|
|
|
418
|
-
def rows(self, where=None, orderby=None, qty=None, lock=None, skip_locked=None):
|
|
426
|
+
def rows(self, where=None, orderby=None, qty=None, lock=None, skip_locked=None, cache_ttl=None, no_cache=False):
|
|
419
427
|
"""
|
|
420
428
|
Generator that yields Row objects matching `where`, up to `qty`.
|
|
421
429
|
"""
|
|
422
430
|
for key in self.ids(
|
|
423
431
|
where=where, orderby=orderby, qty=qty, lock=lock, skip_locked=skip_locked
|
|
424
432
|
):
|
|
425
|
-
yield Row(self, key, lock=lock)
|
|
433
|
+
yield Row(self, key, lock=lock, cache_ttl=cache_ttl, no_cache=no_cache)
|
|
426
434
|
|
|
427
435
|
def ids(
|
|
428
436
|
self,
|
|
@@ -625,6 +633,7 @@ class Table:
|
|
|
625
633
|
)
|
|
626
634
|
if kwds.get("sql_only", False):
|
|
627
635
|
return sql, vals
|
|
636
|
+
_ddl_logger.warning("DDL CREATE FOREIGN KEY on %s columns=%s ref=%s(%s)", self.name, columns, key_to_table, key_to_columns)
|
|
628
637
|
return self.tx.execute(sql, vals, cursor=self.cursor())
|
|
629
638
|
|
|
630
639
|
def drop_foreign_key(self, columns, key_to_table, key_to_columns="sys_id", **kwds):
|
|
@@ -636,6 +645,7 @@ class Table:
|
|
|
636
645
|
)
|
|
637
646
|
if kwds.get("sql_only", False):
|
|
638
647
|
return sql, vals
|
|
648
|
+
_ddl_logger.warning("DDL DROP FOREIGN KEY on %s columns=%s ref=%s(%s)", self.name, columns, key_to_table, key_to_columns)
|
|
639
649
|
return self.tx.execute(sql, vals, cursor=self.cursor())
|
|
640
650
|
|
|
641
651
|
def rename(self, name, **kwds):
|
|
@@ -645,6 +655,7 @@ class Table:
|
|
|
645
655
|
sql, vals = self.sql.rename_table(self.name, name)
|
|
646
656
|
if kwds.get("sql_only", False):
|
|
647
657
|
return sql, vals
|
|
658
|
+
_ddl_logger.warning("DDL RENAME TABLE %s to %s", self.name, name)
|
|
648
659
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
649
660
|
self.name = name
|
|
650
661
|
|
|
@@ -784,6 +795,7 @@ class Table:
|
|
|
784
795
|
return statements[0]
|
|
785
796
|
return statements
|
|
786
797
|
|
|
798
|
+
_ddl_logger.warning("DDL ALTER TABLE %s columns=%s mode=%s", self.name, list(columns.keys()), mode)
|
|
787
799
|
for sql, vals in statements:
|
|
788
800
|
if not sql:
|
|
789
801
|
continue
|
|
@@ -808,6 +820,7 @@ class Table:
|
|
|
808
820
|
)
|
|
809
821
|
if kwds.get("sql_only", False):
|
|
810
822
|
return sql, vals
|
|
823
|
+
_ddl_logger.warning("DDL ALTER COLUMN TYPE on %s column=%s", self.name, column)
|
|
811
824
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
812
825
|
|
|
813
826
|
@create_missing
|
|
@@ -1069,9 +1082,10 @@ class Table:
|
|
|
1069
1082
|
"""
|
|
1070
1083
|
Returns the sum of the given column across rows matching `where`.
|
|
1071
1084
|
"""
|
|
1085
|
+
qcol = self.sql.quote_identifier(column)
|
|
1072
1086
|
sql, vals = self.sql.select(
|
|
1073
1087
|
self.tx,
|
|
1074
|
-
columns=f"coalesce(sum(coalesce({
|
|
1088
|
+
columns=f"coalesce(sum(coalesce({qcol},0)),0)",
|
|
1075
1089
|
table=self.name,
|
|
1076
1090
|
where=where,
|
|
1077
1091
|
)
|
|
@@ -1322,6 +1336,7 @@ class Table:
|
|
|
1322
1336
|
)
|
|
1323
1337
|
if kwds.get("sql_only", False):
|
|
1324
1338
|
return sql, vals
|
|
1339
|
+
_ddl_logger.warning("DDL CREATE VIEW %s temp=%s", name, temp)
|
|
1325
1340
|
return self.tx.execute(sql, vals)
|
|
1326
1341
|
|
|
1327
1342
|
def drop_view(self, name, silent=True, **kwds):
|
|
@@ -1331,6 +1346,7 @@ class Table:
|
|
|
1331
1346
|
sql, vals = self.sql.drop_view(name=name, silent=silent)
|
|
1332
1347
|
if kwds.get("sql_only", False):
|
|
1333
1348
|
return sql, vals
|
|
1349
|
+
_ddl_logger.warning("DDL DROP VIEW %s", name)
|
|
1334
1350
|
return self.tx.execute(sql, vals)
|
|
1335
1351
|
|
|
1336
1352
|
def alter_trigger(self, name="USER", state="ENABLE", **kwds):
|
|
@@ -1340,6 +1356,7 @@ class Table:
|
|
|
1340
1356
|
sql, vals = self.sql.alter_trigger(table=self.name, state=state, name=name)
|
|
1341
1357
|
if kwds.get("sql_only", False):
|
|
1342
1358
|
return sql, vals
|
|
1359
|
+
_ddl_logger.warning("DDL ALTER TRIGGER %s on %s state=%s", name, self.name, state)
|
|
1343
1360
|
return self.tx.execute(sql, vals)
|
|
1344
1361
|
|
|
1345
1362
|
def rename_column(self, orig, new, **kwds):
|
|
@@ -1349,6 +1366,7 @@ class Table:
|
|
|
1349
1366
|
sql, vals = self.sql.rename_column(table=self.name, orig=orig, new=new)
|
|
1350
1367
|
if kwds.get("sql_only", False):
|
|
1351
1368
|
return sql, vals
|
|
1369
|
+
_ddl_logger.warning("DDL RENAME COLUMN on %s %s -> %s", self.name, orig, new)
|
|
1352
1370
|
return self.tx.execute(sql, vals)
|
|
1353
1371
|
|
|
1354
1372
|
def set_sequence(self, next_value=1000, **kwds):
|
|
@@ -1358,6 +1376,7 @@ class Table:
|
|
|
1358
1376
|
sql, vals = self.sql.set_sequence(table=self.name, next_value=next_value)
|
|
1359
1377
|
if kwds.get("sql_only", False):
|
|
1360
1378
|
return sql, vals
|
|
1379
|
+
_ddl_logger.warning("DDL SET SEQUENCE on %s next_value=%s", self.name, next_value)
|
|
1361
1380
|
return self.tx.execute(sql, vals).scalar()
|
|
1362
1381
|
|
|
1363
1382
|
def get_sequence(self, **kwds):
|
|
@@ -1396,8 +1415,9 @@ class Table:
|
|
|
1396
1415
|
"""
|
|
1397
1416
|
Returns the MAX() of the specified column.
|
|
1398
1417
|
"""
|
|
1418
|
+
qcol = self.sql.quote_identifier(column)
|
|
1399
1419
|
sql, vals = self.sql.select(
|
|
1400
|
-
self.tx, columns=f"max({
|
|
1420
|
+
self.tx, columns=f"max({qcol})", table=self.name, where=where
|
|
1401
1421
|
)
|
|
1402
1422
|
if kwds.get("sql_only", False):
|
|
1403
1423
|
return sql, vals
|
|
@@ -1408,8 +1428,9 @@ class Table:
|
|
|
1408
1428
|
"""
|
|
1409
1429
|
Returns the MIN() of the specified column.
|
|
1410
1430
|
"""
|
|
1431
|
+
qcol = self.sql.quote_identifier(column)
|
|
1411
1432
|
sql, vals = self.sql.select(
|
|
1412
|
-
self.tx, columns=f"min({
|
|
1433
|
+
self.tx, columns=f"min({qcol})", table=self.name, where=where
|
|
1413
1434
|
)
|
|
1414
1435
|
if kwds.get("sql_only", False):
|
|
1415
1436
|
return sql, vals
|
|
@@ -38,6 +38,16 @@ class BaseSQLDialect(ABC):
|
|
|
38
38
|
DatabaseObjectExistsErrorCodes: List[str] = []
|
|
39
39
|
DataIntegrityErrorCodes: List[str] = []
|
|
40
40
|
|
|
41
|
+
@classmethod
|
|
42
|
+
def quote_identifier(cls, name: str) -> str:
|
|
43
|
+
"""Always-quote a single SQL identifier to prevent injection.
|
|
44
|
+
|
|
45
|
+
Uses standard SQL double-quoting. Dialect subclasses override for
|
|
46
|
+
MySQL (backticks) and SQL Server (brackets).
|
|
47
|
+
"""
|
|
48
|
+
escaped = name.replace('"', '""')
|
|
49
|
+
return f'"{escaped}"'
|
|
50
|
+
|
|
41
51
|
@classmethod
|
|
42
52
|
@abstractmethod
|
|
43
53
|
def get_error(cls, e: Exception) -> Optional[str]:
|
|
@@ -48,6 +48,11 @@ class SQL(BaseSQLDialect):
|
|
|
48
48
|
type_column_identifier = "DATA_TYPE"
|
|
49
49
|
is_nullable = "IS_NULLABLE"
|
|
50
50
|
|
|
51
|
+
@classmethod
|
|
52
|
+
def quote_identifier(cls, name: str) -> str:
|
|
53
|
+
escaped = name.replace('`', '``')
|
|
54
|
+
return f'`{escaped}`'
|
|
55
|
+
|
|
51
56
|
default_schema = ""
|
|
52
57
|
|
|
53
58
|
ApplicationErrorCodes = []
|
|
@@ -48,6 +48,11 @@ class SQL(BaseSQLDialect):
|
|
|
48
48
|
type_column_identifier = "DATA_TYPE"
|
|
49
49
|
is_nullable = "IS_NULLABLE"
|
|
50
50
|
|
|
51
|
+
@classmethod
|
|
52
|
+
def quote_identifier(cls, name: str) -> str:
|
|
53
|
+
escaped = name.replace(']', ']]')
|
|
54
|
+
return f'[{escaped}]'
|
|
55
|
+
|
|
51
56
|
default_schema = "dbo"
|
|
52
57
|
|
|
53
58
|
# SQL Server error numbers
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from datetime import date, datetime, time, timedelta
|
|
2
4
|
from decimal import Decimal
|
|
3
5
|
from io import BytesIO
|
|
4
|
-
from typing import Dict, List
|
|
6
|
+
from typing import TYPE_CHECKING, Dict, List
|
|
5
7
|
import base64
|
|
6
8
|
import re
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from openpyxl.
|
|
10
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
11
|
+
import openpyxl
|
|
12
|
+
from openpyxl.styles import NamedStyle
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
NUMBER_FORMAT_RE = re.compile(r"[#0?][#0?,]*(?:\.[#0?]+)?")
|
|
@@ -107,6 +109,8 @@ def _display_value(cell) -> str:
|
|
|
107
109
|
|
|
108
110
|
def autosize_columns(ws, fixed: Dict[str, float] | None = None):
|
|
109
111
|
"""Autosize columns in the worksheet based on content length."""
|
|
112
|
+
from openpyxl.utils import get_column_letter
|
|
113
|
+
|
|
110
114
|
fixed = fixed or {}
|
|
111
115
|
for col in ws.columns:
|
|
112
116
|
max_length = 0
|
|
@@ -135,6 +139,9 @@ def create_spreadsheet(
|
|
|
135
139
|
auto_size: bool = True,
|
|
136
140
|
):
|
|
137
141
|
"""Create an Excel spreadsheet with specified headers, rows, and styles."""
|
|
142
|
+
import openpyxl
|
|
143
|
+
from openpyxl.styles import Alignment, Border, Font, NamedStyle, Side
|
|
144
|
+
|
|
138
145
|
styles = styles or {}
|
|
139
146
|
merge = merge or []
|
|
140
147
|
formats = formats or {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: velocity-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -19,12 +19,16 @@ Classifier: Operating System :: OS Independent
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: boto3>=1.35.0
|
|
23
|
-
Requires-Dist: requests>=2.32.0
|
|
24
|
-
Requires-Dist: jinja2>=3.1.0
|
|
25
|
-
Requires-Dist: xlrd>=2.0.1
|
|
26
|
-
Requires-Dist: openpyxl>=3.1.0
|
|
27
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"
|
|
28
32
|
Provides-Extra: mysql
|
|
29
33
|
Requires-Dist: mysql-connector-python>=9.0.0; extra == "mysql"
|
|
30
34
|
Provides-Extra: sqlserver
|
|
@@ -34,6 +38,8 @@ Requires-Dist: psycopg[binary]>=3.2.0; extra == "postgres"
|
|
|
34
38
|
Provides-Extra: payment
|
|
35
39
|
Requires-Dist: stripe>=12.0.0; extra == "payment"
|
|
36
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"
|
|
37
43
|
Provides-Extra: dev
|
|
38
44
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
39
45
|
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
|
|
@@ -168,6 +168,8 @@ tests/test_prepared_statements.py
|
|
|
168
168
|
tests/test_psycopg3_upgrade.py
|
|
169
169
|
tests/test_query_cache.py
|
|
170
170
|
tests/test_row_batch_update.py
|
|
171
|
+
tests/test_row_cache_staleness.py
|
|
172
|
+
tests/test_security_hardening.py
|
|
171
173
|
tests/test_sqs_per_record_transactions.py
|
|
172
174
|
tests/test_sys_modified_count_postgres_demo.py
|
|
173
175
|
tests/test_table_alter.py
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
sqlparse>=0.5.0
|
|
2
|
+
|
|
3
|
+
[all]
|
|
4
|
+
velocity-python[aws,excel,http,payment,postgres,templates]
|
|
5
|
+
|
|
6
|
+
[aws]
|
|
1
7
|
boto3>=1.35.0
|
|
2
8
|
requests>=2.32.0
|
|
3
|
-
jinja2>=3.1.0
|
|
4
|
-
xlrd>=2.0.1
|
|
5
|
-
openpyxl>=3.1.0
|
|
6
|
-
sqlparse>=0.5.0
|
|
7
9
|
|
|
8
10
|
[dev]
|
|
9
11
|
pytest>=8.0.0
|
|
@@ -17,6 +19,12 @@ pre-commit>=4.0.0
|
|
|
17
19
|
sphinx>=8.0.0
|
|
18
20
|
sphinx-rtd-theme>=3.0.0
|
|
19
21
|
|
|
22
|
+
[excel]
|
|
23
|
+
openpyxl>=3.1.0
|
|
24
|
+
|
|
25
|
+
[http]
|
|
26
|
+
requests>=2.32.0
|
|
27
|
+
|
|
20
28
|
[mysql]
|
|
21
29
|
mysql-connector-python>=9.0.0
|
|
22
30
|
|
|
@@ -30,6 +38,9 @@ psycopg[binary]>=3.2.0
|
|
|
30
38
|
[sqlserver]
|
|
31
39
|
python-tds>=1.15.0
|
|
32
40
|
|
|
41
|
+
[templates]
|
|
42
|
+
jinja2>=3.1.0
|
|
43
|
+
|
|
33
44
|
[test]
|
|
34
45
|
pytest>=8.0.0
|
|
35
46
|
pytest-cov>=6.0.0
|
|
@@ -26,6 +26,9 @@ def _make_row(cache=None, pk=None):
|
|
|
26
26
|
object.__setattr__(row, "_column_set", None)
|
|
27
27
|
object.__setattr__(row, "_batching", False)
|
|
28
28
|
object.__setattr__(row, "_pending", {})
|
|
29
|
+
object.__setattr__(row, "_cache_ttl", None)
|
|
30
|
+
object.__setattr__(row, "_cache_time", None)
|
|
31
|
+
object.__setattr__(row, "_no_cache", False)
|
|
29
32
|
return row
|
|
30
33
|
|
|
31
34
|
|