velocity-python 0.1.22__tar.gz → 0.1.24__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 (173) hide show
  1. {velocity_python-0.1.22/src/velocity_python.egg-info → velocity_python-0.1.24}/PKG-INFO +1 -1
  2. {velocity_python-0.1.22 → velocity_python-0.1.24}/pyproject.toml +1 -1
  3. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/amplify_build.py +62 -31
  5. velocity_python-0.1.24/src/velocity/aws/ssm_config.py +94 -0
  6. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/async_support.py +10 -2
  7. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/transaction.py +16 -2
  8. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/postgres/__init__.py +14 -13
  9. {velocity_python-0.1.22 → velocity_python-0.1.24/src/velocity_python.egg-info}/PKG-INFO +1 -1
  10. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  11. velocity_python-0.1.24/tests/test_amplify_build.py +150 -0
  12. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_async_support.py +5 -0
  13. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_observability.py +3 -0
  14. velocity_python-0.1.22/tests/test_amplify_build.py +0 -70
  15. {velocity_python-0.1.22 → velocity_python-0.1.24}/LICENSE +0 -0
  16. {velocity_python-0.1.22 → velocity_python-0.1.24}/README.md +0 -0
  17. {velocity_python-0.1.22 → velocity_python-0.1.24}/setup.cfg +0 -0
  18. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/__init__.py +0 -0
  19. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/amplify.py +0 -0
  20. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/__init__.py +0 -0
  21. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/base_handler.py +0 -0
  22. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/context.py +0 -0
  23. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/context_factory.py +0 -0
  24. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/exceptions.py +0 -0
  25. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  26. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  27. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  28. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  29. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/perf.py +0 -0
  30. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/response.py +0 -0
  31. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  32. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/s3.py +0 -0
  33. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/tests/__init__.py +0 -0
  34. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  35. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  36. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/aws/tests/test_response.py +0 -0
  37. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/__init__.py +0 -0
  38. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/__init__.py +0 -0
  39. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/column.py +0 -0
  40. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/database.py +0 -0
  41. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/decorators.py +0 -0
  42. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/engine.py +0 -0
  43. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/result.py +0 -0
  44. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/row.py +0 -0
  45. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/sequence.py +0 -0
  46. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/table.py +0 -0
  47. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/core/view.py +0 -0
  48. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/exceptions.py +0 -0
  49. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/migrations.py +0 -0
  50. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/__init__.py +0 -0
  51. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/base/__init__.py +0 -0
  52. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/base/initializer.py +0 -0
  53. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/base/operators.py +0 -0
  54. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/base/sql.py +0 -0
  55. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/base/types.py +0 -0
  56. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/mysql/__init__.py +0 -0
  57. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/mysql/operators.py +0 -0
  58. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/mysql/reserved.py +0 -0
  59. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/mysql/sql.py +0 -0
  60. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/mysql/types.py +0 -0
  61. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/postgres/operators.py +0 -0
  62. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/postgres/reserved.py +0 -0
  63. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/postgres/sql.py +0 -0
  64. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/postgres/types.py +0 -0
  65. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  66. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlite/operators.py +0 -0
  67. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  68. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlite/sql.py +0 -0
  69. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlite/types.py +0 -0
  70. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  71. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  72. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  73. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  74. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/sqlserver/types.py +0 -0
  75. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/servers/tablehelper.py +0 -0
  76. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/__init__.py +0 -0
  77. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/common_db_test.py +0 -0
  78. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/__init__.py +0 -0
  79. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/common.py +0 -0
  80. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_column.py +0 -0
  81. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  82. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_database.py +0 -0
  83. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  84. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  85. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  86. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_result.py +0 -0
  87. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_row.py +0 -0
  88. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  89. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  90. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  91. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  92. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  93. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_table.py +0 -0
  94. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  95. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  96. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/sql/__init__.py +0 -0
  97. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/sql/common.py +0 -0
  98. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  99. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  100. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  101. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_db_utils.py +0 -0
  102. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_postgres.py +0 -0
  103. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  104. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  105. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_result_caching.py +0 -0
  106. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  107. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  108. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  109. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  110. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_sql_builder.py +0 -0
  111. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_tablehelper.py +0 -0
  112. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/tests/test_view_helper.py +0 -0
  113. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/db/utils.py +0 -0
  114. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/logging.py +0 -0
  115. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/__init__.py +0 -0
  116. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/conv/__init__.py +0 -0
  117. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/conv/iconv.py +0 -0
  118. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/conv/oconv.py +0 -0
  119. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/db.py +0 -0
  120. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/export.py +0 -0
  121. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/format.py +0 -0
  122. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/mail.py +0 -0
  123. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/merge.py +0 -0
  124. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/pdf.py +0 -0
  125. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/__init__.py +0 -0
  126. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_db.py +0 -0
  127. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_fix.py +0 -0
  128. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_format.py +0 -0
  129. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_iconv.py +0 -0
  130. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_merge.py +0 -0
  131. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_oconv.py +0 -0
  132. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_original_error.py +0 -0
  133. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tests/test_timer.py +0 -0
  134. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/timer.py +0 -0
  135. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/misc/tools.py +0 -0
  136. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/__init__.py +0 -0
  137. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/authorizenet_adapter.py +0 -0
  138. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/base_adapter.py +0 -0
  139. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/braintree_adapter.py +0 -0
  140. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/charge_rules.py +0 -0
  141. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity/payment/stripe_adapter.py +0 -0
  142. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  143. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity_python.egg-info/entry_points.txt +0 -0
  144. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity_python.egg-info/requires.txt +0 -0
  145. {velocity_python-0.1.22 → velocity_python-0.1.24}/src/velocity_python.egg-info/top_level.txt +0 -0
  146. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_batch_operations.py +0 -0
  147. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_concurrency_safety.py +0 -0
  148. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_connection_pool.py +0 -0
  149. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_connection_resilience.py +0 -0
  150. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_decorators.py +0 -0
  151. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_email_processing.py +0 -0
  152. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_iconv_money_to_cents.py +0 -0
  153. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_lambda_handler.py +0 -0
  154. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_lambda_handler_auth.py +0 -0
  155. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_mixins_import.py +0 -0
  156. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_n_plus_one.py +0 -0
  157. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_payment_braintree_adapter.py +0 -0
  158. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_payment_profile_sorting.py +0 -0
  159. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_payment_stripe_adapter.py +0 -0
  160. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_pdf.py +0 -0
  161. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_prepared_statements.py +0 -0
  162. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_psycopg3_upgrade.py +0 -0
  163. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_query_cache.py +0 -0
  164. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_row_batch_update.py +0 -0
  165. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_row_cache_staleness.py +0 -0
  166. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_row_dirty_tracking.py +0 -0
  167. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_schema_migrations.py +0 -0
  168. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_security_hardening.py +0 -0
  169. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_spreadsheet_functions.py +0 -0
  170. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_sqs_per_record_transactions.py +0 -0
  171. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  172. {velocity_python-0.1.22 → velocity_python-0.1.24}/tests/test_table_alter.py +0 -0
  173. {velocity_python-0.1.22 → velocity_python-0.1.24}/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.22
