velocity-python 0.1.113__tar.gz → 0.1.115__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 (305) hide show
  1. {velocity_python-0.1.113/src/velocity_python.egg-info → velocity_python-0.1.115}/PKG-INFO +2 -1
  2. {velocity_python-0.1.113 → velocity_python-0.1.115}/README.md +1 -0
  3. {velocity_python-0.1.113 → velocity_python-0.1.115}/pyproject.toml +1 -1
  4. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/__init__.py +1 -1
  5. velocity_python-0.1.115/src/velocity/aws/ops.py +500 -0
  6. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/result.py +13 -4
  7. {velocity_python-0.1.113 → velocity_python-0.1.115/src/velocity_python.egg-info}/PKG-INFO +2 -1
  8. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity_python.egg-info/SOURCES.txt +3 -0
  9. velocity_python-0.1.115/tests/test_ops.py +395 -0
  10. velocity_python-0.1.115/tests/test_result_returning.py +70 -0
  11. {velocity_python-0.1.113 → velocity_python-0.1.115}/LICENSE +0 -0
  12. {velocity_python-0.1.113 → velocity_python-0.1.115}/setup.cfg +0 -0
  13. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/__init__.py +0 -0
  14. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/amplify.py +0 -0
  15. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/amplify_build.py +0 -0
  16. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/__init__.py +0 -0
  17. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/backfill.py +0 -0
  18. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/indexing.py +0 -0
  19. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/references.py +0 -0
  20. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/service.py +0 -0
  21. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/assets/usage_index.py +0 -0
  22. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/blobs.py +0 -0
  23. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/__init__.py +0 -0
  24. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/base.py +0 -0
  25. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/dispatch.py +0 -0
  26. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/infra.py +0 -0
  27. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/jobs.py +0 -0
  28. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/provisioning.py +0 -0
  29. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/retention.py +0 -0
  30. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/security.py +0 -0
  31. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/capabilities/single_session.py +0 -0
  32. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/__init__.py +0 -0
  33. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/config.py +0 -0
  34. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/operations.py +0 -0
  35. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/policies.py +0 -0
  36. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/reconcile.py +0 -0
  37. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/deploy/targets.py +0 -0
  38. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/dirty_pipeline.py +0 -0
  39. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/email.py +0 -0
  40. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/feature_flags.py +0 -0
  41. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/__init__.py +0 -0
  42. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/base_handler.py +0 -0
  43. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/context.py +0 -0
  44. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/context_factory.py +0 -0
  45. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/exceptions.py +0 -0
  46. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  47. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/masquerade.py +0 -0
  48. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  49. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  50. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  51. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  52. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  53. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/perf.py +0 -0
  54. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/response.py +0 -0
  55. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  56. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/job_lifecycle.py +0 -0
  57. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/job_results.py +0 -0
  58. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/mirror.py +0 -0
  59. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/s3.py +0 -0
  60. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/sms.py +0 -0
  61. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/ssm_config.py +0 -0
  62. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/__init__.py +0 -0
  63. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  64. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  65. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  66. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  67. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  68. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_email.py +0 -0
  69. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  70. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_response.py +0 -0
  71. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/aws/tests/test_sms.py +0 -0
  72. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/cache.py +0 -0
  73. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/__init__.py +0 -0
  74. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/__init__.py +0 -0
  75. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/async_support.py +0 -0
  76. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/column.py +0 -0
  77. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/database.py +0 -0
  78. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/decorators.py +0 -0
  79. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/engine.py +0 -0
  80. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/jsonproxy.py +0 -0
  81. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/lazy_engine.py +0 -0
  82. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/row.py +0 -0
  83. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/sequence.py +0 -0
  84. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/table.py +0 -0
  85. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/transaction.py +0 -0
  86. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/core/view.py +0 -0
  87. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/exceptions.py +0 -0
  88. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/maintenance.py +0 -0
  89. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/migrations.py +0 -0
  90. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/__init__.py +0 -0
  91. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/base/__init__.py +0 -0
  92. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/base/initializer.py +0 -0
  93. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/base/operators.py +0 -0
  94. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/base/sql.py +0 -0
  95. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/base/types.py +0 -0
  96. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/mysql/__init__.py +0 -0
  97. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/mysql/operators.py +0 -0
  98. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/mysql/reserved.py +0 -0
  99. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/mysql/sql.py +0 -0
  100. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/mysql/types.py +0 -0
  101. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/postgres/__init__.py +0 -0
  102. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/postgres/operators.py +0 -0
  103. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/postgres/reserved.py +0 -0
  104. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/postgres/sql.py +0 -0
  105. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/postgres/types.py +0 -0
  106. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  107. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlite/operators.py +0 -0
  108. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  109. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlite/sql.py +0 -0
  110. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlite/types.py +0 -0
  111. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  112. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  113. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  114. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  115. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/sqlserver/types.py +0 -0
  116. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/servers/tablehelper.py +0 -0
  117. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/__init__.py +0 -0
  118. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/common_db_test.py +0 -0
  119. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/__init__.py +0 -0
  120. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/common.py +0 -0
  121. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/conftest.py +0 -0
  122. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_column.py +0 -0
  123. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  124. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_database.py +0 -0
  125. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  126. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  127. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  128. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_result.py +0 -0
  129. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_row.py +0 -0
  130. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  131. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  132. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  133. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  134. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  135. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_table.py +0 -0
  136. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  137. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  138. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/sql/__init__.py +0 -0
  139. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/sql/common.py +0 -0
  140. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  141. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  142. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  143. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_db_utils.py +0 -0
  144. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_postgres.py +0 -0
  145. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  146. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  147. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_result_caching.py +0 -0
  148. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  149. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  150. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  151. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  152. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_sql_builder.py +0 -0
  153. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_tablehelper.py +0 -0
  154. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/tests/test_view_helper.py +0 -0
  155. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/db/utils.py +0 -0
  156. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/logging.py +0 -0
  157. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/__init__.py +0 -0
  158. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/conv/__init__.py +0 -0
  159. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/conv/iconv.py +0 -0
  160. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/conv/oconv.py +0 -0
  161. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/db.py +0 -0
  162. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/export.py +0 -0
  163. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/format.py +0 -0
  164. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/mail.py +0 -0
  165. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/merge.py +0 -0
  166. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/pdf.py +0 -0
  167. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/__init__.py +0 -0
  168. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_db.py +0 -0
  169. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_fix.py +0 -0
  170. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_format.py +0 -0
  171. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_iconv.py +0 -0
  172. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_mail_send.py +0 -0
  173. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_merge.py +0 -0
  174. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_oconv.py +0 -0
  175. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_original_error.py +0 -0
  176. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tests/test_timer.py +0 -0
  177. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/timer.py +0 -0
  178. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/misc/tools.py +0 -0
  179. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/__init__.py +0 -0
  180. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/core/__init__.py +0 -0
  181. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/core/capabilities.py +0 -0
  182. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/core/store.py +0 -0
  183. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/core/where.py +0 -0
  184. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/exceptions.py +0 -0
  185. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/__init__.py +0 -0
  186. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/base/__init__.py +0 -0
  187. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  188. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  189. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  190. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  191. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  192. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  193. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  194. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  195. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  196. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  197. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  198. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  199. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/__init__.py +0 -0
  200. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/authorizenet_adapter.py +0 -0
  201. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/authorizenet_mirror.py +0 -0
  202. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/base_adapter.py +0 -0
  203. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/braintree_adapter.py +0 -0
  204. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/braintree_mirror.py +0 -0
  205. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/charge_rules.py +0 -0
  206. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/stripe_adapter.py +0 -0
  207. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity/payment/stripe_mirror.py +0 -0
  208. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  209. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity_python.egg-info/entry_points.txt +0 -0
  210. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity_python.egg-info/requires.txt +0 -0
  211. {velocity_python-0.1.113 → velocity_python-0.1.115}/src/velocity_python.egg-info/top_level.txt +0 -0
  212. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_access_error_403.py +0 -0
  213. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_activity_tracking_aborted_tx.py +0 -0
  214. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_amplify_build.py +0 -0
  215. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_asset_indexing.py +0 -0
  216. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_asset_references.py +0 -0
  217. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_assets_service.py +0 -0
  218. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_async_support.py +0 -0
  219. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_batch_operations.py +0 -0
  220. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_blob_store.py +0 -0
  221. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_capabilities.py +0 -0
  222. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_capability_catalog.py +0 -0
  223. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_cognito_user_cache.py +0 -0
  224. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_column_tx_arg.py +0 -0
  225. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_concurrency_safety.py +0 -0
  226. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_connection_pool.py +0 -0
  227. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_connection_resilience.py +0 -0
  228. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_context_flags_loader.py +0 -0
  229. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_context_job_descriptions.py +0 -0
  230. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_data_service_query_cap.py +0 -0
  231. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_data_service_renumber.py +0 -0
  232. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_data_service_save_as.py +0 -0
  233. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_db_credentials_ssm_cascade.py +0 -0
  234. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_db_maintenance.py +0 -0
  235. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_decorators.py +0 -0
  236. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_delete_hook_fk_blocked.py +0 -0
  237. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_dirty_pipeline_fast_path.py +0 -0
  238. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_email_processing.py +0 -0
  239. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_enqueue_send_failures.py +0 -0
  240. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_feature_flags.py +0 -0
  241. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_get_cognito_user_provider.py +0 -0
  242. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_http_handler_rollback.py +0 -0
  243. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_iconv_money_to_cents.py +0 -0
  244. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_identifier_injection_guard.py +0 -0
  245. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_job_handler.py +0 -0
  246. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_job_lifecycle.py +0 -0
  247. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_job_results.py +0 -0
  248. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_json_columns.py +0 -0
  249. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_jsonb_dict_adapter.py +0 -0
  250. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_lambda_handler.py +0 -0
  251. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_lambda_handler_auth.py +0 -0
  252. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_lambda_handler_masquerade.py +0 -0
  253. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_lambda_handler_user_cache.py +0 -0
  254. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_lazy_engine.py +0 -0
  255. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_masquerade_grant.py +0 -0
  256. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_mirror.py +0 -0
  257. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_mixins_import.py +0 -0
  258. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_n_plus_one.py +0 -0
  259. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_dynamodb.py +0 -0
  260. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_memory.py +0 -0
  261. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_mongodb.py +0 -0
  262. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_redis.py +0 -0
  263. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_translation.py +0 -0
  264. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_nosql_where.py +0 -0
  265. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_observability.py +0 -0
  266. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_on_commit.py +0 -0
  267. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_authorizenet_adapter.py +0 -0
  268. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_braintree_adapter.py +0 -0
  269. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_braintree_mirror.py +0 -0
  270. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_mirror_empty_table.py +0 -0
  271. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_profile_sorting.py +0 -0
  272. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_payment_stripe_adapter.py +0 -0
  273. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_pdf.py +0 -0
  274. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_prepared_statements.py +0 -0
  275. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_psycopg3_upgrade.py +0 -0
  276. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_query_cache.py +0 -0
  277. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_query_missing_table_unlocked.py +0 -0
  278. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_read_cache.py +0 -0
  279. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_response.py +0 -0
  280. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_restricted_direct_tables.py +0 -0
  281. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_retry_side_effect_guard.py +0 -0
  282. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_return_default_safety.py +0 -0
  283. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_row_batch_update.py +0 -0
  284. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_row_cache_staleness.py +0 -0
  285. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_schema_migrations.py +0 -0
  286. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_security_capability.py +0 -0
  287. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_security_hardening.py +0 -0
  288. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_server_cursor.py +0 -0
  289. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_session_control.py +0 -0
  290. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_single_autocommit_safety.py +0 -0
  291. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_single_session_capability.py +0 -0
  292. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_spreadsheet_functions.py +0 -0
  293. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_sqlite_backend.py +0 -0
  294. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_sqs_per_record_transactions.py +0 -0
  295. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_ssm_config.py +0 -0
  296. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_store_user_data.py +0 -0
  297. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  298. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_table_alter.py +0 -0
  299. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_table_find_cache.py +0 -0
  300. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_table_purge.py +0 -0
  301. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_transaction_class_wrapping.py +0 -0
  302. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_transaction_commit_and_ownership.py +0 -0
  303. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_transaction_edge_cases.py +0 -0
  304. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_where_clause_validation.py +0 -0
  305. {velocity_python-0.1.113 → velocity_python-0.1.115}/tests/test_write_hook_create_flow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.113
