dblift 2.0.1__py3-none-any.whl

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 (408) hide show
  1. api/__init__.py +12 -0
  2. api/_cli_support.py +27 -0
  3. api/_client_factory.py +387 -0
  4. api/_client_operations.py +258 -0
  5. api/_engine_config.py +70 -0
  6. api/async_client.py +138 -0
  7. api/client.py +974 -0
  8. api/events.py +579 -0
  9. api/migrations.py +5 -0
  10. api/py.typed +0 -0
  11. cli/__init__.py +10 -0
  12. cli/_command_handlers.py +94 -0
  13. cli/_config_helpers.py +599 -0
  14. cli/_constants.py +33 -0
  15. cli/_output.py +179 -0
  16. cli/_parser_setup.py +416 -0
  17. cli/db_utils.py +518 -0
  18. cli/extensions.py +56 -0
  19. cli/handlers/__init__.py +11 -0
  20. cli/handlers/_shared.py +132 -0
  21. cli/handlers/baseline.py +18 -0
  22. cli/handlers/clean.py +17 -0
  23. cli/handlers/import_flyway.py +17 -0
  24. cli/handlers/info.py +147 -0
  25. cli/handlers/migrate.py +46 -0
  26. cli/handlers/repair.py +18 -0
  27. cli/handlers/undo.py +32 -0
  28. cli/handlers/validate.py +29 -0
  29. cli/main.py +442 -0
  30. config/__init__.py +10 -0
  31. config/_credential_masking.py +71 -0
  32. config/_subclasses/__init__.py +12 -0
  33. config/_subclasses/cosmosdb_config.py +94 -0
  34. config/_subclasses/db2_config.py +56 -0
  35. config/_subclasses/dummy_config.py +19 -0
  36. config/_subclasses/mysql_config.py +91 -0
  37. config/_subclasses/oracle_config.py +48 -0
  38. config/_subclasses/postgresql_config.py +56 -0
  39. config/_subclasses/sqlite_config.py +88 -0
  40. config/_subclasses/sqlserver_config.py +116 -0
  41. config/_url_builder_mixin.py +119 -0
  42. config/config_builder.py +335 -0
  43. config/database_config.py +615 -0
  44. config/dblift_config.py +1139 -0
  45. config/errors.py +5 -0
  46. config/secrets/__init__.py +16 -0
  47. config/secrets/_cache.py +37 -0
  48. config/secrets/_provider_base.py +27 -0
  49. config/secrets/_registry.py +117 -0
  50. config/secrets/_resolver.py +83 -0
  51. config/secrets/_secrets_config.py +60 -0
  52. config/validation_config.py +124 -0
  53. core/__init__.py +59 -0
  54. core/constants.py +72 -0
  55. core/dialect_boundary.py +275 -0
  56. core/exceptions.py +55 -0
  57. core/features.py +28 -0
  58. core/introspection/__init__.py +23 -0
  59. core/introspection/_column_enricher.py +279 -0
  60. core/introspection/_partition_enricher.py +184 -0
  61. core/introspection/_schema_orchestrator.py +241 -0
  62. core/introspection/_utils.py +22 -0
  63. core/introspection/_vendor_property_applier.py +70 -0
  64. core/introspection/base_introspector.py +889 -0
  65. core/introspection/capability_matrix.py +276 -0
  66. core/introspection/extractors/__init__.py +25 -0
  67. core/introspection/extractors/base_extractor.py +142 -0
  68. core/introspection/extractors/column_extractor.py +230 -0
  69. core/introspection/extractors/constraint_extractor.py +598 -0
  70. core/introspection/extractors/index_extractor.py +344 -0
  71. core/introspection/extractors/misc_extractor.py +730 -0
  72. core/introspection/extractors/procedure_extractor.py +821 -0
  73. core/introspection/extractors/sequence_extractor.py +138 -0
  74. core/introspection/extractors/table_extractor.py +467 -0
  75. core/introspection/extractors/trigger_extractor.py +170 -0
  76. core/introspection/extractors/view_extractor.py +323 -0
  77. core/introspection/introspector_factory.py +136 -0
  78. core/introspection/result.py +204 -0
  79. core/introspection/schema_introspector.py +16 -0
  80. core/introspection/vendor_queries_base.py +924 -0
  81. core/introspection/vendor_queries_factory.py +153 -0
  82. core/introspection/vendor_queries_protocols.py +225 -0
  83. core/introspection/version_detector.py +279 -0
  84. core/logger/__init__.py +448 -0
  85. core/logger/_base.py +145 -0
  86. core/logger/_factory.py +239 -0
  87. core/logger/_formatters.py +120 -0
  88. core/logger/_levels.py +112 -0
  89. core/logger/_multi.py +135 -0
  90. core/logger/_null.py +66 -0
  91. core/logger/console.py +195 -0
  92. core/logger/formatters/__init__.py +30 -0
  93. core/logger/formatters/_format_diff_meta.py +117 -0
  94. core/logger/formatters/_format_diff_object.py +195 -0
  95. core/logger/formatters/_format_diff_routine.py +208 -0
  96. core/logger/formatters/_format_diff_table.py +250 -0
  97. core/logger/formatters/_formatter_impl.py +567 -0
  98. core/logger/formatters/diff_utils.py +396 -0
  99. core/logger/formatters/factory.py +104 -0
  100. core/logger/formatters/formatter.py +12 -0
  101. core/logger/formatters/htmlformatter.py +876 -0
  102. core/logger/formatters/jsonformatter.py +978 -0
  103. core/logger/log.py +782 -0
  104. core/logger/results.py +904 -0
  105. core/migration/__init__.py +33 -0
  106. core/migration/_type_match.py +111 -0
  107. core/migration/clean_summary.py +86 -0
  108. core/migration/commands/__init__.py +6 -0
  109. core/migration/commands/_script_events.py +18 -0
  110. core/migration/commands/base_command.py +1025 -0
  111. core/migration/commands/baseline_command.py +96 -0
  112. core/migration/commands/clean_command.py +342 -0
  113. core/migration/commands/import_flyway_command.py +190 -0
  114. core/migration/commands/info_command.py +208 -0
  115. core/migration/commands/migrate_command.py +853 -0
  116. core/migration/commands/repair_command.py +769 -0
  117. core/migration/commands/undo_command.py +869 -0
  118. core/migration/commands/validate_command.py +90 -0
  119. core/migration/encoding.py +90 -0
  120. core/migration/executor/__init__.py +12 -0
  121. core/migration/executor/execution_engine.py +1196 -0
  122. core/migration/executor/migration_executor.py +439 -0
  123. core/migration/executor/migration_helpers.py +142 -0
  124. core/migration/executor/placeholder_manager.py +77 -0
  125. core/migration/executor/transaction_policy.py +63 -0
  126. core/migration/executors/__init__.py +20 -0
  127. core/migration/executors/base_executor.py +179 -0
  128. core/migration/executors/executor_factory.py +251 -0
  129. core/migration/executors/python_executor.py +379 -0
  130. core/migration/executors/sql_executor.py +273 -0
  131. core/migration/formats/__init__.py +14 -0
  132. core/migration/formats/format_detector.py +204 -0
  133. core/migration/formats/migration_format.py +67 -0
  134. core/migration/history/__init__.py +1 -0
  135. core/migration/history/migration_history_manager.py +384 -0
  136. core/migration/journals/__init__.py +14 -0
  137. core/migration/journals/migration_journal.py +618 -0
  138. core/migration/migration.py +775 -0
  139. core/migration/placeholders/__init__.py +1 -0
  140. core/migration/placeholders/placeholder_service.py +84 -0
  141. core/migration/rules/__init__.py +1 -0
  142. core/migration/rules/migration_rules.py +145 -0
  143. core/migration/scripting/__init__.py +1 -0
  144. core/migration/scripting/migration_script_manager.py +721 -0
  145. core/migration/scripting/undo_script_generator/__init__.py +24 -0
  146. core/migration/scripting/undo_script_generator/_ddl_reversers.py +485 -0
  147. core/migration/scripting/undo_script_generator/_dml_reversers.py +319 -0
  148. core/migration/scripting/undo_script_generator/_extractors.py +511 -0
  149. core/migration/scripting/undo_script_generator/_generator.py +310 -0
  150. core/migration/scripting/undo_script_generator/_helpers.py +532 -0
  151. core/migration/scripting/undo_script_generator/_models.py +18 -0
  152. core/migration/scripting/undo_script_generator/_reversers.py +864 -0
  153. core/migration/sql/__init__.py +1 -0
  154. core/migration/sql/execution_statement.py +38 -0
  155. core/migration/sql/sql_analyzer.py +806 -0
  156. core/migration/sql/sql_execution_service.py +431 -0
  157. core/migration/sql/statement_splitter.py +74 -0
  158. core/migration/state/__init__.py +5 -0
  159. core/migration/state/migration_data_service.py +603 -0
  160. core/migration/state/migration_display_state.py +36 -0
  161. core/migration/state/migration_formatter.py +215 -0
  162. core/migration/state/migration_state.py +166 -0
  163. core/migration/state/migration_state_manager.py +830 -0
  164. core/migration/state/migration_state_service.py +244 -0
  165. core/migration/ui/__init__.py +15 -0
  166. core/migration/ui/data_collector.py +926 -0
  167. core/migration/ui/display_formatters.py +251 -0
  168. core/migration/ui/migration_analyzer.py +259 -0
  169. core/migration/ui/migration_ui.py +362 -0
  170. core/migration/ui/table_renderer.py +250 -0
  171. core/migration/version_utils.py +94 -0
  172. core/normalization/__init__.py +35 -0
  173. core/normalization/data_type_normalizer.py +5 -0
  174. core/normalization/dependency_resolver.py +435 -0
  175. core/normalization/identifier_normalizer.py +297 -0
  176. core/normalization/object_orderer.py +230 -0
  177. core/normalization/type_constants.py +154 -0
  178. core/normalization/type_mapper.py +220 -0
  179. core/normalization/type_mappings.py +89 -0
  180. core/normalization/type_normalizer.py +245 -0
  181. core/seams/__init__.py +1 -0
  182. core/seams/event_listeners.py +26 -0
  183. core/seams/introspection.py +18 -0
  184. core/sql_model/__init__.py +60 -0
  185. core/sql_model/_base_parse_result.py +491 -0
  186. core/sql_model/_base_sql_column.py +194 -0
  187. core/sql_model/_base_sql_constraint.py +223 -0
  188. core/sql_model/_base_sql_object.py +233 -0
  189. core/sql_model/_base_sql_statement.py +100 -0
  190. core/sql_model/base.py +43 -0
  191. core/sql_model/constraint_validator.py +469 -0
  192. core/sql_model/database_link.py +151 -0
  193. core/sql_model/dialect.py +482 -0
  194. core/sql_model/event.py +155 -0
  195. core/sql_model/extension.py +109 -0
  196. core/sql_model/foreign_data_wrapper.py +113 -0
  197. core/sql_model/foreign_server.py +138 -0
  198. core/sql_model/index.py +279 -0
  199. core/sql_model/linked_server.py +170 -0
  200. core/sql_model/module.py +112 -0
  201. core/sql_model/package.py +134 -0
  202. core/sql_model/partition.py +180 -0
  203. core/sql_model/procedure.py +321 -0
  204. core/sql_model/sequence.py +208 -0
  205. core/sql_model/synonym.py +143 -0
  206. core/sql_model/table.py +1106 -0
  207. core/sql_model/table_canonicalizer.py +45 -0
  208. core/sql_model/table_options.py +73 -0
  209. core/sql_model/trigger.py +310 -0
  210. core/sql_model/user_defined_type.py +151 -0
  211. core/sql_model/view.py +429 -0
  212. core/sql_model/view_options.py +95 -0
  213. core/sql_parser/__init__.py +5 -0
  214. core/sql_parser/_partition_handler.py +113 -0
  215. core/sql_parser/_sqlglot_builders.py +634 -0
  216. core/sql_parser/base_statement_parser.py +356 -0
  217. core/sql_parser/base_tokenizer.py +473 -0
  218. core/sql_parser/common/__init__.py +1 -0
  219. core/sql_parser/common/base_parser.py +598 -0
  220. core/sql_parser/dialects/__init__.py +5 -0
  221. core/sql_parser/dialects/base_config.py +125 -0
  222. core/sql_parser/enhanced_regex_parser.py +453 -0
  223. core/sql_parser/hybrid_parser.py +1106 -0
  224. core/sql_parser/parser_context.py +89 -0
  225. core/sql_parser/parser_factory.py +215 -0
  226. core/sql_parser/parser_interface.py +93 -0
  227. core/sql_parser/sqlglot_parser.py +544 -0
  228. core/sql_parser/tokens.py +48 -0
  229. core/sql_parser/unified_regex_parser.py +644 -0
  230. core/sql_validator/__init__.py +5 -0
  231. core/sql_validator/_checksum_validator.py +341 -0
  232. core/sql_validator/_flyway_compatibility.py +301 -0
  233. core/sql_validator/_migration_filter.py +221 -0
  234. core/sql_validator/_sql_syntax_validator.py +168 -0
  235. core/sql_validator/_strict_mode_validator.py +136 -0
  236. core/sql_validator/migration_validator.py +923 -0
  237. core/state/sql_script_formatter.py +144 -0
  238. core/state/sql_statement.py +41 -0
  239. core/utils/__init__.py +1 -0
  240. core/utils/database_url_parser.py +104 -0
  241. core/utils/metadata_helpers.py +83 -0
  242. core/utils/row_access.py +192 -0
  243. core/utils/string_utils.py +21 -0
  244. core/utils/url_masking.py +24 -0
  245. db/__init__.py +21 -0
  246. db/base_provider.py +266 -0
  247. db/base_quirks.py +1620 -0
  248. db/constants.py +5 -0
  249. db/error.py +344 -0
  250. db/error_handler.py +349 -0
  251. db/exceptions.py +11 -0
  252. db/native_connection_manager.py +70 -0
  253. db/object_naming.py +39 -0
  254. db/plugins/__init__.py +9 -0
  255. db/plugins/base_history_manager.py +616 -0
  256. db/plugins/base_locking_manager.py +154 -0
  257. db/plugins/base_query_executor.py +253 -0
  258. db/plugins/base_schema_operations.py +288 -0
  259. db/plugins/base_snapshot_manager.py +172 -0
  260. db/plugins/base_undo_manager.py +126 -0
  261. db/plugins/cosmosdb/__init__.py +12 -0
  262. db/plugins/cosmosdb/cosmosdb/__init__.py +19 -0
  263. db/plugins/cosmosdb/cosmosdb/_sdk.py +28 -0
  264. db/plugins/cosmosdb/cosmosdb/connection_manager.py +247 -0
  265. db/plugins/cosmosdb/cosmosdb/history_manager.py +427 -0
  266. db/plugins/cosmosdb/cosmosdb/locking_manager.py +465 -0
  267. db/plugins/cosmosdb/cosmosdb/query_executor.py +1518 -0
  268. db/plugins/cosmosdb/cosmosdb/schema_operations.py +416 -0
  269. db/plugins/cosmosdb/introspection/__init__.py +3 -0
  270. db/plugins/cosmosdb/parser/__init__.py +5 -0
  271. db/plugins/cosmosdb/parser/cosmosdb_regex_parser.py +64 -0
  272. db/plugins/cosmosdb/plugin.py +19 -0
  273. db/plugins/cosmosdb/provider.py +364 -0
  274. db/plugins/cosmosdb/quirks.py +240 -0
  275. db/plugins/cosmosdb/sdk_translator/__init__.py +39 -0
  276. db/plugins/cosmosdb/sdk_translator/_executor.py +422 -0
  277. db/plugins/cosmosdb/sdk_translator/_executors.py +356 -0
  278. db/plugins/cosmosdb/sdk_translator/_parsing.py +53 -0
  279. db/plugins/cosmosdb/sdk_translator/_script_generation.py +141 -0
  280. db/plugins/cosmosdb/sdk_translator/_translator.py +129 -0
  281. db/plugins/cosmosdb/sdk_translator/_translators.py +795 -0
  282. db/plugins/db2/__init__.py +11 -0
  283. db/plugins/db2/db2/__init__.py +11 -0
  284. db/plugins/db2/db2/history_manager.py +317 -0
  285. db/plugins/db2/db2/locking_manager.py +558 -0
  286. db/plugins/db2/db2/schema_operations.py +985 -0
  287. db/plugins/db2/introspection/__init__.py +3 -0
  288. db/plugins/db2/parser/__init__.py +9 -0
  289. db/plugins/db2/parser/db2_regex_parser.py +668 -0
  290. db/plugins/db2/parser/parser_config.py +1147 -0
  291. db/plugins/db2/plugin.py +22 -0
  292. db/plugins/db2/provider.py +420 -0
  293. db/plugins/db2/quirks.py +382 -0
  294. db/plugins/db2/sqlalchemy_url.py +62 -0
  295. db/plugins/mariadb/__init__.py +13 -0
  296. db/plugins/mariadb/plugin.py +21 -0
  297. db/plugins/mariadb/provider.py +40 -0
  298. db/plugins/mariadb/quirks.py +50 -0
  299. db/plugins/mysql/__init__.py +11 -0
  300. db/plugins/mysql/introspection/__init__.py +1 -0
  301. db/plugins/mysql/mysql/__init__.py +11 -0
  302. db/plugins/mysql/mysql/history_manager.py +384 -0
  303. db/plugins/mysql/mysql/locking_manager.py +371 -0
  304. db/plugins/mysql/mysql/schema_operations.py +555 -0
  305. db/plugins/mysql/parser/__init__.py +1 -0
  306. db/plugins/mysql/parser/mysql_regex_parser.py +531 -0
  307. db/plugins/mysql/parser/mysql_statement_parser.py +255 -0
  308. db/plugins/mysql/parser/mysql_tokenizer.py +460 -0
  309. db/plugins/mysql/parser/parser_config.py +670 -0
  310. db/plugins/mysql/plugin.py +22 -0
  311. db/plugins/mysql/provider.py +329 -0
  312. db/plugins/mysql/quirks.py +540 -0
  313. db/plugins/mysql/sqlalchemy_url.py +67 -0
  314. db/plugins/oracle/__init__.py +11 -0
  315. db/plugins/oracle/introspection/__init__.py +3 -0
  316. db/plugins/oracle/introspection/oracle_utils.py +86 -0
  317. db/plugins/oracle/oracle/__init__.py +14 -0
  318. db/plugins/oracle/oracle/dbms_output.py +49 -0
  319. db/plugins/oracle/oracle/history_manager.py +444 -0
  320. db/plugins/oracle/oracle/locking_manager.py +436 -0
  321. db/plugins/oracle/oracle/schema_operations.py +1134 -0
  322. db/plugins/oracle/parser/__init__.py +13 -0
  323. db/plugins/oracle/parser/_comments.py +57 -0
  324. db/plugins/oracle/parser/_object_extractor.py +203 -0
  325. db/plugins/oracle/parser/_plsql_block.py +727 -0
  326. db/plugins/oracle/parser/_sqlplus.py +312 -0
  327. db/plugins/oracle/parser/_statement_splitter.py +233 -0
  328. db/plugins/oracle/parser/oracle_parser.py +297 -0
  329. db/plugins/oracle/parser/oracle_statement_parser.py +434 -0
  330. db/plugins/oracle/parser/oracle_tokenizer.py +251 -0
  331. db/plugins/oracle/parser/parser_config.py +330 -0
  332. db/plugins/oracle/parser/sqlplus_context.py +179 -0
  333. db/plugins/oracle/plugin.py +22 -0
  334. db/plugins/oracle/provider.py +736 -0
  335. db/plugins/oracle/quirks.py +743 -0
  336. db/plugins/oracle/sqlalchemy_url.py +65 -0
  337. db/plugins/postgresql/__init__.py +12 -0
  338. db/plugins/postgresql/_provider_query_executor.py +25 -0
  339. db/plugins/postgresql/introspection/__init__.py +3 -0
  340. db/plugins/postgresql/parser/__init__.py +10 -0
  341. db/plugins/postgresql/parser/parser_config.py +876 -0
  342. db/plugins/postgresql/parser/postgresql_regex_parser.py +691 -0
  343. db/plugins/postgresql/parser/postgresql_statement_parser.py +118 -0
  344. db/plugins/postgresql/parser/postgresql_tokenizer.py +325 -0
  345. db/plugins/postgresql/plugin.py +29 -0
  346. db/plugins/postgresql/postgresql/__init__.py +11 -0
  347. db/plugins/postgresql/postgresql/history_manager.py +231 -0
  348. db/plugins/postgresql/postgresql/locking_manager.py +310 -0
  349. db/plugins/postgresql/postgresql/schema_operations.py +690 -0
  350. db/plugins/postgresql/provider.py +248 -0
  351. db/plugins/postgresql/quirks.py +722 -0
  352. db/plugins/postgresql/sqlalchemy_url.py +65 -0
  353. db/plugins/sqlite/__init__.py +12 -0
  354. db/plugins/sqlite/introspection/__init__.py +3 -0
  355. db/plugins/sqlite/parser/__init__.py +9 -0
  356. db/plugins/sqlite/parser/parser_config.py +377 -0
  357. db/plugins/sqlite/parser/sqlite_regex_parser.py +338 -0
  358. db/plugins/sqlite/plugin.py +21 -0
  359. db/plugins/sqlite/provider.py +504 -0
  360. db/plugins/sqlite/quirks.py +96 -0
  361. db/plugins/sqlite/sqlalchemy_url.py +16 -0
  362. db/plugins/sqlite/sqlite/__init__.py +24 -0
  363. db/plugins/sqlite/sqlite/connection_manager.py +162 -0
  364. db/plugins/sqlite/sqlite/history_manager.py +233 -0
  365. db/plugins/sqlite/sqlite/locking_manager.py +216 -0
  366. db/plugins/sqlite/sqlite/query_executor.py +243 -0
  367. db/plugins/sqlite/sqlite/schema_operations.py +303 -0
  368. db/plugins/sqlserver/__init__.py +11 -0
  369. db/plugins/sqlserver/introspection/__init__.py +3 -0
  370. db/plugins/sqlserver/parser/__init__.py +9 -0
  371. db/plugins/sqlserver/parser/parser_config.py +604 -0
  372. db/plugins/sqlserver/parser/sqlserver_regex_parser.py +368 -0
  373. db/plugins/sqlserver/parser/sqlserver_statement_parser.py +173 -0
  374. db/plugins/sqlserver/parser/sqlserver_tokenizer.py +238 -0
  375. db/plugins/sqlserver/parser/tsql_batch_separator.py +14 -0
  376. db/plugins/sqlserver/plugin.py +22 -0
  377. db/plugins/sqlserver/provider.py +615 -0
  378. db/plugins/sqlserver/quirks.py +489 -0
  379. db/plugins/sqlserver/sqlalchemy_url.py +120 -0
  380. db/plugins/sqlserver/sqlserver/__init__.py +11 -0
  381. db/plugins/sqlserver/sqlserver/history_manager.py +382 -0
  382. db/plugins/sqlserver/sqlserver/locking_manager.py +328 -0
  383. db/plugins/sqlserver/sqlserver/schema_operations.py +524 -0
  384. db/provider_capabilities.py +85 -0
  385. db/provider_interfaces.py +347 -0
  386. db/provider_registry.py +623 -0
  387. db/sqlalchemy_provider.py +340 -0
  388. db/value_utils.py +10 -0
  389. dblift-2.0.1.dist-info/METADATA +1075 -0
  390. dblift-2.0.1.dist-info/RECORD +408 -0
  391. dblift-2.0.1.dist-info/WHEEL +5 -0
  392. dblift-2.0.1.dist-info/entry_points.txt +12 -0
  393. dblift-2.0.1.dist-info/licenses/LICENSE +201 -0
  394. dblift-2.0.1.dist-info/top_level.txt +6 -0
  395. integrations/__init__.py +30 -0
  396. integrations/django/__init__.py +1 -0
  397. integrations/django/_client.py +54 -0
  398. integrations/django/_engine.py +53 -0
  399. integrations/django/apps.py +14 -0
  400. integrations/django/checks.py +35 -0
  401. integrations/django/management/__init__.py +1 -0
  402. integrations/django/management/commands/__init__.py +1 -0
  403. integrations/django/management/commands/dblift_info.py +25 -0
  404. integrations/django/management/commands/dblift_migrate.py +24 -0
  405. integrations/django/management/commands/dblift_validate.py +24 -0
  406. integrations/fastapi.py +153 -0
  407. integrations/flask.py +82 -0
  408. integrations/opentelemetry.py +155 -0
