velocity-python 0.1.106__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 (295) hide show
  1. {velocity_python-0.1.106 → velocity_python-0.1.107}/PKG-INFO +1 -1
  2. {velocity_python-0.1.106 → velocity_python-0.1.107}/pyproject.toml +1 -1
  3. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/__init__.py +18 -1
  5. velocity_python-0.1.107/src/velocity/aws/capabilities/base.py +254 -0
  6. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/dispatch.py +12 -1
  7. velocity_python-0.1.107/src/velocity/aws/capabilities/infra.py +121 -0
  8. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/jobs.py +6 -0
  9. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/retention.py +18 -1
  10. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/security.py +72 -4
  11. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/single_session.py +7 -0
  12. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/PKG-INFO +1 -1
  13. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  14. velocity_python-0.1.107/tests/test_capability_catalog.py +188 -0
  15. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_security_capability.py +15 -1
  16. velocity_python-0.1.106/src/velocity/aws/capabilities/base.py +0 -79
  17. velocity_python-0.1.106/src/velocity/aws/capabilities/infra.py +0 -43
  18. {velocity_python-0.1.106 → velocity_python-0.1.107}/LICENSE +0 -0
  19. {velocity_python-0.1.106 → velocity_python-0.1.107}/README.md +0 -0
  20. {velocity_python-0.1.106 → velocity_python-0.1.107}/setup.cfg +0 -0
  21. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/__init__.py +0 -0
  22. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/amplify.py +0 -0
  23. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/amplify_build.py +0 -0
  24. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/__init__.py +0 -0
  25. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/backfill.py +0 -0
  26. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/indexing.py +0 -0
  27. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/references.py +0 -0
  28. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/service.py +0 -0
  29. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/assets/usage_index.py +0 -0
  30. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/blobs.py +0 -0
  31. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/capabilities/provisioning.py +0 -0
  32. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/__init__.py +0 -0
  33. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/config.py +0 -0
  34. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/operations.py +0 -0
  35. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/policies.py +0 -0
  36. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/reconcile.py +0 -0
  37. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/deploy/targets.py +0 -0
  38. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/dirty_pipeline.py +0 -0
  39. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/email.py +0 -0
  40. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/feature_flags.py +0 -0
  41. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/__init__.py +0 -0
  42. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/base_handler.py +0 -0
  43. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/context.py +0 -0
  44. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/context_factory.py +0 -0
  45. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/exceptions.py +0 -0
  46. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  47. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/masquerade.py +0 -0
  48. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  49. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  50. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  51. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  52. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  53. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/perf.py +0 -0
  54. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/response.py +0 -0
  55. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  56. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/job_lifecycle.py +0 -0
  57. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/job_results.py +0 -0
  58. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/s3.py +0 -0
  59. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/sms.py +0 -0
  60. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/ssm_config.py +0 -0
  61. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/__init__.py +0 -0
  62. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  63. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  64. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  65. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  66. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  67. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_email.py +0 -0
  68. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  69. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_response.py +0 -0
  70. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/aws/tests/test_sms.py +0 -0
  71. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/__init__.py +0 -0
  72. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/__init__.py +0 -0
  73. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/async_support.py +0 -0
  74. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/column.py +0 -0
  75. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/database.py +0 -0
  76. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/decorators.py +0 -0
  77. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/engine.py +0 -0
  78. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/jsonproxy.py +0 -0
  79. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/result.py +0 -0
  80. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/row.py +0 -0
  81. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/sequence.py +0 -0
  82. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/table.py +0 -0
  83. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/transaction.py +0 -0
  84. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/core/view.py +0 -0
  85. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/exceptions.py +0 -0
  86. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/maintenance.py +0 -0
  87. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/migrations.py +0 -0
  88. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/__init__.py +0 -0
  89. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/base/__init__.py +0 -0
  90. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/base/initializer.py +0 -0
  91. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/base/operators.py +0 -0
  92. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/base/sql.py +0 -0
  93. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/base/types.py +0 -0
  94. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/__init__.py +0 -0
  95. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/operators.py +0 -0
  96. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/reserved.py +0 -0
  97. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/sql.py +0 -0
  98. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/mysql/types.py +0 -0
  99. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/__init__.py +0 -0
  100. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/operators.py +0 -0
  101. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/reserved.py +0 -0
  102. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/sql.py +0 -0
  103. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/postgres/types.py +0 -0
  104. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  105. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/operators.py +0 -0
  106. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  107. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/sql.py +0 -0
  108. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlite/types.py +0 -0
  109. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  110. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  111. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  112. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  113. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/sqlserver/types.py +0 -0
  114. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/servers/tablehelper.py +0 -0
  115. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/__init__.py +0 -0
  116. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/common_db_test.py +0 -0
  117. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/__init__.py +0 -0
  118. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/common.py +0 -0
  119. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/conftest.py +0 -0
  120. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_column.py +0 -0
  121. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  122. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_database.py +0 -0
  123. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  124. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  125. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  126. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_result.py +0 -0
  127. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_row.py +0 -0
  128. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  129. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  130. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  131. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  132. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  133. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_table.py +0 -0
  134. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  135. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  136. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/sql/__init__.py +0 -0
  137. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/sql/common.py +0 -0
  138. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  139. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  140. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  141. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_db_utils.py +0 -0
  142. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_postgres.py +0 -0
  143. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  144. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  145. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_result_caching.py +0 -0
  146. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  147. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  148. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  149. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  150. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_sql_builder.py +0 -0
  151. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_tablehelper.py +0 -0
  152. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/tests/test_view_helper.py +0 -0
  153. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/db/utils.py +0 -0
  154. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/logging.py +0 -0
  155. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/__init__.py +0 -0
  156. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/conv/__init__.py +0 -0
  157. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/conv/iconv.py +0 -0
  158. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/conv/oconv.py +0 -0
  159. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/db.py +0 -0
  160. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/export.py +0 -0
  161. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/format.py +0 -0
  162. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/mail.py +0 -0
  163. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/merge.py +0 -0
  164. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/pdf.py +0 -0
  165. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/__init__.py +0 -0
  166. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_db.py +0 -0
  167. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_fix.py +0 -0
  168. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_format.py +0 -0
  169. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_iconv.py +0 -0
  170. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_mail_send.py +0 -0
  171. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_merge.py +0 -0
  172. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_oconv.py +0 -0
  173. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_original_error.py +0 -0
  174. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tests/test_timer.py +0 -0
  175. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/timer.py +0 -0
  176. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/misc/tools.py +0 -0
  177. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/__init__.py +0 -0
  178. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/core/__init__.py +0 -0
  179. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/core/capabilities.py +0 -0
  180. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/core/store.py +0 -0
  181. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/core/where.py +0 -0
  182. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/exceptions.py +0 -0
  183. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/__init__.py +0 -0
  184. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/base/__init__.py +0 -0
  185. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  186. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  187. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  188. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  189. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  190. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  191. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  192. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  193. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  194. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  195. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  196. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  197. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/__init__.py +0 -0
  198. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/authorizenet_adapter.py +0 -0
  199. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/authorizenet_mirror.py +0 -0
  200. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/base_adapter.py +0 -0
  201. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/braintree_adapter.py +0 -0
  202. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/braintree_mirror.py +0 -0
  203. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/charge_rules.py +0 -0
  204. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/stripe_adapter.py +0 -0
  205. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity/payment/stripe_mirror.py +0 -0
  206. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  207. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/entry_points.txt +0 -0
  208. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/requires.txt +0 -0
  209. {velocity_python-0.1.106 → velocity_python-0.1.107}/src/velocity_python.egg-info/top_level.txt +0 -0
  210. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_access_error_403.py +0 -0
  211. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_activity_tracking_aborted_tx.py +0 -0
  212. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_amplify_build.py +0 -0
  213. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_asset_indexing.py +0 -0
  214. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_asset_references.py +0 -0
  215. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_assets_service.py +0 -0
  216. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_async_support.py +0 -0
  217. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_batch_operations.py +0 -0
  218. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_blob_store.py +0 -0
  219. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_capabilities.py +0 -0
  220. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_column_tx_arg.py +0 -0
  221. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_concurrency_safety.py +0 -0
  222. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_connection_pool.py +0 -0
  223. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_connection_resilience.py +0 -0
  224. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_context_flags_loader.py +0 -0
  225. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_context_job_descriptions.py +0 -0
  226. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_data_service_query_cap.py +0 -0
  227. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_data_service_renumber.py +0 -0
  228. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_data_service_save_as.py +0 -0
  229. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_db_credentials_ssm_cascade.py +0 -0
  230. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_db_maintenance.py +0 -0
  231. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_decorators.py +0 -0
  232. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_delete_hook_fk_blocked.py +0 -0
  233. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_dirty_pipeline_fast_path.py +0 -0
  234. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_email_processing.py +0 -0
  235. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_enqueue_send_failures.py +0 -0
  236. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_feature_flags.py +0 -0
  237. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_get_cognito_user_provider.py +0 -0
  238. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_http_handler_rollback.py +0 -0
  239. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_iconv_money_to_cents.py +0 -0
  240. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_identifier_injection_guard.py +0 -0
  241. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_job_handler.py +0 -0
  242. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_job_lifecycle.py +0 -0
  243. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_job_results.py +0 -0
  244. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_json_columns.py +0 -0
  245. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_jsonb_dict_adapter.py +0 -0
  246. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_lambda_handler.py +0 -0
  247. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_lambda_handler_auth.py +0 -0
  248. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_lambda_handler_masquerade.py +0 -0
  249. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_masquerade_grant.py +0 -0
  250. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_mixins_import.py +0 -0
  251. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_n_plus_one.py +0 -0
  252. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_dynamodb.py +0 -0
  253. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_memory.py +0 -0
  254. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_mongodb.py +0 -0
  255. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_redis.py +0 -0
  256. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_translation.py +0 -0
  257. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_nosql_where.py +0 -0
  258. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_observability.py +0 -0
  259. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_on_commit.py +0 -0
  260. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_authorizenet_adapter.py +0 -0
  261. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_braintree_adapter.py +0 -0
  262. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_braintree_mirror.py +0 -0
  263. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_mirror_empty_table.py +0 -0
  264. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_profile_sorting.py +0 -0
  265. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_payment_stripe_adapter.py +0 -0
  266. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_pdf.py +0 -0
  267. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_prepared_statements.py +0 -0
  268. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_psycopg3_upgrade.py +0 -0
  269. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_query_cache.py +0 -0
  270. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_query_missing_table_unlocked.py +0 -0
  271. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_response.py +0 -0
  272. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_restricted_direct_tables.py +0 -0
  273. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_retry_side_effect_guard.py +0 -0
  274. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_return_default_safety.py +0 -0
  275. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_row_batch_update.py +0 -0
  276. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_row_cache_staleness.py +0 -0
  277. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_schema_migrations.py +0 -0
  278. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_security_hardening.py +0 -0
  279. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_server_cursor.py +0 -0
  280. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_session_control.py +0 -0
  281. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_single_autocommit_safety.py +0 -0
  282. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_single_session_capability.py +0 -0
  283. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_spreadsheet_functions.py +0 -0
  284. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_sqlite_backend.py +0 -0
  285. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_sqs_per_record_transactions.py +0 -0
  286. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_ssm_config.py +0 -0
  287. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_store_user_data.py +0 -0
  288. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  289. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_table_alter.py +0 -0
  290. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_table_purge.py +0 -0
  291. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_transaction_class_wrapping.py +0 -0
  292. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_transaction_commit_and_ownership.py +0 -0
  293. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_transaction_edge_cases.py +0 -0
  294. {velocity_python-0.1.106 → velocity_python-0.1.107}/tests/test_where_clause_validation.py +0 -0
  295. {velocity_python-0.1.106 → 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.106
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.106"
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.106"
1
+ __version__ = version = "0.1.107"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -10,13 +10,23 @@ from .base import ( # noqa: F401
10
10
  EVERY_15_MINUTES,
11
11
  HOURLY,
12
12
  NIGHTLY,
13
+ POLICY_KINDS,
13
14
  REGISTRY,
14
15
  Capability,
15
16
  capability,
17
+ catalog,
18
+ describe,
16
19
  get,
20
+ load_config,
21
+ policy_field,
17
22
  resolve_config,
23
+ validate_policy,
24
+ )
25
+ from .dispatch import ( # noqa: F401
26
+ CapabilityCatalogMixin,
27
+ CapabilityDispatcher,
28
+ run_capability,
18
29
  )
19
- from .dispatch import CapabilityDispatcher, run_capability # noqa: F401
20
30
  from .provisioning import provision, trigger_key # noqa: F401
21
31
 
22
32
  # built-ins (importing registers them)
@@ -29,14 +39,21 @@ from .security import SecurityMixin # noqa: E402,F401
29
39
 
30
40
  __all__ = [
31
41
  "Capability",
42
+ "CapabilityCatalogMixin",
32
43
  "CapabilityDispatcher",
44
+ "POLICY_KINDS",
33
45
  "REGISTRY",
34
46
  "capability",
47
+ "catalog",
48
+ "describe",
35
49
  "get",
50
+ "load_config",
51
+ "policy_field",
36
52
  "provision",
37
53
  "resolve_config",
38
54
  "run_capability",
39
55
  "trigger_key",
56
+ "validate_policy",
40
57
  "NIGHTLY",
41
58
  "HOURLY",
42
59
  "EVERY_15_MINUTES",
@@ -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
@@ -13,7 +13,7 @@ import json
13
13
 
14
14
  from velocity.aws import feature_flags
15
15
 
16
- from .base import REGISTRY, resolve_config
16
+ from .base import REGISTRY, catalog, resolve_config
17
17
  from .provisioning import SITE_TABLE
18
18
 
19
19
 
@@ -48,3 +48,14 @@ class CapabilityDispatcher:
48
48
  tx, context, payload.get("capability"), payload.get("action")
49
49
  )
50
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
+ )
@@ -26,6 +26,12 @@ from .base import EVERY_15_MINUTES, Capability, capability
26
26
  @capability
