sustained 2.24.2__tar.gz → 2.26.0__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 (367) hide show
  1. sustained-2.26.0/CHANGELOG.md +481 -0
  2. {sustained-2.24.2 → sustained-2.26.0}/PKG-INFO +24 -4
  3. {sustained-2.24.2 → sustained-2.26.0}/README.md +2 -1
  4. sustained-2.26.0/pyproject.toml +78 -0
  5. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/__init__.py +2 -0
  6. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/aio.py +272 -65
  7. sustained-2.26.0/src/sustained/aio_migrations.py +882 -0
  8. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/aio_pool.py +107 -35
  9. sustained-2.26.0/src/sustained/analysis.py +371 -0
  10. sustained-2.26.0/src/sustained/autogenerate/__init__.py +554 -0
  11. sustained-2.26.0/src/sustained/autogenerate/column_steps.py +782 -0
  12. sustained-2.26.0/src/sustained/autogenerate/constraints.py +423 -0
  13. sustained-2.26.0/src/sustained/autogenerate/diff.py +816 -0
  14. sustained-2.26.0/src/sustained/autogenerate/online.py +397 -0
  15. sustained-2.26.0/src/sustained/autogenerate/statements.py +654 -0
  16. sustained-2.26.0/src/sustained/autogenerate/steps.py +942 -0
  17. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builder.py +208 -85
  18. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builder.pyi +12 -8
  19. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/conditional_clause_builder.py +40 -13
  20. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/conditional_clause_builder.pyi +38 -19
  21. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/group_by_builder.py +4 -3
  22. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/group_by_builder.pyi +2 -1
  23. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/join_builder.py +60 -40
  24. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/join_builder.pyi +15 -13
  25. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/order_by_builder.py +6 -3
  26. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/order_by_builder.pyi +2 -1
  27. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/select_clause_builder.py +1 -1
  28. sustained-2.26.0/src/sustained/cli/__init__.py +387 -0
  29. sustained-2.26.0/src/sustained/cli/__main__.py +5 -0
  30. sustained-2.26.0/src/sustained/cli/commands.py +393 -0
  31. sustained-2.26.0/src/sustained/cli/config.py +266 -0
  32. sustained-2.26.0/src/sustained/cli/output.py +51 -0
  33. sustained-2.26.0/src/sustained/cli/plan.py +439 -0
  34. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/athena.py +1 -0
  35. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/base.py +249 -14
  36. sustained-2.26.0/src/sustained/compilers/default.py +16 -0
  37. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/duckdb.py +26 -0
  38. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/mssql.py +102 -2
  39. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/mysql.py +53 -14
  40. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/postgres.py +12 -0
  41. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/presto.py +16 -1
  42. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/ddl.py +199 -43
  43. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/dialects.py +2 -1
  44. sustained-2.26.0/src/sustained/driver_errors.py +36 -0
  45. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/exceptions.py +38 -0
  46. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/execution.py +98 -1
  47. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/expressions.py +17 -4
  48. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/functions.py +2 -2
  49. sustained-2.26.0/src/sustained/guards/__init__.py +98 -0
  50. sustained-2.26.0/src/sustained/guards/core.py +65 -0
  51. sustained-2.26.0/src/sustained/guards/impact.py +229 -0
  52. sustained-2.24.2/src/sustained/guards.py → sustained-2.26.0/src/sustained/guards/text.py +59 -125
  53. sustained-2.26.0/src/sustained/impact/__init__.py +93 -0
  54. sustained-2.26.0/src/sustained/impact/analyzer.py +626 -0
  55. sustained-2.26.0/src/sustained/impact/context.py +378 -0
  56. sustained-2.26.0/src/sustained/impact/model.py +401 -0
  57. sustained-2.26.0/src/sustained/impact/preflight.py +418 -0
  58. sustained-2.26.0/src/sustained/impact/recognizer/__init__.py +466 -0
  59. sustained-2.26.0/src/sustained/impact/recognizer/alter.py +492 -0
  60. sustained-2.26.0/src/sustained/impact/recognizer/create_drop.py +323 -0
  61. sustained-2.26.0/src/sustained/impact/recognizer/cursor.py +548 -0
  62. sustained-2.26.0/src/sustained/impact/recognizer/definitions.py +348 -0
  63. sustained-2.26.0/src/sustained/impact/recognizer/session.py +86 -0
  64. sustained-2.26.0/src/sustained/impact/recognizer/sources.py +145 -0
  65. sustained-2.26.0/src/sustained/impact/recognizer/statements.py +603 -0
  66. sustained-2.26.0/src/sustained/impact/recognizer/volatility.py +178 -0
  67. sustained-2.26.0/src/sustained/impact/report.py +466 -0
  68. sustained-2.26.0/src/sustained/impact/rules/__init__.py +348 -0
  69. sustained-2.26.0/src/sustained/impact/rules/common.py +220 -0
  70. sustained-2.26.0/src/sustained/impact/rules/duckdb/__init__.py +116 -0
  71. sustained-2.26.0/src/sustained/impact/rules/duckdb/catalog.py +147 -0
  72. sustained-2.26.0/src/sustained/impact/rules/duckdb/context.py +77 -0
  73. sustained-2.26.0/src/sustained/impact/rules/duckdb/statements.py +322 -0
  74. sustained-2.26.0/src/sustained/impact/rules/mssql/__init__.py +159 -0
  75. sustained-2.26.0/src/sustained/impact/rules/mssql/alter.py +720 -0
  76. sustained-2.26.0/src/sustained/impact/rules/mssql/catalog.py +355 -0
  77. sustained-2.26.0/src/sustained/impact/rules/mssql/context.py +121 -0
  78. sustained-2.26.0/src/sustained/impact/rules/mssql/facts.py +232 -0
  79. sustained-2.26.0/src/sustained/impact/rules/mssql/locks.py +97 -0
  80. sustained-2.26.0/src/sustained/impact/rules/mssql/preflight.py +162 -0
  81. sustained-2.26.0/src/sustained/impact/rules/mssql/statements.py +391 -0
  82. sustained-2.26.0/src/sustained/impact/rules/mssql/trace.py +394 -0
  83. sustained-2.26.0/src/sustained/impact/rules/mysql/__init__.py +173 -0
  84. sustained-2.26.0/src/sustained/impact/rules/mysql/actions.py +364 -0
  85. sustained-2.26.0/src/sustained/impact/rules/mysql/catalog.py +487 -0
  86. sustained-2.26.0/src/sustained/impact/rules/mysql/column_types.py +347 -0
  87. sustained-2.26.0/src/sustained/impact/rules/mysql/columns.py +458 -0
  88. sustained-2.26.0/src/sustained/impact/rules/mysql/context.py +248 -0
  89. sustained-2.26.0/src/sustained/impact/rules/mysql/facts.py +113 -0
  90. sustained-2.26.0/src/sustained/impact/rules/mysql/locks.py +143 -0
  91. sustained-2.26.0/src/sustained/impact/rules/mysql/online.py +450 -0
  92. sustained-2.26.0/src/sustained/impact/rules/mysql/preflight.py +220 -0
  93. sustained-2.26.0/src/sustained/impact/rules/mysql/statements.py +357 -0
  94. sustained-2.26.0/src/sustained/impact/rules/mysql/trace.py +247 -0
  95. sustained-2.26.0/src/sustained/impact/rules/postgres/__init__.py +301 -0
  96. sustained-2.26.0/src/sustained/impact/rules/postgres/alter.py +681 -0
  97. sustained-2.26.0/src/sustained/impact/rules/postgres/catalog.py +396 -0
  98. sustained-2.26.0/src/sustained/impact/rules/postgres/column_types.py +427 -0
  99. sustained-2.26.0/src/sustained/impact/rules/postgres/context.py +362 -0
  100. sustained-2.26.0/src/sustained/impact/rules/postgres/locks.py +60 -0
  101. sustained-2.26.0/src/sustained/impact/rules/postgres/partitions.py +207 -0
  102. sustained-2.26.0/src/sustained/impact/rules/postgres/preflight.py +275 -0
  103. sustained-2.26.0/src/sustained/impact/rules/postgres/remedies.py +143 -0
  104. sustained-2.26.0/src/sustained/impact/rules/postgres/statements.py +723 -0
  105. sustained-2.26.0/src/sustained/impact/rules/postgres/trace.py +387 -0
  106. sustained-2.26.0/src/sustained/impact/rules/sqlite/__init__.py +106 -0
  107. sustained-2.26.0/src/sustained/impact/rules/sqlite/catalog.py +123 -0
  108. sustained-2.26.0/src/sustained/impact/rules/sqlite/context.py +151 -0
  109. sustained-2.26.0/src/sustained/impact/rules/sqlite/statements.py +485 -0
  110. sustained-2.26.0/src/sustained/impact/state.py +765 -0
  111. sustained-2.26.0/src/sustained/impact/tokens.py +471 -0
  112. sustained-2.26.0/src/sustained/impact/window.py +231 -0
  113. sustained-2.26.0/src/sustained/introspect/__init__.py +248 -0
  114. sustained-2.26.0/src/sustained/introspect/compare.py +71 -0
  115. sustained-2.26.0/src/sustained/introspect/duckdb.py +176 -0
  116. sustained-2.26.0/src/sustained/introspect/information_schema.py +438 -0
  117. sustained-2.26.0/src/sustained/introspect/model.py +298 -0
  118. sustained-2.26.0/src/sustained/introspect/mssql.py +80 -0
  119. sustained-2.26.0/src/sustained/introspect/mysql.py +149 -0
  120. sustained-2.26.0/src/sustained/introspect/normalize.py +278 -0
  121. sustained-2.26.0/src/sustained/introspect/postgres.py +459 -0
  122. sustained-2.26.0/src/sustained/introspect/runner.py +270 -0
  123. sustained-2.26.0/src/sustained/introspect/scope.py +110 -0
  124. sustained-2.26.0/src/sustained/introspect/sqlite.py +280 -0
  125. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/migration_files.py +207 -9
  126. sustained-2.26.0/src/sustained/migrations/__init__.py +257 -0
  127. sustained-2.26.0/src/sustained/migrations/checks.py +276 -0
  128. sustained-2.26.0/src/sustained/migrations/core/__init__.py +16 -0
  129. sustained-2.26.0/src/sustained/migrations/core/base.py +97 -0
  130. sustained-2.26.0/src/sustained/migrations/core/bookkeeping.py +633 -0
  131. sustained-2.26.0/src/sustained/migrations/core/rehearsing.py +506 -0
  132. sustained-2.26.0/src/sustained/migrations/core/requests.py +297 -0
  133. sustained-2.26.0/src/sustained/migrations/core/runs.py +873 -0
  134. sustained-2.26.0/src/sustained/migrations/core/tracing.py +215 -0
  135. sustained-2.26.0/src/sustained/migrations/migration.py +474 -0
  136. sustained-2.26.0/src/sustained/migrations/migrator.py +1085 -0
  137. sustained-2.26.0/src/sustained/migrations/planning.py +414 -0
  138. sustained-2.26.0/src/sustained/migrations/rehearsal.py +559 -0
  139. sustained-2.26.0/src/sustained/migrations/replay.py +141 -0
  140. sustained-2.26.0/src/sustained/migrations/tracking.py +190 -0
  141. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/model.py +17 -6
  142. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/pool.py +50 -37
  143. sustained-2.26.0/src/sustained/rebuild.py +394 -0
  144. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/rendering.py +63 -1
  145. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/schema.py +29 -11
  146. sustained-2.26.0/src/sustained/type_changes.py +272 -0
  147. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/types.py +8 -2
  148. {sustained-2.24.2 → sustained-2.26.0}/support.json +6 -6
  149. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/aio_lifecycle.py +4 -0
  150. sustained-2.26.0/tests/integration/column_types.py +215 -0
  151. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/harness.py +18 -4
  152. sustained-2.26.0/tests/integration/impact.py +1017 -0
  153. sustained-2.26.0/tests/integration/impact_duckdb.py +242 -0
  154. sustained-2.26.0/tests/integration/impact_innodb.py +599 -0
  155. sustained-2.26.0/tests/integration/impact_mssql.py +407 -0
  156. sustained-2.26.0/tests/integration/impact_sqlite.py +154 -0
  157. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/lifecycle.py +14 -3
  158. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/queries.py +51 -0
  159. sustained-2.26.0/tests/integration/schema_changes.py +316 -0
  160. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_contract.py +25 -4
  161. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_duckdb.py +5 -1
  162. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_mariadb.py +6 -1
  163. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_mssql.py +5 -1
  164. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_mysql.py +6 -1
  165. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_postgres.py +6 -1
  166. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_sqlite.py +5 -1
  167. sustained-2.26.0/tests/test_aio_pool_cancellation.py +76 -0
  168. sustained-2.26.0/tests/test_alter_column_lifts.py +278 -0
  169. {sustained-2.24.2 → sustained-2.26.0}/tests/test_analysis.py +194 -0
  170. {sustained-2.24.2 → sustained-2.26.0}/tests/test_analyst_sql.py +1 -1
  171. {sustained-2.24.2 → sustained-2.26.0}/tests/test_async.py +5 -0
  172. sustained-2.26.0/tests/test_async_autocommit_scope.py +93 -0
  173. sustained-2.26.0/tests/test_async_commit_failure.py +63 -0
  174. sustained-2.26.0/tests/test_async_migrations.py +714 -0
  175. sustained-2.26.0/tests/test_async_read_only_status.py +48 -0
  176. sustained-2.26.0/tests/test_async_sessions.py +161 -0
  177. sustained-2.26.0/tests/test_async_thread_cancellation.py +144 -0
  178. {sustained-2.24.2 → sustained-2.26.0}/tests/test_athena.py +4 -0
  179. {sustained-2.24.2 → sustained-2.26.0}/tests/test_autogenerate.py +84 -17
  180. sustained-2.26.0/tests/test_autogenerate_online.py +1177 -0
  181. sustained-2.26.0/tests/test_baseline_failed_row.py +127 -0
  182. sustained-2.26.0/tests/test_batch_insert_template.py +58 -0
  183. {sustained-2.24.2 → sustained-2.26.0}/tests/test_cli.py +864 -13
  184. {sustained-2.24.2 → sustained-2.26.0}/tests/test_column_comments.py +18 -0
  185. {sustained-2.24.2 → sustained-2.26.0}/tests/test_constraint_autogenerate.py +12 -5
  186. sustained-2.26.0/tests/test_cte_hoisting.py +142 -0
  187. {sustained-2.24.2 → sustained-2.26.0}/tests/test_ddl_steps.py +10 -10
  188. sustained-2.26.0/tests/test_deploy.py +205 -0
  189. {sustained-2.24.2 → sustained-2.26.0}/tests/test_dialect_behaviors.py +1 -1
  190. {sustained-2.24.2 → sustained-2.26.0}/tests/test_dialect_ddl.py +5 -5
  191. {sustained-2.24.2 → sustained-2.26.0}/tests/test_dialect_functions.py +6 -3
  192. sustained-2.26.0/tests/test_dml.py +184 -0
  193. sustained-2.26.0/tests/test_driver_errors.py +76 -0
  194. sustained-2.26.0/tests/test_duckdb_constraints.py +235 -0
  195. sustained-2.26.0/tests/test_duckdb_dialect.py +165 -0
  196. sustained-2.26.0/tests/test_enum_checks.py +137 -0
  197. {sustained-2.24.2 → sustained-2.26.0}/tests/test_enum_columns.py +182 -5
  198. sustained-2.26.0/tests/test_extra_table_drops.py +144 -0
  199. sustained-2.26.0/tests/test_failed_down.py +179 -0
  200. sustained-2.26.0/tests/test_failed_write_rollback.py +109 -0
  201. sustained-2.26.0/tests/test_first_row_cap.py +119 -0
  202. sustained-2.26.0/tests/test_foreign_key_schemas.py +202 -0
  203. {sustained-2.24.2 → sustained-2.26.0}/tests/test_guards.py +60 -0
  204. sustained-2.26.0/tests/test_identifier_quoting.py +364 -0
  205. sustained-2.26.0/tests/test_impact_analyze.py +1090 -0
  206. sustained-2.26.0/tests/test_impact_context.py +480 -0
  207. sustained-2.26.0/tests/test_impact_duckdb.py +424 -0
  208. sustained-2.26.0/tests/test_impact_guards.py +428 -0
  209. sustained-2.26.0/tests/test_impact_intent.py +300 -0
  210. sustained-2.26.0/tests/test_impact_model.py +105 -0
  211. sustained-2.26.0/tests/test_impact_mssql.py +1205 -0
  212. sustained-2.26.0/tests/test_impact_mysql.py +386 -0
  213. sustained-2.26.0/tests/test_impact_mysql_alter.py +912 -0
  214. sustained-2.26.0/tests/test_impact_mysql_assert.py +241 -0
  215. sustained-2.26.0/tests/test_impact_mysql_run.py +270 -0
  216. sustained-2.26.0/tests/test_impact_mysql_statements.py +220 -0
  217. sustained-2.26.0/tests/test_impact_mysql_trace.py +359 -0
  218. sustained-2.26.0/tests/test_impact_postgres.py +506 -0
  219. sustained-2.26.0/tests/test_impact_postgres_partitions.py +1109 -0
  220. sustained-2.26.0/tests/test_impact_postgres_remedies.py +337 -0
  221. sustained-2.26.0/tests/test_impact_postgres_schema.py +513 -0
  222. sustained-2.26.0/tests/test_impact_preflight.py +981 -0
  223. sustained-2.26.0/tests/test_impact_recognizer.py +729 -0
  224. sustained-2.26.0/tests/test_impact_recognizer_alter.py +497 -0
  225. sustained-2.26.0/tests/test_impact_recognizer_statements.py +344 -0
  226. sustained-2.26.0/tests/test_impact_report.py +341 -0
  227. sustained-2.26.0/tests/test_impact_script.py +135 -0
  228. sustained-2.26.0/tests/test_impact_sqlite.py +642 -0
  229. sustained-2.26.0/tests/test_impact_tokens.py +248 -0
  230. sustained-2.26.0/tests/test_impact_trace.py +455 -0
  231. {sustained-2.24.2 → sustained-2.26.0}/tests/test_index_introspection.py +57 -7
  232. sustained-2.26.0/tests/test_is_boolean.py +77 -0
  233. {sustained-2.24.2 → sustained-2.26.0}/tests/test_join_builder.py +41 -2
  234. sustained-2.26.0/tests/test_join_schema.py +102 -0
  235. sustained-2.26.0/tests/test_keyword_identifiers.py +109 -0
  236. {sustained-2.24.2 → sustained-2.26.0}/tests/test_lambda_join_builder.py +2 -2
  237. sustained-2.26.0/tests/test_matrix.py +56 -0
  238. {sustained-2.24.2 → sustained-2.26.0}/tests/test_migration_files.py +122 -0
  239. {sustained-2.24.2 → sustained-2.26.0}/tests/test_migration_locking.py +138 -1
  240. sustained-2.26.0/tests/test_migrations.py +884 -0
  241. sustained-2.26.0/tests/test_migrator_behaviour.py +2212 -0
  242. sustained-2.26.0/tests/test_migrator_open_transaction.py +164 -0
  243. {sustained-2.24.2 → sustained-2.26.0}/tests/test_mssql_compiler.py +79 -2
  244. sustained-2.26.0/tests/test_mssql_introspection.py +161 -0
  245. {sustained-2.24.2 → sustained-2.26.0}/tests/test_mysql_introspection.py +594 -3
  246. {sustained-2.24.2 → sustained-2.26.0}/tests/test_mysql_migrations.py +16 -0
  247. sustained-2.26.0/tests/test_name_spelling.py +144 -0
  248. sustained-2.26.0/tests/test_percent_escaping.py +113 -0
  249. sustained-2.26.0/tests/test_pool_waiters.py +202 -0
  250. {sustained-2.24.2 → sustained-2.26.0}/tests/test_postgres_introspection.py +116 -5
  251. {sustained-2.24.2 → sustained-2.26.0}/tests/test_predicates.py +13 -0
  252. {sustained-2.24.2 → sustained-2.26.0}/tests/test_query_builder.py +2 -3
  253. {sustained-2.24.2 → sustained-2.26.0}/tests/test_raw_bindings.py +1 -1
  254. sustained-2.26.0/tests/test_read_applied_records.py +91 -0
  255. sustained-2.26.0/tests/test_rename_foreign_keys.py +76 -0
  256. {sustained-2.24.2 → sustained-2.26.0}/tests/test_result_formats.py +1 -1
  257. {sustained-2.24.2 → sustained-2.26.0}/tests/test_schema_ddl.py +11 -11
  258. sustained-2.26.0/tests/test_schema_qualified_drops.py +240 -0
  259. sustained-2.26.0/tests/test_source_compat.py +109 -0
  260. {sustained-2.24.2 → sustained-2.26.0}/tests/test_sqlite_introspection.py +68 -1
  261. sustained-2.26.0/tests/test_sqlite_rebuild.py +339 -0
  262. sustained-2.26.0/tests/test_sync_changelog.py +94 -0
  263. sustained-2.26.0/tests/test_sync_support.py +42 -0
  264. {sustained-2.24.2 → sustained-2.26.0}/tests/test_table_constraints.py +7 -7
  265. sustained-2.26.0/tests/test_transaction_autocommit.py +70 -0
  266. {sustained-2.24.2 → sustained-2.26.0}/tests/test_transactions.py +5 -0
  267. sustained-2.26.0/tests/test_type_changes.py +217 -0
  268. {sustained-2.24.2 → sustained-2.26.0}/tests/test_typing.py +39 -0
  269. sustained-2.26.0/tests/test_unique_removal.py +139 -0
  270. sustained-2.26.0/tests/test_up_applied_on_failure.py +72 -0
  271. sustained-2.26.0/tests/test_value_literals.py +152 -0
  272. sustained-2.24.2/.pre-commit-config.yaml +0 -56
  273. sustained-2.24.2/CHANGELOG.md +0 -605
  274. sustained-2.24.2/DEVELOPERS.md +0 -121
  275. sustained-2.24.2/deploy.py +0 -148
  276. sustained-2.24.2/docker/compose.yaml +0 -140
  277. sustained-2.24.2/docs/CNAME +0 -1
  278. sustained-2.24.2/docs/_config.yml +0 -3
  279. sustained-2.24.2/docs/_data/nav.yml +0 -59
  280. sustained-2.24.2/docs/_layouts/default.html +0 -100
  281. sustained-2.24.2/docs/assets/css/style.scss +0 -759
  282. sustained-2.24.2/docs/assets/js/docs.js +0 -204
  283. sustained-2.24.2/docs/changelog.md +0 -612
  284. sustained-2.24.2/docs/dialects.md +0 -248
  285. sustained-2.24.2/docs/executing.md +0 -399
  286. sustained-2.24.2/docs/filtering.md +0 -282
  287. sustained-2.24.2/docs/getting-started.md +0 -310
  288. sustained-2.24.2/docs/grouping.md +0 -148
  289. sustained-2.24.2/docs/index.md +0 -69
  290. sustained-2.24.2/docs/models.md +0 -187
  291. sustained-2.24.2/docs/queries.md +0 -422
  292. sustained-2.24.2/docs/recipes.md +0 -768
  293. sustained-2.24.2/docs/reference/cli.md +0 -234
  294. sustained-2.24.2/docs/reference/dialects.md +0 -132
  295. sustained-2.24.2/docs/reference/errors.md +0 -187
  296. sustained-2.24.2/docs/reference/execution.md +0 -305
  297. sustained-2.24.2/docs/reference/index.md +0 -77
  298. sustained-2.24.2/docs/reference/migrations.md +0 -639
  299. sustained-2.24.2/docs/reference/model.md +0 -240
  300. sustained-2.24.2/docs/reference/predicates.md +0 -222
  301. sustained-2.24.2/docs/reference/query-builder.md +0 -497
  302. sustained-2.24.2/docs/reference/schema.md +0 -215
  303. sustained-2.24.2/docs/relations.md +0 -245
  304. sustained-2.24.2/docs/schema.md +0 -842
  305. sustained-2.24.2/docs/support.md +0 -132
  306. sustained-2.24.2/matrix.py +0 -439
  307. sustained-2.24.2/pyproject.toml +0 -36
  308. sustained-2.24.2/src/sustained/aio_migrations.py +0 -1456
  309. sustained-2.24.2/src/sustained/analysis.py +0 -214
  310. sustained-2.24.2/src/sustained/autogenerate.py +0 -1852
  311. sustained-2.24.2/src/sustained/cli.py +0 -906
  312. sustained-2.24.2/src/sustained/introspect.py +0 -1615
  313. sustained-2.24.2/src/sustained/migrations.py +0 -3005
  314. sustained-2.24.2/sync_changelog.py +0 -61
  315. sustained-2.24.2/sync_support.py +0 -198
  316. sustained-2.24.2/tests/integration/column_types.py +0 -127
  317. sustained-2.24.2/tests/test_async_migrations.py +0 -1368
  318. sustained-2.24.2/tests/test_dml.py +0 -97
  319. sustained-2.24.2/tests/test_duckdb_dialect.py +0 -90
  320. sustained-2.24.2/tests/test_identifier_quoting.py +0 -101
  321. sustained-2.24.2/tests/test_migrations.py +0 -2140
  322. {sustained-2.24.2 → sustained-2.26.0}/.gitignore +0 -0
  323. {sustained-2.24.2 → sustained-2.26.0}/LICENSE +0 -0
  324. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/__main__.py +0 -0
  325. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/__init__.py +0 -0
  326. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/__init__.pyi +0 -0
  327. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/having_builder.py +0 -0
  328. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/having_builder.pyi +0 -0
  329. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/select_clause_builder.pyi +0 -0
  330. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/where_builder.py +0 -0
  331. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/builders/where_builder.pyi +0 -0
  332. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/compilers/__init__.py +0 -0
  333. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/naming.py +0 -0
  334. {sustained-2.24.2 → sustained-2.26.0}/src/sustained/py.typed +0 -0
  335. {sustained-2.24.2 → sustained-2.26.0}/tests/__init__.py +0 -0
  336. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/__init__.py +0 -0
  337. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/file_migrations.py +0 -0
  338. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_athena.py +0 -0
  339. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/test_presto.py +0 -0
  340. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/transactions.py +0 -0
  341. {sustained-2.24.2 → sustained-2.26.0}/tests/integration/writes.py +0 -0
  342. {sustained-2.24.2 → sustained-2.26.0}/tests/test_aio_pool.py +0 -0
  343. {sustained-2.24.2 → sustained-2.26.0}/tests/test_ambiguous_columns.py +0 -0
  344. {sustained-2.24.2 → sustained-2.26.0}/tests/test_builder_edge_cases.py +0 -0
  345. {sustained-2.24.2 → sustained-2.26.0}/tests/test_builder_ergonomics.py +0 -0
  346. {sustained-2.24.2 → sustained-2.26.0}/tests/test_builder_robustness.py +0 -0
  347. {sustained-2.24.2 → sustained-2.26.0}/tests/test_dialect.py +0 -0
  348. {sustained-2.24.2 → sustained-2.26.0}/tests/test_etl_statements.py +0 -0
  349. {sustained-2.24.2 → sustained-2.26.0}/tests/test_execution.py +0 -0
  350. {sustained-2.24.2 → sustained-2.26.0}/tests/test_execution_safety.py +0 -0
  351. {sustained-2.24.2 → sustained-2.26.0}/tests/test_expressions.py +0 -0
  352. {sustained-2.24.2 → sustained-2.26.0}/tests/test_functions.py +0 -0
  353. {sustained-2.24.2 → sustained-2.26.0}/tests/test_having_builder.py +0 -0
  354. {sustained-2.24.2 → sustained-2.26.0}/tests/test_method_names.py +0 -0
  355. {sustained-2.24.2 → sustained-2.26.0}/tests/test_migration_guards.py +0 -0
  356. {sustained-2.24.2 → sustained-2.26.0}/tests/test_model.py +0 -0
  357. {sustained-2.24.2 → sustained-2.26.0}/tests/test_model_features.py +0 -0
  358. {sustained-2.24.2 → sustained-2.26.0}/tests/test_mysql_compiler.py +0 -0
  359. {sustained-2.24.2 → sustained-2.26.0}/tests/test_order_by_builder.py +0 -0
  360. {sustained-2.24.2 → sustained-2.26.0}/tests/test_parameterization.py +0 -0
  361. {sustained-2.24.2 → sustained-2.26.0}/tests/test_pool.py +0 -0
  362. {sustained-2.24.2 → sustained-2.26.0}/tests/test_postgres_compiler.py +0 -0
  363. {sustained-2.24.2 → sustained-2.26.0}/tests/test_select_clause_builder.py +0 -0
  364. {sustained-2.24.2 → sustained-2.26.0}/tests/test_stub_parity.py +0 -0
  365. {sustained-2.24.2 → sustained-2.26.0}/tests/test_upsert.py +0 -0
  366. {sustained-2.24.2 → sustained-2.26.0}/tests/test_view_filter.py +0 -0
  367. {sustained-2.24.2 → sustained-2.26.0}/tests/test_where_builder.py +0 -0
