velocity-python 0.1.105__tar.gz → 0.1.106__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 (292) hide show
  1. {velocity_python-0.1.105 → velocity_python-0.1.106}/PKG-INFO +1 -1
  2. {velocity_python-0.1.105 → velocity_python-0.1.106}/pyproject.toml +1 -1
  3. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.1.106/src/velocity/aws/capabilities/__init__.py +43 -0
  5. velocity_python-0.1.106/src/velocity/aws/capabilities/base.py +79 -0
  6. velocity_python-0.1.106/src/velocity/aws/capabilities/dispatch.py +50 -0
  7. velocity_python-0.1.106/src/velocity/aws/capabilities/infra.py +43 -0
  8. velocity_python-0.1.106/src/velocity/aws/capabilities/jobs.py +53 -0
  9. velocity_python-0.1.106/src/velocity/aws/capabilities/provisioning.py +180 -0
  10. velocity_python-0.1.106/src/velocity/aws/capabilities/retention.py +65 -0
  11. velocity_python-0.1.106/src/velocity/aws/capabilities/security.py +351 -0
  12. velocity_python-0.1.106/src/velocity/aws/capabilities/single_session.py +60 -0
  13. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/feature_flags.py +64 -14
  14. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/base_handler.py +22 -1
  15. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/context.py +10 -7
  16. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/lambda_handler.py +15 -0
  17. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/job_handler.py +13 -28
  18. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/session_control.py +39 -11
  19. velocity_python-0.1.106/src/velocity/aws/job_lifecycle.py +46 -0
  20. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/table.py +33 -0
  21. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/PKG-INFO +1 -1
  22. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/SOURCES.txt +17 -0
  23. velocity_python-0.1.106/tests/test_access_error_403.py +68 -0
  24. velocity_python-0.1.106/tests/test_capabilities.py +363 -0
  25. velocity_python-0.1.106/tests/test_context_flags_loader.py +68 -0
  26. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_feature_flags.py +17 -4
  27. velocity_python-0.1.106/tests/test_job_lifecycle.py +102 -0
  28. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler_masquerade.py +35 -2
  29. velocity_python-0.1.106/tests/test_security_capability.py +293 -0
  30. velocity_python-0.1.106/tests/test_single_session_capability.py +84 -0
  31. velocity_python-0.1.106/tests/test_table_purge.py +75 -0
  32. {velocity_python-0.1.105 → velocity_python-0.1.106}/LICENSE +0 -0
  33. {velocity_python-0.1.105 → velocity_python-0.1.106}/README.md +0 -0
  34. {velocity_python-0.1.105 → velocity_python-0.1.106}/setup.cfg +0 -0
  35. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/__init__.py +0 -0
  36. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/amplify.py +0 -0
  37. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/amplify_build.py +0 -0
  38. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/__init__.py +0 -0
  39. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/backfill.py +0 -0
  40. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/indexing.py +0 -0
  41. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/references.py +0 -0
  42. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/service.py +0 -0
  43. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/assets/usage_index.py +0 -0
  44. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/blobs.py +0 -0
  45. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/__init__.py +0 -0
  46. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/config.py +0 -0
  47. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/operations.py +0 -0
  48. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/policies.py +0 -0
  49. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/reconcile.py +0 -0
  50. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/deploy/targets.py +0 -0
  51. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/dirty_pipeline.py +0 -0
  52. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/email.py +0 -0
  53. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/__init__.py +0 -0
  54. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/context_factory.py +0 -0
  55. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/exceptions.py +0 -0
  56. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/masquerade.py +0 -0
  57. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  58. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  59. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  60. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/perf.py +0 -0
  61. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/response.py +0 -0
  62. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  63. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/job_results.py +0 -0
  64. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/s3.py +0 -0
  65. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/sms.py +0 -0
  66. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/ssm_config.py +0 -0
  67. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/__init__.py +0 -0
  68. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  69. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  70. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  71. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  72. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  73. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_email.py +0 -0
  74. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  75. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_response.py +0 -0
  76. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/aws/tests/test_sms.py +0 -0
  77. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/__init__.py +0 -0
  78. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/__init__.py +0 -0
  79. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/async_support.py +0 -0
  80. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/column.py +0 -0
  81. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/database.py +0 -0
  82. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/decorators.py +0 -0
  83. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/engine.py +0 -0
  84. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/jsonproxy.py +0 -0
  85. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/result.py +0 -0
  86. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/row.py +0 -0
  87. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/sequence.py +0 -0
  88. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/transaction.py +0 -0
  89. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/core/view.py +0 -0
  90. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/exceptions.py +0 -0
  91. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/maintenance.py +0 -0
  92. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/migrations.py +0 -0
  93. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/__init__.py +0 -0
  94. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/__init__.py +0 -0
  95. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/initializer.py +0 -0
  96. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/operators.py +0 -0
  97. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/sql.py +0 -0
  98. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/base/types.py +0 -0
  99. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/__init__.py +0 -0
  100. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/operators.py +0 -0
  101. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/reserved.py +0 -0
  102. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/sql.py +0 -0
  103. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/mysql/types.py +0 -0
  104. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/__init__.py +0 -0
  105. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/operators.py +0 -0
  106. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/reserved.py +0 -0
  107. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/sql.py +0 -0
  108. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/postgres/types.py +0 -0
  109. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  110. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/operators.py +0 -0
  111. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  112. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/sql.py +0 -0
  113. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlite/types.py +0 -0
  114. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  115. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  116. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  117. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  118. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/sqlserver/types.py +0 -0
  119. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/servers/tablehelper.py +0 -0
  120. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/__init__.py +0 -0
  121. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/common_db_test.py +0 -0
  122. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/__init__.py +0 -0
  123. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/common.py +0 -0
  124. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/conftest.py +0 -0
  125. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_column.py +0 -0
  126. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  127. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_database.py +0 -0
  128. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  129. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  130. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  131. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_result.py +0 -0
  132. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_row.py +0 -0
  133. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  134. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  135. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  136. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  137. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  138. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_table.py +0 -0
  139. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  140. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  141. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/__init__.py +0 -0
  142. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/common.py +0 -0
  143. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  144. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  145. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  146. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_db_utils.py +0 -0
  147. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_postgres.py +0 -0
  148. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  149. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  150. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_result_caching.py +0 -0
  151. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  152. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  153. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  154. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  155. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_sql_builder.py +0 -0
  156. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_tablehelper.py +0 -0
  157. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/tests/test_view_helper.py +0 -0
  158. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/db/utils.py +0 -0
  159. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/logging.py +0 -0
  160. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/__init__.py +0 -0
  161. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/__init__.py +0 -0
  162. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/iconv.py +0 -0
  163. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/conv/oconv.py +0 -0
  164. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/db.py +0 -0
  165. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/export.py +0 -0
  166. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/format.py +0 -0
  167. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/mail.py +0 -0
  168. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/merge.py +0 -0
  169. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/pdf.py +0 -0
  170. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/__init__.py +0 -0
  171. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_db.py +0 -0
  172. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_fix.py +0 -0
  173. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_format.py +0 -0
  174. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_iconv.py +0 -0
  175. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_mail_send.py +0 -0
  176. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_merge.py +0 -0
  177. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_oconv.py +0 -0
  178. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_original_error.py +0 -0
  179. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tests/test_timer.py +0 -0
  180. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/timer.py +0 -0
  181. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/misc/tools.py +0 -0
  182. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/__init__.py +0 -0
  183. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/__init__.py +0 -0
  184. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/capabilities.py +0 -0
  185. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/store.py +0 -0
  186. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/core/where.py +0 -0
  187. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/exceptions.py +0 -0
  188. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/__init__.py +0 -0
  189. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/base/__init__.py +0 -0
  190. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  191. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  192. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  193. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  194. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  195. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  196. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  197. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  198. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  199. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  200. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  201. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  202. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/__init__.py +0 -0
  203. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/authorizenet_adapter.py +0 -0
  204. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/authorizenet_mirror.py +0 -0
  205. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/base_adapter.py +0 -0
  206. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/braintree_adapter.py +0 -0
  207. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/braintree_mirror.py +0 -0
  208. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/charge_rules.py +0 -0
  209. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/stripe_adapter.py +0 -0
  210. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity/payment/stripe_mirror.py +0 -0
  211. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  212. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/entry_points.txt +0 -0
  213. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/requires.txt +0 -0
  214. {velocity_python-0.1.105 → velocity_python-0.1.106}/src/velocity_python.egg-info/top_level.txt +0 -0
  215. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_activity_tracking_aborted_tx.py +0 -0
  216. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_amplify_build.py +0 -0
  217. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_asset_indexing.py +0 -0
  218. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_asset_references.py +0 -0
  219. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_assets_service.py +0 -0
  220. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_async_support.py +0 -0
  221. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_batch_operations.py +0 -0
  222. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_blob_store.py +0 -0
  223. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_column_tx_arg.py +0 -0
  224. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_concurrency_safety.py +0 -0
  225. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_connection_pool.py +0 -0
  226. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_connection_resilience.py +0 -0
  227. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_context_job_descriptions.py +0 -0
  228. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_query_cap.py +0 -0
  229. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_renumber.py +0 -0
  230. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_data_service_save_as.py +0 -0
  231. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_db_credentials_ssm_cascade.py +0 -0
  232. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_db_maintenance.py +0 -0
  233. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_decorators.py +0 -0
  234. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_delete_hook_fk_blocked.py +0 -0
  235. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_dirty_pipeline_fast_path.py +0 -0
  236. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_email_processing.py +0 -0
  237. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_enqueue_send_failures.py +0 -0
  238. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_get_cognito_user_provider.py +0 -0
  239. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_http_handler_rollback.py +0 -0
  240. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_iconv_money_to_cents.py +0 -0
  241. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_identifier_injection_guard.py +0 -0
  242. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_job_handler.py +0 -0
  243. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_job_results.py +0 -0
  244. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_json_columns.py +0 -0
  245. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_jsonb_dict_adapter.py +0 -0
  246. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler.py +0 -0
  247. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_lambda_handler_auth.py +0 -0
  248. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_masquerade_grant.py +0 -0
  249. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_mixins_import.py +0 -0
  250. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_n_plus_one.py +0 -0
  251. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_dynamodb.py +0 -0
  252. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_memory.py +0 -0
  253. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_mongodb.py +0 -0
  254. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_redis.py +0 -0
  255. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_translation.py +0 -0
  256. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_nosql_where.py +0 -0
  257. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_observability.py +0 -0
  258. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_on_commit.py +0 -0
  259. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_authorizenet_adapter.py +0 -0
  260. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_braintree_adapter.py +0 -0
  261. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_braintree_mirror.py +0 -0
  262. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_mirror_empty_table.py +0 -0
  263. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_profile_sorting.py +0 -0
  264. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_payment_stripe_adapter.py +0 -0
  265. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_pdf.py +0 -0
  266. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_prepared_statements.py +0 -0
  267. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_psycopg3_upgrade.py +0 -0
  268. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_query_cache.py +0 -0
  269. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_query_missing_table_unlocked.py +0 -0
  270. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_response.py +0 -0
  271. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_restricted_direct_tables.py +0 -0
  272. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_retry_side_effect_guard.py +0 -0
  273. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_return_default_safety.py +0 -0
  274. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_row_batch_update.py +0 -0
  275. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_row_cache_staleness.py +0 -0
  276. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_schema_migrations.py +0 -0
  277. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_security_hardening.py +0 -0
  278. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_server_cursor.py +0 -0
  279. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_session_control.py +0 -0
  280. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_single_autocommit_safety.py +0 -0
  281. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_spreadsheet_functions.py +0 -0
  282. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sqlite_backend.py +0 -0
  283. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sqs_per_record_transactions.py +0 -0
  284. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_ssm_config.py +0 -0
  285. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_store_user_data.py +0 -0
  286. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  287. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_table_alter.py +0 -0
  288. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_class_wrapping.py +0 -0
  289. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_commit_and_ownership.py +0 -0
  290. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_transaction_edge_cases.py +0 -0
  291. {velocity_python-0.1.105 → velocity_python-0.1.106}/tests/test_where_clause_validation.py +0 -0
  292. {velocity_python-0.1.105 → velocity_python-0.1.106}/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.105
