velocity-python 0.1.108__tar.gz → 0.1.110__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 (300) hide show
  1. {velocity_python-0.1.108/src/velocity_python.egg-info → velocity_python-0.1.110}/PKG-INFO +2 -1
  2. {velocity_python-0.1.108 → velocity_python-0.1.110}/README.md +1 -0
  3. {velocity_python-0.1.108 → velocity_python-0.1.110}/pyproject.toml +1 -1
  4. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/__init__.py +1 -1
  5. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/security.py +11 -13
  6. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/feature_flags.py +85 -55
  7. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/context.py +91 -48
  8. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/lambda_handler.py +26 -5
  9. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/data_service.py +10 -2
  10. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/session_control.py +4 -3
  11. velocity_python-0.1.110/src/velocity/aws/mirror.py +267 -0
  12. velocity_python-0.1.110/src/velocity/cache.py +182 -0
  13. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/table.py +131 -19
  14. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/transaction.py +7 -1
  15. {velocity_python-0.1.108 → velocity_python-0.1.110/src/velocity_python.egg-info}/PKG-INFO +2 -1
  16. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/SOURCES.txt +7 -0
  17. velocity_python-0.1.110/tests/test_cognito_user_cache.py +81 -0
  18. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_feature_flags.py +85 -0
  19. velocity_python-0.1.110/tests/test_lambda_handler_user_cache.py +87 -0
  20. velocity_python-0.1.110/tests/test_mirror.py +232 -0
  21. velocity_python-0.1.110/tests/test_read_cache.py +119 -0
  22. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_single_session_capability.py +4 -0
  23. velocity_python-0.1.110/tests/test_table_find_cache.py +172 -0
  24. {velocity_python-0.1.108 → velocity_python-0.1.110}/LICENSE +0 -0
  25. {velocity_python-0.1.108 → velocity_python-0.1.110}/setup.cfg +0 -0
  26. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/__init__.py +0 -0
  27. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/amplify.py +0 -0
  28. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/amplify_build.py +0 -0
  29. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/__init__.py +0 -0
  30. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/backfill.py +0 -0
  31. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/indexing.py +0 -0
  32. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/references.py +0 -0
  33. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/service.py +0 -0
  34. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/assets/usage_index.py +0 -0
  35. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/blobs.py +0 -0
  36. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/__init__.py +0 -0
  37. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/base.py +0 -0
  38. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/dispatch.py +0 -0
  39. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/infra.py +0 -0
  40. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/jobs.py +0 -0
  41. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/provisioning.py +0 -0
  42. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/retention.py +0 -0
  43. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/capabilities/single_session.py +0 -0
  44. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/__init__.py +0 -0
  45. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/config.py +0 -0
  46. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/operations.py +0 -0
  47. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/policies.py +0 -0
  48. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/reconcile.py +0 -0
  49. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/deploy/targets.py +0 -0
  50. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/dirty_pipeline.py +0 -0
  51. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/email.py +0 -0
  52. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/__init__.py +0 -0
  53. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/base_handler.py +0 -0
  54. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/context_factory.py +0 -0
  55. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/exceptions.py +0 -0
  56. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/masquerade.py +0 -0
  57. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  58. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  59. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  60. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/perf.py +0 -0
  61. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/response.py +0 -0
  62. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  63. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/job_lifecycle.py +0 -0
  64. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/job_results.py +0 -0
  65. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/s3.py +0 -0
  66. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/sms.py +0 -0
  67. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/ssm_config.py +0 -0
  68. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/__init__.py +0 -0
  69. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  70. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  71. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  72. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  73. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  74. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_email.py +0 -0
  75. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  76. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_response.py +0 -0
  77. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/aws/tests/test_sms.py +0 -0
  78. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/__init__.py +0 -0
  79. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/__init__.py +0 -0
  80. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/async_support.py +0 -0
  81. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/column.py +0 -0
  82. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/database.py +0 -0
  83. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/decorators.py +0 -0
  84. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/engine.py +0 -0
  85. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/jsonproxy.py +0 -0
  86. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/result.py +0 -0
  87. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/row.py +0 -0
  88. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/sequence.py +0 -0
  89. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/core/view.py +0 -0
  90. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/exceptions.py +0 -0
  91. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/maintenance.py +0 -0
  92. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/migrations.py +0 -0
  93. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/__init__.py +0 -0
  94. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/__init__.py +0 -0
  95. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/initializer.py +0 -0
  96. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/operators.py +0 -0
  97. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/sql.py +0 -0
  98. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/base/types.py +0 -0
  99. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/__init__.py +0 -0
  100. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/operators.py +0 -0
  101. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/reserved.py +0 -0
  102. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/sql.py +0 -0
  103. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/mysql/types.py +0 -0
  104. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/__init__.py +0 -0
  105. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/operators.py +0 -0
  106. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/reserved.py +0 -0
  107. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/sql.py +0 -0
  108. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/postgres/types.py +0 -0
  109. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  110. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/operators.py +0 -0
  111. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  112. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/sql.py +0 -0
  113. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlite/types.py +0 -0
  114. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  115. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  116. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  117. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  118. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/sqlserver/types.py +0 -0
  119. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/servers/tablehelper.py +0 -0
  120. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/__init__.py +0 -0
  121. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/common_db_test.py +0 -0
  122. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/__init__.py +0 -0
  123. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/common.py +0 -0
  124. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/conftest.py +0 -0
  125. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_column.py +0 -0
  126. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  127. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_database.py +0 -0
  128. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  129. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  130. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  131. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_result.py +0 -0
  132. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row.py +0 -0
  133. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  134. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  135. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  136. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  137. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  138. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table.py +0 -0
  139. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  140. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  141. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/__init__.py +0 -0
  142. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/common.py +0 -0
  143. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  144. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  145. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  146. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_db_utils.py +0 -0
  147. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres.py +0 -0
  148. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  149. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  150. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_caching.py +0 -0
  151. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  152. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  153. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  154. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  155. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_sql_builder.py +0 -0
  156. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_tablehelper.py +0 -0
  157. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/tests/test_view_helper.py +0 -0
  158. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/db/utils.py +0 -0
  159. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/logging.py +0 -0
  160. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/__init__.py +0 -0
  161. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/__init__.py +0 -0
  162. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/iconv.py +0 -0
  163. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/conv/oconv.py +0 -0
  164. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/db.py +0 -0
  165. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/export.py +0 -0
  166. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/format.py +0 -0
  167. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/mail.py +0 -0
  168. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/merge.py +0 -0
  169. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/pdf.py +0 -0
  170. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/__init__.py +0 -0
  171. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_db.py +0 -0
  172. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_fix.py +0 -0
  173. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_format.py +0 -0
  174. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_iconv.py +0 -0
  175. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_mail_send.py +0 -0
  176. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_merge.py +0 -0
  177. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_oconv.py +0 -0
  178. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_original_error.py +0 -0
  179. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tests/test_timer.py +0 -0
  180. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/timer.py +0 -0
  181. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/misc/tools.py +0 -0
  182. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/__init__.py +0 -0
  183. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/__init__.py +0 -0
  184. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/capabilities.py +0 -0
  185. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/store.py +0 -0
  186. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/core/where.py +0 -0
  187. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/exceptions.py +0 -0
  188. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/__init__.py +0 -0
  189. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/base/__init__.py +0 -0
  190. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  191. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  192. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  193. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  194. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  195. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  196. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  197. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  198. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  199. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  200. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  201. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  202. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/__init__.py +0 -0
  203. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_adapter.py +0 -0
  204. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/authorizenet_mirror.py +0 -0
  205. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/base_adapter.py +0 -0
  206. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/braintree_adapter.py +0 -0
  207. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/braintree_mirror.py +0 -0
  208. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/charge_rules.py +0 -0
  209. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/stripe_adapter.py +0 -0
  210. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity/payment/stripe_mirror.py +0 -0
  211. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  212. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/entry_points.txt +0 -0
  213. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/requires.txt +0 -0
  214. {velocity_python-0.1.108 → velocity_python-0.1.110}/src/velocity_python.egg-info/top_level.txt +0 -0
  215. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_access_error_403.py +0 -0
  216. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_activity_tracking_aborted_tx.py +0 -0
  217. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_amplify_build.py +0 -0
  218. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_asset_indexing.py +0 -0
  219. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_asset_references.py +0 -0
  220. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_assets_service.py +0 -0
  221. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_async_support.py +0 -0
  222. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_batch_operations.py +0 -0
  223. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_blob_store.py +0 -0
  224. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_capabilities.py +0 -0
  225. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_capability_catalog.py +0 -0
  226. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_column_tx_arg.py +0 -0
  227. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_concurrency_safety.py +0 -0
  228. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_connection_pool.py +0 -0
  229. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_connection_resilience.py +0 -0
  230. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_context_flags_loader.py +0 -0
  231. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_context_job_descriptions.py +0 -0
  232. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_query_cap.py +0 -0
  233. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_renumber.py +0 -0
  234. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_data_service_save_as.py +0 -0
  235. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_db_credentials_ssm_cascade.py +0 -0
  236. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_db_maintenance.py +0 -0
  237. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_decorators.py +0 -0
  238. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_delete_hook_fk_blocked.py +0 -0
  239. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_dirty_pipeline_fast_path.py +0 -0
  240. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_email_processing.py +0 -0
  241. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_enqueue_send_failures.py +0 -0
  242. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_get_cognito_user_provider.py +0 -0
  243. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_http_handler_rollback.py +0 -0
  244. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_iconv_money_to_cents.py +0 -0
  245. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_identifier_injection_guard.py +0 -0
  246. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_handler.py +0 -0
  247. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_lifecycle.py +0 -0
  248. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_job_results.py +0 -0
  249. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_json_columns.py +0 -0
  250. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_jsonb_dict_adapter.py +0 -0
  251. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler.py +0 -0
  252. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler_auth.py +0 -0
  253. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_lambda_handler_masquerade.py +0 -0
  254. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_masquerade_grant.py +0 -0
  255. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_mixins_import.py +0 -0
  256. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_n_plus_one.py +0 -0
  257. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_dynamodb.py +0 -0
  258. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_memory.py +0 -0
  259. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_mongodb.py +0 -0
  260. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_redis.py +0 -0
  261. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_translation.py +0 -0
  262. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_nosql_where.py +0 -0
  263. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_observability.py +0 -0
  264. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_on_commit.py +0 -0
  265. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_authorizenet_adapter.py +0 -0
  266. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_braintree_adapter.py +0 -0
  267. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_braintree_mirror.py +0 -0
  268. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_mirror_empty_table.py +0 -0
  269. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_profile_sorting.py +0 -0
  270. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_payment_stripe_adapter.py +0 -0
  271. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_pdf.py +0 -0
  272. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_prepared_statements.py +0 -0
  273. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_psycopg3_upgrade.py +0 -0
  274. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_query_cache.py +0 -0
  275. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_query_missing_table_unlocked.py +0 -0
  276. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_response.py +0 -0
  277. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_restricted_direct_tables.py +0 -0
  278. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_retry_side_effect_guard.py +0 -0
  279. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_return_default_safety.py +0 -0
  280. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_row_batch_update.py +0 -0
  281. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_row_cache_staleness.py +0 -0
  282. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_schema_migrations.py +0 -0
  283. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_security_capability.py +0 -0
  284. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_security_hardening.py +0 -0
  285. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_server_cursor.py +0 -0
  286. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_session_control.py +0 -0
  287. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_single_autocommit_safety.py +0 -0
  288. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_spreadsheet_functions.py +0 -0
  289. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sqlite_backend.py +0 -0
  290. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sqs_per_record_transactions.py +0 -0
  291. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_ssm_config.py +0 -0
  292. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_store_user_data.py +0 -0
  293. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  294. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_table_alter.py +0 -0
  295. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_table_purge.py +0 -0
  296. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_class_wrapping.py +0 -0
  297. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_commit_and_ownership.py +0 -0
  298. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_transaction_edge_cases.py +0 -0
  299. {velocity_python-0.1.108 → velocity_python-0.1.110}/tests/test_where_clause_validation.py +0 -0
  300. {velocity_python-0.1.108 → velocity_python-0.1.110}/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.108
