velocity-python 0.1.3__tar.gz → 0.1.4__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.3 → velocity_python-0.1.4}/PKG-INFO +1 -1
- {velocity_python-0.1.3 → velocity_python-0.1.4}/pyproject.toml +1 -1
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/decorators.py +6 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/engine.py +63 -4
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/transaction.py +85 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- velocity_python-0.1.4/tests/test_observability.py +443 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/LICENSE +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/README.md +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/setup.cfg +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/formbuilder/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/formbuilder/reshaper.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/validators/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/validators/formbuilder_template.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/demo_profiles.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/profiles.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_formbuilder_reshaper.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_formbuilder_template_validator.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_demo_profiles.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_profiles.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_router.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_where_clause_validation.py +0 -0
|
@@ -121,6 +121,12 @@ def return_default(
|
|
|
121
121
|
e,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
+
# R12 — Increment per-transaction counter for swallowed exceptions.
|
|
125
|
+
try:
|
|
126
|
+
self.tx._return_default_count = getattr(self.tx, "_return_default_count", 0) + 1
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
124
130
|
# Capture swallowed exceptions for upstream diagnostics.
|
|
125
131
|
# This decorator intentionally returns a default value instead of
|
|
126
132
|
# raising, but consumers (e.g. API handlers) may still want to
|
|
@@ -77,6 +77,7 @@ class ConnectionPool:
|
|
|
77
77
|
if self._closed:
|
|
78
78
|
raise exceptions.DbConnectionError("Connection pool is closed")
|
|
79
79
|
|
|
80
|
+
t0 = time.perf_counter()
|
|
80
81
|
self._available.acquire() # blocks when maxconn reached
|
|
81
82
|
|
|
82
83
|
with self._lock:
|
|
@@ -84,7 +85,11 @@ class ConnectionPool:
|
|
|
84
85
|
while self._pool:
|
|
85
86
|
conn = self._pool.pop()
|
|
86
87
|
if self._is_alive(conn):
|
|
87
|
-
|
|
88
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
89
|
+
logger.debug(
|
|
90
|
+
"Pool: reusing idle connection (pool_size=%d, wait=%.1f ms)",
|
|
91
|
+
len(self._pool), elapsed_ms,
|
|
92
|
+
)
|
|
88
93
|
return conn
|
|
89
94
|
# Dead connection — close it silently and create a fresh one.
|
|
90
95
|
logger.debug("Pool: discarding dead idle connection")
|
|
@@ -95,7 +100,11 @@ class ConnectionPool:
|
|
|
95
100
|
try:
|
|
96
101
|
conn = self._connect_fn()
|
|
97
102
|
self._total_created += 1
|
|
98
|
-
|
|
103
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
104
|
+
logger.debug(
|
|
105
|
+
"Pool: created new connection (total=%d, wait=%.1f ms)",
|
|
106
|
+
self._total_created, elapsed_ms,
|
|
107
|
+
)
|
|
99
108
|
return conn
|
|
100
109
|
except Exception:
|
|
101
110
|
self._available.release()
|
|
@@ -277,10 +286,12 @@ class Engine:
|
|
|
277
286
|
If pooling is enabled, borrows from the pool.
|
|
278
287
|
If the database is missing, tries to create it, then reconnect.
|
|
279
288
|
"""
|
|
289
|
+
t0 = time.perf_counter()
|
|
280
290
|
if self.__pool:
|
|
281
291
|
try:
|
|
282
292
|
conn = self.__pool.getconn()
|
|
283
|
-
|
|
293
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
294
|
+
logger.debug("Engine.connect: obtained pooled connection (%.1f ms)", elapsed_ms)
|
|
284
295
|
return conn
|
|
285
296
|
except exceptions.DbDatabaseMissingError:
|
|
286
297
|
self.create_database()
|
|
@@ -291,7 +302,8 @@ class Engine:
|
|
|
291
302
|
|
|
292
303
|
try:
|
|
293
304
|
conn = self._raw_connect()
|
|
294
|
-
|
|
305
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
306
|
+
logger.debug("Engine.connect: created direct connection (%.1f ms)", elapsed_ms)
|
|
295
307
|
except exceptions.DbDatabaseMissingError:
|
|
296
308
|
self.create_database()
|
|
297
309
|
conn = self._raw_connect()
|
|
@@ -496,6 +508,53 @@ class Engine:
|
|
|
496
508
|
# if depth == 0:
|
|
497
509
|
# delattr(_tx, "_exec_function_depth")
|
|
498
510
|
|
|
511
|
+
def perf_log(self, func):
|
|
512
|
+
"""
|
|
513
|
+
Decorator that logs wall time, query count, and total query time
|
|
514
|
+
for a ``@engine.transaction``-wrapped function.
|
|
515
|
+
|
|
516
|
+
Usage::
|
|
517
|
+
|
|
518
|
+
@engine.perf_log
|
|
519
|
+
@engine.transaction
|
|
520
|
+
def some_work(tx):
|
|
521
|
+
...
|
|
522
|
+
|
|
523
|
+
The ``tx`` parameter **must** be a keyword argument or the first
|
|
524
|
+
positional ``Transaction`` so we can read its counters.
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
@wraps(func)
|
|
528
|
+
def wrapper(*args, **kwds):
|
|
529
|
+
t0 = time.perf_counter()
|
|
530
|
+
result = func(*args, **kwds)
|
|
531
|
+
wall_ms = (time.perf_counter() - t0) * 1000
|
|
532
|
+
|
|
533
|
+
# Find the Transaction among the arguments.
|
|
534
|
+
tx = kwds.get("tx")
|
|
535
|
+
if tx is None:
|
|
536
|
+
for a in args:
|
|
537
|
+
if isinstance(a, Transaction):
|
|
538
|
+
tx = a
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
qcount = getattr(tx, "_query_count", 0) if tx else 0
|
|
542
|
+
qtime = getattr(tx, "_query_time_ms", 0.0) if tx else 0.0
|
|
543
|
+
|
|
544
|
+
logger.info(
|
|
545
|
+
"perf_log %s: wall=%.1f ms, queries=%d, query_time=%.1f ms",
|
|
546
|
+
getattr(func, "__qualname__", func.__name__),
|
|
547
|
+
wall_ms, qcount, qtime,
|
|
548
|
+
extra={
|
|
549
|
+
"wall_ms": round(wall_ms, 1),
|
|
550
|
+
"query_count": qcount,
|
|
551
|
+
"query_time_ms": round(qtime, 1),
|
|
552
|
+
},
|
|
553
|
+
)
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
return wrapper
|
|
557
|
+
|
|
499
558
|
@property
|
|
500
559
|
def driver(self):
|
|
501
560
|
return self.__driver
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
3
|
+
import time as _time
|
|
2
4
|
import traceback
|
|
3
5
|
from collections import OrderedDict
|
|
4
6
|
|
|
@@ -14,9 +16,60 @@ from velocity.misc.db import randomword
|
|
|
14
16
|
|
|
15
17
|
debug = False
|
|
16
18
|
|
|
19
|
+
_logger = logging.getLogger("velocity.db.transaction")
|
|
20
|
+
|
|
17
21
|
# Default maximum number of cached query results per transaction.
|
|
18
22
|
_DEFAULT_QUERY_CACHE_SIZE = int(os.environ.get("VELOCITY_QUERY_CACHE_SIZE", "100"))
|
|
19
23
|
|
|
24
|
+
# Slow-query threshold in milliseconds (0 = disabled).
|
|
25
|
+
_SLOW_QUERY_MS = int(os.environ.get("VELOCITY_SLOW_QUERY_MS", "500"))
|
|
26
|
+
|
|
27
|
+
_SQL_OP_PREFIXES = {
|
|
28
|
+
"select": "SELECT",
|
|
29
|
+
"insert": "INSERT",
|
|
30
|
+
"update": "UPDATE",
|
|
31
|
+
"delete": "DELETE",
|
|
32
|
+
"create": "DDL",
|
|
33
|
+
"alter": "DDL",
|
|
34
|
+
"drop": "DDL",
|
|
35
|
+
"set": "SET",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _classify_sql(sql):
|
|
40
|
+
"""Return a short operation label (SELECT, INSERT, …) from a SQL string."""
|
|
41
|
+
if not sql:
|
|
42
|
+
return "OTHER"
|
|
43
|
+
first = sql.lstrip().split(None, 1)[0].lower() if sql.strip() else ""
|
|
44
|
+
return _SQL_OP_PREFIXES.get(first, "OTHER")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_table_name(sql):
|
|
48
|
+
"""Best-effort extraction of the main table name from a SQL statement."""
|
|
49
|
+
if not sql:
|
|
50
|
+
return None
|
|
51
|
+
upper = sql.strip().upper()
|
|
52
|
+
lowered = sql.strip()
|
|
53
|
+
try:
|
|
54
|
+
if upper.startswith("SELECT"):
|
|
55
|
+
idx = upper.find(" FROM ")
|
|
56
|
+
if idx != -1:
|
|
57
|
+
rest = lowered[idx + 6:].strip()
|
|
58
|
+
return rest.split()[0].strip('"').strip("'") if rest else None
|
|
59
|
+
elif upper.startswith("INSERT"):
|
|
60
|
+
idx = upper.find(" INTO ")
|
|
61
|
+
if idx != -1:
|
|
62
|
+
rest = lowered[idx + 6:].strip()
|
|
63
|
+
return rest.split()[0].strip('"').strip("'") if rest else None
|
|
64
|
+
elif upper.startswith(("UPDATE", "DELETE FROM")):
|
|
65
|
+
parts = lowered.split()
|
|
66
|
+
if len(parts) >= 2:
|
|
67
|
+
token = parts[2] if upper.startswith("DELETE") and len(parts) > 2 else parts[1]
|
|
68
|
+
return token.strip('"').strip("'")
|
|
69
|
+
except (IndexError, ValueError):
|
|
70
|
+
pass
|
|
71
|
+
return None
|
|
72
|
+
|
|
20
73
|
|
|
21
74
|
class Transaction:
|
|
22
75
|
"""
|
|
@@ -32,6 +85,10 @@ class Transaction:
|
|
|
32
85
|
# R5 — Transaction-scoped query cache (opt-in via cache=True on select).
|
|
33
86
|
self.__query_cache: OrderedDict = OrderedDict()
|
|
34
87
|
self.__query_cache_max = _DEFAULT_QUERY_CACHE_SIZE
|
|
88
|
+
# R12 — Observability counters.
|
|
89
|
+
self._query_count = 0
|
|
90
|
+
self._query_time_ms = 0.0
|
|
91
|
+
self._return_default_count = 0
|
|
35
92
|
|
|
36
93
|
def __str__(self):
|
|
37
94
|
config = mask_config_for_display(self.engine.config)
|
|
@@ -119,6 +176,7 @@ class Transaction:
|
|
|
119
176
|
if prepare is None:
|
|
120
177
|
prepare = getattr(self.engine, "prepare_enabled", False)
|
|
121
178
|
|
|
179
|
+
t0 = _time.perf_counter()
|
|
122
180
|
try:
|
|
123
181
|
if parms:
|
|
124
182
|
cursor.execute(sql, parms, prepare=prepare)
|
|
@@ -136,6 +194,24 @@ class Transaction:
|
|
|
136
194
|
except Exception as e:
|
|
137
195
|
raise self.engine.process_error(e, sql, parms)
|
|
138
196
|
|
|
197
|
+
elapsed_ms = (_time.perf_counter() - t0) * 1000
|
|
198
|
+
self._query_count += 1
|
|
199
|
+
self._query_time_ms += elapsed_ms
|
|
200
|
+
|
|
201
|
+
# R12 — Slow query logging.
|
|
202
|
+
if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
|
|
203
|
+
op = _classify_sql(sql)
|
|
204
|
+
tbl = _extract_table_name(sql)
|
|
205
|
+
_logger.warning(
|
|
206
|
+
"Slow query (%s): %.1f ms table=%s",
|
|
207
|
+
op, elapsed_ms, tbl,
|
|
208
|
+
extra={
|
|
209
|
+
"query_duration_ms": round(elapsed_ms, 1),
|
|
210
|
+
"table_name": tbl,
|
|
211
|
+
"operation": op,
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
139
215
|
if single:
|
|
140
216
|
self.connection.autocommit = False
|
|
141
217
|
|
|
@@ -195,6 +271,15 @@ class Transaction:
|
|
|
195
271
|
if debug:
|
|
196
272
|
print(f"{id(self)} --- connection commit.")
|
|
197
273
|
self.connection.commit()
|
|
274
|
+
if self._query_count:
|
|
275
|
+
_logger.debug(
|
|
276
|
+
"Transaction commit: %d queries in %.1f ms",
|
|
277
|
+
self._query_count, self._query_time_ms,
|
|
278
|
+
extra={
|
|
279
|
+
"query_count": self._query_count,
|
|
280
|
+
"query_time_ms": round(self._query_time_ms, 1),
|
|
281
|
+
},
|
|
282
|
+
)
|
|
198
283
|
|
|
199
284
|
def rollback(self):
|
|
200
285
|
"""
|
|
@@ -159,6 +159,7 @@ tests/test_iconv_money_to_cents.py
|
|
|
159
159
|
tests/test_lambda_handler.py
|
|
160
160
|
tests/test_lambda_handler_auth.py
|
|
161
161
|
tests/test_mixins_import.py
|
|
162
|
+
tests/test_observability.py
|
|
162
163
|
tests/test_payment_braintree_adapter.py
|
|
163
164
|
tests/test_payment_demo_profiles.py
|
|
164
165
|
tests/test_payment_profiles.py
|