velocity-python 0.1.20__tar.gz → 0.1.22__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 (171) hide show
  1. {velocity_python-0.1.20/src/velocity_python.egg-info → velocity_python-0.1.22}/PKG-INFO +1 -1
  2. {velocity_python-0.1.20 → velocity_python-0.1.22}/pyproject.toml +1 -1
  3. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/__init__.py +20 -0
  5. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/amplify_build.py +1 -1
  6. velocity_python-0.1.22/src/velocity/aws/s3.py +175 -0
  7. {velocity_python-0.1.20 → velocity_python-0.1.22/src/velocity_python.egg-info}/PKG-INFO +1 -1
  8. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  9. {velocity_python-0.1.20 → velocity_python-0.1.22}/LICENSE +0 -0
  10. {velocity_python-0.1.20 → velocity_python-0.1.22}/README.md +0 -0
  11. {velocity_python-0.1.20 → velocity_python-0.1.22}/setup.cfg +0 -0
  12. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/amplify.py +0 -0
  13. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/__init__.py +0 -0
  14. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/base_handler.py +0 -0
  15. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/context.py +0 -0
  16. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/context_factory.py +0 -0
  17. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/exceptions.py +0 -0
  18. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  19. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  20. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  21. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  22. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/perf.py +0 -0
  23. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/response.py +0 -0
  24. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  25. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/tests/__init__.py +0 -0
  26. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  27. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  28. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/aws/tests/test_response.py +0 -0
  29. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/__init__.py +0 -0
  30. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/__init__.py +0 -0
  31. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/async_support.py +0 -0
  32. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/column.py +0 -0
  33. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/database.py +0 -0
  34. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/decorators.py +0 -0
  35. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/engine.py +0 -0
  36. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/result.py +0 -0
  37. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/row.py +0 -0
  38. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/sequence.py +0 -0
  39. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/table.py +0 -0
  40. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/transaction.py +0 -0
  41. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/core/view.py +0 -0
  42. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/exceptions.py +0 -0
  43. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/migrations.py +0 -0
  44. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/__init__.py +0 -0
  45. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/base/__init__.py +0 -0
  46. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/base/initializer.py +0 -0
  47. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/base/operators.py +0 -0
  48. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/base/sql.py +0 -0
  49. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/base/types.py +0 -0
  50. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/mysql/__init__.py +0 -0
  51. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/mysql/operators.py +0 -0
  52. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/mysql/reserved.py +0 -0
  53. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/mysql/sql.py +0 -0
  54. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/mysql/types.py +0 -0
  55. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/postgres/__init__.py +0 -0
  56. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/postgres/operators.py +0 -0
  57. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/postgres/reserved.py +0 -0
  58. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/postgres/sql.py +0 -0
  59. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/postgres/types.py +0 -0
  60. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  61. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlite/operators.py +0 -0
  62. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  63. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlite/sql.py +0 -0
  64. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlite/types.py +0 -0
  65. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  66. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  67. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  68. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  69. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/sqlserver/types.py +0 -0
  70. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/servers/tablehelper.py +0 -0
  71. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/__init__.py +0 -0
  72. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/common_db_test.py +0 -0
  73. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/__init__.py +0 -0
  74. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/common.py +0 -0
  75. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_column.py +0 -0
  76. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  77. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_database.py +0 -0
  78. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  79. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  80. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  81. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_result.py +0 -0
  82. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_row.py +0 -0
  83. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  84. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  85. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  86. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  87. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  88. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_table.py +0 -0
  89. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  90. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  91. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/sql/__init__.py +0 -0
  92. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/sql/common.py +0 -0
  93. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  94. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  95. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  96. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_db_utils.py +0 -0
  97. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_postgres.py +0 -0
  98. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  99. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  100. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_result_caching.py +0 -0
  101. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  102. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  103. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  104. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  105. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_sql_builder.py +0 -0
  106. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_tablehelper.py +0 -0
  107. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/tests/test_view_helper.py +0 -0
  108. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/db/utils.py +0 -0
  109. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/logging.py +0 -0
  110. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/__init__.py +0 -0
  111. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/conv/__init__.py +0 -0
  112. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/conv/iconv.py +0 -0
  113. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/conv/oconv.py +0 -0
  114. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/db.py +0 -0
  115. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/export.py +0 -0
  116. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/format.py +0 -0
  117. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/mail.py +0 -0
  118. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/merge.py +0 -0
  119. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/pdf.py +0 -0
  120. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/__init__.py +0 -0
  121. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_db.py +0 -0
  122. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_fix.py +0 -0
  123. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_format.py +0 -0
  124. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_iconv.py +0 -0
  125. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_merge.py +0 -0
  126. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_oconv.py +0 -0
  127. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_original_error.py +0 -0
  128. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tests/test_timer.py +0 -0
  129. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/timer.py +0 -0
  130. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/misc/tools.py +0 -0
  131. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/__init__.py +0 -0
  132. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/authorizenet_adapter.py +0 -0
  133. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/base_adapter.py +0 -0
  134. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/braintree_adapter.py +0 -0
  135. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/charge_rules.py +0 -0
  136. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity/payment/stripe_adapter.py +0 -0
  137. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  138. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity_python.egg-info/entry_points.txt +0 -0
  139. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity_python.egg-info/requires.txt +0 -0
  140. {velocity_python-0.1.20 → velocity_python-0.1.22}/src/velocity_python.egg-info/top_level.txt +0 -0
  141. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_amplify_build.py +0 -0
  142. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_async_support.py +0 -0
  143. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_batch_operations.py +0 -0
  144. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_concurrency_safety.py +0 -0
  145. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_connection_pool.py +0 -0
  146. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_connection_resilience.py +0 -0
  147. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_decorators.py +0 -0
  148. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_email_processing.py +0 -0
  149. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_iconv_money_to_cents.py +0 -0
  150. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_lambda_handler.py +0 -0
  151. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_lambda_handler_auth.py +0 -0
  152. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_mixins_import.py +0 -0
  153. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_n_plus_one.py +0 -0
  154. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_observability.py +0 -0
  155. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_payment_braintree_adapter.py +0 -0
  156. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_payment_profile_sorting.py +0 -0
  157. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_payment_stripe_adapter.py +0 -0
  158. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_pdf.py +0 -0
  159. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_prepared_statements.py +0 -0
  160. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_psycopg3_upgrade.py +0 -0
  161. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_query_cache.py +0 -0
  162. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_row_batch_update.py +0 -0
  163. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_row_cache_staleness.py +0 -0
  164. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_row_dirty_tracking.py +0 -0
  165. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_schema_migrations.py +0 -0
  166. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_security_hardening.py +0 -0
  167. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_spreadsheet_functions.py +0 -0
  168. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_sqs_per_record_transactions.py +0 -0
  169. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  170. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_table_alter.py +0 -0
  171. {velocity_python-0.1.20 → velocity_python-0.1.22}/tests/test_where_clause_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.20