3
+ Version: 0.1.24
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.22"
7
+ version = "0.1.24"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.22"
1
+ __version__ = version = "0.1.24"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -235,6 +235,36 @@ def retryable_call(
235
235
  raise
236
236
 
237
237
 
238
+ def resolve_latest_layer_arns(
239
+ lambda_client,
240
+ current_layers: Sequence[Mapping[str, Any]],
241
+ ) -> Sequence[str]:
242
+ updated_layer_arns = []
243
+
244
+ for layer in current_layers or ():
245
+ layer_arn = layer["Arn"]
246
+ layer_name = layer_arn.split(":")[-2]
247
+ current_version = layer_arn.split(":")[-1]
248
+
249
+ latest_response = retryable_call(
250
+ lambda_client.list_layer_versions,
251
+ LayerName=layer_name,
252
+ MaxItems=1,
253
+ )
254
+
255
+ if latest_response and latest_response.get("LayerVersions"):
256
+ latest_version_info = latest_response["LayerVersions"][0]
257
+ latest_version = latest_version_info["Version"]
258
+ latest_arn = latest_version_info["LayerVersionArn"]
259
+ updated_layer_arns.append(
260
+ latest_arn if str(latest_version) != str(current_version) else layer_arn
261
+ )
262
+ else:
263
+ updated_layer_arns.append(layer_arn)
264
+
265
+ return updated_layer_arns
266
+
267
+
238
268
  def _serialize_policy_document(policy_document: Any) -> str:
239
269
  if isinstance(policy_document, str):
240
270
  return policy_document
@@ -443,6 +473,16 @@ def run_backend_deployment(
443
473
 
444
474
  timeout_value = config.resolve_timeout(function_name)
445
475
  memory_value = config.resolve_memory_size(function_name)
476
+ layers_value = None
477
+ current_layers = function.get("Layers") or []
478
+ if current_layers:
479
+ try:
480
+ layers_value = list(resolve_latest_layer_arns(lambda_client, current_layers))
481
+ except Exception as exc:
482
+ print(
483
+ f"[WARN] Unable to resolve latest layers for {function_name}: {exc}. "
484
+ "Keeping current layer attachments."
485
+ )
446
486
  print(
447
487
  f"[INFO] Updating Lambda function {function_name} with timeout={timeout_value}"
448
488
  + (
@@ -456,6 +496,7 @@ def run_backend_deployment(
456
496
  memory_size=memory_value,
457
497
  subnet_ids=subnet_ids,
458
498
  security_group_ids=security_group_ids,
499
+ layers=layers_value,
459
500
  )
460
501
 
461
502
  if not set_cloudwatch_logging(
@@ -563,44 +604,34 @@ def update_lambda_layers_to_latest(
563
604
  layer_arn = layer["Arn"]
564
605
  layer_name = layer_arn.split(":")[-2]
565
606
  current_version = layer_arn.split(":")[-1]
566
-
567
607
  print(
568
608
  f"[INFO] Processing layer: {layer_name} (current version: {current_version})"
569
609
  )
570
610
 
571
- try:
572
- latest_response = retryable_call(
573
- lambda_client.list_layer_versions,
574
- LayerName=layer_name,
575
- MaxItems=1,
611
+ try:
612
+ updated_layer_arns = list(
613
+ resolve_latest_layer_arns(lambda_client, current_layers)
614
+ )
615
+ except Exception as exc:
616
+ print(f"[ERROR] Failed to resolve latest layers: {exc}")
617
+ print("[INFO] Keeping current layer versions")
618
+ updated_layer_arns = [layer["Arn"] for layer in current_layers]
619
+
620
+ for original_arn, updated_arn in zip(
621
+ [layer["Arn"] for layer in current_layers],
622
+ updated_layer_arns,
623
+ ):
624
+ layer_name = original_arn.split(":")[-2]
625
+ current_version = original_arn.split(":")[-1]
626
+ latest_version = updated_arn.split(":")[-1]
627
+ if updated_arn != original_arn:
628
+ print(
629
+ f"[INFO] Updating {layer_name}: v{current_version} -> v{latest_version}"
576
630
  )
577
-
578
- if latest_response and latest_response.get("LayerVersions"):
579
- latest_version_info = latest_response["LayerVersions"][0]
580
- latest_version = latest_version_info["Version"]
581
- latest_arn = latest_version_info["LayerVersionArn"]
582
-
583
- if str(latest_version) != str(current_version):
584
- print(
585
- f"[INFO] Updating {layer_name}: v{current_version} -> v{latest_version}"
586
- )
587
- updated_layer_arns.append(latest_arn)
588
- else:
589
- print(
590
- f"[INFO] {layer_name} is already at latest version (v{latest_version})"
591
- )
592
- updated_layer_arns.append(layer_arn)
593
- else:
594
- print(
595
- f"[WARN] No versions found for layer {layer_name}, keeping current version"
596
- )
597
- updated_layer_arns.append(layer_arn)
598
- except Exception as exc:
631
+ else:
599
632
  print(
600
- f"[ERROR] Failed to get latest version for layer {layer_name}: {exc}"
633
+ f"[INFO] {layer_name} is already at latest version (v{latest_version})"
601
634
  )
602
- print(f"[INFO] Keeping current version for {layer_name}")
603
- updated_layer_arns.append(layer_arn)
604
635
 
605
636
  current_layer_arns = [layer["Arn"] for layer in current_layers]
606
637
  if updated_layer_arns != current_layer_arns:
@@ -0,0 +1,94 @@
1
+ """
2
+ SSM-backed configuration with environment-variable fallback.
3
+
4
+ Opt-in: set ``VELOCITY_SSM_ENABLED=true`` in your Amplify environment variables
5
+ (pushed via ``bootstrap.py secrets``).
6
+
7
+ When enabled, ``getenv(key)`` reads from SSM at::
8
+
9
+ /{ProjectName}/{ENV}/{key}
10
+
11
+ and falls back to ``os.environ`` on a miss or any error. Results are cached
12
+ for the process lifetime so SSM is called at most once per key per Lambda
13
+ cold start.
14
+
15
+ When **not** enabled (the default), ``getenv`` is a thin wrapper around
16
+ ``os.environ.get`` with zero extra overhead — preserving full backward
17
+ compatibility for projects that don't use SSM (e.g. caringcent).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ import os
24
+ from typing import Optional
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Per-process cache: key → value (str) or sentinel _MISS.
29
+ _MISS = object()
30
+ _cache: dict[str, object] = {}
31
+
32
+ # Lazily evaluated once per process.
33
+ _SSM_ENABLED: bool | None = None
34
+
35
+
36
+ def _is_enabled() -> bool:
37
+ global _SSM_ENABLED
38
+ if _SSM_ENABLED is None:
39
+ _SSM_ENABLED = os.environ.get('VELOCITY_SSM_ENABLED', '').lower() in ('1', 'true', 'yes')
40
+ return _SSM_ENABLED
41
+
42
+
43
+ def _ssm_prefix() -> str | None:
44
+ """Return '/{ProjectName}/{ENV}' or None if either var is missing."""
45
+ project = os.environ.get('ProjectName')
46
+ env = os.environ.get('ENV')
47
+ if project and env:
48
+ return f'/{project}/{env}'
49
+ return None
50
+
51
+
52
+ def _fetch(key: str) -> str | None:
53
+ """Single SSM GetParameter call; returns value or None on any failure."""
54
+ prefix = _ssm_prefix()
55
+ if prefix is None:
56
+ return None
57
+ param_name = f'{prefix}/{key}'
58
+ try:
59
+ import boto3
60
+ ssm = boto3.client('ssm', region_name=os.environ.get('REGION', 'us-east-1'))
61
+ resp = ssm.get_parameter(Name=param_name, WithDecryption=True)
62
+ return resp['Parameter']['Value']
63
+ except Exception as exc:
64
+ logger.debug('SSM miss for %s: %s', param_name, exc)
65
+ return None
66
+
67
+
68
+ def getenv(key: str, default: Optional[str] = None) -> Optional[str]:
69
+ """
70
+ Read a config value, checking SSM before ``os.environ``.
71
+
72
+ SSM is only consulted when ``VELOCITY_SSM_ENABLED=true`` is set in the
73
+ Lambda environment *and* both ``ProjectName`` and ``ENV`` are present.
74
+ All other cases fall straight through to ``os.environ``.
75
+
76
+ Results are cached per process so SSM is queried at most once per key
77
+ per Lambda cold start.
78
+ """
79
+ if _is_enabled():
80
+ if key not in _cache:
81
+ value = _fetch(key)
82
+ _cache[key] = value if value is not None else _MISS
83
+ cached = _cache[key]
84
+ if cached is not _MISS:
85
+ return cached # type: ignore[return-value]
86
+
87
+ return os.environ.get(key, default)
88
+
89
+
90
+ def clear_cache() -> None:
91
+ """Clear the SSM cache (useful in tests)."""
92
+ _cache.clear()
93
+ global _SSM_ENABLED
94
+ _SSM_ENABLED = None
@@ -493,10 +493,18 @@ class AsyncTransaction:
493
493
 
494
494
  # R12 — Slow query logging
495
495
  if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
496
+ from velocity.db.core.transaction import _summarize_sql
497
+
498
+ sql_preview = _summarize_sql(sql)
496
499
  _logger.warning(
497
500
  "Slow async query: %.1f ms sql=%s",
498
- elapsed_ms, sql[:200],
499
- extra={"query_duration_ms": round(elapsed_ms, 1)},
501
+ elapsed_ms,
502
+ sql_preview,
503
+ extra={
504
+ "query_duration_ms": round(elapsed_ms, 1),
505
+ "sql_preview": sql_preview,
506
+ },
507
+ stack_info=True,
500
508
  )
501
509
 
502
510
  # R14 — N+1 detection
@@ -23,6 +23,7 @@ _DEFAULT_QUERY_CACHE_SIZE = int(os.environ.get("VELOCITY_QUERY_CACHE_SIZE", "100
23
23
 
24
24
  # Slow-query threshold in milliseconds (0 = disabled).
25
25
  _SLOW_QUERY_MS = int(os.environ.get("VELOCITY_SLOW_QUERY_MS", "500"))
26
+ _SLOW_QUERY_SQL_CHARS = int(os.environ.get("VELOCITY_SLOW_QUERY_SQL_CHARS", "4000"))
26
27
 
27
28
  # N+1 detection: warn when the same table is SELECTed more than this many
28
29
  # times within a single transaction. 0 = disabled. Only active when the
@@ -76,6 +77,16 @@ def _extract_table_name(sql):
76
77
  return None
77
78
 
78
79
 
80
+ def _summarize_sql(sql, limit=None):
81
+ if not sql:
82
+ return None
83
+ compact = " ".join(str(sql).split())
84
+ max_chars = _SLOW_QUERY_SQL_CHARS if limit is None else limit
85
+ if max_chars and len(compact) > max_chars:
86
+ return compact[:max_chars] + "... [truncated]"
87
+ return compact
88
+
89
+
79
90
  class Transaction:
80
91
  """
81
92
  Encapsulates a single transaction in the database (connection + commit/rollback).
@@ -210,14 +221,17 @@ class Transaction:
210
221
  if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
211
222
  op = _classify_sql(sql)
212
223
  tbl = _extract_table_name(sql)
224
+ sql_preview = _summarize_sql(sql)
213
225
  _logger.warning(
214
- "Slow query (%s): %.1f ms table=%s",
215
- op, elapsed_ms, tbl,
226
+ "Slow query (%s): %.1f ms table=%s sql=%s",
227
+ op, elapsed_ms, tbl, sql_preview,
216
228
  extra={
217
229
  "query_duration_ms": round(elapsed_ms, 1),
218
230
  "table_name": tbl,
219
231
  "operation": op,
232
+ "sql_preview": sql_preview,
220
233
  },
234
+ stack_info=True,
221
235
  )
222
236
 
223
237
  # R14 — N+1 detection (only when debug=True).
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from ..base.initializer import BaseInitializer
3
3
  from velocity.db.core import engine
4
+ from velocity.aws.ssm_config import getenv as _getenv
4
5
 
5
6
 
6
7
  # Default TCP keepalive settings for PostgreSQL connections.
@@ -41,14 +42,14 @@ class PostgreSQLInitializer(BaseInitializer):
41
42
 
42
43
  from .sql import SQL
43
44
 
44
- # Base configuration from environment
45
+ # Base configuration from environment / SSM
45
46
  # psycopg v3 uses libpq param names; 'dbname' not 'database'.
46
47
  base_config = {
47
- "dbname": os.environ.get("DBDatabase"),
48
- "host": os.environ.get("DBHost"),
49
- "port": os.environ.get("DBPort"),
50
- "user": os.environ.get("DBUser"),
51
- "password": os.environ.get("DBPassword"),
48
+ "dbname": _getenv("DBDatabase"),
49
+ "host": _getenv("DBHost"),
50
+ "port": _getenv("DBPort"),
51
+ "user": _getenv("DBUser"),
52
+ "password": _getenv("DBPassword"),
52
53
  }
53
54
 
54
55
  # Remove None values
@@ -65,7 +66,7 @@ class PostgreSQLInitializer(BaseInitializer):
65
66
 
66
67
  # SSL mode — default to "prefer" so connections upgrade when the
67
68
  # server supports TLS but don't fail when it doesn't.
68
- ssl_mode = os.environ.get("VELOCITY_SSL_MODE")
69
+ ssl_mode = _getenv("VELOCITY_SSL_MODE")
69
70
  if ssl_mode:
70
71
  final_config.setdefault("sslmode", ssl_mode)
71
72
 
@@ -108,11 +109,11 @@ def initialize(config=None, schema_locked=False, **kwargs):
108
109
  from .sql import SQL
109
110
 
110
111
  konfig = {
111
- "dbname": os.environ["DBDatabase"],
112
- "host": os.environ["DBHost"],
113
- "port": os.environ["DBPort"],
114
- "user": os.environ["DBUser"],
115
- "password": os.environ["DBPassword"],
112
+ "dbname": _getenv("DBDatabase") or os.environ["DBDatabase"],
113
+ "host": _getenv("DBHost") or os.environ["DBHost"],
114
+ "port": _getenv("DBPort") or os.environ["DBPort"],
115
+ "user": _getenv("DBUser") or os.environ["DBUser"],
116
+ "password": _getenv("DBPassword") or os.environ["DBPassword"],
116
117
  }
117
118
  konfig.update(config or {})
118
119
  konfig.update(kwargs)
@@ -122,7 +123,7 @@ def initialize(config=None, schema_locked=False, **kwargs):
122
123
  konfig.setdefault(key, default_val)
123
124
 
124
125
  # SSL mode.
125
- ssl_mode = os.environ.get("VELOCITY_SSL_MODE")
126
+ ssl_mode = _getenv("VELOCITY_SSL_MODE")
126
127
  if ssl_mode:
127
128
  konfig.setdefault("sslmode", ssl_mode)
128
129
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.22
3
+ Version: 0.1.24
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
@@ -7,6 +7,7 @@ src/velocity/aws/__init__.py
7
7
  src/velocity/aws/amplify.py
8
8
  src/velocity/aws/amplify_build.py
9
9
  src/velocity/aws/s3.py
10
+ src/velocity/aws/ssm_config.py
10
11
  src/velocity/aws/handlers/__init__.py
11
12
  src/velocity/aws/handlers/base_handler.py
12
13
  src/velocity/aws/handlers/context.py
@@ -0,0 +1,150 @@
1
+ from velocity.aws.amplify_build import (
2
+ BackendDeploymentConfig,
3
+ build_environment_variables,
4
+ get_amplify_app_id_and_branch,
5
+ run_backend_deployment,
6
+ )
7
+
8
+
9
+ class FakeAmplifyProject:
10
+ def get_merged_env_vars(self, branch):
11
+ return {"EXISTING": branch}
12
+
13
+ def get_region(self):
14
+ return "us-east-1"
15
+
16
+
17
+ class FakeDeployProject(FakeAmplifyProject):
18
+ def __init__(self):
19
+ self.updated_functions = []
20
+
21
+ def list_lambda_functions_filtered(self, branch):
22
+ return [
23
+ {
24
+ "FunctionName": f"ClientEvents-{branch}",
25
+ "Layers": [
26
+ {
27
+ "Arn": "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-support:225"
28
+ },
29
+ {
30
+ "Arn": "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-accounting:34"
31
+ },
32
+ ],
33
+ }
34
+ ]
35
+
36
+ def update_lambda_function(self, **kwargs):
37
+ self.updated_functions.append(kwargs)
38
+
39
+
40
+ class FakeLambdaClient:
41
+ def list_layer_versions(self, LayerName, MaxItems):
42
+ versions = {
43
+ "py-lib-support": [
44
+ {
45
+ "Version": 240,
46
+ "LayerVersionArn": "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-support:240",
47
+ }
48
+ ],
49
+ "py-lib-accounting": [
50
+ {
51
+ "Version": 34,
52
+ "LayerVersionArn": "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-accounting:34",
53
+ }
54
+ ],
55
+ }
56
+ return {"LayerVersions": versions[LayerName][:MaxItems]}
57
+
58
+
59
+ def test_backend_deployment_config_resolves_queue_timeout_and_memory_size():
60
+ config = BackendDeploymentConfig(
61
+ queue_name_template="clients-queue-{branch}",
62
+ short_timeout_tokens=("Events", "Data"),
63
+ memory_size_by_token={"QueueHandler": 1024, "Events": 512},
64
+ )
65
+
66
+ assert config.resolve_queue_name("Demo") == "clients-queue-demo"
67
+ assert config.resolve_timeout("ClientEvents-demo") == 60
68
+ assert config.resolve_timeout("ClientUtility-demo") == 900
69
+ assert config.resolve_memory_size("ClientQueueHandler-demo") == 1024
70
+ assert config.resolve_memory_size("ClientEvents-demo") == 512
71
+ assert config.resolve_memory_size("ClientUtility-demo") is None
72
+
73
+
74
+ def test_build_environment_variables_reads_project_metadata(tmp_path, monkeypatch):
75
+ amplify_dir = tmp_path / "amplify" / "backend"
76
+ amplify_dir.mkdir(parents=True)
77
+ (amplify_dir / "amplify-meta.json").write_text('{"UserPoolId": "us-east-1_demoPool",}')
78
+
79
+ monkeypatch.setenv("AWS_JOB_ID", "job-123")
80
+ app = FakeAmplifyProject()
81
+
82
+ env_vars = build_environment_variables(
83
+ app,
84
+ "demo",
85
+ "clients-queue-demo",
86
+ project_root=tmp_path,
87
+ )
88
+
89
+ assert env_vars["EXISTING"] == "demo"
90
+ assert env_vars["SqsWorkQueue"] == "clients-queue-demo"
91
+ assert env_vars["AWS_JOB_ID"] == "job-123"
92
+ assert env_vars["AWS_USER_POOL_ID"] == "us-east-1_demoPool"
93
+ assert env_vars["USER_BRANCH"] == "demo"
94
+ assert env_vars["REACT_APP_USER_BRANCH"] == "demo"
95
+ assert env_vars["USER_REGION"] == "us-east-1"
96
+ assert env_vars["LOGLEVEL"] == "INFO"
97
+ assert "BUILD_DATETIME" in env_vars
98
+
99
+
100
+ def test_get_amplify_app_id_and_branch_uses_team_provider_info(tmp_path, monkeypatch):
101
+ team_provider_path = tmp_path / "amplify"
102
+ team_provider_path.mkdir(parents=True)
103
+ (team_provider_path / "team-provider-info.json").write_text(
104
+ '{"demo": {"awscloudformation": {"AmplifyAppId": "app-123"}}}'
105
+ )
106
+
107
+ monkeypatch.setenv("AWS_BRANCH", "demo")
108
+ monkeypatch.delenv("AWS_APP_ID", raising=False)
109
+
110
+ app_id, branch = get_amplify_app_id_and_branch(tmp_path)
111
+
112
+ assert app_id == "app-123"
113
+ assert branch == "demo"
114
+
115
+
116
+ def test_run_backend_deployment_refreshes_lambda_layers(monkeypatch, tmp_path):
117
+ monkeypatch.setattr(
118
+ "velocity.aws.amplify_build.ensure_lambda_policies_and_attach",
119
+ lambda *args, **kwargs: None,
120
+ )
121
+ monkeypatch.setattr(
122
+ "velocity.aws.amplify_build.set_cloudwatch_logging",
123
+ lambda *args, **kwargs: True,
124
+ )
125
+
126
+ app = FakeDeployProject()
127
+ config = BackendDeploymentConfig(
128
+ queue_name_template="clients-queue-{branch}",
129
+ short_timeout_tokens=("Events",),
130
+ memory_size_by_token={"Events": 512},
131
+ )
132
+
133
+ run_backend_deployment(
134
+ "app-123",
135
+ "demo",
136
+ config,
137
+ project_root=tmp_path,
138
+ app=app,
139
+ lambda_client=FakeLambdaClient(),
140
+ logs_client=object(),
141
+ )
142
+
143
+ assert len(app.updated_functions) == 1
144
+ assert app.updated_functions[0]["function_name"] == "ClientEvents-demo"
145
+ assert app.updated_functions[0]["timeout"] == 60
146
+ assert app.updated_functions[0]["memory_size"] == 512
147
+ assert app.updated_functions[0]["layers"] == [
148
+ "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-support:240",
149
+ "arn:aws:lambda:us-east-1:741671896925:layer:py-lib-accounting:34",
150
+ ]
@@ -687,3 +687,8 @@ class TestAsyncSlowQueryLogging:
687
687
  if "Slow" in str(c)
688
688
  ]
689
689
  assert len(slow_calls) >= 1
690
+ args = slow_calls[0].args
691
+ kwargs = slow_calls[0].kwargs
692
+ assert args[2] == "SELECT * FROM large_table"
693
+ assert kwargs["extra"]["sql_preview"] == "SELECT * FROM large_table"
694
+ assert kwargs["stack_info"] is True
@@ -177,6 +177,7 @@ class TestSlowQueryLogging:
177
177
 
178
178
  assert any("Slow query" in r.message for r in caplog.records)
179
179
  assert any("big_table" in r.message for r in caplog.records)
180
+ assert any("sql=SELECT * FROM big_table" in r.message for r in caplog.records)
180
181
 
181
182
  def test_fast_query_no_warning(self, caplog):
182
183
  tx = _make_tx()
@@ -230,6 +231,8 @@ class TestSlowQueryLogging:
230
231
  assert rec.query_duration_ms > 0
231
232
  assert rec.table_name == "orders"
232
233
  assert rec.operation == "SELECT"
234
+ assert rec.sql_preview == "SELECT * FROM orders WHERE id = 1"
235
+ assert rec.stack_info
233
236
 
234
237
 
235
238
  # ──────────────────────────────────────────────────────────────────────
@@ -1,70 +0,0 @@
1
- from velocity.aws.amplify_build import (
2
- BackendDeploymentConfig,
3
- build_environment_variables,
4
- get_amplify_app_id_and_branch,
5
- )
6
-
7
-
8
- class FakeAmplifyProject:
9
- def get_merged_env_vars(self, branch):
10
- return {"EXISTING": branch}
11
-
12
- def get_region(self):
13
- return "us-east-1"
14
-
15
-
16
- def test_backend_deployment_config_resolves_queue_timeout_and_memory_size():
17
- config = BackendDeploymentConfig(
18
- queue_name_template="clients-queue-{branch}",
19
- short_timeout_tokens=("Events", "Data"),
20
- memory_size_by_token={"QueueHandler": 1024, "Events": 512},
21
- )
22
-
23
- assert config.resolve_queue_name("Demo") == "clients-queue-demo"
24
- assert config.resolve_timeout("ClientEvents-demo") == 60
25
- assert config.resolve_timeout("ClientUtility-demo") == 900
26
- assert config.resolve_memory_size("ClientQueueHandler-demo") == 1024
27
- assert config.resolve_memory_size("ClientEvents-demo") == 512
28
- assert config.resolve_memory_size("ClientUtility-demo") is None
29
-
30
-
31
- def test_build_environment_variables_reads_project_metadata(tmp_path, monkeypatch):
32
- amplify_dir = tmp_path / "amplify" / "backend"
33
- amplify_dir.mkdir(parents=True)
34
- (amplify_dir / "amplify-meta.json").write_text('{"UserPoolId": "us-east-1_demoPool",}')
35
-
36
- monkeypatch.setenv("AWS_JOB_ID", "job-123")
37
- app = FakeAmplifyProject()
38
-
39
- env_vars = build_environment_variables(
40
- app,
41
- "demo",
42
- "clients-queue-demo",
43
- project_root=tmp_path,
44
- )
45
-
46
- assert env_vars["EXISTING"] == "demo"
47
- assert env_vars["SqsWorkQueue"] == "clients-queue-demo"
48
- assert env_vars["AWS_JOB_ID"] == "job-123"
49
- assert env_vars["AWS_USER_POOL_ID"] == "us-east-1_demoPool"
50
- assert env_vars["USER_BRANCH"] == "demo"
51
- assert env_vars["REACT_APP_USER_BRANCH"] == "demo"
52
- assert env_vars["USER_REGION"] == "us-east-1"
53
- assert env_vars["LOGLEVEL"] == "INFO"
54
- assert "BUILD_DATETIME" in env_vars
55
-
56
-
57
- def test_get_amplify_app_id_and_branch_uses_team_provider_info(tmp_path, monkeypatch):
58
- team_provider_path = tmp_path / "amplify"
59
- team_provider_path.mkdir(parents=True)
60
- (team_provider_path / "team-provider-info.json").write_text(
61
- '{"demo": {"awscloudformation": {"AmplifyAppId": "app-123"}}}'
62
- )
63
-
64
- monkeypatch.setenv("AWS_BRANCH", "demo")
65
- monkeypatch.delenv("AWS_APP_ID", raising=False)
66
-
67
- app_id, branch = get_amplify_app_id_and_branch(tmp_path)
68
-
69
- assert app_id == "app-123"
70
- assert branch == "demo"