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.
Files changed (179) hide show
  1. {velocity_python-0.1.3 → velocity_python-0.1.4}/PKG-INFO +1 -1
  2. {velocity_python-0.1.3 → velocity_python-0.1.4}/pyproject.toml +1 -1
  3. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/decorators.py +6 -0
  5. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/engine.py +63 -4
  6. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/transaction.py +85 -0
  7. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/PKG-INFO +1 -1
  8. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  9. velocity_python-0.1.4/tests/test_observability.py +443 -0
  10. {velocity_python-0.1.3 → velocity_python-0.1.4}/LICENSE +0 -0
  11. {velocity_python-0.1.3 → velocity_python-0.1.4}/README.md +0 -0
  12. {velocity_python-0.1.3 → velocity_python-0.1.4}/setup.cfg +0 -0
  13. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/__init__.py +0 -0
  14. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/formbuilder/__init__.py +0 -0
  15. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/formbuilder/reshaper.py +0 -0
  16. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/invoices.py +0 -0
  17. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/orders.py +0 -0
  18. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/payments.py +0 -0
  19. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/purchase_orders.py +0 -0
  20. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/__init__.py +0 -0
  21. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_email_processing.py +0 -0
  22. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
  23. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
  24. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/validators/__init__.py +0 -0
  25. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/app/validators/formbuilder_template.py +0 -0
  26. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/__init__.py +0 -0
  27. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/amplify.py +0 -0
  28. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/amplify_build.py +0 -0
  29. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/__init__.py +0 -0
  30. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/base_handler.py +0 -0
  31. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/context.py +0 -0
  32. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/context_factory.py +0 -0
  33. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/exceptions.py +0 -0
  34. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  35. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  36. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  37. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  38. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/perf.py +0 -0
  39. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/response.py +0 -0
  40. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  41. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/__init__.py +0 -0
  42. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  43. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  44. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/aws/tests/test_response.py +0 -0
  45. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/__init__.py +0 -0
  46. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/__init__.py +0 -0
  47. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/column.py +0 -0
  48. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/database.py +0 -0
  49. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/result.py +0 -0
  50. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/row.py +0 -0
  51. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/sequence.py +0 -0
  52. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/table.py +0 -0
  53. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/core/view.py +0 -0
  54. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/exceptions.py +0 -0
  55. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/__init__.py +0 -0
  56. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/__init__.py +0 -0
  57. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/initializer.py +0 -0
  58. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/operators.py +0 -0
  59. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/sql.py +0 -0
  60. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/base/types.py +0 -0
  61. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/__init__.py +0 -0
  62. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/operators.py +0 -0
  63. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/reserved.py +0 -0
  64. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/sql.py +0 -0
  65. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/types.py +0 -0
  66. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/__init__.py +0 -0
  67. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/operators.py +0 -0
  68. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/reserved.py +0 -0
  69. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/sql.py +0 -0
  70. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/types.py +0 -0
  71. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  72. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/operators.py +0 -0
  73. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  74. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/sql.py +0 -0
  75. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/types.py +0 -0
  76. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  77. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  78. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  79. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  80. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/types.py +0 -0
  81. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/servers/tablehelper.py +0 -0
  82. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/__init__.py +0 -0
  83. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/common_db_test.py +0 -0
  84. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/__init__.py +0 -0
  85. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/common.py +0 -0
  86. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_column.py +0 -0
  87. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  88. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_database.py +0 -0
  89. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  90. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  91. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  92. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_result.py +0 -0
  93. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row.py +0 -0
  94. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  95. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  96. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  97. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  98. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  99. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table.py +0 -0
  100. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  101. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  102. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/__init__.py +0 -0
  103. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/common.py +0 -0
  104. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  105. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  106. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  107. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_db_utils.py +0 -0
  108. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres.py +0 -0
  109. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  110. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  111. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_caching.py +0 -0
  112. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  113. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  114. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  115. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  116. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_sql_builder.py +0 -0
  117. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_tablehelper.py +0 -0
  118. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/tests/test_view_helper.py +0 -0
  119. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/db/utils.py +0 -0
  120. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/logging.py +0 -0
  121. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/__init__.py +0 -0
  122. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/__init__.py +0 -0
  123. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/iconv.py +0 -0
  124. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/conv/oconv.py +0 -0
  125. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/db.py +0 -0
  126. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/export.py +0 -0
  127. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/format.py +0 -0
  128. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/mail.py +0 -0
  129. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/merge.py +0 -0
  130. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/__init__.py +0 -0
  131. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_db.py +0 -0
  132. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_fix.py +0 -0
  133. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_format.py +0 -0
  134. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_iconv.py +0 -0
  135. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_merge.py +0 -0
  136. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_oconv.py +0 -0
  137. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_original_error.py +0 -0
  138. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tests/test_timer.py +0 -0
  139. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/timer.py +0 -0
  140. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/misc/tools.py +0 -0
  141. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/__init__.py +0 -0
  142. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/authorizenet_adapter.py +0 -0
  143. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/base_adapter.py +0 -0
  144. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/braintree_adapter.py +0 -0
  145. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/charge_rules.py +0 -0
  146. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/demo_profiles.py +0 -0
  147. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/profiles.py +0 -0
  148. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/router.py +0 -0
  149. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity/payment/stripe_adapter.py +0 -0
  150. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  151. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/requires.txt +0 -0
  152. {velocity_python-0.1.3 → velocity_python-0.1.4}/src/velocity_python.egg-info/top_level.txt +0 -0
  153. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_amplify_build.py +0 -0
  154. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_batch_operations.py +0 -0
  155. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_concurrency_safety.py +0 -0
  156. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_connection_pool.py +0 -0
  157. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_connection_resilience.py +0 -0
  158. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_decorators.py +0 -0
  159. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_formbuilder_reshaper.py +0 -0
  160. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_formbuilder_template_validator.py +0 -0
  161. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_iconv_money_to_cents.py +0 -0
  162. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_lambda_handler.py +0 -0
  163. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_lambda_handler_auth.py +0 -0
  164. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_mixins_import.py +0 -0
  165. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_braintree_adapter.py +0 -0
  166. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_demo_profiles.py +0 -0
  167. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_profiles.py +0 -0
  168. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_router.py +0 -0
  169. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_payment_stripe_adapter.py +0 -0
  170. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_prepared_statements.py +0 -0
  171. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_psycopg3_upgrade.py +0 -0
  172. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_query_cache.py +0 -0
  173. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_row_batch_update.py +0 -0
  174. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_row_cache_staleness.py +0 -0
  175. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_security_hardening.py +0 -0
  176. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_sqs_per_record_transactions.py +0 -0
  177. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  178. {velocity_python-0.1.3 → velocity_python-0.1.4}/tests/test_table_alter.py +0 -0
  179. {velocity_python-0.1.3 → velocity_python-0.1.4}/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
3
+ Version: 0.1.4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.3"
1
+ __version__ = version = "0.1.4"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -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
- logger.debug("Pool: reusing idle connection (pool_size=%d)", len(self._pool))
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
- logger.debug("Pool: created new connection (total=%d)", self._total_created)
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
- logger.debug("Engine.connect: obtained pooled connection")
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
- logger.debug("Engine.connect: created direct connection")
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
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -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