27
27
  class Jobs(Capability):
28
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
+ )
29
35
  defaults = {"job_stale_minutes": "16", "job_retention_days": "30"}
30
36
  schedules = (("expire_stale", EVERY_15_MINUTES),)
31
37
 
@@ -18,7 +18,7 @@ from __future__ import annotations
18
18
  import datetime
19
19
  import re
20
20
 
21
- from .base import NIGHTLY, Capability, capability
21
+ from .base import NIGHTLY, TABLE_DAYS, Capability, capability, policy_field
22
22
 
23
23
  IDENTIFIER = re.compile(r"^[a-z_][a-z0-9_]*$")
24
24
  TIMESTAMP_COLUMN = "sys_created"
@@ -29,8 +29,25 @@ MAX_BATCHES = 200 # 1M rows per run — well inside the Lambda budget
29
29
  @capability
30
30
  class Retention(Capability):
31
31
  name = "retention"
32
+ label = "Retention (table pruning)"
33
+ description = (
34
+ "Deletes rows older than a per-table window so log-like tables stop "
35
+ "growing forever. Only the tables listed in the policy are ever touched; "
36
+ "rows are aged by sys_created and deleted nightly in small batches."
37
+ )
32
38
  defaults = {"purge_default_days": "90"}
33
39
  schedules = (("purge", NIGHTLY),)