3
+ Version: 0.1.110
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
@@ -251,6 +251,7 @@ Full documentation is included in the `docs/` directory of the source distributi
251
251
  | Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
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
+ | Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
254
255
  | NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
255
256
  | Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
256
257
  | Utilities | `docs/utilities.md` | Excel export, data conversion, formatting, timers, email parsing and SMTP sending |
@@ -157,6 +157,7 @@ Full documentation is included in the `docs/` directory of the source distributi
157
157
  | Async Support | `docs/async.md` | AsyncTransaction, AsyncTable, AsyncResult, parallel queries with gather() |
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
+ | Cross-database Mirroring | `docs/mirroring.md` | Keep a table identical in two databases (demo ↔ production) by natural key; rwx hooks, FK translation, reconcile |
160
161
  | NoSQL Adapters | `docs/nosql.md` | Redis, DynamoDB, MongoDB, Elasticsearch, Cassandra, Couchbase behind one capability-honest contract |
161
162
  | Payment Processing | `docs/payment.md` | Stripe and Braintree adapters, payment lifecycle |
162
163
  | 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.108"
7
+ version = "0.1.110"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.108"
1
+ __version__ = version = "0.1.110"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -27,9 +27,9 @@ from __future__ import annotations
27
27
  import datetime
28
28
  import ipaddress