3
+ Version: 0.1.22
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.20"
7
+ version = "0.1.22"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.20"
1
+ __version__ = version = "0.1.22"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -28,6 +28,16 @@ _LAZY_ATTRS = {
28
28
  "initialize_build_environment": "velocity.aws.amplify_build",
29
29
  "run_backend_deployment": "velocity.aws.amplify_build",
30
30
  "update_lambda_layers_to_latest": "velocity.aws.amplify_build",
31
+ # S3 utilities
32
+ "generate_presigned_get": "velocity.aws.s3",
33
+ "generate_presigned_put": "velocity.aws.s3",
34
+ "list_client_files": "velocity.aws.s3",
35
+ "normalize_client_prefix": "velocity.aws.s3",
36
+ "CLIENT_FILES_BUCKET": "velocity.aws.s3",
37
+ "PUBLIC_ASSETS_BUCKET": "velocity.aws.s3",
38
+ "BACKOFFICE_BUCKET": "velocity.aws.s3",
39
+ "PRIVATE_BUCKETS": "velocity.aws.s3",
40
+ "ALLOWED_UPLOAD_BUCKETS": "velocity.aws.s3",
31
41
  }
32
42
 
33
43
 
@@ -47,4 +57,14 @@ __all__ = [
47
57
  "initialize_build_environment",
48
58
  "run_backend_deployment",
49
59
  "update_lambda_layers_to_latest",
60
+ # S3 utilities
61
+ "generate_presigned_get",
62
+ "generate_presigned_put",
63
+ "list_client_files",
64
+ "normalize_client_prefix",
65
+ "CLIENT_FILES_BUCKET",
66
+ "PUBLIC_ASSETS_BUCKET",
67
+ "BACKOFFICE_BUCKET",
68
+ "PRIVATE_BUCKETS",
69
+ "ALLOWED_UPLOAD_BUCKETS",
50
70
  ]
