velocity-python 0.1.24__tar.gz → 0.1.26__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 (182) hide show
  1. {velocity_python-0.1.24/src/velocity_python.egg-info → velocity_python-0.1.26}/PKG-INFO +1 -1
  2. {velocity_python-0.1.24 → velocity_python-0.1.26}/pyproject.toml +1 -1
  3. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/__init__.py +39 -0
  5. velocity_python-0.1.26/src/velocity/aws/assets/__init__.py +57 -0
  6. velocity_python-0.1.26/src/velocity/aws/assets/backfill.py +140 -0
  7. velocity_python-0.1.26/src/velocity/aws/assets/indexing.py +217 -0
  8. velocity_python-0.1.26/src/velocity/aws/assets/references.py +263 -0
  9. velocity_python-0.1.26/src/velocity/aws/assets/service.py +275 -0
  10. velocity_python-0.1.26/src/velocity/aws/assets/usage_index.py +124 -0
  11. velocity_python-0.1.26/src/velocity/aws/dirty_pipeline.py +118 -0
  12. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/context.py +12 -0
  13. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/mixins/data_service.py +26 -0
  14. {velocity_python-0.1.24 → velocity_python-0.1.26/src/velocity_python.egg-info}/PKG-INFO +1 -1
  15. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity_python.egg-info/SOURCES.txt +10 -0
  16. velocity_python-0.1.26/tests/test_asset_references.py +167 -0
  17. velocity_python-0.1.26/tests/test_assets_service.py +314 -0
  18. velocity_python-0.1.26/tests/test_dirty_pipeline_fast_path.py +118 -0
  19. {velocity_python-0.1.24 → velocity_python-0.1.26}/LICENSE +0 -0
  20. {velocity_python-0.1.24 → velocity_python-0.1.26}/README.md +0 -0
  21. {velocity_python-0.1.24 → velocity_python-0.1.26}/setup.cfg +0 -0
  22. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/amplify.py +0 -0
  23. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/amplify_build.py +0 -0
  24. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/__init__.py +0 -0
  25. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/base_handler.py +0 -0
  26. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/context_factory.py +0 -0
  27. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/exceptions.py +0 -0
  28. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  29. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  30. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  31. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/perf.py +0 -0
  32. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/response.py +0 -0
  33. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  34. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/s3.py +0 -0
  35. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/ssm_config.py +0 -0
  36. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/tests/__init__.py +0 -0
  37. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  38. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  39. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/aws/tests/test_response.py +0 -0
  40. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/__init__.py +0 -0
  41. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/__init__.py +0 -0
  42. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/async_support.py +0 -0
  43. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/column.py +0 -0
  44. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/database.py +0 -0
  45. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/decorators.py +0 -0
  46. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/engine.py +0 -0
  47. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/result.py +0 -0
  48. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/row.py +0 -0
  49. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/sequence.py +0 -0
  50. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/table.py +0 -0
  51. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/transaction.py +0 -0
  52. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/core/view.py +0 -0
  53. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/exceptions.py +0 -0
  54. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/migrations.py +0 -0
  55. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/__init__.py +0 -0
  56. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/base/__init__.py +0 -0
  57. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/base/initializer.py +0 -0
  58. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/base/operators.py +0 -0
  59. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/base/sql.py +0 -0
  60. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/base/types.py +0 -0
  61. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/mysql/__init__.py +0 -0
  62. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/mysql/operators.py +0 -0
  63. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/mysql/reserved.py +0 -0
  64. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/mysql/sql.py +0 -0
  65. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/mysql/types.py +0 -0
  66. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/postgres/__init__.py +0 -0
  67. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/postgres/operators.py +0 -0
  68. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/postgres/reserved.py +0 -0
  69. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/postgres/sql.py +0 -0
  70. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/postgres/types.py +0 -0
  71. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  72. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlite/operators.py +0 -0
  73. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  74. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlite/sql.py +0 -0
  75. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlite/types.py +0 -0
  76. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  77. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  78. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  79. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  80. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/sqlserver/types.py +0 -0
  81. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/servers/tablehelper.py +0 -0
  82. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/__init__.py +0 -0
  83. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/common_db_test.py +0 -0
  84. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/__init__.py +0 -0
  85. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/common.py +0 -0
  86. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_column.py +0 -0
  87. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  88. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_database.py +0 -0
  89. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  90. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  91. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  92. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_result.py +0 -0
  93. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_row.py +0 -0
  94. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  95. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  96. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  97. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  98. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  99. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_table.py +0 -0
  100. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  101. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  102. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/sql/__init__.py +0 -0
  103. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/sql/common.py +0 -0
  104. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  105. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  106. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  107. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_db_utils.py +0 -0
  108. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_postgres.py +0 -0
  109. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  110. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  111. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_result_caching.py +0 -0
  112. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  113. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  114. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  115. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  116. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_sql_builder.py +0 -0
  117. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_tablehelper.py +0 -0
  118. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/tests/test_view_helper.py +0 -0
  119. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/db/utils.py +0 -0
  120. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/logging.py +0 -0
  121. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/__init__.py +0 -0
  122. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/conv/__init__.py +0 -0
  123. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/conv/iconv.py +0 -0
  124. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/conv/oconv.py +0 -0
  125. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/db.py +0 -0
  126. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/export.py +0 -0
  127. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/format.py +0 -0
  128. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/mail.py +0 -0
  129. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/merge.py +0 -0
  130. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/pdf.py +0 -0
  131. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/__init__.py +0 -0
  132. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_db.py +0 -0
  133. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_fix.py +0 -0
  134. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_format.py +0 -0
  135. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_iconv.py +0 -0
  136. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_merge.py +0 -0
  137. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_oconv.py +0 -0
  138. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_original_error.py +0 -0
  139. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tests/test_timer.py +0 -0
  140. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/timer.py +0 -0
  141. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/misc/tools.py +0 -0
  142. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/__init__.py +0 -0
  143. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/authorizenet_adapter.py +0 -0
  144. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/base_adapter.py +0 -0
  145. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/braintree_adapter.py +0 -0
  146. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/charge_rules.py +0 -0
  147. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity/payment/stripe_adapter.py +0 -0
  148. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  149. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity_python.egg-info/entry_points.txt +0 -0
  150. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity_python.egg-info/requires.txt +0 -0
  151. {velocity_python-0.1.24 → velocity_python-0.1.26}/src/velocity_python.egg-info/top_level.txt +0 -0
  152. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_amplify_build.py +0 -0
  153. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_async_support.py +0 -0
  154. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_batch_operations.py +0 -0
  155. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_concurrency_safety.py +0 -0
  156. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_connection_pool.py +0 -0
  157. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_connection_resilience.py +0 -0
  158. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_decorators.py +0 -0
  159. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_email_processing.py +0 -0
  160. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_iconv_money_to_cents.py +0 -0
  161. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_lambda_handler.py +0 -0
  162. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_lambda_handler_auth.py +0 -0
  163. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_mixins_import.py +0 -0
  164. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_n_plus_one.py +0 -0
  165. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_observability.py +0 -0
  166. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_payment_braintree_adapter.py +0 -0
  167. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_payment_profile_sorting.py +0 -0
  168. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_payment_stripe_adapter.py +0 -0
  169. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_pdf.py +0 -0
  170. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_prepared_statements.py +0 -0
  171. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_psycopg3_upgrade.py +0 -0
  172. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_query_cache.py +0 -0
  173. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_row_batch_update.py +0 -0
  174. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_row_cache_staleness.py +0 -0
  175. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_row_dirty_tracking.py +0 -0
  176. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_schema_migrations.py +0 -0
  177. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_security_hardening.py +0 -0
  178. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_spreadsheet_functions.py +0 -0
  179. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_sqs_per_record_transactions.py +0 -0
  180. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  181. {velocity_python-0.1.24 → velocity_python-0.1.26}/tests/test_table_alter.py +0 -0
  182. {velocity_python-0.1.24 → velocity_python-0.1.26}/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.24