@@ -0,0 +1,481 @@
1
+ # Changelog
2
+
3
+ ## 2.26.0
4
+
5
+ ### Added
6
+
7
+ - `sustained impact`, `Migrator.impact()`, and `AsyncMigrator.impact()` report, for each statement a run would apply, the tables it locks, what the lock blocks, the work it does (catalog change, scan, row writes, index build, or rewrite), and how long the lock lasts, with the safer form of the statement where one exists. `--json` prints the report as one object, and the guide is [Statement impact](https://sustained.tbmh.org/impact).
8
+ - The impact analysis covers PostgreSQL, InnoDB tables on MySQL 8.0.19 and later and MariaDB 10.6 and later, SQL Server 2012 and later, SQLite 3.35 and later, and DuckDB 1.0 and later, and reads the server's version, settings, table sizes, and schema so that blocking work on a large table is `danger`. `sustained.impact.analyze(statements, dialect)` analyzes statements without a connection, and a statement the analysis cannot read is reported as unknown, never as safe. Each statement is read against what the earlier statements of the run did, such as a table created, renamed, or attached as a partition, a domain created, or a `ROLLBACK`, and a fact the server read missed, such as a table's size or its partitions, is named in a finding and counted by the guards as unknown.
9
+ - `sustained plan` lists the statements with a `warn` or `danger` finding in an `impact` section, and `sustained script --annotate` and `script(direction, annotate=True)` on either migrator print each statement's impact above it as SQL comments.
10
+ - The guards `max_blocking()`, `no_rewrite()`, `lock_timeout_required()`, and `no_unknown_impact()` in `sustained.guards` block statements by their analyzed impact instead of their text. `up()` and `sustained migrate` put each statement's impact on `MigrationStatement.impact` for custom guards, and print each `danger` finding on stderr when no configured guard reads impact.
11
+ - `sustained rehearse --trace` and `rehearse(trace=True)` on either migrator run each statement on its own on PostgreSQL, MySQL, MariaDB, and SQL Server, record the locks, rewrites, and index builds the server shows, and report each difference from the prediction as an `impact.mismatch` finding. On MySQL and MariaDB the scratch rehearsal also finds the `ALGORITHM` and `LOCK` clause the server accepts.
12
+ - `sustained impact --live`, `impact(live=True)`, and `preflight()` on either migrator list the sessions a run's statements would wait behind on the server now, and the transactions open at least `--older-than` seconds, 60 by default. `up(preflight="warn")` prints them before any migration applies, `up(preflight="refuse")` raises `PreflightBlocked` instead, and `sustained migrate --preflight warn|refuse` does the same and exits 5 on `PreflightBlocked`.
13
+ - `assert_algorithm=True` on MySQL and MariaDB, on `plan()`, `up()`, `rehearse()`, and `impact()` of either migrator and on `autogenerate()`, writes the predicted `ALGORITHM` and `LOCK` clause on each generated statement predicted to run `INSTANT` or with `LOCK=NONE`, so the server refuses them instead of running a slower algorithm or taking a stronger lock. The CLI takes `--assert-algorithm` and the config module's `assert_algorithm`.
14
+ - `online=True` on `autogenerate_migrations()`, and with `models` on `up()`, `rehearse()`, `impact()`, and `preflight()`, splits a PostgreSQL diff into migration `<id>`, which changes only the catalog in one transaction, and `<id>_online`, which runs outside a transaction to build indexes concurrently, run backfills, and validate constraints, and runs again from its first statement after a failure. On MySQL and MariaDB it does what `assert_algorithm=True` does; the CLI takes `--online` and the config module's `online`, and `plan_migrations()` on either migrator returns the generated migrations as a list.
15
+ - `rehearse(lock_timeout=seconds)` on either migrator, and the config module's `rehearsal_lock_timeout`, fail a rehearsal whose statement waits longer than that for a lock.
16
+ - `impact()` and `preflight()` on either migrator take the diff options `up()` takes. On SQLite, `exact_counts=True` and `--exact-counts` count each table's rows and read its bytes from `dbstat` instead of estimating them.
17
+
18
+ ### Changed
19
+
20
+ - `index_must_be_concurrent()` passes `CREATE INDEX ... ON ONLY`, which builds no index and cannot take `CONCURRENTLY`, so a partitioned table can be indexed without blocking writes.
21
+ - Error messages name the engine, such as "The live preflight does not cover SQLite.", where they named the `Dialects` member.
22
+
23
+ ### Fixed
24
+
25
+ - The destructive labels and the `no_drops()`, `no_table_rewrite()`, `index_must_be_concurrent()`, and `no_lock_without_timeout()` guards read comments and whitespace as each engine does, so a statement such as `DROP/**/TABLE t` is labelled. `destructive_statements()` takes a `dialect`.
26
+ - A new table's foreign key to a column or unique index that the same diff adds to an existing table is added after them, where the generated migration used to fail.
27
+ - The PostgreSQL schema read recognizes an invalid index, such as one a failed `CREATE INDEX CONCURRENTLY` leaves, and a constraint that is not validated, and the diff builds the index again or emits `VALIDATE CONSTRAINT`. The partitions of a partitioned table no longer read as extra tables.
28
+
29
+ ## 2.25.0
30
+
31
+ ### Added
32
+
33
+ - `record_scratch_rehearsal(results)` on either migrator writes every rehearsal row a passing `rehearse(scratch=True)` proved onto the real database, and `run_outcome(applied, run)` returns the rehearsal outcome recorded for a run.
34
+ - `Migrator.read_schema(models)` reads the schema once for `plan()` and `autogenerate()`, which take it as `snapshot`, and `sustained plan` reads the schema once instead of up to three times.
35
+ - The `--json` commands print one object with an `error` key when a run fails, and a failed `migrate` lists the migrations it applied.
36
+ - A SQL migration file honours `DELIMITER <token>` lines, so a MySQL trigger or procedure body loads from a file.
37
+ - `AsyncAdapter.session()` keeps a block's statements on one database session, and `AsyncAdapter.autocommit_scope()` runs a non-transactional migration with the driver's transaction control off. A custom adapter that opens a new session per statement has to override `session()`.
38
+ - `MigrationStatement.destructive`, `sustained.types.ColumnReference`, and type stubs that accept `raw()` columns in `where()`, `having()`, `select()`, `orderBy()`, and `groupBy()`.
39
+ - The schema read records more of the catalog, including each object's own spelling, schemas, collations, `ON UPDATE`, `AUTO_INCREMENT`, and SQLite's unnamed checks, triggers, and views.
40
+
41
+ ### Changed
42
+
43
+ - A column or table name string must be an identifier path, `*`, `table.*`, or a call on one column such as `COUNT(*)`, and every identifier in it is quoted, so `orderBy(request.args["sort"])` can no longer put SQL into the statement. Any other string raises `ValueError`; write it with `QueryBuilder.raw()`.
44
+ - `whereIn()`, `whereExists()`, `havingIn()`, `havingExists()`, and their NOT and OR forms raise `ValueError` for a string argument, and `in_()` and `not_in()` raise it for a `str` or `bytes` argument. A subquery written as SQL needs `QueryBuilder.raw()`.
45
+ - An INSERT, UPDATE, or DELETE raises `ValueError` when the builder has a clause the write does not render, such as `orderBy()`, `limit()`, a join, or a CTE.
46
+ - Every alias, CTE name included, is quoted through `quote_alias()`, and a dotted alias raises `ValueError`.
47
+ - `up()`, `down()`, `down_to()`, `baseline()`, `repair()`, and `record_rehearsal()` raise `ValueError` inside an open `transaction()` or `async_transaction()` block, since their commits also committed the caller's statements.
48
+ - `down()` raises `MigrationError` while a failed attempt is on record, a down step that fails without a rollback marks its row as failed, and `on_error` fires when `down()` fails.
49
+ - A tracking table read that fails for a reason other than a missing table raises the driver's error, where it read as a database with no history.
50
+ - The migration checksum counts splitting or joining statements as an edit, rows written by earlier releases still match, and `repair()` rewrites them in the current format.
51
+ - A narrowing type change, such as `numeric(18,6)` to `numeric(18,2)`, is labelled destructive, and the destructive scan matches more forms of column drops and DELETE. The diff refuses to remove a value from a MySQL enum column.
52
+ - The default dialect quotes identifiers in DDL, so a table or column named `order` no longer breaks CREATE TABLE or the SQLite rebuild. The generated SQL changes, so a rehearsal row recorded for a generated migration no longer matches.
53
+ - The asyncpg adapter reads `%%` in a statement as one `%` sign, as psycopg does.
54
+
55
+ ### Fixed
56
+
57
+ - The package imports on Python 3.9 to 3.11, where `up(models=...)`, `plan`, `drift`, and the async migrator raised `SyntaxError`.
58
+ - Async transactions: a DuckDB transaction stays on one session, an async rehearsal counts as an open transaction, a failed commit in `async_transaction()` rolls the block back, and `AsyncMigrator` runs a non-transactional migration with transaction control off. An async rehearsal on DuckDB or SQLite no longer applies its migrations for real.
59
+ - The migration lock scope rolls the session back before it unlocks, so a failed non-transactional migration on Postgres no longer blocks every other migrator. `baseline()` refuses a migration with a failed row, and `AsyncMigrator.status()`, `statuses()`, and `validate()` no longer create the tracking table.
60
+ - A cancelled `acquire()` or `release()` keeps the pool slot, and both pools wake a waiter when a slot frees.
61
+ - Query rendering: `to_sql()` doubles literal `%` signs on Postgres and MySQL, dates, timestamps, decimals, and bytes render as SQL literals, and a many-to-many `leftJoinRelated()` keeps rows with no link row. On MSSQL, `first()` uses `TOP 1`, a nested CTE moves into the top-level WITH clause, string literals take the `N` prefix, and `MOD()` renders as `%`.
62
+ - Schema diff: the SQLite rebuild quotes every name and keeps collations, checks, UNIQUE constraints, and triggers, enum CHECKs on SQLite and SQL Server are diffed, and foreign key targets and actions are read on MySQL, MariaDB, and SQL Server. Drops follow the tables that point at them, DuckDB and SQL Server lift indexes and defaults across a column change, DuckDB plans converge, and SQL Server reads column lengths and precision.
63
+ - The SQL file splitter keeps semicolons inside strings, quoted identifiers, comments, and dollar-quoted bodies, a rehearsal records its rows in one transaction, `script("down")` renders a generated migration from its tracking row, and a 200,000-row insert on SQLite takes half the time.
64
+
65
+ ## 2.24.2
66
+
67
+ ### Fixed
68
+
69
+ - A second join through the same many-to-many relation names its link table `<alias>_<link table>`, so two aliased joins through one relation no longer render the same link table twice. A repeated join through a link table without an alias raises `ValueError`.
70
+ - Every query builder method name matches without regard to case or underscores, as the documentation described, so `where_ilike`, `WHERE_IN`, and `COUNT()` resolve.
71
+
72
+ ## 2.24.1
73
+
74
+ ### Fixed
75
+
76
+ - A non-transactional migration on a sqlite3 connection opened with `connect(factory=...)` runs outside the implicit transaction, so the rebuild's foreign key pragmas take effect.
77
+ - A schema read on Presto or Trino no longer raises when two schemas have a table of the same name.
78
+ - A statement given the pool inside `async_transaction(pool)` runs on the block's adapter, where it ran outside the transaction, and a nested block opens a savepoint.
79
+ - `ConnectionPool.release()` rolls back every connection it takes back, and `AsyncConnectionPool.release()` keeps an adapter whose rollback raised when it answers `SELECT 1`, so a DuckDB connection no longer keeps an open transaction or loses an in-memory database.
80
+ - A rehearsal leaves a migration with `transactional=False` out of the run and reports `up_ok` as `None`, so a run that contains one can pass.
81
+ - The model registry keeps the newest class after a module reload, the async Postgres schema read survives a missing view, a MySQL or SQL Server column change restates the column's current default and comment, and the rehearsal keys compute each checksum once.
82
+
83
+ ## 2.24.0
84
+
85
+ ### Added
86
+
87
+ - `Migration(transactional=False)`, or a `-- sustained: no transaction` comment in a SQL file, runs a migration outside a transaction, which `CREATE INDEX CONCURRENTLY` needs. A failure leaves the earlier statements applied; clean up and run `repair()`.
88
+ - `AsyncMigrator` gains `script()`, `plan()`, and `drift()`, and its `up()` and `rehearse()` take `models` and the diff options.
89
+ - `AsyncConnectionPool` opens adapters from an async factory up to `max_size` and works with `Model.bind_async()`. Statements run through `pool.scope()`.
90
+ - Guards receive each statement as a `MigrationStatement`, which names its migration and whether it runs in a transaction, and `no_lock_without_timeout()` scopes a `SET LOCAL lock_timeout` to its own migration.
91
+ - Check constraints are read on MySQL, MariaDB, SQL Server, and DuckDB, so a declared `Check` diffs there, and DuckDB enum types are read from `duckdb_types()`.
92
+ - `crossJoin()` accepts a table on its own.
93
+
94
+ ### Changed
95
+
96
+ - Every parameter after `allow_out_of_order` on `up()` of either migrator is keyword-only.
97
+ - `Migration` raises `ValueError` for a checksum given on SQL, statement-list, or ddl steps, which hash themselves. Run `repair()` if a stored row no longer matches.
98
+ - `down()` refuses a migration whose checksum no longer matches its tracking row unless given `allow_changed=True` or `sustained down --allow-changed`, and refuses a revert count below 1.
99
+ - A run that includes `DELETE FROM`, `DROP VIEW`, `DROP MATERIALIZED VIEW`, `DROP DATABASE`, or `DROP SCHEMA ... CASCADE` needs a passing rehearsal row, or `--unrehearsed`, before `migrate` applies it.
100
+ - `no_lock_without_timeout()` reads the run in order, so a `SET lock_timeout` written after an `ALTER TABLE` no longer excuses it.
101
+ - An undeclared check on MySQL, MariaDB, SQL Server, or DuckDB is a diff note instead of a refusal, and the Postgres, SQL Server, and DuckDB schema reads cover the connection's schema and the schemas the models declare.
102
+
103
+ ### Fixed
104
+
105
+ - Query rendering: a `Subquery` binds its values as parameters, nested expressions render for the query's dialect, identifiers escape their quote character, and `on()` validates its operator. Raw SQL no longer counts a `?` inside a string literal, and an offset with no limit runs on SQLite.
106
+ - Every cursor closes when its statement finishes, which stops pyodbc and MySQL "commands out of sync" errors; `close()` joins the `Cursor` protocol, so a test double needs it.
107
+ - Transactions and pools: `transaction()` belongs to the thread that opened it, a query given a pool inside `transaction(pool)` runs on the pinned connection, `ConnectionPool.release()` rolls back and drops a connection that fails a probe, and `async_transaction()` works on psycopg2. A rehearsal on DuckDB rolls back its DDL.
108
+ - Migrations: both migrators raise `MigrationError` when the advisory lock is refused, `script()`, `status()`, `pending()`, `validate()`, and `plan` no longer create the tracking table, and `down()` checks the whole revert window before it reverts anything. `up(target=A)` followed by `up(target=B)` no longer asks for a rehearsal it already proved.
109
+ - A result set that repeats a column name raises `AmbiguousColumns`, attribute access for a column the row does not have raises, and a string model reference resolves through the module that declares the relation.
110
+ - Schema diff: views stay out of the schema read, a MySQL or SQL Server column change keeps NOT NULL, the default, the identity, and the comment, new tables are created in dependency order, and a nullability drift no longer regenerates the type. Presto and Trino raise `DialectError` for a table rebuild, and a SQLite rebuild of a referenced table runs with foreign keys off.
111
+ - The destructive scan labels `DELETE FROM`, `DROP VIEW`, `DROP MATERIALIZED VIEW`, `DROP DATABASE`, and `DROP SCHEMA ... CASCADE`, the statement splitter handles a semicolon followed by a comment, and a misnamed migration file raises. `plan --json` includes a custom guard's verdicts.
112
+
113
+ ## 2.23.1
114
+
115
+ ### Fixed
116
+
117
+ - Athena parameterized queries run, with `?` as the placeholder; set `pyathena.paramstyle = "qmark"` (pyathena 3 or later). Parameters are sent as strings through the new `prepare_execution` hook, which `to_sql()` output needs when you execute it yourself.
118
+ - Athena DDL quotes identifiers with backticks, every string column renders `STRING` for Iceberg tables without drifting, and introspection reads only the connection's schema.
119
+
120
+ ## 2.23.0
121
+
122
+ ### Added
123
+
124
+ - Every column definition takes a `comment`, which introspection reads back and the diff compares, and `set_column_comment` sets one in a hand-written migration. Athena refuses a change after `CREATE TABLE`.
125
+
126
+ ## 2.22.0
127
+
128
+ ### Added
129
+
130
+ - `Binary()` in `sustained.schema` declares a bytes column in each dialect's type, such as `BYTEA` on Postgres and `VARBINARY(MAX)` on SQL Server.
131
+ - The **Covered** column on the [support page](https://sustained.tbmh.org/support) maps each claim to a module in `tests/integration`, and a contract test fails when they disagree.
132
+ - `matrix.py` gains a `<name>-latest` target per container database, which runs the newest release the vendor supports.
133
+
134
+ ### Fixed
135
+
136
+ - `transaction()` nests with `SAVE TRANSACTION` on SQL Server and raises `DialectError` for nesting on DuckDB, and it works on the duckdb driver, so a failed multi-statement migration rolls back. sqlite3 connections in legacy transaction control get an explicit `BEGIN`.
137
+ - A NOT NULL change with a `backfill` runs on DuckDB, plain indexes are read back on MySQL, MariaDB, SQL Server, and DuckDB, and `union()` and the other set operations run on SQLite.
138
+
139
+ ## 2.21.0
140
+
141
+ ### Added
142
+
143
+ - `Enum(*values, name=...)` in `sustained.schema` declares a column over a named, ordered value list, and migration generation adds values to it. Removing or reordering values refuses with a rebuild recipe.
144
+ - `Check(name, expression)` and `ForeignKey(name, columns, references, on_delete=, on_update=)` declare named table constraints in `tableConstraints`, and migration generation adds them, with changed and undeclared ones gated by `allow_drops`.
145
+ - `sustained.ddl` provides typed steps for hand-written migrations that render for the dialect at run time, so one migration serves every dialect. A migration whose up step is all reversible ddl steps derives its down step.
146
+ - The Postgres schema read covers foreign key targets, non-unique indexes, varchar lengths, precision and scale, CHECK expressions, and enum values.
147
+
148
+ ### Changed
149
+
150
+ - `DROP TYPE` and `DROP CONSTRAINT` count as destructive: `plan` labels them, `no_drops()` blocks them, and they need a rehearsal.
151
+ - Declared table constraints raise `DialectError` on Presto and Athena.
152
+
153
+ ## 2.20.0
154
+
155
+ ### Changed
156
+
157
+ - The row a rehearsal writes is called a rehearsal row, where it was a receipt: `rehearsal_key()` replaces `receipt_key()`, and the outcome constants are `REHEARSAL_PASSED`, `REHEARSAL_FAILED`, and `REHEARSAL_OVERRIDE`. Stored rows are untouched, so a rehearsal recorded by an earlier version still opens the gate.
158
+ - `sustained rehearse` prints `rehearsal row recorded` and `rehearsal row not recorded`, where it printed `receipt`.
159
+ - `receipt_key()`, `RECEIPT_PASSED`, `RECEIPT_FAILED`, and `RECEIPT_OVERRIDE` are deprecated. Each raises a `DeprecationWarning` naming its replacement and goes away in 3.0.
160
+
161
+ ## 2.19.0
162
+
163
+ ### Added
164
+
165
+ - A written [support policy](https://sustained.tbmh.org/support) that lists the supported databases and Python versions and states the deprecation path and what each version number promises. `sync_support.py` renders it from `support.json`.
166
+ - An integration suite in `tests/integration/` that runs the migration lifecycle and a query on every supported server, and `matrix.py`, which starts the servers from `docker/compose.yaml` and prints one line per server.
167
+ - `Compiler.compile_create_table()` renders the whole CREATE TABLE statement.
168
+
169
+ ### Fixed
170
+
171
+ - The tracking table works on MySQL, where the `generated` column name is reserved, and on SQL Server, which has no `CREATE TABLE IF NOT EXISTS`. The MySQL advisory lock is taken on MariaDB.
172
+
173
+ ## 2.18.0
174
+
175
+ ### Added
176
+
177
+ - `Dialects.MYSQL` compiles for MySQL and MariaDB, with upserts, `AUTO_INCREMENT`, a `GET_LOCK` advisory lock, and `for_update()` with `SKIP LOCKED` and `NOWAIT` on MySQL 8.0. `rehearse()` needs a scratch database there, and `returning()`, `STRING_AGG`, and a unique key or literal default on a `Text()` or `Json()` column raise.
178
+ - Column types render in the spelling `information_schema` reports back, so tables created from models do not drift on MySQL, and MariaDB `Json()` columns read back as JSON.
179
+ - `Compiler.supports_transactional_ddl()` reports whether rolled-back DDL goes away, and `Compiler.inline_references()` reports whether an inline `REFERENCES` clause creates a foreign key.
180
+
181
+ ### Changed
182
+
183
+ - Schema reading moved from `sustained.autogenerate` to `sustained.introspect`, and every name still imports from the old module.
184
+ - Default and type normalization treat `current_timestamp()` and `CURRENT_TIMESTAMP`, and the `*TEXT` variants, as equal.
185
+
186
+ ## 2.17.0
187
+
188
+ ### Added
189
+
190
+ - The tracking table stores a generated migration's up and down statements, so `down()` can revert a migration the process never diffed.
191
+ - `up(unrehearsed=True)` records the waiver as a rehearsal row with the outcome `override`, which never opens the gate for a later run.
192
+ - `migrate` exits 4 when a run that removes data has no passing rehearsal.
193
+ - `Migrator.drift()` and `SchemaDiff.outstanding()` take `ignore_changed_columns`.
194
+
195
+ ### Fixed
196
+
197
+ - A SQLite table rebuild keeps undeclared columns, their data, and hand-made indexes, and an index on an expression no longer crashes introspection.
198
+ - Rehearsals with models and rename hints run, blame only the down steps that failed, apply the generated migration before the repeatables, and record rows that cover `migrate --target`.
199
+ - `no_lock_without_timeout()` is Postgres only and fires only on a `SET` statement, and the `plan` footer no longer says `run: sustained rehearse` after a rehearsal recorded its row.
200
+ - Filter and write values accept `datetime`, `date`, `Decimal`, and `bytes` under a strict type checker, and a failed savepoint no longer reuses a savepoint name.
201
+
202
+ ## 2.16.1
203
+
204
+ ### Added
205
+
206
+ - `Connection`, `Cursor`, `Binding`, `SqlValue`, `RowValue`, `ColumnDescription`, and `RelationTree` in `sustained.types`, and driver protocols in `sustained.aio`, so any DB-API 2.0 driver connection type-checks.
207
+
208
+ ### Changed
209
+
210
+ - Roughly 180 `Any` annotations were replaced with specific types, and migration callbacks and callable steps are typed.
211
+ - `in_()` and `not_in()` accept any sequence of values.
212
+
213
+ ## 2.16.0
214
+
215
+ ### Added
216
+
217
+ - The query builder is generic over its model: `Show.query()` is a `QueryBuilder[Show]`, so `run()` is `List[Show]` and `first()` is `Optional[Show]`. The select list does not narrow the result type.
218
+ - `WriteBuilder[Model]` types the result of `insert()`, `update()`, `delete()`, and the other writes, as the row count or the RETURNING rows.
219
+
220
+ ### Changed
221
+
222
+ - Argument positions that take any query are declared `QueryBuilder[Any]`.
223
+
224
+ ## 2.15.0
225
+
226
+ ### Added
227
+
228
+ - Guards, rules over the statements a run would apply that return `block` or `warn` verdicts. Both migrators and the CLI config module take `guards=[...]`, and `up()` raises `GuardBlocked` before any statement runs.
229
+ - `sustained.guards` provides `no_drops()`, `index_must_be_concurrent()`, `no_table_rewrite()`, `no_lock_without_timeout()`, and `max_statements(n)`.
230
+ - `sustained plan` prints a `guards` section, and `plan` and `migrate` exit 3 when a guard blocks a statement.
231
+ - Both migrators take `callbacks=Callbacks(...)`, so `before_migrate`, `after_migrate`, and `on_error` reach library callers.
232
+
233
+ ### Changed
234
+
235
+ - `after_migrate` fires before the post-run drift report, and `rehearse` does not enforce guards.
236
+
237
+ ## 2.14.0
238
+
239
+ ### Added
240
+
241
+ - A passing rehearsal writes a rehearsal row, and `up()` refuses a run that removes data, such as a DROP TABLE, a column drop, or a TRUNCATE, unless a passing rehearsal covers that exact run. `up(unrehearsed=True)` and `sustained migrate --unrehearsed` apply anyway, and the refusal raises `RehearsalRequired`.
242
+ - `record_rehearsal()`, `rehearsal_outcome()`, and `rehearsed()` on both migrators, and a `rehearsal_table` option on both constructors and the CLI config module.
243
+
244
+ ### Changed
245
+
246
+ - `rehearse()` returns a `Rehearsal`, a `list` that also has `key`, `recorded`, and `ok`, and `rehearse(scratch=True)` records nothing through the API.
247
+ - `sustained plan` prints `run: sustained rehearse` when a pending migration removes data, and Sustained's own tables are left out of every diff.
248
+
249
+ ## 2.13.0
250
+
251
+ ### Added
252
+
253
+ - `Migrator.up(models=[...])` diffs the models against the database and applies the generated migration after everything else pending, and `sustained migrate` and `sustained rehearse` pass the config module's `models`.
254
+ - `Migrator.rehearse(models=[...])` rehearses the generated migration and reports whether the models landed and the down steps reversed. `sustained rehearse --json` prints the result.
255
+ - `Migrator.drift(models)` returns what the models still ask for, and `sustained migrate` reports it after a run.
256
+
257
+ ### Changed
258
+
259
+ - `plan --json` reports each pending migration's statements with a destructive flag, and `PendingSummary.statements` became `PendingSummary.sql`.
260
+ - The generated diff no longer refuses objects the models do not declare; `ignore_undeclared=False` restores the refusal.
261
+ - `Migrator.sync()` is deprecated in favour of `up(models=[...])` and goes away in 3.0.
262
+
263
+ ## 2.12.0
264
+
265
+ ### Added
266
+
267
+ - `withGraphFetched()` takes a dotted path such as `'shows.tickets'` and loads each level in one batched query.
268
+ - Async eager loading covers link-table relations and dotted paths, and `async_transaction()` nests through savepoints.
269
+
270
+ ### Fixed
271
+
272
+ - The type stubs describe the join and clause methods the runtime accepts, `LENGTH` keeps its registration, and `IntrospectedTable` and `FunctionMetadata` instances no longer share mappings.
273
+
274
+ ## 2.11.0
275
+
276
+ ### Changed
277
+
278
+ - A targeted `up()` does not run the repeatables; the next full `up()` runs them.
279
+ - The destructive scan labels a column drop written without the COLUMN keyword.
280
+
281
+ ### Fixed
282
+
283
+ - `repair()` keeps the stored checksum of a changed repeatable, so the change still re-runs it.
284
+ - A malformed placeholder such as `${my-key}` raises `ValueError` naming the file.
285
+ - `rehearse()` reads the migration state inside the advisory lock, and `AsyncMigrator.rehearse()` refuses a connection in autocommit mode.
286
+
287
+ ## 2.10.0
288
+
289
+ ### Added
290
+
291
+ - `sustained rehearse` and `Migrator.rehearse()` apply every pending migration, run the down steps, and roll it all back, on SQLite, Postgres, and DuckDB. A config module's `get_rehearsal_connection()` sends the rehearsal to a scratch database.
292
+ - Config module callbacks `before_migrate`, `after_migrate`, and `on_error` around `sustained migrate`.
293
+ - `Migrator.connection` and `AsyncMigrator.adapter` properties.
294
+
295
+ ### Changed
296
+
297
+ - A failing statement, a connection that will not open, or a directory that will not load prints as an error line on the command line instead of a traceback.
298
+
299
+ ## 2.9.0
300
+
301
+ ### Added
302
+
303
+ - `sustained plan` shows the pending migrations, the problems `validate` would report, and the drift against the config module's `models`, and exits 0 when current, 2 when pending, and 1 on problems.
304
+ - `sustained.analysis` labels drops and truncates in the plan as destructive.
305
+ - `--json` on `status`, `validate`, and `plan` prints one JSON object.
306
+
307
+ ## 2.8.0
308
+
309
+ ### Added
310
+
311
+ - Repeatable migrations, a `<id>.repeat.sql` file or `Migration(id, up, repeatable=True)`, run again whenever their checksum changes.
312
+ - `statuses()` on both migrators returns each migration's state, which `sustained status` prints.
313
+ - `${key}` placeholders in SQL migration files, filled from `load_migrations(placeholders=...)` or the config module.
314
+
315
+ ### Changed
316
+
317
+ - `pending()` also returns repeatables whose checksum changed.
318
+
319
+ ## 2.7.0
320
+
321
+ ### Added
322
+
323
+ - `load_migrations(directory)` loads migrations from `<id>.up.sql` and `<id>.down.sql` files.
324
+ - `baseline(target)` on both migrators records migrations up to the target as applied without running them.
325
+ - `Migrator.plan(models, ...)` returns the migration `sync()` would generate without applying it.
326
+ - The `sustained` command and `python -m sustained` drive a `Migrator` from a config module, with `status`, `migrate`, `down`, `validate`, `repair`, `script`, and `baseline`.
327
+
328
+ ## 2.6.0
329
+
330
+ ### Added
331
+
332
+ - The tracking table records a sequence number, a checksum, the execution time, and a success flag, and older tables upgrade in place.
333
+ - `validate()` on both migrators raises `MigrationError` on failed attempts, unknown applied ids, checksum mismatches, and out-of-order pending migrations, and `repair()` removes failed attempts and rewrites checksums.
334
+ - Migration runs take an exclusive advisory lock on Postgres and MSSQL.
335
+
336
+ ### Changed
337
+
338
+ - `up()` validates before running. `validate=False` skips the checks, and `allow_out_of_order=True` accepts a pending migration ordered before an applied one.
339
+
340
+ ### Fixed
341
+
342
+ - The tracking table upgrade keeps a recorded failed attempt after an interrupted earlier upgrade.
343
+
344
+ ## 2.5.0
345
+
346
+ ### Added
347
+
348
+ - The AWS Athena dialect, `Dialects.ATHENA`, with MERGE upserts on Iceberg tables and Athena's type spellings.
349
+ - `TableOptions(location, partitioned_by, properties)` declares a model's storage clauses on Athena.
350
+ - Migrations on engines without transactions, and a tracking table created without constraints where the engine has none.
351
+
352
+ ## 2.4.0
353
+
354
+ ### Added
355
+
356
+ - Introspection reads primary keys, unique constraints, foreign keys, column defaults, and indexes.
357
+ - Type and nullability changes generate migrations, with a table rebuild on SQLite, and `renames` and `table_renames` hints generate renames instead of a drop and an add.
358
+ - Declared indexes via `Index`, and a `backfill` on a column definition for NOT NULL adds.
359
+ - `migration_sql()` and `Migrator.script()` render the SQL a run would execute.
360
+ - `AsyncMigrator` runs migrations on an `AsyncAdapter`.
361
+
362
+ ## 2.3.0
363
+
364
+ ### Added
365
+
366
+ - `diff_schema()` compares the models with the live database, and `autogenerate()` builds a `Migration` from the difference.
367
+ - `Migrator.sync(models)` diffs, generates, and applies in one call, and `Migrator.down_to(id)` reverts to a target.
368
+
369
+ ## 2.2.0
370
+
371
+ ### Added
372
+
373
+ - Typed column definitions in `tableColumns`, and `create_table_sql()`, `create_table()`, and `drop_table()` on models.
374
+ - A migration runner with ordered `Migration` objects, up and down steps, a tracking table, and targets.
375
+ - `ConnectionPool`, a thread-safe pool of DB-API connections that `Model.bind()` and every execution entry point accept.
376
+ - Async execution with `arun()`, `afirst()`, and `ato_dicts()` through `DbApiAsyncAdapter`, `AiosqliteAdapter`, and `AsyncpgAdapter`, with `Model.bind_async()` and `async_transaction()`.
377
+
378
+ ## 2.1.0
379
+
380
+ ### Added
381
+
382
+ - Typed predicates such as `Model.c.age > 21` and `col()`, combined with `&`, `|`, and `~`, and `whereRaw()` and `havingRaw()` with `?` value markers.
383
+ - `Model.transaction()` with savepoint nesting, and `set_statement_listener()` for the SQL, parameters, and duration of every statement.
384
+ - Upserts with `insert().onConflict(cols).merge()` and `.ignore()`, `insert_from()`, and `create_table_as()`.
385
+ - `to_dicts()`, `to_df()`, and `to_arrow()` result formats.
386
+ - The DuckDB dialect.
387
+ - Recursive CTEs, `intersect()` and `except_()`, `distinctOn()`, `groupByRollup()`, `groupByCube()`, `groupByGroupingSets()`, `qualify()`, `for_update()`, `total()`, `cursor_page()`, and `explain()`.
388
+ - Eager loading of `ManyToManyRelation`, and per-dialect function names such as `NOW()` as `GETDATE()` on MSSQL.
389
+
390
+ ## 2.0.0
391
+
392
+ ### Added
393
+
394
+ - `to_sql()` returns the statement and its parameters, and `insert()`, `update()`, `delete()`, and `returning()` build writes.
395
+ - `Model.bind(connection)`, `run()`, and `first()` run queries on any DB-API 2.0 connection and return model instances.
396
+ - `withGraphFetched()` eager loading for HasMany, HasOne, and BelongsToOne relations.
397
+ - Class-level column access such as `User.id`, an optional `columns` declaration, and a model registry for string `modelClass` references.
398
+ - `clone()`, `page()`, snake_case aliases for every camelCase method, window function arguments and frames, the `'column AS alias'` shorthand, and `IS NULL` for a comparison with `None`.
399
+
400
+ ### Changed
401
+
402
+ - **Breaking:** String arguments to `select_func()` and the dynamic function methods are column references; wrap literal values in `Literal()`.
403
+ - **Breaking:** `where()` and `having()` validate operators against an allowlist, `update()` and `delete()` require a `where()` clause, and an empty `whereIn()` list or two CTEs with one alias and different definitions raise `ValueError`.
404
+ - **Breaking:** `top()` raises `DialectError` outside MSSQL, and on MSSQL `limit()` and `offset()` raise it without an `ORDER BY`.
405
+ - **Breaking:** `whereILike()` compiles to `LOWER(col) LIKE LOWER(pattern)` without native ILIKE, booleans render as `TRUE` and `FALSE` (`1` and `0` on MSSQL), and column references in WHERE, HAVING, and GROUP BY are quoted per dialect.
406
+ - **Breaking:** `with_()` requires a `QueryBuilder` and renders it when the query renders.
407
+ - **Breaking:** The minimum Python version is 3.9.
408
+
409
+ ### Fixed
410
+
411
+ - `copy.copy`, `copy.deepcopy`, and `pickle` work on builders and models.
412
+ - Union members keep their `ORDER BY` and `LIMIT`, nested CTEs move into the top-level `WITH` clause, `GROUP BY` and MSSQL quote a dotted path as a path, and Presto renders `OFFSET` before `LIMIT`.
413
+ - `limit()`, `offset()`, and `top()` reject booleans and negative numbers.
414
+
415
+ ## 1.1.0
416
+
417
+ ### Added
418
+
419
+ - A query builds once and compiles for a chosen dialect: the default, PostgreSQL, MSSQL, or Presto.
420
+ - `select_func()` and the function methods raise `DialectError` at build time for a function the dialect does not support.
421
+
422
+ ## 1.0.2
423
+
424
+ ### Fixed
425
+
426
+ - The type stubs declare the join methods they were missing.
427
+
428
+ ## 1.0.1
429
+
430
+ ### Fixed
431
+
432
+ - Installs include the type stubs.
433
+
434
+ ## 1.0.0
435
+
436
+ ### Added
437
+
438
+ - Type stub files for the builders, packaged with the distribution.
439
+
440
+ ## 0.0.7
441
+
442
+ ### Added
443
+
444
+ - `USING` clauses on joins, and subqueries in JOIN ON clauses.
445
+ - LIKE and NULL checks in WHERE and HAVING clauses.
446
+
447
+ ## 0.0.6
448
+
449
+ ### Added
450
+
451
+ - `distinct()` on the query builder.
452
+ - `avg()`, `min()`, and `max()` aggregate methods.
453
+ - `Func`, for calling any SQL function, and `Subquery`, for embedding a subquery in the SELECT list.
454
+
455
+ ## 0.0.5
456
+
457
+ ### Added
458
+
459
+ - A select clause builder with fluent methods for complex select lists.
460
+ - `Column`, for marking a value as a column reference rather than a literal.
461
+ - The expression classes export from the top-level package.
462
+
463
+ ### Changed
464
+
465
+ - `Any` annotations across the codebase were replaced with specific types.
466
+
467
+ ## 0.0.4
468
+
469
+ ### Added
470
+
471
+ - ORDER BY, LIMIT, TOP, and OFFSET clauses.
472
+ - UNION queries.
473
+ - Subqueries in FROM expressions and in conditional clauses, and EXISTS and BETWEEN conditions.
474
+
475
+ ## 0.0.3
476
+
477
+ First tagged release. SELECT query building with joins, WHERE, GROUP BY, and HAVING clauses, relation-aware joins through `joinRelated()`, and a builder split into per-clause components.
478
+
479
+ ### Fixed
480
+
481
+ - `andWhere()` could start a WHERE clause on its own.
@@ -1,14 +1,33 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sustained
3
- Version: 2.24.2
4
- Summary: A Python query builder and schema migration tool, inspired by Objection.js
5
- Project-URL: Homepage, https://github.com/wetherc/sustained
3
+ Version: 2.26.0
4
+ Summary: A Python ORM, SQL query builder, and schema migration tool, inspired by Objection.js and Knex
5
+ Project-URL: Homepage, https://sustained.tbmh.org
6
+ Project-URL: Documentation, https://sustained.tbmh.org
7
+ Project-URL: Changelog, https://sustained.tbmh.org/changelog
8
+ Project-URL: Source, https://github.com/wetherc/sustained
6
9
  Project-URL: Issues, https://github.com/wetherc/sustained/issues
7
10
  Author-email: Christopher Wetherill <git@tbmh.org>
8
11
  License-Expression: MIT
9
12
  License-File: LICENSE
13
+ Keywords: aiosqlite,asyncpg,athena,database migrations,duckdb,knex,mariadb,migration tool,mssql,mysql,objection.js,orm,postgresql,presto,query builder,schema migrations,sql,sql server,sqlite
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Intended Audience :: Developers
10
17
  Classifier: Operating System :: OS Independent
11
18
  Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Programming Language :: SQL
27
+ Classifier: Topic :: Database
28
+ Classifier: Topic :: Database :: Front-Ends
29
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
+ Classifier: Typing :: Typed
12
31
  Requires-Python: >=3.9
13
32
  Description-Content-Type: text/markdown
14
33
 
@@ -44,11 +63,12 @@ With Sustained, schema migrations are:
44
63
  - **Generated from your models.** `Migrator.up(models=[...])` diffs the live database against your models, generates the migration, records it, and applies it. If you run it again after a model change, it applies only the difference. `down()` rolls it back.
45
64
  - **Rehearsed before they land.** `sustained rehearse` applies every pending migration, runs the downgrade steps to test the revert plan, and rolls the whole thing back. If a migration fails to run or fails to reverse, the rehearsal reports it before the migration reaches the real schema. A config module can send the rehearsal to a scratch database instead.
46
65
  - **Planned in one screen.** `sustained plan` shows your pending migrations, outstanding problems that `validate` would report, and any gap between your models and the database's current state.
66
+ - **Read for their impact on a live database.** `sustained impact` reports, for each statement a run would apply on PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, or DuckDB, the tables it locks, whether reads or writes wait, whether it scans, rebuilds an index, or rewrites the table, and how long the lock is held. It flags a lock held until commit across a later backfill, and names the safer form, such as `CREATE INDEX CONCURRENTLY` or `NOT VALID` followed by `VALIDATE CONSTRAINT`. [Statement impact](https://sustained.tbmh.org/impact) lists the rules.
47
67
  - **Verified before every run.** Sustained keeps a per-database tracking table that records a sequence number, a SHA-256 checksum, an apply timestamp, execution time, and a success flag per migration. `validate` refuses a run when a migration was edited after it ran, arrives out of order, or left a failed attempt behind. After manual corrections, `repair` deletes failed runs from the tracking table and updates script checksums.
48
68
  - **Gated by custom safeguards.** A guard is a built-in rule such as `no_drops()`, `index_must_be_concurrent()`, or `max_statements(n)`, or a function you write. Guards read every statement a run would apply and block the deployment when a rule fails.
49
69
  - **Safe by default.** Drops need an explicit `allow_drops=True`, renames need explicit hints, and `NOT NULL` changes need a `default` or `backfill`, so destructive changes never run by default.
50
70
  - **Written your way.** Migrations can be Python `Migration` objects, `<id>.up.sql` and `<id>.down.sql` files with `${placeholders}`, or `<id>.repeat.sql` files for views and seed data, which re-run whenever their contents change.
51
- - **Ready for deploys.** The `sustained` console script runs `plan`, `status`, `rehearse`, `migrate`, `down`, `validate`, `repair`, `script`, and `baseline`, with exit codes for pipelines and `before_migrate`, `after_migrate`, and `on_error` callbacks around a run. Concurrent deploys queue on an advisory lock. `baseline` adopts a database whose schema already matches the migrations. `script('up')` renders the SQL for a DBA instead of running it. `AsyncMigrator` does all of this on an async adapter.
71
+ - **Ready for deploys.** The `sustained` console script runs `plan`, `impact`, `status`, `rehearse`, `migrate`, `down`, `validate`, `repair`, `script`, and `baseline`, with exit codes for pipelines and `before_migrate`, `after_migrate`, and `on_error` callbacks around a run. Concurrent deploys queue on an advisory lock. `baseline` adopts a database whose schema already matches the migrations. `script('up')` renders the SQL for a DBA instead of running it. `AsyncMigrator` does all of this on an async adapter.
52
72
 
53
73
 
54
74
  ## Installation
@@ -30,11 +30,12 @@ With Sustained, schema migrations are:
30
30
  - **Generated from your models.** `Migrator.up(models=[...])` diffs the live database against your models, generates the migration, records it, and applies it. If you run it again after a model change, it applies only the difference. `down()` rolls it back.
31
31
  - **Rehearsed before they land.** `sustained rehearse` applies every pending migration, runs the downgrade steps to test the revert plan, and rolls the whole thing back. If a migration fails to run or fails to reverse, the rehearsal reports it before the migration reaches the real schema. A config module can send the rehearsal to a scratch database instead.
32
32
  - **Planned in one screen.** `sustained plan` shows your pending migrations, outstanding problems that `validate` would report, and any gap between your models and the database's current state.
33
+ - **Read for their impact on a live database.** `sustained impact` reports, for each statement a run would apply on PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, or DuckDB, the tables it locks, whether reads or writes wait, whether it scans, rebuilds an index, or rewrites the table, and how long the lock is held. It flags a lock held until commit across a later backfill, and names the safer form, such as `CREATE INDEX CONCURRENTLY` or `NOT VALID` followed by `VALIDATE CONSTRAINT`. [Statement impact](https://sustained.tbmh.org/impact) lists the rules.
33
34
  - **Verified before every run.** Sustained keeps a per-database tracking table that records a sequence number, a SHA-256 checksum, an apply timestamp, execution time, and a success flag per migration. `validate` refuses a run when a migration was edited after it ran, arrives out of order, or left a failed attempt behind. After manual corrections, `repair` deletes failed runs from the tracking table and updates script checksums.
34
35
  - **Gated by custom safeguards.** A guard is a built-in rule such as `no_drops()`, `index_must_be_concurrent()`, or `max_statements(n)`, or a function you write. Guards read every statement a run would apply and block the deployment when a rule fails.
35
36
  - **Safe by default.** Drops need an explicit `allow_drops=True`, renames need explicit hints, and `NOT NULL` changes need a `default` or `backfill`, so destructive changes never run by default.
36
37
  - **Written your way.** Migrations can be Python `Migration` objects, `<id>.up.sql` and `<id>.down.sql` files with `${placeholders}`, or `<id>.repeat.sql` files for views and seed data, which re-run whenever their contents change.
37
- - **Ready for deploys.** The `sustained` console script runs `plan`, `status`, `rehearse`, `migrate`, `down`, `validate`, `repair`, `script`, and `baseline`, with exit codes for pipelines and `before_migrate`, `after_migrate`, and `on_error` callbacks around a run. Concurrent deploys queue on an advisory lock. `baseline` adopts a database whose schema already matches the migrations. `script('up')` renders the SQL for a DBA instead of running it. `AsyncMigrator` does all of this on an async adapter.
38
+ - **Ready for deploys.** The `sustained` console script runs `plan`, `impact`, `status`, `rehearse`, `migrate`, `down`, `validate`, `repair`, `script`, and `baseline`, with exit codes for pipelines and `before_migrate`, `after_migrate`, and `on_error` callbacks around a run. Concurrent deploys queue on an advisory lock. `baseline` adopts a database whose schema already matches the migrations. `script('up')` renders the SQL for a DBA instead of running it. `AsyncMigrator` does all of this on an async adapter.
38
39
 
39
40
 
40
41
  ## Installation
@@ -0,0 +1,78 @@
1
+ [build-system]
2
+ requires = [ "hatchling",]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sustained"
7
+ version = "2.26.0"
8
+ description = "A Python ORM, SQL query builder, and schema migration tool, inspired by Objection.js and Knex"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ keywords = [ "orm", "sql", "query builder", "schema migrations", "database migrations", "migration tool", "objection.js", "knex", "postgresql", "mysql", "mariadb", "sql server", "mssql", "sqlite", "duckdb", "athena", "presto", "asyncpg", "aiosqlite",]
12
+ classifiers = [
13
+ "Development Status :: 5 - Production/Stable",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: SQL",
24
+ "Operating System :: OS Independent",
25
+ "Framework :: AsyncIO",
26
+ "Topic :: Database",
27
+ "Topic :: Database :: Front-Ends",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Typing :: Typed",
30
+ ]
31
+ license = "MIT"
32
+ license-files = [ "LICENSE",]
33
+ [[project.authors]]
34
+ name = "Christopher Wetherill"
35
+ email = "git@tbmh.org"
36
+
37
+ [project.scripts]
38
+ sustained = "sustained.cli:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://sustained.tbmh.org"
42
+ Documentation = "https://sustained.tbmh.org"
43
+ Changelog = "https://sustained.tbmh.org/changelog"
44
+ Source = "https://github.com/wetherc/sustained"
45
+ Issues = "https://github.com/wetherc/sustained/issues"
46
+
47
+ [tool.isort]
48
+ profile = "black"
49
+
50
+ # Ruff only checks function size and complexity; black and isort own the
51
+ # formatting. Each limit sits at the worst function in src/ today, so a
52
+ # new function cannot be worse than any we already have. Lower a limit
53
+ # when the function that sets it is broken up.
54
+ [tool.ruff.lint]
55
+ select = [ "C901", "PLR0912", "PLR0913", "PLR0915",]
56
+
57
+ [tool.ruff.lint.mccabe]
58
+ max-complexity = 27
59
+
60
+ [tool.ruff.lint.pylint]
61
+ max-branches = 26
62
+ max-args = 15
63
+ max-statements = 93
64
+
65
+ [tool.mypy]
66
+ mypy_path = "src"
67
+ explicit_package_bases = true
68
+ strict = true
69
+ check_untyped_defs = true
70
+ exclude = [ "build/", "venv/",]
71
+
72
+ [tool.hatch.build]
73
+ dev-mode-dirs = [ "src",]
74
+
75
+ # Hatchling reads only .gitignore, so a build in a working tree would pack
76
+ # files excluded some other way, such as through .git/info/exclude.
77
+ [tool.hatch.build.targets.sdist]
78
+ include = [ "/src", "/tests", "/support.json", "/CHANGELOG.md",]