40
+ policy = (
41
+ policy_field(
42
+ "tables",
43
+ TABLE_DAYS,
44
+ "Tables to prune",
45
+ "Each table and how many days of rows to keep. Leave days blank to "
46
+ "use the purge_default_days flag. Archives are not logs — list only "
47
+ "tables you are happy to lose old rows from.",
48
+ placeholder="aws_api_activity",
49
+ ),
50
+ )
34
51
 
35
52
  def handle(self, action, tx, context, config):
36
53
  if action != "purge":
@@ -31,7 +31,15 @@ import time
31
31
 
32
32
  from velocity.aws import feature_flags
33
33
 
34
- from .base import HOURLY, Capability, capability, resolve_config
34
+ from .base import (
35
+ CIDR_LIST,
36
+ COUNTRY_LIST,
37
+ HOURLY,
38
+ Capability,
39
+ capability,
40
+ policy_field,
41
+ resolve_config,
42
+ )
35
43
  from .provisioning import SITE_TABLE
36
44
 
37
45
  log = logging.getLogger(__name__)
@@ -174,9 +182,22 @@ def _cached(key, loader, *, now=None):
174
182
 
175
183
 
176
184
  def _site_row(tx):
177
- return _cached(
178
- ("site", "security"), lambda: tx.table(SITE_TABLE).find({"name": "security"})
179
- )
185
+ """The site's `security` row from sys_capabilities, cached per container.
186
+
187
+ A site that has never provisioned a capability has no sys_capabilities
188
+ table at all — a bare find() there is a failing statement on every request
189
+ (rolled back and logged by velocity, but noise and a wasted round trip).
190
+ exists() is a cheap catalog check and the honest answer: not provisioned
191
+ here, so the mixin is a no-op.
192
+ """
193
+
194
+ def load():
195
+ table = tx.table(SITE_TABLE)
196
+ if not table.exists():
197
+ return None
198
+ return table.find({"name": "security"})
199
+
200
+ return _cached(("site", "security"), load)
180
201
 