@@ -377,7 +377,7 @@ def set_cloudwatch_logging(
377
377
  f"arn:aws:lambda:{region}:{account_id}:function:LambdaCloudWatchNotification"
378
378
  ),
379
379
  "filterName": function_name,
380
- "filterPattern": "?ERROR ?WARN ?EXCEPTION ?CRITICAL",
380
+ "filterPattern": "?error ?exception ?warning ?critical ?ERROR ?EXCEPTION ?WARN ?CRITICAL",
381
381
  }
382
382
  for _attempt in range(2):
383
383
  try:
@@ -0,0 +1,175 @@
1
+ """Centralized S3 utilities: presigned URL generation and private-file listing.
2
+
3
+ Architectural principles enforced here
4
+ ---------------------------------------
5
+ * All files in *private* buckets (e.g. ``caringcent.client.files``) MUST be
6
+ stored without a public ACL. Access is granted exclusively through
7
+ time-limited presigned GET URLs generated by this module.
8
+ * Direct browser-to-S3 uploads are supported via presigned PUT URLs so that
9
+ file bytes never pass through Lambda (avoids the 6 MB API Gateway payload
10
+ ceiling and reduces Lambda cost).
11
+ * Public-asset buckets (``donate.resources``, ``backoffice.resources``) remain
12
+ unaffected by this module; they continue to use ``ACL: public-read`` when
13
+ uploaded via ``helpers.upload(public=True)``.
14
+
15
+ Bucket constants
16
+ -----------------
17
+ Import and compare against these rather than hard-coding bucket names in
18
+ Lambda handlers so a rename only needs to happen in one place.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import re
23
+ import boto3
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Bucket name constants
27
+ # ---------------------------------------------------------------------------
28
+
29
+ CLIENT_FILES_BUCKET: str = "caringcent.client.files"
30
+ """Private bucket that stores per-client files (billing reports, uploads)."""
31
+
32
+ PUBLIC_ASSETS_BUCKET: str = "donate.resources"
33
+ """Public bucket for images and form-builder assets embedded in donation forms."""
34
+
35
+ BACKOFFICE_BUCKET: str = "backoffice.resources"
36
+ """Internal bucket for BackOffice admin assets (help videos, data-load ZIPs)."""
37
+
38
+ # Buckets that must never be served with a public ACL.
39
+ PRIVATE_BUCKETS: frozenset[str] = frozenset({CLIENT_FILES_BUCKET})
40
+
41
+ # Buckets that are permitted as upload destinations from BackOffice.
42
+ ALLOWED_UPLOAD_BUCKETS: frozenset[str] = frozenset(
43
+ {CLIENT_FILES_BUCKET, PUBLIC_ASSETS_BUCKET, BACKOFFICE_BUCKET}
44
+ )
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Presigned URL helpers
49
+ # ---------------------------------------------------------------------------
50
+
51
+ def generate_presigned_get(
52
+ bucket: str,
53
+ key: str,
54
+ *,
55
+ expires_in: int = 3600,
56
+ filename: str | None = None,
57
+ content_type: str | None = None,
58
+ ) -> str:
59
+ """Return a presigned GET URL for a private S3 object.
60
+
61
+ Args:
62
+ bucket: S3 bucket name.
63
+ key: S3 object key.
64
+ expires_in: URL lifetime in seconds (default 1 hour).
65
+ filename: When provided the presigned URL includes a
66
+ ``Content-Disposition: attachment; filename="..."`` override so the
67
+ browser downloads the file under *filename* instead of navigating
68
+ to the raw key. This also prevents the browser from navigating
69
+ away from the current SPA page.
70
+ content_type: Optional ``Content-Type`` override embedded in the
71
+ presigned URL query-string.
72
+
73
+ Returns:
74
+ A time-limited, signed HTTPS URL that grants GET access to the object.
75
+ """
76
+ params: dict = {"Bucket": bucket, "Key": key}
77
+ if filename:
78
+ params["ResponseContentDisposition"] = f'attachment; filename="{filename}"'
79
+ if content_type:
80
+ params["ResponseContentType"] = content_type
81
+ s3 = boto3.client("s3")
82
+ return s3.generate_presigned_url("get_object", Params=params, ExpiresIn=expires_in)
83
+
84
+
85
+ def generate_presigned_put(
86
+ bucket: str,
87
+ key: str,
88
+ *,
89
+ content_type: str | None = None,
90
+ expires_in: int = 900,
91
+ ) -> dict:
92
+ """Return a presigned PUT URL for a direct browser-to-S3 upload.
93
+
94
+ Using this URL the browser PUTs the file body directly to S3, bypassing
95
+ Lambda entirely. This removes the 6 MB API Gateway payload limit and
96
+ reduces Lambda invocation cost for large files.
97
+
98
+ Args:
99
+ bucket: S3 bucket name.
100
+ key: S3 object key for the uploaded file.
101
+ content_type: Optional MIME type. When supplied the client **must**
102
+ send the same ``Content-Type`` header in the PUT request, otherwise
103
+ S3 rejects the upload with a 403.
104
+ expires_in: URL lifetime in seconds (default 15 minutes).
105
+
106
+ Returns:
107
+ dict with keys ``url`` (the presigned PUT URL), ``key``, and
108
+ ``bucket``. Pass this dict back to the frontend; the browser calls
109
+ ``fetch(url, { method: 'PUT', body: file })`` to upload.
110
+ """
111
+ params: dict = {"Bucket": bucket, "Key": key}
112
+ if content_type:
113
+ params["ContentType"] = content_type
114
+ s3 = boto3.client("s3")
115
+ url = s3.generate_presigned_url("put_object", Params=params, ExpiresIn=expires_in)
116
+ return {"url": url, "key": key, "bucket": bucket}
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Object listing helpers
121
+ # ---------------------------------------------------------------------------
122
+
123
+ def normalize_client_prefix(client_id: str) -> str:
124
+ """Normalise *client_id* to the S3 key prefix used when storing files.
125
+
126
+ The convention (established by ``OnActionAdminUploadFiles``) is
127
+ ``lowercase + spaces → hyphens``, matching the existing
128
+ ``client_id.lower().replace(" ", "-")`` pattern.
129
+ """
130
+ return re.sub(r"\s+", "-", (client_id or "").strip().lower())
131
+
132
+
133
+ def list_client_files(
134
+ bucket: str,
135
+ client_prefix: str,
136
+ *,
137
+ expires_in: int = 3600,
138
+ ) -> list[dict]:
139
+ """List non-empty objects under *client_prefix* and attach presigned URLs.
140
+
141
+ Zero-byte objects (S3 directory markers created by the console) are
142
+ silently skipped.
143
+
144
+ Each returned dict is the original S3 ``Contents`` entry enriched with:
145
+
146
+ * ``SignedUrl`` – presigned GET URL valid for *expires_in* seconds.
147
+ * ``Header`` – humanized sub-folder name (second-to-last key component),
148
+ used as the category label in :class:`S3DownloadsList`.
149
+ * ``FileName`` – the bare filename (last key component).
150
+
151
+ Args:
152
+ bucket: S3 bucket name.
153
+ client_prefix: Key prefix, typically the normalized client ID.
154
+ expires_in: Presigned URL lifetime in seconds (default 1 hour).
155
+
156
+ Returns:
157
+ List of enriched object dicts, empty if the prefix has no objects.
158
+ """
159
+ s3 = boto3.client("s3")
160
+ result = s3.list_objects_v2(Bucket=bucket, Prefix=client_prefix, MaxKeys=100)
161
+ enriched: list[dict] = []
162
+ for obj in result.get("Contents", []):
163
+ if obj.get("Size", 0) == 0:
164
+ continue # skip directory-marker objects
165
+ key: str = obj["Key"]
166
+ parts = key.split("/")
167
+ obj["SignedUrl"] = generate_presigned_get(bucket, key, expires_in=expires_in)
168
+ obj["Header"] = (
169
+ parts[-2].replace("_", " ").replace("-", " ").title()
170
+ if len(parts) > 1
171
+ else ""
172
+ )
173
+ obj["FileName"] = parts[-1]
174
+ enriched.append(obj)
175
+ return enriched
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.20
3
+ Version: 0.1.22
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
@@ -6,6 +6,7 @@ src/velocity/logging.py
6
6
  src/velocity/aws/__init__.py
7
7
  src/velocity/aws/amplify.py
8
8
  src/velocity/aws/amplify_build.py
9
+ src/velocity/aws/s3.py
9
10
  src/velocity/aws/handlers/__init__.py
10
11
  src/velocity/aws/handlers/base_handler.py
11
12
  src/velocity/aws/handlers/context.py