velocity-python 0.1.12__tar.gz → 0.1.13__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 (188) hide show
  1. {velocity_python-0.1.12/src/velocity_python.egg-info → velocity_python-0.1.13}/PKG-INFO +4 -2
  2. {velocity_python-0.1.12 → velocity_python-0.1.13}/pyproject.toml +5 -2
  3. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.1.13/src/velocity/misc/pdf.py +199 -0
  5. {velocity_python-0.1.12 → velocity_python-0.1.13/src/velocity_python.egg-info}/PKG-INFO +4 -2
  6. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  7. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity_python.egg-info/requires.txt +4 -1
  8. velocity_python-0.1.13/tests/test_pdf.py +188 -0
  9. {velocity_python-0.1.12 → velocity_python-0.1.13}/LICENSE +0 -0
  10. {velocity_python-0.1.12 → velocity_python-0.1.13}/README.md +0 -0
  11. {velocity_python-0.1.12 → velocity_python-0.1.13}/setup.cfg +0 -0
  12. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/__init__.py +0 -0
  13. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/formbuilder/__init__.py +0 -0
  14. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/formbuilder/reshaper.py +0 -0
  15. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/invoices.py +0 -0
  16. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/orders.py +0 -0
  17. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/payments.py +0 -0
  18. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/purchase_orders.py +0 -0
  19. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/tests/__init__.py +0 -0
  20. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/tests/test_email_processing.py +0 -0
  21. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
  22. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
  23. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/validators/__init__.py +0 -0
  24. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/app/validators/formbuilder_template.py +0 -0
  25. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/__init__.py +0 -0
  26. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/amplify.py +0 -0
  27. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/amplify_build.py +0 -0
  28. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/__init__.py +0 -0
  29. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/base_handler.py +0 -0
  30. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/context.py +0 -0
  31. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/context_factory.py +0 -0
  32. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/exceptions.py +0 -0
  33. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  34. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  35. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  36. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  37. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/perf.py +0 -0
  38. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/response.py +0 -0
  39. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  40. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/tests/__init__.py +0 -0
  41. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  42. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  43. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/aws/tests/test_response.py +0 -0
  44. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/__init__.py +0 -0
  45. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/__init__.py +0 -0
  46. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/async_support.py +0 -0
  47. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/column.py +0 -0
  48. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/database.py +0 -0
  49. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/decorators.py +0 -0
  50. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/engine.py +0 -0
  51. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/result.py +0 -0
  52. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/row.py +0 -0
  53. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/sequence.py +0 -0
  54. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/table.py +0 -0
  55. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/transaction.py +0 -0
  56. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/core/view.py +0 -0
  57. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/exceptions.py +0 -0
  58. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/migrations.py +0 -0
  59. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/__init__.py +0 -0
  60. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/base/__init__.py +0 -0
  61. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/base/initializer.py +0 -0
  62. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/base/operators.py +0 -0
  63. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/base/sql.py +0 -0
  64. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/base/types.py +0 -0
  65. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/mysql/__init__.py +0 -0
  66. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/mysql/operators.py +0 -0
  67. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/mysql/reserved.py +0 -0
  68. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/mysql/sql.py +0 -0
  69. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/mysql/types.py +0 -0
  70. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/postgres/__init__.py +0 -0
  71. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/postgres/operators.py +0 -0
  72. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/postgres/reserved.py +0 -0
  73. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/postgres/sql.py +0 -0
  74. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/postgres/types.py +0 -0
  75. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  76. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlite/operators.py +0 -0
  77. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  78. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlite/sql.py +0 -0
  79. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlite/types.py +0 -0
  80. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  81. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  82. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  83. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  84. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/sqlserver/types.py +0 -0
  85. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/servers/tablehelper.py +0 -0
  86. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/__init__.py +0 -0
  87. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/common_db_test.py +0 -0
  88. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/__init__.py +0 -0
  89. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/common.py +0 -0
  90. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_column.py +0 -0
  91. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  92. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_database.py +0 -0
  93. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  94. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  95. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  96. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_result.py +0 -0
  97. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_row.py +0 -0
  98. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  99. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  100. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  101. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  102. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  103. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_table.py +0 -0
  104. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  105. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  106. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/sql/__init__.py +0 -0
  107. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/sql/common.py +0 -0
  108. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  109. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  110. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  111. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_db_utils.py +0 -0
  112. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_postgres.py +0 -0
  113. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  114. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  115. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_result_caching.py +0 -0
  116. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  117. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  118. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  119. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  120. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_sql_builder.py +0 -0
  121. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_tablehelper.py +0 -0
  122. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/tests/test_view_helper.py +0 -0
  123. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/db/utils.py +0 -0
  124. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/logging.py +0 -0
  125. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/__init__.py +0 -0
  126. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/conv/__init__.py +0 -0
  127. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/conv/iconv.py +0 -0
  128. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/conv/oconv.py +0 -0
  129. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/db.py +0 -0
  130. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/export.py +0 -0
  131. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/format.py +0 -0
  132. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/mail.py +0 -0
  133. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/merge.py +0 -0
  134. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/__init__.py +0 -0
  135. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_db.py +0 -0
  136. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_fix.py +0 -0
  137. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_format.py +0 -0
  138. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_iconv.py +0 -0
  139. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_merge.py +0 -0
  140. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_oconv.py +0 -0
  141. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_original_error.py +0 -0
  142. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tests/test_timer.py +0 -0
  143. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/timer.py +0 -0
  144. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/misc/tools.py +0 -0
  145. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/__init__.py +0 -0
  146. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/authorizenet_adapter.py +0 -0
  147. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/base_adapter.py +0 -0
  148. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/braintree_adapter.py +0 -0
  149. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/charge_rules.py +0 -0
  150. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/demo_profiles.py +0 -0
  151. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/profiles.py +0 -0
  152. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/router.py +0 -0
  153. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity/payment/stripe_adapter.py +0 -0
  154. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  155. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity_python.egg-info/entry_points.txt +0 -0
  156. {velocity_python-0.1.12 → velocity_python-0.1.13}/src/velocity_python.egg-info/top_level.txt +0 -0
  157. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_amplify_build.py +0 -0
  158. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_async_support.py +0 -0
  159. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_batch_operations.py +0 -0
  160. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_concurrency_safety.py +0 -0
  161. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_connection_pool.py +0 -0
  162. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_connection_resilience.py +0 -0
  163. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_decorators.py +0 -0
  164. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_formbuilder_reshaper.py +0 -0
  165. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_formbuilder_template_validator.py +0 -0
  166. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_iconv_money_to_cents.py +0 -0
  167. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_lambda_handler.py +0 -0
  168. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_lambda_handler_auth.py +0 -0
  169. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_mixins_import.py +0 -0
  170. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_n_plus_one.py +0 -0
  171. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_observability.py +0 -0
  172. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_payment_braintree_adapter.py +0 -0
  173. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_payment_demo_profiles.py +0 -0
  174. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_payment_profiles.py +0 -0
  175. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_payment_router.py +0 -0
  176. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_payment_stripe_adapter.py +0 -0
  177. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_prepared_statements.py +0 -0
  178. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_psycopg3_upgrade.py +0 -0
  179. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_query_cache.py +0 -0
  180. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_row_batch_update.py +0 -0
  181. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_row_cache_staleness.py +0 -0
  182. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_row_dirty_tracking.py +0 -0
  183. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_schema_migrations.py +0 -0
  184. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_security_hardening.py +0 -0
  185. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_sqs_per_record_transactions.py +0 -0
  186. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  187. {velocity_python-0.1.12 → velocity_python-0.1.13}/tests/test_table_alter.py +0 -0
  188. {velocity_python-0.1.12 → velocity_python-0.1.13}/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.12