181
202
 
182
203
  def _flags(tx):
@@ -311,7 +332,54 @@ class SecurityMixin:
311
332
  @capability
312
333
  class Security(Capability):
313
334
  name = "security"
335
+ label = "Security guards"
336
+ description = (
337
+ "Request-path guards in the application: IP deny/allow lists, country "
338
+ "allow/block, per-IP and per-user rate limits, payload caps, and an "
339
+ "audit of every refusal. Each guard is a switch on the Security page; "
340
+ "this policy holds the lists."
341
+ )
314
342
  defaults = {**SWITCHES, **KNOBS}
343
+ policy = (
344
+ policy_field(
345
+ "ip_denylist",
346
+ CIDR_LIST,
347
+ "IP deny list",
348
+ "Addresses or CIDR ranges refused outright (when the IP deny list "
349
+ "switch is on).",
350
+ placeholder="203.0.113.0/24",
351
+ ),
352
+ policy_field(
353
+ "ip_allowlist",
354
+ CIDR_LIST,
355
+ "IP allow list",
356
+ "When the IP allow list switch is on, ONLY these addresses may call "
357
+ "— an office/VPN lock-down. Leave empty for no restriction.",
358
+ placeholder="198.51.100.0/24",
359
+ ),
360
+ policy_field(
361
+ "blocked_countries",
362
+ COUNTRY_LIST,
363
+ "Blocked countries",
364
+ "Requests from these countries are refused (country switch on; "
365
+ "judged from the CloudFront viewer country).",
366
+ ),
367
+ policy_field(
368
+ "allowed_countries",
369
+ COUNTRY_LIST,
370
+ "Allowed countries",
371
+ "When set, only these countries may call. Leave empty to allow all "
372
+ "but the blocked ones.",
373
+ ),
374
+ policy_field(
375
+ "rate_limit_exempt",
376
+ CIDR_LIST,
377
+ "Rate-limit exempt networks",
378
+ "Addresses never counted against the rate limit (monitors, the "
379
+ "office, a partner integration).",
380
+ placeholder="192.0.2.0/24",
381
+ ),
382
+ )
315
383
  schema = {
316
384
  EVENTS_TABLE: {
317
385
  "guard": "TEXT",
@@ -26,6 +26,13 @@ SESSION_TABLE = "app_session"
26
26
  @capability
27
27
  class SingleSession(Capability):
28
28
  name = "single_session"
29
+ label = "Single session per user"
30
+ description = (
31
+ "One active login per user: a new sign-in ends the older session. "
32
+ "Enforcement and the staleness window are per-environment flags; the "
33
+ "reaper deletes sessions not seen for the retention window. No "
34
+ "per-site policy."
35
+ )
29
36
  defaults = {
30
37
  "single_session_enforcement": "true",
31
38
  "session_stale_minutes": "10",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.106
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
@@ -211,6 +211,7 @@ tests/test_async_support.py
211
211
  tests/test_batch_operations.py
212
212
  tests/test_blob_store.py
213
213
  tests/test_capabilities.py
214
+ tests/test_capability_catalog.py
214
215
  tests/test_column_tx_arg.py
215
216
  tests/test_concurrency_safety.py
216
217
  tests/test_connection_pool.py