velocity-python 0.1.105__tar.gz → 0.1.107__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 (293) hide show
  1. {velocity_python-0.1.105 → velocity_python-0.1.107}/PKG-INFO +1 -1
  2. {velocity_python-0.1.105 → velocity_python-0.1.107}/pyproject.toml +1 -1
  3. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.1.107/src/velocity/aws/capabilities/__init__.py +60 -0
  5. velocity_python-0.1.107/src/velocity/aws/capabilities/base.py +254 -0
  6. velocity_python-0.1.107/src/velocity/aws/capabilities/dispatch.py +61 -0
  7. velocity_python-0.1.107/src/velocity/aws/capabilities/infra.py +121 -0
  8. velocity_python-0.1.107/src/velocity/aws/capabilities/jobs.py +59 -0
  9. velocity_python-0.1.107/src/velocity/aws/capabilities/provisioning.py +180 -0
  10. velocity_python-0.1.107/src/velocity/aws/capabilities/retention.py +82 -0
  11. velocity_python-0.1.107/src/velocity/aws/capabilities/security.py +419 -0
  12. velocity_python-0.1.107/src/velocity/aws/capabilities/single_session.py +67 -0
  13. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/feature_flags.py +64 -14
  14. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/base_handler.py +22 -1
  15. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/context.py +10 -7
  16. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/lambda_handler.py +15 -0
  17. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/job_handler.py +13 -28
  18. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/session_control.py +39 -11
  19. velocity_python-0.1.107/src/velocity/aws/job_lifecycle.py +46 -0
  20. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/table.py +33 -0
  21. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/PKG-INFO +1 -1
  22. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/SOURCES.txt +18 -0
  23. velocity_python-0.1.107/tests/test_access_error_403.py +68 -0
  24. velocity_python-0.1.107/tests/test_capabilities.py +363 -0
  25. velocity_python-0.1.107/tests/test_capability_catalog.py +188 -0
  26. velocity_python-0.1.107/tests/test_context_flags_loader.py +68 -0
  27. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_feature_flags.py +17 -4
  28. velocity_python-0.1.107/tests/test_job_lifecycle.py +102 -0
  29. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_lambda_handler_masquerade.py +35 -2
  30. velocity_python-0.1.107/tests/test_security_capability.py +307 -0
  31. velocity_python-0.1.107/tests/test_single_session_capability.py +84 -0
  32. velocity_python-0.1.107/tests/test_table_purge.py +75 -0
  33. {velocity_python-0.1.105 → velocity_python-0.1.107}/LICENSE +0 -0
  34. {velocity_python-0.1.105 → velocity_python-0.1.107}/README.md +0 -0
  35. {velocity_python-0.1.105 → velocity_python-0.1.107}/setup.cfg +0 -0
  36. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/__init__.py +0 -0
  37. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/amplify.py +0 -0
  38. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/amplify_build.py +0 -0
  39. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/__init__.py +0 -0
  40. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/backfill.py +0 -0
  41. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/indexing.py +0 -0
  42. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/references.py +0 -0
  43. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/service.py +0 -0
  44. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/assets/usage_index.py +0 -0
  45. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/blobs.py +0 -0
  46. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/__init__.py +0 -0
  47. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/config.py +0 -0
  48. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/operations.py +0 -0
  49. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/policies.py +0 -0
  50. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/reconcile.py +0 -0
  51. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/deploy/targets.py +0 -0
  52. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/dirty_pipeline.py +0 -0
  53. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/email.py +0 -0
  54. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/__init__.py +0 -0
  55. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/context_factory.py +0 -0
  56. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/exceptions.py +0 -0
  57. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/masquerade.py +0 -0
  58. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  59. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  60. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  61. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/perf.py +0 -0
  62. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/response.py +0 -0
  63. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  64. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/job_results.py +0 -0
  65. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/s3.py +0 -0
  66. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/sms.py +0 -0
  67. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/ssm_config.py +0 -0
  68. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/__init__.py +0 -0
  69. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  70. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  71. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  72. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  73. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  74. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_email.py +0 -0
  75. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  76. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_response.py +0 -0
  77. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/aws/tests/test_sms.py +0 -0
  78. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/__init__.py +0 -0
  79. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/__init__.py +0 -0
  80. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/async_support.py +0 -0
  81. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/column.py +0 -0
  82. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/database.py +0 -0
  83. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/decorators.py +0 -0
  84. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/engine.py +0 -0
  85. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/jsonproxy.py +0 -0
  86. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/result.py +0 -0
  87. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/row.py +0 -0
  88. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/sequence.py +0 -0
  89. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/transaction.py +0 -0
  90. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/core/view.py +0 -0
  91. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/exceptions.py +0 -0
  92. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/maintenance.py +0 -0
  93. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/migrations.py +0 -0
  94. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/__init__.py +0 -0
  95. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/base/__init__.py +0 -0
  96. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/base/initializer.py +0 -0
  97. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/base/operators.py +0 -0
  98. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/base/sql.py +0 -0
  99. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/base/types.py +0 -0
  100. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/__init__.py +0 -0
  101. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/operators.py +0 -0
  102. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/reserved.py +0 -0
  103. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/sql.py +0 -0
  104. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/types.py +0 -0
  105. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/__init__.py +0 -0
  106. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/operators.py +0 -0
  107. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/reserved.py +0 -0
  108. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/sql.py +0 -0
  109. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/types.py +0 -0
  110. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  111. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/operators.py +0 -0
  112. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  113. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/sql.py +0 -0
  114. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/types.py +0 -0
  115. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  116. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  117. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  118. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  119. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/types.py +0 -0
  120. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/servers/tablehelper.py +0 -0
  121. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/__init__.py +0 -0
  122. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/common_db_test.py +0 -0
  123. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/__init__.py +0 -0
  124. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/common.py +0 -0
  125. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/conftest.py +0 -0
  126. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_column.py +0 -0
  127. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  128. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_database.py +0 -0
  129. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  130. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  131. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  132. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_result.py +0 -0
  133. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_row.py +0 -0
  134. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  135. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  136. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  137. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  138. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  139. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_table.py +0 -0
  140. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  141. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  142. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/sql/__init__.py +0 -0
  143. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/sql/common.py +0 -0
  144. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  145. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  146. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  147. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_db_utils.py +0 -0
  148. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_postgres.py +0 -0
  149. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  150. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  151. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_result_caching.py +0 -0
  152. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  153. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  154. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  155. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  156. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_sql_builder.py +0 -0
  157. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_tablehelper.py +0 -0
  158. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/tests/test_view_helper.py +0 -0
  159. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/db/utils.py +0 -0
  160. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/logging.py +0 -0
  161. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/__init__.py +0 -0
  162. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/conv/__init__.py +0 -0
  163. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/conv/iconv.py +0 -0
  164. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/conv/oconv.py +0 -0
  165. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/db.py +0 -0
  166. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/export.py +0 -0
  167. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/format.py +0 -0
  168. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/mail.py +0 -0
  169. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/merge.py +0 -0
  170. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/pdf.py +0 -0
  171. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/__init__.py +0 -0
  172. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_db.py +0 -0
  173. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_fix.py +0 -0
  174. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_format.py +0 -0
  175. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_iconv.py +0 -0
  176. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_mail_send.py +0 -0
  177. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_merge.py +0 -0
  178. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_oconv.py +0 -0
  179. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_original_error.py +0 -0
  180. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tests/test_timer.py +0 -0
  181. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/timer.py +0 -0
  182. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/misc/tools.py +0 -0
  183. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/__init__.py +0 -0
  184. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/core/__init__.py +0 -0
  185. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/core/capabilities.py +0 -0
  186. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/core/store.py +0 -0
  187. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/core/where.py +0 -0
  188. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/exceptions.py +0 -0
  189. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/__init__.py +0 -0
  190. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/base/__init__.py +0 -0
  191. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  192. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  193. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  194. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  195. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  196. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  197. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  198. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  199. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  200. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  201. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  202. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  203. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/__init__.py +0 -0
  204. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/authorizenet_adapter.py +0 -0
  205. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/authorizenet_mirror.py +0 -0
  206. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/base_adapter.py +0 -0
  207. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/braintree_adapter.py +0 -0
  208. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/braintree_mirror.py +0 -0
  209. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/charge_rules.py +0 -0
  210. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/stripe_adapter.py +0 -0
  211. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity/payment/stripe_mirror.py +0 -0
  212. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  213. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/entry_points.txt +0 -0
  214. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/requires.txt +0 -0
  215. {velocity_python-0.1.105 → velocity_python-0.1.107}/src/velocity_python.egg-info/top_level.txt +0 -0
  216. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_activity_tracking_aborted_tx.py +0 -0
  217. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_amplify_build.py +0 -0
  218. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_asset_indexing.py +0 -0
  219. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_asset_references.py +0 -0
  220. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_assets_service.py +0 -0
  221. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_async_support.py +0 -0
  222. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_batch_operations.py +0 -0
  223. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_blob_store.py +0 -0
  224. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_column_tx_arg.py +0 -0
  225. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_concurrency_safety.py +0 -0
  226. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_connection_pool.py +0 -0
  227. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_connection_resilience.py +0 -0
  228. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_context_job_descriptions.py +0 -0
  229. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_data_service_query_cap.py +0 -0
  230. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_data_service_renumber.py +0 -0
  231. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_data_service_save_as.py +0 -0
  232. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_db_credentials_ssm_cascade.py +0 -0
  233. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_db_maintenance.py +0 -0
  234. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_decorators.py +0 -0
  235. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_delete_hook_fk_blocked.py +0 -0
  236. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_dirty_pipeline_fast_path.py +0 -0
  237. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_email_processing.py +0 -0
  238. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_enqueue_send_failures.py +0 -0
  239. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_get_cognito_user_provider.py +0 -0
  240. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_http_handler_rollback.py +0 -0
  241. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_iconv_money_to_cents.py +0 -0
  242. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_identifier_injection_guard.py +0 -0
  243. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_job_handler.py +0 -0
  244. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_job_results.py +0 -0
  245. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_json_columns.py +0 -0
  246. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_jsonb_dict_adapter.py +0 -0
  247. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_lambda_handler.py +0 -0
  248. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_lambda_handler_auth.py +0 -0
  249. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_masquerade_grant.py +0 -0
  250. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_mixins_import.py +0 -0
  251. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_n_plus_one.py +0 -0
  252. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_dynamodb.py +0 -0
  253. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_memory.py +0 -0
  254. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_mongodb.py +0 -0
  255. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_redis.py +0 -0
  256. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_translation.py +0 -0
  257. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_nosql_where.py +0 -0
  258. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_observability.py +0 -0
  259. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_on_commit.py +0 -0
  260. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_authorizenet_adapter.py +0 -0
  261. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_braintree_adapter.py +0 -0
  262. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_braintree_mirror.py +0 -0
  263. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_mirror_empty_table.py +0 -0
  264. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_profile_sorting.py +0 -0
  265. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_payment_stripe_adapter.py +0 -0
  266. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_pdf.py +0 -0
  267. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_prepared_statements.py +0 -0
  268. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_psycopg3_upgrade.py +0 -0
  269. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_query_cache.py +0 -0
  270. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_query_missing_table_unlocked.py +0 -0
  271. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_response.py +0 -0
  272. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_restricted_direct_tables.py +0 -0
  273. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_retry_side_effect_guard.py +0 -0
  274. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_return_default_safety.py +0 -0
  275. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_row_batch_update.py +0 -0
  276. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_row_cache_staleness.py +0 -0
  277. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_schema_migrations.py +0 -0
  278. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_security_hardening.py +0 -0
  279. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_server_cursor.py +0 -0
  280. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_session_control.py +0 -0
  281. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_single_autocommit_safety.py +0 -0
  282. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_spreadsheet_functions.py +0 -0
  283. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_sqlite_backend.py +0 -0
  284. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_sqs_per_record_transactions.py +0 -0
  285. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_ssm_config.py +0 -0
  286. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_store_user_data.py +0 -0
  287. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  288. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_table_alter.py +0 -0
  289. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_transaction_class_wrapping.py +0 -0
  290. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_transaction_commit_and_ownership.py +0 -0
  291. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_transaction_edge_cases.py +0 -0
  292. {velocity_python-0.1.105 → velocity_python-0.1.107}/tests/test_where_clause_validation.py +0 -0
  293. {velocity_python-0.1.105 → velocity_python-0.1.107}/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.107
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.107"
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.107"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,60 @@
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
+ POLICY_KINDS,
14
+ REGISTRY,
15
+ Capability,
16
+ capability,
17
+ catalog,
18
+ describe,
19
+ get,
20
+ load_config,
21
+ policy_field,
22
+ resolve_config,
23
+ validate_policy,
24
+ )
25
+ from .dispatch import ( # noqa: F401
26
+ CapabilityCatalogMixin,
27
+ CapabilityDispatcher,
28
+ run_capability,
29
+ )
30
+ from .provisioning import provision, trigger_key # noqa: F401
31
+
32
+ # built-ins (importing registers them)
33
+ from . import retention # noqa: E402,F401
34
+ from . import security # noqa: E402,F401
35
+ from . import infra # noqa: E402,F401
36
+ from . import single_session # noqa: E402,F401
37
+ from . import jobs # noqa: E402,F401
38
+ from .security import SecurityMixin # noqa: E402,F401
39
+
40
+ __all__ = [
41
+ "Capability",
42
+ "CapabilityCatalogMixin",
43
+ "CapabilityDispatcher",
44
+ "POLICY_KINDS",
45
+ "REGISTRY",
46
+ "capability",
47
+ "catalog",
48
+ "describe",
49
+ "get",
50
+ "load_config",
51
+ "policy_field",
52
+ "provision",
53
+ "resolve_config",
54
+ "run_capability",
55
+ "trigger_key",
56
+ "validate_policy",
57
+ "NIGHTLY",
58
+ "HOURLY",
59
+ "EVERY_15_MINUTES",
60
+ ]
@@ -0,0 +1,254 @@
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
+ A capability also **describes itself** — ``label``, ``description`` and a
26
+ ``policy`` schema (:func:`policy_field`) — so an admin UI can offer the
27
+ registry as a pick-list and render a real editor for each policy instead of
28
+ a JSON box (:func:`catalog`), and so a write can be checked against the same
29
+ declaration before it lands (:func:`validate_policy`). One declaration, next
30
+ to the code that reads the config; nothing to keep in step by hand.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import ipaddress
36
+ import json
37
+ import re
38
+
39
+ # cron expressions the scheduler (croniter) accepts
40
+ NIGHTLY = "0 3 * * *"
41
+ HOURLY = "0 * * * *"
42
+ EVERY_15_MINUTES = "*/15 * * * *"
43
+ CRON_WORDS = {
44
+ NIGHTLY: "nightly at 03:00 UTC",
45
+ HOURLY: "hourly",
46
+ EVERY_15_MINUTES: "every 15 minutes",
47
+ }
48
+
49
+ # policy field kinds an editor knows how to render and this module validates
50
+ CIDR_LIST = "cidr_list" # ["203.0.113.0/24", "198.51.100.7"]
51
+ COUNTRY_LIST = "country_list" # ["US", "CA"] ISO 3166-1 alpha-2
52
+ TABLE_DAYS = "table_days" # {"aws_api_activity": 90}
53
+ NUMBER = "number"
54
+ TEXT = "text"
55
+ BOOLEAN = "boolean"
56
+ CHOICE = "choice" # one of `options`
57
+ LIST = "list" # free strings
58
+ POLICY_KINDS = (CIDR_LIST, COUNTRY_LIST, TABLE_DAYS, NUMBER, TEXT, BOOLEAN, CHOICE, LIST)
59
+
60
+ IDENTIFIER = re.compile(r"^[a-z_][a-z0-9_]*$")
61
+ COUNTRY_CODE = re.compile(r"^[A-Z]{2}$")
62
+
63
+ REGISTRY: dict = {}
64
+
65
+
66
+ def policy_field(name, kind, label, help="", **extra) -> dict:
67
+ """One entry of a capability's ``policy`` schema. ``extra`` carries
68
+ kind-specific hints an editor may use (``options`` for a choice,
69
+ ``default``, ``placeholder``, ``multiline``, ``min``)."""
70
+ if kind not in POLICY_KINDS:
71
+ raise ValueError(f"policy field {name!r}: unknown kind {kind!r}")
72
+ return {"name": name, "kind": kind, "label": label, "help": help, **extra}
73
+
74
+
75
+ class Capability:
76
+ name: str = ""
77
+ label: str = "" # human name; defaults to the name titled
78
+ description: str = "" # one or two sentences for the pick-list
79
+ tier: str = "runtime" # "runtime" (a Lambda runs it) | "infra" (stacks)
80
+ schema: dict = {} # {table: {column: sql type}} -> ensure_schema
81
+ defaults: dict = {} # flag name -> default; seeded as TEXT feature flags
82
+ schedules: tuple = () # ((action, cron), ...)
83
+ mixins: tuple = () # handler mixins the site attaches at runtime
84
+ applies_to: tuple = ("demo", "production") # default stages; registry can force
85
+ policy: tuple = () # (policy_field(...), ...) — the per-site config shape
86
+
87
+ def handle(self, action, tx, context, config):
88
+ """Do the work for one scheduled/queued ``action``. Return a JSON-able
89
+ summary (it lands in the queue response and the job log)."""
90
+ raise NotImplementedError(f"{self.name}: no handler for action {action!r}")
91
+
92
+ def on_refresh(self, tx, config):
93
+ """Hook run after a demo refresh (optional) — scrub/neutralise here."""
94
+ return None
95
+
96
+
97
+ def capability(cls):
98
+ """Class decorator: instantiate and register under ``cls.name``."""
99
+ if not getattr(cls, "name", ""):
100
+ raise ValueError(f"{cls.__name__} must set a non-empty `name`")
101
+ REGISTRY[cls.name] = cls()
102
+ return cls
103
+
104
+
105
+ def get(name):
106
+ try:
107
+ return REGISTRY[name]
108
+ except KeyError:
109
+ known = ", ".join(sorted(REGISTRY)) or "(none registered)"
110
+ raise KeyError(f"unknown capability {name!r}; registered: {known}") from None
111
+
112
+
113
+ def resolve_config(cap, flags=None, row_config=None) -> dict:
114
+ """defaults <- feature flags (declared knobs only) <- site registry config."""
115
+ config = dict(cap.defaults)
116
+ flags = flags or {}
117
+ for key in cap.defaults:
118
+ value = flags.get(key)
119
+ if value not in (None, ""):
120
+ config[key] = value
121
+ config.update(row_config or {})
122
+ return config
123
+
124
+
125
+ # ── self-description ────────────────────────────────────────────────────
126
+
127
+
128
+ def describe(cap) -> dict:
129
+ """JSON-able description of one capability for a UI or a CLI."""
130
+ return {
131
+ "name": cap.name,
132
+ "label": cap.label or cap.name.replace("_", " ").title(),
133
+ "description": cap.description,
134
+ "tier": cap.tier,
135
+ "applies_to": list(cap.applies_to),
136
+ "schedules": [
137
+ {"action": action, "cron": cron, "when": CRON_WORDS.get(cron, cron)}
138
+ for action, cron in cap.schedules
139
+ ],
140
+ "flags": list(cap.defaults),
141
+ "policy": [dict(field) for field in cap.policy],
142
+ }
143
+
144
+
145
+ def catalog() -> list:
146
+ """Every registered capability, described, ordered by label — the
147
+ pick-list an admin UI shows."""
148
+ return sorted((describe(cap) for cap in REGISTRY.values()), key=lambda d: d["label"])
149
+
150
+
151
+ def load_config(raw) -> dict:
152
+ """A row's ``config`` as a dict: JSON text, a dict, or empty."""
153
+ if raw in (None, ""):
154
+ return {}
155
+ if isinstance(raw, str):
156
+ value = json.loads(raw)
157
+ else:
158
+ value = raw
159
+ if not isinstance(value, dict):
160
+ raise ValueError("policy must be a JSON object")
161
+ return value
162
+
163
+
164
+ def validate_policy(name, config) -> list:
165
+ """Check a site's ``config`` against the capability's declared policy.
166
+
167
+ Returns human-readable problems (empty = valid). Declared fields are
168
+ typed; undeclared keys pass through untouched — a policy may carry more
169
+ than the editor knows (an older row, a hand edit), and the capability
170
+ decides what it reads. An unknown capability name is itself a problem.
171
+ """
172
+ cap = REGISTRY.get(name)
173
+ if cap is None:
174
+ known = ", ".join(sorted(REGISTRY)) or "(none registered)"
175
+ return [f"unknown capability {name!r}; registered: {known}"]
176
+ try:
177
+ config = load_config(config)
178
+ except ValueError as exc:
179
+ return [f"policy is not valid JSON: {exc}"]
180
+
181
+ problems = []
182
+ for field in cap.policy:
183
+ value = config.get(field["name"])
184
+ if value in (None, "", [], {}):
185
+ continue
186
+ problems.extend(
187
+ f"{field['label']}: {problem}"
188
+ for problem in _check_field(field, value)
189
+ )
190
+ return problems
191
+
192
+
193
+ def _check_field(field, value):
194
+ kind = field["kind"]
195
+ if kind in (CIDR_LIST, COUNTRY_LIST, LIST):
196
+ if not isinstance(value, (list, tuple)):
197
+ return ["must be a list"]
198
+ problems = []
199
+ for item in value:
200
+ item = str(item).strip()
201
+ if kind == CIDR_LIST:
202
+ try:
203
+ ipaddress.ip_network(item, strict=False)
204
+ except ValueError:
205
+ problems.append(f"{item!r} is not an IP address or CIDR")
206
+ elif kind == COUNTRY_LIST and not COUNTRY_CODE.match(item.upper()):
207
+ problems.append(f"{item!r} is not a two-letter country code")
208
+ return problems
209
+ if kind == TABLE_DAYS:
210
+ if not isinstance(value, dict):
211
+ return ["must map table names to days"]
212
+ problems = []
213
+ for table, days in value.items():
214
+ if not IDENTIFIER.match(str(table)):
215
+ problems.append(f"{table!r} is not a table name")
216
+ if days in (None, "", 0):
217
+ continue # falls back to the default window
218
+ if not _is_int(days) or int(days) < 1:
219
+ problems.append(f"{table}: days must be a whole number >= 1")
220
+ return problems
221
+ if kind == NUMBER:
222
+ if isinstance(value, bool) or not _is_number(value):
223
+ return ["must be a number"]
224
+ minimum = field.get("min")
225
+ if minimum is not None and float(value) < minimum:
226
+ return [f"must be >= {minimum}"]
227
+ return []
228
+ if kind == BOOLEAN:
229
+ ok = isinstance(value, bool) or str(value).lower() in ("true", "false")
230
+ return [] if ok else ["must be true or false"]
231
+ if kind == CHOICE:
232
+ options = field.get("options") or ()
233
+ return [] if value in options else [f"must be one of {', '.join(options)}"]
234
+ if kind == TEXT:
235
+ return [] if isinstance(value, str) else ["must be text"]
236
+ return []
237
+
238
+
239
+ def _is_int(value) -> bool:
240
+ if isinstance(value, bool):
241
+ return False
242
+ if isinstance(value, int):
243
+ return True
244
+ return isinstance(value, str) and value.strip().isdigit()
245
+
246
+
247
+ def _is_number(value) -> bool:
248
+ if isinstance(value, (int, float)):
249
+ return True
250
+ try:
251
+ float(str(value))
252
+ return True
253
+ except ValueError:
254
+ return False
@@ -0,0 +1,61 @@
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, catalog, 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)
51
+
52
+
53
+ class CapabilityCatalogMixin:
54
+ """Web-handler mixin for the control plane: ``capability-catalog`` answers
55
+ with every registered capability described (``{"rows": [...]}`` — the
56
+ shape velocity-react's ApiSelect and option hooks consume), so the admin
57
+ UI's pick-list and policy editors come from the code, not a copy."""
58
+
59
+ def OnActionCapabilityCatalog(self, tx, context):
60
+ del tx
61
+ context.response().set_body({"rows": catalog()})
@@ -0,0 +1,121 @@
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 BOOLEAN, CHOICE, NUMBER, TEXT, Capability, capability, policy_field
20
+
21
+
22
+ class InfraCapability(Capability):
23
+ tier = "infra"
24
+ schedules = ()
25
+ schema = {}
26
+
27
+ def handle(self, action, tx, context, config):
28
+ raise RuntimeError(
29
+ f"{self.name} is an infra capability rendered into the site's stacks "
30
+ f"by sitefactory; it has no runtime action ({action!r})"
31
+ )
32
+
33
+
34
+ @capability
35
+ class SecurityHeaders(InfraCapability):
36
+ name = "security_headers"
37
+ label = "Security headers (CloudFront)"
38
+ description = (
39
+ "Browser hardening headers on every web response: HSTS, nosniff, frame "
40
+ "options, referrer policy, and an optional Content-Security-Policy. "
41
+ "Free; on for every factory site by default — add a row to turn it "
42
+ "off or to set a CSP. Applied by site-update-web, not the backend deploy."
43
+ )
44
+ # the keys sitefactory.templates.web.security_headers_config reads
45
+ policy = (
46
+ policy_field(
47
+ "enabled",
48
+ BOOLEAN,
49
+ "Send security headers",
50
+ "Off removes the headers policy from the distribution.",
51
+ default=True,
52
+ ),
53
+ policy_field(
54
+ "csp",
55
+ TEXT,
56
+ "Content-Security-Policy",
57
+ "Optional. Sent verbatim; a wrong CSP blocks your own scripts, so "
58
+ "test on demo first.",
59
+ multiline=True,
60
+ placeholder="default-src 'self'; img-src 'self' data:",
61
+ ),
62
+ policy_field(
63
+ "frame_options",
64
+ CHOICE,
65
+ "Frame options",
66
+ "SAMEORIGIN lets the site frame itself; DENY forbids all framing.",
67
+ options=("SAMEORIGIN", "DENY"),
68
+ default="SAMEORIGIN",
69
+ ),
70
+ policy_field(
71
+ "referrer_policy",
72
+ CHOICE,
73
+ "Referrer policy",
74
+ "",
75
+ options=(
76
+ "strict-origin-when-cross-origin",
77
+ "no-referrer",
78
+ "same-origin",
79
+ "strict-origin",
80
+ ),
81
+ default="strict-origin-when-cross-origin",
82
+ ),
83
+ policy_field(
84
+ "hsts_max_age",
85
+ NUMBER,
86
+ "HSTS max-age (seconds)",
87
+ "Two years (63072000) is the preload-list minimum.",
88
+ default=63072000,
89
+ min=0,
90
+ ),
91
+ )
92
+
93
+
94
+ @capability
95
+ class ApiThrottling(InfraCapability):
96
+ name = "api_throttling"
97
+ label = "API throttling (API Gateway)"
98
+ description = (
99
+ "Stage-level request throttling on the private and public APIs — the "
100
+ "first, free line against floods. Free; the template defaults (100 "
101
+ "requests/s, burst 200) apply without a row. Applied by site-up."
102
+ )
103
+ # the keys sitefactory.driver reads for the api template
104
+ policy = (
105
+ policy_field(
106
+ "rate",
107
+ NUMBER,
108
+ "Steady rate (requests / second)",
109
+ "",
110
+ default=100,
111
+ min=1,
112
+ ),
113
+ policy_field(
114
+ "burst",
115
+ NUMBER,
116
+ "Burst (requests)",
117
+ "Short spikes above the steady rate that are still served.",
118
+ default=200,
119
+ min=1,
120
+ ),
121
+ )
@@ -0,0 +1,59 @@
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
+ label = "Job hygiene"
30
+ description = (
31
+ "Marks asynchronous jobs whose worker died as Failed (instead of "
32
+ "Running forever) and prunes old job activity. Windows are per-"
33
+ "environment flags; no per-site policy."
34
+ )
35
+ defaults = {"job_stale_minutes": "16", "job_retention_days": "30"}
36
+ schedules = (("expire_stale", EVERY_15_MINUTES),)
37
+
38
+ def handle(self, action, tx, context, config):
39
+ if action != "expire_stale":
40
+ raise ValueError(f"jobs: unknown action {action!r}")
41
+ now = datetime.datetime.now(datetime.timezone.utc)
42
+ stale_after = datetime.timedelta(
43
+ minutes=int(config.get("job_stale_minutes") or 16)
44
+ )
45
+ jobs = tx.table(JOB_ACTIVITY_TABLE)
46
+
47
+ expired = 0
48
+ for status in sorted(STALE_JOB_STATUSES):
49
+ for job in jobs.select(where={"status": status}):
50
+ if job_is_stale(job, now=now, stale_after=stale_after):
51
+ jobs.update(
52
+ {"status": "Failed", "message": json.dumps(STALE_FAILURE)},
53
+ where={"sys_id": job["sys_id"]},
54
+ )
55
+ expired += 1
56
+
57
+ days = int(config.get("job_retention_days") or 30)
58
+ purged = jobs.purge({"<sys_created": now - datetime.timedelta(days=days)})
59
+ return {"expired": expired, "purged": purged}