3
+ Version: 0.1.13
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
@@ -29,6 +29,8 @@ Provides-Extra: excel
29
29
  Requires-Dist: openpyxl>=3.1.0; extra == "excel"
30
30
  Provides-Extra: templates
31
31
  Requires-Dist: jinja2>=3.1.0; extra == "templates"
32
+ Provides-Extra: pdf
33
+ Requires-Dist: weasyprint>=62.0; extra == "pdf"
32
34
  Provides-Extra: http
33
35
  Requires-Dist: requests>=2.32.0; extra == "http"
34
36
  Provides-Extra: mysql
@@ -41,7 +43,7 @@ Provides-Extra: payment
41
43
  Requires-Dist: stripe>=12.0.0; extra == "payment"
42
44
  Requires-Dist: braintree>=4.30.0; extra == "payment"
43
45
  Provides-Extra: all
44
- Requires-Dist: velocity-python[aws,excel,http,payment,postgres,templates]; extra == "all"
46
+ Requires-Dist: velocity-python[aws,excel,http,payment,pdf,postgres,templates]; extra == "all"
45
47
  Provides-Extra: dev
46
48
  Requires-Dist: pytest>=8.0.0; extra == "dev"
47
49
  Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.12"
7
+ version = "0.1.13"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -48,6 +48,9 @@ excel = [
48
48
  templates = [
49
49
  "jinja2>=3.1.0",
50
50
  ]
51
+ pdf = [
52
+ "weasyprint>=62.0",
53
+ ]
51
54
  http = [
52
55
  "requests>=2.32.0",
53
56
  ]
@@ -65,7 +68,7 @@ payment = [
65
68
  "braintree>=4.30.0",
66
69
  ]
67
70
  all = [
68
- "velocity-python[postgres,aws,excel,templates,http,payment]",
71
+ "velocity-python[postgres,aws,excel,templates,http,payment,pdf]",
69
72
  ]
70
73
  dev = [
71
74
  "pytest>=8.0.0",
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.12"
1
+ __version__ = version = "0.1.13"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,199 @@
1
+ """
2
+ HTML-to-PDF generation using WeasyPrint.
3
+
4
+ Provides a simple API for converting HTML strings to PDF bytes,
5
+ with support for page size, orientation, margins, and page footers.
6
+
7
+ Usage::
8
+
9
+ from velocity.misc.pdf import html_to_pdf
10
+
11
+ # Simple conversion
12
+ pdf_bytes = html_to_pdf("<h1>Hello</h1><p>World</p>")
13
+
14
+ # With options
15
+ pdf_bytes = html_to_pdf(
16
+ html,
17
+ page_size="Letter",
18
+ orientation="landscape",
19
+ margin="0.5in",
20
+ footer_right="Page {page} of {pages}",
21
+ )
22
+
23
+ # Wrap a body fragment with a full HTML document + default styles
24
+ from velocity.misc.pdf import wrap_html, html_to_pdf
25
+
26
+ html = wrap_html("My Report", "<p>Content here</p>")
27
+ pdf_bytes = html_to_pdf(html)
28
+
29
+ Requires the ``pdf`` extra::
30
+
31
+ pip install velocity-python[pdf]
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import logging
37
+ import os
38
+ from typing import Optional
39
+
40
+ logger = logging.getLogger("velocity.misc.pdf")
41
+
42
+ # On AWS Lambda, fontconfig needs to know where fonts.conf lives.
43
+ # The py-lib-support layer ships fonts/ at /opt/fonts/.
44
+ if "AWS_LAMBDA_FUNCTION_NAME" in os.environ:
45
+ os.environ.setdefault("FONTCONFIG_PATH", "/opt/fonts")
46
+
47
+ try:
48
+ import weasyprint
49
+ except ImportError:
50
+ weasyprint = None # type: ignore[assignment]
51
+
52
+ _DEFAULT_BODY_CSS = """\
53
+ body {
54
+ font-family: Arial, Helvetica, sans-serif;
55
+ font-size: 11pt;
56
+ line-height: 1.6;
57
+ color: #222;
58
+ margin: 0;
59
+ padding: 24px;
60
+ }
61
+ h1 { font-size: 20px; margin-bottom: 16px; }
62
+ p { margin: 0 0 12px; line-height: 1.6; }
63
+ ol, ul { padding-left: 24px; }
64
+ li { margin-bottom: 8px; }
65
+ """
66
+
67
+
68
+ def _require_weasyprint():
69
+ if weasyprint is None:
70
+ raise ImportError(
71
+ "weasyprint is required for PDF generation. "
72
+ "Install it with: pip install velocity-python[pdf]"
73
+ )
74
+
75
+
76
+ def _build_page_css(
77
+ page_size: str = "Letter",
78
+ orientation: str = "portrait",
79
+ margin: Optional[str] = None,
80
+ margin_top: Optional[str] = None,
81
+ margin_right: Optional[str] = None,
82
+ margin_bottom: Optional[str] = None,
83
+ margin_left: Optional[str] = None,
84
+ footer_right: Optional[str] = None,
85
+ footer_center: Optional[str] = None,
86
+ footer_font_size: str = "8pt",
87
+ ) -> str:
88
+ """Build a ``@page`` CSS rule from the given options."""
89
+ parts = [f"size: {page_size} {orientation};"]
90
+
91
+ if margin:
92
+ parts.append(f"margin: {margin};")
93
+ else:
94
+ mt = margin_top or "0.5in"
95
+ mr = margin_right or "0.5in"
96
+ mb = margin_bottom or "0.75in"
97
+ ml = margin_left or "0.5in"
98
+ parts.append(f"margin: {mt} {mr} {mb} {ml};")
99
+
100
+ footer_blocks = []
101
+ if footer_right:
102
+ content = footer_right.replace("{page}", '" counter(page) "').replace("{pages}", '" counter(pages) "')
103
+ content = f'" {content} "'
104
+ footer_blocks.append(
105
+ f"@bottom-right {{ content: {content}; font-size: {footer_font_size}; color: #666; }}"
106
+ )
107
+ if footer_center:
108
+ content = footer_center.replace("{page}", '" counter(page) "').replace("{pages}", '" counter(pages) "')
109
+ content = f'" {content} "'
110
+ footer_blocks.append(
111
+ f"@bottom-center {{ content: {content}; font-size: {footer_font_size}; color: #666; }}"
112
+ )
113
+
114
+ rule_body = "\n ".join(parts)
115
+ footer_body = "\n ".join(footer_blocks)
116
+ return f"@page {{\n {rule_body}\n {footer_body}\n}}"
117
+
118
+
119
+ def html_to_pdf(
120
+ html: str,
121
+ *,
122
+ page_size: str = "Letter",
123
+ orientation: str = "portrait",
124
+ margin: Optional[str] = None,
125
+ margin_top: Optional[str] = None,
126
+ margin_right: Optional[str] = None,
127
+ margin_bottom: Optional[str] = None,
128
+ margin_left: Optional[str] = None,
129
+ footer_right: Optional[str] = None,
130
+ footer_center: Optional[str] = None,
131
+ footer_font_size: str = "8pt",
132
+ ) -> bytes:
133
+ """
134
+ Convert an HTML string to PDF bytes.
135
+
136
+ Args:
137
+ html: Complete HTML document string.
138
+ page_size: CSS page size (e.g. "Letter", "A4"). Default "Letter".
139
+ orientation: "portrait" or "landscape". Default "portrait".
140
+ margin: Shorthand margin for all sides (e.g. "0.5in").
141
+ margin_top/right/bottom/left: Individual margins (override ``margin``).
142
+ footer_right: Right-aligned footer text. Use ``{page}`` and ``{pages}``
143
+ for page numbering (e.g. ``"Page {page} of {pages}"``).
144
+ footer_center: Center-aligned footer text. Same placeholders.
145
+ footer_font_size: CSS font size for footer. Default "8pt".
146
+
147
+ Returns:
148
+ PDF file contents as bytes.
149
+
150
+ Raises:
151
+ ImportError: If weasyprint is not installed.
152
+ """
153
+ _require_weasyprint()
154
+
155
+ page_css = _build_page_css(
156
+ page_size=page_size,
157
+ orientation=orientation,
158
+ margin=margin,
159
+ margin_top=margin_top,
160
+ margin_right=margin_right,
161
+ margin_bottom=margin_bottom,
162
+ margin_left=margin_left,
163
+ footer_right=footer_right,
164
+ footer_center=footer_center,
165
+ footer_font_size=footer_font_size,
166
+ )
167
+
168
+ css = weasyprint.CSS(string=page_css)
169
+ doc = weasyprint.HTML(string=html)
170
+ return doc.write_pdf(stylesheets=[css])
171
+
172
+
173
+ def wrap_html(title: str, body: str, css: str = "") -> str:
174
+ """
175
+ Wrap an HTML body fragment in a full HTML document with default styles.
176
+
177
+ Args:
178
+ title: Document ``<title>`` and ``<h1>`` heading.
179
+ body: HTML body content (fragment, not full document).
180
+ css: Additional CSS to include after the default body styles.
181
+
182
+ Returns:
183
+ Complete HTML document string ready for :func:`html_to_pdf`.
184
+ """
185
+ all_css = _DEFAULT_BODY_CSS + "\n" + css
186
+ return f"""<!DOCTYPE html>
187
+ <html>
188
+ <head>
189
+ <meta charset="utf-8">
190
+ <title>{title}</title>
191
+ <style>
192
+ {all_css}
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <h1>{title}</h1>
197
+ {body}
198
+ </body>
199
+ </html>"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.12
3
+ Version: 0.1.13
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
@@ -29,6 +29,8 @@ Provides-Extra: excel
29
29
  Requires-Dist: openpyxl>=3.1.0; extra == "excel"
30
30
  Provides-Extra: templates
31
31
  Requires-Dist: jinja2>=3.1.0; extra == "templates"
32
+ Provides-Extra: pdf
33
+ Requires-Dist: weasyprint>=62.0; extra == "pdf"
32
34
  Provides-Extra: http
33
35
  Requires-Dist: requests>=2.32.0; extra == "http"
34
36
  Provides-Extra: mysql
@@ -41,7 +43,7 @@ Provides-Extra: payment
41
43
  Requires-Dist: stripe>=12.0.0; extra == "payment"
42
44
  Requires-Dist: braintree>=4.30.0; extra == "payment"
43
45
  Provides-Extra: all
44
- Requires-Dist: velocity-python[aws,excel,http,payment,postgres,templates]; extra == "all"
46
+ Requires-Dist: velocity-python[aws,excel,http,payment,pdf,postgres,templates]; extra == "all"
45
47
  Provides-Extra: dev
46
48
  Requires-Dist: pytest>=8.0.0; extra == "dev"
47
49
  Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
@@ -121,6 +121,7 @@ src/velocity/misc/export.py
121
121
  src/velocity/misc/format.py
122
122
  src/velocity/misc/mail.py
123
123
  src/velocity/misc/merge.py
124
+ src/velocity/misc/pdf.py
124
125
  src/velocity/misc/timer.py
125
126
  src/velocity/misc/tools.py
126
127
  src/velocity/misc/conv/__init__.py
@@ -170,6 +171,7 @@ tests/test_payment_demo_profiles.py
170
171
  tests/test_payment_profiles.py
171
172
  tests/test_payment_router.py
172
173
  tests/test_payment_stripe_adapter.py
174
+ tests/test_pdf.py
173
175
  tests/test_prepared_statements.py
174
176
  tests/test_psycopg3_upgrade.py
175
177
  tests/test_query_cache.py
@@ -1,7 +1,7 @@
1
1
  sqlparse>=0.5.0
2
2
 
3
3
  [all]
4
- velocity-python[aws,excel,http,payment,postgres,templates]
4
+ velocity-python[aws,excel,http,payment,pdf,postgres,templates]
5
5
 
6
6
  [aws]
7
7
  boto3>=1.35.0
@@ -33,6 +33,9 @@ mysql-connector-python>=9.0.0
33
33
  stripe>=12.0.0
34
34
  braintree>=4.30.0
35
35
 
36
+ [pdf]
37
+ weasyprint>=62.0
38
+
36
39
  [postgres]
37
40
  psycopg[binary]>=3.2.0
38
41
 
@@ -0,0 +1,188 @@
1
+ """Tests for velocity.misc.pdf — HTML-to-PDF generation."""
2
+
3
+ import pytest
4
+ from unittest.mock import patch, MagicMock
5
+
6
+ from velocity.misc.pdf import (
7
+ html_to_pdf,
8
+ wrap_html,
9
+ _build_page_css,
10
+ _DEFAULT_BODY_CSS,
11
+ )
12
+
13
+
14
+ # ── wrap_html ───────────────────────────────────────────────────────
15
+
16
+
17
+ class TestWrapHtml:
18
+
19
+ def test_produces_full_document(self):
20
+ result = wrap_html("My Title", "<p>Hello</p>")
21
+ assert "<!DOCTYPE html>" in result
22
+ assert "<title>My Title</title>" in result
23
+ assert "<h1>My Title</h1>" in result
24
+ assert "<p>Hello</p>" in result
25
+
26
+ def test_includes_default_css(self):
27
+ result = wrap_html("T", "<p>body</p>")
28
+ assert "font-family:" in result
29
+ assert _DEFAULT_BODY_CSS.strip()[:30] in result
30
+
31
+ def test_includes_custom_css(self):
32
+ result = wrap_html("T", "<p>body</p>", css=".custom { color: red; }")
33
+ assert ".custom { color: red; }" in result
34
+
35
+ def test_empty_body(self):
36
+ result = wrap_html("T", "")
37
+ assert "<h1>T</h1>" in result
38
+
39
+
40
+ # ── _build_page_css ─────────────────────────────────────────────────
41
+
42
+
43
+ class TestBuildPageCss:
44
+
45
+ def test_defaults(self):
46
+ css = _build_page_css()
47
+ assert "size: Letter portrait;" in css
48
+ assert "margin:" in css
49
+
50
+ def test_landscape(self):
51
+ css = _build_page_css(orientation="landscape")
52
+ assert "size: Letter landscape;" in css
53
+
54
+ def test_a4(self):
55
+ css = _build_page_css(page_size="A4")
56
+ assert "size: A4 portrait;" in css
57
+
58
+ def test_shorthand_margin(self):
59
+ css = _build_page_css(margin="1in")
60
+ assert "margin: 1in;" in css
61
+
62
+ def test_individual_margins(self):
63
+ css = _build_page_css(
64
+ margin_top="0.5in",
65
+ margin_right="0.45in",
66
+ margin_bottom="0.55in",
67
+ margin_left="0.45in",
68
+ )
69
+ assert "margin: 0.5in 0.45in 0.55in 0.45in;" in css
70
+
71
+ def test_footer_right(self):
72
+ css = _build_page_css(footer_right="Page {page} of {pages}")
73
+ assert "@bottom-right" in css
74
+ assert 'counter(page)' in css
75
+ assert 'counter(pages)' in css
76
+
77
+ def test_footer_center(self):
78
+ css = _build_page_css(footer_center="CaringCent, LLC")
79
+ assert "@bottom-center" in css
80
+ assert "CaringCent, LLC" in css
81
+
82
+ def test_footer_font_size(self):
83
+ css = _build_page_css(footer_right="pg", footer_font_size="10pt")
84
+ assert "font-size: 10pt;" in css
85
+
86
+ def test_no_footers(self):
87
+ css = _build_page_css()
88
+ assert "@bottom-right" not in css
89
+ assert "@bottom-center" not in css
90
+
91
+
92
+ # ── html_to_pdf ─────────────────────────────────────────────────────
93
+
94
+
95
+ class TestHtmlToPdf:
96
+
97
+ def test_raises_without_weasyprint(self):
98
+ with patch("velocity.misc.pdf.weasyprint", None):
99
+ with pytest.raises(ImportError, match="weasyprint is required"):
100
+ html_to_pdf("<h1>test</h1>")
101
+
102
+ def test_calls_weasyprint(self):
103
+ mock_wp = MagicMock()
104
+ mock_doc = MagicMock()
105
+ mock_doc.write_pdf.return_value = b"%PDF-fake"
106
+ mock_wp.HTML.return_value = mock_doc
107
+ mock_wp.CSS.return_value = MagicMock()
108
+
109
+ with patch("velocity.misc.pdf.weasyprint", mock_wp):
110
+ result = html_to_pdf("<h1>Hello</h1>")
111
+
112
+ assert result == b"%PDF-fake"
113
+ mock_wp.HTML.assert_called_once()
114
+ call_kwargs = mock_wp.HTML.call_args
115
+ assert "<h1>Hello</h1>" in call_kwargs[1]["string"]
116
+ mock_doc.write_pdf.assert_called_once()
117
+
118
+ def test_passes_page_css_as_stylesheet(self):
119
+ mock_wp = MagicMock()
120
+ mock_doc = MagicMock()
121
+ mock_doc.write_pdf.return_value = b"%PDF"
122
+ mock_wp.HTML.return_value = mock_doc
123
+ mock_css_obj = MagicMock()
124
+ mock_wp.CSS.return_value = mock_css_obj
125
+
126
+ with patch("velocity.misc.pdf.weasyprint", mock_wp):
127
+ html_to_pdf(
128
+ "<h1>test</h1>",
129
+ page_size="A4",
130
+ orientation="landscape",
131
+ footer_right="Page {page}",
132
+ )
133
+
134
+ # CSS should contain @page with landscape A4
135
+ css_string = mock_wp.CSS.call_args[1]["string"]
136
+ assert "A4 landscape" in css_string
137
+ assert "@bottom-right" in css_string
138
+
139
+ # write_pdf should receive the CSS object
140
+ mock_doc.write_pdf.assert_called_once_with(stylesheets=[mock_css_obj])
141
+
142
+ def test_landscape_with_margins(self):
143
+ mock_wp = MagicMock()
144
+ mock_doc = MagicMock()
145
+ mock_doc.write_pdf.return_value = b"%PDF"
146
+ mock_wp.HTML.return_value = mock_doc
147
+ mock_wp.CSS.return_value = MagicMock()
148
+
149
+ with patch("velocity.misc.pdf.weasyprint", mock_wp):
150
+ html_to_pdf(
151
+ "<h1>report</h1>",
152
+ orientation="landscape",
153
+ margin_top="0.5in",
154
+ margin_right="0.45in",
155
+ margin_bottom="0.55in",
156
+ margin_left="0.45in",
157
+ footer_right="Page {page} of {pages}",
158
+ footer_font_size="8pt",
159
+ )
160
+
161
+ css_string = mock_wp.CSS.call_args[1]["string"]
162
+ assert "landscape" in css_string
163
+ assert "0.5in 0.45in 0.55in 0.45in" in css_string
164
+ assert "counter(page)" in css_string
165
+ assert "counter(pages)" in css_string
166
+
167
+
168
+ # ── Integration with wrap_html + html_to_pdf ────────────────────────
169
+
170
+
171
+ class TestIntegration:
172
+
173
+ def test_wrap_then_convert(self):
174
+ mock_wp = MagicMock()
175
+ mock_doc = MagicMock()
176
+ mock_doc.write_pdf.return_value = b"%PDF-wrapped"
177
+ mock_wp.HTML.return_value = mock_doc
178
+ mock_wp.CSS.return_value = MagicMock()
179
+
180
+ html = wrap_html("Report", "<p>Content</p>")
181
+
182
+ with patch("velocity.misc.pdf.weasyprint", mock_wp):
183
+ result = html_to_pdf(html)
184
+
185
+ assert result == b"%PDF-wrapped"
186
+ passed_html = mock_wp.HTML.call_args[1]["string"]
187
+ assert "<title>Report</title>" in passed_html
188
+ assert "<p>Content</p>" in passed_html