api/_engine_config.py ADDED
@@ -0,0 +1,70 @@
1
+ """Derive DbliftConfig from an existing SQLAlchemy Engine (for from_sqlalchemy)."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, List, Optional, Union
5
+
6
+ from sqlalchemy.engine import Engine
7
+
8
+ from config import DbliftConfig
9
+ from config.errors import ConfigurationError
10
+ from db.provider_registry import ProviderRegistry
11
+
12
+
13
+ def config_from_engine(
14
+ engine: Engine,
15
+ *,
16
+ schema: Optional[str] = None,
17
+ migrations_dir: Optional[Union[str, Path, List[Union[str, Path]]]] = None,
18
+ ) -> DbliftConfig:
19
+ """Build a DbliftConfig from a SQLAlchemy Engine for runtime integration.
20
+
21
+ Validates that the engine's dialect is supported by a first-party (or
22
+ registered) provider. The resulting config can be passed to
23
+ DBLiftClient or used to construct a provider that re-uses the engine.
24
+
25
+ Raises:
26
+ ConfigurationError: if no matching dblift provider supports the
27
+ engine's dialect (user should install the matching extra).
28
+ """
29
+ url = engine.url.render_as_string(hide_password=False)
30
+ provider_cls = ProviderRegistry.get_provider_by_url(url)
31
+ if provider_cls is None:
32
+ raise ConfigurationError(
33
+ f"Unsupported SQLAlchemy dialect for dblift: {engine.url.drivername!r}. "
34
+ "Install the matching dblift extra (e.g. dblift[postgresql]) or "
35
+ "use DBLiftClient.from_config()."
36
+ )
37
+
38
+ # Derive canonical db type from the matched provider key if possible.
39
+ # Fall back to the URL scheme (canonicalized) — the config layer will
40
+ # also normalize on construction.
41
+ db_type = None
42
+ # ProviderRegistry keeps plugins by canonical key; try to reverse-lookup
43
+ # a stable name from the class if the registry exposes it, else use scheme.
44
+ try:
45
+ # Best-effort: many plugins register under the primary name.
46
+ # We canonicalize the scheme portion.
47
+ scheme = engine.url.drivername.split("+", 1)[0]
48
+ db_type = ProviderRegistry.canonical_dialect_name(scheme) or scheme
49
+ except Exception:
50
+ db_type = "postgresql" # lint: allow-dialect-string: safe fallback only; real type comes from URL scheme + ProviderRegistry.canonical_dialect_name above # noqa: E501
51
+
52
+ db_dict: dict[str, Any] = {"url": url, "type": db_type}
53
+ if schema is not None:
54
+ db_dict["schema"] = schema
55
+
56
+ payload: dict[str, Any] = {"database": db_dict}
57
+
58
+ config = DbliftConfig.from_dict(payload)
59
+
60
+ # ``migrations_dir`` accepts str / Path / list (matching the rest of the
61
+ # client API), but ``DbliftConfig.from_dict`` treats ``migrations.directory``
62
+ # as a plain string (os.path.isabs / startswith). Passing a Path or list into
63
+ # the dict would raise during construction, so normalize via the shared
64
+ # helper instead, which handles all three shapes (and multi-dir lists).
65
+ if migrations_dir is not None:
66
+ from api._client_factory import normalize_migrations_dirs
67
+
68
+ normalize_migrations_dirs(config, migrations_dir)
69
+
70
+ return config
api/async_client.py ADDED
@@ -0,0 +1,138 @@
1
+ """Async facade over :class:`DBLiftClient` for asyncio apps.
2
+
3
+ Each operation runs in one dedicated worker thread so the event loop is not
4
+ blocked and the wrapped synchronous client keeps thread affinity. This is not
5
+ native async DB I/O: the call occupies that worker thread. A per-instance
6
+ ``asyncio.Lock`` serializes operations because the underlying sync client holds
7
+ a single shared connection and is not safe for concurrent use.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from concurrent.futures import ThreadPoolExecutor
14
+ from functools import partial
15
+ from typing import Any
16
+
17
+ from api.client import DBLiftClient
18
+ from api.events import EventEmitter
19
+
20
+
21
+ class AsyncDBLiftClient:
22
+ """Async wrapper around a synchronous :class:`DBLiftClient`."""
23
+
24
+ def __init__(self, sync_client: DBLiftClient) -> None:
25
+ """Wrap an existing synchronous DBLift client."""
26
+ self._sync = sync_client
27
+ self._executor = ThreadPoolExecutor(
28
+ max_workers=1,
29
+ thread_name_prefix="dblift-async-client",
30
+ )
31
+ self._lock = asyncio.Lock()
32
+ self._closed = False
33
+
34
+ @classmethod
35
+ def from_sqlalchemy(cls, engine: Any = None, **kwargs: Any) -> "AsyncDBLiftClient":
36
+ """Create an async client from a SQLAlchemy engine or connection."""
37
+ return cls(DBLiftClient.from_sqlalchemy(engine, **kwargs))
38
+
39
+ @classmethod
40
+ def from_config(cls, config: Any, **kwargs: Any) -> "AsyncDBLiftClient":
41
+ """Create an async client from a DBLift config object."""
42
+ return cls(DBLiftClient.from_config(config, **kwargs))
43
+
44
+ @classmethod
45
+ def from_config_file(cls, config_path: str, **kwargs: Any) -> "AsyncDBLiftClient":
46
+ """Create an async client from a DBLift config file path."""
47
+ return cls(DBLiftClient.from_config_file(config_path, **kwargs))
48
+
49
+ @property
50
+ def events(self) -> EventEmitter:
51
+ """Return the wrapped sync client's event emitter."""
52
+ return self._sync.events
53
+
54
+ async def _run(self, fn: Any, *args: Any, **kwargs: Any) -> Any:
55
+ async with self._lock:
56
+ if self._closed:
57
+ raise RuntimeError("AsyncDBLiftClient is closed")
58
+ return await self._run_in_thread(fn, *args, **kwargs)
59
+
60
+ async def _run_in_thread(self, fn: Any, *args: Any, **kwargs: Any) -> Any:
61
+ loop = asyncio.get_running_loop()
62
+ worker = loop.run_in_executor(self._executor, partial(fn, *args, **kwargs))
63
+ cancelled = False
64
+ while True:
65
+ try:
66
+ result = await asyncio.shield(worker)
67
+ except asyncio.CancelledError:
68
+ cancelled = True
69
+ if worker.done():
70
+ raise
71
+ continue
72
+ except Exception:
73
+ if cancelled:
74
+ raise asyncio.CancelledError from None
75
+ raise
76
+ if cancelled:
77
+ raise asyncio.CancelledError
78
+ return result
79
+
80
+ async def migrate(self, *args: Any, **kwargs: Any) -> Any:
81
+ """Apply pending migrations without blocking the event loop."""
82
+ return await self._run(self._sync.migrate, *args, **kwargs)
83
+
84
+ async def info(self, *args: Any, **kwargs: Any) -> Any:
85
+ """Return migration status information without blocking the event loop."""
86
+ return await self._run(self._sync.info, *args, **kwargs)
87
+
88
+ async def validate(self, *args: Any, **kwargs: Any) -> Any:
89
+ """Validate migrations without blocking the event loop."""
90
+ return await self._run(self._sync.validate, *args, **kwargs)
91
+
92
+ async def undo(self, *args: Any, **kwargs: Any) -> Any:
93
+ """Undo migrations without blocking the event loop."""
94
+ return await self._run(self._sync.undo, *args, **kwargs)
95
+
96
+ async def generate_undo_script(self, *args: Any, **kwargs: Any) -> Any:
97
+ """Generate one undo script without blocking the event loop."""
98
+ return await self._run(self._sync.generate_undo_script, *args, **kwargs)
99
+
100
+ async def generate_undo_scripts(self, *args: Any, **kwargs: Any) -> Any:
101
+ """Generate undo scripts without blocking the event loop."""
102
+ return await self._run(self._sync.generate_undo_scripts, *args, **kwargs)
103
+
104
+ async def clean(self, *args: Any, **kwargs: Any) -> Any:
105
+ """Clean database objects without blocking the event loop."""
106
+ return await self._run(self._sync.clean, *args, **kwargs)
107
+
108
+ async def baseline(self, *args: Any, **kwargs: Any) -> Any:
109
+ """Create a baseline without blocking the event loop."""
110
+ return await self._run(self._sync.baseline, *args, **kwargs)
111
+
112
+ async def repair(self, *args: Any, **kwargs: Any) -> Any:
113
+ """Repair migration metadata without blocking the event loop."""
114
+ return await self._run(self._sync.repair, *args, **kwargs)
115
+
116
+ async def import_flyway(self, *args: Any, **kwargs: Any) -> Any:
117
+ """Import Flyway metadata without blocking the event loop."""
118
+ return await self._run(self._sync.import_flyway, *args, **kwargs)
119
+
120
+ async def aclose(self) -> None:
121
+ """Release resources held by the wrapped sync client."""
122
+ await self._exit(None, None, None)
123
+
124
+ async def _exit(self, exc_type: Any, exc: Any, tb: Any) -> None:
125
+ async with self._lock:
126
+ if self._closed:
127
+ return
128
+ try:
129
+ await self._run_in_thread(self._sync.__exit__, exc_type, exc, tb)
130
+ finally:
131
+ self._closed = True
132
+ self._executor.shutdown(wait=False)
133
+
134
+ async def __aenter__(self) -> "AsyncDBLiftClient":
135
+ return self
136
+
137
+ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
138
+ await self._exit(exc_type, exc, tb)