3
+ Version: 0.1.26
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.24"
7
+ version = "0.1.26"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.24"
1
+ __version__ = version = "0.1.26"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -38,6 +38,26 @@ _LAZY_ATTRS = {
38
38
  "BACKOFFICE_BUCKET": "velocity.aws.s3",
39
39
  "PRIVATE_BUCKETS": "velocity.aws.s3",
40
40
  "ALLOWED_UPLOAD_BUCKETS": "velocity.aws.s3",
41
+ # Asset helpers
42
+ "AssetReference": "velocity.aws.assets",
43
+ "AssetUsage": "velocity.aws.assets",
44
+ "AssetUsageReconciliation": "velocity.aws.assets",
45
+ "AssetBackfillSummary": "velocity.aws.assets",
46
+ "AssetRecord": "velocity.aws.assets",
47
+ "AssetUploadResult": "velocity.aws.assets",
48
+ "backfill_asset_records": "velocity.aws.assets",
49
+ "build_asset_key": "velocity.aws.assets",
50
+ "build_asset_usages": "velocity.aws.assets",
51
+ "build_backfill_asset_data": "velocity.aws.assets",
52
+ "find_existing_asset": "velocity.aws.assets",
53
+ "extract_asset_references_from_html": "velocity.aws.assets",
54
+ "extract_asset_references_from_record": "velocity.aws.assets",
55
+ "extract_asset_references_from_value": "velocity.aws.assets",
56
+ "iter_s3_objects": "velocity.aws.assets",
57
+ "list_assets": "velocity.aws.assets",
58
+ "normalize_asset_record": "velocity.aws.assets",
59
+ "reconcile_asset_usages": "velocity.aws.assets",
60
+ "upload_image_asset": "velocity.aws.assets",
41
61
  }