29
29
  import logging
30
- import time
31
30
 
32
31
  from velocity.aws import feature_flags
32
+ from velocity.cache import memo, read_cache
33
33
 
34
34
  from .base import (
35
35
  CIDR_LIST,
@@ -62,11 +62,10 @@ KNOBS = {
62
62
  "security_events_days": "90",
63
63
  }
64
64
 
65
- _cache: dict = {}
66
-
67
-
68
65
  def clear_cache():
69
- _cache.clear()
66
+ """Forget the cached site row AND the shared flags row (tests)."""
67
+ read_cache.invalidate(SITE_TABLE)
68
+ feature_flags.clear_cache()
70
69
 
71
70
 
72
71
  # ── request facts ───────────────────────────────────────────────────────
@@ -172,13 +171,9 @@ def check_rate(tx, bucket, *, limit, now, table=RATE_TABLE):
172
171
 
173
172
 
174
173
  def _cached(key, loader, *, now=None):
175
- now = now or time.monotonic()
176
- hit = _cache.get(key)
177
- if hit and hit[0] > now:
178
- return hit[1]
179
- value = loader()
180
- _cache[key] = (now + CACHE_TTL_SECONDS, value)
181
- return value
174
+ """The process read cache (velocity.cache), tagged with the site table
175
+ so provisioning's own writes to it in this process drop the entry."""
176
+ return memo(("security",) + tuple(key), loader, CACHE_TTL_SECONDS, tables=(SITE_TABLE,), now=now)
182
177
 
183
178
 
184
179
  def _site_row(tx):
@@ -201,7 +196,10 @@ def _site_row(tx):
201
196
 
202
197
 
203
198
  def _flags(tx):
204
- return _cached(("flags",), lambda: feature_flags.get(tx))
199
+ """The stage's flags row from velocity's container cache — shared with
200
+ the handler context and session control, so the guards add no read of
201
+ their own to a request."""
202
+ return feature_flags.get_cached(tx)
205
203
 
206
204
 
207
205
  def _row_config(row) -> dict:
@@ -23,19 +23,30 @@ carries one row per stage and each environment reads its own; the alternate
23
23
  database name comes from config (:data:`ALTERNATE_DB_CONFIG_KEY`). (An app that
24
24
  one day consolidates several systems into one database can widen the key then;
25
25
  today the stage is the whole identity.)
26
+
27
+ The mirroring itself is the generic :mod:`velocity.aws.mirror` (any table,
28
+ any natural key, optional foreign-key translation); this module is that
29
+ mechanism applied to the flags table, keyed by ``applied_system``.
26
30
  """
27
31
 
28
32
  from __future__ import annotations
29
33
 
30
34
  import logging
31
35
 
36
+ from velocity.aws import mirror as _mirror
37
+ from velocity.cache import memo, read_cache
38
+
32
39
  log = logging.getLogger(__name__)
33
40
 
34
41
  SYSTEM_COLUMN = "applied_system"
35
42
  DEFAULT_TABLE = "sys_feature_flags"
36
- ALTERNATE_DB_CONFIG_KEY = "DBAlternate"
43
+ ALTERNATE_DB_CONFIG_KEY = _mirror.ALTERNATE_DB_CONFIG_KEY
44
+ # how long a warm container may reuse the flags row before re-reading it
45
+ CACHE_SECONDS_CONFIG_KEY = "FeatureFlagCacheSeconds"
46
+ DEFAULT_CACHE_SECONDS = 60
37
47
 
38
- _alternate_engines: dict = {}
48
+ _alternate_engines = _mirror._alternate_engines
49
+ _cache_seconds: float | None = None
39
50
 
40
51
 
41
52
  def current_system(default: str | None = None) -> str | None:
@@ -68,20 +79,74 @@ def get(
68
79
  return dict(row)
69
80
 
70
81
 
82
+ def cache_seconds() -> float:
83
+ """Seconds a warm container may reuse the flags row: the
84
+ ``FeatureFlagCacheSeconds`` config key, default 60; ``0`` reads the row
85
+ on every call. Resolved once per container."""
86
+ global _cache_seconds
87
+ if _cache_seconds is None:
88
+ raw = None
89
+ try:
90
+ from velocity.aws import ssm_config
91
+
92
+ raw = ssm_config.getenv(CACHE_SECONDS_CONFIG_KEY, None)
93
+ except Exception:
94
+ raw = None
95
+ try:
96
+ _cache_seconds = (
97
+ float(raw) if raw not in (None, "") else float(DEFAULT_CACHE_SECONDS)
98
+ )
99
+ except (TypeError, ValueError):
100
+ _cache_seconds = float(DEFAULT_CACHE_SECONDS)
101
+ return _cache_seconds
102
+
103
+
104
+ def clear_cache(table: str = DEFAULT_TABLE) -> None:
105
+ """Forget cached rows (after a local flags write; tests)."""
106
+ global _cache_seconds
107
+ read_cache.invalidate(table)
108
+ _cache_seconds = None
109
+
110
+
111
+ def get_cached(
112
+ tx, system: str | None = None, *, table: str = DEFAULT_TABLE, now: float | None = None
113
+ ) -> dict:
114
+ """:func:`get` through the process read cache (:mod:`velocity.cache`)
115
+ — the hot-path reader.
116
+
117
+ The flags row is ONE row per stage, and every reader in a request
118
+ (the handler context, the security guards, session control, any
119
+ capability knob) wants the same one: this makes it at most one read per
120
+ :func:`cache_seconds` per container, however many readers there are.
121
+ Tagged with the table, so a flags write through the table API in this
122
+ process (the mirror hook, a save) drops it at once; the other
123
+ environment sees the change within the TTL.
124
+ """
125
+ system = system if system is not None else current_system()
126
+ row = memo(
127
+ ("feature_flags", table, system),
128
+ lambda: dict(get(tx, system, table=table)),
129
+ cache_seconds(),
130
+ tables=(table,),
131
+ now=now,
132
+ )
133
+ return dict(row)
134
+
135
+
71
136
  def load(tx, loader=None) -> dict:
72
137
  """The request-time flag source used by handler contexts.
73
138
 
74
139
  An injected ``loader(tx)`` wins (apps that resolve flags their own way);
75
- otherwise :func:`get` — velocity's stage-scoped reader is the source,
76
- never raising into the request. The legacy CaringCent
77
- ``support.app.helpers`` loader remains only as a last resort when the
78
- velocity reader finds no row (it raises on a missing row, which those
79
- apps rely on); absent that package the answer is ``{}``.
140
+ otherwise :func:`get_cached` — velocity's stage-scoped reader behind the
141
+ container cache — is the source, never raising into the request. The
142
+ legacy CaringCent ``support.app.helpers`` loader remains only as a last
143
+ resort when the velocity reader finds no row (it raises on a missing
144
+ row, which those apps rely on); absent that package the answer is ``{}``.
80
145
  """
81
146
  if callable(loader):
82
147
  return loader(tx)
83
148
  try:
84
- flags = get(tx)
149
+ flags = get_cached(tx)
85
150
  except Exception:
86
151
  flags = {}
87
152
  if flags:
@@ -101,28 +166,13 @@ def _legacy_support_loader(tx):
101
166
  return helpers.get_feature_flags(tx)
102
167
 
103
168
 
104
- def _without_sys(row) -> dict:
105
- """A row's business columns — the ``sys_*`` bookkeeping never crosses over."""
106
- return {k: v for k, v in dict(row).items() if not str(k).startswith("sys_")}
169
+ _without_sys = _mirror.business_columns
170
+ _toast = _mirror.toast
107
171
 
108
172
 
109
173
  def alternate_engine(alternate_db: str | None = None):
110
- """Engine for the alternate database — same instance, other ``database``.
111
-
112
- Host/port/user/password are inherited from the running config, so no
113
- separate credentials are needed; only the database name changes. Cached per
114
- database name so repeated mirrors in one process reuse the connection.
115
- """
116
- from velocity.aws import ssm_config
117
- from velocity.db.servers import postgres
118
-
119
- if alternate_db is None:
120
- alternate_db = ssm_config.getenv(ALTERNATE_DB_CONFIG_KEY, raise_on_missing=True)
121
- if alternate_db not in _alternate_engines:
122
- _alternate_engines[alternate_db] = postgres.initialize(
123
- {"database": alternate_db}
124
- )
125
- return _alternate_engines[alternate_db]
174
+ """Engine for the alternate database — see :func:`velocity.aws.mirror.alternate_engine`."""
175
+ return _mirror.alternate_engine(alternate_db)
126
176
 
127
177
 
128
178
  def mirror_row(
@@ -133,14 +183,9 @@ def mirror_row(
133
183
  Returns the mirrored key, or ``None`` when the row has no system key (a
134
184
  keyless row is skipped, never mirrored to an arbitrary target).
135
185
  """
136
- data = _without_sys(row)
137
- key = data.get(SYSTEM_COLUMN)
138
- if not key:
139
- return None
140
186
  engine = engine or alternate_engine(alternate_db)
141
- with engine.transaction() as alt:
142
- alt.table(table).merge(data, pk={SYSTEM_COLUMN: key})
143
- return key
187
+ pk = _mirror.mirror_row(row, table=table, key=(SYSTEM_COLUMN,), engine=engine)
188
+ return pk[SYSTEM_COLUMN] if pk else None
144
189
 
145
190
 
146
191
  def reconcile(
@@ -149,27 +194,11 @@ def reconcile(
149
194
  """Mirror every flags row into the alternate database. Catch-up companion of
150
195
  :func:`mirror_row`. Returns ``{"synced": [...], "skipped": [...]}``."""
151
196
  engine = engine or alternate_engine(alternate_db)
152
- synced: list = []
153
- skipped: list = []
154
- rows = list(tx.table(table).select())
155
- with engine.transaction() as alt:
156
- for row in rows:
157
- data = _without_sys(row)
158
- key = data.get(SYSTEM_COLUMN)
159
- if not key:
160
- skipped.append(dict(row).get("sys_id"))
161
- continue
162
- alt.table(table).merge(data, pk={SYSTEM_COLUMN: key})
163
- synced.append(key)
164
- return {"synced": synced, "skipped": skipped}
165
-
166
-
167
- def _toast(context, message: str, level: str) -> None:
168
- """Best-effort operator toast; a context without one is fine (a queue run)."""
169
- response = getattr(context, "response", None)
170
- toast = getattr(response() if callable(response) else None, "toast", None)
171
- if callable(toast):
172
- toast(message, level)
197
+ result = _mirror.reconcile(tx, table=table, key=(SYSTEM_COLUMN,), engine=engine)
198
+ return {
199
+ "synced": [pk[SYSTEM_COLUMN] for pk in result["synced"]],
200
+ "skipped": result["skipped"],
201
+ }
173
202
 
174
203
 
175
204
  def make_mirror_hook(
@@ -187,6 +216,7 @@ def make_mirror_hook(
187
216
 
188
217
  def hook(tx, table_name, sys_id, row, context):
189
218
  del tx, table_name, sys_id
219
+ clear_cache() # the row just changed here: this container re-reads it
190
220
  try:
191
221
  key = mirror_row(row, alternate_db=alternate_db, table=table)
192
222
  if notify and key:
@@ -17,6 +17,7 @@ from velocity.aws.ssm_config import get_project_name as config_get_project_name
17
17
  from velocity.aws.ssm_config import get_region as config_get_region
18
18
  from velocity.aws.ssm_config import get_stage as config_get_stage
19
19
  from velocity.logging import get_logger
20
+ from velocity.cache import memo
20
21
 
21
22
  engine = velocity.db.postgres.initialize()
22
23
 
@@ -81,6 +82,38 @@ def _get_controls_vars() -> dict:
81
82
  }
82
83
 
83
84
 
85
+ # ── the signed-in user, cached per container ─────────────────────────────
86
+ USER_CACHE_CONFIG_KEY = "UserCacheSeconds"
87
+ DEFAULT_USER_CACHE_SECONDS = 60
88
+ _user_cache_seconds = None
89
+
90
+
91
+ def user_cache_seconds() -> float:
92
+ """Seconds a warm container may reuse a signed-in user's Cognito record and
93
+ user-table row: the ``UserCacheSeconds`` config key, default 60. A disabled
94
+ or deleted user keeps that container's access until it expires — accepted
95
+ policy (Paul, 2026-09-13). ``0`` looks the user up on every request."""
96
+ global _user_cache_seconds
97
+ if _user_cache_seconds is None:
98
+ raw = None
99
+ try:
100
+ raw = config_getenv(USER_CACHE_CONFIG_KEY, None)
101
+ except Exception:
102
+ raw = None
103
+ try:
104
+ _user_cache_seconds = (
105
+ float(raw) if raw not in (None, "") else float(DEFAULT_USER_CACHE_SECONDS)
106
+ )
107
+ except (TypeError, ValueError):
108
+ _user_cache_seconds = float(DEFAULT_USER_CACHE_SECONDS)
109
+ return _user_cache_seconds
110
+
111
+
112
+ def clear_user_cache_config() -> None:
113
+ global _user_cache_seconds
114
+ _user_cache_seconds = None
115
+
116
+
84
117
  @engine.transaction
85
118
  class Context:
86
119
 
@@ -807,63 +840,73 @@ class Context:
807
840
  "cannot resolve a Cognito user"
808
841
  ) from None
809
842
 
810
- try:
811
- self.perf.start("cognito admin_get_user")
812
- response = cognito_client.admin_get_user(
813
- UserPoolId=user_pool_id,
814
- Username=user_sub,
815
- )
816
- self.perf.log("cognito admin_get_user")
817
- resolved_username = response.get("Username")
818
- except cognito_client.exceptions.UserNotFoundException:
843
+ # one Cognito round trip per user per UserCacheSeconds per container
844
+ # (velocity.cache); a lookup that fails is never cached
845
+ def resolve():
819
846
  try:
820
- self.perf.start("cognito list_users")
821
- list_response = cognito_client.list_users(
847
+ self.perf.start("cognito admin_get_user")
848
+ response = cognito_client.admin_get_user(
822
849
  UserPoolId=user_pool_id,
823
- Filter=f'sub = "{user_sub}"',
824
- Limit=1,
850
+ Username=user_sub,
825
851
  )
826
- self.perf.log("cognito list_users")
852
+ self.perf.log("cognito admin_get_user")
853
+ resolved_username = response.get("Username")
854
+ except cognito_client.exceptions.UserNotFoundException:
855
+ try:
856
+ self.perf.start("cognito list_users")
857
+ list_response = cognito_client.list_users(
858
+ UserPoolId=user_pool_id,
859
+ Filter=f'sub = "{user_sub}"',
860
+ Limit=1,
861
+ )
862
+ self.perf.log("cognito list_users")
863
+ except ClientError as exc:
864
+ message = exc.response.get("Error", {}).get("Message", "Unknown error")
865
+ raise AlertError(
866
+ f"Failed to search Cognito user by sub: {message}"
867
+ ) from None
868
+
869
+ users = list_response.get("Users", [])
870
+ if not users:
871
+ raise AlertError("Cognito user not found") from None
872
+
873
+ user = users[0]
874
+ response = {
875
+ "Username": user.get("Username"),
876
+ "UserAttributes": user.get("Attributes", []),
877
+ "Enabled": user.get("Enabled"),
878
+ "UserStatus": user.get("UserStatus"),
879
+ "UserCreateDate": user.get("UserCreateDate"),
880
+ "UserLastModifiedDate": user.get("UserLastModifiedDate"),
881
+ }
882
+ resolved_username = response.get("Username")
827
883
  except ClientError as exc:
828
884
  message = exc.response.get("Error", {}).get("Message", "Unknown error")
829
- raise AlertError(
830
- f"Failed to search Cognito user by sub: {message}"
831
- ) from None
832
-
833
- users = list_response.get("Users", [])
834
- if not users:
835
- raise AlertError("Cognito user not found") from None
836
-
837
- user = users[0]
838
- response = {
839
- "Username": user.get("Username"),
840
- "UserAttributes": user.get("Attributes", []),
841
- "Enabled": user.get("Enabled"),
842
- "UserStatus": user.get("UserStatus"),
843
- "UserCreateDate": user.get("UserCreateDate"),
844
- "UserLastModifiedDate": user.get("UserLastModifiedDate"),
885
+ raise AlertError(f"Failed to retrieve Cognito user: {message}") from None
886
+
887
+ attributes = {
888
+ attr["Name"]: attr.get("Value")
889
+ for attr in response.get("UserAttributes", [])
845
890
  }
846
- resolved_username = response.get("Username")
847
- except ClientError as exc:
848
- message = exc.response.get("Error", {}).get("Message", "Unknown error")
849
- raise AlertError(f"Failed to retrieve Cognito user: {message}") from None
850
-
851
- attributes = {
852
- attr["Name"]: attr.get("Value")
853
- for attr in response.get("UserAttributes", [])
854
- }
855
891
 
856
- user = {
857
- "username": resolved_username or user_sub,
858
- "email": attributes.get("email"),
859
- "attributes": attributes,
860
- "enabled": response.get("Enabled"),
861
- "user_status": response.get("UserStatus"),
862
- "sub": attributes.get("sub", user_sub),
863
- }
892
+ user = {
893
+ "username": resolved_username or user_sub,
894
+ "email": attributes.get("email"),
895
+ "attributes": attributes,
896
+ "enabled": response.get("Enabled"),
897
+ "user_status": response.get("UserStatus"),
898
+ "sub": attributes.get("sub", user_sub),
899
+ }
864
900
 
865
- self.perf.log("get_cognito_user total")
901
+ return user
866
902
 
903
+ user = memo(
904
+ ("cognito", user_pool_id, user_sub),
905
+ resolve,
906
+ user_cache_seconds(),
907
+ cache_none=False,
908
+ )
909
+ self.perf.log("get_cognito_user total")
867
910
  return user
868
911
 
869
912
  def get_cognito_user_optional(self, aws_event):
@@ -161,15 +161,13 @@ class LambdaHandler(BaseHandler):
161
161
  )
162
162
 
163
163
  context.perf.start("user lookup")
164
- row = tx.table(self.user_table).find(
165
- {"email_address": session.get("email_address")}
166
- )
164
+ user = self._cached_user(tx, session.get("email_address"))
167
165
  context.perf.log("user lookup")
168
- if not row:
166
+ if not user:
169
167
  raise Exception(
170
168
  "A valid user with permission is required to access this function [DB]"
171
169
  )
172
- self.current_user = row.to_dict()
170
+ self.current_user = user
173
171
 
174
172
  temp = copy.deepcopy(context.postdata() or {})
175
173
  payload = temp.get("payload")
@@ -181,6 +179,29 @@ class LambdaHandler(BaseHandler):
181
179
  extra={"payload": pprint.pformat(temp)},
182
180
  )
183
181
 
182
+ def _cached_user(self, tx, email_address):
183
+ """The user-table row for the signed-in user as a dict, through the
184
+ process read cache (velocity.cache) for ``UserCacheSeconds``: one
185
+ query per TTL per container instead of one per request. A miss is
186
+ never cached, so a user created a moment ago signs in at once; a
187
+ write to the user table in this process drops the entry."""
188
+ from velocity.cache import memo
189
+
190
+ from velocity.aws.handlers.context import user_cache_seconds
191
+
192
+ def load():
193
+ row = tx.table(self.user_table).find({"email_address": email_address})
194
+ return row.to_dict() if row else None
195
+
196
+ user = memo(
197
+ ("user", self.user_table, email_address),
198
+ load,
199
+ user_cache_seconds(),
200
+ tables=(self.user_table,),
201
+ cache_none=False,
202
+ )
203
+ return dict(user) if user else None
204
+
184
205
  def afterAction(self, tx, context):
185
206
  self._enhanced_after_action(tx, context)
186
207
  logger.debug("starting LamdaHandler.afterAction")
@@ -20,6 +20,10 @@ from velocity.aws.handlers.exceptions import AlertError
20
20
  from velocity.db.exceptions import DbDataIntegrityError
21
21
  from velocity.misc import export
22
22
 
23
+ # a table's column list changes only on deploy (schema sync) or a lazy ALTER
24
+ # in this process (which drops the entry): reuse it across requests
25
+ SCHEMA_CACHE_SECONDS = 300
26
+
23
27
  logger = logging.getLogger(__name__)
24
28
 
25
29
  # Postgres FK-violation text, e.g.:
@@ -522,7 +526,9 @@ class DataServiceMixin:
522
526
  order_dir = parts[1].lower()
523
527
 
524
528
  if not order_col:
525
- available = [c for c in tx.table(obj).sys_columns()]
529
+ available = [
530
+ c for c in tx.table(obj).sys_columns(cache_ttl=SCHEMA_CACHE_SECONDS)
531
+ ]
526
532
  lowered = {c.lower(): c for c in available}
527
533
  order_col = lowered.get("sys_id") or lowered.get("sys_created")
528
534
  if not order_col and available:
@@ -551,7 +557,9 @@ class DataServiceMixin:
551
557
  _inject_row_number_expression(params)
552
558
  requested_cols = _extract_requested_column_names(params.get("columns"))
553
559
  if requested_cols:
554
- available_cols = {c.lower() for c in tx.table(obj).sys_columns()}
560
+ available_cols = {
561
+ c.lower() for c in tx.table(obj).sys_columns(cache_ttl=SCHEMA_CACHE_SECONDS)
562
+ }
555
563
  missing = sorted(
556
564
  {c for c in requested_cols if c.lower() not in available_cols},
557
565
  key=lambda v: v.lower(),
@@ -120,14 +120,15 @@ class SessionControlMixin:
120
120
  # ── switch resolution ────────────────────────────────────────────────
121
121
  def _session_flags(self, tx):
122
122
  """The stage's feature flags (the `single_session` capability's
123
- knobs) {} when there is no tx or no flags table, so sessions never
124
- depend on the flag row existing."""
123
+ knobs) from velocity's container cache no read of their own per
124
+ heartbeat; {} when there is no tx or no flags table, so sessions
125
+ never depend on the flag row existing."""
125
126
  if tx is None:
126
127
  return {}
127
128
  try:
128
129
  from velocity.aws import feature_flags
129
130
 
130
- return feature_flags.get(tx) or {}
131
+ return feature_flags.get_cached(tx) or {}
131
132
  except Exception:
132
133
  return {}
133
134