sqlspec 0.14.1__py3-none-any.whl → 0.16.0__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.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +480 -121
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +18 -65
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +666 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +164 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +18 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
- sqlspec-0.16.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/migrations/__init__.py
CHANGED
|
@@ -5,6 +5,13 @@ and driver architecture for database versioning.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from sqlspec.migrations.commands import AsyncMigrationCommands, MigrationCommands, SyncMigrationCommands
|
|
8
|
+
from sqlspec.migrations.loaders import (
|
|
9
|
+
BaseMigrationLoader,
|
|
10
|
+
MigrationLoadError,
|
|
11
|
+
PythonFileLoader,
|
|
12
|
+
SQLFileLoader,
|
|
13
|
+
get_migration_loader,
|
|
14
|
+
)
|
|
8
15
|
from sqlspec.migrations.runner import AsyncMigrationRunner, SyncMigrationRunner
|
|
9
16
|
from sqlspec.migrations.tracker import AsyncMigrationTracker, SyncMigrationTracker
|
|
10
17
|
from sqlspec.migrations.utils import create_migration_file, drop_all, get_author
|
|
@@ -13,11 +20,16 @@ __all__ = (
|
|
|
13
20
|
"AsyncMigrationCommands",
|
|
14
21
|
"AsyncMigrationRunner",
|
|
15
22
|
"AsyncMigrationTracker",
|
|
23
|
+
"BaseMigrationLoader",
|
|
16
24
|
"MigrationCommands",
|
|
25
|
+
"MigrationLoadError",
|
|
26
|
+
"PythonFileLoader",
|
|
27
|
+
"SQLFileLoader",
|
|
17
28
|
"SyncMigrationCommands",
|
|
18
29
|
"SyncMigrationRunner",
|
|
19
30
|
"SyncMigrationTracker",
|
|
20
31
|
"create_migration_file",
|
|
21
32
|
"drop_all",
|
|
22
33
|
"get_author",
|
|
34
|
+
"get_migration_loader",
|
|
23
35
|
)
|
sqlspec/migrations/base.py
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
This module provides abstract base classes for migration components.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import operator
|
|
6
7
|
from abc import ABC, abstractmethod
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Generic, Optional, TypeVar
|
|
9
10
|
|
|
11
|
+
from sqlspec._sql import sql
|
|
12
|
+
from sqlspec.builder._ddl import CreateTable
|
|
13
|
+
from sqlspec.core.statement import SQL
|
|
10
14
|
from sqlspec.loader import SQLFileLoader
|
|
11
|
-
from sqlspec.
|
|
15
|
+
from sqlspec.migrations.loaders import get_migration_loader
|
|
12
16
|
from sqlspec.utils.logging import get_logger
|
|
17
|
+
from sqlspec.utils.sync_tools import run_
|
|
13
18
|
|
|
14
19
|
__all__ = ("BaseMigrationCommands", "BaseMigrationRunner", "BaseMigrationTracker")
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
logger = get_logger("migrations.base")
|
|
18
23
|
|
|
19
|
-
# Type variables for generic driver and config types
|
|
20
24
|
DriverT = TypeVar("DriverT")
|
|
21
25
|
ConfigT = TypeVar("ConfigT")
|
|
22
26
|
|
|
@@ -38,18 +42,23 @@ class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
|
38
42
|
Returns:
|
|
39
43
|
SQL object for table creation.
|
|
40
44
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
builder = CreateTable(self.version_table)
|
|
46
|
+
if not hasattr(builder, "_columns"):
|
|
47
|
+
builder._columns = []
|
|
48
|
+
if not hasattr(builder, "_constraints"):
|
|
49
|
+
builder._constraints = []
|
|
50
|
+
if not hasattr(builder, "_table_options"):
|
|
51
|
+
builder._table_options = {}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
builder.if_not_exists()
|
|
55
|
+
.column("version_num", "VARCHAR(32)", primary_key=True)
|
|
56
|
+
.column("description", "TEXT")
|
|
57
|
+
.column("applied_at", "TIMESTAMP", not_null=True, default="CURRENT_TIMESTAMP")
|
|
58
|
+
.column("execution_time_ms", "INTEGER")
|
|
59
|
+
.column("checksum", "VARCHAR(64)")
|
|
60
|
+
.column("applied_by", "VARCHAR(255)")
|
|
61
|
+
).to_statement()
|
|
53
62
|
|
|
54
63
|
def _get_current_version_sql(self) -> SQL:
|
|
55
64
|
"""Get SQL for retrieving current version.
|
|
@@ -57,7 +66,10 @@ class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
|
57
66
|
Returns:
|
|
58
67
|
SQL object for version query.
|
|
59
68
|
"""
|
|
60
|
-
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
sql.select("version_num").from_(self.version_table).order_by("version_num DESC").limit(1)
|
|
72
|
+
).to_statement()
|
|
61
73
|
|
|
62
74
|
def _get_applied_migrations_sql(self) -> SQL:
|
|
63
75
|
"""Get SQL for retrieving all applied migrations.
|
|
@@ -65,7 +77,8 @@ class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
|
65
77
|
Returns:
|
|
66
78
|
SQL object for migrations query.
|
|
67
79
|
"""
|
|
68
|
-
|
|
80
|
+
|
|
81
|
+
return (sql.select("*").from_(self.version_table).order_by("version_num")).to_statement()
|
|
69
82
|
|
|
70
83
|
def _get_record_migration_sql(
|
|
71
84
|
self, version: str, description: str, execution_time_ms: int, checksum: str, applied_by: str
|
|
@@ -82,14 +95,12 @@ class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
|
82
95
|
Returns:
|
|
83
96
|
SQL object for insert.
|
|
84
97
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
description,
|
|
89
|
-
execution_time_ms,
|
|
90
|
-
|
|
91
|
-
applied_by,
|
|
92
|
-
)
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
sql.insert(self.version_table)
|
|
101
|
+
.columns("version_num", "description", "execution_time_ms", "checksum", "applied_by")
|
|
102
|
+
.values(version, description, execution_time_ms, checksum, applied_by)
|
|
103
|
+
).to_statement()
|
|
93
104
|
|
|
94
105
|
def _get_remove_migration_sql(self, version: str) -> SQL:
|
|
95
106
|
"""Get SQL for removing a migration record.
|
|
@@ -100,7 +111,8 @@ class BaseMigrationTracker(ABC, Generic[DriverT]):
|
|
|
100
111
|
Returns:
|
|
101
112
|
SQL object for delete.
|
|
102
113
|
"""
|
|
103
|
-
|
|
114
|
+
|
|
115
|
+
return (sql.delete().from_(self.version_table).where(sql.version_num == version)).to_statement()
|
|
104
116
|
|
|
105
117
|
@abstractmethod
|
|
106
118
|
def ensure_tracking_table(self, driver: DriverT) -> Any:
|
|
@@ -141,9 +153,10 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
141
153
|
"""
|
|
142
154
|
self.migrations_path = migrations_path
|
|
143
155
|
self.loader = SQLFileLoader()
|
|
156
|
+
self.project_root: Optional[Path] = None
|
|
144
157
|
|
|
145
158
|
def _extract_version(self, filename: str) -> Optional[str]:
|
|
146
|
-
"""Extract version from filename
|
|
159
|
+
"""Extract version from filename.
|
|
147
160
|
|
|
148
161
|
Args:
|
|
149
162
|
filename: The migration filename.
|
|
@@ -152,9 +165,7 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
152
165
|
The extracted version string or None.
|
|
153
166
|
"""
|
|
154
167
|
parts = filename.split("_", 1)
|
|
155
|
-
if parts and parts[0].isdigit()
|
|
156
|
-
return parts[0].zfill(4)
|
|
157
|
-
return None
|
|
168
|
+
return parts[0].zfill(4) if parts and parts[0].isdigit() else None
|
|
158
169
|
|
|
159
170
|
def _calculate_checksum(self, content: str) -> str:
|
|
160
171
|
"""Calculate MD5 checksum of migration content.
|
|
@@ -163,14 +174,14 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
163
174
|
content: The migration file content.
|
|
164
175
|
|
|
165
176
|
Returns:
|
|
166
|
-
|
|
177
|
+
MD5 checksum hex string.
|
|
167
178
|
"""
|
|
168
179
|
import hashlib
|
|
169
180
|
|
|
170
181
|
return hashlib.md5(content.encode()).hexdigest() # noqa: S324
|
|
171
182
|
|
|
172
183
|
def _get_migration_files_sync(self) -> "list[tuple[str, Path]]":
|
|
173
|
-
"""Get all migration files sorted by version
|
|
184
|
+
"""Get all migration files sorted by version.
|
|
174
185
|
|
|
175
186
|
Returns:
|
|
176
187
|
List of tuples containing (version, file_path).
|
|
@@ -179,14 +190,15 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
179
190
|
return []
|
|
180
191
|
|
|
181
192
|
migrations = []
|
|
182
|
-
for
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
for pattern in ["*.sql", "*.py"]:
|
|
194
|
+
for file_path in self.migrations_path.glob(pattern):
|
|
195
|
+
if file_path.name.startswith("."):
|
|
196
|
+
continue
|
|
197
|
+
version = self._extract_version(file_path.name)
|
|
198
|
+
if version:
|
|
199
|
+
migrations.append((version, file_path))
|
|
188
200
|
|
|
189
|
-
return sorted(migrations, key=
|
|
201
|
+
return sorted(migrations, key=operator.itemgetter(0))
|
|
190
202
|
|
|
191
203
|
def _load_migration_metadata(self, file_path: Path) -> "dict[str, Any]":
|
|
192
204
|
"""Load migration metadata from file.
|
|
@@ -195,32 +207,37 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
195
207
|
file_path: Path to the migration file.
|
|
196
208
|
|
|
197
209
|
Returns:
|
|
198
|
-
|
|
210
|
+
Migration metadata dictionary.
|
|
199
211
|
"""
|
|
200
|
-
self.loader.clear_cache()
|
|
201
|
-
self.loader.load_sql(file_path)
|
|
202
212
|
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
loader = get_migration_loader(file_path, self.migrations_path, self.project_root)
|
|
214
|
+
loader.validate_migration_file(file_path)
|
|
215
|
+
content = file_path.read_text(encoding="utf-8")
|
|
205
216
|
checksum = self._calculate_checksum(content)
|
|
206
|
-
|
|
207
|
-
# Extract metadata
|
|
208
217
|
version = self._extract_version(file_path.name)
|
|
209
218
|
description = file_path.stem.split("_", 1)[1] if "_" in file_path.stem else ""
|
|
210
219
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
has_upgrade, has_downgrade = True, False
|
|
221
|
+
|
|
222
|
+
if file_path.suffix == ".sql":
|
|
223
|
+
up_query, down_query = f"migrate-{version}-up", f"migrate-{version}-down"
|
|
224
|
+
self.loader.clear_cache()
|
|
225
|
+
self.loader.load_sql(file_path)
|
|
226
|
+
has_upgrade, has_downgrade = self.loader.has_query(up_query), self.loader.has_query(down_query)
|
|
227
|
+
else:
|
|
228
|
+
try:
|
|
229
|
+
has_downgrade = bool(run_(loader.get_down_sql)(file_path))
|
|
230
|
+
except Exception:
|
|
231
|
+
has_downgrade = False
|
|
214
232
|
|
|
215
233
|
return {
|
|
216
234
|
"version": version,
|
|
217
235
|
"description": description,
|
|
218
236
|
"file_path": file_path,
|
|
219
237
|
"checksum": checksum,
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"has_downgrade": self.loader.has_query(down_query),
|
|
238
|
+
"has_upgrade": has_upgrade,
|
|
239
|
+
"has_downgrade": has_downgrade,
|
|
240
|
+
"loader": loader,
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
def _get_migration_sql(self, migration: "dict[str, Any]", direction: str) -> Optional[SQL]:
|
|
@@ -233,17 +250,29 @@ class BaseMigrationRunner(ABC, Generic[DriverT]):
|
|
|
233
250
|
Returns:
|
|
234
251
|
SQL object for the migration.
|
|
235
252
|
"""
|
|
236
|
-
|
|
237
|
-
has_key = f"has_{direction}grade"
|
|
238
|
-
|
|
239
|
-
if not migration.get(has_key):
|
|
253
|
+
if not migration.get(f"has_{direction}grade"):
|
|
240
254
|
if direction == "down":
|
|
241
255
|
logger.warning("Migration %s has no downgrade query", migration["version"])
|
|
242
256
|
return None
|
|
243
257
|
msg = f"Migration {migration['version']} has no upgrade query"
|
|
244
258
|
raise ValueError(msg)
|
|
245
259
|
|
|
246
|
-
|
|
260
|
+
file_path, loader = migration["file_path"], migration["loader"]
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
method = loader.get_up_sql if direction == "up" else loader.get_down_sql
|
|
264
|
+
sql_statements = run_(method)(file_path)
|
|
265
|
+
|
|
266
|
+
except Exception as e:
|
|
267
|
+
if direction == "down":
|
|
268
|
+
logger.warning("Failed to load downgrade for migration %s: %s", migration["version"], e)
|
|
269
|
+
return None
|
|
270
|
+
msg = f"Failed to load upgrade for migration {migration['version']}: {e}"
|
|
271
|
+
raise ValueError(msg) from e
|
|
272
|
+
else:
|
|
273
|
+
if sql_statements:
|
|
274
|
+
return SQL(sql_statements[0])
|
|
275
|
+
return None
|
|
247
276
|
|
|
248
277
|
@abstractmethod
|
|
249
278
|
def get_migration_files(self) -> Any:
|
|
@@ -281,20 +310,17 @@ class BaseMigrationCommands(ABC, Generic[ConfigT, DriverT]):
|
|
|
281
310
|
config: The SQLSpec configuration.
|
|
282
311
|
"""
|
|
283
312
|
self.config = config
|
|
284
|
-
|
|
285
|
-
# Get migration settings from config
|
|
286
|
-
migration_config = getattr(self.config, "migration_config", {})
|
|
287
|
-
if migration_config is None:
|
|
288
|
-
migration_config = {}
|
|
313
|
+
migration_config = getattr(self.config, "migration_config", {}) or {}
|
|
289
314
|
|
|
290
315
|
self.version_table = migration_config.get("version_table_name", "sqlspec_migrations")
|
|
291
316
|
self.migrations_path = Path(migration_config.get("script_location", "migrations"))
|
|
317
|
+
self.project_root = Path(migration_config["project_root"]) if "project_root" in migration_config else None
|
|
292
318
|
|
|
293
319
|
def _get_init_readme_content(self) -> str:
|
|
294
|
-
"""Get
|
|
320
|
+
"""Get README content for migration directory initialization.
|
|
295
321
|
|
|
296
322
|
Returns:
|
|
297
|
-
|
|
323
|
+
README markdown content.
|
|
298
324
|
"""
|
|
299
325
|
return """# SQLSpec Migrations
|
|
300
326
|
|
|
@@ -334,7 +360,7 @@ This naming ensures proper sorting and avoids conflicts when loading multiple fi
|
|
|
334
360
|
"""
|
|
335
361
|
|
|
336
362
|
def init_directory(self, directory: str, package: bool = True) -> None:
|
|
337
|
-
"""Initialize migration directory structure
|
|
363
|
+
"""Initialize migration directory structure.
|
|
338
364
|
|
|
339
365
|
Args:
|
|
340
366
|
directory: Directory to initialize migrations in.
|
|
@@ -350,11 +376,9 @@ This naming ensures proper sorting and avoids conflicts when loading multiple fi
|
|
|
350
376
|
if package:
|
|
351
377
|
(migrations_dir / "__init__.py").touch()
|
|
352
378
|
|
|
353
|
-
# Create README
|
|
354
379
|
readme = migrations_dir / "README.md"
|
|
355
380
|
readme.write_text(self._get_init_readme_content())
|
|
356
381
|
|
|
357
|
-
# Create .gitkeep for empty directory
|
|
358
382
|
(migrations_dir / ".gitkeep").touch()
|
|
359
383
|
|
|
360
384
|
console.print(f"[green]Initialized migrations in {directory}[/]")
|
|
@@ -385,6 +409,6 @@ This naming ensures proper sorting and avoids conflicts when loading multiple fi
|
|
|
385
409
|
...
|
|
386
410
|
|
|
387
411
|
@abstractmethod
|
|
388
|
-
def revision(self, message: str) -> Any:
|
|
412
|
+
def revision(self, message: str, file_type: str = "sql") -> Any:
|
|
389
413
|
"""Create a new migration file."""
|
|
390
414
|
...
|