3
+ Version: 0.1.106
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.105"
7
+ version = "0.1.106"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.105"
1
+ __version__ = version = "0.1.106"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,43 @@
1
+ """System-level capabilities a site turns on: declared once in code, toggled
2
+ per site/environment by the control plane, materialised by
3
+ :func:`provisioning.provision` at deploy time and run by
4
+ :class:`dispatch.CapabilityDispatcher` at runtime. See :mod:`base`.
5
+
6
+ Importing this package registers the built-in capabilities.
7
+ """
8
+
9
+ from .base import ( # noqa: F401
10
+ EVERY_15_MINUTES,
11
+ HOURLY,
12
+ NIGHTLY,
13
+ REGISTRY,
14
+ Capability,
15
+ capability,
16
+ get,
17
+ resolve_config,
18
+ )
19
+ from .dispatch import CapabilityDispatcher, run_capability # noqa: F401
20
+ from .provisioning import provision, trigger_key # noqa: F401
21
+
22
+ # built-ins (importing registers them)
23
+ from . import retention # noqa: E402,F401
24
+ from . import security # noqa: E402,F401
25
+ from . import infra # noqa: E402,F401
26
+ from . import single_session # noqa: E402,F401
27
+ from . import jobs # noqa: E402,F401
28
+ from .security import SecurityMixin # noqa: E402,F401
29
+
30
+ __all__ = [
31
+ "Capability",
32
+ "CapabilityDispatcher",
33
+ "REGISTRY",
34
+ "capability",
35
+ "get",
36
+ "provision",
37
+ "resolve_config",
38
+ "run_capability",
39
+ "trigger_key",
40
+ "NIGHTLY",
41
+ "HOURLY",
42
+ "EVERY_15_MINUTES",
43
+ ]
@@ -0,0 +1,79 @@
1
+ """Capabilities: self-describing system-level features a site can turn on.
2
+
3
+ A whole class of features has the same shape — some schema, a recurring
4
+ schedule, a handler, a few config knobs, maybe a handler mixin: retention
5
+ pruning, session reaping, the feature-flag mirror, mail harvest, demo-safety
6
+ scrubs, ops metrics. A :class:`Capability` declares all of that in ONE object,
7
+ so the deploy pipeline can materialise it (see :mod:`provisioning`) and the
8
+ queue handler can run it (see :mod:`dispatch`) without a bespoke migration or
9
+ wiring per feature.
10
+
11
+ Division of labour:
12
+
13
+ * **code** (a subclass registered with :func:`capability`) says what the
14
+ feature *is* — its schema, default cadence, default knobs, and the work;
15
+ * the **control plane** (the site registry) says which sites/environments run
16
+ it and with what policy, delivered to the site as an entry that
17
+ :func:`provisioning.provision` materialises.
18
+
19
+ Config resolution (:func:`resolve_config`): the capability's ``defaults`` are
20
+ the floor; the environment's feature-flag row overrides the knobs the
21
+ capability declares (so they are edited on the Feature Flags page and mirrored
22
+ between databases); the site's registry ``config`` overrides everything (the
23
+ per-site policy — e.g. *which* tables retention may touch).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ # cron expressions the scheduler (croniter) accepts
29
+ NIGHTLY = "0 3 * * *"
30
+ HOURLY = "0 * * * *"
31
+ EVERY_15_MINUTES = "*/15 * * * *"
32
+
33
+ REGISTRY: dict = {}
34
+
35
+
36
+ class Capability:
37
+ name: str = ""
38
+ schema: dict = {} # {table: {column: sql type}} -> ensure_schema
39
+ defaults: dict = {} # flag name -> default; seeded as TEXT feature flags
40
+ schedules: tuple = () # ((action, cron), ...)
41
+ mixins: tuple = () # handler mixins the site attaches at runtime
42
+ applies_to: tuple = ("demo", "production") # default stages; registry can force
43
+
44
+ def handle(self, action, tx, context, config):
45
+ """Do the work for one scheduled/queued ``action``. Return a JSON-able
46
+ summary (it lands in the queue response and the job log)."""
47
+ raise NotImplementedError(f"{self.name}: no handler for action {action!r}")
48
+
49
+ def on_refresh(self, tx, config):
50
+ """Hook run after a demo refresh (optional) — scrub/neutralise here."""
51
+ return None
52
+
53
+
54
+ def capability(cls):
55
+ """Class decorator: instantiate and register under ``cls.name``."""
56
+ if not getattr(cls, "name", ""):
57
+ raise ValueError(f"{cls.__name__} must set a non-empty `name`")
58
+ REGISTRY[cls.name] = cls()
59
+ return cls
60
+
61
+
62
+ def get(name):
63
+ try:
64
+ return REGISTRY[name]
65
+ except KeyError:
66
+ known = ", ".join(sorted(REGISTRY)) or "(none registered)"
67
+ raise KeyError(f"unknown capability {name!r}; registered: {known}") from None
68
+
69
+
70
+ def resolve_config(cap, flags=None, row_config=None) -> dict:
71
+ """defaults <- feature flags (declared knobs only) <- site registry config."""
72
+ config = dict(cap.defaults)
73
+ flags = flags or {}
74
+ for key in cap.defaults:
75
+ value = flags.get(key)
76
+ if value not in (None, ""):
77
+ config[key] = value
78
+ config.update(row_config or {})
79
+ return config
@@ -0,0 +1,50 @@
1
+ """Runtime dispatch: route the ``run-capability`` queue action to a capability.
2
+
3
+ A site's queue handler mixes in :class:`CapabilityDispatcher`; the scheduler
4
+ stubs seeded by :mod:`provision` enqueue ``run-capability`` with
5
+ ``{"capability": name, "action": action}`` and this routes it. Config is
6
+ resolved locally — the site's own ``sys_capabilities`` row plus its feature
7
+ flags — so the runtime never reaches into the control plane.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ from velocity.aws import feature_flags
15
+
16
+ from .base import REGISTRY, resolve_config
17
+ from .provisioning import SITE_TABLE
18
+
19
+
20
+ def run_capability(tx, context, name, action):
21
+ """Resolve config and run one capability action. Returns the response body."""
22
+ cap = REGISTRY.get(name)
23
+ if cap is None:
24
+ return {"error": f"unknown capability {name!r}", "capability": name}
25
+
26
+ row = tx.table(SITE_TABLE).find({"name": name}) or {}
27
+ if row and row.get("enabled") is False:
28
+ return {
29
+ "capability": name,
30
+ "action": action,
31
+ "skipped": "disabled for this site",
32
+ }
33
+
34
+ raw = row.get("config") if row else None
35
+ row_config = json.loads(raw) if isinstance(raw, str) and raw else (raw or {})
36
+ config = resolve_config(cap, flags=feature_flags.get(tx), row_config=row_config)
37
+
38
+ result = cap.handle(action, tx, context, config)
39
+ return {"capability": name, "action": action, "result": result}
40
+
41
+
42
+ class CapabilityDispatcher:
43
+ """Queue-handler mixin: ``run-capability`` -> the registered capability."""
44
+
45
+ def OnActionRunCapability(self, tx, context):
46
+ payload = context.payload() or {}
47
+ body = run_capability(
48
+ tx, context, payload.get("capability"), payload.get("action")
49
+ )
50
+ context.response().set_body(body)
@@ -0,0 +1,43 @@
1
+ """Infra-tier capabilities: rendered into a site's CloudFormation stacks by
2
+ the sitefactory templates rather than run by a Lambda.
3
+
4
+ They are registered here so the registry recognises their names (the
5
+ provisioner would otherwise refuse an unknown capability) and so they sit
6
+ on the same Site Capabilities page as everything else; their ``config`` is
7
+ the template's policy. Nothing here executes at runtime.
8
+
9
+ * ``security_headers`` — the CloudFront response-headers policy on the web
10
+ distribution (HSTS, nosniff, frame options, referrer policy, XSS-off, and
11
+ an optional ``csp``). Free; ON by default for every factory site — a row
12
+ is only needed to turn it OFF or to set a Content-Security-Policy.
13
+ * ``api_throttling`` — API Gateway stage throttling (``rate`` requests/s,
14
+ ``burst``). Free; the template defaults apply without a row.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from .base import Capability, capability
20
+
21
+
22
+ class InfraCapability(Capability):
23
+ schedules = ()
24
+ schema = {}
25
+
26
+ def handle(self, action, tx, context, config):
27
+ raise RuntimeError(
28
+ f"{self.name} is an infra capability rendered into the site's stacks "
29
+ f"by sitefactory; it has no runtime action ({action!r})"
30
+ )
31
+
32
+
33
+ @capability
34
+ class SecurityHeaders(InfraCapability):
35
+ name = "security_headers"
36
+ # policy lives in the row's config: {"csp": "...", "frame_options": "DENY"}
37
+ defaults = {}
38
+
39
+
40
+ @capability
41
+ class ApiThrottling(InfraCapability):
42
+ name = "api_throttling"
43
+ defaults = {} # {"rate": 100, "burst": 200}
@@ -0,0 +1,53 @@
1
+ """jobs: the scheduled half of asynchronous-job hygiene.
2
+
3
+ ``JobHandler`` only discovers a stale job (worker died: Lambda timeout / OOM)
4
+ when someone polls its status — an abandoned job nobody polls stays "Running"
5
+ forever, and ``aws_job_activity`` is never pruned. This capability sweeps on
6
+ a schedule with the SAME predicate the web tier uses
7
+ (``velocity.aws.job_lifecycle.job_is_stale``): stale jobs are persisted as
8
+ Failed with the standard message, and old activity rows are purged.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import datetime
14
+ import json
15
+
16
+ from velocity.aws.job_lifecycle import (
17
+ JOB_ACTIVITY_TABLE,
18
+ STALE_FAILURE,
19
+ STALE_JOB_STATUSES,
20
+ job_is_stale,
21
+ )
22
+
23
+ from .base import EVERY_15_MINUTES, Capability, capability
24
+
25
+
26
+ @capability
27
+ class Jobs(Capability):
28
+ name = "jobs"
29
+ defaults = {"job_stale_minutes": "16", "job_retention_days": "30"}
30
+ schedules = (("expire_stale", EVERY_15_MINUTES),)
31
+
32
+ def handle(self, action, tx, context, config):
33
+ if action != "expire_stale":
34
+ raise ValueError(f"jobs: unknown action {action!r}")
35
+ now = datetime.datetime.now(datetime.timezone.utc)
36
+ stale_after = datetime.timedelta(
37
+ minutes=int(config.get("job_stale_minutes") or 16)
38
+ )
39
+ jobs = tx.table(JOB_ACTIVITY_TABLE)
40
+
41
+ expired = 0
42
+ for status in sorted(STALE_JOB_STATUSES):
43
+ for job in jobs.select(where={"status": status}):
44
+ if job_is_stale(job, now=now, stale_after=stale_after):
45
+ jobs.update(
46
+ {"status": "Failed", "message": json.dumps(STALE_FAILURE)},
47
+ where={"sys_id": job["sys_id"]},
48
+ )
49
+ expired += 1
50
+
51
+ days = int(config.get("job_retention_days") or 30)
52
+ purged = jobs.purge({"<sys_created": now - datetime.timedelta(days=days)})
53
+ return {"expired": expired, "purged": purged}
@@ -0,0 +1,180 @@
1
+ """Deploy-time provisioning: materialise a site's enabled capabilities.
2
+
3
+ Called by the sitefactory ``provision-capabilities`` step (part of
4
+ ``deploy-backend``) with the ``capabilities`` block of ``site.json``, which is
5
+ the site registry rendered for one environment::
6
+
7
+ [{"name": "retention", "enabled": True, "config": {"tables": {...}}}, ...]
8
+
9
+ For each entry this (1) records it in the site's own ``sys_capabilities``
10
+ table — the runtime reads config from there, never from the control plane —
11
+ (2) ensures the capability's schema, (3) seeds its feature-flag knobs (filling
12
+ only what is unset, never clobbering an edit), and (4) upserts one
13
+ ``sys_task_triggers`` row + one ``program_objects`` stub per schedule.
14
+
15
+ Everything is keyed deterministically (``cap:<name>:<action>``), so running it
16
+ on every deploy is idempotent — and that is the point: a demo refresh wipes
17
+ the triggers, the next deploy puts them back. A disabled entry disables its
18
+ triggers rather than deleting them, so the ledger of runs survives.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import datetime
24
+ import json
25
+
26
+ from velocity.aws import feature_flags
27
+
28
+ from .base import get
29
+
30
+ SITE_TABLE = "sys_capabilities" # per-site materialised registry
31
+ TRIGGERS_TABLE = "sys_task_triggers"
32
+ PROGRAMS_TABLE = "program_objects"
33
+ FLAGS_TABLE = "sys_feature_flags"
34
+ RUN_ACTION = "run-capability"
35
+ SEEDED_BY = "provision-capabilities"
36
+ EXECUTION_ENVIRONMENT = "SQS Event"
37
+
38
+ # The scheduler runs program source with exec() and only {tx, handler,
39
+ # context, attrs, postdata} in scope — so the stub is a single enqueue and the
40
+ # real logic stays in git-tracked capability code.
41
+ PROGRAM_SOURCE = (
42
+ "result = context.enqueue(action=%r, payload={%r: %r, %r: %r}, user=%r)\n"
43
+ 'print("Enqueued", %r, result.get("batch_id"))\n'
44
+ )
45
+
46
+
47
+ def trigger_key(cap_name: str, action: str) -> str:
48
+ return f"cap:{cap_name}:{action}"
49
+
50
+
51
+ def program_source(cap_name: str, action: str) -> str:
52
+ return PROGRAM_SOURCE % (
53
+ RUN_ACTION,
54
+ "capability",
55
+ cap_name,
56
+ "action",
57
+ action,
58
+ "Scheduler",
59
+ RUN_ACTION,
60
+ )
61
+
62
+
63
+ def provision(tx, stage: str, entries, *, now=None) -> list:
64
+ """Materialise ``entries`` for ``stage``. Returns [(name, outcome), ...]."""
65
+ now = now or datetime.datetime.utcnow()
66
+ tx.table(SITE_TABLE).ensure_schema(
67
+ {"name": "TEXT", "enabled": "BOOLEAN", "config": "TEXT"}
68
+ )
69
+ report = []
70
+ for entry in entries or []:
71
+ name = entry["name"]
72
+ cap = get(name)
73
+ enabled = bool(entry.get("enabled", True))
74
+ applies = stage in cap.applies_to or bool(entry.get("force"))
75
+ active = enabled and applies
76
+
77
+ tx.table(SITE_TABLE).merge(
78
+ {
79
+ "name": name,
80
+ "enabled": active,
81
+ "config": json.dumps(entry.get("config") or {}),
82
+ "sys_modified_by": SEEDED_BY,
83
+ },
84
+ pk={"name": name},
85
+ )
86
+ if not active:
87
+ _set_schedules_enabled(tx, cap, False)
88
+ report.append((name, "disabled" if not enabled else f"not for {stage}"))
89
+ continue
90
+
91
+ for table, columns in cap.schema.items():
92
+ tx.table(table).ensure_schema(columns)
93
+
94
+ if cap.defaults:
95
+ _seed_flag_defaults(tx, stage, cap)
96
+
97
+ for action, cron in cap.schedules:
98
+ _upsert_schedule(tx, cap, action, cron, now)
99
+
100
+ report.append((name, "provisioned"))
101
+ return report
102
+
103
+
104
+ def _seed_flag_defaults(tx, stage, cap):
105
+ """Ensure the knob columns exist and fill only the ones that are unset."""
106
+ tx.table(FLAGS_TABLE).ensure_schema({key: "TEXT" for key in cap.defaults})
107
+ row = feature_flags.get(tx, stage)
108
+ if not row:
109
+ return # the app's own seed creates the stage row; nothing to fill yet
110
+ missing = {
111
+ key: str(value)
112
+ for key, value in cap.defaults.items()
113
+ if row.get(key) in (None, "")
114
+ }
115
+ if missing:
116
+ tx.table(FLAGS_TABLE).update(
117
+ missing, where={feature_flags.SYSTEM_COLUMN: stage}
118
+ )
119
+
120
+
121
+ def _upsert_schedule(tx, cap, action, cron, now):
122
+ key = trigger_key(cap.name, action)
123
+ triggers = tx.table(TRIGGERS_TABLE)
124
+ if triggers.find({"trigger_name": key}):
125
+ # keep next_run / last_run / run_count — only the definition refreshes
126
+ triggers.update(
127
+ {"cron_expression": cron, "enabled": True, "sys_modified_by": SEEDED_BY},
128
+ where={"trigger_name": key},
129
+ )
130
+ else:
131
+ triggers.insert(
132
+ {
133
+ "name": f"{cap.name}: {action}",
134
+ "description": f"Capability '{cap.name}' -> '{action}' ({cron}).",
135
+ "trigger_name": key,
136
+ "cron_expression": cron,
137
+ # due immediately: backdated a day so it fires on the next tick
138
+ # regardless of the session time zone (next_run is zone-less)
139
+ "next_run": now - datetime.timedelta(days=1),
140
+ "enabled": True,
141
+ "run_count": 0,
142
+ "sys_modified_by": SEEDED_BY,
143
+ }
144
+ )
145
+
146
+ source = program_source(cap.name, action)
147
+ row = {
148
+ "program_name": f"{cap.name}: {action}",
149
+ "program_handle": key,
150
+ "program_description": (
151
+ f"Enqueues '{RUN_ACTION}' for capability '{cap.name}' action "
152
+ f"'{action}'. Provisioned by sitefactory; do not hand-edit."
153
+ ),
154
+ "source_code": source,
155
+ "tags": f"capability,{cap.name}",
156
+ "execution_environment": EXECUTION_ENVIRONMENT,
157
+ "trigger": key,
158
+ "last_saved_date": now,
159
+ "num_lines": len(source.splitlines()),
160
+ "num_chars": len(source),
161
+ "version": 1,
162
+ "sys_modified_by": SEEDED_BY,
163
+ }
164
+ programs = tx.table(PROGRAMS_TABLE)
165
+ where = {"execution_environment": EXECUTION_ENVIRONMENT, "trigger": key}
166
+ if programs.find(where):
167
+ programs.update(row, where=where)
168
+ else:
169
+ programs.insert(row)
170
+
171
+
172
+ def _set_schedules_enabled(tx, cap, enabled: bool):
173
+ triggers = tx.table(TRIGGERS_TABLE)
174
+ for action, _cron in cap.schedules:
175
+ key = trigger_key(cap.name, action)
176
+ if triggers.find({"trigger_name": key}):
177
+ triggers.update(
178
+ {"enabled": enabled, "sys_modified_by": SEEDED_BY},
179
+ where={"trigger_name": key},
180
+ )
@@ -0,0 +1,65 @@
1
+ """Retention: delete rows older than a per-table window.
2
+
3
+ The *policy* — which tables, how many days — is the site's registry config,
4
+ never guessed here; this capability only knows how to purge safely. A big
5
+ table is not necessarily a prunable one (an email archive is not a log), so
6
+ nothing is touched unless it is named in ``config["tables"]``::
7
+
8
+ {"tables": {"aws_api_activity": 90, "sys_log_email": 30, "filebuffer": 14}}
9
+
10
+ A window of ``null``/``0`` in that map falls back to the ``purge_default_days``
11
+ feature flag. The delete goes through velocity's :meth:`Table.purge` — batch-
12
+ bounded and committed per batch, so a run stays inside the Lambda budget and
13
+ never holds a long lock; an unfinished table just continues next run.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import datetime
19
+ import re
20
+
21
+ from .base import NIGHTLY, Capability, capability
22
+
23
+ IDENTIFIER = re.compile(r"^[a-z_][a-z0-9_]*$")
24
+ TIMESTAMP_COLUMN = "sys_created"
25
+ BATCH_SIZE = 5000
26
+ MAX_BATCHES = 200 # 1M rows per run — well inside the Lambda budget
27
+
28
+
29
+ @capability
30
+ class Retention(Capability):
31
+ name = "retention"
32
+ defaults = {"purge_default_days": "90"}
33
+ schedules = (("purge", NIGHTLY),)
34
+
35
+ def handle(self, action, tx, context, config):
36
+ if action != "purge":
37
+ raise ValueError(f"retention: unknown action {action!r}")
38
+ default_days = int(config.get("purge_default_days") or 90)
39
+ results = {}
40
+ for table, days in (config.get("tables") or {}).items():
41
+ results[table] = purge_table(tx, table, int(days or default_days))
42
+ return results
43
+
44
+
45
+ def purge_table(
46
+ tx, table, days, *, batch=BATCH_SIZE, max_batches=MAX_BATCHES, now=None
47
+ ):
48
+ """Delete rows of ``table`` whose ``sys_created`` is older than ``days``."""
49
+ if not IDENTIFIER.match(str(table)):
50
+ raise ValueError(f"retention: refusing unsafe table name {table!r}")
51
+ if days < 1:
52
+ raise ValueError(f"retention: window must be >= 1 day for {table!r}")
53
+
54
+ target = tx.table(table)
55
+ if not target.exists():
56
+ return {"skipped": "table does not exist"}
57
+ if TIMESTAMP_COLUMN not in target.sys_columns():
58
+ return {"skipped": f"no {TIMESTAMP_COLUMN} column"}
59
+
60
+ now = now or datetime.datetime.now(datetime.timezone.utc)
61
+ cutoff = now - datetime.timedelta(days=days)
62
+ deleted = target.purge(
63
+ {f"<{TIMESTAMP_COLUMN}": cutoff}, batch=batch, max_batches=max_batches
64
+ )
65
+ return {"deleted": deleted, "days": days}