42
62
 
43
63
 
@@ -67,4 +87,23 @@ __all__ = [
67
87
  "BACKOFFICE_BUCKET",
68
88
  "PRIVATE_BUCKETS",
69
89
  "ALLOWED_UPLOAD_BUCKETS",
90
+ "AssetBackfillSummary",
91
+ "AssetReference",
92
+ "AssetRecord",
93
+ "AssetUploadResult",
94
+ "AssetUsage",
95
+ "AssetUsageReconciliation",
96
+ "backfill_asset_records",
97
+ "build_asset_key",
98
+ "build_asset_usages",
99
+ "build_backfill_asset_data",
100
+ "extract_asset_references_from_html",
101
+ "extract_asset_references_from_record",
102
+ "extract_asset_references_from_value",
103
+ "find_existing_asset",
104
+ "iter_s3_objects",
105
+ "list_assets",
106
+ "normalize_asset_record",
107
+ "reconcile_asset_usages",
108
+ "upload_image_asset",
70
109
  ]
@@ -0,0 +1,57 @@
1
+ from velocity.aws.assets.references import (
2
+ AssetReference,
3
+ extract_asset_references_from_html,
4
+ extract_asset_references_from_record,
5
+ extract_asset_references_from_value,
6
+ )
7
+ from velocity.aws.assets.backfill import (
8
+ AssetBackfillSummary,
9
+ backfill_asset_records,
10
+ build_backfill_asset_data,
11
+ iter_s3_objects,
12
+ )
13
+ from velocity.aws.assets.service import (
14
+ AssetRecord,
15
+ AssetUploadResult,
16
+ build_asset_key,
17
+ find_existing_asset,
18
+ list_assets,
19
+ normalize_asset_record,
20
+ upload_image_asset,
21
+ )
22
+ from velocity.aws.assets.usage_index import (
23
+ AssetUsage,
24
+ AssetUsageReconciliation,
25
+ build_asset_usages,
26
+ reconcile_asset_usages,
27
+ )
28
+ from velocity.aws.assets.indexing import (
29
+ FORM_BUILDER_TEMPLATE_HTML_FIELDS,
30
+ FORM_BUILDER_TEMPLATE_IMAGE_FIELDS,
31
+ reconcile_form_builder_template_asset_usages,
32
+ )
33
+
34
+ __all__ = [
35
+ "AssetReference",
36
+ "AssetBackfillSummary",
37
+ "AssetRecord",
38
+ "AssetUploadResult",
39
+ "AssetUsage",
40
+ "AssetUsageReconciliation",
41
+ "backfill_asset_records",
42
+ "build_asset_key",
43
+ "build_asset_usages",
44
+ "build_backfill_asset_data",
45
+ "extract_asset_references_from_html",
46
+ "extract_asset_references_from_record",
47
+ "extract_asset_references_from_value",
48
+ "find_existing_asset",
49
+ "FORM_BUILDER_TEMPLATE_HTML_FIELDS",
50
+ "FORM_BUILDER_TEMPLATE_IMAGE_FIELDS",
51
+ "iter_s3_objects",
52
+ "list_assets",
53
+ "normalize_asset_record",
54
+ "reconcile_form_builder_template_asset_usages",
55
+ "reconcile_asset_usages",
56
+ "upload_image_asset",
57
+ ]
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import os
5
+ from typing import Any, Callable, Iterable, Mapping
6
+
7
+ from velocity.aws.assets.service import AssetRecord, normalize_asset_record
8
+ from velocity.aws.s3 import PRIVATE_BUCKETS
9
+
10
+
11
+ HeadLoader = Callable[[str, str], Mapping[str, Any] | None]
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class AssetBackfillSummary:
16
+ scanned: int
17
+ inserted: int
18
+ skipped_existing: int
19
+ skipped_unsupported: int
20
+
21
+
22
+ def iter_s3_objects(
23
+ bucket: str,
24
+ *,
25
+ prefix: str = "",
26
+ s3_client=None,
27
+ ) -> Iterable[dict[str, Any]]:
28
+ if s3_client is None:
29
+ import boto3
30
+
31
+ s3_client = boto3.client("s3")
32
+ paginator = s3_client.get_paginator("list_objects_v2")
33
+ for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
34
+ for row in page.get("Contents", []):
35
+ yield row
36
+
37
+
38
+ def build_backfill_asset_data(
39
+ bucket: str,
40
+ object_info: Mapping[str, Any],
41
+ *,
42
+ head_info: Mapping[str, Any] | None = None,
43
+ ) -> dict[str, Any] | None:
44
+ key = str(object_info.get("Key") or "").strip()
45
+ if not key or _is_thumbnail_key(key) or key.endswith("/"):
46
+ return None
47
+ metadata = dict((head_info or {}).get("Metadata") or {})
48
+ filename = os.path.basename(key)
49
+ file_ext = os.path.splitext(filename)[1][1:].lower() or None
50
+ row = {
51
+ "bucket": bucket,
52
+ "key": key,
53
+ "url": f"https://s3.amazonaws.com/{bucket}/{key}",
54
+ "public": bucket not in PRIVATE_BUCKETS,
55
+ "name": filename,
56
+ "type": file_ext,
57
+ "etag": _clean_etag(object_info.get("ETag") or (head_info or {}).get("ETag")),
58
+ "filesize": _coerce_int(object_info.get("Size") or (head_info or {}).get("ContentLength")),
59
+ "hash": metadata.get("hash"),
60
+ "thumbnail_key": metadata.get("thumbnail_key"),
61
+ "client": None if key.startswith("site/uploads") else key.split("/", 1)[0],
62
+ }
63
+ row.update(_copy_if_present(metadata, ["format", "width", "height", "is_animated"]))
64
+ return row
65
+
66
+
67
+ def backfill_asset_records(
68
+ tx,
69
+ *,
70
+ bucket: str,
71
+ objects: Iterable[Mapping[str, Any]],
72
+ head_loader: HeadLoader | None = None,
73
+ ) -> tuple[list[AssetRecord], AssetBackfillSummary]:
74
+ inserted_assets: list[AssetRecord] = []
75
+ scanned = 0
76
+ inserted = 0
77
+ skipped_existing = 0
78
+ skipped_unsupported = 0
79
+
80
+ for object_info in objects:
81
+ scanned += 1
82
+ key = str(object_info.get("Key") or "").strip()
83
+ if not key or _is_thumbnail_key(key) or key.endswith("/"):
84
+ skipped_unsupported += 1
85
+ continue
86
+ if tx.table("images").find({"key": key}):
87
+ skipped_existing += 1
88
+ continue
89
+ head_info = head_loader(bucket, key) if head_loader else None
90
+ row = build_backfill_asset_data(bucket, object_info, head_info=head_info)
91
+ if not row:
92
+ skipped_unsupported += 1
93
+ continue
94
+ tx.table("images").upsert(row, {"key": row["key"]})
95
+ inserted += 1
96
+ stored = tx.table("images").find({"key": row["key"]})
97
+ inserted_assets.append(normalize_asset_record(stored.to_dict() if stored else row))
98
+
99
+ return inserted_assets, AssetBackfillSummary(
100
+ scanned=scanned,
101
+ inserted=inserted,
102
+ skipped_existing=skipped_existing,
103
+ skipped_unsupported=skipped_unsupported,
104
+ )
105
+
106
+
107
+ def _is_thumbnail_key(key: str) -> bool:
108
+ stem, ext = os.path.splitext(key)
109
+ return stem.endswith("-thumb") and ext.lower() == ".png"
110
+
111
+
112
+ def _clean_etag(value: Any) -> str | None:
113
+ if value is None:
114
+ return None
115
+ text = str(value).strip().strip('"')
116
+ return text or None
117
+
118
+
119
+ def _coerce_int(value: Any) -> int | None:
120
+ if value in (None, ""):
121
+ return None
122
+ try:
123
+ return int(value)
124
+ except (TypeError, ValueError):
125
+ return None
126
+
127
+
128
+ def _copy_if_present(source: Mapping[str, Any], keys: list[str]) -> dict[str, Any]:
129
+ result: dict[str, Any] = {}
130
+ for key in keys:
131
+ if key not in source:
132
+ continue
133
+ value = source[key]
134
+ if key in {"width", "height"}:
135
+ result[key] = _coerce_int(value)
136
+ elif key == "is_animated":
137
+ result[key] = str(value).lower() in {"1", "true", "yes", "y"}
138
+ else:
139
+ result[key] = value
140
+ return result
@@ -0,0 +1,217 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping
4
+
5
+ from velocity.aws.assets.references import AssetReference, extract_asset_references_from_record
6
+ from velocity.aws.assets.usage_index import (
7
+ AssetUsage,
8
+ AssetUsageReconciliation,
9
+ build_asset_usages,
10
+ reconcile_asset_usages,
11
+ )
12
+
13
+
14
+ FORM_BUILDER_TEMPLATE_HTML_FIELDS = (
15
+ "body_message_text",
16
+ "form_instructions",
17
+ "confirmation_body_text",
18
+ "receipt_body_text",
19
+ "rally_receipt_body_text",
20
+ "pii_instructions",
21
+ "metric_section_instructions",
22
+ "donation_section_instructions",
23
+ *tuple(f"donation_{index}_html" for index in range(1, 10)),
24
+ )
25
+
26
+ FORM_BUILDER_TEMPLATE_IMAGE_FIELDS = (
27
+ "receipt_header_image",
28
+ "secure_image_url",
29
+ "header_banner_image_url",
30
+ "background_image_url",
31
+ "main_image_url",
32
+ "body_header_url",
33
+ "spot_image",
34
+ "confirmation_header_image",
35
+ "confirmation_body_image",
36
+ *tuple(f"donation_{index}_image" for index in range(1, 10)),
37
+ *tuple(f"metric_{index}_image" for index in range(1, 10)),
38
+ )
39
+
40
+
41
+ def reconcile_form_builder_template_asset_usages(
42
+ tx,
43
+ row: Mapping[str, Any] | Any,
44
+ *,
45
+ html_fields=FORM_BUILDER_TEMPLATE_HTML_FIELDS,
46
+ image_fields=FORM_BUILDER_TEMPLATE_IMAGE_FIELDS,
47
+ last_indexed_by: str = "velocity.aws.dirty_pipeline",
48
+ ) -> AssetUsageReconciliation:
49
+ source_row = _row_to_dict(row)
50
+ source_sys_id = _as_string(source_row.get("sys_id"))
51
+ if not source_sys_id:
52
+ raise ValueError("form_builder_template row must include sys_id")
53
+
54
+ references = extract_asset_references_from_record(
55
+ source_row,
56
+ html_fields=html_fields,
57
+ image_fields=image_fields,
58
+ )
59
+ desired = build_asset_usages(
60
+ references,
61
+ source_table="form_builder_template",
62
+ source_sys_id=source_sys_id,
63
+ source_record_label=_build_source_record_label(source_row),
64
+ last_source_modified=_as_string(source_row.get("sys_modified")),
65
+ last_indexed_by=last_indexed_by,
66
+ asset_id_resolver=lambda reference: resolve_asset_id_for_reference(tx, reference),
67
+ )
68
+ existing = load_asset_usages(
69
+ tx,
70
+ source_table="form_builder_template",
71
+ source_sys_id=source_sys_id,
72
+ )
73
+ reconciliation = reconcile_asset_usages(existing, desired)
74
+ apply_asset_usage_reconciliation(tx, reconciliation)
75
+ return reconciliation
76
+
77
+
78
+ def load_asset_usages(tx, *, source_table: str, source_sys_id: str) -> list[AssetUsage]:
79
+ rows = tx.table("asset_usage_index").select(
80
+ where={
81
+ "source_table": source_table,
82
+ "source_sys_id": source_sys_id,
83
+ }
84
+ ).all()
85
+ return [_row_to_asset_usage(row) for row in rows]
86
+
87
+
88
+ def apply_asset_usage_reconciliation(tx, reconciliation: AssetUsageReconciliation) -> None:
89
+ table = tx.table("asset_usage_index")
90
+ for usage in reconciliation.to_create + reconciliation.to_refresh:
91
+ row = _asset_usage_to_row(usage)
92
+ table.upsert(
93
+ row,
94
+ {
95
+ "asset_id": row["asset_id"],
96
+ "source_table": row["source_table"],
97
+ "source_sys_id": row["source_sys_id"],
98
+ "source_field": row["source_field"],
99
+ },
100
+ )
101
+
102
+ for usage in reconciliation.to_delete:
103
+ table.delete(
104
+ where={
105
+ "asset_id": usage.asset_id,
106
+ "source_table": usage.source_table,
107
+ "source_sys_id": usage.source_sys_id,
108
+ "source_field": usage.source_field,
109
+ }
110
+ )
111
+
112
+
113
+ def resolve_asset_id_for_reference(tx, reference: AssetReference) -> str | None:
114
+ if reference.asset_id:
115
+ return reference.asset_id
116
+
117
+ images = tx.table("images")
118
+ for candidate_key in _candidate_storage_keys(reference.storage_key):
119
+ row = images.find({"key": candidate_key})
120
+ asset_id = _extract_sys_id(row)
121
+ if asset_id:
122
+ return asset_id
123
+
124
+ if reference.url:
125
+ row = images.find({"url": reference.url})
126
+ asset_id = _extract_sys_id(row)
127
+ if asset_id:
128
+ return asset_id
129
+
130
+ return None
131
+
132
+
133
+ def _candidate_storage_keys(storage_key: str | None) -> list[str]:
134
+ if not storage_key:
135
+ return []
136
+
137
+ raw_value = storage_key.strip()
138
+ normalized_value = raw_value.lstrip("/")
139
+ candidates = [
140
+ raw_value,
141
+ normalized_value,
142
+ f"/{normalized_value}",
143
+ f"//{normalized_value}",
144
+ ]
145
+ deduped: list[str] = []
146
+ for candidate in candidates:
147
+ if candidate and candidate not in deduped:
148
+ deduped.append(candidate)
149
+ return deduped
150
+
151
+
152
+ def _row_to_asset_usage(row: Mapping[str, Any] | Any) -> AssetUsage:
153
+ data = _row_to_dict(row)
154
+ return AssetUsage(
155
+ asset_id=_as_string(data.get("asset_id")) or "",
156
+ source_table=_as_string(data.get("source_table")) or "",
157
+ source_sys_id=_as_string(data.get("source_sys_id")) or "",
158
+ source_field=_as_string(data.get("source_field")) or "",
159
+ source_field_type=_as_string(data.get("source_field_type")) or "",
160
+ asset_reference_value=_as_string(data.get("asset_reference_value")),
161
+ asset_storage_key_snapshot=_as_string(data.get("asset_storage_key_snapshot")),
162
+ asset_public_url_snapshot=_as_string(data.get("asset_public_url_snapshot")),
163
+ source_record_label=_as_string(data.get("source_record_label")),
164
+ last_source_modified=_as_string(data.get("last_source_modified")),
165
+ last_indexed_by=_as_string(data.get("last_indexed_by")),
166
+ )
167
+
168
+
169
+ def _asset_usage_to_row(usage: AssetUsage) -> dict[str, Any]:
170
+ return {
171
+ "asset_id": usage.asset_id,
172
+ "source_table": usage.source_table,
173
+ "source_sys_id": usage.source_sys_id,
174
+ "source_field": usage.source_field,
175
+ "source_field_type": usage.source_field_type,
176
+ "asset_reference_value": usage.asset_reference_value,
177
+ "asset_storage_key_snapshot": usage.asset_storage_key_snapshot,
178
+ "asset_public_url_snapshot": usage.asset_public_url_snapshot,
179
+ "source_record_label": usage.source_record_label,
180
+ "last_source_modified": usage.last_source_modified,
181
+ "last_indexed_by": usage.last_indexed_by,
182
+ }
183
+
184
+
185
+ def _build_source_record_label(row: Mapping[str, Any]) -> str | None:
186
+ for key in ("description", "page_title", "campaign"):
187
+ label = _as_string(row.get(key))
188
+ if label:
189
+ return label
190
+
191
+ client = _as_string(row.get("client"))
192
+ campaign = _as_string(row.get("campaign"))
193
+ if client and campaign:
194
+ return f"{client} - {campaign}"
195
+ return client or campaign
196
+
197
+
198
+ def _extract_sys_id(row: Mapping[str, Any] | Any) -> str | None:
199
+ data = _row_to_dict(row)
200
+ return _as_string(data.get("sys_id"))
201
+
202
+
203
+ def _row_to_dict(row: Mapping[str, Any] | Any) -> dict[str, Any]:
204
+ if row is None:
205
+ return {}
206
+ if hasattr(row, "to_dict"):
207
+ return row.to_dict()
208
+ if isinstance(row, Mapping):
209
+ return dict(row)
210
+ raise TypeError(f"Unsupported row type: {type(row)!r}")
211
+
212
+
213
+ def _as_string(value: Any) -> str | None:
214
+ if value is None:
215
+ return None
216
+ text = str(value).strip()
217
+ return text or None