3
+ Version: 0.1.115
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
@@ -252,6 +252,7 @@ Full documentation is included in the `docs/` directory of the source distributi
252
252
  | Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
253
253
  | AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions, async jobs (JobHandler + job result stores) |
254
254
  | Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
255
+ | Operations Dashboard | `docs/operations.md` | What the automation is doing: schedules, jobs, failures, stuck work, with drill-down (`velocity.aws.ops` + velocity-react `OpsDashboard`) |
255
256
  | NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
256
257
  | Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
257
258
  | Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
@@ -158,6 +158,7 @@ Full documentation is included in the `docs/` directory of the source distributi
158
158
  | Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
159
159
  | AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions, async jobs (JobHandler + job result stores) |
160
160
  | Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
161
+ | Operations Dashboard | `docs/operations.md` | What the automation is doing: schedules, jobs, failures, stuck work, with drill-down (`velocity.aws.ops` + velocity-react `OpsDashboard`) |
161
162
  | NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
162
163
  | Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
163
164
  | Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.113"
7
+ version = "0.1.115"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.113"
1
+ __version__ = version = "0.1.115"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,500 @@
1
+ """Operations read model: what the automation is doing, from velocity's own tables.
2
+
3
+ Every velocity app already records its automated work — schedules in
4
+ ``sys_task_triggers`` (run by ``program_objects`` stubs), queued work in
5
+ ``aws_job_activity`` (JobHandler), guard refusals in ``sys_security_events``,
6
+ the capabilities switched on in ``sys_capabilities`` — but nothing read those
7
+ tables *together*. This module is that read model: one overview for a
8
+ dashboard, and the three drill-downs under it. It owns no table and writes
9
+ nothing; an app gets the whole feature by mixing :class:`OpsDashboardMixin`
10
+ into a web handler (velocity-react's ``OpsDashboard`` is the matching UI).
11
+
12
+ * :func:`overview` — headline tiles, a zero-filled runs-over-time series,
13
+ per-action rollups (runs, failures, p50/p95/max duration, last outcome),
14
+ schedule health, the latest failures, stuck work, enabled capabilities.
15
+ * :func:`action_runs` — the recent runs of one action.
16
+ * :func:`job_detail` — one run in full (payload, message, error, traceback).
17
+ * :func:`schedule_detail` — one schedule, its program, and its recent runs.
18
+
19
+ Timestamps are the database's (naive UTC) and leave here as ISO-8601 with a
20
+ ``Z``. The aggregate SQL is PostgreSQL (``filter``, ``percentile_cont``); every
21
+ statement carries an ``/* ops:<name> */`` marker so it is recognisable in
22
+ ``pg_stat_activity`` and slow-query logs. A section whose table does not exist
23
+ is simply empty — an app that has never provisioned security still gets a
24
+ dashboard.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import datetime
30
+ import json
31
+ from decimal import Decimal
32
+
33
+ from velocity.aws.job_lifecycle import (
34
+ DEFAULT_STALE_AFTER,
35
+ JOB_ACTIVITY_TABLE,
36
+ STALE_JOB_STATUSES,
37
+ )
38
+ from velocity.cache import db_identity, memo
39
+
40
+ TRIGGERS_TABLE = "sys_task_triggers"
41
+ PROGRAMS_TABLE = "program_objects"
42
+ SECURITY_EVENTS_TABLE = "sys_security_events"
43
+ CAPABILITIES_TABLE = "sys_capabilities"
44
+
45
+ DEFAULT_HOURS = 24
46
+ MAX_HOURS = 24 * 30
47
+ OVERVIEW_CACHE_SECONDS = 15 # absorbs several tabs polling the same window
48
+ SCHEMA_CACHE_SECONDS = 300
49
+ OVERDUE_GRACE = datetime.timedelta(minutes=10)
50
+ RECENT_FAILURES = 10
51
+ PROGRAM_JOB_ACTION = "run-program-object"
52
+ PROGRAM_JOB_DESCRIPTION = "Program Object: {handle}"
53
+
54
+
55
+ # ── small shapes ─────────────────────────────────────────────────────────
56
+
57
+
58
+ def _utcnow():
59
+ return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
60
+
61
+
62
+ def _iso(value):
63
+ """Database timestamps are naive UTC; say so on the wire."""
64
+ if value is None:
65
+ return None
66
+ if isinstance(value, datetime.datetime):
67
+ if value.tzinfo is not None:
68
+ value = value.astimezone(datetime.timezone.utc).replace(tzinfo=None)
69
+ return value.isoformat(timespec="seconds") + "Z"
70
+ return str(value)
71
+
72
+
73
+ def _num(value, digits=2):
74
+ if value is None:
75
+ return None
76
+ if isinstance(value, Decimal):
77
+ value = float(value)
78
+ return round(value, digits) if isinstance(value, float) else value
79
+
80
+
81
+ def _seconds(value):
82
+ """An INTERVAL (timedelta) or number as seconds."""
83
+ if value is None:
84
+ return None
85
+ if isinstance(value, datetime.timedelta):
86
+ return round(value.total_seconds(), 2)
87
+ return _num(value)
88
+
89
+
90
+ def _first_line(text, limit=240):
91
+ if not text:
92
+ return None
93
+ line = str(text).strip().splitlines()[0] if str(text).strip() else ""
94
+ return line[:limit] or None
95
+
96
+
97
+ def _maybe_json(text):
98
+ if not isinstance(text, str) or not text.strip():
99
+ return text
100
+ try:
101
+ return json.loads(text)
102
+ except ValueError:
103
+ return text
104
+
105
+
106
+ def clamp_hours(value, default=DEFAULT_HOURS):
107
+ try:
108
+ hours = int(value)
109
+ except (TypeError, ValueError):
110
+ return default
111
+ return max(1, min(MAX_HOURS, hours))
112
+
113
+
114
+ def bucket_minutes(hours):
115
+ """Bucket width for the runs-over-time series: about 24-56 columns."""
116
+ if hours <= 6:
117
+ return 15
118
+ if hours <= 48:
119
+ return 60
120
+ if hours <= 24 * 7:
121
+ return 180
122
+ return 720
123
+
124
+
125
+ def _has(tx, table):
126
+ try:
127
+ return bool(tx.table(table).exists(cache_ttl=SCHEMA_CACHE_SECONDS))
128
+ except Exception:
129
+ return False
130
+
131
+
132
+ def _rows(tx, sql, params=()):
133
+ return [dict(row) for row in tx.execute(sql, list(params)).as_dict().all()]
134
+
135
+
136
+ # ── pure shaping (unit-tested without a database) ────────────────────────
137
+
138
+
139
+ def fill_timeline(rows, *, since, until, minutes):
140
+ """Zero-filled buckets from ``since`` to ``until`` — a gap in the data is a
141
+ real zero on the chart, not a missing column."""
142
+ step = datetime.timedelta(minutes=minutes)
143
+ epoch = datetime.datetime(1970, 1, 1)
144
+ start = epoch + step * ((since - epoch) // step)
145
+ by_bucket = {}
146
+ for row in rows:
147
+ bucket = row.get("bucket")
148
+ if isinstance(bucket, datetime.datetime) and bucket.tzinfo is not None:
149
+ bucket = bucket.astimezone(datetime.timezone.utc).replace(tzinfo=None)
150
+ by_bucket[bucket] = row
151
+ out = []
152
+ cursor = start
153
+ while cursor <= until:
154
+ row = by_bucket.get(cursor, {})
155
+ out.append(
156
+ {
157
+ "bucket": _iso(cursor),
158
+ "completed": int(row.get("completed") or 0),
159
+ "failed": int(row.get("failed") or 0),
160
+ "other": int(row.get("other") or 0),
161
+ }
162
+ )
163
+ cursor += step
164
+ return out
165
+
166
+
167
+ def schedule_health(trigger, *, now, grace=OVERDUE_GRACE):
168
+ """``disabled`` | ``never`` (enabled, never ran) | ``overdue`` (its next
169
+ run is in the past beyond the grace) | ``ok``."""
170
+ if not trigger.get("enabled"):
171
+ return "disabled"
172
+ next_run = trigger.get("next_run")
173
+ if next_run is not None and next_run + grace < now:
174
+ return "overdue"
175
+ if not trigger.get("run_count") and trigger.get("last_run") is None:
176
+ return "never"
177
+ return "ok"
178
+
179
+
180
+ def shape_action(row, last_status=None):
181
+ runs = int(row.get("runs") or 0)
182
+ failed = int(row.get("failed") or 0)
183
+ return {
184
+ "action": row.get("action"),
185
+ "runs": runs,
186
+ "completed": int(row.get("completed") or 0),
187
+ "failed": failed,
188
+ "cancelled": int(row.get("cancelled") or 0),
189
+ "open": int(row.get("open") or 0),
190
+ "stale": int(row.get("stale") or 0),
191
+ "fail_rate": round(failed / runs, 4) if runs else 0.0,
192
+ "p50_seconds": _num(row.get("p50_seconds")),
193
+ "p95_seconds": _num(row.get("p95_seconds")),
194
+ "max_seconds": _num(row.get("max_seconds")),
195
+ "last_run": _iso(row.get("last_run")),
196
+ "last_status": last_status,
197
+ }
198
+
199
+
200
+ def shape_run(row):
201
+ return {
202
+ "job_id": row.get("job_id"),
203
+ "action": row.get("action"),
204
+ "status": row.get("status"),
205
+ "created": _iso(row.get("sys_created")),
206
+ "started": _iso(row.get("handler_start_timestamp")),
207
+ "completed": _iso(row.get("handler_complete_timestamp")),
208
+ "duration_seconds": _seconds(row.get("handler_duration")),
209
+ "created_by": row.get("created_by"),
210
+ "description": row.get("description"),
211
+ "error": _first_line(row.get("error")),
212
+ }
213
+
214
+
215
+ def shape_schedule(trigger, program=None, *, now):
216
+ program = program or {}
217
+ return {
218
+ "name": trigger.get("name"),
219
+ "trigger_name": trigger.get("trigger_name"),
220
+ "description": trigger.get("description"),
221
+ "cron": trigger.get("cron_expression"),
222
+ "enabled": bool(trigger.get("enabled")),
223
+ "run_count": int(trigger.get("run_count") or 0),
224
+ "last_run": _iso(trigger.get("last_run")),
225
+ "next_run": _iso(trigger.get("next_run")),
226
+ "health": schedule_health(trigger, now=now),
227
+ "program": program.get("program_handle"),
228
+ "program_name": program.get("program_name"),
229
+ }
230
+
231
+
232
+ # ── the read model ───────────────────────────────────────────────────────
233
+
234
+ _RUN_COLUMNS = (
235
+ "job_id, action, status, sys_created, handler_start_timestamp, "
236
+ "handler_complete_timestamp, handler_duration, created_by, description, error"
237
+ )
238
+
239
+
240
+ def _open_statuses():
241
+ return sorted(STALE_JOB_STATUSES)
242
+
243
+
244
+ def overview(tx, *, hours=DEFAULT_HOURS, now=None):
245
+ """The dashboard in one call. Cached per container for a few seconds, so
246
+ several tabs polling the same window cost one set of queries."""
247
+ hours = clamp_hours(hours)
248
+ if now is not None:
249
+ return _overview(tx, hours, now)
250
+ key = ("ops", "overview", db_identity(getattr(tx, "engine", None)), hours)
251
+ return memo(key, lambda: _overview(tx, hours, _utcnow()), OVERVIEW_CACHE_SECONDS)
252
+
253
+
254
+ def _overview(tx, hours, now):
255
+ since = now - datetime.timedelta(hours=hours)
256
+ stale_before = now - DEFAULT_STALE_AFTER
257
+ minutes = bucket_minutes(hours)
258
+ actions, timeline, failures, stuck = [], [], [], 0
259
+
260
+ if _has(tx, JOB_ACTIVITY_TABLE):
261
+ open_statuses = _open_statuses()
262
+ marks = ", ".join(["%s"] * len(open_statuses))
263
+ rollups = _rows(
264
+ tx,
265
+ f"/* ops:actions */ select action, count(*) as runs, "
266
+ "count(*) filter (where status = 'Completed') as completed, "
267
+ "count(*) filter (where status = 'Failed') as failed, "
268
+ "count(*) filter (where status = 'Cancelled') as cancelled, "
269
+ f"count(*) filter (where status in ({marks})) as open, "
270
+ f"count(*) filter (where status in ({marks}) and "
271
+ "coalesce(handler_start_timestamp, sys_created) < %s) as stale, "
272
+ "percentile_cont(0.5) within group (order by extract(epoch from "
273
+ "handler_duration)) as p50_seconds, "
274
+ "percentile_cont(0.95) within group (order by extract(epoch from "
275
+ "handler_duration)) as p95_seconds, "
276
+ "max(extract(epoch from handler_duration)) as max_seconds, "
277
+ "max(sys_created) as last_run "
278
+ f"from {JOB_ACTIVITY_TABLE} where sys_created >= %s "
279
+ "group by action order by failed desc, runs desc, action",
280
+ [*open_statuses, *open_statuses, stale_before, since],
281
+ )
282
+ last = {
283
+ row["action"]: row["status"]
284
+ for row in _rows(
285
+ tx,
286
+ "/* ops:last-status */ select distinct on (action) action, status "
287
+ f"from {JOB_ACTIVITY_TABLE} where sys_created >= %s "
288
+ "order by action, sys_id desc",
289
+ [since],
290
+ )
291
+ }
292
+ actions = [shape_action(row, last.get(row.get("action"))) for row in rollups]
293
+
294
+ seconds = minutes * 60
295
+ timeline = fill_timeline(
296
+ _rows(
297
+ tx,
298
+ "/* ops:timeline */ select to_timestamp(floor(extract(epoch from "
299
+ "sys_created) / %s) * %s) at time zone 'UTC' as bucket, "
300
+ "count(*) filter (where status = 'Completed') as completed, "
301
+ "count(*) filter (where status = 'Failed') as failed, "
302
+ "count(*) filter (where status not in ('Completed', 'Failed')) "
303
+ f"as other from {JOB_ACTIVITY_TABLE} where sys_created >= %s "
304
+ "group by 1 order by 1",
305
+ [seconds, seconds, since],
306
+ ),
307
+ since=since,
308
+ until=now,
309
+ minutes=minutes,
310
+ )
311
+ failures = [
312
+ shape_run(row)
313
+ for row in _rows(
314
+ tx,
315
+ f"/* ops:failures */ select {_RUN_COLUMNS} from {JOB_ACTIVITY_TABLE} "
316
+ "where status = 'Failed' and sys_created >= %s "
317
+ "order by sys_id desc limit %s",
318
+ [since, RECENT_FAILURES],
319
+ )
320
+ ]
321
+ # stuck work is counted over ALL time: a job abandoned last week is
322
+ # still stuck today, whatever window the dashboard is showing
323
+ stuck = int(
324
+ _rows(
325
+ tx,
326
+ f"/* ops:stuck */ select count(*) as n from {JOB_ACTIVITY_TABLE} "
327
+ f"where status in ({marks}) and "
328
+ "coalesce(handler_start_timestamp, sys_created) < %s",
329
+ [*open_statuses, stale_before],
330
+ )[0]["n"]
331
+ or 0
332
+ )
333
+
334
+ schedules = _schedules(tx, now)
335
+ refusals = None
336
+ if _has(tx, SECURITY_EVENTS_TABLE):
337
+ refusals = int(
338
+ _rows(
339
+ tx,
340
+ "/* ops:refusals */ select count(*) as n from "
341
+ f"{SECURITY_EVENTS_TABLE} where sys_created >= %s",
342
+ [since],
343
+ )[0]["n"]
344
+ or 0
345
+ )
346
+ capabilities = []
347
+ if _has(tx, CAPABILITIES_TABLE):
348
+ capabilities = [
349
+ {"name": row.get("name"), "enabled": row.get("enabled") is not False}
350
+ for row in tx.table(CAPABILITIES_TABLE).select(orderby="name")
351
+ ]
352
+
353
+ runs = sum(a["runs"] for a in actions)
354
+ failed = sum(a["failed"] for a in actions)
355
+ return {
356
+ "generated_at": _iso(now),
357
+ "window_hours": hours,
358
+ "bucket_minutes": minutes,
359
+ "tiles": {
360
+ "runs": runs,
361
+ "failed": failed,
362
+ "fail_rate": round(failed / runs, 4) if runs else 0.0,
363
+ "open": sum(a["open"] for a in actions),
364
+ "stuck": stuck,
365
+ "schedules_enabled": sum(1 for s in schedules if s["enabled"]),
366
+ "schedules_overdue": sum(1 for s in schedules if s["health"] == "overdue"),
367
+ "refusals": refusals,
368
+ },
369
+ "timeline": timeline,
370
+ "actions": actions,
371
+ "schedules": schedules,
372
+ "failures": failures,
373
+ "capabilities": capabilities,
374
+ }
375
+
376
+
377
+ def _schedules(tx, now):
378
+ if not _has(tx, TRIGGERS_TABLE):
379
+ return []
380
+ programs = {}
381
+ if _has(tx, PROGRAMS_TABLE):
382
+ for row in tx.table(PROGRAMS_TABLE).select(
383
+ ["program_name", "program_handle", "trigger"]
384
+ ):
385
+ programs[row.get("trigger")] = dict(row)
386
+ return [
387
+ shape_schedule(dict(row), programs.get(row.get("trigger_name")), now=now)
388
+ for row in tx.table(TRIGGERS_TABLE).select(orderby="name")
389
+ ]
390
+
391
+
392
+ def action_runs(tx, action, *, hours=DEFAULT_HOURS, limit=50, now=None):
393
+ """The recent runs of one action, newest first."""
394
+ hours = clamp_hours(hours)
395
+ now = now or _utcnow()
396
+ limit = max(1, min(200, int(limit or 50)))
397
+ runs = []
398
+ if action and _has(tx, JOB_ACTIVITY_TABLE):
399
+ runs = [
400
+ shape_run(row)
401
+ for row in _rows(
402
+ tx,
403
+ f"/* ops:action-runs */ select {_RUN_COLUMNS} from "
404
+ f"{JOB_ACTIVITY_TABLE} where action = %s and sys_created >= %s "
405
+ "order by sys_id desc limit %s",
406
+ [action, now - datetime.timedelta(hours=hours), limit],
407
+ )
408
+ ]
409
+ return {"action": action, "window_hours": hours, "runs": runs}
410
+
411
+
412
+ def job_detail(tx, job_id):
413
+ """One run in full; ``None`` when there is no such job."""
414
+ if not job_id or not _has(tx, JOB_ACTIVITY_TABLE):
415
+ return None
416
+ row = tx.table(JOB_ACTIVITY_TABLE).find({"job_id": job_id})
417
+ if not row:
418
+ return None
419
+ row = dict(row)
420
+ detail = shape_run(row)
421
+ detail.update(
422
+ {
423
+ "batch_id": row.get("batch_id"),
424
+ "payload": _maybe_json(row.get("payload")),
425
+ "message": _maybe_json(row.get("message")),
426
+ "error": row.get("error"),
427
+ "traceback": row.get("traceback"),
428
+ }
429
+ )
430
+ return detail
431
+
432
+
433
+ def schedule_detail(tx, trigger_name, *, limit=20, now=None):
434
+ """One schedule, the program it runs, and that program's recent runs."""
435
+ now = now or _utcnow()
436
+ if not trigger_name or not _has(tx, TRIGGERS_TABLE):
437
+ return None
438
+ trigger = tx.table(TRIGGERS_TABLE).find({"trigger_name": trigger_name})
439
+ if not trigger:
440
+ return None
441
+ program = None
442
+ if _has(tx, PROGRAMS_TABLE):
443
+ program = tx.table(PROGRAMS_TABLE).find({"trigger": trigger_name})
444
+ program = dict(program) if program else {}
445
+ detail = shape_schedule(dict(trigger), program, now=now)
446
+ detail["program_description"] = program.get("program_description")
447
+ detail["source_code"] = program.get("source_code")
448
+ runs = []
449
+ if program.get("program_handle") and _has(tx, JOB_ACTIVITY_TABLE):
450
+ runs = [
451
+ shape_run(row)
452
+ for row in _rows(
453
+ tx,
454
+ f"/* ops:schedule-runs */ select {_RUN_COLUMNS} from "
455
+ f"{JOB_ACTIVITY_TABLE} where action = %s and description = %s "
456
+ "order by sys_id desc limit %s",
457
+ [
458
+ PROGRAM_JOB_ACTION,
459
+ PROGRAM_JOB_DESCRIPTION.format(handle=program["program_handle"]),
460
+ max(1, min(100, int(limit or 20))),
461
+ ],
462
+ )
463
+ ]
464
+ detail["runs"] = runs
465
+ return detail
466
+
467
+
468
+ # ── the handler mixin ────────────────────────────────────────────────────
469
+
470
+
471
+ class OpsDashboardMixin:
472
+ """Web-handler mixin: the operations read model as four read-only actions
473
+ (``ops-overview``, ``ops-action-runs``, ``ops-job``, ``ops-schedule``).
474
+ Payloads: ``{hours}``, ``{action, hours, limit}``, ``{job_id}``,
475
+ ``{trigger_name}``. velocity-react's ``OpsDashboard`` consumes them."""
476
+
477
+ def OnActionOpsOverview(self, tx, context):
478
+ payload = context.payload() or {}
479
+ context.response().set_body(overview(tx, hours=payload.get("hours")))
480
+
481
+ def OnActionOpsActionRuns(self, tx, context):
482
+ payload = context.payload() or {}
483
+ context.response().set_body(
484
+ action_runs(
485
+ tx,
486
+ payload.get("action"),
487
+ hours=payload.get("hours"),
488
+ limit=payload.get("limit"),
489
+ )
490
+ )
491
+
492
+ def OnActionOpsJob(self, tx, context):
493
+ payload = context.payload() or {}
494
+ context.response().set_body({"job": job_detail(tx, payload.get("job_id"))})
495
+
496
+ def OnActionOpsSchedule(self, tx, context):
497
+ payload = context.payload() or {}
498
+ context.response().set_body(
499
+ {"schedule": schedule_detail(tx, payload.get("trigger_name"))}
500
+ )
@@ -81,10 +81,19 @@ class Result:
81
81
  if self._first_row_fetched or not self._cursor:
