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.
- api/__init__.py +12 -0
- api/_cli_support.py +27 -0
- api/_client_factory.py +387 -0
- api/_client_operations.py +258 -0
- api/_engine_config.py +70 -0
- api/async_client.py +138 -0
- api/client.py +974 -0
- api/events.py +579 -0
- api/migrations.py +5 -0
- api/py.typed +0 -0
- cli/__init__.py +10 -0
- cli/_command_handlers.py +94 -0
- cli/_config_helpers.py +599 -0
- cli/_constants.py +33 -0
- cli/_output.py +179 -0
- cli/_parser_setup.py +416 -0
- cli/db_utils.py +518 -0
- cli/extensions.py +56 -0
- cli/handlers/__init__.py +11 -0
- cli/handlers/_shared.py +132 -0
- cli/handlers/baseline.py +18 -0
- cli/handlers/clean.py +17 -0
- cli/handlers/import_flyway.py +17 -0
- cli/handlers/info.py +147 -0
- cli/handlers/migrate.py +46 -0
- cli/handlers/repair.py +18 -0
- cli/handlers/undo.py +32 -0
- cli/handlers/validate.py +29 -0
- cli/main.py +442 -0
- config/__init__.py +10 -0
- config/_credential_masking.py +71 -0
- config/_subclasses/__init__.py +12 -0
- config/_subclasses/cosmosdb_config.py +94 -0
- config/_subclasses/db2_config.py +56 -0
- config/_subclasses/dummy_config.py +19 -0
- config/_subclasses/mysql_config.py +91 -0
- config/_subclasses/oracle_config.py +48 -0
- config/_subclasses/postgresql_config.py +56 -0
- config/_subclasses/sqlite_config.py +88 -0
- config/_subclasses/sqlserver_config.py +116 -0
- config/_url_builder_mixin.py +119 -0
- config/config_builder.py +335 -0
- config/database_config.py +615 -0
- config/dblift_config.py +1139 -0
- config/errors.py +5 -0
- config/secrets/__init__.py +16 -0
- config/secrets/_cache.py +37 -0
- config/secrets/_provider_base.py +27 -0
- config/secrets/_registry.py +117 -0
- config/secrets/_resolver.py +83 -0
- config/secrets/_secrets_config.py +60 -0
- config/validation_config.py +124 -0
- core/__init__.py +59 -0
- core/constants.py +72 -0
- core/dialect_boundary.py +275 -0
- core/exceptions.py +55 -0
- core/features.py +28 -0
- core/introspection/__init__.py +23 -0
- core/introspection/_column_enricher.py +279 -0
- core/introspection/_partition_enricher.py +184 -0
- core/introspection/_schema_orchestrator.py +241 -0
- core/introspection/_utils.py +22 -0
- core/introspection/_vendor_property_applier.py +70 -0
- core/introspection/base_introspector.py +889 -0
- core/introspection/capability_matrix.py +276 -0
- core/introspection/extractors/__init__.py +25 -0
- core/introspection/extractors/base_extractor.py +142 -0
- core/introspection/extractors/column_extractor.py +230 -0
- core/introspection/extractors/constraint_extractor.py +598 -0
- core/introspection/extractors/index_extractor.py +344 -0
- core/introspection/extractors/misc_extractor.py +730 -0
- core/introspection/extractors/procedure_extractor.py +821 -0
- core/introspection/extractors/sequence_extractor.py +138 -0
- core/introspection/extractors/table_extractor.py +467 -0
- core/introspection/extractors/trigger_extractor.py +170 -0
- core/introspection/extractors/view_extractor.py +323 -0
- core/introspection/introspector_factory.py +136 -0
- core/introspection/result.py +204 -0
- core/introspection/schema_introspector.py +16 -0
- core/introspection/vendor_queries_base.py +924 -0
- core/introspection/vendor_queries_factory.py +153 -0
- core/introspection/vendor_queries_protocols.py +225 -0
- core/introspection/version_detector.py +279 -0
- core/logger/__init__.py +448 -0
- core/logger/_base.py +145 -0
- core/logger/_factory.py +239 -0
- core/logger/_formatters.py +120 -0
- core/logger/_levels.py +112 -0
- core/logger/_multi.py +135 -0
- core/logger/_null.py +66 -0
- core/logger/console.py +195 -0
- core/logger/formatters/__init__.py +30 -0
- core/logger/formatters/_format_diff_meta.py +117 -0
- core/logger/formatters/_format_diff_object.py +195 -0
- core/logger/formatters/_format_diff_routine.py +208 -0
- core/logger/formatters/_format_diff_table.py +250 -0
- core/logger/formatters/_formatter_impl.py +567 -0
- core/logger/formatters/diff_utils.py +396 -0
- core/logger/formatters/factory.py +104 -0
- core/logger/formatters/formatter.py +12 -0
- core/logger/formatters/htmlformatter.py +876 -0
- core/logger/formatters/jsonformatter.py +978 -0
- core/logger/log.py +782 -0
- core/logger/results.py +904 -0
- core/migration/__init__.py +33 -0
- core/migration/_type_match.py +111 -0
- core/migration/clean_summary.py +86 -0
- core/migration/commands/__init__.py +6 -0
- core/migration/commands/_script_events.py +18 -0
- core/migration/commands/base_command.py +1025 -0
- core/migration/commands/baseline_command.py +96 -0
- core/migration/commands/clean_command.py +342 -0
- core/migration/commands/import_flyway_command.py +190 -0
- core/migration/commands/info_command.py +208 -0
- core/migration/commands/migrate_command.py +853 -0
- core/migration/commands/repair_command.py +769 -0
- core/migration/commands/undo_command.py +869 -0
- core/migration/commands/validate_command.py +90 -0
- core/migration/encoding.py +90 -0
- core/migration/executor/__init__.py +12 -0
- core/migration/executor/execution_engine.py +1196 -0
- core/migration/executor/migration_executor.py +439 -0
- core/migration/executor/migration_helpers.py +142 -0
- core/migration/executor/placeholder_manager.py +77 -0
- core/migration/executor/transaction_policy.py +63 -0
- core/migration/executors/__init__.py +20 -0
- core/migration/executors/base_executor.py +179 -0
- core/migration/executors/executor_factory.py +251 -0
- core/migration/executors/python_executor.py +379 -0
- core/migration/executors/sql_executor.py +273 -0
- core/migration/formats/__init__.py +14 -0
- core/migration/formats/format_detector.py +204 -0
- core/migration/formats/migration_format.py +67 -0
- core/migration/history/__init__.py +1 -0
- core/migration/history/migration_history_manager.py +384 -0
- core/migration/journals/__init__.py +14 -0
- core/migration/journals/migration_journal.py +618 -0
- core/migration/migration.py +775 -0
- core/migration/placeholders/__init__.py +1 -0
- core/migration/placeholders/placeholder_service.py +84 -0
- core/migration/rules/__init__.py +1 -0
- core/migration/rules/migration_rules.py +145 -0
- core/migration/scripting/__init__.py +1 -0
- core/migration/scripting/migration_script_manager.py +721 -0
- core/migration/scripting/undo_script_generator/__init__.py +24 -0
- core/migration/scripting/undo_script_generator/_ddl_reversers.py +485 -0
- core/migration/scripting/undo_script_generator/_dml_reversers.py +319 -0
- core/migration/scripting/undo_script_generator/_extractors.py +511 -0
- core/migration/scripting/undo_script_generator/_generator.py +310 -0
- core/migration/scripting/undo_script_generator/_helpers.py +532 -0
- core/migration/scripting/undo_script_generator/_models.py +18 -0
- core/migration/scripting/undo_script_generator/_reversers.py +864 -0
- core/migration/sql/__init__.py +1 -0
- core/migration/sql/execution_statement.py +38 -0
- core/migration/sql/sql_analyzer.py +806 -0
- core/migration/sql/sql_execution_service.py +431 -0
- core/migration/sql/statement_splitter.py +74 -0
- core/migration/state/__init__.py +5 -0
- core/migration/state/migration_data_service.py +603 -0
- core/migration/state/migration_display_state.py +36 -0
- core/migration/state/migration_formatter.py +215 -0
- core/migration/state/migration_state.py +166 -0
- core/migration/state/migration_state_manager.py +830 -0
- core/migration/state/migration_state_service.py +244 -0
- core/migration/ui/__init__.py +15 -0
- core/migration/ui/data_collector.py +926 -0
- core/migration/ui/display_formatters.py +251 -0
- core/migration/ui/migration_analyzer.py +259 -0
- core/migration/ui/migration_ui.py +362 -0
- core/migration/ui/table_renderer.py +250 -0
- core/migration/version_utils.py +94 -0
- core/normalization/__init__.py +35 -0
- core/normalization/data_type_normalizer.py +5 -0
- core/normalization/dependency_resolver.py +435 -0
- core/normalization/identifier_normalizer.py +297 -0
- core/normalization/object_orderer.py +230 -0
- core/normalization/type_constants.py +154 -0
- core/normalization/type_mapper.py +220 -0
- core/normalization/type_mappings.py +89 -0
- core/normalization/type_normalizer.py +245 -0
- core/seams/__init__.py +1 -0
- core/seams/event_listeners.py +26 -0
- core/seams/introspection.py +18 -0
- core/sql_model/__init__.py +60 -0
- core/sql_model/_base_parse_result.py +491 -0
- core/sql_model/_base_sql_column.py +194 -0
- core/sql_model/_base_sql_constraint.py +223 -0
- core/sql_model/_base_sql_object.py +233 -0
- core/sql_model/_base_sql_statement.py +100 -0
- core/sql_model/base.py +43 -0
- core/sql_model/constraint_validator.py +469 -0
- core/sql_model/database_link.py +151 -0
- core/sql_model/dialect.py +482 -0
- core/sql_model/event.py +155 -0
- core/sql_model/extension.py +109 -0
- core/sql_model/foreign_data_wrapper.py +113 -0
- core/sql_model/foreign_server.py +138 -0
- core/sql_model/index.py +279 -0
- core/sql_model/linked_server.py +170 -0
- core/sql_model/module.py +112 -0
- core/sql_model/package.py +134 -0
- core/sql_model/partition.py +180 -0
- core/sql_model/procedure.py +321 -0
- core/sql_model/sequence.py +208 -0
- core/sql_model/synonym.py +143 -0
- core/sql_model/table.py +1106 -0
- core/sql_model/table_canonicalizer.py +45 -0
- core/sql_model/table_options.py +73 -0
- core/sql_model/trigger.py +310 -0
- core/sql_model/user_defined_type.py +151 -0
- core/sql_model/view.py +429 -0
- core/sql_model/view_options.py +95 -0
- core/sql_parser/__init__.py +5 -0
- core/sql_parser/_partition_handler.py +113 -0
- core/sql_parser/_sqlglot_builders.py +634 -0
- core/sql_parser/base_statement_parser.py +356 -0
- core/sql_parser/base_tokenizer.py +473 -0
- core/sql_parser/common/__init__.py +1 -0
- core/sql_parser/common/base_parser.py +598 -0
- core/sql_parser/dialects/__init__.py +5 -0
- core/sql_parser/dialects/base_config.py +125 -0
- core/sql_parser/enhanced_regex_parser.py +453 -0
- core/sql_parser/hybrid_parser.py +1106 -0
- core/sql_parser/parser_context.py +89 -0
- core/sql_parser/parser_factory.py +215 -0
- core/sql_parser/parser_interface.py +93 -0
- core/sql_parser/sqlglot_parser.py +544 -0
- core/sql_parser/tokens.py +48 -0
- core/sql_parser/unified_regex_parser.py +644 -0
- core/sql_validator/__init__.py +5 -0
- core/sql_validator/_checksum_validator.py +341 -0
- core/sql_validator/_flyway_compatibility.py +301 -0
- core/sql_validator/_migration_filter.py +221 -0
- core/sql_validator/_sql_syntax_validator.py +168 -0
- core/sql_validator/_strict_mode_validator.py +136 -0
- core/sql_validator/migration_validator.py +923 -0
- core/state/sql_script_formatter.py +144 -0
- core/state/sql_statement.py +41 -0
- core/utils/__init__.py +1 -0
- core/utils/database_url_parser.py +104 -0
- core/utils/metadata_helpers.py +83 -0
- core/utils/row_access.py +192 -0
- core/utils/string_utils.py +21 -0
- core/utils/url_masking.py +24 -0
- db/__init__.py +21 -0
- db/base_provider.py +266 -0
- db/base_quirks.py +1620 -0
- db/constants.py +5 -0
- db/error.py +344 -0
- db/error_handler.py +349 -0
- db/exceptions.py +11 -0
- db/native_connection_manager.py +70 -0
- db/object_naming.py +39 -0
- db/plugins/__init__.py +9 -0
- db/plugins/base_history_manager.py +616 -0
- db/plugins/base_locking_manager.py +154 -0
- db/plugins/base_query_executor.py +253 -0
- db/plugins/base_schema_operations.py +288 -0
- db/plugins/base_snapshot_manager.py +172 -0
- db/plugins/base_undo_manager.py +126 -0
- db/plugins/cosmosdb/__init__.py +12 -0
- db/plugins/cosmosdb/cosmosdb/__init__.py +19 -0
- db/plugins/cosmosdb/cosmosdb/_sdk.py +28 -0
- db/plugins/cosmosdb/cosmosdb/connection_manager.py +247 -0
- db/plugins/cosmosdb/cosmosdb/history_manager.py +427 -0
- db/plugins/cosmosdb/cosmosdb/locking_manager.py +465 -0
- db/plugins/cosmosdb/cosmosdb/query_executor.py +1518 -0
- db/plugins/cosmosdb/cosmosdb/schema_operations.py +416 -0
- db/plugins/cosmosdb/introspection/__init__.py +3 -0
- db/plugins/cosmosdb/parser/__init__.py +5 -0
- db/plugins/cosmosdb/parser/cosmosdb_regex_parser.py +64 -0
- db/plugins/cosmosdb/plugin.py +19 -0
- db/plugins/cosmosdb/provider.py +364 -0
- db/plugins/cosmosdb/quirks.py +240 -0
- db/plugins/cosmosdb/sdk_translator/__init__.py +39 -0
- db/plugins/cosmosdb/sdk_translator/_executor.py +422 -0
- db/plugins/cosmosdb/sdk_translator/_executors.py +356 -0
- db/plugins/cosmosdb/sdk_translator/_parsing.py +53 -0
- db/plugins/cosmosdb/sdk_translator/_script_generation.py +141 -0
- db/plugins/cosmosdb/sdk_translator/_translator.py +129 -0
- db/plugins/cosmosdb/sdk_translator/_translators.py +795 -0
- db/plugins/db2/__init__.py +11 -0
- db/plugins/db2/db2/__init__.py +11 -0
- db/plugins/db2/db2/history_manager.py +317 -0
- db/plugins/db2/db2/locking_manager.py +558 -0
- db/plugins/db2/db2/schema_operations.py +985 -0
- db/plugins/db2/introspection/__init__.py +3 -0
- db/plugins/db2/parser/__init__.py +9 -0
- db/plugins/db2/parser/db2_regex_parser.py +668 -0
- db/plugins/db2/parser/parser_config.py +1147 -0
- db/plugins/db2/plugin.py +22 -0
- db/plugins/db2/provider.py +420 -0
- db/plugins/db2/quirks.py +382 -0
- db/plugins/db2/sqlalchemy_url.py +62 -0
- db/plugins/mariadb/__init__.py +13 -0
- db/plugins/mariadb/plugin.py +21 -0
- db/plugins/mariadb/provider.py +40 -0
- db/plugins/mariadb/quirks.py +50 -0
- db/plugins/mysql/__init__.py +11 -0
- db/plugins/mysql/introspection/__init__.py +1 -0
- db/plugins/mysql/mysql/__init__.py +11 -0
- db/plugins/mysql/mysql/history_manager.py +384 -0
- db/plugins/mysql/mysql/locking_manager.py +371 -0
- db/plugins/mysql/mysql/schema_operations.py +555 -0
- db/plugins/mysql/parser/__init__.py +1 -0
- db/plugins/mysql/parser/mysql_regex_parser.py +531 -0
- db/plugins/mysql/parser/mysql_statement_parser.py +255 -0
- db/plugins/mysql/parser/mysql_tokenizer.py +460 -0
- db/plugins/mysql/parser/parser_config.py +670 -0
- db/plugins/mysql/plugin.py +22 -0
- db/plugins/mysql/provider.py +329 -0
- db/plugins/mysql/quirks.py +540 -0
- db/plugins/mysql/sqlalchemy_url.py +67 -0
- db/plugins/oracle/__init__.py +11 -0
- db/plugins/oracle/introspection/__init__.py +3 -0
- db/plugins/oracle/introspection/oracle_utils.py +86 -0
- db/plugins/oracle/oracle/__init__.py +14 -0
- db/plugins/oracle/oracle/dbms_output.py +49 -0
- db/plugins/oracle/oracle/history_manager.py +444 -0
- db/plugins/oracle/oracle/locking_manager.py +436 -0
- db/plugins/oracle/oracle/schema_operations.py +1134 -0
- db/plugins/oracle/parser/__init__.py +13 -0
- db/plugins/oracle/parser/_comments.py +57 -0
- db/plugins/oracle/parser/_object_extractor.py +203 -0
- db/plugins/oracle/parser/_plsql_block.py +727 -0
- db/plugins/oracle/parser/_sqlplus.py +312 -0
- db/plugins/oracle/parser/_statement_splitter.py +233 -0
- db/plugins/oracle/parser/oracle_parser.py +297 -0
- db/plugins/oracle/parser/oracle_statement_parser.py +434 -0
- db/plugins/oracle/parser/oracle_tokenizer.py +251 -0
- db/plugins/oracle/parser/parser_config.py +330 -0
- db/plugins/oracle/parser/sqlplus_context.py +179 -0
- db/plugins/oracle/plugin.py +22 -0
- db/plugins/oracle/provider.py +736 -0
- db/plugins/oracle/quirks.py +743 -0
- db/plugins/oracle/sqlalchemy_url.py +65 -0
- db/plugins/postgresql/__init__.py +12 -0
- db/plugins/postgresql/_provider_query_executor.py +25 -0
- db/plugins/postgresql/introspection/__init__.py +3 -0
- db/plugins/postgresql/parser/__init__.py +10 -0
- db/plugins/postgresql/parser/parser_config.py +876 -0
- db/plugins/postgresql/parser/postgresql_regex_parser.py +691 -0
- db/plugins/postgresql/parser/postgresql_statement_parser.py +118 -0
- db/plugins/postgresql/parser/postgresql_tokenizer.py +325 -0
- db/plugins/postgresql/plugin.py +29 -0
- db/plugins/postgresql/postgresql/__init__.py +11 -0
- db/plugins/postgresql/postgresql/history_manager.py +231 -0
- db/plugins/postgresql/postgresql/locking_manager.py +310 -0
- db/plugins/postgresql/postgresql/schema_operations.py +690 -0
- db/plugins/postgresql/provider.py +248 -0
- db/plugins/postgresql/quirks.py +722 -0
- db/plugins/postgresql/sqlalchemy_url.py +65 -0
- db/plugins/sqlite/__init__.py +12 -0
- db/plugins/sqlite/introspection/__init__.py +3 -0
- db/plugins/sqlite/parser/__init__.py +9 -0
- db/plugins/sqlite/parser/parser_config.py +377 -0
- db/plugins/sqlite/parser/sqlite_regex_parser.py +338 -0
- db/plugins/sqlite/plugin.py +21 -0
- db/plugins/sqlite/provider.py +504 -0
- db/plugins/sqlite/quirks.py +96 -0
- db/plugins/sqlite/sqlalchemy_url.py +16 -0
- db/plugins/sqlite/sqlite/__init__.py +24 -0
- db/plugins/sqlite/sqlite/connection_manager.py +162 -0
- db/plugins/sqlite/sqlite/history_manager.py +233 -0
- db/plugins/sqlite/sqlite/locking_manager.py +216 -0
- db/plugins/sqlite/sqlite/query_executor.py +243 -0
- db/plugins/sqlite/sqlite/schema_operations.py +303 -0
- db/plugins/sqlserver/__init__.py +11 -0
- db/plugins/sqlserver/introspection/__init__.py +3 -0
- db/plugins/sqlserver/parser/__init__.py +9 -0
- db/plugins/sqlserver/parser/parser_config.py +604 -0
- db/plugins/sqlserver/parser/sqlserver_regex_parser.py +368 -0
- db/plugins/sqlserver/parser/sqlserver_statement_parser.py +173 -0
- db/plugins/sqlserver/parser/sqlserver_tokenizer.py +238 -0
- db/plugins/sqlserver/parser/tsql_batch_separator.py +14 -0
- db/plugins/sqlserver/plugin.py +22 -0
- db/plugins/sqlserver/provider.py +615 -0
- db/plugins/sqlserver/quirks.py +489 -0
- db/plugins/sqlserver/sqlalchemy_url.py +120 -0
- db/plugins/sqlserver/sqlserver/__init__.py +11 -0
- db/plugins/sqlserver/sqlserver/history_manager.py +382 -0
- db/plugins/sqlserver/sqlserver/locking_manager.py +328 -0
- db/plugins/sqlserver/sqlserver/schema_operations.py +524 -0
- db/provider_capabilities.py +85 -0
- db/provider_interfaces.py +347 -0
- db/provider_registry.py +623 -0
- db/sqlalchemy_provider.py +340 -0
- db/value_utils.py +10 -0
- dblift-2.0.1.dist-info/METADATA +1075 -0
- dblift-2.0.1.dist-info/RECORD +408 -0
- dblift-2.0.1.dist-info/WHEEL +5 -0
- dblift-2.0.1.dist-info/entry_points.txt +12 -0
- dblift-2.0.1.dist-info/licenses/LICENSE +201 -0
- dblift-2.0.1.dist-info/top_level.txt +6 -0
- integrations/__init__.py +30 -0
- integrations/django/__init__.py +1 -0
- integrations/django/_client.py +54 -0
- integrations/django/_engine.py +53 -0
- integrations/django/apps.py +14 -0
- integrations/django/checks.py +35 -0
- integrations/django/management/__init__.py +1 -0
- integrations/django/management/commands/__init__.py +1 -0
- integrations/django/management/commands/dblift_info.py +25 -0
- integrations/django/management/commands/dblift_migrate.py +24 -0
- integrations/django/management/commands/dblift_validate.py +24 -0
- integrations/fastapi.py +153 -0
- integrations/flask.py +82 -0
- 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)
|