82
82
  return
83
83
 
84
- # Don't try to fetch from INSERT/UPDATE/DELETE operations
85
- # These operations don't return rows, only rowcount
86
- if self.__sql and self.__sql.strip().upper().startswith(
87
- ("INSERT", "UPDATE", "DELETE", "TRUNCATE")
84
+ # A write returns rows exactly when the driver describes a result set:
85
+ # INSERT/UPDATE/DELETE ... RETURNING (PostgreSQL), OUTPUT (SQL Server).
86
+ # Judging by the verb alone marked EVERY write as row-less, so
87
+ # `UPDATE ... RETURNING sys_id` silently reported nothing — a claim taken
88
+ # that way looked refused forever (tenspace.gmail, 2026-09-17). A write
89
+ # with no described columns has only a rowcount, and fetchone() on it
90
+ # raises on most drivers, so that case is still short-circuited.
91
+ if (
92
+ self.__sql
93
+ and self.__sql.strip()
94
+ .upper()
95
+ .startswith(("INSERT", "UPDATE", "DELETE", "TRUNCATE"))
96
+ and not self._headers
88
97
  ):
89
98
  self._exhausted = True
90
99
  self._first_row_fetched = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.113
3
+ Version: 0.1.115
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
@@ -252,6 +252,7 @@ Full documentation is included in the `docs/` directory of the source distributi
252
252
  | Configuration Reference | `docs/configuration.md` | All environment variables, engine options, and connection settings |
253
253
  | AWS Lambda Handlers | `docs/aws-handlers.md` | LambdaHandler, SqsHandler, auth modes, per-record transactions, async jobs (JobHandler + job result stores) |
254
254
  | Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
255
+ | Operations Dashboard | `docs/operations.md` | What the automation is doing: schedules, jobs, failures, stuck work, with drill-down (`velocity.aws.ops` + velocity-react `OpsDashboard`) |
255
256
  | NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
256
257
  | Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
257
258
  | Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
@@ -14,6 +14,7 @@ src/velocity/aws/feature_flags.py
14
14
  src/velocity/aws/job_lifecycle.py
15
15
  src/velocity/aws/job_results.py
16
16
  src/velocity/aws/mirror.py
17
+ src/velocity/aws/ops.py
17
18
  src/velocity/aws/s3.py
18
19
  src/velocity/aws/sms.py
19
20
  src/velocity/aws/ssm_config.py
@@ -259,6 +260,7 @@ tests/test_nosql_translation.py
259
260
  tests/test_nosql_where.py
260
261
  tests/test_observability.py
261
262
  tests/test_on_commit.py
263
+ tests/test_ops.py
262
264
  tests/test_payment_authorizenet_adapter.py
263
265
  tests/test_payment_braintree_adapter.py
264
266
  tests/test_payment_braintree_mirror.py
@@ -273,6 +275,7 @@ tests/test_query_missing_table_unlocked.py
273
275
  tests/test_read_cache.py
274
276
  tests/test_response.py
275
277
  tests/test_restricted_direct_tables.py
278
+ tests/test_result_returning.py
276
279
  tests/test_retry_side_effect_guard.py
277
280
  tests/test_return_default_safety.py
278
281
  tests/test_row_batch_update.py